wendkeep 0.85.1 → 0.87.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 (49) hide show
  1. package/.githooks/commit-msg +16 -0
  2. package/.githooks/prepare-commit-msg +16 -0
  3. package/CHANGELOG.md +40 -0
  4. package/README.en.md +4 -1
  5. package/README.md +4 -1
  6. package/docs/en/commands/commit.md +159 -0
  7. package/docs/en/commands/evidence-embeddings.md +243 -0
  8. package/docs/en/commands/mcp.md +67 -7
  9. package/docs/pt-BR/commands/commit.md +159 -0
  10. package/docs/pt-BR/commands/evidence-embeddings.md +244 -0
  11. package/docs/pt-BR/commands/mcp.md +66 -7
  12. package/hooks/evidence-context.mjs +41 -7
  13. package/hooks/evidence-recall.mjs +10 -0
  14. package/package.json +5 -2
  15. package/packages/cli/src/index.mjs +11 -1
  16. package/packages/commit/package.json +6 -0
  17. package/packages/commit/src/cli.mjs +89 -0
  18. package/packages/commit/src/commit-input.mjs +181 -0
  19. package/packages/commit/src/commit-message.mjs +51 -0
  20. package/packages/commit/src/commit-policy.mjs +144 -0
  21. package/packages/commit/src/git-runtime.mjs +428 -0
  22. package/packages/commit/src/index.mjs +28 -0
  23. package/packages/commit/src/proof-validation.mjs +443 -0
  24. package/packages/mcp/src/effects.mjs +3 -2
  25. package/packages/mcp/src/evidence-recall.mjs +130 -0
  26. package/packages/mcp/src/executor.mjs +4 -0
  27. package/packages/mcp/src/server.mjs +31 -1
  28. package/packages/vault/src/evidence-embedding-plugin.mjs +531 -0
  29. package/packages/vault/src/evidence-index-store.mjs +360 -0
  30. package/packages/vault/src/evidence-recall-page.mjs +381 -0
  31. package/packages/vault/src/evidence-search-index.mjs +917 -0
  32. package/packages/vault/src/index.mjs +12 -1
  33. package/packages/vault/src/memory-ledger-view-base.mjs +545 -0
  34. package/packages/vault/src/memory-ledger-view.mjs +41 -0
  35. package/packages/vault/src/memory-rotation-store.mjs +967 -0
  36. package/packages/vault/src/memory-segment-store.mjs +820 -0
  37. package/packages/vault/src/memory-snapshot-store.mjs +1105 -0
  38. package/packages/vault/src/memory-store-base.mjs +1161 -0
  39. package/packages/vault/src/memory-store-core.mjs +2 -0
  40. package/packages/vault/src/memory-store.mjs +46 -1161
  41. package/schema/commit-message-v1.schema.json +75 -0
  42. package/scripts/validate-commit-range.mjs +244 -0
  43. package/src/doctor.mjs +48 -5
  44. package/src/evidence-search-health.mjs +221 -0
  45. package/src/git-commit-hooks.mjs +112 -0
  46. package/src/init.mjs +13 -0
  47. package/src/memory-scale-health.mjs +210 -0
  48. package/src/observer-snapshot.mjs +87 -1
  49. package/src/skills-seed.mjs +79 -0
@@ -0,0 +1,112 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ export const GIT_COMMIT_HOOKS = ['prepare-commit-msg', 'commit-msg'];
7
+ export const GIT_COMMIT_HOOKS_PATH = '.githooks';
8
+
9
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
10
+ const packagedHooks = join(packageRoot, '.githooks');
11
+
12
+ function git(projectRoot, args, { allowFailure = false } = {}) {
13
+ const result = spawnSync('git', args, {
14
+ cwd: projectRoot,
15
+ encoding: 'utf8',
16
+ windowsHide: true,
17
+ });
18
+ if (!allowFailure && result.status !== 0) {
19
+ const error = new Error((result.stderr || `git ${args.join(' ')} failed`).trim());
20
+ error.code = 'WENDKEEP_COMMIT_GIT_FAILED';
21
+ throw error;
22
+ }
23
+ return result;
24
+ }
25
+
26
+ function hookState(projectRoot, name) {
27
+ const source = join(packagedHooks, name);
28
+ const target = join(projectRoot, GIT_COMMIT_HOOKS_PATH, name);
29
+ if (!existsSync(target)) return { name, source, target, state: 'missing' };
30
+ return {
31
+ name,
32
+ source,
33
+ target,
34
+ state: readFileSync(target).equals(readFileSync(source)) ? 'current' : 'drift',
35
+ };
36
+ }
37
+
38
+ export function inspectGitCommitHooks({ projectRoot = process.cwd() } = {}) {
39
+ const root = resolve(projectRoot);
40
+ const repository = git(root, ['rev-parse', '--show-toplevel'], { allowFailure: true });
41
+ if (repository.status !== 0) {
42
+ return { status: 'unavailable', configured: false, issues: ['not a Git repository'], repair: '' };
43
+ }
44
+ const configured = git(root, ['config', '--local', '--get', 'core.hooksPath'], { allowFailure: true });
45
+ const configuredPath = configured.status === 0 ? configured.stdout.trim().replaceAll('\\', '/') : '';
46
+ if (!configuredPath) {
47
+ return { status: 'disabled', configured: false, issues: [], repair: 'wendkeep init --git-commit-hooks --yes' };
48
+ }
49
+ if (!['.githooks', './.githooks'].includes(configuredPath)) {
50
+ return {
51
+ status: 'drift',
52
+ configured: true,
53
+ configuredPath,
54
+ issues: [`core.hooksPath points to ${configuredPath}, not .githooks`],
55
+ repair: 'wendkeep init --git-commit-hooks --force --yes',
56
+ };
57
+ }
58
+ const states = GIT_COMMIT_HOOKS.map((name) => hookState(root, name));
59
+ const issues = states.filter((item) => item.state !== 'current').map((item) => (
60
+ item.state === 'missing'
61
+ ? `${item.name}: missing`
62
+ : `${item.name}: content differs from the installed WendKeep version`
63
+ ));
64
+ return {
65
+ status: issues.length ? (states.some((item) => item.state === 'missing') ? 'missing' : 'drift') : 'healthy',
66
+ configured: true,
67
+ configuredPath,
68
+ issues,
69
+ repair: issues.length ? 'wendkeep init --git-commit-hooks --force --yes' : '',
70
+ };
71
+ }
72
+
73
+ export function installGitCommitHooks({ projectRoot = process.cwd(), force = false } = {}) {
74
+ const root = resolve(projectRoot);
75
+ git(root, ['rev-parse', '--show-toplevel']);
76
+ const configured = git(root, ['config', '--local', '--get', 'core.hooksPath'], { allowFailure: true });
77
+ const configuredPath = configured.status === 0 ? configured.stdout.trim().replaceAll('\\', '/') : '';
78
+ if (configuredPath && !['.githooks', './.githooks'].includes(configuredPath) && !force) {
79
+ return {
80
+ status: 'conflict',
81
+ conflicts: ['core.hooksPath'],
82
+ configuredPath,
83
+ repair: 'wendkeep init --git-commit-hooks --force --yes',
84
+ };
85
+ }
86
+ const states = GIT_COMMIT_HOOKS.map((name) => hookState(root, name));
87
+ const conflicts = states.filter((item) => item.state === 'drift').map((item) => item.name);
88
+ if (conflicts.length && !force) {
89
+ return { status: 'conflict', conflicts, repair: 'wendkeep init --git-commit-hooks --force --yes' };
90
+ }
91
+ mkdirSync(join(root, GIT_COMMIT_HOOKS_PATH), { recursive: true });
92
+ let changed = false;
93
+ for (const item of states) {
94
+ if (item.state === 'current') continue;
95
+ if (item.state === 'drift' && force && !existsSync(`${item.target}.bak`)) {
96
+ copyFileSync(item.target, `${item.target}.bak`);
97
+ }
98
+ copyFileSync(item.source, item.target);
99
+ try { chmodSync(item.target, 0o755); } catch { /* Git for Windows uses its executable shim. */ }
100
+ changed = true;
101
+ }
102
+ if (configured.status !== 0 || configured.stdout.trim().replaceAll('\\', '/') !== GIT_COMMIT_HOOKS_PATH) {
103
+ git(root, ['config', '--local', 'core.hooksPath', GIT_COMMIT_HOOKS_PATH]);
104
+ changed = true;
105
+ }
106
+ return {
107
+ status: changed ? 'installed' : 'unchanged',
108
+ conflicts: [],
109
+ hooks: GIT_COMMIT_HOOKS,
110
+ path: join(root, GIT_COMMIT_HOOKS_PATH),
111
+ };
112
+ }
package/src/init.mjs CHANGED
@@ -41,6 +41,7 @@ import { adoptSpecsState, ensureSpecsReadme, SPECS_STATE_FILE } from '../hooks/s
41
41
  import { bindProjectVault, readProjectBinding } from './project-vault.mjs';
42
42
  import { seedMemoryV2 } from './memory.mjs';
43
43
  import { installVscodeWorktreeTasks } from './worktree.mjs';
44
+ import { installGitCommitHooks } from './git-commit-hooks.mjs';
44
45
  import {
45
46
  DEFAULT_OPERATING_PROFILE,
46
47
  normalizeOperatingProfile,
@@ -69,6 +70,7 @@ function parseArgs(argv) {
69
70
  else if (a === '--no-companions') args.noCompanions = true;
70
71
  else if (a === '--no-colors') args.noColors = true;
71
72
  else if (a === '--vscode-worktree-tasks') args.vscodeWorktreeTasks = true;
73
+ else if (a === '--git-commit-hooks') args.gitCommitHooks = true;
72
74
  else if (a === '--dotcontext-mcp') args.dotcontextMcp = argv[++i];
73
75
  else if (a.startsWith('--dotcontext-mcp=')) args.dotcontextMcp = a.slice(17);
74
76
  else if (a === '--dotcontext-hooks') args.dotcontextHooks = argv[++i];
@@ -639,6 +641,17 @@ export async function runInit(argv) {
639
641
  }
640
642
  log(M.codexTrust);
641
643
 
644
+ // Git commit policy is opt-in because this is the only init surface that writes
645
+ // repository-local Git configuration. Existing custom hooks are never overwritten silently.
646
+ if (args.gitCommitHooks) {
647
+ const hooks = installGitCommitHooks({ projectRoot: projectPath, force: args.force });
648
+ if (hooks.status === 'conflict') {
649
+ log(` [!] Git commit hooks preserved (${hooks.conflicts.join(', ')}); review and rerun ${hooks.repair}`);
650
+ } else {
651
+ log(` Git commit hooks: ${hooks.status} (${hooks.path})`);
652
+ }
653
+ }
654
+
642
655
  // 3. .mcp.json --------------------------------------------------------------
643
656
  // Written when the native WendKeep MCP is wanted OR a selected companion ships an MCP server.
644
657
  const companionMcp = companionMcpPatch(companions, skipMcp);
@@ -0,0 +1,210 @@
1
+ import {
2
+ readMemoryLedgerGeneration,
3
+ readMemoryProjectionSnapshot,
4
+ readMemoryRotationJournal,
5
+ readMemoryRotationReceipts,
6
+ readMemorySegmentManifest,
7
+ } from '../hooks/memory-store.mjs';
8
+ import {
9
+ quoteCommandArgument,
10
+ WENDKEEP_COMMAND,
11
+ } from '../hooks/obsidian-common.mjs';
12
+
13
+ export const MEMORY_SCALE_HEALTH_SCHEMA_VERSION = 1;
14
+
15
+ function safeReason(value, fallback = '') {
16
+ const text = String(value || fallback).trim();
17
+ return text
18
+ ? text.replace(/[^A-Za-z0-9._:-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 120)
19
+ : null;
20
+ }
21
+
22
+ function numberMetric(value) {
23
+ const number = Number(value || 0);
24
+ return Number.isSafeInteger(number) && number >= 0 ? number : 0;
25
+ }
26
+
27
+ export function emptyMemoryScaleMetrics() {
28
+ return {
29
+ scaleSchemaVersion: MEMORY_SCALE_HEALTH_SCHEMA_VERSION,
30
+ scaleStatus: 'unknown',
31
+ scaleErrorCode: null,
32
+ snapshotStatus: 'unknown',
33
+ snapshotReason: null,
34
+ snapshotEvents: 0,
35
+ snapshotLedgerBytes: 0,
36
+ snapshotTailEvents: 0,
37
+ snapshotTailBytes: 0,
38
+ segmentStatus: 'unknown',
39
+ segmentCount: 0,
40
+ segmentCoveredEvents: 0,
41
+ segmentCoveredBytes: 0,
42
+ segmentPendingEvents: 0,
43
+ generationStatus: 'unknown',
44
+ generation: 0,
45
+ generationSourceEvents: 0,
46
+ generationActiveTailEvents: 0,
47
+ generationRotatedAt: null,
48
+ rotationJournal: 'unknown',
49
+ rotationRecoveryRequired: false,
50
+ rotationReceiptsStatus: 'unknown',
51
+ rotationReceipts: 0,
52
+ rotationReceiptCheckpoint: 'unknown',
53
+ };
54
+ }
55
+
56
+ function inspectSnapshot(vaultBase, metrics) {
57
+ try {
58
+ const snapshot = readMemoryProjectionSnapshot(vaultBase);
59
+ const tailStatus = snapshot.status === 'ok' ? snapshot.tail?.status : null;
60
+ metrics.snapshotStatus = snapshot.status === 'ok' && tailStatus !== 'ok'
61
+ ? 'invalid'
62
+ : snapshot.status;
63
+ metrics.snapshotReason = safeReason(
64
+ snapshot.reason || snapshot.tail?.reason,
65
+ metrics.snapshotStatus === 'invalid' ? 'snapshot-tail-unavailable' : '',
66
+ );
67
+ if (snapshot.status === 'ok') {
68
+ metrics.snapshotEvents = numberMetric(snapshot.snapshot?.event_count);
69
+ metrics.snapshotLedgerBytes = numberMetric(snapshot.snapshot?.ledger_bytes);
70
+ metrics.snapshotTailEvents = Array.isArray(snapshot.tail?.events)
71
+ ? snapshot.tail.events.length
72
+ : 0;
73
+ metrics.snapshotTailBytes = numberMetric(snapshot.tail?.bytes);
74
+ }
75
+ } catch (error) {
76
+ if (error?.code === 'VAULT_PATH_UNSAFE') throw error;
77
+ metrics.snapshotStatus = 'invalid';
78
+ metrics.snapshotReason = safeReason(error?.code, 'snapshot-read-failed');
79
+ }
80
+ }
81
+
82
+ function inspectSegments(vaultBase, ledgerEvents, metrics) {
83
+ try {
84
+ const manifest = readMemorySegmentManifest(vaultBase);
85
+ metrics.segmentStatus = manifest.status;
86
+ if (manifest.status === 'ok') {
87
+ metrics.segmentCount = numberMetric(manifest.manifest?.segment_count);
88
+ metrics.segmentCoveredEvents = numberMetric(manifest.manifest?.covered_event_count);
89
+ metrics.segmentCoveredBytes = numberMetric(manifest.manifest?.covered_bytes);
90
+ }
91
+ metrics.segmentPendingEvents = Math.max(
92
+ 0,
93
+ numberMetric(ledgerEvents) - metrics.segmentCoveredEvents,
94
+ );
95
+ } catch (error) {
96
+ if (error?.code === 'VAULT_PATH_UNSAFE') throw error;
97
+ metrics.segmentStatus = 'invalid';
98
+ metrics.scaleErrorCode ||= safeReason(error?.code, 'segment-manifest-read-failed');
99
+ }
100
+ }
101
+
102
+ function inspectRotation(vaultBase, ledgerEvents, metrics) {
103
+ try {
104
+ const generation = readMemoryLedgerGeneration(vaultBase);
105
+ metrics.generationStatus = generation.status;
106
+ if (generation.status === 'ok') {
107
+ metrics.generation = numberMetric(generation.state?.generation);
108
+ metrics.generationSourceEvents = numberMetric(generation.state?.source_event_count);
109
+ metrics.generationActiveTailEvents = Math.max(
110
+ 0,
111
+ numberMetric(ledgerEvents) - metrics.generationSourceEvents,
112
+ );
113
+ metrics.generationRotatedAt = String(generation.state?.rotated_at || '') || null;
114
+ }
115
+ } catch (error) {
116
+ if (error?.code === 'VAULT_PATH_UNSAFE') throw error;
117
+ metrics.generationStatus = 'invalid';
118
+ metrics.scaleErrorCode ||= safeReason(error?.code, 'generation-read-failed');
119
+ }
120
+
121
+ try {
122
+ const journal = readMemoryRotationJournal(vaultBase);
123
+ metrics.rotationJournal = journal.status === 'ok'
124
+ ? String(journal.journal?.stage || 'invalid')
125
+ : journal.status;
126
+ metrics.rotationRecoveryRequired = journal.status !== 'missing';
127
+ } catch (error) {
128
+ if (error?.code === 'VAULT_PATH_UNSAFE') throw error;
129
+ metrics.rotationJournal = 'invalid';
130
+ metrics.rotationRecoveryRequired = true;
131
+ metrics.scaleErrorCode ||= safeReason(error?.code, 'rotation-journal-read-failed');
132
+ }
133
+
134
+ try {
135
+ const receipts = readMemoryRotationReceipts(vaultBase);
136
+ metrics.rotationReceiptsStatus = receipts.status;
137
+ metrics.rotationReceipts = Array.isArray(receipts.receipts)
138
+ ? receipts.receipts.length
139
+ : 0;
140
+ metrics.rotationReceiptCheckpoint = String(receipts.checkpointStatus || 'unknown');
141
+ } catch (error) {
142
+ if (error?.code === 'VAULT_PATH_UNSAFE') throw error;
143
+ metrics.rotationReceiptsStatus = 'invalid';
144
+ metrics.rotationReceiptCheckpoint = 'invalid';
145
+ metrics.scaleErrorCode ||= safeReason(error?.code, 'rotation-receipts-read-failed');
146
+ }
147
+ }
148
+
149
+ function deriveScaleStatus(metrics) {
150
+ const invalid = [
151
+ metrics.snapshotStatus,
152
+ metrics.segmentStatus,
153
+ metrics.generationStatus,
154
+ metrics.rotationJournal,
155
+ metrics.rotationReceiptsStatus,
156
+ metrics.rotationReceiptCheckpoint,
157
+ ].some((status) => ['invalid', 'corrupt', 'blocked'].includes(status));
158
+ if (invalid || metrics.scaleErrorCode) return 'degraded';
159
+ if (metrics.rotationRecoveryRequired) return 'warning';
160
+ return 'healthy';
161
+ }
162
+
163
+ export function inspectMemoryScaleHealth(vaultBase, { ledgerEvents = 0 } = {}) {
164
+ const metrics = emptyMemoryScaleMetrics();
165
+ inspectSnapshot(vaultBase, metrics);
166
+ inspectSegments(vaultBase, ledgerEvents, metrics);
167
+ inspectRotation(vaultBase, ledgerEvents, metrics);
168
+ metrics.scaleStatus = deriveScaleStatus(metrics);
169
+ return metrics;
170
+ }
171
+
172
+ function statusCommand(vaultBase) {
173
+ return `${WENDKEEP_COMMAND} memory status --gate --vault ${quoteCommandArgument(vaultBase)}`;
174
+ }
175
+
176
+ export function augmentVaultHealthWithMemoryScale(result, vaultBase) {
177
+ const memory = result?.metrics?.memory;
178
+ if (!memory || ['legacy', 'blocked'].includes(result.memoryStatus)) return result;
179
+
180
+ try {
181
+ const scale = inspectMemoryScaleHealth(vaultBase, {
182
+ ledgerEvents: memory.ledgerEvents,
183
+ });
184
+ return {
185
+ ...result,
186
+ metrics: {
187
+ ...result.metrics,
188
+ memory: { ...memory, ...scale },
189
+ },
190
+ };
191
+ } catch (error) {
192
+ const code = safeReason(error?.code, 'memory-scale-boundary-unsafe');
193
+ const failure = `Memória: Artefatos de escala da memória estão inseguros ou ilegíveis (${code}). Inspecione com: ${statusCommand(vaultBase)}.`;
194
+ return {
195
+ ...result,
196
+ ok: false,
197
+ memoryStatus: 'blocked',
198
+ failures: [...(result.failures || []), failure],
199
+ metrics: {
200
+ ...result.metrics,
201
+ memory: {
202
+ ...memory,
203
+ ...emptyMemoryScaleMetrics(),
204
+ scaleStatus: 'blocked',
205
+ scaleErrorCode: code,
206
+ },
207
+ },
208
+ };
209
+ }
210
+ }
@@ -5,6 +5,8 @@ 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 { inspectEvidenceSearchHealth } from './evidence-search-health.mjs';
9
+ import { augmentVaultHealthWithMemoryScale } from './memory-scale-health.mjs';
8
10
  import { inspectSyncOutbox, readLocalSyncState } from './sync-outbox.mjs';
9
11
 
10
12
  export const OBSERVER_SCHEMA_VERSION = 1;
@@ -27,6 +29,11 @@ function safeText(value, max = MAX_TEXT) {
27
29
  .slice(0, max);
28
30
  }
29
31
 
32
+ function safeCount(value) {
33
+ const number = Number(value || 0);
34
+ return Number.isSafeInteger(number) && number >= 0 ? number : 0;
35
+ }
36
+
30
37
  function isoNow(value) {
31
38
  const date = value instanceof Date ? value : new Date(value ?? Date.now());
32
39
  if (Number.isNaN(date.getTime())) throw fail('captured_at inválido.');
@@ -65,9 +72,86 @@ function activeSessionSummary(vaultBase, control, registry) {
65
72
  };
66
73
  }
67
74
 
75
+ function recallSearchSummary(metrics) {
76
+ return {
77
+ schema_version: 1,
78
+ status: safeText(metrics?.status || 'unknown', 32),
79
+ ...(metrics?.errorCode ? { error_code: safeText(metrics.errorCode, 120) } : {}),
80
+ authority: {
81
+ status: safeText(metrics?.authorityStatus || 'unknown', 32),
82
+ bytes: safeCount(metrics?.authorityBytes),
83
+ },
84
+ incremental: {
85
+ status: safeText(metrics?.incrementalStateStatus || 'unknown', 32),
86
+ bytes: safeCount(metrics?.incrementalStateBytes),
87
+ documents: safeCount(metrics?.documentCount),
88
+ },
89
+ search: {
90
+ status: safeText(metrics?.searchStateStatus || 'unknown', 32),
91
+ bytes: safeCount(metrics?.searchStateBytes),
92
+ chunks: safeCount(metrics?.rowCount),
93
+ source_index_current: metrics?.sourceIndexCurrent === true,
94
+ source_state_current: metrics?.sourceStateCurrent === true,
95
+ },
96
+ lexical: {
97
+ status: safeText(metrics?.lexicalStatus || 'unknown', 32),
98
+ bytes: safeCount(metrics?.lexicalBytes),
99
+ },
100
+ sqlite: {
101
+ status: safeText(metrics?.sqliteStatus || 'unknown', 32),
102
+ bytes: safeCount(metrics?.sqliteBytes),
103
+ capability: metrics?.sqliteCapability === true,
104
+ },
105
+ backend: safeText(metrics?.backend || 'unavailable', 40),
106
+ };
107
+ }
108
+
109
+ function memoryScaleSummary(memory = {}) {
110
+ if (memory.scaleSchemaVersion !== 1) return null;
111
+ return {
112
+ schema_version: 1,
113
+ status: safeText(memory.scaleStatus || 'unknown', 32),
114
+ ...(memory.scaleErrorCode ? { error_code: safeText(memory.scaleErrorCode, 120) } : {}),
115
+ snapshot: {
116
+ status: safeText(memory.snapshotStatus || 'unknown', 32),
117
+ ...(memory.snapshotReason ? { reason: safeText(memory.snapshotReason, 120) } : {}),
118
+ event_count: safeCount(memory.snapshotEvents),
119
+ ledger_bytes: safeCount(memory.snapshotLedgerBytes),
120
+ tail_events: safeCount(memory.snapshotTailEvents),
121
+ tail_bytes: safeCount(memory.snapshotTailBytes),
122
+ },
123
+ segments: {
124
+ status: safeText(memory.segmentStatus || 'unknown', 32),
125
+ count: safeCount(memory.segmentCount),
126
+ covered_events: safeCount(memory.segmentCoveredEvents),
127
+ covered_bytes: safeCount(memory.segmentCoveredBytes),
128
+ pending_events: safeCount(memory.segmentPendingEvents),
129
+ },
130
+ generation: {
131
+ status: safeText(memory.generationStatus || 'unknown', 32),
132
+ number: safeCount(memory.generation),
133
+ source_events: safeCount(memory.generationSourceEvents),
134
+ active_tail_events: safeCount(memory.generationActiveTailEvents),
135
+ rotated_at: safeText(memory.generationRotatedAt || '', 40),
136
+ },
137
+ rotation: {
138
+ journal: safeText(memory.rotationJournal || 'unknown', 32),
139
+ recovery_required: memory.rotationRecoveryRequired === true,
140
+ receipts_status: safeText(memory.rotationReceiptsStatus || 'unknown', 32),
141
+ receipts: safeCount(memory.rotationReceipts),
142
+ receipt_checkpoint: safeText(memory.rotationReceiptCheckpoint || 'unknown', 32),
143
+ },
144
+ };
145
+ }
146
+
68
147
  function healthSummary(vaultBase) {
69
148
  try {
70
- const health = runVaultHealth({ vaultBase });
149
+ const health = augmentVaultHealthWithMemoryScale(
150
+ runVaultHealth({ vaultBase }),
151
+ vaultBase,
152
+ );
153
+ const recall = recallSearchSummary(inspectEvidenceSearchHealth(vaultBase));
154
+ const memoryScale = memoryScaleSummary(health.metrics?.memory);
71
155
  return {
72
156
  ok: health.ok === true,
73
157
  status: safeText(health.memoryStatus || (health.ok ? 'healthy' : 'degraded'), 40),
@@ -75,6 +159,8 @@ function healthSummary(vaultBase) {
75
159
  warning_count: Array.isArray(health.warnings) ? health.warnings.length : 0,
76
160
  registry_sessions: Number(health.metrics?.registrySessions || 0),
77
161
  derived_notes: Number(health.metrics?.derivedNotes || 0),
162
+ recall_search: recall,
163
+ ...(memoryScale ? { memory_scale: memoryScale } : {}),
78
164
  };
79
165
  } catch {
80
166
  return {
@@ -524,6 +524,83 @@ the author — even if you wrote the code, enter as if you'd never seen it. Fres
524
524
  - \`verdict-template.json\` — the exact shape of the \`verdict.json\` to write.
525
525
  `;
526
526
 
527
+ const WK_COMMIT = `# Commit universal baseado em evidências
528
+
529
+ Use para preparar commits de implementação que precisem do contrato WendKeep. O gerador recebe
530
+ somente referências sanitizadas e o diff staged resumido; nunca leia, copie ou publique o Vault,
531
+ \`.brain\`, registros de sessão, tokens ou segredos na mensagem.
532
+
533
+ ## Fluxo
534
+
535
+ 1. Confirme que autoridade e referências de evidência estão atuais. O draft **não aceita** texto
536
+ caller-controlled em \`tasks\` ou \`tests\`: o runtime deriva tasks de Task Contracts concluídos;
537
+ Tests vêm somente de \`[sensor:<id>]\` executado pelo coletor.
538
+ Sensors do Envelope devem corresponder exatamente à reexecução; só a reexecução gera Tests.
539
+ \`[phase:verify]\` sozinho nunca prova execução, e o range reexecuta o sensor no SHA exato,
540
+ rederiva authority/artefatos/task/spec/Scope/config no SHA. Envelope/Verdict/receipt/TDD ficam
541
+ somente na validação local e são omitidos da Evidence remota; nunca publique seus IDs locais.
542
+ Tasks com \`[req:]\` exigem \`spec\` versionada e sanitizada. Use
543
+ \`authority.kind: adr\` sempre que existe uma change ou ADR causal. Somente sob perfil observado
544
+ \`OFF\`, sem context/change/lease ou ADR causal real, use \`authority.kind: native\`, com
545
+ \`Issue #NNN\` e design versionado sob \`docs/superpowers/specs/\` ou \`plans/\`.
546
+ 2. Crie um JSON conforme \`schema/commit-message-v1.schema.json\`. Não declare \`fresh\`,
547
+ \`verified\`, \`tasks\` ou \`tests\`: o runtime rederiva prova, digest SHA-256 do artefato,
548
+ binding completo de identidade/snapshot/tasks/spec/config e Scope do diff staged. Expected
549
+ ausente, envelope vazio, task pendente,
550
+ verdict sem selos/cobertura/independência ou
551
+ receipt sem chain/observation válidos falham fechados. \`Co-Authored-By\` é omitido
552
+ enquanto não houver identidade registrada confiável.
553
+ 3. Com os arquivos já staged, rode
554
+ \`wendkeep commit context --input <arquivo.json>\`. O CLI calcula o hash do index e grava o
555
+ contexto sanitizado dentro de \`.git\`, fora do working tree.
556
+ 4. Rode \`git commit\` normalmente. Os hooks opt-in revalidam contexto, hash, privacidade e
557
+ trivialidade antes de consumir o contexto. Instale-os
558
+ explicitamente com \`wendkeep init --git-commit-hooks --yes\`.
559
+ 5. Para diagnóstico, rode \`wendkeep doctor\`; para limpar contexto abandonado, use
560
+ \`wendkeep commit context --clear\`.
561
+
562
+ Não use \`--no-verify\` para implementação. Amend, merge e squash preservam a mensagem existente e
563
+ não recebem conteúdo inventado. A validação remota do PR continua sendo a defesa contra bypass.
564
+ `;
565
+
566
+ const WK_COMMIT_EN = `# Evidence-based universal commit
567
+
568
+ Use this skill to prepare implementation commits governed by the WendKeep message contract. The
569
+ generator receives sanitized references and a staged-diff summary only; never read, copy, or publish
570
+ the Vault, \`.brain\`, session registries, tokens, or secrets into a commit message.
571
+
572
+ ## Flow
573
+
574
+ 1. Confirm that authority and evidence references are current. The draft **does not accept**
575
+ caller-controlled \`tasks\` or \`tests\`; runtime derives tasks from completed Task Contracts.
576
+ Tests come only from a collector-executed \`[sensor:<id>]\`.
577
+ Envelope sensors must exactly match reexecution; only reexecution emits Tests.
578
+ \`[phase:verify]\` alone never proves execution, and range re-executes the sensor at the exact
579
+ SHA and re-derives authority/artifacts/task/spec/Scope/config there. Envelope/Verdict/receipt/TDD
580
+ stay local-only and are omitted from remote Evidence; never publish their local IDs. Tasks with
581
+ \`[req:]\` require a versioned sanitized \`spec\`. Use \`authority.kind: adr\`
582
+ whenever a causal change or ADR exists. Only under observed profile \`OFF\`, with no real causal
583
+ context/change/lease or ADR, use \`authority.kind: native\` with \`Issue #NNN\` and a versioned
584
+ design under \`docs/superpowers/specs/\` or \`plans/\`.
585
+ 2. Create JSON matching \`schema/commit-message-v1.schema.json\`. Do not claim \`fresh\`,
586
+ \`verified\`, \`tasks\`, or \`tests\`: runtime re-derives proof, artifact SHA-256, canonical
587
+ full identity/snapshot/tasks/spec/config binding, and staged-diff Scope. Missing expected fields,
588
+ empty envelopes, pending tasks, verdicts without
589
+ seals/coverage/independence, and receipts
590
+ without a valid chain/observation fail closed. \`Co-Authored-By\` is omitted
591
+ until a trusted identity registry can resolve it.
592
+ 3. After staging the files, run \`wendkeep commit context --input <file.json>\`. The CLI hashes the
593
+ Git index and stores the sanitized context inside \`.git\`, outside the working tree.
594
+ 4. Run \`git commit\` normally. The opt-in hooks revalidate context, hash, privacy, and triviality
595
+ before consuming context. Install them
596
+ explicitly with \`wendkeep init --git-commit-hooks --yes\`.
597
+ 5. Run \`wendkeep doctor\` for diagnostics; abandon a context with
598
+ \`wendkeep commit context --clear\`.
599
+
600
+ Do not use \`--no-verify\` for implementation work. Amend, merge, and squash preserve the existing
601
+ message and never receive invented content. The PR range check remains the remote bypass defense.
602
+ `;
603
+
527
604
  // --- bundled templates (shipped alongside the relevant SKILL.md) -------------
528
605
 
529
606
  // Shared, language-neutral verdict skeleton for the independent verify pass.
@@ -709,6 +786,7 @@ const WK_SKILLS_PT = [
709
786
  skill('wk-brainstorming', 'Use quando a ideia ainda é vaga ou o usuário quer discutir/planejar uma feature (inclusive em plan mode) — vira design aprovado, com closure gate e tabela out-of-scope, antes de código.', BRAINSTORMING, [{ name: 'design-template.md', content: DESIGN_TEMPLATE_PT }]),
710
787
  skill('wk-planning', 'Use após um design aprovado ou um plano aceito (inclusive plan mode) — decompõe em plano de tarefas TDD bite-sized e registra na change ativa.', PLANNING, [{ name: 'plan-template.md', content: PLAN_TEMPLATE_PT }]),
711
788
  skill('wk-verify', 'Use no verify deep — passe independente read-only (autor≠verificador) que re-deriva a cobertura do spec e grava verdict.json.', VERIFY, [{ name: 'spec-reviewer-prompt.md', content: REVIEWER_PROMPT_PT }, { name: 'verdict-template.json', content: VERDICT_TEMPLATE }]),
789
+ skill('wk-commit', 'Use ao preparar commits de implementação baseados em autoridade causal, tarefas, testes e evidências verificadas, com hooks Git opt-in e privacidade local.', WK_COMMIT),
712
790
  ];
713
791
 
714
792
  const WK_SKILLS_EN = [
@@ -718,6 +796,7 @@ const WK_SKILLS_EN = [
718
796
  skill('wk-brainstorming', 'Use when the idea is still vague or the user wants to discuss/plan a feature (plan mode included) — turns it into an approved design, with a closure gate and out-of-scope table, before code.', BRAINSTORMING_EN, [{ name: 'design-template.md', content: DESIGN_TEMPLATE_EN }]),
719
797
  skill('wk-planning', 'Use after an approved design or an accepted plan (plan mode included) — decomposes it into a bite-sized TDD task plan recorded in the active change.', PLANNING_EN, [{ name: 'plan-template.md', content: PLAN_TEMPLATE_EN }]),
720
798
  skill('wk-verify', 'Use in verify deep — an independent read-only pass (author≠verifier) that re-derives spec coverage and writes verdict.json.', VERIFY_EN, [{ name: 'spec-reviewer-prompt.md', content: REVIEWER_PROMPT_EN }, { name: 'verdict-template.json', content: VERDICT_TEMPLATE }]),
799
+ skill('wk-commit', 'Use when preparing implementation commits from causal authority, tasks, tests, and verified evidence, with opt-in Git hooks and local privacy.', WK_COMMIT_EN),
721
800
  ];
722
801
 
723
802
  // Skill set for a locale. WK_SKILLS stays the pt-BR set for back-compat.