clawvault 3.2.1 → 3.4.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.
- package/README.md +56 -16
- package/bin/clawvault.js +0 -2
- package/bin/command-registration.test.js +15 -2
- package/bin/help-contract.test.js +16 -0
- package/bin/register-core-commands.js +88 -0
- package/bin/register-core-commands.test.js +80 -0
- package/bin/register-maintenance-commands.js +84 -7
- package/bin/register-query-commands.js +45 -28
- package/bin/register-query-commands.test.js +15 -0
- package/bin/test-helpers/cli-command-fixtures.js +1 -0
- package/dist/chunk-2PKBIKDH.js +130 -0
- package/dist/{chunk-U67V476Y.js → chunk-2ZDO52B4.js} +18 -1
- package/dist/{chunk-ZZA73MFY.js → chunk-33DOSHTA.js} +176 -36
- package/dist/chunk-35JCYSRR.js +158 -0
- package/dist/{chunk-AZYOKJYC.js → chunk-4PY655YM.js} +13 -1
- package/dist/{chunk-2JQ3O2YL.js → chunk-5EFSWZO6.js} +3 -3
- package/dist/{chunk-Y3TIJEBP.js → chunk-7SWP5FKU.js} +34 -613
- package/dist/{chunk-4VQTUVH7.js → chunk-7YZWHM36.js} +52 -26
- package/dist/{chunk-URXDAUVH.js → chunk-AXSJIFOJ.js} +174 -1
- package/dist/{chunk-4ITRXIVT.js → chunk-BLQXXX7Q.js} +6 -6
- package/dist/chunk-CSHO3PJB.js +684 -0
- package/dist/chunk-D5U3Q4N5.js +872 -0
- package/dist/chunk-DCF4KMFD.js +158 -0
- package/dist/{chunk-S5OJEGFG.js → chunk-DOIUYIXV.js} +2 -2
- package/dist/{chunk-YXQCA6B7.js → chunk-DVOUSOR3.js} +112 -7
- package/dist/{chunk-YDWHS4LJ.js → chunk-ECGJYWNA.js} +205 -33
- package/dist/{chunk-QMHPQYUV.js → chunk-EL6UBSX5.js} +7 -6
- package/dist/chunk-FZ5I2NF7.js +352 -0
- package/dist/{chunk-WJVWINEM.js → chunk-GFCHWMGD.js} +55 -6
- package/dist/{chunk-GNJL4YGR.js → chunk-GJO3CFUN.js} +30 -6
- package/dist/chunk-H3JZIB5O.js +322 -0
- package/dist/chunk-HEHO7SMV.js +51 -0
- package/dist/{chunk-UCQAOZHW.js → chunk-HGDDW24U.js} +3 -3
- package/dist/chunk-J3YUXVID.js +907 -0
- package/dist/{chunk-Y6VJKXGL.js → chunk-KCYWJDDW.js} +1 -1
- package/dist/{chunk-P5EPF6MB.js → chunk-MW5C6ZQA.js} +110 -13
- package/dist/chunk-NSXYM6EZ.js +255 -0
- package/dist/{chunk-YNIPYN4F.js → chunk-OFOCU2V4.js} +6 -5
- package/dist/{chunk-42MXU7A6.js → chunk-P62WHA27.js} +58 -47
- package/dist/chunk-PTWPPVC7.js +972 -0
- package/dist/{chunk-FAKNOB7Y.js → chunk-QFWERBDP.js} +2 -2
- package/dist/chunk-QYQAGBTM.js +2097 -0
- package/dist/chunk-RL2L6I6K.js +223 -0
- package/dist/{chunk-IIOU45CK.js → chunk-S7N7HI5E.js} +2 -2
- package/dist/{chunk-ECRZL5XR.js → chunk-T7E764W3.js} +23 -7
- package/dist/{chunk-MNPUYCHQ.js → chunk-TWMI3SNN.js} +6 -5
- package/dist/{chunk-2RAZ4ZFE.js → chunk-VBILES4B.js} +1 -1
- package/dist/{chunk-PI4WMLMG.js → chunk-VXAGOLDP.js} +1 -1
- package/dist/{chunk-SS4B7P7V.js → chunk-YIDV4VV2.js} +1 -1
- package/dist/chunk-YTRZNA64.js +37 -0
- package/dist/chunk-ZKWPCBYT.js +600 -0
- package/dist/cli/index.js +28 -21
- package/dist/commands/archive.js +3 -3
- package/dist/commands/backlog.js +1 -1
- package/dist/commands/benchmark.d.ts +12 -0
- package/dist/commands/benchmark.js +12 -0
- package/dist/commands/blocked.js +1 -1
- package/dist/commands/canvas.js +2 -2
- package/dist/commands/checkpoint.js +1 -1
- package/dist/commands/compat.js +1 -1
- package/dist/commands/context.js +8 -7
- package/dist/commands/doctor.d.ts +8 -3
- package/dist/commands/doctor.js +8 -22
- package/dist/commands/embed.js +6 -5
- package/dist/commands/entities.d.ts +8 -1
- package/dist/commands/entities.js +46 -3
- package/dist/commands/graph.js +4 -4
- package/dist/commands/inbox.d.ts +23 -0
- package/dist/commands/inbox.js +11 -0
- package/dist/commands/inject.d.ts +1 -1
- package/dist/commands/inject.js +5 -5
- package/dist/commands/kanban.js +1 -1
- package/dist/commands/link.js +5 -5
- package/dist/commands/maintain.d.ts +32 -0
- package/dist/commands/maintain.js +13 -0
- package/dist/commands/migrate-observations.js +3 -3
- package/dist/commands/observe.js +11 -10
- package/dist/commands/project.js +2 -2
- package/dist/commands/rebuild-embeddings.js +48 -17
- package/dist/commands/rebuild.js +9 -8
- package/dist/commands/recall.d.ts +14 -0
- package/dist/commands/recall.js +15 -0
- package/dist/commands/recover.js +1 -1
- package/dist/commands/reflect.js +6 -6
- package/dist/commands/repair-session.js +1 -1
- package/dist/commands/replay.js +10 -9
- package/dist/commands/session-recap.js +1 -1
- package/dist/commands/setup.js +4 -3
- package/dist/commands/shell-init.js +1 -1
- package/dist/commands/sleep.d.ts +1 -1
- package/dist/commands/sleep.js +20 -18
- package/dist/commands/status.js +40 -26
- package/dist/commands/sync-bd.js +3 -3
- package/dist/commands/tailscale.js +3 -3
- package/dist/commands/task.js +1 -1
- package/dist/commands/template.js +1 -1
- package/dist/commands/wake.d.ts +1 -1
- package/dist/commands/wake.js +10 -9
- package/dist/index.d.ts +233 -16
- package/dist/index.js +325 -111
- package/dist/{inject-DYUrDqQO.d.ts → inject-DEb_jpLi.d.ts} +3 -1
- package/dist/lib/auto-linker.js +2 -2
- package/dist/lib/canvas-layout.js +1 -1
- package/dist/lib/config.js +2 -2
- package/dist/lib/entity-index.js +1 -1
- package/dist/lib/project-utils.js +2 -2
- package/dist/lib/session-repair.js +1 -1
- package/dist/lib/session-utils.js +1 -1
- package/dist/lib/tailscale.js +1 -1
- package/dist/lib/task-utils.js +1 -1
- package/dist/lib/template-engine.js +1 -1
- package/dist/lib/webdav.js +1 -1
- package/dist/onnxruntime_binding-5QEF3SUC.node +0 -0
- package/dist/onnxruntime_binding-BKPKNEGC.node +0 -0
- package/dist/onnxruntime_binding-FMOXGIUT.node +0 -0
- package/dist/onnxruntime_binding-OI2KMXC5.node +0 -0
- package/dist/onnxruntime_binding-UX44MLAZ.node +0 -0
- package/dist/onnxruntime_binding-Y2W7N7WY.node +0 -0
- package/dist/openclaw-plugin--gqA2BZw.d.ts +267 -0
- package/dist/openclaw-plugin.d.ts +4 -0
- package/dist/openclaw-plugin.js +20 -0
- package/dist/transformers.node-A2ZRORSQ.js +46775 -0
- package/dist/types-CbL-wIKi.d.ts +36 -0
- package/dist/{types-BbWJoC1c.d.ts → types-DslKvCaj.d.ts} +51 -1
- package/hooks/clawvault/HOOK.md +25 -8
- package/hooks/clawvault/handler.js +215 -78
- package/hooks/clawvault/handler.test.js +109 -43
- package/hooks/clawvault/integrity.js +112 -0
- package/hooks/clawvault/integrity.test.js +32 -0
- package/hooks/clawvault/openclaw.plugin.json +133 -15
- package/openclaw.plugin.json +161 -194
- package/package.json +8 -5
- package/bin/register-workgraph-commands.js +0 -451
- package/dist/chunk-5PJ4STIC.js +0 -465
- package/dist/chunk-ERNE2FZ5.js +0 -189
- package/dist/chunk-HR4KN6S2.js +0 -152
- package/dist/chunk-IJBFGPCS.js +0 -33
- package/dist/chunk-K7PNYS45.js +0 -93
- package/dist/chunk-NTOPJI7W.js +0 -207
- package/dist/chunk-PG56HX5T.js +0 -154
- package/dist/chunk-QPDDIHXE.js +0 -501
- package/dist/chunk-WIOLLGAD.js +0 -190
- package/dist/chunk-WMGIIABP.js +0 -15
- package/dist/ledger-B7g7jhqG.d.ts +0 -44
- package/dist/plugin/index.d.ts +0 -352
- package/dist/plugin/index.js +0 -4264
- package/dist/registry-BR4326o0.d.ts +0 -30
- package/dist/store-CA-6sKCJ.d.ts +0 -34
- package/dist/thread-B9LhXNU0.d.ts +0 -41
- package/dist/workgraph/index.d.ts +0 -5
- package/dist/workgraph/index.js +0 -23
- package/dist/workgraph/ledger.d.ts +0 -2
- package/dist/workgraph/ledger.js +0 -25
- package/dist/workgraph/registry.d.ts +0 -2
- package/dist/workgraph/registry.js +0 -19
- package/dist/workgraph/store.d.ts +0 -2
- package/dist/workgraph/store.js +0 -25
- package/dist/workgraph/thread.d.ts +0 -2
- package/dist/workgraph/thread.js +0 -25
- package/dist/workgraph/types.d.ts +0 -54
- package/dist/workgraph/types.js +0 -7
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
*An elephant never forgets. Neither should your AI.*
|
|
14
14
|
|
|
15
|
-
[Documentation](https://clawvault.dev) · [npm Package](https://www.npmjs.com/package/clawvault) · [Obsidian Plugin](https://clawvault.dev/obsidian) · [
|
|
15
|
+
[Documentation](https://clawvault.dev) · [npm Package](https://www.npmjs.com/package/clawvault) · [Obsidian Plugin](https://clawvault.dev/obsidian) · [GitHub](https://github.com/Versatly/clawvault)
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
@@ -59,7 +59,7 @@ Unlike vector databases or cloud-based memory solutions, ClawVault is:
|
|
|
59
59
|
│ ▼ ▼ │
|
|
60
60
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
61
61
|
│ │ wake │◀──▶│ context │◀──▶│ Graph │◀──▶│ Search │ │
|
|
62
|
-
│ │ sleep │ │ profiles │ │ Traversal│ │(
|
|
62
|
+
│ │ sleep │ │ profiles │ │ Traversal│ │(hybrid) │ │
|
|
63
63
|
│ │checkpoint│ └──────────┘ └──────────┘ └──────────┘ │
|
|
64
64
|
│ └──────────┘ │
|
|
65
65
|
│ │
|
|
@@ -97,8 +97,11 @@ These primitives map directly to CLI commands and vault structure, creating a co
|
|
|
97
97
|
# Install ClawVault CLI
|
|
98
98
|
npm install -g clawvault
|
|
99
99
|
|
|
100
|
-
#
|
|
100
|
+
# Optional: install qmd for backward-compatible fallback paths
|
|
101
101
|
npm install -g github:tobi/qmd
|
|
102
|
+
|
|
103
|
+
# Quick verification
|
|
104
|
+
clawvault doctor
|
|
102
105
|
```
|
|
103
106
|
|
|
104
107
|
### Initialize Your Vault
|
|
@@ -131,12 +134,18 @@ clawvault sleep "finished auth rollout" --next "implement migration"
|
|
|
131
134
|
### Search and Context
|
|
132
135
|
|
|
133
136
|
```bash
|
|
134
|
-
#
|
|
137
|
+
# In-process hybrid search (BM25 + semantic reranking)
|
|
135
138
|
clawvault search "postgresql"
|
|
136
139
|
|
|
137
|
-
# Semantic search
|
|
140
|
+
# Semantic/vector search commands (requires hosted embeddings configured)
|
|
138
141
|
clawvault vsearch "what did we decide about storage"
|
|
139
142
|
|
|
143
|
+
# Configure hosted embeddings (OpenAI/Gemini/Ollama)
|
|
144
|
+
clawvault config set search.embeddings.provider openai
|
|
145
|
+
clawvault config set search.embeddings.model text-embedding-3-small
|
|
146
|
+
clawvault config set search.embeddings.apiKey "$OPENAI_API_KEY"
|
|
147
|
+
clawvault rebuild-embeddings
|
|
148
|
+
|
|
140
149
|
# Get context for a task
|
|
141
150
|
clawvault context "database migration"
|
|
142
151
|
clawvault context --profile planning "Q1 roadmap"
|
|
@@ -163,6 +172,12 @@ ClawVault v3 adds **write-time fact extraction** and **entity graphs** to the co
|
|
|
163
172
|
|
|
164
173
|
## Features
|
|
165
174
|
|
|
175
|
+
### v3.3 Highlights
|
|
176
|
+
|
|
177
|
+
- **In-process hybrid search engine** — BM25 + hosted semantic embeddings + cross-encoder reranking, with `qmd` now optional.
|
|
178
|
+
- **Python SDK (`clawvault-py`)** — PyPI package with a `Vault` class, BM25 search, and checkpoint/wake lifecycle helpers.
|
|
179
|
+
- **Inbox + background workers** — `clawvault inbox add` and `clawvault maintain` with Curator, Janitor, Distiller, and Surveyor workers.
|
|
180
|
+
|
|
166
181
|
### Memory Graph
|
|
167
182
|
|
|
168
183
|
ClawVault builds a typed knowledge graph from wiki-links, tags, and frontmatter:
|
|
@@ -215,6 +230,20 @@ clawvault inject "How should we handle the deployment?"
|
|
|
215
230
|
clawvault inject --enable-llm "What's our pricing strategy?"
|
|
216
231
|
```
|
|
217
232
|
|
|
233
|
+
### Python SDK (clawvault-py)
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
pip install clawvault-py
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
from clawvault import Vault
|
|
241
|
+
|
|
242
|
+
vault = Vault("~/memory")
|
|
243
|
+
results = vault.search_bm25("postgresql decision")
|
|
244
|
+
vault.checkpoint("working on auth rollout")
|
|
245
|
+
```
|
|
246
|
+
|
|
218
247
|
---
|
|
219
248
|
|
|
220
249
|
## Obsidian Integration
|
|
@@ -269,10 +298,14 @@ See [docs/openclaw-plugin-usage.md](docs/openclaw-plugin-usage.md) for detailed
|
|
|
269
298
|
|
|
270
299
|
---
|
|
271
300
|
|
|
272
|
-
## Requirements
|
|
301
|
+
## System Requirements
|
|
302
|
+
|
|
303
|
+
- Node.js 18+ (22+ recommended)
|
|
304
|
+
- npm 9+
|
|
305
|
+
- Linux, macOS, Windows, or WSL
|
|
306
|
+
- `qmd` is optional (used only for backward-compatible fallback paths; in-process search is built in)
|
|
273
307
|
|
|
274
|
-
-
|
|
275
|
-
- `qmd` installed and available on `PATH` (for search/context features)
|
|
308
|
+
For Linux-specific install and PATH guidance, see [docs/getting-started/installation.md](docs/getting-started/installation.md).
|
|
276
309
|
|
|
277
310
|
## LLM Providers
|
|
278
311
|
|
|
@@ -444,14 +477,21 @@ vault/
|
|
|
444
477
|
|
|
445
478
|
## Troubleshooting
|
|
446
479
|
|
|
447
|
-
-
|
|
448
|
-
- run `
|
|
449
|
-
|
|
450
|
-
-
|
|
480
|
+
- First-line diagnostic:
|
|
481
|
+
- run `clawvault doctor` after install or environment changes
|
|
482
|
+
- Global install fails with `EACCES` / permission denied:
|
|
483
|
+
- run `npm config set prefix ~/.npm-global`
|
|
484
|
+
- add `export PATH="$HOME/.npm-global/bin:$PATH"` to your shell rc and reload shell
|
|
485
|
+
- `clawvault: command not found` after install:
|
|
486
|
+
- check `npm config get prefix`
|
|
487
|
+
- ensure `<prefix>/bin` is present in your `PATH`
|
|
488
|
+
- `qmd` fallback errors:
|
|
489
|
+
- `qmd` is optional; in-process BM25 search is available without it
|
|
490
|
+
- if you want fallback compatibility, ensure `qmd --version` works in the same shell
|
|
491
|
+
- Hook/plugin not active in OpenClaw:
|
|
492
|
+
- run `openclaw hooks install clawvault`
|
|
493
|
+
- run `openclaw hooks enable clawvault`
|
|
451
494
|
- verify with `openclaw hooks list --verbose`
|
|
452
|
-
- `qmd` errors:
|
|
453
|
-
- ensure `qmd --version` works from same shell
|
|
454
|
-
- rerun `clawvault setup` after qmd install
|
|
455
495
|
- OpenClaw integration drift:
|
|
456
496
|
- run `clawvault compat`
|
|
457
497
|
- Session transcript corruption:
|
|
@@ -484,7 +524,7 @@ See our [contribution guidelines](https://github.com/Versatly/clawvault/blob/mai
|
|
|
484
524
|
|
|
485
525
|
---
|
|
486
526
|
|
|
487
|
-
**$
|
|
527
|
+
**$CLAW**: [`5Fjr82MTB8mvxkzi9FYtvrUsPiDGE2M29w3dYcZpump`](https://pump.fun/coin/5Fjr82MTB8mvxkzi9FYtvrUsPiDGE2M29w3dYcZpump)
|
|
488
528
|
|
|
489
529
|
## License
|
|
490
530
|
|
package/bin/clawvault.js
CHANGED
|
@@ -23,7 +23,6 @@ import { registerProjectCommands } from './register-project-commands.js';
|
|
|
23
23
|
|
|
24
24
|
import { registerTaskCommands } from './register-task-commands.js';
|
|
25
25
|
|
|
26
|
-
import { registerWorkgraphCommands } from './register-workgraph-commands.js';
|
|
27
26
|
import { registerTailscaleCommands } from './register-tailscale-commands.js';
|
|
28
27
|
import {
|
|
29
28
|
getVault,
|
|
@@ -109,7 +108,6 @@ registerProjectCommands(program, {
|
|
|
109
108
|
resolveVaultPath
|
|
110
109
|
});
|
|
111
110
|
|
|
112
|
-
registerWorkgraphCommands(program, { chalk, resolveVaultPath });
|
|
113
111
|
registerTailscaleCommands(program, { chalk });
|
|
114
112
|
registerConfigCommands(program, { chalk, resolveVaultPath });
|
|
115
113
|
registerRouteCommands(program, { chalk, resolveVaultPath });
|
|
@@ -35,7 +35,7 @@ describe('CLI command registration modules', () => {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
const names = listCommandNames(program);
|
|
38
|
-
expect(names).toEqual(expect.arrayContaining(['init', 'setup', 'store', 'capture']));
|
|
38
|
+
expect(names).toEqual(expect.arrayContaining(['init', 'setup', 'store', 'patch', 'capture', 'inbox']));
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
it('registers query commands with profile option', () => {
|
|
@@ -49,7 +49,7 @@ describe('CLI command registration modules', () => {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
const names = listCommandNames(program);
|
|
52
|
-
expect(names).toEqual(expect.arrayContaining(['search', 'vsearch', 'context', 'inject', 'observe', 'reflect', 'session-recap']));
|
|
52
|
+
expect(names).toEqual(expect.arrayContaining(['search', 'vsearch', 'context', 'recall', 'inject', 'observe', 'reflect', 'session-recap']));
|
|
53
53
|
|
|
54
54
|
const contextCommand = program.commands.find((command) => command.name() === 'context');
|
|
55
55
|
const profileOption = contextCommand?.options.find((option) => option.flags.includes('--profile <profile>'));
|
|
@@ -121,10 +121,13 @@ describe('CLI command registration modules', () => {
|
|
|
121
121
|
const names = listCommandNames(program);
|
|
122
122
|
expect(names).toEqual(expect.arrayContaining([
|
|
123
123
|
'doctor',
|
|
124
|
+
'benchmark',
|
|
125
|
+
'maintain',
|
|
124
126
|
'embed',
|
|
125
127
|
'compat',
|
|
126
128
|
'graph',
|
|
127
129
|
'entities',
|
|
130
|
+
'entity',
|
|
128
131
|
'link',
|
|
129
132
|
'rebuild',
|
|
130
133
|
'archive',
|
|
@@ -163,4 +166,14 @@ describe('CLI command registration modules', () => {
|
|
|
163
166
|
const unique = new Set(names);
|
|
164
167
|
expect(unique.size).toBe(names.length);
|
|
165
168
|
});
|
|
169
|
+
|
|
170
|
+
it('does not register removed workgraph commands', () => {
|
|
171
|
+
const program = registerAllCommandModules(new Command());
|
|
172
|
+
const names = listCommandNames(program);
|
|
173
|
+
|
|
174
|
+
expect(names).not.toContain('wg');
|
|
175
|
+
expect(names).not.toContain('thread');
|
|
176
|
+
expect(names).not.toContain('primitive');
|
|
177
|
+
expect(names).not.toContain('ledger');
|
|
178
|
+
});
|
|
166
179
|
});
|
|
@@ -6,9 +6,14 @@ describe('CLI help contract', () => {
|
|
|
6
6
|
const help = registerAllCommandModules().helpInformation();
|
|
7
7
|
expect(help).toContain('init');
|
|
8
8
|
expect(help).toContain('context');
|
|
9
|
+
expect(help).toContain('recall');
|
|
9
10
|
expect(help).toContain('inject');
|
|
10
11
|
expect(help).toContain('doctor');
|
|
12
|
+
expect(help).toContain('benchmark');
|
|
13
|
+
expect(help).toContain('maintain');
|
|
11
14
|
expect(help).toContain('embed');
|
|
15
|
+
expect(help).toContain('inbox');
|
|
16
|
+
expect(help).toContain('patch');
|
|
12
17
|
expect(help).toContain('compat');
|
|
13
18
|
expect(help).toContain('graph');
|
|
14
19
|
expect(help).toContain('reflect');
|
|
@@ -18,6 +23,7 @@ describe('CLI help contract', () => {
|
|
|
18
23
|
expect(help).toContain('template');
|
|
19
24
|
expect(help).toContain('config');
|
|
20
25
|
expect(help).toContain('route');
|
|
26
|
+
expect(help).toContain('entity');
|
|
21
27
|
});
|
|
22
28
|
|
|
23
29
|
it('documents context/compat/inject/project help details', () => {
|
|
@@ -36,4 +42,14 @@ describe('CLI help contract', () => {
|
|
|
36
42
|
expect(projectListHelp).toContain('archived projects are hidden');
|
|
37
43
|
expect(projectBoardHelp).toContain('default: status');
|
|
38
44
|
});
|
|
45
|
+
|
|
46
|
+
it('does not advertise removed workgraph commands', () => {
|
|
47
|
+
const program = registerAllCommandModules();
|
|
48
|
+
const names = program.commands.map((command) => command.name());
|
|
49
|
+
|
|
50
|
+
expect(names).not.toContain('wg');
|
|
51
|
+
expect(names).not.toContain('thread');
|
|
52
|
+
expect(names).not.toContain('primitive');
|
|
53
|
+
expect(names).not.toContain('ledger');
|
|
54
|
+
});
|
|
39
55
|
});
|
|
@@ -212,6 +212,66 @@ export function registerCoreCommands(
|
|
|
212
212
|
}
|
|
213
213
|
});
|
|
214
214
|
|
|
215
|
+
// === CAPTURE ===
|
|
216
|
+
program
|
|
217
|
+
.command('patch <idOrPath>')
|
|
218
|
+
.description('Patch an existing memory document')
|
|
219
|
+
.option('--append <text>', 'Append text to the document body (or target section)')
|
|
220
|
+
.option('--replace <text>', 'Text to replace')
|
|
221
|
+
.option('--with <text>', 'Replacement text used with --replace')
|
|
222
|
+
.option('--section <heading>', 'Limit patching to a markdown section heading')
|
|
223
|
+
.option('--content <text>', 'Replace document body (or section body) with text')
|
|
224
|
+
.option('-v, --vault <path>', 'Vault path (default: find nearest)')
|
|
225
|
+
.action(async (idOrPath, options) => {
|
|
226
|
+
try {
|
|
227
|
+
const modeFlags = [
|
|
228
|
+
typeof options.append === 'string',
|
|
229
|
+
typeof options.replace === 'string',
|
|
230
|
+
typeof options.content === 'string'
|
|
231
|
+
];
|
|
232
|
+
const selectedModes = modeFlags.filter(Boolean).length;
|
|
233
|
+
if (selectedModes !== 1) {
|
|
234
|
+
throw new Error('Select exactly one patch mode: --append, --replace/--with, or --content.');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (typeof options.with === 'string' && typeof options.replace !== 'string') {
|
|
238
|
+
throw new Error('--with can only be used together with --replace.');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const vault = await getVault(options.vault);
|
|
242
|
+
const patchOptions = {
|
|
243
|
+
idOrPath,
|
|
244
|
+
mode: 'content'
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
if (typeof options.append === 'string') {
|
|
248
|
+
patchOptions.mode = 'append';
|
|
249
|
+
patchOptions.append = options.append;
|
|
250
|
+
} else if (typeof options.replace === 'string') {
|
|
251
|
+
if (typeof options.with !== 'string') {
|
|
252
|
+
throw new Error('--replace requires --with.');
|
|
253
|
+
}
|
|
254
|
+
patchOptions.mode = 'replace';
|
|
255
|
+
patchOptions.replace = options.replace;
|
|
256
|
+
patchOptions.with = options.with;
|
|
257
|
+
} else if (typeof options.content === 'string') {
|
|
258
|
+
patchOptions.mode = 'content';
|
|
259
|
+
patchOptions.content = options.content;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (typeof options.section === 'string') {
|
|
263
|
+
patchOptions.section = options.section;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const doc = await vault.patch(patchOptions);
|
|
267
|
+
console.log(chalk.green(`✓ Patched: ${doc.id}`));
|
|
268
|
+
console.log(chalk.dim(` Path: ${doc.path}`));
|
|
269
|
+
} catch (err) {
|
|
270
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
215
275
|
// === CAPTURE ===
|
|
216
276
|
program
|
|
217
277
|
.command('capture <note>')
|
|
@@ -234,4 +294,32 @@ export function registerCoreCommands(
|
|
|
234
294
|
process.exit(1);
|
|
235
295
|
}
|
|
236
296
|
});
|
|
297
|
+
|
|
298
|
+
// === INBOX ===
|
|
299
|
+
const inbox = program
|
|
300
|
+
.command('inbox')
|
|
301
|
+
.description('Manage raw captures in the inbox');
|
|
302
|
+
|
|
303
|
+
inbox
|
|
304
|
+
.command('add [content]')
|
|
305
|
+
.description('Add content to inbox (or pipe stdin)')
|
|
306
|
+
.option('-t, --title <title>', 'Capture title')
|
|
307
|
+
.option('--source <source>', 'Capture source label')
|
|
308
|
+
.option('--stdin', 'Read content from stdin')
|
|
309
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
310
|
+
.action(async (content, options) => {
|
|
311
|
+
try {
|
|
312
|
+
const { inboxAddCommand } = await import('../dist/commands/inbox.js');
|
|
313
|
+
await inboxAddCommand({
|
|
314
|
+
vaultPath: options.vault,
|
|
315
|
+
content,
|
|
316
|
+
title: options.title,
|
|
317
|
+
source: options.source,
|
|
318
|
+
stdin: options.stdin || !process.stdin.isTTY
|
|
319
|
+
});
|
|
320
|
+
} catch (err) {
|
|
321
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
237
325
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { registerCoreCommands } from './register-core-commands.js';
|
|
6
|
+
import { chalkStub } from './test-helpers/cli-command-fixtures.js';
|
|
7
|
+
|
|
8
|
+
function buildProgram(patchImpl) {
|
|
9
|
+
const program = new Command();
|
|
10
|
+
registerCoreCommands(program, {
|
|
11
|
+
chalk: chalkStub,
|
|
12
|
+
path,
|
|
13
|
+
fs,
|
|
14
|
+
createVault: async () => ({ getCategories: () => [], getQmdRoot: () => '', getQmdCollection: () => '' }),
|
|
15
|
+
getVault: async () => ({ patch: patchImpl }),
|
|
16
|
+
runQmd: async () => {}
|
|
17
|
+
});
|
|
18
|
+
return program;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('register-core-commands patch command', () => {
|
|
22
|
+
it('exposes patch command mode flags', () => {
|
|
23
|
+
const program = buildProgram(async () => ({ id: 'decisions/example', path: '/vault/decisions/example.md' }));
|
|
24
|
+
const patchCommand = program.commands.find((command) => command.name() === 'patch');
|
|
25
|
+
expect(patchCommand).toBeDefined();
|
|
26
|
+
const optionFlags = patchCommand?.options.map((option) => option.flags) ?? [];
|
|
27
|
+
expect(optionFlags).toEqual(expect.arrayContaining([
|
|
28
|
+
'--append <text>',
|
|
29
|
+
'--replace <text>',
|
|
30
|
+
'--with <text>',
|
|
31
|
+
'--section <heading>',
|
|
32
|
+
'--content <text>',
|
|
33
|
+
'-v, --vault <path>'
|
|
34
|
+
]));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('forwards append mode payload to vault.patch', async () => {
|
|
38
|
+
const patchMock = vi.fn(async () => ({ id: 'decisions/example', path: '/vault/decisions/example.md' }));
|
|
39
|
+
const program = buildProgram(patchMock);
|
|
40
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
41
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
await program.parseAsync(['patch', 'decisions/example', '--append', 'new line'], { from: 'user' });
|
|
45
|
+
expect(patchMock).toHaveBeenCalledWith({
|
|
46
|
+
idOrPath: 'decisions/example',
|
|
47
|
+
mode: 'append',
|
|
48
|
+
append: 'new line'
|
|
49
|
+
});
|
|
50
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
51
|
+
} finally {
|
|
52
|
+
logSpy.mockRestore();
|
|
53
|
+
errorSpy.mockRestore();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('forwards section/content mode payload to vault.patch', async () => {
|
|
58
|
+
const patchMock = vi.fn(async () => ({ id: 'decisions/example', path: '/vault/decisions/example.md' }));
|
|
59
|
+
const program = buildProgram(patchMock);
|
|
60
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
61
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await program.parseAsync(
|
|
65
|
+
['patch', 'decisions/example', '--section', 'Notes', '--content', 'updated notes'],
|
|
66
|
+
{ from: 'user' }
|
|
67
|
+
);
|
|
68
|
+
expect(patchMock).toHaveBeenCalledWith({
|
|
69
|
+
idOrPath: 'decisions/example',
|
|
70
|
+
mode: 'content',
|
|
71
|
+
content: 'updated notes',
|
|
72
|
+
section: 'Notes'
|
|
73
|
+
});
|
|
74
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
75
|
+
} finally {
|
|
76
|
+
logSpy.mockRestore();
|
|
77
|
+
errorSpy.mockRestore();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -4,10 +4,33 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export function registerMaintenanceCommands(program, { chalk }) {
|
|
7
|
+
// === MAINTAIN ===
|
|
8
|
+
program
|
|
9
|
+
.command('maintain')
|
|
10
|
+
.description('Run background inbox maintenance workers')
|
|
11
|
+
.option('--worker <name>', 'Run a single worker (curator|janitor|distiller|surveyor)')
|
|
12
|
+
.option('--limit <n>', 'Limit inbox items processed per worker', (value) => Number.parseInt(value, 10))
|
|
13
|
+
.option('--dry-run', 'Preview actions without writing files')
|
|
14
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
15
|
+
.action(async (options) => {
|
|
16
|
+
try {
|
|
17
|
+
const { maintainCommand } = await import('../dist/commands/maintain.js');
|
|
18
|
+
await maintainCommand({
|
|
19
|
+
vaultPath: options.vault,
|
|
20
|
+
worker: options.worker,
|
|
21
|
+
limit: options.limit,
|
|
22
|
+
dryRun: options.dryRun
|
|
23
|
+
});
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
7
30
|
// === DOCTOR (health check) ===
|
|
8
31
|
program
|
|
9
32
|
.command('doctor')
|
|
10
|
-
.description('
|
|
33
|
+
.description('Run installation and environment diagnostics')
|
|
11
34
|
.option('-v, --vault <path>', 'Vault path')
|
|
12
35
|
.option('--fix', 'Apply safe auto-fixes for qmd index, embeddings, and dead collections')
|
|
13
36
|
.option('--json', 'Output machine-readable JSON')
|
|
@@ -24,10 +47,11 @@ export function registerMaintenanceCommands(program, { chalk }) {
|
|
|
24
47
|
return;
|
|
25
48
|
}
|
|
26
49
|
|
|
27
|
-
console.log(chalk.cyan('\n🩺 ClawVault
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
console.log(chalk.cyan('\n🩺 ClawVault Doctor Report\n'));
|
|
51
|
+
if (report.vaultPath) {
|
|
52
|
+
console.log(chalk.dim(`Vault: ${report.vaultPath}`));
|
|
53
|
+
}
|
|
54
|
+
console.log(chalk.dim(`Generated: ${report.generatedAt}`));
|
|
31
55
|
console.log();
|
|
32
56
|
|
|
33
57
|
for (const check of report.checks) {
|
|
@@ -36,7 +60,7 @@ export function registerMaintenanceCommands(program, { chalk }) {
|
|
|
36
60
|
: check.status === 'warn'
|
|
37
61
|
? chalk.yellow('⚠')
|
|
38
62
|
: chalk.red('✗');
|
|
39
|
-
const line = `${check.label}: ${check.detail}
|
|
63
|
+
const line = check.detail ? `${check.label}: ${check.detail}` : check.label;
|
|
40
64
|
const renderedLine = check.status === 'ok'
|
|
41
65
|
? chalk.green(line)
|
|
42
66
|
: check.status === 'warn'
|
|
@@ -150,11 +174,37 @@ export function registerMaintenanceCommands(program, { chalk }) {
|
|
|
150
174
|
.command('entities')
|
|
151
175
|
.description('List all linkable entities in the vault')
|
|
152
176
|
.option('-v, --vault <path>', 'Vault path')
|
|
177
|
+
.option('--refresh', 'Regenerate entity profiles before listing')
|
|
153
178
|
.option('--json', 'Output as JSON')
|
|
154
179
|
.action(async (options) => {
|
|
155
180
|
try {
|
|
156
181
|
const { entitiesCommand } = await import('../dist/commands/entities.js');
|
|
157
|
-
await entitiesCommand({
|
|
182
|
+
await entitiesCommand({
|
|
183
|
+
json: options.json,
|
|
184
|
+
vaultPath: options.vault,
|
|
185
|
+
refresh: options.refresh
|
|
186
|
+
});
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// === ENTITY ===
|
|
194
|
+
program
|
|
195
|
+
.command('entity <name>')
|
|
196
|
+
.description('Show synthesized profile for one entity')
|
|
197
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
198
|
+
.option('--refresh', 'Regenerate entity profiles before lookup')
|
|
199
|
+
.option('--json', 'Output as JSON')
|
|
200
|
+
.action(async (name, options) => {
|
|
201
|
+
try {
|
|
202
|
+
const { entityCommand } = await import('../dist/commands/entities.js');
|
|
203
|
+
await entityCommand(name, {
|
|
204
|
+
json: options.json,
|
|
205
|
+
vaultPath: options.vault,
|
|
206
|
+
refresh: options.refresh
|
|
207
|
+
});
|
|
158
208
|
} catch (err) {
|
|
159
209
|
console.error(chalk.red(`Error: ${err.message}`));
|
|
160
210
|
process.exit(1);
|
|
@@ -298,4 +348,31 @@ export function registerMaintenanceCommands(program, { chalk }) {
|
|
|
298
348
|
process.exit(1);
|
|
299
349
|
}
|
|
300
350
|
});
|
|
351
|
+
|
|
352
|
+
// === BENCHMARK ===
|
|
353
|
+
const benchmark = program
|
|
354
|
+
.command('benchmark')
|
|
355
|
+
.description('Run benchmark harnesses');
|
|
356
|
+
|
|
357
|
+
benchmark
|
|
358
|
+
.command('observer')
|
|
359
|
+
.description('Evaluate observer output quality against annotated transcripts')
|
|
360
|
+
.option('--fixtures-dir <path>', 'Fixture root directory (default: testdata/observer-benchmark)')
|
|
361
|
+
.option('--provider <provider>', 'Compression provider (mock|anthropic|openai|gemini|xai|openai-compatible|ollama|minimax|zai)', 'mock')
|
|
362
|
+
.option('--model <model>', 'Model override for live provider runs')
|
|
363
|
+
.option('--report-format <format>', 'Report output format (json|text)', 'text')
|
|
364
|
+
.action(async (options) => {
|
|
365
|
+
try {
|
|
366
|
+
const { benchmarkObserverCommand } = await import('../dist/commands/benchmark.js');
|
|
367
|
+
await benchmarkObserverCommand({
|
|
368
|
+
fixturesDir: options.fixturesDir,
|
|
369
|
+
provider: options.provider,
|
|
370
|
+
model: options.model,
|
|
371
|
+
reportFormat: options.reportFormat
|
|
372
|
+
});
|
|
373
|
+
} catch (err) {
|
|
374
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
301
378
|
}
|
|
@@ -17,7 +17,7 @@ export function registerQueryCommands(
|
|
|
17
17
|
// === SEARCH ===
|
|
18
18
|
program
|
|
19
19
|
.command('search <query>')
|
|
20
|
-
.description('Search the vault
|
|
20
|
+
.description('Search the vault using in-process hybrid retrieval (BM25 + semantic when configured)')
|
|
21
21
|
.option('-n, --limit <n>', 'Max results (default: 10)', '10')
|
|
22
22
|
.option('-c, --category <category>', 'Filter by category')
|
|
23
23
|
.option('--tags <tags>', 'Filter by tags (comma-separated)')
|
|
@@ -25,8 +25,8 @@ export function registerQueryCommands(
|
|
|
25
25
|
.option('--full', 'Include full content in results')
|
|
26
26
|
.option('-v, --vault <path>', 'Vault path')
|
|
27
27
|
.option('--json', 'Output as JSON')
|
|
28
|
-
.option('--semantic', '
|
|
29
|
-
.option('--rebuild-embeddings', 'Rebuild
|
|
28
|
+
.option('--semantic', 'Legacy alias. Hybrid retrieval is already default when embeddings are configured.')
|
|
29
|
+
.option('--rebuild-embeddings', 'Rebuild hosted embedding cache before searching')
|
|
30
30
|
.action(async (query, options) => {
|
|
31
31
|
try {
|
|
32
32
|
const vaultPath = resolveVaultPath(options.vault);
|
|
@@ -45,34 +45,16 @@ export function registerQueryCommands(
|
|
|
45
45
|
console.log();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
limit: options.semantic ? 50 : parseInt(options.limit, 10),
|
|
48
|
+
const results = await vault.find(query, {
|
|
49
|
+
limit: parseInt(options.limit, 10),
|
|
51
50
|
category: options.category,
|
|
52
51
|
tags: options.tags?.split(',').map((value) => value.trim()),
|
|
53
52
|
fullContent: options.full,
|
|
54
53
|
temporalBoost: options.recent
|
|
55
54
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// Apply hybrid search if --semantic flag is set
|
|
61
|
-
if (options.semantic) {
|
|
62
|
-
const { EmbeddingCache, hybridSearch } = await import('../dist/lib/hybrid-search.js');
|
|
63
|
-
const cache = new EmbeddingCache(vaultPath);
|
|
64
|
-
cache.load();
|
|
65
|
-
|
|
66
|
-
if (cache.size === 0) {
|
|
67
|
-
console.log(chalk.yellow('Warning: No embeddings found. Run with --rebuild-embeddings to build the cache.'));
|
|
68
|
-
} else {
|
|
69
|
-
results = await hybridSearch(query, bm25Results, cache, {
|
|
70
|
-
topK: parseInt(options.limit, 10),
|
|
71
|
-
rrfK: 60
|
|
72
|
-
});
|
|
73
|
-
searchMode = 'Hybrid (BM25 + Semantic)';
|
|
74
|
-
}
|
|
75
|
-
}
|
|
55
|
+
const searchMode = options.semantic
|
|
56
|
+
? 'Hybrid (legacy flag acknowledged)'
|
|
57
|
+
: 'Hybrid (in-process)';
|
|
76
58
|
|
|
77
59
|
if (options.json) {
|
|
78
60
|
console.log(JSON.stringify({ searchMode, results }, null, 2));
|
|
@@ -84,7 +66,7 @@ export function registerQueryCommands(
|
|
|
84
66
|
return;
|
|
85
67
|
}
|
|
86
68
|
|
|
87
|
-
const icon =
|
|
69
|
+
const icon = '🔍🧠';
|
|
88
70
|
console.log(chalk.cyan(`\n${icon} Found ${results.length} result(s) for "${query}" [${searchMode}]:\n`));
|
|
89
71
|
|
|
90
72
|
for (const result of results) {
|
|
@@ -114,7 +96,7 @@ export function registerQueryCommands(
|
|
|
114
96
|
// === VSEARCH ===
|
|
115
97
|
program
|
|
116
98
|
.command('vsearch <query>')
|
|
117
|
-
.description('Semantic search via
|
|
99
|
+
.description('Semantic search via hosted embeddings (qmd fallback when available)')
|
|
118
100
|
.option('-n, --limit <n>', 'Max results (default: 5)', '5')
|
|
119
101
|
.option('-c, --category <category>', 'Filter by category')
|
|
120
102
|
.option('--tags <tags>', 'Filter by tags (comma-separated)')
|
|
@@ -220,6 +202,41 @@ export function registerQueryCommands(
|
|
|
220
202
|
}
|
|
221
203
|
});
|
|
222
204
|
|
|
205
|
+
// === INJECT ===
|
|
206
|
+
program
|
|
207
|
+
.command('recall <query>')
|
|
208
|
+
.description('Recall memory context with strategy classification (quick|entity|temporal|verification|relationship)')
|
|
209
|
+
.option('-n, --limit <n>', 'Max results (default: 6)', '6')
|
|
210
|
+
.option('--strategy <strategy>', 'Override strategy (quick|entity|temporal|verification|relationship)')
|
|
211
|
+
.option('--json', 'Output as JSON')
|
|
212
|
+
.option('--no-sources', 'Hide source paths in recall context')
|
|
213
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
214
|
+
.action(async (query, options) => {
|
|
215
|
+
try {
|
|
216
|
+
const parsedLimit = Number.parseInt(options.limit, 10);
|
|
217
|
+
if (!Number.isFinite(parsedLimit) || parsedLimit <= 0) {
|
|
218
|
+
throw new Error(`Invalid --limit value: ${options.limit}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const allowedStrategies = new Set(['quick', 'entity', 'temporal', 'verification', 'relationship']);
|
|
222
|
+
if (options.strategy && !allowedStrategies.has(options.strategy)) {
|
|
223
|
+
throw new Error(`Invalid --strategy value: ${options.strategy}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const { recallCommand } = await import('../dist/commands/recall.js');
|
|
227
|
+
await recallCommand(query, {
|
|
228
|
+
vaultPath: resolveVaultPath(options.vault),
|
|
229
|
+
limit: parsedLimit,
|
|
230
|
+
strategy: options.strategy,
|
|
231
|
+
json: options.json,
|
|
232
|
+
includeSources: options.sources
|
|
233
|
+
});
|
|
234
|
+
} catch (err) {
|
|
235
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
223
240
|
// === INJECT ===
|
|
224
241
|
program
|
|
225
242
|
.command('inject <message>')
|