wendkeep 0.80.2 → 0.85.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 (75) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.en.md +28 -11
  3. package/README.md +28 -11
  4. package/docs/en/commands/capabilities.md +82 -0
  5. package/docs/en/commands/getting-started.md +3 -1
  6. package/docs/en/commands/mcp.md +99 -0
  7. package/docs/en/commands/portable.md +88 -0
  8. package/docs/en/commands/sync-protocol.md +58 -0
  9. package/docs/en/commands/tdd.md +96 -0
  10. package/docs/en/commands/verify.md +5 -0
  11. package/docs/pt-BR/commands/capabilities.md +82 -0
  12. package/docs/pt-BR/commands/getting-started.md +3 -2
  13. package/docs/pt-BR/commands/mcp.md +99 -0
  14. package/docs/pt-BR/commands/portable.md +87 -0
  15. package/docs/pt-BR/commands/sync-protocol.md +58 -0
  16. package/docs/pt-BR/commands/tdd.md +96 -0
  17. package/docs/pt-BR/commands/verify.md +5 -0
  18. package/hooks/active-context-store.mjs +2 -0
  19. package/hooks/change-core.mjs +5 -0
  20. package/hooks/project-scope.mjs +2 -1
  21. package/hooks/session-ensure.mjs +23 -7
  22. package/hooks/session-start.mjs +20 -5
  23. package/package.json +3 -3
  24. package/packages/cli/src/index.mjs +42 -2
  25. package/packages/harness/src/sensors-core.mjs +16 -3
  26. package/packages/integrations/src/capabilities.mjs +220 -0
  27. package/packages/integrations/src/index.mjs +1 -0
  28. package/packages/mcp/src/audit.mjs +49 -0
  29. package/packages/mcp/src/cli.mjs +78 -0
  30. package/packages/mcp/src/config.mjs +22 -1
  31. package/packages/mcp/src/effects.mjs +115 -0
  32. package/packages/mcp/src/executor.mjs +354 -0
  33. package/packages/mcp/src/index.mjs +7 -0
  34. package/packages/mcp/src/server.mjs +342 -0
  35. package/packages/mcp/src/stdio.mjs +38 -0
  36. package/packages/mcp/src/sync.mjs +56 -0
  37. package/packages/pi/package.json +2 -1
  38. package/packages/pi/src/index.mjs +29 -0
  39. package/schema/handoff-contract-v1.schema.json +4 -0
  40. package/schema/host-capability-manifest-v1.schema.json +46 -0
  41. package/schema/host-coverage-v1.schema.json +55 -0
  42. package/schema/mcp-effect-manifest-v1.schema.json +36 -0
  43. package/schema/mcp-tool-input-v1.schema.json +32 -0
  44. package/schema/mcp-tool-result-v1.schema.json +22 -0
  45. package/schema/portable-active-work-v1.schema.json +38 -0
  46. package/schema/portable-state-v1.schema.json +36 -0
  47. package/schema/sync-event-v1.schema.json +25 -0
  48. package/schema/sync-private-envelope-v1.schema.json +16 -0
  49. package/schema/sync-state-v1.schema.json +18 -0
  50. package/schema/task-contract-v1.schema.json +2 -0
  51. package/schema/tdd-attestation-v1.schema.json +39 -0
  52. package/schema/wendkeep.evidence-envelope-v2.schema.json +17 -0
  53. package/schema/wendkeep.sensors.schema.json +19 -0
  54. package/src/active-context-runtime.mjs +1 -0
  55. package/src/capabilities.mjs +50 -0
  56. package/src/doctor.mjs +28 -0
  57. package/src/evidence-envelope.mjs +12 -6
  58. package/src/host-capabilities.mjs +34 -0
  59. package/src/init.mjs +3 -3
  60. package/src/mcp.mjs +7 -0
  61. package/src/observer-snapshot.mjs +25 -0
  62. package/src/portable.mjs +558 -0
  63. package/src/skills-seed.mjs +26 -0
  64. package/src/sync-adapters.mjs +188 -0
  65. package/src/sync-outbox.mjs +155 -0
  66. package/src/sync-protocol-cli.mjs +277 -0
  67. package/src/sync-protocol.mjs +368 -0
  68. package/src/sync.mjs +8 -0
  69. package/src/task-contracts.mjs +67 -2
  70. package/src/task.mjs +5 -1
  71. package/src/tdd-attestation-store.mjs +98 -0
  72. package/src/tdd-attestation.mjs +254 -0
  73. package/src/tdd.mjs +198 -0
  74. package/src/vault-readme.mjs +4 -4
  75. package/src/verify.mjs +24 -0
@@ -0,0 +1,96 @@
1
+ # Atestação TDD causal
2
+
3
+ **PT-BR** · [English](../../en/commands/tdd.md)
4
+
5
+ ## Objetivo
6
+
7
+ `wendkeep tdd` registra uma prova auditável de que um teste ligado a tarefa e requisito foi
8
+ observado em RED e depois em GREEN no mesmo projeto, repositório, worktree, work session e change.
9
+ Ele complementa cobertura, mutação, sensores e revisão; não substitui nenhum deles.
10
+
11
+ ## Quando usar
12
+
13
+ Use antes e depois de implementar comportamento testável ligado a uma tarefa `[tdd]`.
14
+
15
+ ## Quando não usar
16
+
17
+ Não use para health checks, testes já verdes ou falhas de ambiente. Use sensores/doctor para
18
+ infraestrutura e um waiver humano apenas quando o comportamento realmente não for testável.
19
+
20
+ ## Pré-requisitos
21
+
22
+ Change ativa, active context causal, tarefa com `[req:ID]`, teste relativo ao projeto e comando
23
+ determinístico que possa ser repetido em RED e GREEN.
24
+
25
+ ## Sintaxe
26
+
27
+ ```bash
28
+ wendkeep tdd red <task-id> --requirement <ID> --test <path> --command "<comando>" --session <id>
29
+ wendkeep tdd green <task-id> --command "<comando>" --session <id>
30
+ wendkeep tdd status <task-id> --session <id> [--json]
31
+ wendkeep tdd waive <task-id> --requirement <ID> --reason "<motivo>" --authority "<humano>" --session <id>
32
+ ```
33
+
34
+ `--test` pode ser repetido. Todos os paths são relativos ao projeto. `--change <slug>`,
35
+ `--project <raiz>`, `--vault <cofre>` e `--json` seguem as convenções dos demais comandos.
36
+
37
+ ## Contrato RED → GREEN
38
+
39
+ - RED válido é uma falha comportamental. Teste já verde, erro de sintaxe/import, módulo ausente,
40
+ configuração inválida ou comando inexistente gera `invalid`.
41
+ - GREEN precisa manter a identidade causal e a branch do RED, executar com sucesso e observar
42
+ uma mudança de produção posterior ao RED. GREEN de outra worktree, tarefa ou requisito não fecha.
43
+ - Mudança de path do teste fica em `review_flags`. Refactor ou commit depois do GREEN torna a prova
44
+ stale até nova execução de `tdd green`; o GREEN anterior permanece em `green_history`.
45
+ - Waiver exige motivo e autoridade humana explícita. Não existe waiver silencioso.
46
+
47
+ O store `08-Mudanças/<slug>/tdd-attestations.json` guarda digests SHA-256, cauda sanitizada e
48
+ limitada a 2.000 caracteres e paths relativos — nunca a saída completa. O Evidence Envelope,
49
+ `verificacao.json`, handoff e Observer expõem a atestação e seu ID ao revisor.
50
+
51
+ ## Gate por perfil
52
+
53
+ - `OFF` e `FLOW`: atestação opcional.
54
+ - `GUIDE`: recomendada para comportamento testável.
55
+ - `GOVERN`: obrigatória quando a tarefa possui `[tdd]`.
56
+ - `ASSURE`: obrigatória para tarefa executável com requisito ou sensor, salvo waiver explícito.
57
+
58
+ Marque a tarefa assim:
59
+
60
+ ```markdown
61
+ - [ ] 1.1 persiste a preferência [req:UI-1] [sensor:tests] [tdd]
62
+ ```
63
+
64
+ Atestação stale/invalid, mutante sobrevivente ou ausência de GREEN/waiver produz
65
+ `TASK_TDD_ATTESTATION_MISSING_OR_INVALID` no Task Contract e bloqueia Execute → Verify.
66
+
67
+ ## Exemplos
68
+
69
+ ```bash
70
+ wendkeep tdd red 1.1 --requirement UI-1 --test tests/ui.test.mjs --command "npm test" --session abc
71
+ wendkeep tdd green 1.1 --command "npm test" --session abc
72
+ wendkeep tdd status 1.1 --session abc --json
73
+ ```
74
+
75
+ ## Resultado esperado
76
+
77
+ Uma entrada causal em `tdd-attestations.json`, referenciada pelo Task Contract e pelas superfícies
78
+ de evidência, com estado atual e histórico de revalidação auditáveis.
79
+
80
+ ## Opções e códigos de saída
81
+
82
+ - `0`: estado observado é válido (`red-observed`, `green-observed` ou `waived`; status verde/waived).
83
+ - `1`: observação executou, mas ficou `invalid`, RED ainda não chegou a GREEN ou status está stale.
84
+ - `2`: uso, contexto, identidade, store ou autoridade de waiver inválidos.
85
+
86
+ ## Erros comuns e diagnóstico
87
+
88
+ - `TDD_RED_ALREADY_GREEN`: escreva primeiro o teste discriminante que falha.
89
+ - `TDD_RED_INFRASTRUCTURE_FAILURE`: corrija import, sintaxe, configuração ou comando e repita RED.
90
+ - `TDD_GREEN_STALE_AFTER_REFACTOR`: repita GREEN no checkout atual.
91
+ - `TDD_IMPLEMENTATION_NOT_AFTER_RED`: a prova não observou diff de produção posterior ao RED.
92
+
93
+ ## Próximos passos
94
+
95
+ Rode [verify](verify.md), faça o passe independente e continue o
96
+ [ciclo de changes](changes-and-verification.md).
@@ -88,6 +88,11 @@ mesmo diretório e rename atômico. No deep, `verificacao.json` e `verdict.json`
88
88
  `evidenceEnvelopeId` e `evidenceBinding` completo; o verificador independente deve preservar ambos
89
89
  no verdict.
90
90
 
91
+ O envelope carrega `tdd_attestations`, e `verificacao.json`, `tddAttestations`. Em GOVERN, uma tarefa marcada
92
+ `[tdd]` precisa de GREEN atual ou waiver explícito; em ASSURE, isso vale para comportamento
93
+ testável. Refactor/commit posterior ou mutante sobrevivente invalida o GREEN no Task Contract.
94
+ Veja [Atestação TDD causal](tdd.md).
95
+
91
96
  Evidência v1 continua legível como `legacy-unbound`, nunca como autoridade equivalente. Rode
92
97
  `wendkeep change status <slug>` para ver `bound`, `stale` ou `context-mismatch`.
93
98
 
@@ -740,6 +740,7 @@ export function setActiveContextChange(vaultBase, identity, slug, options = {})
740
740
  }
741
741
  return mutateActiveContext(vaultBase, identity, (context) => ({
742
742
  ...context,
743
+ ...(identity?.hostCoverage ? { host_coverage: structuredClone(identity.hostCoverage) } : {}),
743
744
  change_slug: normalizedSlug,
744
745
  state: 'active',
745
746
  }), options);
@@ -760,6 +761,7 @@ export function setActiveContextDelivery(vaultBase, identity, deliveryId, option
760
761
  }
761
762
  return mutateActiveContext(vaultBase, identity, (context) => ({
762
763
  ...context,
764
+ ...(identity?.hostCoverage ? { host_coverage: structuredClone(identity.hostCoverage) } : {}),
763
765
  delivery_id: normalizedId,
764
766
  state: 'active',
765
767
  }), options);
@@ -286,6 +286,7 @@ export function parseTasks(md) {
286
286
  const dependencyReG = /\[depends:\s*([\w.-]+)\]/g;
287
287
  const artifactReG = /\[artifact:\s*([\w.-]+)\]/g;
288
288
  const phaseReG = /\[phase:\s*([\w.-]+)\]/g;
289
+ const tddReG = /\[tdd\]/gi;
289
290
  let m;
290
291
  while ((m = re.exec(String(md))) !== null) {
291
292
  let text = m[3].trim();
@@ -294,12 +295,15 @@ export function parseTasks(md) {
294
295
  const dependencies = [...new Set([...text.matchAll(dependencyReG)].map((entry) => entry[1]))];
295
296
  const artifacts = [...new Set([...text.matchAll(artifactReG)].map((entry) => entry[1]))];
296
297
  const phases = [...new Set([...text.matchAll(phaseReG)].map((entry) => entry[1]))];
298
+ const tdd = tddReG.test(text);
299
+ tddReG.lastIndex = 0;
297
300
  const sensor = sensors[0];
298
301
  if (sensors.length) text = text.replace(sensorReG, '');
299
302
  if (reqs.length) text = text.replace(reqReG, '');
300
303
  if (dependencies.length) text = text.replace(dependencyReG, '');
301
304
  if (artifacts.length) text = text.replace(artifactReG, '');
302
305
  if (phases.length) text = text.replace(phaseReG, '');
306
+ if (tdd) text = text.replace(tddReG, '');
303
307
  text = text.replace(/\s+/g, ' ').trim();
304
308
  // `sensor` stays as alias of the first id — older consumers keep working.
305
309
  // `req` stays as alias of the first id — older consumers keep working.
@@ -312,6 +316,7 @@ export function parseTasks(md) {
312
316
  ...(dependencies.length ? { dependencies } : {}),
313
317
  ...(artifacts.length ? { artifacts } : {}),
314
318
  ...(phases.length ? { phase: phases[0] } : {}),
319
+ ...(tdd ? { tdd: true } : {}),
315
320
  });
316
321
  }
317
322
  return tasks;
@@ -1,6 +1,7 @@
1
1
  import { realpathSync } from 'node:fs';
2
2
  import { isAbsolute, resolve } from 'node:path';
3
3
  import { spawnSync } from 'node:child_process';
4
+ import { resolveMcpToolEffect } from '../packages/mcp/src/effects.mjs';
4
5
 
5
6
  const TOOL_CWD_FIELDS = ['cwd', 'workdir', 'work_dir', 'working_directory', 'directory'];
6
7
  const SAFE_GIT_COMMANDS = new Set([
@@ -396,7 +397,7 @@ export function concurrentScopeConflicts(expectedScope, activeSessions = [], cur
396
397
 
397
398
  function toolIsMutable(input) {
398
399
  const name = input?.tool_name || input?.toolName || '';
399
- if (/^mcp__/i.test(name)) return true;
400
+ if (/^mcp__/i.test(name)) return resolveMcpToolEffect(name).effect !== 'read';
400
401
  return MUTABLE_TOOL_NAMES.has(name) || /^mcp__.*(?:write|edit|delete|move|rename|apply)/i.test(name);
401
402
  }
402
403
 
@@ -36,6 +36,7 @@ import { resolveSessionIdentity, sessionWorkSessionPatch } from './session-ident
36
36
  import { readCodexRolloutMeta } from './codex-rollout-meta.mjs';
37
37
  import { mutateSessionNote } from './session-note-io.mjs';
38
38
  import { captureProjectScope, projectScopePatch } from './project-scope.mjs';
39
+ import { buildHostCoverage } from '../src/host-capabilities.mjs';
39
40
 
40
41
  function sessionIdFromInput(input) {
41
42
  return input.session_id || input.sessionId || input.codex_session_id || '';
@@ -162,13 +163,16 @@ function allocateSessionPath(vaultBase, now, summary = 'session') {
162
163
  };
163
164
  }
164
165
 
165
- function buildAdditionalContext({ relPath, startedAt, vaultBase }) {
166
+ function buildAdditionalContext({ relPath, startedAt, vaultBase, hostCoverage = null }) {
166
167
  const controlRel = toVaultRelative(vaultBase, controlPath(vaultBase));
167
168
  return [
168
169
  '<obsidian_session>',
169
170
  `Sessão Obsidian ativa: ${relPath}`,
170
171
  `Controle atualizado: ${controlRel}`,
171
172
  `Início: ${startedAt}`,
173
+ '<host_capability_coverage>',
174
+ `Host: ${hostCoverage?.host_id || 'unknown'}; coverage: ${hostCoverage?.degraded ? 'degraded' : 'complete'}.`,
175
+ '</host_capability_coverage>',
172
176
  '',
173
177
  'Use a sessão ativa como log desta conversa.',
174
178
  'Antes de registrar informações, leia `.brain/CURRENT_SESSION.md` no vault.',
@@ -273,7 +277,7 @@ function findSessionForInput(vaultBase, input, control) {
273
277
  return { sessionId, relPath: '', startedAt: '', fromRegistry: false };
274
278
  }
275
279
 
276
- function activateExistingSession({ vaultBase, relPath, startedAt, sessionId, input, now, identity, scopePatch = {} }) {
280
+ function activateExistingSession({ vaultBase, relPath, startedAt, sessionId, input, now, identity, scopePatch = {}, hostCoverage = null }) {
277
281
  const sessionPath = join(vaultBase, relPath);
278
282
  if (!existsSync(sessionPath)) return false;
279
283
 
@@ -296,6 +300,7 @@ function activateExistingSession({ vaultBase, relPath, startedAt, sessionId, inp
296
300
  transcript_path: identity.transcriptPath,
297
301
  transcript_id: identity.transcriptId,
298
302
  provider: identity.provider,
303
+ host_coverage: hostCoverage,
299
304
  ...workSessionPatch(vaultBase, sessionId, input),
300
305
  ...scopePatch,
301
306
  ...causalTurnPatch(input, now),
@@ -303,7 +308,7 @@ function activateExistingSession({ vaultBase, relPath, startedAt, sessionId, inp
303
308
  return true;
304
309
  }
305
310
 
306
- function createSession({ vaultBase, sessionId, input, now, identity, scopePatch = {} }) {
311
+ function createSession({ vaultBase, sessionId, input, now, identity, scopePatch = {}, hostCoverage = null }) {
307
312
  const summary = sessionSummaryFromInput(input);
308
313
  const { absPath, relPath } = allocateSessionPath(vaultBase, now, summary);
309
314
  const startedAt = formatLocalIso(now);
@@ -325,6 +330,7 @@ function createSession({ vaultBase, sessionId, input, now, identity, scopePatch
325
330
  transcript_path: identity.transcriptPath,
326
331
  transcript_id: identity.transcriptId,
327
332
  provider: identity.provider,
333
+ host_coverage: hostCoverage,
328
334
  ...workSessionPatch(vaultBase, sessionId, input),
329
335
  ...scopePatch,
330
336
  ...causalTurnPatch(input, now),
@@ -332,11 +338,11 @@ function createSession({ vaultBase, sessionId, input, now, identity, scopePatch
332
338
  return { relPath, startedAt };
333
339
  }
334
340
 
335
- function outputActiveContext({ relPath, startedAt, vaultBase, message }) {
341
+ function outputActiveContext({ relPath, startedAt, vaultBase, message, hostCoverage = null }) {
336
342
  writeHookOutput({
337
343
  hookSpecificOutput: {
338
344
  hookEventName: 'UserPromptSubmit',
339
- additionalContext: buildAdditionalContext({ relPath, startedAt, vaultBase }),
345
+ additionalContext: buildAdditionalContext({ relPath, startedAt, vaultBase, hostCoverage }),
340
346
  },
341
347
  systemMessage: message,
342
348
  });
@@ -359,6 +365,11 @@ function main() {
359
365
  return;
360
366
  }
361
367
  const sessionId = identity.canonicalConversationId;
368
+ const hostCoverage = buildHostCoverage({
369
+ hostId: identity.provider,
370
+ hostVersion: input.host_version || input.hostVersion || process.env.WENDKEEP_HOST_VERSION || '',
371
+ observedAt: now.toISOString(),
372
+ });
362
373
 
363
374
  // UserPromptSubmit also fires inside Codex subagents. The child belongs to the parent's
364
375
  // observability graph, but it is not a new main turn: registering it through causalTurnPatch
@@ -369,6 +380,7 @@ function main() {
369
380
  upsertSessionRegistry(vaultBase, sessionId, {
370
381
  transcript_paths: [identity.transcriptPath],
371
382
  provider: identity.provider,
383
+ host_coverage: hostCoverage,
372
384
  ...workSessionPatch(vaultBase, sessionId, input),
373
385
  });
374
386
  }
@@ -407,6 +419,7 @@ function main() {
407
419
  transcript_path: identity.transcriptPath,
408
420
  transcript_id: identity.transcriptId,
409
421
  provider: identity.provider,
422
+ host_coverage: hostCoverage,
410
423
  ...workSessionPatch(vaultBase, sessionId, input),
411
424
  ...scopePatch,
412
425
  ...causalTurnPatch(input, now),
@@ -453,6 +466,7 @@ function main() {
453
466
  transcript_path: identity.transcriptPath,
454
467
  transcript_id: identity.transcriptId,
455
468
  provider: identity.provider,
469
+ host_coverage: hostCoverage,
456
470
  ...workSessionPatch(vaultBase, sessionId || control.session_id, input),
457
471
  ...scopePatch,
458
472
  ...causalTurnPatch(input, now),
@@ -466,22 +480,24 @@ function main() {
466
480
  const resolvedTarget = registered?.session_file
467
481
  ? { sessionId, relPath: registered.session_file, startedAt: registered.started_at || '' }
468
482
  : { sessionId, relPath: '', startedAt: '' };
469
- if (resolvedTarget.relPath && activateExistingSession({ vaultBase, relPath: resolvedTarget.relPath, startedAt: resolvedTarget.startedAt, sessionId, input, now, identity, scopePatch })) {
483
+ if (resolvedTarget.relPath && activateExistingSession({ vaultBase, relPath: resolvedTarget.relPath, startedAt: resolvedTarget.startedAt, sessionId, input, now, identity, scopePatch, hostCoverage })) {
470
484
  outputActiveContext({
471
485
  relPath: resolvedTarget.relPath,
472
486
  startedAt: resolvedTarget.startedAt || formatLocalIso(now),
473
487
  vaultBase,
474
488
  message: `Sessão Obsidian reaberta em ${resolvedTarget.relPath}. ${basename(controlPath(vaultBase))} atualizado.`,
489
+ hostCoverage,
475
490
  });
476
491
  return;
477
492
  }
478
493
 
479
- const created = createSession({ vaultBase, sessionId, input, now, identity, scopePatch });
494
+ const created = createSession({ vaultBase, sessionId, input, now, identity, scopePatch, hostCoverage });
480
495
  outputActiveContext({
481
496
  relPath: created.relPath,
482
497
  startedAt: created.startedAt,
483
498
  vaultBase,
484
499
  message: `Sessão Obsidian criada em ${created.relPath}. ${basename(controlPath(vaultBase))} atualizado.`,
500
+ hostCoverage,
485
501
  });
486
502
  }
487
503
 
@@ -34,6 +34,7 @@ import {
34
34
  } from './obsidian-common.mjs';
35
35
  import { resolveSessionIdentity, sessionWorkSessionPatch } from './session-identity.mjs';
36
36
  import { captureProjectScope, projectScopePatch } from './project-scope.mjs';
37
+ import { buildHostCoverage } from '../src/host-capabilities.mjs';
37
38
 
38
39
  export function buildSessionContent({ relPath, now, summary = 'session', provider: providerId, sessionId = '' }) {
39
40
  const date = formatDate(now);
@@ -133,13 +134,21 @@ export function allocateSessionPath(vaultBase, now, summary = 'session') {
133
134
  };
134
135
  }
135
136
 
136
- function buildAdditionalContext({ relPath, startedAt, vaultBase }) {
137
+ function buildAdditionalContext({ relPath, startedAt, vaultBase, hostCoverage = null }) {
137
138
  const controlRel = toVaultRelative(vaultBase, controlPath(vaultBase));
139
+ const degradation = (hostCoverage?.degradations || [])
140
+ .filter((item) => item.capability)
141
+ .map((item) => `${item.capability}=${item.state}`)
142
+ .join(', ');
138
143
  return [
139
144
  '<obsidian_session>',
140
145
  `Sessão Obsidian ativa: ${relPath}`,
141
146
  `Controle atualizado: ${controlRel}`,
142
147
  `Início: ${startedAt}`,
148
+ '<host_capability_coverage>',
149
+ `Host: ${hostCoverage?.host_id || 'unknown'}; coverage: ${hostCoverage?.degraded ? 'degraded' : 'complete'}.`,
150
+ ...(degradation ? [`Lacunas declaradas: ${degradation}. Não fabrique autoridade ausente.`] : []),
151
+ '</host_capability_coverage>',
143
152
  '',
144
153
  'Use a sessão ativa como log desta conversa.',
145
154
  'Antes de registrar informações, leia `.brain/CURRENT_SESSION.md` no vault.',
@@ -159,6 +168,11 @@ function main() {
159
168
  warnIfDefaultVault(input);
160
169
  const now = new Date();
161
170
  const provider = providerMeta();
171
+ const hostCoverage = buildHostCoverage({
172
+ hostId: provider.id,
173
+ hostVersion: input.host_version || input.hostVersion || process.env.WENDKEEP_HOST_VERSION || '',
174
+ observedAt: now.toISOString(),
175
+ });
162
176
  const identity = resolveSessionIdentity(vaultBase, input, provider.id);
163
177
  if (identity.state !== 'resolved') {
164
178
  writeHookOutput({
@@ -194,6 +208,7 @@ function main() {
194
208
  const activationStartedAt = formatLocalIso(now);
195
209
  const registerActivation = (canonicalSessionId, patch) => upsertSessionRegistry(vaultBase, canonicalSessionId, {
196
210
  ...patch,
211
+ host_coverage: hostCoverage,
197
212
  ...workSessionPatchFor(canonicalSessionId),
198
213
  ...scopePatchFor(canonicalSessionId),
199
214
  activation_id: activationId,
@@ -232,7 +247,7 @@ function main() {
232
247
  additionalContext: buildAdditionalContext({
233
248
  relPath: control.session_file,
234
249
  startedAt: control.started_at,
235
- vaultBase,
250
+ vaultBase, hostCoverage,
236
251
  }),
237
252
  },
238
253
  });
@@ -275,7 +290,7 @@ function main() {
275
290
  writeHookOutput({
276
291
  hookSpecificOutput: {
277
292
  hookEventName: 'SessionStart',
278
- additionalContext: buildAdditionalContext({ relPath: known.session_file, startedAt, vaultBase }),
293
+ additionalContext: buildAdditionalContext({ relPath: known.session_file, startedAt, vaultBase, hostCoverage }),
279
294
  },
280
295
  });
281
296
  return;
@@ -318,7 +333,7 @@ function main() {
318
333
  writeHookOutput({
319
334
  hookSpecificOutput: {
320
335
  hookEventName: 'SessionStart',
321
- additionalContext: buildAdditionalContext({ relPath: match.session_file, startedAt, vaultBase }),
336
+ additionalContext: buildAdditionalContext({ relPath: match.session_file, startedAt, vaultBase, hostCoverage }),
322
337
  },
323
338
  });
324
339
  return;
@@ -349,7 +364,7 @@ function main() {
349
364
  writeHookOutput({
350
365
  hookSpecificOutput: {
351
366
  hookEventName: 'SessionStart',
352
- additionalContext: buildAdditionalContext({ relPath, startedAt, vaultBase }),
367
+ additionalContext: buildAdditionalContext({ relPath, startedAt, vaultBase, hostCoverage }),
353
368
  },
354
369
  systemMessage: [
355
370
  `Sessão ${provider.label} criada em ${relPath}.`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.80.2",
3
+ "version": "0.85.0",
4
4
  "description": "Vault-first persistent memory for AI coding agents, with an optional profile-aware governance runtime: OFF, FLOW, GUIDE, GOVERN, or ASSURE. Local-first and agent-agnostic (Claude Code, Codex, Cursor…).",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -41,8 +41,8 @@
41
41
  "node": ">=18"
42
42
  },
43
43
  "scripts": {
44
- "precheck": "node --check src/task-contracts.mjs && node --check src/task-leases.mjs && node --check src/task.mjs && node --check src/change.mjs && node --check src/archive-operation-lock.mjs && node --check src/worktree.mjs && node --check src/worktree-cleanup.mjs && node --check src/provenance-gate.mjs && node --check src/provenance-sources.mjs && node --check src/receipt-ledger.mjs && node --check src/evidence-envelope.mjs && node --check src/context.mjs && node --check src/active-context-health.mjs && node --check src/active-context-runtime.mjs && node --check hooks/active-context-store.mjs && node --check hooks/change-core.mjs && node --check hooks/brain-inject.mjs && node --check hooks/change-context.mjs && node --check hooks/session-stop.mjs && node --check packages/vault/src/worktree-metadata.mjs && node --check packages/vault/src/evidence-envelope.mjs && node --check packages/vault/src/memory-handoff.mjs",
45
- "check": "node --check scripts/release.mjs && node --check scripts/release-plan.mjs && node --check scripts/release-provenance.mjs && node --check scripts/run-scope.mjs && node --check src/release-provenance.mjs && node --check bin/wendkeep.mjs && node --check packages/cli/src/index.mjs && node --check src/init.mjs && node --check src/doctor.mjs && node --check src/active-context-health.mjs && node --check src/project-vault.mjs && node --check src/observer-auth.mjs && node --check src/observer-privacy.mjs && node --check src/observer-snapshot.mjs && node --check src/observer-store.mjs && node --check src/observer-memory.mjs && node --check src/observer-memory-publish.mjs && node --check src/observer-sql-store.mjs && node --check src/observer-sql-migrate.mjs && node --check src/observer-sql-publish.mjs && node --check src/observer-transcript-store.mjs && node --check src/observer-server.mjs && node --check src/observer.mjs && node --check src/observer-publish.mjs && node --check src/operating-profile.mjs && node --check src/profile.mjs && node --check src/flow.mjs && node --check src/work-kind.mjs && node --check src/delivery.mjs && node --check web/observer/app.mjs && node --check hooks/observer-publish.mjs && node --check hooks/evidence-context.mjs && node --check hooks/active-context-handoff-evidence.mjs && node --check hooks/evidence-recall.mjs && node --check hooks/memory-scope.mjs && node --check hooks/operating-profile-runtime.mjs && node --check hooks/operating-profile-task-store.mjs && node --check hooks/flow-core.mjs && node --check hooks/flow-protected-policy.mjs && node --check hooks/git-snapshot.mjs && node --check hooks/vault-path-safety.mjs && node --check hooks/vault-runtime-store.mjs && node --check packages/harness/src/index.mjs && node --check packages/harness/src/flow-store.mjs && node --check packages/harness/src/operating-profile.mjs && node --check packages/harness/src/sensors-core.mjs && node --check packages/integrations/src/host-hooks.mjs && node --check packages/integrations/src/hook-envelope.mjs && node --check packages/integrations/src/prompt-content.mjs && node --check packages/integrations/src/transcript-usage.mjs && node --check packages/integrations/src/transcripts.mjs && node --check packages/integrations/src/session-identity.mjs && node --check packages/integrations/src/index.mjs && node --check packages/mcp/src/config.mjs && node --check packages/mcp/src/index.mjs && node --check packages/vault/src/index.mjs && node --check packages/vault/src/project-vault.mjs && node --check packages/vault/src/vault-path-safety.mjs && node --check packages/vault/src/locale.mjs && node --check packages/vault/src/memory-schema.mjs && node --check packages/vault/src/memory-mode.mjs && node --check packages/vault/src/memory-scope.mjs && node --check packages/vault/src/memory-candidate-policy.mjs && node --check packages/vault/src/evidence-recall.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/vault/src/memory-store.mjs && node --check packages/vault/src/validate-core.mjs && node --check packages/vault/src/validate-memory.mjs",
44
+ "precheck": "node --check src/capabilities.mjs && node --check src/host-capabilities.mjs && node --check src/task-contracts.mjs && node --check src/task-leases.mjs && node --check src/task.mjs && node --check src/change.mjs && node --check src/archive-operation-lock.mjs && node --check src/worktree.mjs && node --check src/worktree-cleanup.mjs && node --check src/provenance-gate.mjs && node --check src/provenance-sources.mjs && node --check src/receipt-ledger.mjs && node --check src/evidence-envelope.mjs && node --check src/context.mjs && node --check src/active-context-health.mjs && node --check src/active-context-runtime.mjs && node --check hooks/active-context-store.mjs && node --check hooks/change-core.mjs && node --check hooks/brain-inject.mjs && node --check hooks/change-context.mjs && node --check hooks/session-stop.mjs && node --check packages/vault/src/worktree-metadata.mjs && node --check packages/vault/src/evidence-envelope.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/integrations/src/capabilities.mjs && node --check packages/pi/src/index.mjs && node --check src/sync-protocol.mjs && node --check src/sync-outbox.mjs && node --check src/sync-adapters.mjs && node --check src/sync-protocol-cli.mjs && node --check packages/mcp/src/sync.mjs && node --check src/portable.mjs && node --check src/tdd.mjs && node --check src/tdd-attestation.mjs && node --check src/tdd-attestation-store.mjs",
45
+ "check": "node --check scripts/release.mjs && node --check scripts/release-plan.mjs && node --check scripts/release-provenance.mjs && node --check scripts/run-scope.mjs && node --check src/release-provenance.mjs && node --check bin/wendkeep.mjs && node --check packages/cli/src/index.mjs && node --check src/mcp.mjs && node --check src/init.mjs && node --check src/doctor.mjs && node --check src/active-context-health.mjs && node --check src/project-vault.mjs && node --check src/observer-auth.mjs && node --check src/observer-privacy.mjs && node --check src/observer-snapshot.mjs && node --check src/observer-store.mjs && node --check src/observer-memory.mjs && node --check src/observer-memory-publish.mjs && node --check src/observer-sql-store.mjs && node --check src/observer-sql-migrate.mjs && node --check src/observer-sql-publish.mjs && node --check src/observer-transcript-store.mjs && node --check src/observer-server.mjs && node --check src/observer.mjs && node --check src/observer-publish.mjs && node --check src/operating-profile.mjs && node --check src/profile.mjs && node --check src/flow.mjs && node --check src/work-kind.mjs && node --check src/delivery.mjs && node --check web/observer/app.mjs && node --check hooks/observer-publish.mjs && node --check hooks/evidence-context.mjs && node --check hooks/active-context-handoff-evidence.mjs && node --check hooks/evidence-recall.mjs && node --check hooks/memory-scope.mjs && node --check hooks/operating-profile-runtime.mjs && node --check hooks/operating-profile-task-store.mjs && node --check hooks/flow-core.mjs && node --check hooks/flow-protected-policy.mjs && node --check hooks/git-snapshot.mjs && node --check hooks/vault-path-safety.mjs && node --check hooks/vault-runtime-store.mjs && node --check packages/harness/src/index.mjs && node --check packages/harness/src/flow-store.mjs && node --check packages/harness/src/operating-profile.mjs && node --check packages/harness/src/sensors-core.mjs && node --check packages/integrations/src/host-hooks.mjs && node --check packages/integrations/src/hook-envelope.mjs && node --check packages/integrations/src/prompt-content.mjs && node --check packages/integrations/src/transcript-usage.mjs && node --check packages/integrations/src/transcripts.mjs && node --check packages/integrations/src/session-identity.mjs && node --check packages/integrations/src/index.mjs && node --check packages/mcp/src/audit.mjs && node --check packages/mcp/src/cli.mjs && node --check packages/mcp/src/config.mjs && node --check packages/mcp/src/effects.mjs && node --check packages/mcp/src/executor.mjs && node --check packages/mcp/src/server.mjs && node --check packages/mcp/src/stdio.mjs && node --check packages/mcp/src/index.mjs && node --check packages/vault/src/index.mjs && node --check packages/vault/src/project-vault.mjs && node --check packages/vault/src/vault-path-safety.mjs && node --check packages/vault/src/locale.mjs && node --check packages/vault/src/memory-schema.mjs && node --check packages/vault/src/memory-mode.mjs && node --check packages/vault/src/memory-scope.mjs && node --check packages/vault/src/memory-candidate-policy.mjs && node --check packages/vault/src/evidence-recall.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/vault/src/memory-store.mjs && node --check packages/vault/src/validate-core.mjs && node --check packages/vault/src/validate-memory.mjs",
46
46
  "test": "node --test --test-concurrency=2",
47
47
  "test:core": "node scripts/run-scope.mjs core",
48
48
  "release": "node scripts/release.mjs",
@@ -28,7 +28,7 @@ Usage:
28
28
  --vault <path> Obsidian vault folder (default: <project>/.<project-name>-vault).
29
29
  --project <path> Project root to wire (default: current directory).
30
30
  --profile <name> Operating profile: OFF, FLOW, GUIDE, GOVERN (default), or ASSURE.
31
- --no-mcp Do not add the mcpvault MCP server to .mcp.json.
31
+ --no-mcp Do not add the native WendKeep MCP server to .mcp.json.
32
32
  --companions <csv> Companion plugins/MCP to pin: context-mode,caveman,understand-anything
33
33
  (default: none — opt in explicitly). dotcontext is legacy — the native a2 loop replaces it.
34
34
  --no-companions Skip companion plugins/MCP entirely.
@@ -49,8 +49,16 @@ Usage:
49
49
  package first (npm i -D wendkeep@latest); a running process
50
50
  cannot replace itself. · --vault P · --profile <name> · --yes
51
51
  · --vscode-worktree-tasks.
52
+ wendkeep sync status [...] Optional local-first protocol: status | push | pull | conflicts | resolve.
53
+ Uses explicit CAS/conflicts and an offline outbox; --remote path or --url HTTPS.
52
54
 
53
55
  wendkeep doctor [--vault P] Health check. --scope core|runtime · --strict for CI/release.
56
+ wendkeep portable <sub> Shared authored state: status | export | import | diff.
57
+ Default file: .wendkeep/portable/state.json; private runtime stays local.
58
+ wendkeep mcp <serve|config> Native semantic MCP over stdio, or client config generation.
59
+ serve: --vault P · --timeout-ms N.
60
+ config: --client generic|claude|codex|cursor · --vault P.
61
+ wendkeep capabilities [...] Show declared lifecycle/effect coverage by host. --host <id> · --host-version <v> · --json.
54
62
  wendkeep observer <sub> Local multi-project Observer: serve | register | publish | reconcile | status.
55
63
  wendkeep worktree create <slug> [--base ref] [--branch name] [--open vscode|none] [--json]
56
64
  wendkeep worktree list [--json]
@@ -66,6 +74,8 @@ Usage:
66
74
  Repair revalidates orphan/removed contexts or expired request leases without deleting history.
67
75
  wendkeep task <sub> Typed task contracts: list | show | evaluate | claim | release.
68
76
  Resolves the change from the causal active context; supports --session/--change/--json.
77
+ wendkeep tdd <sub> Causal TDD attestations: red | green | status | waive.
78
+ Binds task, requirement, test paths, worktree and work session.
69
79
  wendkeep change <sub> Change lifecycle: new [--simple|--guide] | use | bind <slug> --session <id> | continue | list | show |
70
80
  status | done <id> | undone <id> | diff | archive [--force] | abandon | relink | backlink.
71
81
  --session <id> selects the causal active_context for implicit change operations.
@@ -213,6 +223,15 @@ async function main(argv) {
213
223
  } else if (cmd === 'context') {
214
224
  const { CONTEXT_HELP } = await import('../../../src/context.mjs');
215
225
  process.stdout.write(CONTEXT_HELP);
226
+ } else if (cmd === 'portable') {
227
+ const { PORTABLE_HELP } = await import('../../../src/portable.mjs');
228
+ process.stdout.write(PORTABLE_HELP);
229
+ } else if (cmd === 'mcp') {
230
+ const { MCP_HELP } = await import('../../../src/mcp.mjs');
231
+ process.stdout.write(MCP_HELP);
232
+ } else if (cmd === 'capabilities') {
233
+ const { CAPABILITIES_HELP } = await import('../../../src/capabilities.mjs');
234
+ process.stdout.write(CAPABILITIES_HELP);
216
235
  } else {
217
236
  process.stdout.write(HELP);
218
237
  }
@@ -226,7 +245,7 @@ async function main(argv) {
226
245
  // `sync` starts with `init` and resolves the freshly bound Vault itself. Pre-resolving
227
246
  // here would prevent that repair step from reporting a corrupt binding as its own
228
247
  // first-stage failure (and could never make it as far as the guarded init).
229
- && !['init', 'sync', 'worktree', 'hook', 'observer', '--version', '-v', '--help', '-h', 'help'].includes(cmd)) {
248
+ && !['init', 'sync', 'worktree', 'hook', 'observer', 'mcp', 'capabilities', '--version', '-v', '--help', '-h', 'help'].includes(cmd)) {
230
249
  await preferProjectVault(rest);
231
250
  }
232
251
  switch (cmd) {
@@ -243,12 +262,28 @@ async function main(argv) {
243
262
  process.exit(runDoctor(rest));
244
263
  break;
245
264
  }
265
+ case 'portable': {
266
+ const { runPortable } = await import('../../../src/portable.mjs');
267
+ process.exit(runPortable(rest));
268
+ break;
269
+ }
246
270
  case 'observer': {
247
271
  const { runObserver } = await import('../../../src/observer.mjs');
248
272
  const observerExitCode = await runObserver(rest);
249
273
  if (rest[0] !== 'serve') process.exit(observerExitCode);
250
274
  break;
251
275
  }
276
+ case 'mcp': {
277
+ const { runMcp } = await import('../../../src/mcp.mjs');
278
+ const mcpExitCode = await runMcp(rest);
279
+ if (mcpExitCode) process.exit(mcpExitCode);
280
+ break;
281
+ }
282
+ case 'capabilities': {
283
+ const { runCapabilities } = await import('../../../src/capabilities.mjs');
284
+ process.exit(runCapabilities(rest));
285
+ break;
286
+ }
252
287
  case 'worktree': {
253
288
  const { runWorktree } = await import('../../../src/worktree.mjs');
254
289
  process.exit(await runWorktree(rest));
@@ -299,6 +334,11 @@ async function main(argv) {
299
334
  process.exit(runTask(rest));
300
335
  break;
301
336
  }
337
+ case 'tdd': {
338
+ const { runTdd } = await import('../../../src/tdd.mjs');
339
+ process.exit(runTdd(rest));
340
+ break;
341
+ }
302
342
  case 'session': {
303
343
  const { runSession } = await import('../../../src/session.mjs');
304
344
  runSession(rest);
@@ -73,12 +73,25 @@ export function loadSensors(projectRoot, file = 'wendkeep.sensors.json') {
73
73
  // both into [] made every sensor report "sensor não definido" — a misleading diagnosis.
74
74
  export function loadSensorsDetailed(projectRoot, file = 'wendkeep.sensors.json') {
75
75
  const path = join(projectRoot, file);
76
- if (!existsSync(path)) return { sensors: [], missing: true, error: null, path };
76
+ if (!existsSync(path)) return {
77
+ sensors: [], requiredHostCapabilities: [], hostCapabilityWaivers: [], missing: true, error: null, path,
78
+ };
77
79
  try {
78
80
  const data = JSON.parse(readFileSync(path, 'utf8'));
79
- return { sensors: Array.isArray(data.sensors) ? data.sensors : [], missing: false, error: null, path };
81
+ return {
82
+ sensors: Array.isArray(data.sensors) ? data.sensors : [],
83
+ requiredHostCapabilities: Array.isArray(data.requires_host_capabilities)
84
+ ? data.requires_host_capabilities : [],
85
+ hostCapabilityWaivers: Array.isArray(data.host_capability_waivers)
86
+ ? data.host_capability_waivers : [],
87
+ missing: false,
88
+ error: null,
89
+ path,
90
+ };
80
91
  } catch (e) {
81
- return { sensors: [], missing: false, error: e.message, path };
92
+ return {
93
+ sensors: [], requiredHostCapabilities: [], hostCapabilityWaivers: [], missing: false, error: e.message, path,
94
+ };
82
95
  }
83
96
  }
84
97