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,154 +1,154 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
- import path from 'path';
3
-
4
- const {
5
- spawnMock,
6
- resolveConfiguredVaultPathMock,
7
- clawvaultCtorMock,
8
- loadMock
9
- } = vi.hoisted(() => ({
10
- spawnMock: vi.fn(),
11
- resolveConfiguredVaultPathMock: vi.fn(),
12
- clawvaultCtorMock: vi.fn(),
13
- loadMock: vi.fn()
14
- }));
15
-
16
- vi.mock('child_process', () => ({
17
- spawn: spawnMock
18
- }));
19
-
20
- vi.mock('../dist/index.js', () => ({
21
- ClawVault: clawvaultCtorMock,
22
- resolveVaultPath: resolveConfiguredVaultPathMock,
23
- QmdUnavailableError: class QmdUnavailableError extends Error {},
24
- QMD_INSTALL_COMMAND: 'install-qmd'
25
- }));
26
-
27
- async function loadRuntimeModule() {
28
- vi.resetModules();
29
- return await import('./command-runtime.js');
30
- }
31
-
32
- beforeEach(() => {
33
- vi.clearAllMocks();
34
- clawvaultCtorMock.mockImplementation(() => ({
35
- load: loadMock
36
- }));
37
- });
38
-
39
- describe('command runtime helpers', () => {
40
- it('delegates vault path resolution and loads vaults', async () => {
41
- resolveConfiguredVaultPathMock.mockReturnValue('/resolved/vault');
42
- loadMock.mockResolvedValue(undefined);
43
- const { getVault, resolveVaultPath } = await loadRuntimeModule();
44
-
45
- const resolved = resolveVaultPath('/explicit');
46
- expect(resolveConfiguredVaultPathMock).toHaveBeenCalledWith({ explicitPath: '/explicit' });
47
- expect(resolved).toBe('/resolved/vault');
48
-
49
- await getVault('/explicit');
50
- expect(clawvaultCtorMock).toHaveBeenCalledWith('/resolved/vault');
51
- expect(loadMock).toHaveBeenCalled();
52
- });
53
-
54
- it('maps qmd ENOENT failures to QmdUnavailableError', async () => {
55
- const { runQmd, QmdUnavailableError } = await loadRuntimeModule();
56
- spawnMock.mockImplementation(() => {
57
- const handlers = {};
58
- const proc = {
59
- on: (event, handler) => {
60
- handlers[event] = handler;
61
- }
62
- };
63
- queueMicrotask(() => {
64
- handlers.error?.({ code: 'ENOENT' });
65
- });
66
- return proc;
67
- });
68
-
69
- await expect(runQmd(['update'])).rejects.toBeInstanceOf(QmdUnavailableError);
70
- });
71
-
72
- it('injects qmd index from environment when configured', async () => {
73
- const previous = process.env.CLAWVAULT_QMD_INDEX;
74
- process.env.CLAWVAULT_QMD_INDEX = 'clawvault-test';
75
-
76
- try {
77
- const { runQmd } = await loadRuntimeModule();
78
- spawnMock.mockImplementation((_command, _args) => {
79
- const handlers = {};
80
- const proc = {
81
- on: (event, handler) => {
82
- handlers[event] = handler;
83
- }
84
- };
85
- queueMicrotask(() => {
86
- handlers.close?.(0);
87
- });
88
- return proc;
89
- });
90
-
91
- await runQmd(['update']);
92
- expect(spawnMock).toHaveBeenCalledWith(
93
- 'qmd',
94
- ['--index', 'clawvault-test', 'update'],
95
- { stdio: 'inherit' }
96
- );
97
- } finally {
98
- if (previous === undefined) {
99
- delete process.env.CLAWVAULT_QMD_INDEX;
100
- } else {
101
- process.env.CLAWVAULT_QMD_INDEX = previous;
102
- }
103
- }
104
- });
105
-
106
- it('surfaces qmd non-zero exit codes as errors', async () => {
107
- const { runQmd } = await loadRuntimeModule();
108
- spawnMock.mockImplementation(() => {
109
- const handlers = {};
110
- const proc = {
111
- on: (event, handler) => {
112
- handlers[event] = handler;
113
- }
114
- };
115
- queueMicrotask(() => {
116
- handlers.close?.(2);
117
- });
118
- return proc;
119
- });
120
-
121
- await expect(runQmd(['update'])).rejects.toThrow('qmd exited with code 2');
122
- });
123
-
124
- it('exports and enforces argument/path sanitization helpers', async () => {
125
- const { sanitizeQmdArg, validatePathWithinBase } = await loadRuntimeModule();
126
- expect(sanitizeQmdArg('update')).toBe('update');
127
- expect(sanitizeQmdArg(42)).toBe('42');
128
- expect(() => sanitizeQmdArg('bad\0arg')).toThrow('contains null byte');
129
-
130
- const safePath = validatePathWithinBase('notes/today.md', '/tmp/vault');
131
- expect(safePath).toBe(path.resolve('/tmp/vault', 'notes/today.md'));
132
- expect(() => validatePathWithinBase('../etc/passwd', '/tmp/vault')).toThrow('Path traversal detected');
133
- });
134
-
135
- it('rejects qmd args with null-byte injection attempts', async () => {
136
- const { runQmd } = await loadRuntimeModule();
137
- await expect(runQmd(['up\0date'])).rejects.toThrow('contains null byte');
138
- expect(spawnMock).not.toHaveBeenCalled();
139
- });
140
-
141
- it('prints consistent qmd missing guidance', async () => {
142
- const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
143
- const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
144
- try {
145
- const { printQmdMissing } = await loadRuntimeModule();
146
- printQmdMissing();
147
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('ClawVault requires qmd.'));
148
- expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('install-qmd'));
149
- } finally {
150
- errorSpy.mockRestore();
151
- logSpy.mockRestore();
152
- }
153
- });
154
- });
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import path from 'path';
3
+
4
+ const {
5
+ spawnMock,
6
+ resolveConfiguredVaultPathMock,
7
+ clawvaultCtorMock,
8
+ loadMock
9
+ } = vi.hoisted(() => ({
10
+ spawnMock: vi.fn(),
11
+ resolveConfiguredVaultPathMock: vi.fn(),
12
+ clawvaultCtorMock: vi.fn(),
13
+ loadMock: vi.fn()
14
+ }));
15
+
16
+ vi.mock('child_process', () => ({
17
+ spawn: spawnMock
18
+ }));
19
+
20
+ vi.mock('../dist/index.js', () => ({
21
+ ClawVault: clawvaultCtorMock,
22
+ resolveVaultPath: resolveConfiguredVaultPathMock,
23
+ QmdUnavailableError: class QmdUnavailableError extends Error {},
24
+ QMD_INSTALL_COMMAND: 'install-qmd'
25
+ }));
26
+
27
+ async function loadRuntimeModule() {
28
+ vi.resetModules();
29
+ return await import('./command-runtime.js');
30
+ }
31
+
32
+ beforeEach(() => {
33
+ vi.clearAllMocks();
34
+ clawvaultCtorMock.mockImplementation(() => ({
35
+ load: loadMock
36
+ }));
37
+ });
38
+
39
+ describe('command runtime helpers', () => {
40
+ it('delegates vault path resolution and loads vaults', async () => {
41
+ resolveConfiguredVaultPathMock.mockReturnValue('/resolved/vault');
42
+ loadMock.mockResolvedValue(undefined);
43
+ const { getVault, resolveVaultPath } = await loadRuntimeModule();
44
+
45
+ const resolved = resolveVaultPath('/explicit');
46
+ expect(resolveConfiguredVaultPathMock).toHaveBeenCalledWith({ explicitPath: '/explicit' });
47
+ expect(resolved).toBe('/resolved/vault');
48
+
49
+ await getVault('/explicit');
50
+ expect(clawvaultCtorMock).toHaveBeenCalledWith('/resolved/vault');
51
+ expect(loadMock).toHaveBeenCalled();
52
+ });
53
+
54
+ it('maps qmd ENOENT failures to QmdUnavailableError', async () => {
55
+ const { runQmd, QmdUnavailableError } = await loadRuntimeModule();
56
+ spawnMock.mockImplementation(() => {
57
+ const handlers = {};
58
+ const proc = {
59
+ on: (event, handler) => {
60
+ handlers[event] = handler;
61
+ }
62
+ };
63
+ queueMicrotask(() => {
64
+ handlers.error?.({ code: 'ENOENT' });
65
+ });
66
+ return proc;
67
+ });
68
+
69
+ await expect(runQmd(['update'])).rejects.toBeInstanceOf(QmdUnavailableError);
70
+ });
71
+
72
+ it('injects qmd index from environment when configured', async () => {
73
+ const previous = process.env.CLAWVAULT_QMD_INDEX;
74
+ process.env.CLAWVAULT_QMD_INDEX = 'clawvault-test';
75
+
76
+ try {
77
+ const { runQmd } = await loadRuntimeModule();
78
+ spawnMock.mockImplementation((_command, _args) => {
79
+ const handlers = {};
80
+ const proc = {
81
+ on: (event, handler) => {
82
+ handlers[event] = handler;
83
+ }
84
+ };
85
+ queueMicrotask(() => {
86
+ handlers.close?.(0);
87
+ });
88
+ return proc;
89
+ });
90
+
91
+ await runQmd(['update']);
92
+ expect(spawnMock).toHaveBeenCalledWith(
93
+ 'qmd',
94
+ ['--index', 'clawvault-test', 'update'],
95
+ { stdio: 'inherit' }
96
+ );
97
+ } finally {
98
+ if (previous === undefined) {
99
+ delete process.env.CLAWVAULT_QMD_INDEX;
100
+ } else {
101
+ process.env.CLAWVAULT_QMD_INDEX = previous;
102
+ }
103
+ }
104
+ });
105
+
106
+ it('surfaces qmd non-zero exit codes as errors', async () => {
107
+ const { runQmd } = await loadRuntimeModule();
108
+ spawnMock.mockImplementation(() => {
109
+ const handlers = {};
110
+ const proc = {
111
+ on: (event, handler) => {
112
+ handlers[event] = handler;
113
+ }
114
+ };
115
+ queueMicrotask(() => {
116
+ handlers.close?.(2);
117
+ });
118
+ return proc;
119
+ });
120
+
121
+ await expect(runQmd(['update'])).rejects.toThrow('qmd exited with code 2');
122
+ });
123
+
124
+ it('exports and enforces argument/path sanitization helpers', async () => {
125
+ const { sanitizeQmdArg, validatePathWithinBase } = await loadRuntimeModule();
126
+ expect(sanitizeQmdArg('update')).toBe('update');
127
+ expect(sanitizeQmdArg(42)).toBe('42');
128
+ expect(() => sanitizeQmdArg('bad\0arg')).toThrow('contains null byte');
129
+
130
+ const safePath = validatePathWithinBase('notes/today.md', '/tmp/vault');
131
+ expect(safePath).toBe(path.resolve('/tmp/vault', 'notes/today.md'));
132
+ expect(() => validatePathWithinBase('../etc/passwd', '/tmp/vault')).toThrow('Path traversal detected');
133
+ });
134
+
135
+ it('rejects qmd args with null-byte injection attempts', async () => {
136
+ const { runQmd } = await loadRuntimeModule();
137
+ await expect(runQmd(['up\0date'])).rejects.toThrow('contains null byte');
138
+ expect(spawnMock).not.toHaveBeenCalled();
139
+ });
140
+
141
+ it('prints consistent qmd missing guidance', async () => {
142
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
143
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
144
+ try {
145
+ const { printQmdMissing } = await loadRuntimeModule();
146
+ printQmdMissing();
147
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('ClawVault requires qmd.'));
148
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('install-qmd'));
149
+ } finally {
150
+ errorSpy.mockRestore();
151
+ logSpy.mockRestore();
152
+ }
153
+ });
154
+ });
@@ -1,39 +1,39 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { registerAllCommandModules } from './test-helpers/cli-command-fixtures.js';
3
-
4
- describe('CLI help contract', () => {
5
- it('includes expected high-level command surface', () => {
6
- const help = registerAllCommandModules().helpInformation();
7
- expect(help).toContain('init');
8
- expect(help).toContain('context');
9
- expect(help).toContain('inject');
10
- expect(help).toContain('doctor');
11
- expect(help).toContain('embed');
12
- expect(help).toContain('compat');
13
- expect(help).toContain('graph');
14
- expect(help).toContain('reflect');
15
- expect(help).toContain('replay');
16
- expect(help).toContain('repair-session');
17
- expect(help).toContain('project');
18
- expect(help).toContain('template');
19
- expect(help).toContain('config');
20
- expect(help).toContain('route');
21
- });
22
-
23
- it('documents context/compat/inject/project help details', () => {
24
- const program = registerAllCommandModules();
25
- const contextHelp = program.commands.find((command) => command.name() === 'context')?.helpInformation() ?? '';
26
- const compatHelp = program.commands.find((command) => command.name() === 'compat')?.helpInformation() ?? '';
27
- const injectHelp = program.commands.find((command) => command.name() === 'inject')?.helpInformation() ?? '';
28
- const projectCommand = program.commands.find((command) => command.name() === 'project');
29
- const projectListHelp = projectCommand?.commands.find((command) => command.name() === 'list')?.helpInformation() ?? '';
30
- const projectBoardHelp = projectCommand?.commands.find((command) => command.name() === 'board')?.helpInformation() ?? '';
31
- expect(contextHelp).toContain('--profile <profile>');
32
- expect(contextHelp).toContain('auto');
33
- expect(compatHelp).toContain('--strict');
34
- expect(injectHelp).toContain('inject.maxResults');
35
- expect(injectHelp).toContain('inject.scope');
36
- expect(projectListHelp).toContain('archived projects are hidden');
37
- expect(projectBoardHelp).toContain('default: status');
38
- });
39
- });
1
+ import { describe, expect, it } from 'vitest';
2
+ import { registerAllCommandModules } from './test-helpers/cli-command-fixtures.js';
3
+
4
+ describe('CLI help contract', () => {
5
+ it('includes expected high-level command surface', () => {
6
+ const help = registerAllCommandModules().helpInformation();
7
+ expect(help).toContain('init');
8
+ expect(help).toContain('context');
9
+ expect(help).toContain('inject');
10
+ expect(help).toContain('doctor');
11
+ expect(help).toContain('embed');
12
+ expect(help).toContain('compat');
13
+ expect(help).toContain('graph');
14
+ expect(help).toContain('reflect');
15
+ expect(help).toContain('replay');
16
+ expect(help).toContain('repair-session');
17
+ expect(help).toContain('project');
18
+ expect(help).toContain('template');
19
+ expect(help).toContain('config');
20
+ expect(help).toContain('route');
21
+ });
22
+
23
+ it('documents context/compat/inject/project help details', () => {
24
+ const program = registerAllCommandModules();
25
+ const contextHelp = program.commands.find((command) => command.name() === 'context')?.helpInformation() ?? '';
26
+ const compatHelp = program.commands.find((command) => command.name() === 'compat')?.helpInformation() ?? '';
27
+ const injectHelp = program.commands.find((command) => command.name() === 'inject')?.helpInformation() ?? '';
28
+ const projectCommand = program.commands.find((command) => command.name() === 'project');
29
+ const projectListHelp = projectCommand?.commands.find((command) => command.name() === 'list')?.helpInformation() ?? '';
30
+ const projectBoardHelp = projectCommand?.commands.find((command) => command.name() === 'board')?.helpInformation() ?? '';
31
+ expect(contextHelp).toContain('--profile <profile>');
32
+ expect(contextHelp).toContain('auto');
33
+ expect(compatHelp).toContain('--strict');
34
+ expect(injectHelp).toContain('inject.maxResults');
35
+ expect(injectHelp).toContain('inject.scope');
36
+ expect(projectListHelp).toContain('archived projects are hidden');
37
+ expect(projectBoardHelp).toContain('default: status');
38
+ });
39
+ });