clawvault 3.4.0 → 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.
Files changed (37) hide show
  1. package/CHANGELOG.md +543 -0
  2. package/LICENSE +21 -0
  3. package/README.md +26 -26
  4. package/SKILL.md +369 -0
  5. package/dist/{chunk-X3SPPUFG.js → chunk-JI7VUQV7.js} +118 -132
  6. package/dist/{chunk-QYQAGBTM.js → chunk-QUFQBAHP.js} +148 -125
  7. package/dist/cli/index.js +1 -1
  8. package/dist/commands/compat.js +1 -1
  9. package/dist/commands/observe.js +1 -1
  10. package/dist/commands/status.js +4 -4
  11. package/dist/index.js +11 -8
  12. package/dist/openclaw-plugin.js +6 -1
  13. package/docs/clawhub-security-release-playbook.md +75 -0
  14. package/docs/getting-started/installation.md +99 -0
  15. package/docs/openclaw-plugin-usage.md +152 -0
  16. package/openclaw.plugin.json +1 -1
  17. package/package.json +26 -8
  18. package/bin/command-registration.test.js +0 -179
  19. package/bin/command-runtime.test.js +0 -154
  20. package/bin/help-contract.test.js +0 -55
  21. package/bin/register-config-route-commands.test.js +0 -121
  22. package/bin/register-core-commands.test.js +0 -80
  23. package/bin/register-kanban-commands.test.js +0 -83
  24. package/bin/register-project-commands.test.js +0 -206
  25. package/bin/register-query-commands.test.js +0 -80
  26. package/bin/register-resilience-commands.test.js +0 -81
  27. package/bin/register-task-commands.test.js +0 -69
  28. package/bin/register-template-commands.test.js +0 -87
  29. package/bin/test-helpers/cli-command-fixtures.js +0 -120
  30. package/dashboard/lib/graph-diff.test.js +0 -75
  31. package/dashboard/lib/vault-parser.test.js +0 -254
  32. package/hooks/clawvault/HOOK.md +0 -130
  33. package/hooks/clawvault/handler.js +0 -1696
  34. package/hooks/clawvault/handler.test.js +0 -576
  35. package/hooks/clawvault/integrity.js +0 -112
  36. package/hooks/clawvault/integrity.test.js +0 -32
  37. 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 REQUIRED_HOOK_EVENTS = ["gateway:startup", "command:new", "session:start"];
8
- var REQUIRED_HOOK_BIN = "clawvault";
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 hook runtime validation."
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 checkPackageHookRegistration(options) {
66
- const packageRaw = readOptionalFile(resolveProjectFile("package.json", options.baseDir));
67
- if (!packageRaw) {
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: "hook manifest requirements",
90
+ label: "package plugin registration",
136
91
  status: "error",
137
- detail: "HOOK.md not found"
92
+ detail: packageJson.error
138
93
  };
139
94
  }
140
- try {
141
- const parsed = matter(hookRaw);
142
- const requiresBins = parsed.data?.metadata?.openclaw?.requires?.bins;
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: "hook manifest requirements",
153
- status: "warn",
154
- detail: `Missing required hook bin "${REQUIRED_HOOK_BIN}"`,
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 checkHookHandlerSafety(options) {
166
- const handlerRaw = readOptionalFile(resolveProjectFile("hooks/clawvault/handler.js", options.baseDir));
167
- if (!handlerRaw) {
111
+ function checkPackageExtensionRegistration(options) {
112
+ const packageJson = loadPackageJson(options);
113
+ if (!packageJson.parsed) {
168
114
  return {
169
- label: "hook handler script",
115
+ label: "package extension registration",
170
116
  status: "error",
171
- detail: "handler.js not found"
117
+ detail: packageJson.error
172
118
  };
173
119
  }
174
- const usesExecFileSync = handlerRaw.includes("execFileSync");
175
- const usesExecSync = /\bexecSync\b/.test(handlerRaw);
176
- const enablesShell = /\bshell\s*:\s*true\b/.test(handlerRaw);
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: "hook handler safety",
191
- status: "warn",
192
- detail: `Missing conventions: ${violations.join(", ")}`,
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 { label: "hook handler safety", status: "ok" };
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("hooks/clawvault/openclaw.plugin.json", options.baseDir));
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: "hooks/clawvault/openclaw.plugin.json not found",
205
- hint: "Add openclaw.plugin.json to hooks/clawvault/ for config schema registration."
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 (!parsed.id || parsed.id !== "clawvault") {
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
- const hasVaultPath = Boolean(parsed.configSchema.properties?.vaultPath);
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
- checkPackageHookRegistration(options),
273
+ checkPackagePluginRegistration(options),
274
+ checkPackageExtensionRegistration(options),
288
275
  checkPluginManifest(options),
289
- checkHookManifest(options),
290
- checkHookManifestRequirements(options),
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;