wendkeep 0.72.1 → 0.74.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.en.md +37 -16
  3. package/README.md +37 -16
  4. package/docs/en/commands/changes-and-verification.md +10 -5
  5. package/docs/en/commands/maintenance-and-diagnostics.md +17 -9
  6. package/docs/en/commands/memory.md +16 -1
  7. package/docs/en/commands/observer.md +8 -1
  8. package/docs/en/commands/operating-profiles.md +28 -3
  9. package/docs/pt-BR/commands/changes-and-verification.md +10 -5
  10. package/docs/pt-BR/commands/maintenance-and-diagnostics.md +12 -5
  11. package/docs/pt-BR/commands/memory.md +16 -1
  12. package/docs/pt-BR/commands/observer.md +7 -1
  13. package/docs/pt-BR/commands/operating-profiles.md +28 -3
  14. package/hooks/brain-core.mjs +2 -0
  15. package/hooks/brain-inject.mjs +6 -6
  16. package/hooks/brain-recall.mjs +5 -1
  17. package/hooks/change-context.mjs +11 -0
  18. package/hooks/change-core.mjs +53 -21
  19. package/hooks/change-warn.mjs +2 -0
  20. package/hooks/evidence-context.mjs +41 -0
  21. package/hooks/evidence-recall.mjs +1 -0
  22. package/hooks/harness-doctor.mjs +13 -5
  23. package/hooks/memory-scope.mjs +1 -0
  24. package/hooks/vault-health.mjs +2 -2
  25. package/package.json +2 -2
  26. package/packages/cli/src/index.mjs +13 -3
  27. package/packages/integrations/src/host-hooks.mjs +1 -0
  28. package/packages/vault/src/evidence-recall.mjs +343 -0
  29. package/packages/vault/src/index.mjs +2 -0
  30. package/packages/vault/src/memory-handoff.mjs +58 -3
  31. package/packages/vault/src/memory-schema.mjs +12 -2
  32. package/packages/vault/src/memory-scope.mjs +119 -0
  33. package/packages/vault/src/memory-store.mjs +86 -24
  34. package/schema/observer/004-evidence-recall.sql +25 -0
  35. package/src/change.mjs +10 -4
  36. package/src/delivery.mjs +303 -0
  37. package/src/doctor.mjs +47 -10
  38. package/src/memory.mjs +95 -2
  39. package/src/observer-sql-store.mjs +141 -5
  40. package/src/release-provenance.mjs +47 -0
  41. package/src/skills-seed.mjs +25 -9
  42. package/src/sync-defs.mjs +5 -2
  43. package/src/sync.mjs +2 -2
  44. package/src/taxonomy.mjs +4 -0
  45. package/src/work-kind.mjs +62 -0
@@ -4,9 +4,12 @@ import { createRequire } from 'node:module';
4
4
  import { join, basename, dirname } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { decodeTranscript, encodeTranscript } from './observer-transcript-store.mjs';
7
+ import {
8
+ chunkMarkdownDocument, recallEvidence, recallTerms,
9
+ } from '../packages/vault/src/evidence-recall.mjs';
7
10
 
8
11
  export const OBSERVER_SQL_FILE = 'observer.sqlite';
9
- export const OBSERVER_SQL_SCHEMA_VERSION = 3;
12
+ export const OBSERVER_SQL_SCHEMA_VERSION = 4;
10
13
  export const OBSERVER_EVENT_SCHEMA_VERSION = 1;
11
14
 
12
15
  const SCHEMA_DIR = fileURLToPath(new URL('../schema/observer/', import.meta.url));
@@ -117,12 +120,106 @@ export function migrateObserverDatabase(db) {
117
120
  throw error;
118
121
  }
119
122
  }
123
+ rebuildSqlEvidenceIndex(db, { missingOnly: true });
120
124
  return {
121
125
  version: Number(db.prepare('SELECT COALESCE(MAX(version), 0) AS version FROM schema_migrations').get().version || 0),
122
126
  applied: db.prepare('SELECT version, name, applied_at FROM schema_migrations ORDER BY version').all(),
123
127
  };
124
128
  }
125
129
 
130
+ export function observerFts5Support(db) {
131
+ try {
132
+ db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS evidence_chunks_fts USING fts5(
133
+ chunk_id UNINDEXED,
134
+ project_id UNINDEXED,
135
+ logical_path,
136
+ title,
137
+ heading,
138
+ content,
139
+ tokenize = 'unicode61 remove_diacritics 2'
140
+ )`);
141
+ return { supported: true, engine: 'fts5' };
142
+ } catch (error) {
143
+ return { supported: false, engine: 'lexical-fallback', reason: text(error?.message || error) };
144
+ }
145
+ }
146
+
147
+ function synchronizeSqlFts(db) {
148
+ const support = observerFts5Support(db);
149
+ if (!support.supported) return support;
150
+ const chunks = Number(db.prepare('SELECT COUNT(*) AS count FROM document_chunks').get().count || 0);
151
+ const indexed = Number(db.prepare('SELECT COUNT(*) AS count FROM evidence_chunks_fts').get().count || 0);
152
+ if (chunks === indexed) return support;
153
+ db.exec(`DELETE FROM evidence_chunks_fts;
154
+ INSERT INTO evidence_chunks_fts(chunk_id, project_id, logical_path, title, heading, content)
155
+ SELECT chunk_id, project_id, logical_path, title, heading, content
156
+ FROM document_chunks;`);
157
+ return { ...support, rebuilt: true, chunks };
158
+ }
159
+
160
+ function writeSqlDocumentChunks(db, {
161
+ projectId, logicalPath, content, metadata = {}, entityType = 'document', capturedAt = '',
162
+ } = {}) {
163
+ const chunks = chunkMarkdownDocument({
164
+ projectId,
165
+ logicalPath,
166
+ content,
167
+ metadata: { ...metadata, observed_at: metadata.observed_at || capturedAt },
168
+ entityType,
169
+ });
170
+ db.prepare('DELETE FROM document_chunks WHERE project_id = ? AND logical_path = ?').run(projectId, logicalPath);
171
+ const fts = observerFts5Support(db);
172
+ if (fts.supported) {
173
+ db.prepare('DELETE FROM evidence_chunks_fts WHERE project_id = ? AND logical_path = ?').run(projectId, logicalPath);
174
+ }
175
+ const insert = db.prepare(`INSERT INTO document_chunks(
176
+ chunk_id, project_id, logical_path, title, heading, entity_type, change_slug,
177
+ session_id, work_session_id, authority, observed_at, validity, content_hash, ordinal, content
178
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
179
+ const insertFts = fts.supported
180
+ ? db.prepare('INSERT INTO evidence_chunks_fts(chunk_id, project_id, logical_path, title, heading, content) VALUES (?, ?, ?, ?, ?, ?)')
181
+ : null;
182
+ for (const chunk of chunks) {
183
+ insert.run(
184
+ chunk.chunk_id, chunk.project_id, chunk.logical_path, chunk.title, chunk.heading,
185
+ chunk.entity_type, chunk.change_slug, chunk.session_id, chunk.work_session_id,
186
+ chunk.authority, chunk.observed_at, chunk.validity, chunk.content_hash,
187
+ chunk.ordinal, chunk.content,
188
+ );
189
+ insertFts?.run(chunk.chunk_id, chunk.project_id, chunk.logical_path, chunk.title, chunk.heading, chunk.content);
190
+ }
191
+ return { chunks: chunks.length, fts };
192
+ }
193
+
194
+ export function rebuildSqlEvidenceIndex(db, { missingOnly = false } = {}) {
195
+ const table = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'document_chunks'").get();
196
+ if (!table) return { documents: 0, chunks: 0, fts: { supported: false, engine: 'unavailable' } };
197
+ const documents = db.prepare(`SELECT d.project_id, d.logical_path, d.content, d.metadata_json, d.entity_type, d.captured_at
198
+ FROM documents d
199
+ WHERE d.deleted_at IS NULL
200
+ AND (? = 0 OR NOT EXISTS (
201
+ SELECT 1 FROM document_chunks c
202
+ WHERE c.project_id = d.project_id AND c.logical_path = d.logical_path
203
+ ))
204
+ ORDER BY d.project_id, d.logical_path`).all(missingOnly ? 1 : 0);
205
+ let chunks = 0;
206
+ let fts = observerFts5Support(db);
207
+ for (const document of documents) {
208
+ const result = writeSqlDocumentChunks(db, {
209
+ projectId: document.project_id,
210
+ logicalPath: document.logical_path,
211
+ content: document.content,
212
+ metadata: parseJson(document.metadata_json),
213
+ entityType: document.entity_type,
214
+ capturedAt: document.captured_at,
215
+ });
216
+ chunks += result.chunks;
217
+ fts = result.fts;
218
+ }
219
+ fts = synchronizeSqlFts(db);
220
+ return { documents: documents.length, chunks, fts };
221
+ }
222
+
126
223
  export function ensureObserverDatabase(dataDir) {
127
224
  const db = openObserverDatabase(dataDir);
128
225
  try {
@@ -266,6 +363,14 @@ function applyDocument(db, event) {
266
363
  VALUES (?, ?, ?, ?, 'upsert', ?, ?, ?, ?, ?, ?)
267
364
  `).run(event.event_id, event.project_id, text(p.entity_type || p.entityType, 'memory'), logicalPath, revision, contentHash,
268
365
  text(p.source_session_id || p.sourceSessionId), text(p.source_turn_id || p.sourceTurnId), event.occurred_at, json(p));
366
+ writeSqlDocumentChunks(db, {
367
+ projectId: event.project_id,
368
+ logicalPath,
369
+ content,
370
+ metadata: p.metadata,
371
+ entityType: text(p.entity_type || p.entityType, 'memory'),
372
+ capturedAt: text(p.captured_at || event.occurred_at),
373
+ });
269
374
  return { stale: false };
270
375
  }
271
376
 
@@ -279,6 +384,10 @@ function applyDocumentDelete(db, event) {
279
384
  VALUES (?, ?, ?, ?, 'delete', ?, '', ?, ?, ?, ?)
280
385
  `).run(event.event_id, event.project_id, text(event.payload.entity_type || event.payload.entityType, 'memory'), logicalPath,
281
386
  integer(event.payload.revision, 1), text(event.payload.source_session_id || event.payload.sourceSessionId), text(event.payload.source_turn_id || event.payload.sourceTurnId), event.occurred_at, json(event.payload));
387
+ db.prepare('DELETE FROM document_chunks WHERE project_id = ? AND logical_path = ?').run(event.project_id, logicalPath);
388
+ if (observerFts5Support(db).supported) {
389
+ db.prepare('DELETE FROM evidence_chunks_fts WHERE project_id = ? AND logical_path = ?').run(event.project_id, logicalPath);
390
+ }
282
391
  return { stale: false };
283
392
  }
284
393
 
@@ -542,11 +651,38 @@ export function readSqlTree(db, projectId, prefix = '') {
542
651
  return { schema_version: OBSERVER_SQL_SCHEMA_VERSION, project_id: projectId, prefix, documents: rows };
543
652
  }
544
653
 
545
- export function searchSqlDocuments(db, projectId, query = '') {
654
+ export function searchSqlDocuments(db, projectId, query = '', { forceLexical = false } = {}) {
546
655
  requireProject(db, projectId);
547
- const q = `%${text(query).replace(/[%_]/g, '\\$&')}%`;
548
- const rows = db.prepare(`SELECT logical_path, entity_type, title, content_hash, revision, captured_at, source_session_id, content FROM documents WHERE project_id = ? AND deleted_at IS NULL AND (logical_path LIKE ? ESCAPE '\\' OR title LIKE ? ESCAPE '\\' OR content LIKE ? ESCAPE '\\') ORDER BY captured_at DESC, logical_path LIMIT 100`).all(projectId, q, q, q);
549
- return rows.map((row) => ({ ...row, excerpt: row.content.slice(0, 240) }));
656
+ const terms = recallTerms(query);
657
+ if (!terms.length) return [];
658
+ const fts = forceLexical
659
+ ? { supported: false, engine: 'lexical-fallback' }
660
+ : observerFts5Support(db);
661
+ const columns = `c.chunk_id, c.project_id, c.logical_path, c.title, c.heading,
662
+ c.entity_type, c.change_slug, c.session_id, c.work_session_id, c.authority,
663
+ c.observed_at, c.validity, c.content_hash AS chunk_content_hash, c.ordinal, c.content,
664
+ d.content_hash, d.revision, d.captured_at, d.source_session_id`;
665
+ let rows;
666
+ if (fts.supported) {
667
+ const expression = [...new Set(terms)]
668
+ .map((term) => `"${term.replaceAll('"', '""')}"`)
669
+ .join(' OR ');
670
+ rows = db.prepare(`SELECT ${columns} FROM evidence_chunks_fts f
671
+ JOIN document_chunks c ON c.chunk_id = f.chunk_id
672
+ JOIN documents d ON d.project_id = c.project_id AND d.logical_path = c.logical_path
673
+ WHERE evidence_chunks_fts MATCH ? AND c.project_id = ? AND d.deleted_at IS NULL
674
+ ORDER BY bm25(evidence_chunks_fts, 0, 0, 1.5, 3.0, 2.5, 1.0), c.observed_at DESC
675
+ LIMIT 200`).all(expression, projectId);
676
+ } else {
677
+ // FTS5 may be unavailable in a valid Observer runtime. Rank the complete project corpus
678
+ // instead of truncating by recency before matching, which would make old exact evidence
679
+ // permanently unreachable.
680
+ rows = db.prepare(`SELECT ${columns} FROM document_chunks c
681
+ JOIN documents d ON d.project_id = c.project_id AND d.logical_path = c.logical_path
682
+ WHERE c.project_id = ? AND d.deleted_at IS NULL
683
+ ORDER BY c.observed_at DESC, c.logical_path, c.ordinal`).all(projectId);
684
+ }
685
+ return recallEvidence(rows, query, { topK: 5 });
550
686
  }
551
687
 
552
688
  export function readSqlSync(db, projectId) {
@@ -1,3 +1,8 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { cpSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { basename, join } from 'node:path';
5
+
1
6
  const DEPENDENCY_FIELDS = Object.freeze([
2
7
  'dependencies',
3
8
  'devDependencies',
@@ -5,6 +10,48 @@ const DEPENDENCY_FIELDS = Object.freeze([
5
10
  'peerDependencies',
6
11
  ]);
7
12
 
13
+ export function parsePackIntegrity(raw) {
14
+ const text = String(raw || '');
15
+ const start = text.indexOf('[');
16
+ const end = text.lastIndexOf(']');
17
+ if (start < 0 || end < start) return '';
18
+ try {
19
+ return String(JSON.parse(text.slice(start, end + 1))[0]?.integrity || '');
20
+ } catch {
21
+ return '';
22
+ }
23
+ }
24
+
25
+ export function packIntegrityInIsolatedCopy(root, { execute = execFileSync } = {}) {
26
+ const tempRoot = mkdtempSync(join(tmpdir(), 'wendkeep-release-pack-'));
27
+ const packageRoot = join(tempRoot, 'package');
28
+ const ignored = new Set(['.git', 'node_modules']);
29
+ try {
30
+ const binding = JSON.parse(readFileSync(join(root, '.wendkeep.json'), 'utf8'));
31
+ const vault = String(binding.vault || '');
32
+ if (vault && !vault.includes('/') && !vault.includes('\\')) ignored.add(vault);
33
+ } catch { /* unbound package: nothing else to exclude */ }
34
+ try {
35
+ cpSync(root, packageRoot, {
36
+ recursive: true,
37
+ filter(source) {
38
+ if (source === root) return true;
39
+ const relativeName = basename(source);
40
+ return !ignored.has(relativeName);
41
+ },
42
+ });
43
+ const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
44
+ const raw = execute(command, ['pack', '--dry-run', '--json'], {
45
+ cwd: packageRoot,
46
+ encoding: 'utf8',
47
+ stdio: ['ignore', 'pipe', 'pipe'],
48
+ });
49
+ return parsePackIntegrity(raw);
50
+ } finally {
51
+ rmSync(tempRoot, { recursive: true, force: true });
52
+ }
53
+ }
54
+
8
55
  export function packageHasSelfDependency(pkg = {}) {
9
56
  const name = String(pkg.name || '');
10
57
  if (!name) return false;
@@ -27,10 +27,15 @@ pode persistir \`profile use OFF\` explicitamente.
27
27
 
28
28
  - **FLOW:** ajuste local, reversível e de escopo fechado, sem contrato/spec, segurança,
29
29
  dependência, CI/release ou policy.
30
- - **GUIDE:** mudança compacta de comportamento que precisa de change/spec, sem revisão formal.
30
+ - **GUIDE:** implementação compacta com change, sem spec/design/ADR automáticos quando contract_impact é none.
31
31
  - **GOVERN:** escolha conservadora em caso de dúvida ou risco e para superfícies sensíveis.
32
32
  - **ASSURE:** GOVERN quando confirmação explícita e handoff fazem parte do contrato.
33
33
 
34
+ Classifique também o work kind: inspection, maintenance, implementation, delivery ou recovery.
35
+ Risco operacional e impacto de contrato são independentes. Merge, push, tag e publish de código
36
+ já aprovado usam delivery + ASSURE sem criar outra change; registre com delivery start e conclua
37
+ com delivery finish para gerar receipt.
38
+
34
39
  O harness da LLM faz essa classificação semântica; o Wend Runtime valida e aplica a lease.
35
40
  Se não houver uma sessão causal identificada ou o comando falhar, não fabrique estado: use o
36
41
  perfil efetivo já injetado e trate \`GOVERN\` como fallback conservador quando ele for o padrão.
@@ -39,6 +44,7 @@ perfil efetivo já injetado e trate \`GOVERN\` como fallback conservador quando
39
44
  Antes de editar, leia o **perfil efetivo** injetado pelo WendKeep e siga somente sua rota:
40
45
  - \`OFF\`: não imponha processo Wend; a governança pertence ao **harness nativo da LLM**.
41
46
  - \`FLOW\`: inicie o microcontrato com \`wendkeep flow start\` antes de editar os paths permitidos.
47
+ - \`delivery\`: inicie \`wendkeep delivery start\` antes das operações autorizadas; não crie change.
42
48
  - \`GUIDE\`, \`GOVERN\` ou \`ASSURE\`: não edite código antes de Propose / \`wendkeep change new\`.
43
49
  Este gate nunca transforma \`OFF\` ou \`FLOW\` silenciosamente em \`GOVERN\`.
44
50
  </HARD-GATE>
@@ -47,14 +53,16 @@ Este gate nunca transforma \`OFF\` ou \`FLOW\` silenciosamente em \`GOVERN\`.
47
53
 
48
54
  - **OFF — LLM nativa:** Wend Runtime desligado; esta skill devolve a execução ao harness nativo.
49
55
  - **FLOW — E → V:** \`flow start\` → implementar com wk-tdd → \`flow finish\`; sem change/ADR/verdict.
50
- - **GUIDE — P → E → V:** change compacta, sem revisão formal obrigatória.
56
+ - **GUIDE — P → E → V:** change new --guide, sem design/spec/ADR automáticos quando não há impacto de contrato.
51
57
  - **GOVERN — P → R → E → V:** loop a2 atual, com design/revisão; é o padrão conservador.
52
58
  - **ASSURE — P → R → E → V → C:** GOVERN acrescido de confirmação e handoff explícitos.
53
59
 
54
60
  ## Passos para GUIDE, GOVERN e ASSURE
55
61
 
56
62
  1. **Explore** — entenda o problema antes de propor. Leia o código/contexto relevante.
57
- 2. **Propose** — \`wendkeep change new <slug>\`. Isso cria \`08-Mudanças/<slug>/\` com:
63
+ 2. **Propose** — GUIDE usa \`wendkeep change new <slug> --guide\`; GOVERN/ASSURE usam
64
+ \`wendkeep change new <slug>\`. O GUIDE compacto exige objetivo, critérios de aceite, áreas
65
+ afetadas, testes e resultado. GOVERN/ASSURE criam \`08-Mudanças/<slug>/\` com:
58
66
  - \`proposta.md\` — *por quê* e *o que muda* (o WHAT).
59
67
  - \`design.md\` — a abordagem técnica.
60
68
  - \`tarefas.md\` — a lista de tarefas \`- [ ] N.N descrição\`.
@@ -83,7 +91,7 @@ Este gate nunca transforma \`OFF\` ou \`FLOW\` silenciosamente em \`GOVERN\`.
83
91
  \`[req:]\`) recebe verdict automático — pula este passe.
84
92
  6. **Archive** — \`wendkeep change archive <slug>\`. O *gate* exige sensores verdes **E**
85
93
  \`verdict.json\` cobrindo os \`[req:]\`. Passando, promove os deltas pro \`07-Specs\`,
86
- move a change pro \`_arquivo\` e gera um ADR em \`04-Decisões/\`.
94
+ move a change pro \`_arquivo\`; GUIDE com contract_impact none não gera ADR automático.
87
95
 
88
96
  ## Regras
89
97
 
@@ -296,10 +304,15 @@ persist it explicitly through \`profile use OFF\`.
296
304
 
297
305
  - **FLOW:** a local, reversible, bounded adjustment with no contract/spec, security, dependency,
298
306
  CI/release, or policy impact.
299
- - **GUIDE:** a compact behavior change that needs a change/spec but no formal review.
307
+ - **GUIDE:** compact implementation with a change, but no automatic spec/design/ADR when contract impact is none.
300
308
  - **GOVERN:** the conservative choice when uncertain or risky, and for sensitive surfaces.
301
309
  - **ASSURE:** GOVERN when explicit confirmation and handoff are part of the contract.
302
310
 
311
+ Also classify work kind as inspection, maintenance, implementation, delivery, or recovery.
312
+ Operational risk and contract impact are independent. Merge, push, tag, and publish for
313
+ already-approved code use delivery + ASSURE without another change; use delivery start and
314
+ delivery finish to produce a receipt.
315
+
303
316
  The LLM harness owns semantic classification; Wend Runtime validates and applies the lease. If
304
317
  there is no causally identified session or the command fails, do not fabricate state: use the
305
318
  already injected effective profile, with \`GOVERN\` as the conservative configured fallback.
@@ -308,6 +321,7 @@ already injected effective profile, with \`GOVERN\` as the conservative configur
308
321
  Before editing, read the injected **effective profile** and follow only its route:
309
322
  - \`OFF\`: impose no Wend process; governance belongs to the **native LLM harness**.
310
323
  - \`FLOW\`: start the microcontract with \`wendkeep flow start\` before editing allowed paths.
324
+ - \`delivery\`: run \`wendkeep delivery start\` before authorized operations; do not create a change.
311
325
  - \`GUIDE\`, \`GOVERN\`, or \`ASSURE\`: do not edit code before Propose / \`wendkeep change new\`.
312
326
  This gate never silently turns \`OFF\` or \`FLOW\` into \`GOVERN\`.
313
327
  </HARD-GATE>
@@ -316,15 +330,16 @@ This gate never silently turns \`OFF\` or \`FLOW\` into \`GOVERN\`.
316
330
 
317
331
  - **OFF — native LLM:** Wend Runtime is disabled; this skill returns execution to the native harness.
318
332
  - **FLOW — E → V:** \`flow start\` → implement with wk-tdd → \`flow finish\`; no change/ADR/verdict.
319
- - **GUIDE — P → E → V:** a compact change with no mandatory formal review.
333
+ - **GUIDE — P → E → V:** change new --guide, with no automatic design/spec/ADR when contract impact is none.
320
334
  - **GOVERN — P → R → E → V:** the current a2 loop with design/review; the conservative default.
321
335
  - **ASSURE — P → R → E → V → C:** GOVERN plus explicit confirmation and handoff.
322
336
 
323
337
  ## Steps for GUIDE, GOVERN, and ASSURE
324
338
 
325
339
  1. **Explore** — understand the problem before proposing.
326
- 2. **Propose** — \`wendkeep change new <slug>\` scaffolds \`08-Changes/<slug>/\`
327
- (proposta/design/tarefas + a \`specs/\` delta). The change becomes *current* through global
340
+ 2. **Propose** — GUIDE uses \`wendkeep change new <slug> --guide\`; GOVERN/ASSURE use
341
+ \`wendkeep change new <slug>\`. Compact GUIDE requires objective, acceptance criteria,
342
+ affected areas, tests, and result. The change becomes *current* through global
328
343
  \`.brain/CURRENT_CHANGE.md\`. Multiple changes may stay open; hooks and \`change list/status\`
329
344
  show every pending task, while commands without \`--change\` use only the current change.
330
345
  Before implementation, resolve \`spec_impact\`: \`required\` needs the capability listed in
@@ -341,7 +356,8 @@ This gate never silently turns \`OFF\` or \`FLOW\` into \`GOVERN\`.
341
356
  5. **Verify deep** — the **wk-verify** skill (fresh, author≠verifier) writes \`verdict.json\`.
342
357
  A trivial change (no \`[req:]\`) gets an auto verdict.
343
358
  6. **Archive** — \`wendkeep change archive <slug>\`. The gate needs green sensors AND a
344
- verdict AND no open tasks. It promotes the delta into \`07-Specs\` and mints an ADR.
359
+ verdict AND no open tasks. It promotes the delta into \`07-Specs\`; compact GUIDE does not
360
+ mint an automatic ADR.
345
361
 
346
362
  ## Rules
347
363
  - Multiple changes may stay open. \`CURRENT_CHANGE.md\` marks one current change without hiding
package/src/sync-defs.mjs CHANGED
@@ -66,7 +66,7 @@ The persistent profile is selected explicitly; missing or invalid configuration
66
66
 
67
67
  Before every implementation, the native LLM harness **MUST run the routing gate**:
68
68
  1. Inspect \`wendkeep profile status\`.
69
- 2. Classify the request and choose FLOW, GUIDE, GOVERN, or ASSURE from its scope and risk.
69
+ 2. Classify work kind, contract impact, and operation risk independently; then choose the profile.
70
70
  3. Register the choice with
71
71
  \`wendkeep profile route <FLOW|GUIDE|GOVERN|ASSURE> --session <id> --reason <text>\`.
72
72
  4. Re-check \`wendkeep profile status\` and follow the effective profile before editing.
@@ -82,12 +82,15 @@ fallback when configuration is missing or invalid. The lease expires when the re
82
82
  Route work by the effective profile:
83
83
  - **OFF** — Wend Runtime is disabled and governance belongs to the native LLM harness; Keep Core stays active.
84
84
  - **FLOW** — Execute → Validate through \`wendkeep flow start/finish\`, without creating a change.
85
- - **GUIDE** — Plan → Execute → Validate through a compact change.
85
+ - **GUIDE** — Plan → Execute → Validate through \`change new --guide\`; no automatic spec/design/ADR for contract_impact none.
86
86
  - **GOVERN** — the default a2 loop: \`wendkeep change new <slug>\` → review → implement tasks test-first
87
87
  (tag proof \`[sensor:id]\` and requirement \`[req:ID]\`) → \`wendkeep verify\` →
88
88
  \`wendkeep verify --deep\` + independent read-only verdict → \`wendkeep change archive\`.
89
89
  - **ASSURE** — GOVERN plus explicit confirmation and handoff.
90
90
 
91
+ Delivery of already-approved behavior uses \`wendkeep delivery start/status/finish/abandon\` with
92
+ ASSURE authorization and an append-only receipt. It does not create a new change, spec, or ADR.
93
+
91
94
  Inspect with \`wendkeep profile status\` / \`wendkeep change status\` /
92
95
  \`spec effective --change <slug>\` / \`sensors list\`. Author specs only in
93
96
  \`08-Mudanças/<slug>/specs/\`; \`07-Specs\` is generated and must not be edited directly.
package/src/sync.mjs CHANGED
@@ -81,11 +81,11 @@ export async function runSync(argv) {
81
81
  // código é propagado sem ser tratado como falha da cadeia.
82
82
  step(3, 'doctor');
83
83
  const { runDoctor } = await import('./doctor.mjs');
84
- const doctorCode = runDoctor(['--vault', vaultBase, '--project', projectPath]);
84
+ const doctorCode = runDoctor(['--vault', vaultBase, '--project', projectPath, '--scope', 'core']);
85
85
 
86
86
  // Nunca afirmar "tudo em dia": o doctor sai 0 mesmo tendo listado órfãos, seções
87
87
  // desatualizadas ou modelos sem preço — essas checagens não são fatais. Uma linha final
88
88
  // otimista contradiria o relatório logo acima dela.
89
- process.stdout.write(`\nwendkeep sync: 3 passo(s) concluído(s)${doctorCode ? ' — doctor reportou erros' : ' — veja o relatório do doctor acima'}\n`);
89
+ process.stdout.write(`\nwendkeep sync: ${doctorCode ? 'falhouKeep Core comprometido' : 'concluídoKeep Core saudável'}\n`);
90
90
  return doctorCode;
91
91
  }
package/src/taxonomy.mjs CHANGED
@@ -65,6 +65,8 @@ export const HOOK_FILES = [
65
65
  'subagent-usage.mjs',
66
66
  'pricing.json',
67
67
  'brain-core.mjs',
68
+ 'evidence-recall.mjs',
69
+ 'memory-scope.mjs',
68
70
  'memory-schema.mjs',
69
71
  'memory-store.mjs',
70
72
  'memory-handoff.mjs',
@@ -83,6 +85,7 @@ export const HOOK_FILES = [
83
85
  'harness-doctor.mjs',
84
86
  'lessons-core.mjs',
85
87
  'brain-inject.mjs',
88
+ 'evidence-context.mjs',
86
89
  'brain-recall.mjs',
87
90
  'brain-reindex.mjs',
88
91
  'session-backfill.mjs',
@@ -114,6 +117,7 @@ export const RUNNABLE_HOOKS = [
114
117
  'subagent-stop',
115
118
  'task-log',
116
119
  'brain-inject',
120
+ 'evidence-context',
117
121
  'brain-recall',
118
122
  'brain-reindex',
119
123
  'vault-health',
@@ -0,0 +1,62 @@
1
+ export const WORK_KINDS = Object.freeze([
2
+ 'inspection',
3
+ 'maintenance',
4
+ 'implementation',
5
+ 'delivery',
6
+ 'recovery',
7
+ ]);
8
+
9
+ export const CONTRACT_IMPACTS = Object.freeze(['none', 'internal', 'public']);
10
+
11
+ function normalize(value, values, code, label) {
12
+ const normalized = String(value || '').trim().toLowerCase();
13
+ if (values.includes(normalized)) return normalized;
14
+ const error = new Error(`${label} inválido: ${String(value || '(vazio)')}. Use ${values.join(', ')}.`);
15
+ error.code = code;
16
+ throw error;
17
+ }
18
+
19
+ export function normalizeWorkKind(value) {
20
+ return normalize(value, WORK_KINDS, 'WENDKEEP_WORK_KIND_INVALID', 'work kind');
21
+ }
22
+
23
+ export function normalizeContractImpact(value) {
24
+ return normalize(value, CONTRACT_IMPACTS, 'WENDKEEP_CONTRACT_IMPACT_INVALID', 'contract impact');
25
+ }
26
+
27
+ export function createWorkRoute({
28
+ workKind,
29
+ profile = '',
30
+ contractImpact = 'none',
31
+ operationRisk = [],
32
+ sourceChange = '',
33
+ sourceCommit = '',
34
+ } = {}) {
35
+ const kind = normalizeWorkKind(workKind);
36
+ const impact = normalizeContractImpact(contractImpact);
37
+ const risks = [...new Set((Array.isArray(operationRisk) ? operationRisk : [operationRisk])
38
+ .map((item) => String(item || '').trim()).filter(Boolean))];
39
+ if (kind === 'delivery' && impact !== 'none') {
40
+ const error = new Error('delivery só aceita contract_impact none; alteração de contrato exige implementation.');
41
+ error.code = 'WENDKEEP_DELIVERY_CONTRACT_IMPACT';
42
+ throw error;
43
+ }
44
+ return {
45
+ work_kind: kind,
46
+ profile: String(profile || (kind === 'delivery' ? 'ASSURE' : '')).trim().toUpperCase(),
47
+ contract_impact: impact,
48
+ operation_risk: risks,
49
+ ...(sourceChange ? { source_change: String(sourceChange) } : {}),
50
+ ...(sourceCommit ? { source_commit: String(sourceCommit) } : {}),
51
+ };
52
+ }
53
+
54
+ export function classifyWorkRequest(prompt) {
55
+ const text = String(prompt || '').toLowerCase();
56
+ if (/acompanhar|acompanhe|status|actions|diff|inspecion/.test(text)) return 'inspection';
57
+ if (/token\s+(?:expir|venc)|credencial.*(?:expir|venc)/.test(text)) return 'recovery';
58
+ if (/corrig|corrij|alter|implementar|package\.json.*errad|workflow.*(?:errad|falh)/.test(text)) return 'implementation';
59
+ if (/merge|push|public|publish|tag|release/.test(text)) return 'delivery';
60
+ if (/texto|formata|manuten/.test(text)) return 'maintenance';
61
+ return 'implementation';
62
+ }