clawvault 3.4.1 → 3.5.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/CHANGELOG.md +543 -0
- package/LICENSE +21 -0
- package/SKILL.md +369 -0
- package/dist/{chunk-X3SPPUFG.js → chunk-JI7VUQV7.js} +118 -132
- package/dist/{chunk-PLNK37JD.js → chunk-QUFQBAHP.js} +114 -217
- package/dist/cli/index.js +1 -1
- package/dist/commands/compat.js +1 -1
- package/dist/commands/observe.js +1 -1
- package/dist/commands/status.js +4 -4
- package/dist/index.js +11 -8
- package/dist/openclaw-plugin.js +6 -1
- package/docs/clawhub-security-release-playbook.md +75 -0
- package/docs/getting-started/installation.md +99 -0
- package/docs/openclaw-plugin-usage.md +152 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +26 -8
- package/bin/command-registration.test.js +0 -179
- package/bin/command-runtime.test.js +0 -154
- package/bin/help-contract.test.js +0 -55
- package/bin/register-config-route-commands.test.js +0 -121
- package/bin/register-core-commands.test.js +0 -80
- package/bin/register-kanban-commands.test.js +0 -83
- package/bin/register-project-commands.test.js +0 -206
- package/bin/register-query-commands.test.js +0 -80
- package/bin/register-resilience-commands.test.js +0 -81
- package/bin/register-task-commands.test.js +0 -69
- package/bin/register-template-commands.test.js +0 -87
- package/bin/test-helpers/cli-command-fixtures.js +0 -120
- package/dashboard/lib/graph-diff.test.js +0 -75
- package/dashboard/lib/vault-parser.test.js +0 -254
- package/hooks/clawvault/HOOK.md +0 -130
- package/hooks/clawvault/handler.js +0 -1696
- package/hooks/clawvault/handler.test.js +0 -576
- package/hooks/clawvault/integrity.js +0 -112
- package/hooks/clawvault/integrity.test.js +0 -32
- package/hooks/clawvault/openclaw.plugin.json +0 -190
package/SKILL.md
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clawvault
|
|
3
|
+
version: "3.5.0"
|
|
4
|
+
description: Agent memory system with memory graph, context profiles, checkpoint/recover, structured storage, semantic search, and observational memory. Use when: storing/searching memories, preventing context death, graph-aware context retrieval, repairing broken sessions. Don't use when: general file I/O.
|
|
5
|
+
author: Versatly
|
|
6
|
+
source: https://github.com/Versatly/clawvault
|
|
7
|
+
repository: https://github.com/Versatly/clawvault
|
|
8
|
+
homepage: https://clawvault.dev
|
|
9
|
+
user-invocable: true
|
|
10
|
+
openclaw: {"emoji":"🐘","requires":{"bins":["clawvault","qmd"],"env":[]},"install":[{"id":"node","kind":"node","package":"clawvault","bins":["clawvault"],"label":"Install ClawVault CLI (npm)"},{"id":"qmd","kind":"node","package":"github:tobi/qmd","bins":["qmd"],"label":"Install qmd backend (required for query/context workflows)"}],"homepage":"https://clawvault.dev"}
|
|
11
|
+
metadata: {"openclaw":{"emoji":"🐘","requires":{"bins":["clawvault","qmd"],"env":[]},"install":[{"id":"node","kind":"node","package":"clawvault","bins":["clawvault"],"label":"Install ClawVault CLI (npm)"},{"id":"qmd","kind":"node","package":"github:tobi/qmd","bins":["qmd"],"label":"Install qmd backend (required for query/context workflows)"}],"homepage":"https://clawvault.dev"}}
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# ClawVault 🐘
|
|
15
|
+
|
|
16
|
+
An elephant never forgets. Structured memory for OpenClaw agents.
|
|
17
|
+
|
|
18
|
+
> **Built for [OpenClaw](https://openclaw.ai)**. Canonical install: npm CLI + plugin entry registration.
|
|
19
|
+
|
|
20
|
+
## Security & Transparency
|
|
21
|
+
|
|
22
|
+
**What this skill does:**
|
|
23
|
+
- Reads/writes markdown files in your vault directory (`CLAWVAULT_PATH` or auto-discovered)
|
|
24
|
+
- `repair-session` reads and modifies OpenClaw session transcripts (`~/.openclaw/agents/`) — creates backups before writing
|
|
25
|
+
- Provides an OpenClaw **plugin entry** (`src/openclaw-plugin.ts` -> `dist/openclaw-plugin.js`) with lifecycle handlers and memory tools.
|
|
26
|
+
- `observe --compress` makes LLM API calls (Gemini Flash by default) to compress session transcripts into observations
|
|
27
|
+
|
|
28
|
+
**Environment variables used:**
|
|
29
|
+
- `CLAWVAULT_PATH` — vault location (optional, auto-discovered if not set)
|
|
30
|
+
- `OPENCLAW_HOME` / `OPENCLAW_STATE_DIR` — used by `repair-session` to find session transcripts
|
|
31
|
+
- `GEMINI_API_KEY` — used by `observe` for LLM compression (optional, only if using observe features)
|
|
32
|
+
|
|
33
|
+
**No cloud sync — all data stays local. No network calls except LLM API for observe compression.**
|
|
34
|
+
|
|
35
|
+
**This is a full CLI tool, not instruction-only.** It writes files, registers plugin handlers/tools, and runs code.
|
|
36
|
+
|
|
37
|
+
**Auditability:** the published bundle includes `SKILL.md`, `openclaw.plugin.json`, and built plugin runtime in `dist/openclaw-plugin.js`.
|
|
38
|
+
|
|
39
|
+
## Install (Canonical)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install -g clawvault
|
|
43
|
+
|
|
44
|
+
# Add the package path to plugins.load.paths in openclaw.json
|
|
45
|
+
# Enable plugins.entries.clawvault.enabled=true
|
|
46
|
+
# Set plugins.slots.memory="clawvault"
|
|
47
|
+
# Restart gateway process
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`clawhub install clawvault` can install skill guidance, but plugin runtime wiring is controlled by OpenClaw plugin config.
|
|
51
|
+
|
|
52
|
+
### Recommended Safe Install Flow
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# 1) Review package metadata before install
|
|
56
|
+
npm view clawvault version dist.integrity dist.tarball repository.url
|
|
57
|
+
|
|
58
|
+
# 2) Install CLI + qmd dependency
|
|
59
|
+
npm install -g clawvault@latest
|
|
60
|
+
npm install -g github:tobi/qmd
|
|
61
|
+
|
|
62
|
+
# 3) Review plugin manifest and built entrypoint before enabling
|
|
63
|
+
node -e "const fs=require('fs');['openclaw.plugin.json','dist/openclaw-plugin.js'].forEach((p)=>console.log(fs.existsSync(p)?p:`missing: ${p}`))"
|
|
64
|
+
|
|
65
|
+
# 4) Configure OpenClaw plugin load path + entry, then restart gateway
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Setup
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Initialize vault (creates folder structure + templates)
|
|
72
|
+
clawvault init ~/my-vault
|
|
73
|
+
|
|
74
|
+
# Or set env var to use existing vault
|
|
75
|
+
export CLAWVAULT_PATH=/path/to/memory
|
|
76
|
+
|
|
77
|
+
# Optional: shell integration (aliases + CLAWVAULT_PATH)
|
|
78
|
+
clawvault shell-init >> ~/.bashrc
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Quick Start for New Agents
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Start your session (recover + recap + summary)
|
|
85
|
+
clawvault wake
|
|
86
|
+
|
|
87
|
+
# Capture and checkpoint during work
|
|
88
|
+
clawvault capture "TODO: Review PR tomorrow"
|
|
89
|
+
clawvault checkpoint --working-on "PR review" --focus "type guards"
|
|
90
|
+
|
|
91
|
+
# End your session with a handoff
|
|
92
|
+
clawvault sleep "PR review + type guards" --next "respond to CI" --blocked "waiting for CI"
|
|
93
|
+
|
|
94
|
+
# Health check when something feels off
|
|
95
|
+
clawvault doctor
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Reality Checks Before Use
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Verify runtime compatibility with current OpenClaw setup
|
|
102
|
+
clawvault compat
|
|
103
|
+
|
|
104
|
+
# Verify qmd is available
|
|
105
|
+
qmd --version
|
|
106
|
+
|
|
107
|
+
# Verify OpenClaw CLI is installed in this shell
|
|
108
|
+
openclaw --version
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
ClawVault currently depends on `qmd` for core vault/query flows.
|
|
112
|
+
|
|
113
|
+
## Current Feature Set
|
|
114
|
+
|
|
115
|
+
### Memory Graph
|
|
116
|
+
|
|
117
|
+
ClawVault builds a typed knowledge graph from wiki-links, tags, and frontmatter:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# View graph summary
|
|
121
|
+
clawvault graph
|
|
122
|
+
|
|
123
|
+
# Refresh graph index
|
|
124
|
+
clawvault graph --refresh
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Graph is stored at `.clawvault/graph-index.json` — schema versioned, incremental rebuild.
|
|
128
|
+
|
|
129
|
+
### Graph-Aware Context Retrieval
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Default context (semantic + graph neighbors)
|
|
133
|
+
clawvault context "database decision"
|
|
134
|
+
|
|
135
|
+
# With a profile preset
|
|
136
|
+
clawvault context --profile planning "Q1 roadmap"
|
|
137
|
+
clawvault context --profile incident "production outage"
|
|
138
|
+
clawvault context --profile handoff "session end"
|
|
139
|
+
|
|
140
|
+
# Auto profile (used by OpenClaw plugin)
|
|
141
|
+
clawvault context --profile auto "current task"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Context Profiles
|
|
145
|
+
|
|
146
|
+
| Profile | Purpose |
|
|
147
|
+
|---------|---------|
|
|
148
|
+
| `default` | Balanced retrieval |
|
|
149
|
+
| `planning` | Broader strategic context |
|
|
150
|
+
| `incident` | Recent events, blockers, urgent items |
|
|
151
|
+
| `handoff` | Session transition context |
|
|
152
|
+
| `auto` | Plugin-selected profile based on session intent |
|
|
153
|
+
|
|
154
|
+
### OpenClaw Compatibility Diagnostics
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Check plugin wiring and packaging safety
|
|
158
|
+
clawvault compat
|
|
159
|
+
|
|
160
|
+
# Strict mode for CI
|
|
161
|
+
clawvault compat --strict
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Core Commands
|
|
165
|
+
|
|
166
|
+
### Wake + Sleep (primary)
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
clawvault wake
|
|
170
|
+
clawvault sleep "what I was working on" --next "ship v1" --blocked "waiting for API key"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Store memories by type
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# Types: fact, feeling, decision, lesson, commitment, preference, relationship, project
|
|
177
|
+
clawvault remember decision "Use Postgres over SQLite" --content "Need concurrent writes for multi-agent setup"
|
|
178
|
+
clawvault remember lesson "Context death is survivable" --content "Checkpoint before heavy work"
|
|
179
|
+
clawvault remember relationship "Justin Dukes" --content "Client contact at Hale Pet Door"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Quick capture to inbox
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
clawvault capture "TODO: Review PR tomorrow"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Search (requires qmd installed)
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Keyword search (fast)
|
|
192
|
+
clawvault search "client contacts"
|
|
193
|
+
|
|
194
|
+
# Semantic search (slower, more accurate)
|
|
195
|
+
clawvault vsearch "what did we decide about the database"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Context Death Resilience
|
|
199
|
+
|
|
200
|
+
### Wake (start of session)
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
clawvault wake
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Sleep (end of session)
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
clawvault sleep "what I was working on" --next "finish docs" --blocked "waiting for review"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Checkpoint (save state frequently)
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
clawvault checkpoint --working-on "PR review" --focus "type guards" --blocked "waiting for CI"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Recover (manual check)
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
clawvault recover --clear
|
|
222
|
+
# Shows: death time, last checkpoint, recent handoff
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Handoff (manual session end)
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
clawvault handoff \
|
|
229
|
+
--working-on "ClawVault improvements" \
|
|
230
|
+
--blocked "npm token" \
|
|
231
|
+
--next "publish to npm, create skill" \
|
|
232
|
+
--feeling "productive"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Recap (bootstrap new session)
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
clawvault recap
|
|
239
|
+
# Shows: recent handoffs, active projects, pending commitments, lessons
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Auto-linking
|
|
243
|
+
|
|
244
|
+
Wiki-link entity mentions in markdown files:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# Link all files
|
|
248
|
+
clawvault link --all
|
|
249
|
+
|
|
250
|
+
# Link single file
|
|
251
|
+
clawvault link memory/2024-01-15.md
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Folder Structure
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
vault/
|
|
258
|
+
├── .clawvault/ # Internal state
|
|
259
|
+
│ ├── last-checkpoint.json
|
|
260
|
+
│ └── dirty-death.flag
|
|
261
|
+
├── decisions/ # Key choices with reasoning
|
|
262
|
+
├── lessons/ # Insights and patterns
|
|
263
|
+
├── people/ # One file per person
|
|
264
|
+
├── projects/ # Active work tracking
|
|
265
|
+
├── handoffs/ # Session continuity
|
|
266
|
+
├── inbox/ # Quick captures
|
|
267
|
+
└── templates/ # Document templates
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Best Practices
|
|
271
|
+
|
|
272
|
+
1. **Wake at session start** — `clawvault wake` restores context
|
|
273
|
+
2. **Checkpoint every 10-15 min** during heavy work
|
|
274
|
+
3. **Sleep before session end** — `clawvault sleep` captures next steps
|
|
275
|
+
4. **Use types** — knowing WHAT you're storing helps WHERE to put it
|
|
276
|
+
5. **Wiki-link liberally** — `[[person-name]]` builds your knowledge graph
|
|
277
|
+
|
|
278
|
+
## Checklist for AGENTS.md
|
|
279
|
+
|
|
280
|
+
```markdown
|
|
281
|
+
## Memory Checklist
|
|
282
|
+
- [ ] Run `clawvault wake` at session start
|
|
283
|
+
- [ ] Checkpoint during heavy work
|
|
284
|
+
- [ ] Capture key decisions/lessons with `clawvault remember`
|
|
285
|
+
- [ ] Use wiki-links like `[[person-name]]`
|
|
286
|
+
- [ ] End with `clawvault sleep "..." --next "..." --blocked "..."`
|
|
287
|
+
- [ ] Run `clawvault doctor` when something feels off
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Append this checklist to existing memory instructions. Do not replace your full AGENTS.md behavior unless you intend to.
|
|
291
|
+
|
|
292
|
+
## Session Transcript Repair (v1.5.0+)
|
|
293
|
+
|
|
294
|
+
When the Anthropic API rejects with "unexpected tool_use_id found in tool_result blocks", use:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
# See what's wrong (dry-run)
|
|
298
|
+
clawvault repair-session --dry-run
|
|
299
|
+
|
|
300
|
+
# Fix it
|
|
301
|
+
clawvault repair-session
|
|
302
|
+
|
|
303
|
+
# Repair a specific session
|
|
304
|
+
clawvault repair-session --session <id> --agent <agent-id>
|
|
305
|
+
|
|
306
|
+
# List available sessions
|
|
307
|
+
clawvault repair-session --list
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**What it fixes:**
|
|
311
|
+
- Orphaned `tool_result` blocks referencing non-existent `tool_use` IDs
|
|
312
|
+
- Aborted tool calls with partial JSON
|
|
313
|
+
- Broken parent chain references
|
|
314
|
+
|
|
315
|
+
Backups are created automatically (use `--no-backup` to skip).
|
|
316
|
+
|
|
317
|
+
## Troubleshooting
|
|
318
|
+
|
|
319
|
+
- **qmd not installed** — install qmd, then confirm with `qmd --version`
|
|
320
|
+
- **No ClawVault found** — run `clawvault init` or set `CLAWVAULT_PATH`
|
|
321
|
+
- **CLAWVAULT_PATH missing** — run `clawvault shell-init` and add to shell rc
|
|
322
|
+
- **Too many orphan links** — run `clawvault link --orphans`
|
|
323
|
+
- **Inbox backlog warning** — process or archive inbox items
|
|
324
|
+
- **"unexpected tool_use_id" error** — run `clawvault repair-session`
|
|
325
|
+
- **OpenClaw integration drift** — run `clawvault compat`
|
|
326
|
+
- **Plugin not loading** — verify `plugins.load.paths`, `plugins.entries.clawvault.enabled`, and `plugins.slots.memory="clawvault"` in OpenClaw config
|
|
327
|
+
- **Graph out of date** — run `clawvault graph --refresh`
|
|
328
|
+
- **Wrong context for task** — try `clawvault context --profile incident` or `--profile planning`
|
|
329
|
+
|
|
330
|
+
## Stability Snapshot
|
|
331
|
+
|
|
332
|
+
- Typecheck passes (`npm run typecheck`)
|
|
333
|
+
- Test suite passes (`449/449`)
|
|
334
|
+
- Cross-platform path handling hardened for Windows in:
|
|
335
|
+
- qmd URI/document path normalization
|
|
336
|
+
- WebDAV path safety and filesystem resolution
|
|
337
|
+
- shell-init output expectations
|
|
338
|
+
- OpenClaw runtime wiring validated by `clawvault compat --strict` (requires local `openclaw` binary for full runtime validation)
|
|
339
|
+
|
|
340
|
+
## Integration with qmd
|
|
341
|
+
|
|
342
|
+
ClawVault uses [qmd](https://github.com/tobi/qmd) for search:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
# Install qmd
|
|
346
|
+
bun install -g github:tobi/qmd
|
|
347
|
+
|
|
348
|
+
# Alternative
|
|
349
|
+
npm install -g github:tobi/qmd
|
|
350
|
+
|
|
351
|
+
# Add vault as collection
|
|
352
|
+
qmd collection add /path/to/vault --name my-memory --mask "**/*.md"
|
|
353
|
+
|
|
354
|
+
# Update index
|
|
355
|
+
qmd update && qmd embed
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Environment Variables
|
|
359
|
+
|
|
360
|
+
- `CLAWVAULT_PATH` — Default vault path (skips auto-discovery)
|
|
361
|
+
- `OPENCLAW_HOME` — OpenClaw home directory (used by repair-session)
|
|
362
|
+
- `OPENCLAW_STATE_DIR` — OpenClaw state directory (used by repair-session)
|
|
363
|
+
- `GEMINI_API_KEY` — Used by `observe` for LLM-powered compression (optional)
|
|
364
|
+
|
|
365
|
+
## Links
|
|
366
|
+
|
|
367
|
+
- npm: https://www.npmjs.com/package/clawvault
|
|
368
|
+
- GitHub: https://github.com/Versatly/clawvault
|
|
369
|
+
- Issues: https://github.com/Versatly/clawvault/issues
|
|
@@ -4,8 +4,18 @@ import * as path from "path";
|
|
|
4
4
|
import matter from "gray-matter";
|
|
5
5
|
import { spawnSync } from "child_process";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
-
var
|
|
8
|
-
var
|
|
7
|
+
var REQUIRED_OPENCLAW_PLUGIN_PATH = "./openclaw.plugin.json";
|
|
8
|
+
var REQUIRED_OPENCLAW_EXTENSION = "./dist/openclaw-plugin.js";
|
|
9
|
+
var LEGACY_HOOK_DIR = "hooks/clawvault";
|
|
10
|
+
var FORBIDDEN_PACKAGE_FILE_ENTRIES = [
|
|
11
|
+
"hooks",
|
|
12
|
+
"src",
|
|
13
|
+
"tests",
|
|
14
|
+
"testdata",
|
|
15
|
+
"benchmarks",
|
|
16
|
+
"eval",
|
|
17
|
+
"autoresearch"
|
|
18
|
+
];
|
|
9
19
|
function readOptionalFile(filePath) {
|
|
10
20
|
try {
|
|
11
21
|
if (!fs.existsSync(filePath)) return null;
|
|
@@ -34,6 +44,17 @@ function resolveProjectFile(relativePath, baseDir) {
|
|
|
34
44
|
}
|
|
35
45
|
return path.resolve(findPackageRoot(), relativePath);
|
|
36
46
|
}
|
|
47
|
+
function loadPackageJson(options) {
|
|
48
|
+
const packageRaw = readOptionalFile(resolveProjectFile("package.json", options.baseDir));
|
|
49
|
+
if (!packageRaw) {
|
|
50
|
+
return { parsed: null, error: "package.json not found" };
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
return { parsed: JSON.parse(packageRaw) };
|
|
54
|
+
} catch (err) {
|
|
55
|
+
return { parsed: null, error: err?.message || "Unable to parse package.json" };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
37
58
|
function checkOpenClawCli() {
|
|
38
59
|
const result = spawnSync("openclaw", ["--version"], { stdio: "ignore" });
|
|
39
60
|
if (result.error) {
|
|
@@ -41,7 +62,7 @@ function checkOpenClawCli() {
|
|
|
41
62
|
label: "openclaw CLI available",
|
|
42
63
|
status: "warn",
|
|
43
64
|
detail: "openclaw binary not found",
|
|
44
|
-
hint: "Install OpenClaw CLI to enable
|
|
65
|
+
hint: "Install OpenClaw CLI to enable runtime validation."
|
|
45
66
|
};
|
|
46
67
|
}
|
|
47
68
|
if (typeof result.status === "number" && result.status !== 0) {
|
|
@@ -62,152 +83,69 @@ function checkOpenClawCli() {
|
|
|
62
83
|
}
|
|
63
84
|
return { label: "openclaw CLI available", status: "ok" };
|
|
64
85
|
}
|
|
65
|
-
function
|
|
66
|
-
const
|
|
67
|
-
if (!
|
|
68
|
-
return {
|
|
69
|
-
label: "package hook registration",
|
|
70
|
-
status: "error",
|
|
71
|
-
detail: "package.json not found"
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
try {
|
|
75
|
-
const parsed = JSON.parse(packageRaw);
|
|
76
|
-
const registeredHooks = parsed.openclaw?.hooks ?? [];
|
|
77
|
-
if (registeredHooks.includes("./hooks/clawvault")) {
|
|
78
|
-
return {
|
|
79
|
-
label: "package hook registration",
|
|
80
|
-
status: "ok",
|
|
81
|
-
detail: "./hooks/clawvault"
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
return {
|
|
85
|
-
label: "package hook registration",
|
|
86
|
-
status: "error",
|
|
87
|
-
detail: "Missing ./hooks/clawvault in package openclaw.hooks"
|
|
88
|
-
};
|
|
89
|
-
} catch (err) {
|
|
90
|
-
return {
|
|
91
|
-
label: "package hook registration",
|
|
92
|
-
status: "error",
|
|
93
|
-
detail: err?.message || "Unable to parse package.json"
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function checkHookManifest(options) {
|
|
98
|
-
const hookRaw = readOptionalFile(resolveProjectFile("hooks/clawvault/HOOK.md", options.baseDir));
|
|
99
|
-
if (!hookRaw) {
|
|
100
|
-
return {
|
|
101
|
-
label: "hook manifest",
|
|
102
|
-
status: "error",
|
|
103
|
-
detail: "HOOK.md not found"
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
try {
|
|
107
|
-
const parsed = matter(hookRaw);
|
|
108
|
-
const openclaw = parsed.data?.metadata?.openclaw;
|
|
109
|
-
const events = Array.isArray(openclaw?.events) ? openclaw?.events ?? [] : [];
|
|
110
|
-
const missingEvents = REQUIRED_HOOK_EVENTS.filter((event) => !events.includes(event));
|
|
111
|
-
if (missingEvents.length === 0) {
|
|
112
|
-
return {
|
|
113
|
-
label: "hook manifest events",
|
|
114
|
-
status: "ok",
|
|
115
|
-
detail: events.join(", ")
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
return {
|
|
119
|
-
label: "hook manifest events",
|
|
120
|
-
status: "error",
|
|
121
|
-
detail: `Missing events: ${missingEvents.join(", ")}`
|
|
122
|
-
};
|
|
123
|
-
} catch (err) {
|
|
124
|
-
return {
|
|
125
|
-
label: "hook manifest events",
|
|
126
|
-
status: "error",
|
|
127
|
-
detail: err?.message || "Unable to parse HOOK.md frontmatter"
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
function checkHookManifestRequirements(options) {
|
|
132
|
-
const hookRaw = readOptionalFile(resolveProjectFile("hooks/clawvault/HOOK.md", options.baseDir));
|
|
133
|
-
if (!hookRaw) {
|
|
86
|
+
function checkPackagePluginRegistration(options) {
|
|
87
|
+
const packageJson = loadPackageJson(options);
|
|
88
|
+
if (!packageJson.parsed) {
|
|
134
89
|
return {
|
|
135
|
-
label: "
|
|
90
|
+
label: "package plugin registration",
|
|
136
91
|
status: "error",
|
|
137
|
-
detail:
|
|
92
|
+
detail: packageJson.error
|
|
138
93
|
};
|
|
139
94
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const bins = Array.isArray(requiresBins) ? requiresBins : [];
|
|
144
|
-
if (bins.includes(REQUIRED_HOOK_BIN)) {
|
|
145
|
-
return {
|
|
146
|
-
label: "hook manifest requirements",
|
|
147
|
-
status: "ok",
|
|
148
|
-
detail: `bins: ${bins.join(", ")}`
|
|
149
|
-
};
|
|
150
|
-
}
|
|
95
|
+
const openclaw = packageJson.parsed.openclaw;
|
|
96
|
+
const pluginPath = typeof openclaw?.plugin === "string" ? openclaw.plugin : "";
|
|
97
|
+
if (pluginPath === REQUIRED_OPENCLAW_PLUGIN_PATH) {
|
|
151
98
|
return {
|
|
152
|
-
label: "
|
|
153
|
-
status: "
|
|
154
|
-
detail:
|
|
155
|
-
hint: 'Add metadata.openclaw.requires.bins: ["clawvault"] to hooks/clawvault/HOOK.md.'
|
|
156
|
-
};
|
|
157
|
-
} catch (err) {
|
|
158
|
-
return {
|
|
159
|
-
label: "hook manifest requirements",
|
|
160
|
-
status: "error",
|
|
161
|
-
detail: err?.message || "Unable to parse HOOK.md frontmatter"
|
|
99
|
+
label: "package plugin registration",
|
|
100
|
+
status: "ok",
|
|
101
|
+
detail: pluginPath
|
|
162
102
|
};
|
|
163
103
|
}
|
|
104
|
+
return {
|
|
105
|
+
label: "package plugin registration",
|
|
106
|
+
status: "error",
|
|
107
|
+
detail: `Missing openclaw.plugin=${REQUIRED_OPENCLAW_PLUGIN_PATH}`,
|
|
108
|
+
hint: `Set package.json openclaw.plugin to "${REQUIRED_OPENCLAW_PLUGIN_PATH}".`
|
|
109
|
+
};
|
|
164
110
|
}
|
|
165
|
-
function
|
|
166
|
-
const
|
|
167
|
-
if (!
|
|
111
|
+
function checkPackageExtensionRegistration(options) {
|
|
112
|
+
const packageJson = loadPackageJson(options);
|
|
113
|
+
if (!packageJson.parsed) {
|
|
168
114
|
return {
|
|
169
|
-
label: "
|
|
115
|
+
label: "package extension registration",
|
|
170
116
|
status: "error",
|
|
171
|
-
detail:
|
|
117
|
+
detail: packageJson.error
|
|
172
118
|
};
|
|
173
119
|
}
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
const delegatesAutoProfile = /['"]--profile['"]\s*,\s*['"]auto['"]/.test(handlerRaw);
|
|
178
|
-
const violations = [];
|
|
179
|
-
if (!usesExecFileSync || usesExecSync) {
|
|
180
|
-
violations.push("execFileSync-only execution path");
|
|
181
|
-
}
|
|
182
|
-
if (enablesShell) {
|
|
183
|
-
violations.push("shell:false execution option");
|
|
184
|
-
}
|
|
185
|
-
if (!delegatesAutoProfile) {
|
|
186
|
-
violations.push("shared context profile delegation (--profile auto)");
|
|
187
|
-
}
|
|
188
|
-
if (violations.length > 0) {
|
|
120
|
+
const openclaw = packageJson.parsed.openclaw;
|
|
121
|
+
const extensions = Array.isArray(openclaw?.extensions) ? openclaw.extensions : [];
|
|
122
|
+
if (extensions.includes(REQUIRED_OPENCLAW_EXTENSION)) {
|
|
189
123
|
return {
|
|
190
|
-
label: "
|
|
191
|
-
status: "
|
|
192
|
-
detail:
|
|
193
|
-
hint: "Use execFileSync (no shell), avoid execSync, and delegate profile inference via --profile auto."
|
|
124
|
+
label: "package extension registration",
|
|
125
|
+
status: "ok",
|
|
126
|
+
detail: REQUIRED_OPENCLAW_EXTENSION
|
|
194
127
|
};
|
|
195
128
|
}
|
|
196
|
-
return {
|
|
129
|
+
return {
|
|
130
|
+
label: "package extension registration",
|
|
131
|
+
status: "error",
|
|
132
|
+
detail: `Missing ${REQUIRED_OPENCLAW_EXTENSION} in package openclaw.extensions`,
|
|
133
|
+
hint: `Add "${REQUIRED_OPENCLAW_EXTENSION}" to package.json openclaw.extensions.`
|
|
134
|
+
};
|
|
197
135
|
}
|
|
198
136
|
function checkPluginManifest(options) {
|
|
199
|
-
const manifestRaw = readOptionalFile(resolveProjectFile("
|
|
137
|
+
const manifestRaw = readOptionalFile(resolveProjectFile("openclaw.plugin.json", options.baseDir));
|
|
200
138
|
if (!manifestRaw) {
|
|
201
139
|
return {
|
|
202
140
|
label: "plugin manifest",
|
|
203
141
|
status: "error",
|
|
204
|
-
detail: "
|
|
205
|
-
hint: "Add openclaw.plugin.json
|
|
142
|
+
detail: "openclaw.plugin.json not found",
|
|
143
|
+
hint: "Add root openclaw.plugin.json for OpenClaw plugin config schema."
|
|
206
144
|
};
|
|
207
145
|
}
|
|
208
146
|
try {
|
|
209
147
|
const parsed = JSON.parse(manifestRaw);
|
|
210
|
-
if (
|
|
148
|
+
if (parsed.id !== "clawvault") {
|
|
211
149
|
return {
|
|
212
150
|
label: "plugin manifest",
|
|
213
151
|
status: "error",
|
|
@@ -218,12 +156,10 @@ function checkPluginManifest(options) {
|
|
|
218
156
|
return {
|
|
219
157
|
label: "plugin manifest",
|
|
220
158
|
status: "error",
|
|
221
|
-
detail: "Missing configSchema in plugin manifest"
|
|
222
|
-
hint: "Add configSchema to openclaw.plugin.json for config validation."
|
|
159
|
+
detail: "Missing configSchema in plugin manifest"
|
|
223
160
|
};
|
|
224
161
|
}
|
|
225
|
-
|
|
226
|
-
if (!hasVaultPath) {
|
|
162
|
+
if (!parsed.configSchema.properties?.vaultPath) {
|
|
227
163
|
return {
|
|
228
164
|
label: "plugin manifest",
|
|
229
165
|
status: "warn",
|
|
@@ -244,6 +180,56 @@ function checkPluginManifest(options) {
|
|
|
244
180
|
};
|
|
245
181
|
}
|
|
246
182
|
}
|
|
183
|
+
function checkPackageFilesHygiene(options) {
|
|
184
|
+
const packageJson = loadPackageJson(options);
|
|
185
|
+
if (!packageJson.parsed) {
|
|
186
|
+
return {
|
|
187
|
+
label: "package files hygiene",
|
|
188
|
+
status: "error",
|
|
189
|
+
detail: packageJson.error
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const files = Array.isArray(packageJson.parsed.files) ? packageJson.parsed.files : [];
|
|
193
|
+
if (files.length === 0) {
|
|
194
|
+
return {
|
|
195
|
+
label: "package files hygiene",
|
|
196
|
+
status: "warn",
|
|
197
|
+
detail: "package.json files allowlist is missing",
|
|
198
|
+
hint: "Define package.json files to avoid publishing non-runtime directories."
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const normalized = files.map((entry) => String(entry).replace(/\\/g, "/").replace(/\/+$/, ""));
|
|
202
|
+
const forbidden = normalized.filter(
|
|
203
|
+
(entry) => FORBIDDEN_PACKAGE_FILE_ENTRIES.some((name) => entry === name || entry.startsWith(`${name}/`))
|
|
204
|
+
);
|
|
205
|
+
if (forbidden.length > 0) {
|
|
206
|
+
return {
|
|
207
|
+
label: "package files hygiene",
|
|
208
|
+
status: "warn",
|
|
209
|
+
detail: `Forbidden publish entries: ${forbidden.join(", ")}`,
|
|
210
|
+
hint: "Remove legacy/test/research directories from package.json files."
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
label: "package files hygiene",
|
|
215
|
+
status: "ok"
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function checkLegacyHooksRemoved(options) {
|
|
219
|
+
const hooksPath = resolveProjectFile(LEGACY_HOOK_DIR, options.baseDir);
|
|
220
|
+
if (fs.existsSync(hooksPath)) {
|
|
221
|
+
return {
|
|
222
|
+
label: "legacy hooks removed",
|
|
223
|
+
status: "warn",
|
|
224
|
+
detail: `${LEGACY_HOOK_DIR} still exists`,
|
|
225
|
+
hint: "Remove hooks/clawvault legacy handler artifacts in plugin-first architecture."
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
label: "legacy hooks removed",
|
|
230
|
+
status: "ok"
|
|
231
|
+
};
|
|
232
|
+
}
|
|
247
233
|
function checkSkillMetadata(options) {
|
|
248
234
|
const skillRaw = readOptionalFile(resolveProjectFile("SKILL.md", options.baseDir));
|
|
249
235
|
if (!skillRaw) {
|
|
@@ -284,11 +270,11 @@ function checkSkillMetadata(options) {
|
|
|
284
270
|
function checkOpenClawCompatibility(options = {}) {
|
|
285
271
|
const checks = [
|
|
286
272
|
checkOpenClawCli(),
|
|
287
|
-
|
|
273
|
+
checkPackagePluginRegistration(options),
|
|
274
|
+
checkPackageExtensionRegistration(options),
|
|
288
275
|
checkPluginManifest(options),
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
checkHookHandlerSafety(options),
|
|
276
|
+
checkPackageFilesHygiene(options),
|
|
277
|
+
checkLegacyHooksRemoved(options),
|
|
292
278
|
checkSkillMetadata(options)
|
|
293
279
|
];
|
|
294
280
|
const warnings = checks.filter((check) => check.status === "warn").length;
|