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.
Files changed (36) hide show
  1. package/CHANGELOG.md +543 -0
  2. package/LICENSE +21 -0
  3. package/SKILL.md +369 -0
  4. package/dist/{chunk-X3SPPUFG.js → chunk-JI7VUQV7.js} +118 -132
  5. package/dist/{chunk-PLNK37JD.js → chunk-QUFQBAHP.js} +114 -217
  6. package/dist/cli/index.js +1 -1
  7. package/dist/commands/compat.js +1 -1
  8. package/dist/commands/observe.js +1 -1
  9. package/dist/commands/status.js +4 -4
  10. package/dist/index.js +11 -8
  11. package/dist/openclaw-plugin.js +6 -1
  12. package/docs/clawhub-security-release-playbook.md +75 -0
  13. package/docs/getting-started/installation.md +99 -0
  14. package/docs/openclaw-plugin-usage.md +152 -0
  15. package/openclaw.plugin.json +1 -1
  16. package/package.json +26 -8
  17. package/bin/command-registration.test.js +0 -179
  18. package/bin/command-runtime.test.js +0 -154
  19. package/bin/help-contract.test.js +0 -55
  20. package/bin/register-config-route-commands.test.js +0 -121
  21. package/bin/register-core-commands.test.js +0 -80
  22. package/bin/register-kanban-commands.test.js +0 -83
  23. package/bin/register-project-commands.test.js +0 -206
  24. package/bin/register-query-commands.test.js +0 -80
  25. package/bin/register-resilience-commands.test.js +0 -81
  26. package/bin/register-task-commands.test.js +0 -69
  27. package/bin/register-template-commands.test.js +0 -87
  28. package/bin/test-helpers/cli-command-fixtures.js +0 -120
  29. package/dashboard/lib/graph-diff.test.js +0 -75
  30. package/dashboard/lib/vault-parser.test.js +0 -254
  31. package/hooks/clawvault/HOOK.md +0 -130
  32. package/hooks/clawvault/handler.js +0 -1696
  33. package/hooks/clawvault/handler.test.js +0 -576
  34. package/hooks/clawvault/integrity.js +0 -112
  35. package/hooks/clawvault/integrity.test.js +0 -32
  36. package/hooks/clawvault/openclaw.plugin.json +0 -190
@@ -1,6 +1,19 @@
1
1
  import {
2
2
  buildRecallResult
3
3
  } from "./chunk-RL2L6I6K.js";
4
+ import {
5
+ recover
6
+ } from "./chunk-OIWVQYQF.js";
7
+ import {
8
+ formatAge
9
+ } from "./chunk-7ZRP733D.js";
10
+ import {
11
+ buildSessionRecap
12
+ } from "./chunk-ZKGY7WTT.js";
13
+ import {
14
+ checkpoint,
15
+ flush
16
+ } from "./chunk-F55HGNU4.js";
4
17
  import {
5
18
  synthesizeEntityProfiles
6
19
  } from "./chunk-NSXYM6EZ.js";
@@ -707,7 +720,7 @@ import * as fs4 from "fs";
707
720
  import * as path4 from "path";
708
721
 
709
722
  // src/plugin/clawvault-cli.ts
710
- import { execFileSync } from "child_process";
723
+ import { execFileSync, spawn } from "child_process";
711
724
  import * as fs3 from "fs";
712
725
  import * as path3 from "path";
713
726
 
@@ -806,8 +819,6 @@ function verifyExecutableIntegrity(executablePath, expectedSha256) {
806
819
 
807
820
  // src/plugin/clawvault-cli.ts
808
821
  var MAX_CONTEXT_PROMPT_LENGTH = 500;
809
- var MAX_CONTEXT_SNIPPET_LENGTH = 220;
810
- var MAX_RECAP_SNIPPET_LENGTH = 220;
811
822
  var CLAWVAULT_EXECUTABLE = "clawvault";
812
823
  var ONE_KIB = 1024;
813
824
  var ONE_MIB = ONE_KIB * ONE_KIB;
@@ -823,139 +834,6 @@ function sanitizePromptForContext(value) {
823
834
  if (typeof value !== "string") return "";
824
835
  return value.replace(/[\x00-\x1f\x7f]/g, " ").replace(/\s+/g, " ").trim().slice(0, MAX_CONTEXT_PROMPT_LENGTH);
825
836
  }
826
- function truncateSnippet(snippet) {
827
- const safe = sanitizeForDisplay(snippet).replace(/\s+/g, " ").trim();
828
- if (safe.length <= MAX_CONTEXT_SNIPPET_LENGTH) return safe;
829
- return `${safe.slice(0, MAX_CONTEXT_SNIPPET_LENGTH - 3).trimEnd()}...`;
830
- }
831
- function truncateRecapSnippet(snippet) {
832
- const safe = sanitizeForDisplay(snippet).replace(/\s+/g, " ").trim();
833
- if (safe.length <= MAX_RECAP_SNIPPET_LENGTH) return safe;
834
- return `${safe.slice(0, MAX_RECAP_SNIPPET_LENGTH - 3).trimEnd()}...`;
835
- }
836
- function isRecord2(value) {
837
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
838
- }
839
- function parseTopLevelJson(output) {
840
- const text = output.trim();
841
- if (!text) return null;
842
- const tryParse = (candidate) => {
843
- try {
844
- const parsed = JSON.parse(candidate);
845
- return isRecord2(parsed) ? parsed : null;
846
- } catch {
847
- return null;
848
- }
849
- };
850
- const direct = tryParse(text);
851
- if (direct) return direct;
852
- const findJsonEnd = (start) => {
853
- const open = text[start];
854
- if (open !== "{" && open !== "[") return -1;
855
- const stack = [open];
856
- let inString = false;
857
- let escaped = false;
858
- for (let i = start + 1; i < text.length; i += 1) {
859
- const ch = text[i];
860
- if (inString) {
861
- if (escaped) {
862
- escaped = false;
863
- continue;
864
- }
865
- if (ch === "\\") {
866
- escaped = true;
867
- continue;
868
- }
869
- if (ch === '"') {
870
- inString = false;
871
- }
872
- continue;
873
- }
874
- if (ch === '"') {
875
- inString = true;
876
- continue;
877
- }
878
- if (ch === "{" || ch === "[") {
879
- stack.push(ch);
880
- continue;
881
- }
882
- if (ch === "}" || ch === "]") {
883
- const expected = ch === "}" ? "{" : "[";
884
- const top = stack[stack.length - 1];
885
- if (top !== expected) return -1;
886
- stack.pop();
887
- if (stack.length === 0) {
888
- return i;
889
- }
890
- }
891
- }
892
- return -1;
893
- };
894
- for (let start = 0; start < text.length; start += 1) {
895
- const ch = text[start];
896
- if (ch !== "{" && ch !== "[") continue;
897
- const end = findJsonEnd(start);
898
- if (end < 0) continue;
899
- const parsed = tryParse(text.slice(start, end + 1));
900
- if (parsed) return parsed;
901
- }
902
- return null;
903
- }
904
- function resolveEntryArray(parsed, keys) {
905
- for (const key of keys) {
906
- const value = parsed[key];
907
- if (!Array.isArray(value)) continue;
908
- return value.filter((item) => isRecord2(item));
909
- }
910
- return [];
911
- }
912
- function parseContextJson(output, maxResults) {
913
- const parsed = parseTopLevelJson(output);
914
- if (!parsed) {
915
- return [];
916
- }
917
- const rows = resolveEntryArray(parsed, ["context", "results", "entries", "memories"]);
918
- return rows.slice(0, maxResults).map((entry) => {
919
- const nestedDocument = isRecord2(entry.document) ? entry.document : null;
920
- const title = sanitizeForDisplay(
921
- entry.title ?? nestedDocument?.title ?? nestedDocument?.id ?? entry.path ?? "Untitled"
922
- );
923
- const resolvedPath = sanitizeForDisplay(
924
- entry.path ?? entry.relPath ?? nestedDocument?.path ?? ""
925
- );
926
- const resolvedAge = sanitizeForDisplay(entry.age ?? entry.modified ?? "unknown age");
927
- const snippetSource = String(
928
- entry.snippet ?? entry.text ?? entry.content ?? nestedDocument?.snippet ?? nestedDocument?.content ?? ""
929
- );
930
- return {
931
- title,
932
- path: resolvedPath,
933
- age: resolvedAge,
934
- snippet: truncateSnippet(snippetSource),
935
- score: Number.isFinite(Number(entry.score)) ? Number(entry.score) : 0
936
- };
937
- }).filter((entry) => entry.snippet.length > 0);
938
- }
939
- function parseSessionRecapJson(output, maxResults) {
940
- const parsed = parseTopLevelJson(output);
941
- if (!parsed) {
942
- return [];
943
- }
944
- const rows = resolveEntryArray(parsed, ["messages", "turns", "recap"]);
945
- return rows.map((entry) => {
946
- const role = typeof entry.role === "string" ? entry.role.toLowerCase() : "";
947
- const normalizedRole = role === "user" || role === "human" ? "User" : role === "assistant" || role === "ai" ? "Assistant" : "";
948
- if (!normalizedRole) return null;
949
- const text = truncateRecapSnippet(
950
- typeof entry.text === "string" ? entry.text : typeof entry.content === "string" ? entry.content : ""
951
- );
952
- if (!text) return null;
953
- return {
954
- role: normalizedRole,
955
- text
956
- };
957
- }).filter((entry) => Boolean(entry)).slice(-maxResults);
958
- }
959
837
  function formatSessionContextInjection(recapEntries, memoryEntries) {
960
838
  const lines = [
961
839
  "[ClawVault] Session context restored:",
@@ -1043,22 +921,6 @@ function runClawvault(args, pluginConfig, options = {}) {
1043
921
  };
1044
922
  }
1045
923
  }
1046
- function parseRecoveryOutput(output) {
1047
- if (!output || typeof output !== "string") {
1048
- return { hadDeath: false, workingOn: null };
1049
- }
1050
- const hadDeath = output.includes("Context death detected") || output.includes("died") || output.includes("\u26A0\uFE0F");
1051
- if (!hadDeath) {
1052
- return { hadDeath: false, workingOn: null };
1053
- }
1054
- const workingOnLine = output.split("\n").find((line) => line.toLowerCase().includes("working on"));
1055
- if (!workingOnLine) {
1056
- return { hadDeath: true, workingOn: null };
1057
- }
1058
- const parts = workingOnLine.split(":");
1059
- const workingOn = parts.length > 1 ? sanitizeForDisplay(parts.slice(1).join(":").trim()) : null;
1060
- return { hadDeath: true, workingOn: workingOn || null };
1061
- }
1062
924
  function getObserveCursorPath(vaultPath) {
1063
925
  return path3.join(vaultPath, ".clawvault", OBSERVE_CURSOR_FILE);
1064
926
  }
@@ -1141,12 +1003,43 @@ function shouldObserveActiveSessions(vaultPath, agentId, pluginConfig) {
1141
1003
  return false;
1142
1004
  }
1143
1005
  function runObserverCron(vaultPath, agentId, pluginConfig, options = {}) {
1006
+ if (!isOptInEnabled(pluginConfig, "allowClawvaultExec")) {
1007
+ return false;
1008
+ }
1144
1009
  const args = ["observe", "--cron", "--agent", agentId, "-v", vaultPath];
1145
1010
  if (Number.isFinite(options.minNewBytes) && Number(options.minNewBytes) > 0) {
1146
1011
  args.push("--min-new", String(Math.floor(Number(options.minNewBytes))));
1147
1012
  }
1148
- const result = runClawvault(args, pluginConfig, { timeoutMs: 12e4 });
1149
- return !result.skipped && result.success;
1013
+ const executablePath = resolveExecutablePath(CLAWVAULT_EXECUTABLE, {
1014
+ explicitPath: getConfiguredExecutablePath(pluginConfig)
1015
+ });
1016
+ if (!executablePath) {
1017
+ return false;
1018
+ }
1019
+ const expectedSha256 = getConfiguredExecutableSha256(pluginConfig);
1020
+ const integrityResult = verifyExecutableIntegrity(executablePath, expectedSha256);
1021
+ if (!integrityResult.ok) {
1022
+ return false;
1023
+ }
1024
+ let sanitizedArgs;
1025
+ try {
1026
+ sanitizedArgs = sanitizeExecArgs(args);
1027
+ } catch {
1028
+ return false;
1029
+ }
1030
+ try {
1031
+ const child = spawn(executablePath, sanitizedArgs, {
1032
+ detached: process.platform !== "win32",
1033
+ stdio: "ignore",
1034
+ shell: false
1035
+ });
1036
+ child.on("error", () => {
1037
+ });
1038
+ child.unref();
1039
+ return true;
1040
+ } catch {
1041
+ return false;
1042
+ }
1150
1043
  }
1151
1044
  function resolveSessionKey(input) {
1152
1045
  return sanitizeSessionKey(input);
@@ -1575,20 +1468,51 @@ function rewriteQuestionWithMemoryEvidence(originalContent, memoryHits) {
1575
1468
  }
1576
1469
 
1577
1470
  // src/plugin/vault-context-injector.ts
1471
+ import * as path5 from "path";
1578
1472
  var DEFAULT_MAX_CONTEXT_RESULTS = 4;
1579
1473
  var DEFAULT_MAX_RECAP_RESULTS = 6;
1474
+ var DEFAULT_MIN_CONTEXT_SCORE = 0.2;
1475
+ function normalizeRelativePath(vaultPath, absolutePath) {
1476
+ const relativePath = path5.relative(vaultPath, absolutePath).replace(/\\/g, "/");
1477
+ if (!relativePath || relativePath.startsWith("..")) {
1478
+ return path5.basename(absolutePath);
1479
+ }
1480
+ return relativePath;
1481
+ }
1482
+ function formatContextAge(modified) {
1483
+ const ageMs = Date.now() - modified.getTime();
1484
+ return `${formatAge(ageMs)} ago`;
1485
+ }
1486
+ function toContextEntry(vaultPath, result) {
1487
+ const relativePath = normalizeRelativePath(vaultPath, result.document.path);
1488
+ return {
1489
+ title: sanitizeForDisplay(result.document.title || "Untitled"),
1490
+ path: sanitizeForDisplay(relativePath),
1491
+ age: sanitizeForDisplay(formatContextAge(result.document.modified)),
1492
+ snippet: sanitizeForDisplay(result.snippet).replace(/\s+/g, " ").trim().slice(0, 220),
1493
+ score: Number.isFinite(Number(result.score)) ? Number(result.score) : 0
1494
+ };
1495
+ }
1496
+ function truncateRecapText(text) {
1497
+ const cleaned = sanitizeForDisplay(text).replace(/\s+/g, " ").trim();
1498
+ if (cleaned.length <= 220) return cleaned;
1499
+ return `${cleaned.slice(0, 217).trimEnd()}...`;
1500
+ }
1580
1501
  async function fetchSessionRecapEntries(options) {
1581
1502
  const sessionKey = resolveSessionKey(options.sessionKey);
1582
1503
  if (!sessionKey) return [];
1583
- const recapArgs = ["session-recap", sessionKey, "--format", "json"];
1584
- if (options.agentId) {
1585
- recapArgs.push("--agent", options.agentId);
1586
- }
1587
- const recapResult = runClawvault(recapArgs, options.pluginConfig, { timeoutMs: 2e4 });
1588
- if (!recapResult.success) {
1504
+ try {
1505
+ const recap = await buildSessionRecap(sessionKey, {
1506
+ limit: DEFAULT_MAX_RECAP_RESULTS,
1507
+ agentId: options.agentId
1508
+ });
1509
+ return recap.messages.slice(-DEFAULT_MAX_RECAP_RESULTS).map((message) => ({
1510
+ role: message.role === "user" ? "User" : "Assistant",
1511
+ text: truncateRecapText(message.text)
1512
+ })).filter((message) => message.text.length > 0);
1513
+ } catch {
1589
1514
  return [];
1590
1515
  }
1591
- return parseSessionRecapJson(recapResult.output, DEFAULT_MAX_RECAP_RESULTS);
1592
1516
  }
1593
1517
  async function fetchMemoryContextEntries(options) {
1594
1518
  const prompt = sanitizePromptForContext(options.prompt);
@@ -1603,43 +1527,21 @@ async function fetchMemoryContextEntries(options) {
1603
1527
  return { entries: [], vaultPath: null };
1604
1528
  }
1605
1529
  const maxResults = Number.isFinite(options.maxResults) ? Math.max(1, Math.min(20, Number(options.maxResults))) : DEFAULT_MAX_CONTEXT_RESULTS;
1606
- const profile = options.contextProfile ?? options.pluginConfig.contextProfile ?? "auto";
1607
- const contextArgs = [
1608
- "context",
1609
- prompt,
1610
- "--format",
1611
- "json",
1612
- "--profile",
1613
- profile,
1614
- "--limit",
1615
- String(maxResults),
1616
- "-v",
1617
- vaultPath
1618
- ];
1619
- const contextResult = runClawvault(contextArgs, options.pluginConfig, { timeoutMs: 25e3 });
1620
- if (contextResult.success) {
1530
+ try {
1531
+ const vault = new ClawVault(vaultPath);
1532
+ await vault.load();
1533
+ const results = await vault.find(prompt, {
1534
+ limit: maxResults,
1535
+ minScore: DEFAULT_MIN_CONTEXT_SCORE,
1536
+ temporalBoost: true
1537
+ });
1621
1538
  return {
1622
- entries: parseContextJson(contextResult.output, maxResults),
1539
+ entries: results.map((result) => toContextEntry(vaultPath, result)).filter((entry) => entry.snippet.length > 0),
1623
1540
  vaultPath
1624
1541
  };
1625
- }
1626
- const fallbackSearchArgs = [
1627
- "search",
1628
- prompt,
1629
- "--json",
1630
- "-n",
1631
- String(maxResults),
1632
- "-v",
1633
- vaultPath
1634
- ];
1635
- const fallbackSearchResult = runClawvault(fallbackSearchArgs, options.pluginConfig, { timeoutMs: 25e3 });
1636
- if (!fallbackSearchResult.success) {
1542
+ } catch {
1637
1543
  return { entries: [], vaultPath };
1638
1544
  }
1639
- return {
1640
- entries: parseContextJson(fallbackSearchResult.output, maxResults),
1641
- vaultPath
1642
- };
1643
1545
  }
1644
1546
  async function buildVaultContextInjection(options) {
1645
1547
  const [recapEntries, memoryResult] = await Promise.all([
@@ -1761,13 +1663,13 @@ function createMessageSendingHandler(dependencies) {
1761
1663
 
1762
1664
  // src/plugin/fact-extractor.ts
1763
1665
  import * as fs5 from "fs";
1764
- import * as path5 from "path";
1666
+ import * as path6 from "path";
1765
1667
  import { createHash as createHash2 } from "crypto";
1766
1668
  var FACTS_FILE = "facts.jsonl";
1767
1669
  var ENTITY_GRAPH_FILE = "entity-graph.json";
1768
1670
  var MAX_TEXT_LENGTH = 6e3;
1769
1671
  function ensureClawVaultDir(vaultPath) {
1770
- const dir = path5.join(vaultPath, ".clawvault");
1672
+ const dir = path6.join(vaultPath, ".clawvault");
1771
1673
  if (!fs5.existsSync(dir)) {
1772
1674
  fs5.mkdirSync(dir, { recursive: true });
1773
1675
  }
@@ -1887,7 +1789,7 @@ function buildEntityGraph(facts) {
1887
1789
  };
1888
1790
  }
1889
1791
  function ensureFactsLogFile(vaultPath) {
1890
- const filePath = path5.join(ensureClawVaultDir(vaultPath), FACTS_FILE);
1792
+ const filePath = path6.join(ensureClawVaultDir(vaultPath), FACTS_FILE);
1891
1793
  if (!fs5.existsSync(filePath)) {
1892
1794
  fs5.writeFileSync(filePath, "", "utf-8");
1893
1795
  }
@@ -1900,7 +1802,7 @@ function persistFactsAndGraph(vaultPath, extractedFacts) {
1900
1802
  store.save();
1901
1803
  const allFacts = store.getAllFacts();
1902
1804
  const graph = buildEntityGraph(allFacts);
1903
- const graphPath = path5.join(ensureClawVaultDir(vaultPath), ENTITY_GRAPH_FILE);
1805
+ const graphPath = path6.join(ensureClawVaultDir(vaultPath), ENTITY_GRAPH_FILE);
1904
1806
  fs5.writeFileSync(graphPath, `${JSON.stringify(graph, null, 2)}
1905
1807
  `, "utf-8");
1906
1808
  return {
@@ -1977,19 +1879,16 @@ async function handleGatewayStart(event, ctx, deps) {
1977
1879
  deps.logger?.warn("[clawvault] No vault found, skipping startup recovery");
1978
1880
  return;
1979
1881
  }
1980
- const result = runClawvault(["recover", "--clear", "-v", vaultPath], deps.pluginConfig, {
1981
- timeoutMs: 2e4
1982
- });
1983
- if (result.skipped) {
1984
- return;
1985
- }
1986
- if (!result.success) {
1882
+ let recoveryResult;
1883
+ try {
1884
+ recoveryResult = await recover(vaultPath, { clearFlag: true });
1885
+ } catch {
1987
1886
  deps.logger?.warn("[clawvault] Startup recovery command failed");
1988
1887
  return;
1989
1888
  }
1990
- const parsed = parseRecoveryOutput(result.output);
1991
- if (parsed.hadDeath) {
1992
- const message = parsed.workingOn ? `[ClawVault] Context death detected. Last working on: ${parsed.workingOn}. Run \`clawvault wake\` for full recovery context.` : "[ClawVault] Context death detected. Run `clawvault wake` for full recovery context.";
1889
+ if (recoveryResult.died) {
1890
+ const lastWorkingOn = typeof recoveryResult.checkpoint?.workingOn === "string" ? recoveryResult.checkpoint.workingOn.trim() : "";
1891
+ const message = lastWorkingOn ? `[ClawVault] Context death detected. Last working on: ${lastWorkingOn}. Run \`clawvault wake\` for full recovery context.` : "[ClawVault] Context death detected. Run `clawvault wake` for full recovery context.";
1993
1892
  deps.runtimeState.setStartupRecoveryNotice(message);
1994
1893
  deps.logger?.warn("[clawvault] Context death detected at startup");
1995
1894
  }
@@ -2036,16 +1935,14 @@ async function handleBeforeReset(event, ctx, deps) {
2036
1935
  if (autoCheckpointEnabled) {
2037
1936
  const safeSessionKey = sanitizeForCheckpoint(sessionKey, 120);
2038
1937
  const safeReason = sanitizeForCheckpoint(event.reason ?? "before_reset", 80);
2039
- const checkpointResult = runClawvault([
2040
- "checkpoint",
2041
- "--working-on",
2042
- `Session reset via ${safeReason}`,
2043
- "--focus",
2044
- `Pre-reset checkpoint, session: ${safeSessionKey}`,
2045
- "-v",
2046
- vaultPath
2047
- ], deps.pluginConfig, { timeoutMs: 3e4 });
2048
- if (!checkpointResult.success && !checkpointResult.skipped) {
1938
+ try {
1939
+ await checkpoint({
1940
+ workingOn: `Session reset via ${safeReason}`,
1941
+ focus: `Pre-reset checkpoint, session: ${safeSessionKey}`,
1942
+ vaultPath
1943
+ });
1944
+ await flush();
1945
+ } catch {
2049
1946
  deps.logger?.warn("[clawvault] Auto-checkpoint before reset failed");
2050
1947
  }
2051
1948
  }
package/dist/cli/index.js CHANGED
@@ -11,7 +11,6 @@ import "../chunk-D5U3Q4N5.js";
11
11
  import "../chunk-BLQXXX7Q.js";
12
12
  import "../chunk-VXAGOLDP.js";
13
13
  import "../chunk-7SWP5FKU.js";
14
- import "../chunk-HRLWZGMA.js";
15
14
  import "../chunk-DVOUSOR3.js";
16
15
  import "../chunk-4PY655YM.js";
17
16
  import "../chunk-AXSJIFOJ.js";
@@ -20,6 +19,7 @@ import "../chunk-T7E764W3.js";
20
19
  import "../chunk-HEHO7SMV.js";
21
20
  import "../chunk-2PKBIKDH.js";
22
21
  import "../chunk-35JCYSRR.js";
22
+ import "../chunk-HRLWZGMA.js";
23
23
  import "../chunk-BSJ6RIT7.js";
24
24
  import "../chunk-ECGJYWNA.js";
25
25
  import "../chunk-2CDEETQN.js";
@@ -2,7 +2,7 @@ import {
2
2
  checkOpenClawCompatibility,
3
3
  compatCommand,
4
4
  compatibilityExitCode
5
- } from "../chunk-X3SPPUFG.js";
5
+ } from "../chunk-JI7VUQV7.js";
6
6
  import "../chunk-2ZDO52B4.js";
7
7
  export {
8
8
  checkOpenClawCompatibility,
@@ -4,10 +4,10 @@ import {
4
4
  } from "../chunk-BLQXXX7Q.js";
5
5
  import "../chunk-VXAGOLDP.js";
6
6
  import "../chunk-7SWP5FKU.js";
7
- import "../chunk-HRLWZGMA.js";
8
7
  import "../chunk-DVOUSOR3.js";
9
8
  import "../chunk-4PY655YM.js";
10
9
  import "../chunk-AXSJIFOJ.js";
10
+ import "../chunk-HRLWZGMA.js";
11
11
  import "../chunk-BSJ6RIT7.js";
12
12
  import "../chunk-2CDEETQN.js";
13
13
  import "../chunk-GJO3CFUN.js";
@@ -1,18 +1,18 @@
1
- import {
2
- formatAge
3
- } from "../chunk-7ZRP733D.js";
4
1
  import {
5
2
  scanVaultLinks
6
3
  } from "../chunk-7YZWHM36.js";
7
4
  import "../chunk-J7ZWCI2C.js";
5
+ import {
6
+ formatAge
7
+ } from "../chunk-7ZRP733D.js";
8
8
  import {
9
9
  getObserverStaleness
10
10
  } from "../chunk-VXAGOLDP.js";
11
11
  import "../chunk-7SWP5FKU.js";
12
- import "../chunk-HRLWZGMA.js";
13
12
  import "../chunk-DVOUSOR3.js";
14
13
  import "../chunk-4PY655YM.js";
15
14
  import "../chunk-AXSJIFOJ.js";
15
+ import "../chunk-HRLWZGMA.js";
16
16
  import "../chunk-BSJ6RIT7.js";
17
17
  import {
18
18
  ClawVault
package/dist/index.js CHANGED
@@ -13,11 +13,6 @@ import {
13
13
  registerReplayCommand,
14
14
  replayCommand
15
15
  } from "./chunk-HGDDW24U.js";
16
- import {
17
- buildSessionRecap,
18
- formatSessionRecapMarkdown,
19
- sessionRecapCommand
20
- } from "./chunk-ZKGY7WTT.js";
21
16
  import {
22
17
  setupCommand
23
18
  } from "./chunk-EL6UBSX5.js";
@@ -40,7 +35,7 @@ import {
40
35
  checkOpenClawCompatibility,
41
36
  compatCommand,
42
37
  compatibilityExitCode
43
- } from "./chunk-X3SPPUFG.js";
38
+ } from "./chunk-JI7VUQV7.js";
44
39
  import {
45
40
  doctor
46
41
  } from "./chunk-CSHO3PJB.js";
@@ -60,11 +55,19 @@ import {
60
55
  openclaw_plugin_default,
61
56
  plausibilityScore,
62
57
  registerMemorySlot
63
- } from "./chunk-PLNK37JD.js";
58
+ } from "./chunk-QUFQBAHP.js";
64
59
  import {
65
60
  buildRecallResult,
66
61
  classifyRecallQuery
67
62
  } from "./chunk-RL2L6I6K.js";
63
+ import "./chunk-OIWVQYQF.js";
64
+ import "./chunk-7ZRP733D.js";
65
+ import {
66
+ buildSessionRecap,
67
+ formatSessionRecapMarkdown,
68
+ sessionRecapCommand
69
+ } from "./chunk-ZKGY7WTT.js";
70
+ import "./chunk-F55HGNU4.js";
68
71
  import {
69
72
  ensureEntityProfiles,
70
73
  readEntityProfile,
@@ -146,7 +149,6 @@ import {
146
149
  createLlmFunction,
147
150
  resolveFactExtractionMode
148
151
  } from "./chunk-7SWP5FKU.js";
149
- import "./chunk-HRLWZGMA.js";
150
152
  import {
151
153
  requestLlmCompletion,
152
154
  resolveLlmProvider
@@ -192,6 +194,7 @@ import {
192
194
  } from "./chunk-HEHO7SMV.js";
193
195
  import "./chunk-2PKBIKDH.js";
194
196
  import "./chunk-35JCYSRR.js";
197
+ import "./chunk-HRLWZGMA.js";
195
198
  import {
196
199
  FactStore,
197
200
  extractFactsLlm,
@@ -1,10 +1,15 @@
1
1
  import {
2
2
  createMemorySlotPlugin,
3
3
  openclaw_plugin_default
4
- } from "./chunk-PLNK37JD.js";
4
+ } from "./chunk-QUFQBAHP.js";
5
5
  import "./chunk-RL2L6I6K.js";
6
+ import "./chunk-OIWVQYQF.js";
7
+ import "./chunk-7ZRP733D.js";
8
+ import "./chunk-ZKGY7WTT.js";
9
+ import "./chunk-F55HGNU4.js";
6
10
  import "./chunk-NSXYM6EZ.js";
7
11
  import "./chunk-35JCYSRR.js";
12
+ import "./chunk-HRLWZGMA.js";
8
13
  import "./chunk-BSJ6RIT7.js";
9
14
  import "./chunk-ECGJYWNA.js";
10
15
  import "./chunk-2CDEETQN.js";
@@ -0,0 +1,75 @@
1
+ # ClawHub Security Release Playbook
2
+
3
+ This playbook captures what kept the ClawHub/OpenClaw security review stable for `clawvault` and what repeatedly caused "suspicious" regressions.
4
+
5
+ ## Goal
6
+
7
+ Keep ClawHub scanner classification at least `Benign` by ensuring bundle metadata, SKILL frontmatter, and shipped files stay consistent.
8
+
9
+ ## Known-good frontmatter contract
10
+
11
+ Use compact, parser-safe frontmatter with documented keys only:
12
+
13
+ - `name`, `description`, `author`, `source`, `repository`, `homepage`
14
+ - `user-invocable`
15
+ - `openclaw` (single-line JSON object)
16
+ - `metadata` (single-line JSON object with `openclaw`)
17
+
18
+ For `openclaw` and `metadata.openclaw`, use only documented fields:
19
+
20
+ - `emoji`
21
+ - `requires.bins`
22
+ - `requires.env` (can be `[]` if no required env vars)
23
+ - `install` (installer spec array)
24
+ - `homepage`
25
+
26
+ Avoid non-spec keys inside `openclaw` metadata (for example ad-hoc fields such as `env_optional`), because strict scanners may treat the metadata block as invalid and fall back to "no requirements/install spec".
27
+
28
+ ## Bundle composition
29
+
30
+ Always publish a minimal auditable bundle:
31
+
32
+ - `SKILL.md`
33
+ - `openclaw.plugin.json`
34
+ - `dist/openclaw-plugin.js`
35
+
36
+ If plugin manifest/runtime files are missing from the published bundle, scanners flag visibility/supply-chain concerns.
37
+
38
+ ## Required pre-publish checks
39
+
40
+ 1. Validate SKILL frontmatter is single-line JSON for `openclaw` and `metadata`.
41
+ 2. Confirm runtime dependencies are declared in both:
42
+ - frontmatter metadata (`requires.bins`, `install`)
43
+ - human docs in SKILL (`Install (Canonical)`, safe install flow)
44
+ 3. Confirm `source` and `homepage` fields are present and accurate.
45
+ 4. Confirm plugin manifest/runtime paths referenced in SKILL exist in the bundle.
46
+
47
+ ## Publish + verify workflow
48
+
49
+ 1. Publish skill patch version to ClawHub.
50
+ 2. Wait for propagation (`clawhub inspect` can temporarily return `Skill not found`).
51
+ 3. Verify metadata and files:
52
+ - `npx clawhub inspect clawvault --file SKILL.md`
53
+ - `npx clawhub inspect clawvault --files`
54
+ 4. Verify page classification in browser snapshot (not just CLI):
55
+ - Open `https://clawhub.ai/G9Pedro/clawvault`
56
+ - Confirm status badge is `Benign` (or better) and review details.
57
+
58
+ ## If scanner regresses
59
+
60
+ If warning text mentions mismatch between registry metadata and SKILL/docs:
61
+
62
+ 1. Compare scanner claim to frontmatter values first.
63
+ 2. Remove unsupported keys from metadata block.
64
+ 3. Re-publish patch version with normalized metadata.
65
+ 4. Re-check in browser after propagation.
66
+
67
+ ## Security posture notes
68
+
69
+ Even with clean metadata, this skill can still receive cautionary language because it:
70
+
71
+ - runs plugin lifecycle handlers,
72
+ - reads/modifies OpenClaw session files,
73
+ - and relies on external CLI packages (`clawvault`, `qmd`).
74
+
75
+ That caution is expected and should be addressed with transparent docs, explicit safe-install guidance, and auditable shipped plugin code.