clawvault 3.5.0 → 3.5.2

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 (45) hide show
  1. package/README.md +33 -25
  2. package/bin/command-registration.test.js +179 -0
  3. package/bin/command-runtime.test.js +154 -0
  4. package/bin/help-contract.test.js +55 -0
  5. package/bin/register-config-route-commands.test.js +121 -0
  6. package/bin/register-core-commands.test.js +80 -0
  7. package/bin/register-kanban-commands.test.js +83 -0
  8. package/bin/register-project-commands.test.js +206 -0
  9. package/bin/register-query-commands.test.js +80 -0
  10. package/bin/register-resilience-commands.test.js +81 -0
  11. package/bin/register-task-commands.test.js +69 -0
  12. package/bin/register-template-commands.test.js +87 -0
  13. package/bin/test-helpers/cli-command-fixtures.js +120 -0
  14. package/dashboard/lib/graph-diff.test.js +75 -0
  15. package/dashboard/lib/vault-parser.test.js +254 -0
  16. package/dist/{chunk-DCF4KMFD.js → chunk-DPK6P6BO.js} +3 -3
  17. package/dist/{chunk-BLQXXX7Q.js → chunk-FNFP7N6A.js} +2 -2
  18. package/dist/{chunk-JI7VUQV7.js → chunk-IFUHSHN3.js} +146 -118
  19. package/dist/{chunk-QFWERBDP.js → chunk-J6DW6HBX.js} +1 -1
  20. package/dist/{chunk-VXAGOLDP.js → chunk-LCBHM3D6.js} +1 -1
  21. package/dist/{chunk-HGDDW24U.js → chunk-NTQD55S3.js} +3 -3
  22. package/dist/{chunk-QUFQBAHP.js → chunk-P35SHNAU.js} +93 -147
  23. package/dist/cli/index.js +8 -8
  24. package/dist/commands/compat.js +1 -1
  25. package/dist/commands/inject.js +2 -2
  26. package/dist/commands/maintain.js +2 -2
  27. package/dist/commands/observe.js +4 -4
  28. package/dist/commands/rebuild.js +3 -3
  29. package/dist/commands/replay.js +4 -4
  30. package/dist/commands/sleep.js +5 -5
  31. package/dist/commands/status.js +3 -3
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.js +17 -17
  34. package/dist/{openclaw-plugin--gqA2BZw.d.ts → openclaw-plugin-9M9qCZgl.d.ts} +2 -2
  35. package/dist/openclaw-plugin.d.ts +1 -1
  36. package/dist/openclaw-plugin.js +6 -1
  37. package/package.json +4 -26
  38. package/CHANGELOG.md +0 -543
  39. package/SKILL.md +0 -369
  40. package/docs/clawhub-security-release-playbook.md +0 -75
  41. package/docs/getting-started/installation.md +0 -99
  42. package/docs/openclaw-plugin-usage.md +0 -152
  43. package/dist/{chunk-7SWP5FKU.js → chunk-FSYISBTU.js} +4 -4
  44. package/dist/{chunk-D5U3Q4N5.js → chunk-IOKLQR4W.js} +4 -4
  45. package/dist/{chunk-OFOCU2V4.js → chunk-QL34TMGN.js} +3 -3
@@ -0,0 +1,120 @@
1
+ import { Command } from 'commander';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { registerCoreCommands } from '../register-core-commands.js';
5
+ import { registerMaintenanceCommands } from '../register-maintenance-commands.js';
6
+ import { registerQueryCommands } from '../register-query-commands.js';
7
+ import { registerResilienceCommands } from '../register-resilience-commands.js';
8
+ import { registerSessionLifecycleCommands } from '../register-session-lifecycle-commands.js';
9
+ import { registerTemplateCommands } from '../register-template-commands.js';
10
+ import { registerVaultOperationsCommands } from '../register-vault-operations-commands.js';
11
+ import { registerConfigCommands } from '../register-config-commands.js';
12
+ import { registerRouteCommands } from '../register-route-commands.js';
13
+ import { registerTaskCommands } from '../register-task-commands.js';
14
+ import { registerKanbanCommands } from '../register-kanban-commands.js';
15
+ import { registerProjectCommands } from '../register-project-commands.js';
16
+
17
+ export const chalkStub = {
18
+ cyan: (value) => value,
19
+ green: (value) => value,
20
+ red: (value) => value,
21
+ dim: (value) => value,
22
+ yellow: (value) => value,
23
+ white: (value) => value
24
+ };
25
+
26
+ export function stubResolveVaultPath(value) {
27
+ return value ?? '/vault';
28
+ }
29
+
30
+ export function createVaultStub(overrides = {}) {
31
+ return {
32
+ store: async () => ({}),
33
+ patch: async () => ({}),
34
+ capture: async () => ({}),
35
+ find: async () => [],
36
+ vsearch: async () => [],
37
+ list: async () => [],
38
+ get: async () => null,
39
+ stats: async () => ({ tags: [], categories: {} }),
40
+ sync: async () => ({ copied: [], deleted: [], unchanged: [], errors: [] }),
41
+ reindex: async () => 0,
42
+ remember: async () => ({ id: '' }),
43
+ getQmdCollection: () => '',
44
+ createHandoff: async () => ({ id: '', path: '' }),
45
+ generateRecap: async () => ({}),
46
+ formatRecap: () => '',
47
+ ...overrides
48
+ };
49
+ }
50
+
51
+ export function createGetVaultStub(overrides = {}) {
52
+ return async () => createVaultStub(overrides);
53
+ }
54
+
55
+ export function registerAllCommandModules(program = new Command()) {
56
+ const getVault = createGetVaultStub();
57
+
58
+ registerCoreCommands(program, {
59
+ chalk: chalkStub,
60
+ path,
61
+ fs,
62
+ createVault: async () => ({ getCategories: () => [], getQmdRoot: () => '', getQmdCollection: () => '' }),
63
+ getVault,
64
+ runQmd: async () => {}
65
+ });
66
+
67
+ registerQueryCommands(program, {
68
+ chalk: chalkStub,
69
+ getVault,
70
+ resolveVaultPath: stubResolveVaultPath,
71
+ QmdUnavailableError: class extends Error {},
72
+ printQmdMissing: () => {}
73
+ });
74
+
75
+ registerVaultOperationsCommands(program, {
76
+ chalk: chalkStub,
77
+ fs,
78
+ getVault,
79
+ runQmd: async () => {},
80
+ resolveVaultPath: stubResolveVaultPath,
81
+ path
82
+ });
83
+
84
+ registerMaintenanceCommands(program, { chalk: chalkStub });
85
+ registerResilienceCommands(program, {
86
+ chalk: chalkStub,
87
+ resolveVaultPath: stubResolveVaultPath
88
+ });
89
+ registerSessionLifecycleCommands(program, {
90
+ chalk: chalkStub,
91
+ resolveVaultPath: stubResolveVaultPath,
92
+ QmdUnavailableError: class extends Error {},
93
+ printQmdMissing: () => {},
94
+ getVault,
95
+ runQmd: async () => {}
96
+ });
97
+ registerTemplateCommands(program, { chalk: chalkStub });
98
+ registerConfigCommands(program, {
99
+ chalk: chalkStub,
100
+ resolveVaultPath: stubResolveVaultPath
101
+ });
102
+ registerRouteCommands(program, {
103
+ chalk: chalkStub,
104
+ resolveVaultPath: stubResolveVaultPath
105
+ });
106
+ registerTaskCommands(program, {
107
+ chalk: chalkStub,
108
+ resolveVaultPath: stubResolveVaultPath
109
+ });
110
+ registerKanbanCommands(program, {
111
+ chalk: chalkStub,
112
+ resolveVaultPath: stubResolveVaultPath
113
+ });
114
+ registerProjectCommands(program, {
115
+ chalk: chalkStub,
116
+ resolveVaultPath: stubResolveVaultPath
117
+ });
118
+
119
+ return program;
120
+ }
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { diffGraphs } from './graph-diff.js';
3
+
4
+ describe('diffGraphs', () => {
5
+ it('detects node and edge additions, updates, and removals', () => {
6
+ const previous = {
7
+ nodes: [
8
+ { id: 'a', title: 'A', category: 'root', tags: [], path: 'a.md', missing: false, degree: 1 },
9
+ { id: 'b', title: 'B', category: 'root', tags: ['x'], path: 'b.md', missing: false, degree: 1 },
10
+ { id: 'c', title: 'C', category: 'root', tags: [], path: null, missing: true, degree: 0 }
11
+ ],
12
+ edges: [{ source: 'a', target: 'b' }],
13
+ stats: { nodeCount: 3, edgeCount: 1 }
14
+ };
15
+
16
+ const next = {
17
+ nodes: [
18
+ { id: 'a', title: 'A Updated', category: 'root', tags: [], path: 'a.md', missing: false, degree: 1 },
19
+ { id: 'b', title: 'B', category: 'root', tags: ['x'], path: 'b.md', missing: false, degree: 2 },
20
+ { id: 'd', title: 'D', category: 'projects', tags: [], path: 'd.md', missing: false, degree: 1 }
21
+ ],
22
+ edges: [
23
+ { source: 'a', target: 'b' },
24
+ { source: 'b', target: 'd' }
25
+ ],
26
+ stats: { nodeCount: 3, edgeCount: 2 }
27
+ };
28
+
29
+ const patch = diffGraphs(previous, next);
30
+
31
+ expect(patch.addedNodes).toEqual([next.nodes[2]]);
32
+ expect(patch.updatedNodes).toEqual(expect.arrayContaining([next.nodes[0], next.nodes[1]]));
33
+ expect(patch.removedNodeIds).toEqual(['c']);
34
+ expect(patch.addedEdges).toEqual([{ source: 'b', target: 'd' }]);
35
+ expect(patch.removedEdges).toEqual([]);
36
+ expect(patch.changedNodeIds).toEqual(['a', 'b', 'c', 'd']);
37
+ expect(patch.hasChanges).toBe(true);
38
+ });
39
+
40
+ it('returns hasChanges=false for equivalent graphs', () => {
41
+ const graph = {
42
+ nodes: [{ id: 'a', title: 'A', category: 'root', tags: ['t'], path: 'a.md', missing: false, degree: 0 }],
43
+ edges: [],
44
+ stats: { nodeCount: 1, edgeCount: 0 }
45
+ };
46
+
47
+ const patch = diffGraphs(graph, structuredClone(graph));
48
+
49
+ expect(patch.hasChanges).toBe(false);
50
+ expect(patch.addedNodes).toEqual([]);
51
+ expect(patch.updatedNodes).toEqual([]);
52
+ expect(patch.removedNodeIds).toEqual([]);
53
+ expect(patch.addedEdges).toEqual([]);
54
+ expect(patch.removedEdges).toEqual([]);
55
+ });
56
+
57
+ it('treats edge type changes as edge diff', () => {
58
+ const previous = {
59
+ nodes: [
60
+ { id: 'a', title: 'A', category: 'root', tags: [], path: 'a.md', missing: false, degree: 1 },
61
+ { id: 'b', title: 'B', category: 'root', tags: [], path: 'b.md', missing: false, degree: 1 }
62
+ ],
63
+ edges: [{ source: 'a', target: 'b', type: 'wiki_link' }]
64
+ };
65
+ const next = {
66
+ nodes: previous.nodes,
67
+ edges: [{ source: 'a', target: 'b', type: 'frontmatter_relation', label: 'related' }]
68
+ };
69
+
70
+ const patch = diffGraphs(previous, next);
71
+ expect(patch.addedEdges).toEqual([{ source: 'a', target: 'b', type: 'frontmatter_relation', label: 'related' }]);
72
+ expect(patch.removedEdges).toEqual([{ source: 'a', target: 'b', type: 'wiki_link' }]);
73
+ expect(patch.hasChanges).toBe(true);
74
+ });
75
+ });
@@ -0,0 +1,254 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { describe, expect, it } from 'vitest';
5
+ import { buildVaultGraph } from './vault-parser.js';
6
+
7
+ function makeTempVault() {
8
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'clawvault-dashboard-'));
9
+ }
10
+
11
+ function writeVaultFile(root, relativePath, content) {
12
+ const fullPath = path.join(root, relativePath);
13
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
14
+ fs.writeFileSync(fullPath, content, 'utf8');
15
+ }
16
+
17
+ describe('buildVaultGraph', () => {
18
+ it('builds nodes and edges from markdown wiki-links', async () => {
19
+ const vaultPath = makeTempVault();
20
+ try {
21
+ writeVaultFile(
22
+ vaultPath,
23
+ 'decisions/use-clawvault.md',
24
+ `---
25
+ title: Use ClawVault
26
+ tags: [architecture, memory]
27
+ ---
28
+ Linked to [[projects/clawvault|ClawVault Project]] and [[missing-note]].
29
+ `
30
+ );
31
+ writeVaultFile(vaultPath, 'projects/clawvault.md', '# ClawVault');
32
+
33
+ const graph = await buildVaultGraph(vaultPath);
34
+ const decisionNode = graph.nodes.find((node) => node.id === 'decisions/use-clawvault');
35
+ const unresolvedNode = graph.nodes.find((node) => node.id === 'missing-note');
36
+
37
+ expect(decisionNode).toMatchObject({
38
+ title: 'Use ClawVault',
39
+ category: 'decisions',
40
+ tags: ['architecture', 'memory'],
41
+ type: 'decision'
42
+ });
43
+ expect(graph.edges).toEqual(expect.arrayContaining([
44
+ expect.objectContaining({
45
+ source: 'decisions/use-clawvault',
46
+ target: 'projects/clawvault',
47
+ type: 'wiki_link'
48
+ }),
49
+ expect.objectContaining({
50
+ source: 'decisions/use-clawvault',
51
+ target: 'missing-note',
52
+ type: 'wiki_link'
53
+ }),
54
+ expect.objectContaining({
55
+ source: 'decisions/use-clawvault',
56
+ target: 'tag:architecture',
57
+ type: 'tag'
58
+ })
59
+ ]));
60
+ expect(unresolvedNode).toMatchObject({
61
+ missing: true,
62
+ category: 'unresolved'
63
+ });
64
+ expect(graph.stats.edgeTypeCounts.wiki_link).toBeGreaterThanOrEqual(2);
65
+ expect(graph.stats.edgeTypeCounts.tag).toBeGreaterThanOrEqual(1);
66
+ } finally {
67
+ fs.rmSync(vaultPath, { recursive: true, force: true });
68
+ }
69
+ });
70
+
71
+ it('resolves basename links when there is a unique match', async () => {
72
+ const vaultPath = makeTempVault();
73
+ try {
74
+ writeVaultFile(vaultPath, 'research/notes.md', 'See [[clawvault]].');
75
+ writeVaultFile(vaultPath, 'projects/clawvault.md', '# ClawVault');
76
+
77
+ const graph = await buildVaultGraph(vaultPath);
78
+
79
+ expect(graph.edges).toEqual(expect.arrayContaining([
80
+ expect.objectContaining({
81
+ source: 'research/notes',
82
+ target: 'projects/clawvault',
83
+ type: 'wiki_link'
84
+ })
85
+ ]));
86
+ } finally {
87
+ fs.rmSync(vaultPath, { recursive: true, force: true });
88
+ }
89
+ });
90
+
91
+ it('emits frontmatter relation edges with labels', async () => {
92
+ const vaultPath = makeTempVault();
93
+ try {
94
+ writeVaultFile(
95
+ vaultPath,
96
+ 'decisions/db.md',
97
+ `---
98
+ related:
99
+ - projects/clawvault
100
+ owner: people/alice
101
+ ---
102
+ Decision details`
103
+ );
104
+ writeVaultFile(vaultPath, 'projects/clawvault.md', '# ClawVault');
105
+ writeVaultFile(vaultPath, 'people/alice.md', '# Alice');
106
+
107
+ const graph = await buildVaultGraph(vaultPath);
108
+ expect(graph.edges).toEqual(expect.arrayContaining([
109
+ expect.objectContaining({
110
+ source: 'decisions/db',
111
+ target: 'projects/clawvault',
112
+ type: 'frontmatter_relation',
113
+ label: 'related'
114
+ }),
115
+ expect.objectContaining({
116
+ source: 'decisions/db',
117
+ target: 'people/alice',
118
+ type: 'frontmatter_relation',
119
+ label: 'owner'
120
+ })
121
+ ]));
122
+ } finally {
123
+ fs.rmSync(vaultPath, { recursive: true, force: true });
124
+ }
125
+ });
126
+
127
+ it('loads graph data from memory graph index when present', async () => {
128
+ const vaultPath = makeTempVault();
129
+ try {
130
+ writeVaultFile(vaultPath, 'decisions/use-clawvault.md', '# Placeholder');
131
+ writeVaultFile(vaultPath, 'projects/clawvault.md', '# Placeholder project');
132
+ const decisionMtime = fs.statSync(path.join(vaultPath, 'decisions/use-clawvault.md')).mtimeMs;
133
+ const projectMtime = fs.statSync(path.join(vaultPath, 'projects/clawvault.md')).mtimeMs;
134
+ const indexPath = path.join(vaultPath, '.clawvault', 'graph-index.json');
135
+ fs.mkdirSync(path.dirname(indexPath), { recursive: true });
136
+ fs.writeFileSync(
137
+ indexPath,
138
+ JSON.stringify({
139
+ schemaVersion: 1,
140
+ files: {
141
+ 'decisions/use-clawvault.md': {
142
+ relativePath: 'decisions/use-clawvault.md',
143
+ mtimeMs: decisionMtime
144
+ },
145
+ 'projects/clawvault.md': {
146
+ relativePath: 'projects/clawvault.md',
147
+ mtimeMs: projectMtime
148
+ }
149
+ },
150
+ graph: {
151
+ nodes: [
152
+ {
153
+ id: 'note:decisions/use-clawvault',
154
+ title: 'Use ClawVault',
155
+ type: 'decision',
156
+ category: 'decisions',
157
+ tags: ['architecture'],
158
+ path: 'decisions/use-clawvault.md',
159
+ missing: false,
160
+ degree: 1
161
+ },
162
+ {
163
+ id: 'note:projects/clawvault',
164
+ title: 'ClawVault Project',
165
+ type: 'project',
166
+ category: 'projects',
167
+ tags: [],
168
+ path: 'projects/clawvault.md',
169
+ missing: false,
170
+ degree: 1
171
+ }
172
+ ],
173
+ edges: [
174
+ {
175
+ source: 'note:decisions/use-clawvault',
176
+ target: 'note:projects/clawvault',
177
+ type: 'frontmatter_relation',
178
+ label: 'related'
179
+ }
180
+ ],
181
+ stats: { generatedAt: '2026-02-13T00:00:00.000Z' }
182
+ }
183
+ }),
184
+ 'utf8'
185
+ );
186
+
187
+ const graph = await buildVaultGraph(vaultPath);
188
+ expect(graph.nodes.find((node) => node.id === 'decisions/use-clawvault')).toBeTruthy();
189
+ expect(graph.edges).toEqual([
190
+ {
191
+ source: 'decisions/use-clawvault',
192
+ target: 'projects/clawvault',
193
+ type: 'frontmatter_relation',
194
+ label: 'related'
195
+ }
196
+ ]);
197
+ expect(graph.stats.fileCount).toBe(2);
198
+ } finally {
199
+ fs.rmSync(vaultPath, { recursive: true, force: true });
200
+ }
201
+ });
202
+
203
+ it('falls back to markdown parsing when memory graph index is stale', async () => {
204
+ const vaultPath = makeTempVault();
205
+ try {
206
+ writeVaultFile(vaultPath, 'projects/clawvault.md', '# ClawVault');
207
+ writeVaultFile(vaultPath, 'decisions/use-clawvault.md', 'See [[projects/clawvault]].');
208
+
209
+ const indexPath = path.join(vaultPath, '.clawvault', 'graph-index.json');
210
+ fs.mkdirSync(path.dirname(indexPath), { recursive: true });
211
+ fs.writeFileSync(
212
+ indexPath,
213
+ JSON.stringify({
214
+ schemaVersion: 1,
215
+ generatedAt: '2026-02-13T00:00:00.000Z',
216
+ files: {
217
+ 'decisions/use-clawvault.md': { relativePath: 'decisions/use-clawvault.md', mtimeMs: 1 },
218
+ 'projects/clawvault.md': { relativePath: 'projects/clawvault.md', mtimeMs: 1 }
219
+ },
220
+ graph: {
221
+ nodes: [
222
+ {
223
+ id: 'note:decisions/use-clawvault',
224
+ title: 'Old node',
225
+ type: 'decision',
226
+ category: 'decisions',
227
+ tags: [],
228
+ path: 'decisions/use-clawvault.md',
229
+ missing: false,
230
+ degree: 0
231
+ }
232
+ ],
233
+ edges: [],
234
+ stats: { generatedAt: '2026-02-13T00:00:00.000Z' }
235
+ }
236
+ }),
237
+ 'utf8'
238
+ );
239
+
240
+ const graph = await buildVaultGraph(vaultPath);
241
+ const node = graph.nodes.find((candidate) => candidate.id === 'decisions/use-clawvault');
242
+ expect(node?.title).not.toBe('Old node');
243
+ expect(graph.edges).toEqual(expect.arrayContaining([
244
+ expect.objectContaining({
245
+ source: 'decisions/use-clawvault',
246
+ target: 'projects/clawvault',
247
+ type: 'wiki_link'
248
+ })
249
+ ]));
250
+ } finally {
251
+ fs.rmSync(vaultPath, { recursive: true, force: true });
252
+ }
253
+ });
254
+ });
@@ -6,13 +6,13 @@ import {
6
6
  } from "./chunk-DOIUYIXV.js";
7
7
  import {
8
8
  registerInjectCommand
9
- } from "./chunk-OFOCU2V4.js";
9
+ } from "./chunk-QL34TMGN.js";
10
10
  import {
11
11
  registerMaintainCommand
12
- } from "./chunk-D5U3Q4N5.js";
12
+ } from "./chunk-IOKLQR4W.js";
13
13
  import {
14
14
  registerObserveCommand
15
- } from "./chunk-BLQXXX7Q.js";
15
+ } from "./chunk-FNFP7N6A.js";
16
16
  import {
17
17
  registerContextCommand
18
18
  } from "./chunk-GFCHWMGD.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  observeActiveSessions
3
- } from "./chunk-VXAGOLDP.js";
3
+ } from "./chunk-LCBHM3D6.js";
4
4
  import {
5
5
  Observer
6
- } from "./chunk-7SWP5FKU.js";
6
+ } from "./chunk-FSYISBTU.js";
7
7
  import {
8
8
  resolveVaultPath
9
9
  } from "./chunk-GJO3CFUN.js";