clawvault 2.5.2 → 2.5.3

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 (55) hide show
  1. package/README.md +159 -200
  2. package/bin/clawvault.js +111 -111
  3. package/bin/command-registration.test.js +166 -166
  4. package/bin/command-runtime.js +93 -93
  5. package/bin/command-runtime.test.js +154 -154
  6. package/bin/help-contract.test.js +39 -39
  7. package/bin/register-config-commands.js +153 -153
  8. package/bin/register-config-route-commands.test.js +121 -121
  9. package/bin/register-core-commands.js +237 -237
  10. package/bin/register-kanban-commands.js +56 -56
  11. package/bin/register-kanban-commands.test.js +83 -83
  12. package/bin/register-maintenance-commands.js +282 -282
  13. package/bin/register-project-commands.js +209 -209
  14. package/bin/register-project-commands.test.js +206 -206
  15. package/bin/register-query-commands.js +317 -317
  16. package/bin/register-query-commands.test.js +65 -65
  17. package/bin/register-resilience-commands.js +182 -182
  18. package/bin/register-resilience-commands.test.js +81 -81
  19. package/bin/register-route-commands.js +114 -114
  20. package/bin/register-session-lifecycle-commands.js +206 -206
  21. package/bin/register-tailscale-commands.js +106 -106
  22. package/bin/register-task-commands.js +348 -348
  23. package/bin/register-task-commands.test.js +69 -69
  24. package/bin/register-template-commands.js +72 -72
  25. package/bin/register-vault-operations-commands.js +300 -300
  26. package/bin/test-helpers/cli-command-fixtures.js +119 -119
  27. package/dashboard/lib/graph-diff.js +104 -104
  28. package/dashboard/lib/graph-diff.test.js +75 -75
  29. package/dashboard/lib/vault-parser.js +556 -556
  30. package/dashboard/lib/vault-parser.test.js +254 -254
  31. package/dashboard/public/app.js +796 -796
  32. package/dashboard/public/index.html +52 -52
  33. package/dashboard/public/styles.css +221 -221
  34. package/dashboard/server.js +374 -374
  35. package/dist/{chunk-HWUNREDJ.js → chunk-FG6RJMCN.js} +1 -1
  36. package/dist/{chunk-HRTPQQF2.js → chunk-IZEY5S74.js} +1 -1
  37. package/dist/{chunk-BHO7WSAY.js → chunk-LMEMZGUV.js} +1 -1
  38. package/dist/{chunk-PLZKZW4I.js → chunk-OSMS7QIG.js} +1 -1
  39. package/dist/cli/index.js +3 -3
  40. package/dist/commands/doctor.js +2 -2
  41. package/dist/commands/observe.js +2 -2
  42. package/dist/commands/status.js +1 -1
  43. package/dist/index.js +4 -4
  44. package/hooks/clawvault/HOOK.md +83 -74
  45. package/hooks/clawvault/handler.js +816 -816
  46. package/hooks/clawvault/handler.test.js +263 -263
  47. package/package.json +94 -125
  48. package/templates/checkpoint.md +19 -19
  49. package/templates/daily-note.md +19 -19
  50. package/templates/daily.md +19 -19
  51. package/templates/decision.md +17 -17
  52. package/templates/handoff.md +19 -19
  53. package/templates/lesson.md +16 -16
  54. package/templates/person.md +19 -19
  55. package/templates/project.md +23 -23
@@ -1,65 +1,65 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { Command } from 'commander';
3
- import { registerQueryCommands } from './register-query-commands.js';
4
- import {
5
- chalkStub,
6
- createGetVaultStub,
7
- stubResolveVaultPath
8
- } from './test-helpers/cli-command-fixtures.js';
9
-
10
- function buildProgram() {
11
- const program = new Command();
12
- registerQueryCommands(program, {
13
- chalk: chalkStub,
14
- getVault: createGetVaultStub({ find: async () => [], vsearch: async () => [] }),
15
- resolveVaultPath: stubResolveVaultPath,
16
- QmdUnavailableError: class extends Error {},
17
- printQmdMissing: () => {}
18
- });
19
- return program;
20
- }
21
-
22
- describe('register-query-commands', () => {
23
- it('documents inject command options and config-backed defaults', () => {
24
- const program = buildProgram();
25
- const injectCommand = program.commands.find((command) => command.name() === 'inject');
26
- expect(injectCommand).toBeDefined();
27
-
28
- const injectFlags = injectCommand?.options.map((option) => option.flags) ?? [];
29
- expect(injectFlags).toEqual(expect.arrayContaining([
30
- '-n, --max-results <n>',
31
- '--scope <scope>',
32
- '--enable-llm',
33
- '--disable-llm',
34
- '--format <format>',
35
- '--model <model>',
36
- '-v, --vault <path>'
37
- ]));
38
-
39
- const maxResultsOption = injectCommand?.options.find((option) => option.flags === '-n, --max-results <n>');
40
- const scopeOption = injectCommand?.options.find((option) => option.flags === '--scope <scope>');
41
- const formatOption = injectCommand?.options.find((option) => option.flags === '--format <format>');
42
- expect(maxResultsOption?.description).toContain('inject.maxResults');
43
- expect(scopeOption?.description).toContain('inject.scope');
44
- expect(formatOption?.description).toContain('default: markdown');
45
- });
46
-
47
- it('documents observe extraction toggles and threshold defaults', () => {
48
- const program = buildProgram();
49
- const observeCommand = program.commands.find((command) => command.name() === 'observe');
50
- expect(observeCommand).toBeDefined();
51
-
52
- const observeFlags = observeCommand?.options.map((option) => option.flags) ?? [];
53
- expect(observeFlags).toEqual(expect.arrayContaining([
54
- '--extract-tasks',
55
- '--no-extract-tasks',
56
- '--threshold <n>',
57
- '--reflect-threshold <n>'
58
- ]));
59
-
60
- const thresholdOption = observeCommand?.options.find((option) => option.flags === '--threshold <n>');
61
- const reflectThresholdOption = observeCommand?.options.find((option) => option.flags === '--reflect-threshold <n>');
62
- expect(thresholdOption?.description).toContain('default: 30000');
63
- expect(reflectThresholdOption?.description).toContain('default: 40000');
64
- });
65
- });
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Command } from 'commander';
3
+ import { registerQueryCommands } from './register-query-commands.js';
4
+ import {
5
+ chalkStub,
6
+ createGetVaultStub,
7
+ stubResolveVaultPath
8
+ } from './test-helpers/cli-command-fixtures.js';
9
+
10
+ function buildProgram() {
11
+ const program = new Command();
12
+ registerQueryCommands(program, {
13
+ chalk: chalkStub,
14
+ getVault: createGetVaultStub({ find: async () => [], vsearch: async () => [] }),
15
+ resolveVaultPath: stubResolveVaultPath,
16
+ QmdUnavailableError: class extends Error {},
17
+ printQmdMissing: () => {}
18
+ });
19
+ return program;
20
+ }
21
+
22
+ describe('register-query-commands', () => {
23
+ it('documents inject command options and config-backed defaults', () => {
24
+ const program = buildProgram();
25
+ const injectCommand = program.commands.find((command) => command.name() === 'inject');
26
+ expect(injectCommand).toBeDefined();
27
+
28
+ const injectFlags = injectCommand?.options.map((option) => option.flags) ?? [];
29
+ expect(injectFlags).toEqual(expect.arrayContaining([
30
+ '-n, --max-results <n>',
31
+ '--scope <scope>',
32
+ '--enable-llm',
33
+ '--disable-llm',
34
+ '--format <format>',
35
+ '--model <model>',
36
+ '-v, --vault <path>'
37
+ ]));
38
+
39
+ const maxResultsOption = injectCommand?.options.find((option) => option.flags === '-n, --max-results <n>');
40
+ const scopeOption = injectCommand?.options.find((option) => option.flags === '--scope <scope>');
41
+ const formatOption = injectCommand?.options.find((option) => option.flags === '--format <format>');
42
+ expect(maxResultsOption?.description).toContain('inject.maxResults');
43
+ expect(scopeOption?.description).toContain('inject.scope');
44
+ expect(formatOption?.description).toContain('default: markdown');
45
+ });
46
+
47
+ it('documents observe extraction toggles and threshold defaults', () => {
48
+ const program = buildProgram();
49
+ const observeCommand = program.commands.find((command) => command.name() === 'observe');
50
+ expect(observeCommand).toBeDefined();
51
+
52
+ const observeFlags = observeCommand?.options.map((option) => option.flags) ?? [];
53
+ expect(observeFlags).toEqual(expect.arrayContaining([
54
+ '--extract-tasks',
55
+ '--no-extract-tasks',
56
+ '--threshold <n>',
57
+ '--reflect-threshold <n>'
58
+ ]));
59
+
60
+ const thresholdOption = observeCommand?.options.find((option) => option.flags === '--threshold <n>');
61
+ const reflectThresholdOption = observeCommand?.options.find((option) => option.flags === '--reflect-threshold <n>');
62
+ expect(thresholdOption?.description).toContain('default: 30000');
63
+ expect(reflectThresholdOption?.description).toContain('default: 40000');
64
+ });
65
+ });
@@ -1,182 +1,182 @@
1
- /**
2
- * Context-resilience and session-repair command registrations.
3
- */
4
-
5
- export function registerResilienceCommands(program, { chalk, resolveVaultPath }) {
6
- // === CHECKPOINT ===
7
- program
8
- .command('checkpoint')
9
- .description('Quick state checkpoint for context death resilience')
10
- .option('--working-on <text>', 'What you are currently working on')
11
- .option('--focus <text>', 'Current focus area')
12
- .option('--blocked <text>', 'What is blocking progress')
13
- .option('--urgent', 'Trigger OpenClaw wake after checkpoint')
14
- .option('-v, --vault <path>', 'Vault path')
15
- .option('--json', 'Output as JSON')
16
- .action(async (options) => {
17
- try {
18
- const { checkpoint } = await import('../dist/commands/checkpoint.js');
19
- const data = await checkpoint({
20
- vaultPath: resolveVaultPath(options.vault),
21
- workingOn: options.workingOn,
22
- focus: options.focus,
23
- blocked: options.blocked,
24
- urgent: options.urgent
25
- });
26
-
27
- if (options.json) {
28
- console.log(JSON.stringify(data, null, 2));
29
- } else {
30
- console.log(chalk.green('✓ Checkpoint saved'));
31
- console.log(chalk.dim(` Timestamp: ${data.timestamp}`));
32
- if (data.workingOn) console.log(chalk.dim(` Working on: ${data.workingOn}`));
33
- if (data.focus) console.log(chalk.dim(` Focus: ${data.focus}`));
34
- if (data.blocked) console.log(chalk.dim(` Blocked: ${data.blocked}`));
35
- if (data.urgent) console.log(chalk.dim(' Urgent: yes'));
36
- }
37
- } catch (err) {
38
- console.error(chalk.red(`Error: ${err.message}`));
39
- process.exit(1);
40
- }
41
- });
42
-
43
- // === RECOVER ===
44
- program
45
- .command('recover')
46
- .description('Check for context death and recover state')
47
- .option('--clear', 'Clear the dirty death flag after recovery')
48
- .option('--check', 'Check dirty death flag without clearing it')
49
- .option('--list', 'List saved checkpoints (newest first)')
50
- .option('--verbose', 'Show full checkpoint and handoff content')
51
- .option('-v, --vault <path>', 'Vault path')
52
- .option('--json', 'Output as JSON')
53
- .action(async (options) => {
54
- try {
55
- if (options.check && options.list) {
56
- throw new Error('--check and --list cannot be used together.');
57
- }
58
-
59
- const {
60
- recover,
61
- formatRecoveryInfo,
62
- checkRecoveryStatus,
63
- formatRecoveryCheckStatus,
64
- listCheckpoints,
65
- formatCheckpointList
66
- } = await import('../dist/commands/recover.js');
67
- const vaultPath = resolveVaultPath(options.vault);
68
-
69
- if (options.check) {
70
- const status = await checkRecoveryStatus(vaultPath);
71
- if (options.json) {
72
- console.log(JSON.stringify(status, null, 2));
73
- } else {
74
- console.log(formatRecoveryCheckStatus(status));
75
- }
76
- return;
77
- }
78
-
79
- if (options.list) {
80
- const checkpoints = listCheckpoints(vaultPath);
81
- if (options.json) {
82
- console.log(JSON.stringify(checkpoints, null, 2));
83
- } else {
84
- console.log(formatCheckpointList(checkpoints));
85
- }
86
- return;
87
- }
88
-
89
- const info = await recover(vaultPath, {
90
- clearFlag: options.clear,
91
- verbose: options.verbose
92
- });
93
-
94
- if (options.json) {
95
- console.log(JSON.stringify(info, null, 2));
96
- } else {
97
- console.log(formatRecoveryInfo(info, { verbose: options.verbose }));
98
- }
99
- } catch (err) {
100
- console.error(chalk.red(`Error: ${err.message}`));
101
- process.exit(1);
102
- }
103
- });
104
-
105
- // === STATUS ===
106
- program
107
- .command('status')
108
- .description('Show vault health and status')
109
- .option('-v, --vault <path>', 'Vault path')
110
- .option('--json', 'Output as JSON')
111
- .action(async (options) => {
112
- try {
113
- const { statusCommand } = await import('../dist/commands/status.js');
114
- await statusCommand(resolveVaultPath(options.vault), { json: options.json });
115
- } catch (err) {
116
- console.error(chalk.red(`Error: ${err.message}`));
117
- process.exit(1);
118
- }
119
- });
120
-
121
- // === CLEAN-EXIT ===
122
- program
123
- .command('clean-exit')
124
- .description('Mark session as cleanly exited (clears dirty death flag)')
125
- .option('-v, --vault <path>', 'Vault path')
126
- .action(async (options) => {
127
- try {
128
- const { cleanExit } = await import('../dist/commands/checkpoint.js');
129
- await cleanExit(resolveVaultPath(options.vault));
130
- console.log(chalk.green('✓ Clean exit recorded'));
131
- } catch (err) {
132
- console.error(chalk.red(`Error: ${err.message}`));
133
- process.exit(1);
134
- }
135
- });
136
-
137
- // === REPAIR-SESSION ===
138
- program
139
- .command('repair-session')
140
- .description('Repair corrupted OpenClaw session transcripts')
141
- .option('-s, --session <id>', 'Session ID (defaults to current main session)')
142
- .option('-a, --agent <id>', 'Agent ID (defaults to configured agent)')
143
- .option('--backup', 'Create backup before repair (default: true)', true)
144
- .option('--no-backup', 'Skip backup creation')
145
- .option('--dry-run', 'Show what would be repaired without writing')
146
- .option('--list', 'List available sessions')
147
- .option('--json', 'Output as JSON')
148
- .action(async (options) => {
149
- try {
150
- const {
151
- repairSessionCommand,
152
- formatRepairResult,
153
- listAgentSessions
154
- } = await import('../dist/commands/repair-session.js');
155
-
156
- if (options.list) {
157
- console.log(listAgentSessions(options.agent));
158
- return;
159
- }
160
-
161
- const result = await repairSessionCommand({
162
- sessionId: options.session,
163
- agentId: options.agent,
164
- backup: options.backup,
165
- dryRun: options.dryRun
166
- });
167
-
168
- if (options.json) {
169
- console.log(JSON.stringify(result, null, 2));
170
- } else {
171
- console.log(formatRepairResult(result, { dryRun: options.dryRun }));
172
- }
173
-
174
- if (result.corruptedEntries.length > 0 && !result.repaired) {
175
- process.exit(1);
176
- }
177
- } catch (err) {
178
- console.error(chalk.red(`Error: ${err.message}`));
179
- process.exit(1);
180
- }
181
- });
182
- }
1
+ /**
2
+ * Context-resilience and session-repair command registrations.
3
+ */
4
+
5
+ export function registerResilienceCommands(program, { chalk, resolveVaultPath }) {
6
+ // === CHECKPOINT ===
7
+ program
8
+ .command('checkpoint')
9
+ .description('Quick state checkpoint for context death resilience')
10
+ .option('--working-on <text>', 'What you are currently working on')
11
+ .option('--focus <text>', 'Current focus area')
12
+ .option('--blocked <text>', 'What is blocking progress')
13
+ .option('--urgent', 'Trigger OpenClaw wake after checkpoint')
14
+ .option('-v, --vault <path>', 'Vault path')
15
+ .option('--json', 'Output as JSON')
16
+ .action(async (options) => {
17
+ try {
18
+ const { checkpoint } = await import('../dist/commands/checkpoint.js');
19
+ const data = await checkpoint({
20
+ vaultPath: resolveVaultPath(options.vault),
21
+ workingOn: options.workingOn,
22
+ focus: options.focus,
23
+ blocked: options.blocked,
24
+ urgent: options.urgent
25
+ });
26
+
27
+ if (options.json) {
28
+ console.log(JSON.stringify(data, null, 2));
29
+ } else {
30
+ console.log(chalk.green('✓ Checkpoint saved'));
31
+ console.log(chalk.dim(` Timestamp: ${data.timestamp}`));
32
+ if (data.workingOn) console.log(chalk.dim(` Working on: ${data.workingOn}`));
33
+ if (data.focus) console.log(chalk.dim(` Focus: ${data.focus}`));
34
+ if (data.blocked) console.log(chalk.dim(` Blocked: ${data.blocked}`));
35
+ if (data.urgent) console.log(chalk.dim(' Urgent: yes'));
36
+ }
37
+ } catch (err) {
38
+ console.error(chalk.red(`Error: ${err.message}`));
39
+ process.exit(1);
40
+ }
41
+ });
42
+
43
+ // === RECOVER ===
44
+ program
45
+ .command('recover')
46
+ .description('Check for context death and recover state')
47
+ .option('--clear', 'Clear the dirty death flag after recovery')
48
+ .option('--check', 'Check dirty death flag without clearing it')
49
+ .option('--list', 'List saved checkpoints (newest first)')
50
+ .option('--verbose', 'Show full checkpoint and handoff content')
51
+ .option('-v, --vault <path>', 'Vault path')
52
+ .option('--json', 'Output as JSON')
53
+ .action(async (options) => {
54
+ try {
55
+ if (options.check && options.list) {
56
+ throw new Error('--check and --list cannot be used together.');
57
+ }
58
+
59
+ const {
60
+ recover,
61
+ formatRecoveryInfo,
62
+ checkRecoveryStatus,
63
+ formatRecoveryCheckStatus,
64
+ listCheckpoints,
65
+ formatCheckpointList
66
+ } = await import('../dist/commands/recover.js');
67
+ const vaultPath = resolveVaultPath(options.vault);
68
+
69
+ if (options.check) {
70
+ const status = await checkRecoveryStatus(vaultPath);
71
+ if (options.json) {
72
+ console.log(JSON.stringify(status, null, 2));
73
+ } else {
74
+ console.log(formatRecoveryCheckStatus(status));
75
+ }
76
+ return;
77
+ }
78
+
79
+ if (options.list) {
80
+ const checkpoints = listCheckpoints(vaultPath);
81
+ if (options.json) {
82
+ console.log(JSON.stringify(checkpoints, null, 2));
83
+ } else {
84
+ console.log(formatCheckpointList(checkpoints));
85
+ }
86
+ return;
87
+ }
88
+
89
+ const info = await recover(vaultPath, {
90
+ clearFlag: options.clear,
91
+ verbose: options.verbose
92
+ });
93
+
94
+ if (options.json) {
95
+ console.log(JSON.stringify(info, null, 2));
96
+ } else {
97
+ console.log(formatRecoveryInfo(info, { verbose: options.verbose }));
98
+ }
99
+ } catch (err) {
100
+ console.error(chalk.red(`Error: ${err.message}`));
101
+ process.exit(1);
102
+ }
103
+ });
104
+
105
+ // === STATUS ===
106
+ program
107
+ .command('status')
108
+ .description('Show vault health and status')
109
+ .option('-v, --vault <path>', 'Vault path')
110
+ .option('--json', 'Output as JSON')
111
+ .action(async (options) => {
112
+ try {
113
+ const { statusCommand } = await import('../dist/commands/status.js');
114
+ await statusCommand(resolveVaultPath(options.vault), { json: options.json });
115
+ } catch (err) {
116
+ console.error(chalk.red(`Error: ${err.message}`));
117
+ process.exit(1);
118
+ }
119
+ });
120
+
121
+ // === CLEAN-EXIT ===
122
+ program
123
+ .command('clean-exit')
124
+ .description('Mark session as cleanly exited (clears dirty death flag)')
125
+ .option('-v, --vault <path>', 'Vault path')
126
+ .action(async (options) => {
127
+ try {
128
+ const { cleanExit } = await import('../dist/commands/checkpoint.js');
129
+ await cleanExit(resolveVaultPath(options.vault));
130
+ console.log(chalk.green('✓ Clean exit recorded'));
131
+ } catch (err) {
132
+ console.error(chalk.red(`Error: ${err.message}`));
133
+ process.exit(1);
134
+ }
135
+ });
136
+
137
+ // === REPAIR-SESSION ===
138
+ program
139
+ .command('repair-session')
140
+ .description('Repair corrupted OpenClaw session transcripts')
141
+ .option('-s, --session <id>', 'Session ID (defaults to current main session)')
142
+ .option('-a, --agent <id>', 'Agent ID (defaults to configured agent)')
143
+ .option('--backup', 'Create backup before repair (default: true)', true)
144
+ .option('--no-backup', 'Skip backup creation')
145
+ .option('--dry-run', 'Show what would be repaired without writing')
146
+ .option('--list', 'List available sessions')
147
+ .option('--json', 'Output as JSON')
148
+ .action(async (options) => {
149
+ try {
150
+ const {
151
+ repairSessionCommand,
152
+ formatRepairResult,
153
+ listAgentSessions
154
+ } = await import('../dist/commands/repair-session.js');
155
+
156
+ if (options.list) {
157
+ console.log(listAgentSessions(options.agent));
158
+ return;
159
+ }
160
+
161
+ const result = await repairSessionCommand({
162
+ sessionId: options.session,
163
+ agentId: options.agent,
164
+ backup: options.backup,
165
+ dryRun: options.dryRun
166
+ });
167
+
168
+ if (options.json) {
169
+ console.log(JSON.stringify(result, null, 2));
170
+ } else {
171
+ console.log(formatRepairResult(result, { dryRun: options.dryRun }));
172
+ }
173
+
174
+ if (result.corruptedEntries.length > 0 && !result.repaired) {
175
+ process.exit(1);
176
+ }
177
+ } catch (err) {
178
+ console.error(chalk.red(`Error: ${err.message}`));
179
+ process.exit(1);
180
+ }
181
+ });
182
+ }
@@ -1,81 +1,81 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { Command } from 'commander';
3
- import { registerResilienceCommands } from './register-resilience-commands.js';
4
- import { chalkStub } from './test-helpers/cli-command-fixtures.js';
5
-
6
- const {
7
- recoverMock,
8
- formatRecoveryInfoMock,
9
- checkRecoveryStatusMock,
10
- formatRecoveryCheckStatusMock,
11
- listCheckpointsMock,
12
- formatCheckpointListMock
13
- } = vi.hoisted(() => ({
14
- recoverMock: vi.fn(),
15
- formatRecoveryInfoMock: vi.fn(),
16
- checkRecoveryStatusMock: vi.fn(),
17
- formatRecoveryCheckStatusMock: vi.fn(),
18
- listCheckpointsMock: vi.fn(),
19
- formatCheckpointListMock: vi.fn()
20
- }));
21
-
22
- vi.mock('../dist/commands/recover.js', () => ({
23
- recover: recoverMock,
24
- formatRecoveryInfo: formatRecoveryInfoMock,
25
- checkRecoveryStatus: checkRecoveryStatusMock,
26
- formatRecoveryCheckStatus: formatRecoveryCheckStatusMock,
27
- listCheckpoints: listCheckpointsMock,
28
- formatCheckpointList: formatCheckpointListMock
29
- }));
30
-
31
- function buildProgram() {
32
- const program = new Command();
33
- registerResilienceCommands(program, {
34
- chalk: chalkStub,
35
- resolveVaultPath: (value) => value ?? '/vault'
36
- });
37
- return program;
38
- }
39
-
40
- async function runCommand(args) {
41
- const program = buildProgram();
42
- await program.parseAsync(args, { from: 'user' });
43
- }
44
-
45
- beforeEach(() => {
46
- vi.clearAllMocks();
47
- recoverMock.mockResolvedValue({ died: false });
48
- formatRecoveryInfoMock.mockReturnValue('recover');
49
- checkRecoveryStatusMock.mockResolvedValue({ died: false, deathTime: null, checkpoint: null });
50
- formatRecoveryCheckStatusMock.mockReturnValue('check');
51
- listCheckpointsMock.mockReturnValue([]);
52
- formatCheckpointListMock.mockReturnValue('list');
53
- });
54
-
55
- describe('register-resilience-commands', () => {
56
- it('routes recover --check through check helpers', async () => {
57
- const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
58
- try {
59
- await runCommand(['recover', '--check']);
60
- expect(checkRecoveryStatusMock).toHaveBeenCalledWith('/vault');
61
- expect(formatRecoveryCheckStatusMock).toHaveBeenCalled();
62
- expect(recoverMock).not.toHaveBeenCalled();
63
- expect(logSpy).toHaveBeenCalledWith('check');
64
- } finally {
65
- logSpy.mockRestore();
66
- }
67
- });
68
-
69
- it('routes recover --list through checkpoint listing helpers', async () => {
70
- const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
71
- try {
72
- await runCommand(['recover', '--list']);
73
- expect(listCheckpointsMock).toHaveBeenCalledWith('/vault');
74
- expect(formatCheckpointListMock).toHaveBeenCalled();
75
- expect(recoverMock).not.toHaveBeenCalled();
76
- expect(logSpy).toHaveBeenCalledWith('list');
77
- } finally {
78
- logSpy.mockRestore();
79
- }
80
- });
81
- });
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { Command } from 'commander';
3
+ import { registerResilienceCommands } from './register-resilience-commands.js';
4
+ import { chalkStub } from './test-helpers/cli-command-fixtures.js';
5
+
6
+ const {
7
+ recoverMock,
8
+ formatRecoveryInfoMock,
9
+ checkRecoveryStatusMock,
10
+ formatRecoveryCheckStatusMock,
11
+ listCheckpointsMock,
12
+ formatCheckpointListMock
13
+ } = vi.hoisted(() => ({
14
+ recoverMock: vi.fn(),
15
+ formatRecoveryInfoMock: vi.fn(),
16
+ checkRecoveryStatusMock: vi.fn(),
17
+ formatRecoveryCheckStatusMock: vi.fn(),
18
+ listCheckpointsMock: vi.fn(),
19
+ formatCheckpointListMock: vi.fn()
20
+ }));
21
+
22
+ vi.mock('../dist/commands/recover.js', () => ({
23
+ recover: recoverMock,
24
+ formatRecoveryInfo: formatRecoveryInfoMock,
25
+ checkRecoveryStatus: checkRecoveryStatusMock,
26
+ formatRecoveryCheckStatus: formatRecoveryCheckStatusMock,
27
+ listCheckpoints: listCheckpointsMock,
28
+ formatCheckpointList: formatCheckpointListMock
29
+ }));
30
+
31
+ function buildProgram() {
32
+ const program = new Command();
33
+ registerResilienceCommands(program, {
34
+ chalk: chalkStub,
35
+ resolveVaultPath: (value) => value ?? '/vault'
36
+ });
37
+ return program;
38
+ }
39
+
40
+ async function runCommand(args) {
41
+ const program = buildProgram();
42
+ await program.parseAsync(args, { from: 'user' });
43
+ }
44
+
45
+ beforeEach(() => {
46
+ vi.clearAllMocks();
47
+ recoverMock.mockResolvedValue({ died: false });
48
+ formatRecoveryInfoMock.mockReturnValue('recover');
49
+ checkRecoveryStatusMock.mockResolvedValue({ died: false, deathTime: null, checkpoint: null });
50
+ formatRecoveryCheckStatusMock.mockReturnValue('check');
51
+ listCheckpointsMock.mockReturnValue([]);
52
+ formatCheckpointListMock.mockReturnValue('list');
53
+ });
54
+
55
+ describe('register-resilience-commands', () => {
56
+ it('routes recover --check through check helpers', async () => {
57
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
58
+ try {
59
+ await runCommand(['recover', '--check']);
60
+ expect(checkRecoveryStatusMock).toHaveBeenCalledWith('/vault');
61
+ expect(formatRecoveryCheckStatusMock).toHaveBeenCalled();
62
+ expect(recoverMock).not.toHaveBeenCalled();
63
+ expect(logSpy).toHaveBeenCalledWith('check');
64
+ } finally {
65
+ logSpy.mockRestore();
66
+ }
67
+ });
68
+
69
+ it('routes recover --list through checkpoint listing helpers', async () => {
70
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
71
+ try {
72
+ await runCommand(['recover', '--list']);
73
+ expect(listCheckpointsMock).toHaveBeenCalledWith('/vault');
74
+ expect(formatCheckpointListMock).toHaveBeenCalled();
75
+ expect(recoverMock).not.toHaveBeenCalled();
76
+ expect(logSpy).toHaveBeenCalledWith('list');
77
+ } finally {
78
+ logSpy.mockRestore();
79
+ }
80
+ });
81
+ });