clawvault 2.5.4 → 2.6.1

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 (89) hide show
  1. package/README.md +159 -159
  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 +75 -72
  25. package/bin/register-template-commands.test.js +87 -0
  26. package/bin/register-vault-operations-commands.js +300 -300
  27. package/bin/test-helpers/cli-command-fixtures.js +119 -119
  28. package/dashboard/lib/graph-diff.js +104 -104
  29. package/dashboard/lib/graph-diff.test.js +75 -75
  30. package/dashboard/lib/vault-parser.js +556 -556
  31. package/dashboard/lib/vault-parser.test.js +254 -254
  32. package/dashboard/public/app.js +796 -796
  33. package/dashboard/public/index.html +52 -52
  34. package/dashboard/public/styles.css +221 -221
  35. package/dashboard/server.js +374 -374
  36. package/dist/{chunk-2YDBJS7M.js → chunk-3BTHWPMB.js} +1 -1
  37. package/dist/{chunk-J5EMBUPK.js → chunk-4OXMU5S2.js} +1 -1
  38. package/dist/{chunk-GSD4ALSI.js → chunk-4VRIMU4O.js} +1 -1
  39. package/dist/{chunk-5GZFTAL7.js → chunk-AZYOKJYC.js} +128 -42
  40. package/dist/{chunk-K3CDT7IH.js → chunk-HIHOUSXS.js} +2 -2
  41. package/dist/{chunk-IZEY5S74.js → chunk-IEVLHNLU.js} +1 -1
  42. package/dist/{chunk-OSMS7QIG.js → chunk-ME37YNW3.js} +2 -2
  43. package/dist/chunk-MFAWT5O5.js +301 -0
  44. package/dist/{chunk-TPDH3JPP.js → chunk-PBEE567J.js} +1 -1
  45. package/dist/{chunk-S2IG7VNM.js → chunk-Q2J5YTUF.js} +2 -2
  46. package/dist/{chunk-IOALNTAN.js → chunk-QWQ3TIKS.js} +103 -29
  47. package/dist/{chunk-YCVDVI5B.js → chunk-R2MIW5G7.js} +1 -1
  48. package/dist/{chunk-4IV3R2F5.js → chunk-R6SXNSFD.js} +2 -2
  49. package/dist/{chunk-YOSEUUNB.js → chunk-T76H47ZS.js} +1 -1
  50. package/dist/{chunk-JDLOL2PL.js → chunk-TLGBDTYT.js} +3 -3
  51. package/dist/{chunk-W2HNZC22.js → chunk-UEOUADMO.js} +1 -1
  52. package/dist/cli/index.js +12 -10
  53. package/dist/commands/backlog.js +3 -1
  54. package/dist/commands/blocked.js +3 -1
  55. package/dist/commands/canvas.js +3 -1
  56. package/dist/commands/doctor.js +7 -5
  57. package/dist/commands/inject.js +2 -2
  58. package/dist/commands/kanban.js +4 -2
  59. package/dist/commands/observe.js +7 -5
  60. package/dist/commands/project.js +5 -3
  61. package/dist/commands/rebuild.js +6 -4
  62. package/dist/commands/reflect.js +3 -3
  63. package/dist/commands/replay.js +8 -6
  64. package/dist/commands/setup.js +1 -1
  65. package/dist/commands/sleep.js +7 -5
  66. package/dist/commands/status.js +6 -4
  67. package/dist/commands/task.js +4 -2
  68. package/dist/commands/template.d.ts +10 -1
  69. package/dist/commands/template.js +47 -55
  70. package/dist/commands/wake.js +2 -2
  71. package/dist/index.js +20 -19
  72. package/dist/lib/project-utils.js +4 -2
  73. package/dist/lib/task-utils.d.ts +14 -13
  74. package/dist/lib/task-utils.js +3 -1
  75. package/dist/lib/template-engine.d.ts +1 -0
  76. package/hooks/clawvault/HOOK.md +83 -83
  77. package/hooks/clawvault/handler.js +816 -816
  78. package/hooks/clawvault/handler.test.js +263 -263
  79. package/package.json +94 -94
  80. package/templates/checkpoint.md +34 -19
  81. package/templates/daily-note.md +34 -19
  82. package/templates/daily.md +34 -19
  83. package/templates/decision.md +39 -17
  84. package/templates/handoff.md +34 -19
  85. package/templates/lesson.md +31 -16
  86. package/templates/person.md +37 -19
  87. package/templates/project.md +84 -23
  88. package/templates/task.md +81 -0
  89. /package/dist/{chunk-AXKYDCNN.js → chunk-RVYA52PY.js} +0 -0
@@ -1,153 +1,153 @@
1
- /**
2
- * Runtime config command registrations backed by .clawvault.json.
3
- */
4
-
5
- function stringifyValue(value) {
6
- if (Array.isArray(value)) {
7
- if (value.every((item) => typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean')) {
8
- return value.join(', ');
9
- }
10
- return JSON.stringify(value);
11
- }
12
- if (value && typeof value === 'object') {
13
- return JSON.stringify(value);
14
- }
15
- if (value === null || value === undefined) {
16
- return '(unset)';
17
- }
18
- return String(value);
19
- }
20
-
21
- function flattenConfig(value, prefix = '') {
22
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
23
- return [{ key: prefix || '(root)', value: stringifyValue(value) }];
24
- }
25
-
26
- const rows = [];
27
- const keys = Object.keys(value).sort((left, right) => left.localeCompare(right));
28
- for (const key of keys) {
29
- const next = prefix ? `${prefix}.${key}` : key;
30
- const entry = value[key];
31
- if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
32
- rows.push(...flattenConfig(entry, next));
33
- continue;
34
- }
35
- rows.push({ key: next, value: stringifyValue(entry) });
36
- }
37
- return rows;
38
- }
39
-
40
- function printTable(rows) {
41
- if (rows.length === 0) {
42
- console.log('No config values found.');
43
- return;
44
- }
45
- const keyWidth = Math.max(
46
- 'KEY'.length,
47
- ...rows.map((row) => row.key.length)
48
- );
49
- const valueWidth = Math.max(
50
- 'VALUE'.length,
51
- ...rows.map((row) => row.value.length)
52
- );
53
- const header = `${'KEY'.padEnd(keyWidth)} ${'VALUE'.padEnd(valueWidth)}`;
54
- const divider = `${'-'.repeat(keyWidth)} ${'-'.repeat(valueWidth)}`;
55
- console.log(header);
56
- console.log(divider);
57
- for (const row of rows) {
58
- console.log(`${row.key.padEnd(keyWidth)} ${row.value}`);
59
- }
60
- }
61
-
62
- function normalizeConfigKey(key) {
63
- return String(key || '').trim();
64
- }
65
-
66
- export function registerConfigCommands(program, { chalk, resolveVaultPath }) {
67
- const config = program
68
- .command('config')
69
- .description('Read and modify runtime configuration');
70
-
71
- config
72
- .command('get <key>')
73
- .description('Read a runtime config value (dot-notation supported)')
74
- .option('-v, --vault <path>', 'Vault path')
75
- .action(async (key, options) => {
76
- try {
77
- const { getConfigValue, SUPPORTED_CONFIG_KEYS } = await import('../dist/index.js');
78
- const normalizedKey = normalizeConfigKey(key);
79
- if (!SUPPORTED_CONFIG_KEYS.includes(normalizedKey)) {
80
- throw new Error(`Unsupported config key: ${normalizedKey}`);
81
- }
82
-
83
- const value = getConfigValue(resolveVaultPath(options.vault), normalizedKey);
84
- if (Array.isArray(value)) {
85
- console.log(value.join(','));
86
- return;
87
- }
88
- if (value && typeof value === 'object') {
89
- console.log(JSON.stringify(value, null, 2));
90
- return;
91
- }
92
- console.log(value === undefined || value === null ? '' : String(value));
93
- } catch (err) {
94
- console.error(chalk.red(`Error: ${err.message}`));
95
- process.exit(1);
96
- }
97
- });
98
-
99
- config
100
- .command('set <key> <value>')
101
- .description('Set a runtime config value')
102
- .option('-v, --vault <path>', 'Vault path')
103
- .action(async (key, value, options) => {
104
- try {
105
- const { setConfigValue, SUPPORTED_CONFIG_KEYS } = await import('../dist/index.js');
106
- const normalizedKey = normalizeConfigKey(key);
107
- if (!SUPPORTED_CONFIG_KEYS.includes(normalizedKey)) {
108
- throw new Error(`Unsupported config key: ${normalizedKey}`);
109
- }
110
- const result = setConfigValue(resolveVaultPath(options.vault), normalizedKey, value);
111
- console.log(chalk.green(`✓ Updated ${normalizedKey}`));
112
- console.log(chalk.dim(` Value: ${stringifyValue(result.value)}`));
113
- } catch (err) {
114
- console.error(chalk.red(`Error: ${err.message}`));
115
- process.exit(1);
116
- }
117
- });
118
-
119
- config
120
- .command('list')
121
- .description('List all config values')
122
- .option('-v, --vault <path>', 'Vault path')
123
- .action(async (options) => {
124
- try {
125
- const { listConfig } = await import('../dist/index.js');
126
- const values = listConfig(resolveVaultPath(options.vault));
127
- const rows = flattenConfig(values);
128
- printTable(rows);
129
- } catch (err) {
130
- console.error(chalk.red(`Error: ${err.message}`));
131
- process.exit(1);
132
- }
133
- });
134
-
135
- config
136
- .command('reset')
137
- .description('Reset runtime config values to defaults')
138
- .option('--confirm', 'Confirm reset (required)')
139
- .option('-v, --vault <path>', 'Vault path')
140
- .action(async (options) => {
141
- try {
142
- if (!options.confirm) {
143
- throw new Error('Refusing to reset config without --confirm.');
144
- }
145
- const { resetConfig } = await import('../dist/index.js');
146
- resetConfig(resolveVaultPath(options.vault));
147
- console.log(chalk.green('✓ Config reset to defaults'));
148
- } catch (err) {
149
- console.error(chalk.red(`Error: ${err.message}`));
150
- process.exit(1);
151
- }
152
- });
153
- }
1
+ /**
2
+ * Runtime config command registrations backed by .clawvault.json.
3
+ */
4
+
5
+ function stringifyValue(value) {
6
+ if (Array.isArray(value)) {
7
+ if (value.every((item) => typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean')) {
8
+ return value.join(', ');
9
+ }
10
+ return JSON.stringify(value);
11
+ }
12
+ if (value && typeof value === 'object') {
13
+ return JSON.stringify(value);
14
+ }
15
+ if (value === null || value === undefined) {
16
+ return '(unset)';
17
+ }
18
+ return String(value);
19
+ }
20
+
21
+ function flattenConfig(value, prefix = '') {
22
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
23
+ return [{ key: prefix || '(root)', value: stringifyValue(value) }];
24
+ }
25
+
26
+ const rows = [];
27
+ const keys = Object.keys(value).sort((left, right) => left.localeCompare(right));
28
+ for (const key of keys) {
29
+ const next = prefix ? `${prefix}.${key}` : key;
30
+ const entry = value[key];
31
+ if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
32
+ rows.push(...flattenConfig(entry, next));
33
+ continue;
34
+ }
35
+ rows.push({ key: next, value: stringifyValue(entry) });
36
+ }
37
+ return rows;
38
+ }
39
+
40
+ function printTable(rows) {
41
+ if (rows.length === 0) {
42
+ console.log('No config values found.');
43
+ return;
44
+ }
45
+ const keyWidth = Math.max(
46
+ 'KEY'.length,
47
+ ...rows.map((row) => row.key.length)
48
+ );
49
+ const valueWidth = Math.max(
50
+ 'VALUE'.length,
51
+ ...rows.map((row) => row.value.length)
52
+ );
53
+ const header = `${'KEY'.padEnd(keyWidth)} ${'VALUE'.padEnd(valueWidth)}`;
54
+ const divider = `${'-'.repeat(keyWidth)} ${'-'.repeat(valueWidth)}`;
55
+ console.log(header);
56
+ console.log(divider);
57
+ for (const row of rows) {
58
+ console.log(`${row.key.padEnd(keyWidth)} ${row.value}`);
59
+ }
60
+ }
61
+
62
+ function normalizeConfigKey(key) {
63
+ return String(key || '').trim();
64
+ }
65
+
66
+ export function registerConfigCommands(program, { chalk, resolveVaultPath }) {
67
+ const config = program
68
+ .command('config')
69
+ .description('Read and modify runtime configuration');
70
+
71
+ config
72
+ .command('get <key>')
73
+ .description('Read a runtime config value (dot-notation supported)')
74
+ .option('-v, --vault <path>', 'Vault path')
75
+ .action(async (key, options) => {
76
+ try {
77
+ const { getConfigValue, SUPPORTED_CONFIG_KEYS } = await import('../dist/index.js');
78
+ const normalizedKey = normalizeConfigKey(key);
79
+ if (!SUPPORTED_CONFIG_KEYS.includes(normalizedKey)) {
80
+ throw new Error(`Unsupported config key: ${normalizedKey}`);
81
+ }
82
+
83
+ const value = getConfigValue(resolveVaultPath(options.vault), normalizedKey);
84
+ if (Array.isArray(value)) {
85
+ console.log(value.join(','));
86
+ return;
87
+ }
88
+ if (value && typeof value === 'object') {
89
+ console.log(JSON.stringify(value, null, 2));
90
+ return;
91
+ }
92
+ console.log(value === undefined || value === null ? '' : String(value));
93
+ } catch (err) {
94
+ console.error(chalk.red(`Error: ${err.message}`));
95
+ process.exit(1);
96
+ }
97
+ });
98
+
99
+ config
100
+ .command('set <key> <value>')
101
+ .description('Set a runtime config value')
102
+ .option('-v, --vault <path>', 'Vault path')
103
+ .action(async (key, value, options) => {
104
+ try {
105
+ const { setConfigValue, SUPPORTED_CONFIG_KEYS } = await import('../dist/index.js');
106
+ const normalizedKey = normalizeConfigKey(key);
107
+ if (!SUPPORTED_CONFIG_KEYS.includes(normalizedKey)) {
108
+ throw new Error(`Unsupported config key: ${normalizedKey}`);
109
+ }
110
+ const result = setConfigValue(resolveVaultPath(options.vault), normalizedKey, value);
111
+ console.log(chalk.green(`✓ Updated ${normalizedKey}`));
112
+ console.log(chalk.dim(` Value: ${stringifyValue(result.value)}`));
113
+ } catch (err) {
114
+ console.error(chalk.red(`Error: ${err.message}`));
115
+ process.exit(1);
116
+ }
117
+ });
118
+
119
+ config
120
+ .command('list')
121
+ .description('List all config values')
122
+ .option('-v, --vault <path>', 'Vault path')
123
+ .action(async (options) => {
124
+ try {
125
+ const { listConfig } = await import('../dist/index.js');
126
+ const values = listConfig(resolveVaultPath(options.vault));
127
+ const rows = flattenConfig(values);
128
+ printTable(rows);
129
+ } catch (err) {
130
+ console.error(chalk.red(`Error: ${err.message}`));
131
+ process.exit(1);
132
+ }
133
+ });
134
+
135
+ config
136
+ .command('reset')
137
+ .description('Reset runtime config values to defaults')
138
+ .option('--confirm', 'Confirm reset (required)')
139
+ .option('-v, --vault <path>', 'Vault path')
140
+ .action(async (options) => {
141
+ try {
142
+ if (!options.confirm) {
143
+ throw new Error('Refusing to reset config without --confirm.');
144
+ }
145
+ const { resetConfig } = await import('../dist/index.js');
146
+ resetConfig(resolveVaultPath(options.vault));
147
+ console.log(chalk.green('✓ Config reset to defaults'));
148
+ } catch (err) {
149
+ console.error(chalk.red(`Error: ${err.message}`));
150
+ process.exit(1);
151
+ }
152
+ });
153
+ }
@@ -1,121 +1,121 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { Command } from 'commander';
3
- import { registerConfigCommands } from './register-config-commands.js';
4
- import { registerRouteCommands } from './register-route-commands.js';
5
- import { chalkStub } from './test-helpers/cli-command-fixtures.js';
6
-
7
- const {
8
- getConfigValueMock,
9
- setConfigValueMock,
10
- listConfigMock,
11
- resetConfigMock,
12
- addRouteRuleMock,
13
- listRouteRulesMock,
14
- removeRouteRuleMock,
15
- testRouteRuleMock
16
- } = vi.hoisted(() => ({
17
- getConfigValueMock: vi.fn(),
18
- setConfigValueMock: vi.fn(),
19
- listConfigMock: vi.fn(),
20
- resetConfigMock: vi.fn(),
21
- addRouteRuleMock: vi.fn(),
22
- listRouteRulesMock: vi.fn(),
23
- removeRouteRuleMock: vi.fn(),
24
- testRouteRuleMock: vi.fn()
25
- }));
26
-
27
- vi.mock('../dist/index.js', () => ({
28
- SUPPORTED_CONFIG_KEYS: [
29
- 'name',
30
- 'categories',
31
- 'theme',
32
- 'observe.model',
33
- 'observe.provider',
34
- 'observer.compression.provider',
35
- 'observer.compression.model',
36
- 'observer.compression.baseUrl',
37
- 'observer.compression.apiKey',
38
- 'context.maxResults',
39
- 'context.defaultProfile',
40
- 'graph.maxHops',
41
- 'inject.maxResults',
42
- 'inject.useLlm',
43
- 'inject.scope'
44
- ],
45
- getConfigValue: getConfigValueMock,
46
- setConfigValue: setConfigValueMock,
47
- listConfig: listConfigMock,
48
- resetConfig: resetConfigMock,
49
- addRouteRule: addRouteRuleMock,
50
- listRouteRules: listRouteRulesMock,
51
- removeRouteRule: removeRouteRuleMock,
52
- testRouteRule: testRouteRuleMock
53
- }));
54
-
55
- function buildProgram() {
56
- const program = new Command();
57
- const resolveVaultPath = (value) => value ?? '/vault';
58
- registerConfigCommands(program, { chalk: chalkStub, resolveVaultPath });
59
- registerRouteCommands(program, { chalk: chalkStub, resolveVaultPath });
60
- return program;
61
- }
62
-
63
- async function runCommand(args) {
64
- const program = buildProgram();
65
- await program.parseAsync(args, { from: 'user' });
66
- }
67
-
68
- beforeEach(() => {
69
- vi.clearAllMocks();
70
- getConfigValueMock.mockReturnValue('demo-vault');
71
- setConfigValueMock.mockReturnValue({ value: 'demo-vault' });
72
- listConfigMock.mockReturnValue({ name: 'demo-vault', context: { maxResults: 5 } });
73
- resetConfigMock.mockReturnValue(undefined);
74
- addRouteRuleMock.mockReturnValue({ pattern: 'Pedro', target: 'people/pedro', priority: 1 });
75
- listRouteRulesMock.mockReturnValue([{ pattern: 'Pedro', target: 'people/pedro', priority: 1 }]);
76
- removeRouteRuleMock.mockReturnValue(true);
77
- testRouteRuleMock.mockReturnValue({ pattern: 'Pedro', target: 'people/pedro', priority: 1 });
78
- });
79
-
80
- describe('config and route command registrations', () => {
81
- it('executes config get/set/list subcommands', async () => {
82
- const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
83
- try {
84
- await runCommand(['config', 'get', 'name']);
85
- expect(getConfigValueMock).toHaveBeenCalledWith('/vault', 'name');
86
-
87
- await runCommand(['config', 'set', 'categories', 'people,projects']);
88
- expect(setConfigValueMock).toHaveBeenCalledWith('/vault', 'categories', 'people,projects');
89
-
90
- await runCommand(['config', 'list']);
91
- expect(listConfigMock).toHaveBeenCalledWith('/vault');
92
-
93
- const output = logSpy.mock.calls.map((call) => call.join(' ')).join('\n');
94
- expect(output).toContain('context.maxResults');
95
- } finally {
96
- logSpy.mockRestore();
97
- }
98
- });
99
-
100
- it('executes route add/remove/test subcommands', async () => {
101
- const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
102
- try {
103
- await runCommand(['route', 'add', 'Pedro', 'people/pedro']);
104
- expect(addRouteRuleMock).toHaveBeenCalledWith('/vault', 'Pedro', 'people/pedro');
105
-
106
- await runCommand(['route', 'remove', 'Pedro']);
107
- expect(removeRouteRuleMock).toHaveBeenCalledWith('/vault', 'Pedro');
108
-
109
- await runCommand(['route', 'test', 'Talked to Pedro']);
110
- expect(testRouteRuleMock).toHaveBeenCalledWith('/vault', 'Talked to Pedro');
111
-
112
- await runCommand(['route', 'list']);
113
- expect(listRouteRulesMock).toHaveBeenCalledWith('/vault');
114
-
115
- const output = logSpy.mock.calls.map((call) => call.join(' ')).join('\n');
116
- expect(output).toContain('Route matched');
117
- } finally {
118
- logSpy.mockRestore();
119
- }
120
- });
121
- });
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { Command } from 'commander';
3
+ import { registerConfigCommands } from './register-config-commands.js';
4
+ import { registerRouteCommands } from './register-route-commands.js';
5
+ import { chalkStub } from './test-helpers/cli-command-fixtures.js';
6
+
7
+ const {
8
+ getConfigValueMock,
9
+ setConfigValueMock,
10
+ listConfigMock,
11
+ resetConfigMock,
12
+ addRouteRuleMock,
13
+ listRouteRulesMock,
14
+ removeRouteRuleMock,
15
+ testRouteRuleMock
16
+ } = vi.hoisted(() => ({
17
+ getConfigValueMock: vi.fn(),
18
+ setConfigValueMock: vi.fn(),
19
+ listConfigMock: vi.fn(),
20
+ resetConfigMock: vi.fn(),
21
+ addRouteRuleMock: vi.fn(),
22
+ listRouteRulesMock: vi.fn(),
23
+ removeRouteRuleMock: vi.fn(),
24
+ testRouteRuleMock: vi.fn()
25
+ }));
26
+
27
+ vi.mock('../dist/index.js', () => ({
28
+ SUPPORTED_CONFIG_KEYS: [
29
+ 'name',
30
+ 'categories',
31
+ 'theme',
32
+ 'observe.model',
33
+ 'observe.provider',
34
+ 'observer.compression.provider',
35
+ 'observer.compression.model',
36
+ 'observer.compression.baseUrl',
37
+ 'observer.compression.apiKey',
38
+ 'context.maxResults',
39
+ 'context.defaultProfile',
40
+ 'graph.maxHops',
41
+ 'inject.maxResults',
42
+ 'inject.useLlm',
43
+ 'inject.scope'
44
+ ],
45
+ getConfigValue: getConfigValueMock,
46
+ setConfigValue: setConfigValueMock,
47
+ listConfig: listConfigMock,
48
+ resetConfig: resetConfigMock,
49
+ addRouteRule: addRouteRuleMock,
50
+ listRouteRules: listRouteRulesMock,
51
+ removeRouteRule: removeRouteRuleMock,
52
+ testRouteRule: testRouteRuleMock
53
+ }));
54
+
55
+ function buildProgram() {
56
+ const program = new Command();
57
+ const resolveVaultPath = (value) => value ?? '/vault';
58
+ registerConfigCommands(program, { chalk: chalkStub, resolveVaultPath });
59
+ registerRouteCommands(program, { chalk: chalkStub, resolveVaultPath });
60
+ return program;
61
+ }
62
+
63
+ async function runCommand(args) {
64
+ const program = buildProgram();
65
+ await program.parseAsync(args, { from: 'user' });
66
+ }
67
+
68
+ beforeEach(() => {
69
+ vi.clearAllMocks();
70
+ getConfigValueMock.mockReturnValue('demo-vault');
71
+ setConfigValueMock.mockReturnValue({ value: 'demo-vault' });
72
+ listConfigMock.mockReturnValue({ name: 'demo-vault', context: { maxResults: 5 } });
73
+ resetConfigMock.mockReturnValue(undefined);
74
+ addRouteRuleMock.mockReturnValue({ pattern: 'Pedro', target: 'people/pedro', priority: 1 });
75
+ listRouteRulesMock.mockReturnValue([{ pattern: 'Pedro', target: 'people/pedro', priority: 1 }]);
76
+ removeRouteRuleMock.mockReturnValue(true);
77
+ testRouteRuleMock.mockReturnValue({ pattern: 'Pedro', target: 'people/pedro', priority: 1 });
78
+ });
79
+
80
+ describe('config and route command registrations', () => {
81
+ it('executes config get/set/list subcommands', async () => {
82
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
83
+ try {
84
+ await runCommand(['config', 'get', 'name']);
85
+ expect(getConfigValueMock).toHaveBeenCalledWith('/vault', 'name');
86
+
87
+ await runCommand(['config', 'set', 'categories', 'people,projects']);
88
+ expect(setConfigValueMock).toHaveBeenCalledWith('/vault', 'categories', 'people,projects');
89
+
90
+ await runCommand(['config', 'list']);
91
+ expect(listConfigMock).toHaveBeenCalledWith('/vault');
92
+
93
+ const output = logSpy.mock.calls.map((call) => call.join(' ')).join('\n');
94
+ expect(output).toContain('context.maxResults');
95
+ } finally {
96
+ logSpy.mockRestore();
97
+ }
98
+ });
99
+
100
+ it('executes route add/remove/test subcommands', async () => {
101
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
102
+ try {
103
+ await runCommand(['route', 'add', 'Pedro', 'people/pedro']);
104
+ expect(addRouteRuleMock).toHaveBeenCalledWith('/vault', 'Pedro', 'people/pedro');
105
+
106
+ await runCommand(['route', 'remove', 'Pedro']);
107
+ expect(removeRouteRuleMock).toHaveBeenCalledWith('/vault', 'Pedro');
108
+
109
+ await runCommand(['route', 'test', 'Talked to Pedro']);
110
+ expect(testRouteRuleMock).toHaveBeenCalledWith('/vault', 'Talked to Pedro');
111
+
112
+ await runCommand(['route', 'list']);
113
+ expect(listRouteRulesMock).toHaveBeenCalledWith('/vault');
114
+
115
+ const output = logSpy.mock.calls.map((call) => call.join(' ')).join('\n');
116
+ expect(output).toContain('Route matched');
117
+ } finally {
118
+ logSpy.mockRestore();
119
+ }
120
+ });
121
+ });