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,22 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "wendkeep://schema/mcp-tool-result-v1",
4
+ "title": "WendKeep MCP tool result v1",
5
+ "type": "object",
6
+ "required": ["schema_version"],
7
+ "properties": {
8
+ "schema_version": { "const": 1 },
9
+ "items": { "type": "array" },
10
+ "next_cursor": { "type": "string" },
11
+ "error": {
12
+ "type": "object",
13
+ "additionalProperties": false,
14
+ "required": ["code", "message", "retryable"],
15
+ "properties": {
16
+ "code": { "type": "string", "pattern": "^MCP_[A-Z0-9_]+$" },
17
+ "message": { "type": "string", "maxLength": 500 },
18
+ "retryable": { "type": "boolean" }
19
+ }
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://wendkeep.dev/schema/portable-active-work-v1.schema.json",
4
+ "title": "WendKeep portable active-work v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "schema_version", "active_work_id", "project_id", "repository_id", "change_slug",
9
+ "task_id", "branch", "base_sha", "head_sha", "spec_sha256", "tasks_sha256",
10
+ "status", "completed", "current_action", "next_actions", "blockers", "evidence_refs",
11
+ "updated_at", "revision"
12
+ ],
13
+ "properties": {
14
+ "schema_version": { "const": 1 },
15
+ "active_work_id": { "type": "string", "pattern": "^[a-f0-9]{24}$" },
16
+ "project_id": { "type": "string", "minLength": 1, "maxLength": 160 },
17
+ "repository_id": { "type": "string", "minLength": 1, "maxLength": 160 },
18
+ "change_slug": { "type": "string", "maxLength": 160 },
19
+ "task_id": { "type": "string", "maxLength": 160 },
20
+ "branch": { "type": "string", "maxLength": 240 },
21
+ "base_sha": { "type": "string", "pattern": "^(|[a-f0-9]{40,64})$" },
22
+ "head_sha": { "type": "string", "pattern": "^(|[a-f0-9]{40,64})$" },
23
+ "spec_sha256": { "$ref": "#/$defs/hash" },
24
+ "tasks_sha256": { "$ref": "#/$defs/hash" },
25
+ "status": { "enum": ["in_progress", "completed", "blocked"] },
26
+ "completed": { "$ref": "#/$defs/strings" },
27
+ "current_action": { "type": "object" },
28
+ "next_actions": { "$ref": "#/$defs/strings" },
29
+ "blockers": { "$ref": "#/$defs/strings" },
30
+ "evidence_refs": { "$ref": "#/$defs/strings" },
31
+ "updated_at": { "type": "string", "format": "date-time" },
32
+ "revision": { "type": "integer", "minimum": 0 }
33
+ },
34
+ "$defs": {
35
+ "hash": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
36
+ "strings": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }
37
+ }
38
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://wendkeep.dev/schema/portable-state-v1.schema.json",
4
+ "title": "WendKeep portable authored state v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "kind", "project_id", "repository_id", "authored_sha256", "artifacts", "active_work"],
8
+ "properties": {
9
+ "schema_version": { "const": 1 },
10
+ "kind": { "const": "wendkeep-portable-state" },
11
+ "project_id": { "type": "string", "minLength": 1, "maxLength": 160 },
12
+ "repository_id": { "type": "string", "minLength": 1, "maxLength": 160 },
13
+ "authored_sha256": { "$ref": "#/$defs/hash" },
14
+ "artifacts": {
15
+ "type": "array",
16
+ "items": {
17
+ "type": "object",
18
+ "additionalProperties": false,
19
+ "required": ["path", "category", "content_sha256", "content"],
20
+ "properties": {
21
+ "path": { "type": "string", "pattern": "^(?:\\.brain/CORE\\.md|(?:04-Decisões|04-Decisions|07-Specs|08-Mudanças|08-Changes)/[^.][^\\u0000]*)$" },
22
+ "category": { "enum": ["authored", "derived"] },
23
+ "content_sha256": { "$ref": "#/$defs/hash" },
24
+ "content": { "type": "string" }
25
+ }
26
+ }
27
+ },
28
+ "active_work": {
29
+ "type": "array",
30
+ "items": { "$ref": "portable-active-work-v1.schema.json" }
31
+ }
32
+ },
33
+ "$defs": {
34
+ "hash": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }
35
+ }
36
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://wendkeep.dev/schema/sync-event-v1.schema.json",
4
+ "title": "WendKeep sync event v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "event_id", "project_id", "record_key", "revision", "base_revision", "content_hash", "causal_parent_ids", "actor_id", "device_id", "lease_id", "observed_at", "operation", "privacy", "payload"],
8
+ "properties": {
9
+ "schema_version": { "const": 1 },
10
+ "event_id": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
11
+ "project_id": { "type": "string", "minLength": 1, "maxLength": 160 },
12
+ "record_key": { "type": "string", "minLength": 1, "maxLength": 2048 },
13
+ "revision": { "type": "integer", "minimum": 1 },
14
+ "base_revision": { "type": "integer", "minimum": 0 },
15
+ "content_hash": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
16
+ "causal_parent_ids": { "type": "array", "uniqueItems": true, "items": { "type": "string", "maxLength": 128 } },
17
+ "actor_id": { "type": "string", "minLength": 1, "maxLength": 160 },
18
+ "device_id": { "type": "string", "minLength": 1, "maxLength": 160 },
19
+ "lease_id": { "type": "string", "maxLength": 160 },
20
+ "observed_at": { "type": "string", "format": "date-time" },
21
+ "operation": { "enum": ["put", "tombstone", "resolve"] },
22
+ "privacy": { "enum": ["shared", "private"] },
23
+ "payload": {}
24
+ }
25
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://wendkeep.dev/schema/sync-private-envelope-v1.schema.json",
4
+ "title": "WendKeep private sync envelope v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "algorithm", "key_id", "iv", "ciphertext", "auth_tag"],
8
+ "properties": {
9
+ "schema_version": { "const": 1 },
10
+ "algorithm": { "const": "AES-256-GCM" },
11
+ "key_id": { "type": "string", "minLength": 1, "maxLength": 160 },
12
+ "iv": { "type": "string", "contentEncoding": "base64" },
13
+ "ciphertext": { "type": "string", "contentEncoding": "base64" },
14
+ "auth_tag": { "type": "string", "contentEncoding": "base64" }
15
+ }
16
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://wendkeep.dev/schema/sync-state-v1.schema.json",
4
+ "title": "WendKeep sync state v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "project_id", "records", "conflicts", "pending", "leases", "decisions", "applied_event_ids"],
8
+ "properties": {
9
+ "schema_version": { "const": 1 },
10
+ "project_id": { "type": "string", "minLength": 1, "maxLength": 160 },
11
+ "records": { "type": "object", "additionalProperties": { "type": "object" } },
12
+ "conflicts": { "type": "object", "additionalProperties": { "type": "object" } },
13
+ "pending": { "type": "object", "additionalProperties": { "type": "array", "items": { "$ref": "sync-event-v1.schema.json" } } },
14
+ "leases": { "type": "object", "additionalProperties": { "type": "object" } },
15
+ "decisions": { "type": "array", "items": { "type": "object" } },
16
+ "applied_event_ids": { "type": "array", "uniqueItems": true, "items": { "type": "string", "pattern": "^[a-f0-9]{64}$" } }
17
+ }
18
+ }
@@ -28,6 +28,8 @@
28
28
  "owner": { "type": ["string", "null"] },
29
29
  "work_session_id": { "type": ["string", "null"] },
30
30
  "evidence_envelope_id": { "type": ["string", "null"] },
31
+ "tdd_required": { "type": "boolean" },
32
+ "tdd_attestation_id": { "type": ["string", "null"] },
31
33
  "checked": { "type": "boolean" },
32
34
  "authored_sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
33
35
  "binding": {
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://wendkeep.dev/schema/tdd-attestation-v1.schema.json",
4
+ "title": "WendKeep TDD Attestation v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "schema_version", "attestation_id", "project_id", "repository_id", "worktree_id",
9
+ "work_session_id", "change_slug", "task_id", "requirement_id", "profile", "state",
10
+ "test_paths", "red", "green", "green_history", "waiver", "review_flags", "invalid_reason"
11
+ ],
12
+ "properties": {
13
+ "schema_version": { "const": 1 },
14
+ "attestation_id": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
15
+ "project_id": { "type": "string", "minLength": 1 },
16
+ "repository_id": { "type": "string", "minLength": 1 },
17
+ "worktree_id": { "type": "string", "minLength": 1 },
18
+ "work_session_id": { "type": "string", "minLength": 1 },
19
+ "change_slug": { "type": "string", "minLength": 1 },
20
+ "task_id": { "type": "string", "minLength": 1 },
21
+ "requirement_id": { "type": "string", "minLength": 1 },
22
+ "profile": { "enum": ["OFF", "FLOW", "GUIDE", "GOVERN", "ASSURE"] },
23
+ "state": { "enum": ["red-observed", "green-observed", "invalid", "waived"] },
24
+ "test_paths": { "$ref": "#/$defs/stringArray" },
25
+ "red": { "type": ["object", "null"] },
26
+ "green": { "type": ["object", "null"] },
27
+ "green_history": { "type": "array", "items": { "type": "object" } },
28
+ "waiver": { "type": ["object", "null"] },
29
+ "review_flags": { "$ref": "#/$defs/stringArray" },
30
+ "invalid_reason": { "type": ["string", "null"] }
31
+ },
32
+ "$defs": {
33
+ "stringArray": {
34
+ "type": "array",
35
+ "items": { "type": "string" },
36
+ "uniqueItems": true
37
+ }
38
+ }
39
+ }
@@ -51,11 +51,28 @@
51
51
  "sensors": {
52
52
  "type": "array",
53
53
  "items": { "$ref": "#/$defs/sensor" }
54
+ },
55
+ "host_coverage": { "$ref": "host-coverage-v1.schema.json" },
56
+ "tdd_attestations": {
57
+ "type": "array",
58
+ "items": { "$ref": "#/$defs/tddAttestation" }
54
59
  }
55
60
  },
56
61
  "$defs": {
57
62
  "gitObject": { "type": "string", "pattern": "^[a-f0-9]{40,64}$" },
58
63
  "sha256": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
64
+ "tddAttestation": {
65
+ "type": "object",
66
+ "required": ["schema_version", "attestation_id", "task_id", "requirement_id", "state", "test_paths"],
67
+ "properties": {
68
+ "schema_version": { "const": 1 },
69
+ "attestation_id": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
70
+ "task_id": { "type": "string", "minLength": 1 },
71
+ "requirement_id": { "type": "string", "minLength": 1 },
72
+ "state": { "enum": ["red-observed", "green-observed", "invalid", "waived"] },
73
+ "test_paths": { "type": "array", "items": { "type": "string" } }
74
+ }
75
+ },
59
76
  "sensor": {
60
77
  "type": "object",
61
78
  "required": [
@@ -9,6 +9,25 @@
9
9
  "$schema": { "type": "string" },
10
10
  "version": { "const": 1 },
11
11
  "source": { "type": "string" },
12
+ "requires_host_capabilities": {
13
+ "type": "array",
14
+ "uniqueItems": true,
15
+ "items": { "$ref": "host-capability-manifest-v1.schema.json#/$defs/capabilityName" }
16
+ },
17
+ "host_capability_waivers": {
18
+ "type": "array",
19
+ "items": {
20
+ "type": "object",
21
+ "additionalProperties": false,
22
+ "required": ["capability", "authority", "approved_by", "reason"],
23
+ "properties": {
24
+ "capability": { "$ref": "host-capability-manifest-v1.schema.json#/$defs/capabilityName" },
25
+ "authority": { "const": "human" },
26
+ "approved_by": { "type": "string", "minLength": 1 },
27
+ "reason": { "type": "string", "minLength": 1 }
28
+ }
29
+ }
30
+ },
12
31
  "sensors": {
13
32
  "type": "array",
14
33
  "items": {
@@ -103,6 +103,7 @@ export function resolveRuntimeActiveContext({
103
103
  branch: selected.actual.branch,
104
104
  headSha: selected.actual.head,
105
105
  sessionId: selected.sessionId,
106
+ ...(selected.entry.host_coverage ? { hostCoverage: structuredClone(selected.entry.host_coverage) } : {}),
106
107
  };
107
108
  }
108
109
 
@@ -0,0 +1,50 @@
1
+ import { HOST_CAPABILITY_MANIFESTS } from '../packages/integrations/src/capabilities.mjs';
2
+ import { buildHostCoverage } from './host-capabilities.mjs';
3
+
4
+ export const CAPABILITIES_HELP = `wendkeep capabilities [options]
5
+
6
+ --host <id> claude | codex | pi | generic-mcp (default: all)
7
+ --host-version <v> observed host version for support classification
8
+ --json structured coverage
9
+ `;
10
+
11
+ function option(argv, name) {
12
+ const rows = argv.filter((item) => item === name || item.startsWith(`${name}=`));
13
+ if (rows.length > 1) throw Object.assign(new Error(`${name} must be supplied once`), { code: 'HOST_ARGUMENT_INVALID' });
14
+ const index = argv.indexOf(name);
15
+ if (index >= 0) return argv[index + 1] || '';
16
+ return argv.find((item) => item.startsWith(`${name}=`))?.slice(name.length + 1) || '';
17
+ }
18
+
19
+ export function runCapabilities(argv = []) {
20
+ const json = argv.includes('--json');
21
+ try {
22
+ const known = new Set(['--host', '--host-version', '--json']);
23
+ for (let index = 0; index < argv.length; index += 1) {
24
+ const item = argv[index];
25
+ const flag = item.split('=', 1)[0];
26
+ if (!known.has(flag)) throw Object.assign(new Error(`unknown option: ${item}`), { code: 'HOST_ARGUMENT_INVALID' });
27
+ if (['--host', '--host-version'].includes(item)) {
28
+ const value = argv[index + 1];
29
+ if (!value || value.startsWith('--')) {
30
+ throw Object.assign(new Error(`${item} requires a value`), { code: 'HOST_ARGUMENT_INVALID' });
31
+ }
32
+ index += 1;
33
+ }
34
+ }
35
+ const host = option(argv, '--host');
36
+ const hostVersion = option(argv, '--host-version');
37
+ const ids = host ? [host] : Object.keys(HOST_CAPABILITY_MANIFESTS);
38
+ const rows = ids.map((hostId) => buildHostCoverage({ hostId, hostVersion }));
39
+ const payload = host ? rows[0] : { schema_version: 1, hosts: rows };
40
+ if (json) process.stdout.write(`${JSON.stringify(payload)}\n`);
41
+ else for (const coverage of rows) {
42
+ process.stdout.write(`${coverage.host_id}: ${coverage.degraded ? 'degraded' : 'complete'}\n`);
43
+ for (const item of coverage.capabilities) process.stdout.write(` ${item.capability}: ${item.state} (${item.authority})\n`);
44
+ }
45
+ return 0;
46
+ } catch (error) {
47
+ process.stderr.write(`wendkeep capabilities: ${error?.code || 'HOST_CAPABILITIES_FAILED'}: ${error?.message || error}\n`);
48
+ return 2;
49
+ }
50
+ }
package/src/doctor.mjs CHANGED
@@ -11,6 +11,9 @@ import {
11
11
  inspectActiveContextHealth,
12
12
  renderActiveContextHealthLines,
13
13
  } from './active-context-health.mjs';
14
+ import { inspectPortableState } from './portable.mjs';
15
+ import { inspectSyncOutbox, readLocalSyncState } from './sync-outbox.mjs';
16
+ import { readProjectForValidation } from '../packages/vault/src/validate-memory.mjs';
14
17
 
15
18
  const healthStatusLabel = (status) => ({
16
19
  healthy: 'saudável', warning: 'atenção', degraded: 'degradada', blocked: 'bloqueada', legacy: 'legado',
@@ -147,6 +150,28 @@ export function runDoctor(argv) {
147
150
  const activeContexts = inspectActiveContextHealth({ vaultBase, projectRoot });
148
151
  process.stdout.write(`\n${renderActiveContextHealthLines(activeContexts).join('\n')}\n`);
149
152
 
153
+ const portable = inspectPortableState({ vaultBase, projectRoot });
154
+ process.stdout.write(`\n[portable] ${portable.status}${portable.issues.length ? ` — ${portable.issues.length} divergência(s)` : ''}\n`);
155
+ if (portable.status === 'diverged') process.stdout.write(' → wendkeep portable diff; revise e rode `wendkeep portable export`\n');
156
+ if (portable.status === 'invalid') process.stdout.write(` ✗ ${portable.issues.join(', ')}\n`);
157
+
158
+ const sync = inspectSyncOutbox(vaultBase);
159
+ let syncConflicts = 0;
160
+ if (sync.status !== 'disabled' && sync.status !== 'corrupt') {
161
+ try {
162
+ const projectIdentity = readProjectForValidation(vaultBase);
163
+ const syncState = readLocalSyncState(vaultBase, resolution.projectId || projectIdentity.projectId || 'unknown');
164
+ syncConflicts = Object.values(syncState.conflicts || {}).filter((item) => item?.status === 'open').length;
165
+ } catch {
166
+ sync.status = 'corrupt';
167
+ sync.code = 'WENDKEEP_SYNC_STATE_CORRUPT';
168
+ }
169
+ }
170
+ process.stdout.write(`\n[sync] ${sync.status} · ${sync.pending} pendente(s) · ${syncConflicts} conflito(s) aberto(s)\n`);
171
+ if (sync.status === 'pending') process.stdout.write(' → wendkeep sync push --remote <diretório> (ou --url <endpoint>)\n');
172
+ if (sync.status === 'corrupt') process.stdout.write(` ✗ ${sync.code || 'WENDKEEP_SYNC_OUTBOX_CORRUPT'}\n`);
173
+ if (syncConflicts) process.stdout.write(' → wendkeep sync conflicts; resolva cada conflito explicitamente\n');
174
+
150
175
  // 3. Link/graph health — órfãos que o grafo do Obsidian mostraria, com o comando de reparo.
151
176
  const links = checkVaultLinks(vaultBase);
152
177
  const graphLabel = links.graphColors === true ? 'com cores' : links.graphColors === false ? 'sem cores' : 'sem graph.json';
@@ -197,6 +222,9 @@ export function runDoctor(argv) {
197
222
  || warnings.length
198
223
  || worktrees.issues.length
199
224
  || activeContexts.issues.length
225
+ || ['diverged', 'invalid'].includes(portable.status)
226
+ || sync.status === 'corrupt'
227
+ || syncConflicts
200
228
  || links.derivedOrphans
201
229
  || links.artifactOrphans
202
230
  || links.graphColors === false
@@ -17,10 +17,12 @@ import {
17
17
 
18
18
  export { canonicalSha256, evaluateEvidenceBinding, evidenceSensors };
19
19
 
20
- const PACKAGE_VERSION = JSON.parse(readFileSync(
21
- join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'),
22
- 'utf8',
23
- )).version;
20
+ function packageVersion() {
21
+ return JSON.parse(readFileSync(
22
+ join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'),
23
+ 'utf8',
24
+ )).version;
25
+ }
24
26
 
25
27
  export function sensorConfigSha256(sensors, ids) {
26
28
  const selected = new Set(ids || []);
@@ -260,10 +262,12 @@ export function buildEvidenceEnvelope({
260
262
  effectiveSpecSha256,
261
263
  sensorConfigSha256: configSha256,
262
264
  sensors,
265
+ tddAttestations = [],
263
266
  startedAt,
264
267
  finishedAt,
265
- version = PACKAGE_VERSION,
268
+ version = '',
266
269
  runtimePlatform = `${process.platform}-${process.arch}`,
270
+ hostCoverage = null,
267
271
  } = {}) {
268
272
  const envelope = {
269
273
  schema_version: 2,
@@ -278,11 +282,13 @@ export function buildEvidenceEnvelope({
278
282
  tasks_sha256: tasksSha256,
279
283
  effective_spec_sha256: effectiveSpecSha256,
280
284
  sensor_config_sha256: configSha256,
281
- wendkeep_version: version,
285
+ wendkeep_version: version || packageVersion(),
282
286
  platform: runtimePlatform,
283
287
  started_at: startedAt,
284
288
  finished_at: finishedAt,
285
289
  sensors,
290
+ ...(hostCoverage ? { host_coverage: structuredClone(hostCoverage) } : {}),
291
+ tdd_attestations: tddAttestations,
286
292
  };
287
293
  return { ...envelope, envelope_id: canonicalSha256(envelope) };
288
294
  }
@@ -0,0 +1,34 @@
1
+ import {
2
+ buildCoverageFromManifest,
3
+ } from '../packages/integrations/src/capabilities.mjs';
4
+ import {
5
+ MCP_EFFECT_MANIFEST,
6
+ verifyMcpEffectManifest,
7
+ } from '../packages/mcp/src/effects.mjs';
8
+
9
+ function summarizeToolEffects(manifest) {
10
+ const validation = verifyMcpEffectManifest(manifest);
11
+ const effects = new Set(validation.valid ? manifest.tools.map((tool) => tool.effect) : []);
12
+ return {
13
+ manifest_valid: validation.valid,
14
+ catalog_version: validation.valid ? manifest.catalog_version : '',
15
+ read: effects.has('read'),
16
+ write: effects.has('write'),
17
+ destructive: effects.has('destructive'),
18
+ unknown: 'fail-closed',
19
+ };
20
+ }
21
+
22
+ export function buildHostCoverage({
23
+ hostId,
24
+ hostVersion = '',
25
+ effectManifest = MCP_EFFECT_MANIFEST,
26
+ observedAt,
27
+ } = {}) {
28
+ return buildCoverageFromManifest({
29
+ hostId,
30
+ hostVersion,
31
+ observedAt,
32
+ toolEffects: summarizeToolEffects(effectManifest),
33
+ });
34
+ }
package/src/init.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // `wendkeep init` — cross-platform replacement for setup-vault.ps1.
2
2
  // Creates the vault taxonomy + provider-neutral project binding, NON-DESTRUCTIVELY
3
- // merges the session hooks into .claude/settings.json, and adds the mcpvault server to
3
+ // merges the session hooks into .claude/settings.json, and adds the native MCP server to
4
4
  // .mcp.json. Idempotent: re-running only adds what is missing.
5
5
  import { spawnSync } from 'node:child_process';
6
6
  import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
@@ -516,7 +516,7 @@ export async function runInit(argv) {
516
516
  log(M.header);
517
517
  log(`${M.lProject}: ${projectPath}`);
518
518
  log(`${M.lVault}: ${vaultPath}`);
519
- log(`${M.lMcp}: ${args.mcp ? 'mcpvault (wendkeep-vault)' : M.skipped}`);
519
+ log(`${M.lMcp}: ${args.mcp ? 'wendkeep native (wendkeep-vault)' : M.skipped}`);
520
520
  log(`${M.lCompanions}: ${companions.length ? companions.join(', ') : M.none}`);
521
521
  log(`${M.lColors}: ${args.noColors ? M.skipped : M.colorsOn}\n`);
522
522
 
@@ -640,7 +640,7 @@ export async function runInit(argv) {
640
640
  log(M.codexTrust);
641
641
 
642
642
  // 3. .mcp.json --------------------------------------------------------------
643
- // Written when mcpvault is wanted OR a selected companion ships an MCP server.
643
+ // Written when the native WendKeep MCP is wanted OR a selected companion ships an MCP server.
644
644
  const companionMcp = companionMcpPatch(companions, skipMcp);
645
645
  if (args.mcp || Object.keys(companionMcp).length) {
646
646
  const mcpPath = join(projectPath, '.mcp.json');
package/src/mcp.mjs ADDED
@@ -0,0 +1,7 @@
1
+ // Compatibility facade keeps the CLI and hooks from depending directly on a sibling adapter.
2
+ export { MCP_HELP, runMcp } from '../packages/mcp/src/cli.mjs';
3
+ export {
4
+ MCP_EFFECT_MANIFEST,
5
+ resolveMcpToolEffect,
6
+ verifyMcpEffectManifest,
7
+ } from '../packages/mcp/src/effects.mjs';
@@ -5,6 +5,7 @@ import { allChangesState } from '../hooks/change-core.mjs';
5
5
  import { readControl, readSessionRegistry } from '../hooks/obsidian-common.mjs';
6
6
  import { runVaultHealth } from '../hooks/vault-health.mjs';
7
7
  import { readProjectForValidation } from '../packages/vault/src/validate-memory.mjs';
8
+ import { inspectSyncOutbox, readLocalSyncState } from './sync-outbox.mjs';
8
9
 
9
10
  export const OBSERVER_SCHEMA_VERSION = 1;
10
11
  export const MAX_SNAPSHOT_BYTES = 32 * 1024;
@@ -46,12 +47,21 @@ function activeSessionSummary(vaultBase, control, registry) {
46
47
  .sort((a, b) => String(b.entry?.last_seen || b.entry?.updated_at || '').localeCompare(String(a.entry?.last_seen || a.entry?.updated_at || '')));
47
48
  const selected = entries.find(({ sessionId }) => sessionId === control?.session_id) || entries[0];
48
49
  const entry = selected?.entry || {};
50
+ const coverage = entry.host_coverage;
49
51
  return {
50
52
  status: safeText(control?.status || entry.status || 'inactive', 32),
51
53
  session_id: safeText(control?.session_id || selected?.sessionId || '', 100),
52
54
  provider: safeText(entry.provider || '', 32),
53
55
  change_slug: safeText(entry.change_slug || '', 100),
54
56
  last_seen: safeText(entry.last_seen || entry.updated_at || '', 40),
57
+ ...(coverage ? {
58
+ coverage: {
59
+ host_id: safeText(coverage.host_id || 'unknown', 32),
60
+ degraded: coverage.degraded === true,
61
+ unavailable: (coverage.capabilities || []).filter((item) => item?.state === 'unavailable').length,
62
+ manual: (coverage.capabilities || []).filter((item) => item?.state === 'manual').length,
63
+ },
64
+ } : {}),
55
65
  };
56
66
  }
57
67
 
@@ -83,6 +93,20 @@ function hashEvent(snapshot) {
83
93
  return `obs-${createHash('sha256').update(canonical).digest('hex').slice(0, 24)}`;
84
94
  }
85
95
 
96
+ function syncSummary(vaultBase, projectId) {
97
+ const outbox = inspectSyncOutbox(vaultBase);
98
+ let openConflicts = 0;
99
+ if (!['disabled', 'corrupt'].includes(outbox.status)) {
100
+ try {
101
+ const state = readLocalSyncState(vaultBase, projectId);
102
+ openConflicts = Object.values(state.conflicts || {}).filter((item) => item?.status === 'open').length;
103
+ } catch {
104
+ return { status: 'corrupt', pending: outbox.pending, open_conflicts: 0 };
105
+ }
106
+ }
107
+ return { status: outbox.status, pending: outbox.pending, open_conflicts: openConflicts };
108
+ }
109
+
86
110
  function hasForbiddenKey(value) {
87
111
  if (!value || typeof value !== 'object') return false;
88
112
  for (const [key, child] of Object.entries(value)) {
@@ -145,6 +169,7 @@ export function buildProjectSnapshot({ vaultBase, projectRoot = process.cwd(), n
145
169
  session: activeSessionSummary(vaultBase, control, registry),
146
170
  changes,
147
171
  health: healthSummary(vaultBase),
172
+ sync: syncSummary(vaultBase, project.projectId),
148
173
  };
149
174
  snapshot.event_id = hashEvent(snapshot);
150
175
  const validation = validateObserverSnapshot(snapshot, { projectId: project.projectId });