clawvault 3.2.1 → 3.3.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 (150) hide show
  1. package/README.md +56 -16
  2. package/bin/clawvault.js +0 -2
  3. package/bin/command-registration.test.js +13 -1
  4. package/bin/help-contract.test.js +14 -0
  5. package/bin/register-core-commands.js +88 -0
  6. package/bin/register-core-commands.test.js +80 -0
  7. package/bin/register-maintenance-commands.js +57 -6
  8. package/bin/register-query-commands.js +10 -28
  9. package/bin/test-helpers/cli-command-fixtures.js +1 -0
  10. package/dist/chunk-2PKBIKDH.js +130 -0
  11. package/dist/{chunk-U67V476Y.js → chunk-2ZDO52B4.js} +18 -1
  12. package/dist/{chunk-ZZA73MFY.js → chunk-33DOSHTA.js} +176 -36
  13. package/dist/{chunk-AZYOKJYC.js → chunk-4PY655YM.js} +13 -1
  14. package/dist/{chunk-2JQ3O2YL.js → chunk-5EFSWZO6.js} +3 -3
  15. package/dist/{chunk-Y3TIJEBP.js → chunk-7SWP5FKU.js} +34 -613
  16. package/dist/{chunk-4VQTUVH7.js → chunk-7YZWHM36.js} +52 -26
  17. package/dist/{chunk-URXDAUVH.js → chunk-AXSJIFOJ.js} +174 -1
  18. package/dist/{chunk-4ITRXIVT.js → chunk-BLQXXX7Q.js} +6 -6
  19. package/dist/chunk-CSHO3PJB.js +684 -0
  20. package/dist/{chunk-S5OJEGFG.js → chunk-DOIUYIXV.js} +2 -2
  21. package/dist/{chunk-YXQCA6B7.js → chunk-DVOUSOR3.js} +112 -7
  22. package/dist/{chunk-YDWHS4LJ.js → chunk-ECGJYWNA.js} +205 -33
  23. package/dist/{chunk-QMHPQYUV.js → chunk-EL6UBSX5.js} +7 -6
  24. package/dist/chunk-FZ5I2NF7.js +352 -0
  25. package/dist/{chunk-WJVWINEM.js → chunk-GFCHWMGD.js} +55 -6
  26. package/dist/{chunk-GNJL4YGR.js → chunk-GJO3CFUN.js} +30 -6
  27. package/dist/chunk-H3JZIB5O.js +322 -0
  28. package/dist/chunk-HEHO7SMV.js +51 -0
  29. package/dist/{chunk-UCQAOZHW.js → chunk-HGDDW24U.js} +3 -3
  30. package/dist/chunk-J3YUXVID.js +907 -0
  31. package/dist/{chunk-Y6VJKXGL.js → chunk-KCYWJDDW.js} +1 -1
  32. package/dist/{chunk-P5EPF6MB.js → chunk-MW5C6ZQA.js} +110 -13
  33. package/dist/{chunk-YNIPYN4F.js → chunk-OFOCU2V4.js} +6 -5
  34. package/dist/{chunk-42MXU7A6.js → chunk-P62WHA27.js} +58 -47
  35. package/dist/chunk-PTWPPVC7.js +972 -0
  36. package/dist/{chunk-FAKNOB7Y.js → chunk-QFWERBDP.js} +2 -2
  37. package/dist/{chunk-IIOU45CK.js → chunk-S7N7HI5E.js} +2 -2
  38. package/dist/{chunk-ECRZL5XR.js → chunk-T7E764W3.js} +23 -7
  39. package/dist/chunk-TDWFBDAQ.js +1016 -0
  40. package/dist/{chunk-MNPUYCHQ.js → chunk-TWMI3SNN.js} +6 -5
  41. package/dist/{chunk-2RAZ4ZFE.js → chunk-VBILES4B.js} +1 -1
  42. package/dist/{chunk-PI4WMLMG.js → chunk-VXAGOLDP.js} +1 -1
  43. package/dist/chunk-YCUVAOFC.js +158 -0
  44. package/dist/{chunk-SS4B7P7V.js → chunk-YIDV4VV2.js} +1 -1
  45. package/dist/chunk-ZKWPCBYT.js +600 -0
  46. package/dist/cli/index.js +27 -21
  47. package/dist/commands/archive.js +3 -3
  48. package/dist/commands/backlog.js +1 -1
  49. package/dist/commands/benchmark.d.ts +12 -0
  50. package/dist/commands/benchmark.js +12 -0
  51. package/dist/commands/blocked.js +1 -1
  52. package/dist/commands/canvas.js +2 -2
  53. package/dist/commands/checkpoint.js +1 -1
  54. package/dist/commands/compat.js +1 -1
  55. package/dist/commands/context.js +8 -7
  56. package/dist/commands/doctor.d.ts +8 -3
  57. package/dist/commands/doctor.js +8 -22
  58. package/dist/commands/embed.js +6 -5
  59. package/dist/commands/entities.js +2 -2
  60. package/dist/commands/graph.js +4 -4
  61. package/dist/commands/inbox.d.ts +23 -0
  62. package/dist/commands/inbox.js +11 -0
  63. package/dist/commands/inject.d.ts +1 -1
  64. package/dist/commands/inject.js +5 -5
  65. package/dist/commands/kanban.js +1 -1
  66. package/dist/commands/link.js +9 -9
  67. package/dist/commands/maintain.d.ts +32 -0
  68. package/dist/commands/maintain.js +12 -0
  69. package/dist/commands/migrate-observations.js +3 -3
  70. package/dist/commands/observe.js +11 -10
  71. package/dist/commands/project.js +2 -2
  72. package/dist/commands/rebuild-embeddings.js +48 -17
  73. package/dist/commands/rebuild.js +9 -8
  74. package/dist/commands/recover.js +1 -1
  75. package/dist/commands/reflect.js +6 -6
  76. package/dist/commands/repair-session.js +1 -1
  77. package/dist/commands/replay.js +10 -9
  78. package/dist/commands/session-recap.js +1 -1
  79. package/dist/commands/setup.js +4 -3
  80. package/dist/commands/shell-init.js +1 -1
  81. package/dist/commands/sleep.d.ts +1 -1
  82. package/dist/commands/sleep.js +20 -18
  83. package/dist/commands/status.js +40 -26
  84. package/dist/commands/sync-bd.js +3 -3
  85. package/dist/commands/tailscale.js +3 -3
  86. package/dist/commands/task.js +1 -1
  87. package/dist/commands/template.js +1 -1
  88. package/dist/commands/wake.d.ts +1 -1
  89. package/dist/commands/wake.js +10 -9
  90. package/dist/index.d.ts +175 -16
  91. package/dist/index.js +277 -108
  92. package/dist/{inject-DYUrDqQO.d.ts → inject-DEb_jpLi.d.ts} +3 -1
  93. package/dist/lib/auto-linker.js +2 -2
  94. package/dist/lib/canvas-layout.js +1 -1
  95. package/dist/lib/config.js +2 -2
  96. package/dist/lib/entity-index.js +1 -1
  97. package/dist/lib/project-utils.js +2 -2
  98. package/dist/lib/session-repair.js +1 -1
  99. package/dist/lib/session-utils.js +1 -1
  100. package/dist/lib/tailscale.js +1 -1
  101. package/dist/lib/task-utils.js +1 -1
  102. package/dist/lib/template-engine.js +1 -1
  103. package/dist/lib/webdav.js +1 -1
  104. package/dist/onnxruntime_binding-5QEF3SUC.node +0 -0
  105. package/dist/onnxruntime_binding-BKPKNEGC.node +0 -0
  106. package/dist/onnxruntime_binding-FMOXGIUT.node +0 -0
  107. package/dist/onnxruntime_binding-OI2KMXC5.node +0 -0
  108. package/dist/onnxruntime_binding-UX44MLAZ.node +0 -0
  109. package/dist/onnxruntime_binding-Y2W7N7WY.node +0 -0
  110. package/dist/openclaw-plugin.d.ts +8 -0
  111. package/dist/openclaw-plugin.js +14 -0
  112. package/dist/transformers.node-A2ZRORSQ.js +46775 -0
  113. package/dist/{types-BbWJoC1c.d.ts → types-DslKvCaj.d.ts} +51 -1
  114. package/hooks/clawvault/HOOK.md +25 -8
  115. package/hooks/clawvault/handler.js +215 -78
  116. package/hooks/clawvault/handler.test.js +109 -43
  117. package/hooks/clawvault/integrity.js +112 -0
  118. package/hooks/clawvault/integrity.test.js +32 -0
  119. package/hooks/clawvault/openclaw.plugin.json +133 -15
  120. package/openclaw.plugin.json +131 -203
  121. package/package.json +10 -7
  122. package/bin/register-workgraph-commands.js +0 -451
  123. package/dist/chunk-5PJ4STIC.js +0 -465
  124. package/dist/chunk-ERNE2FZ5.js +0 -189
  125. package/dist/chunk-HR4KN6S2.js +0 -152
  126. package/dist/chunk-IJBFGPCS.js +0 -33
  127. package/dist/chunk-K7PNYS45.js +0 -93
  128. package/dist/chunk-NTOPJI7W.js +0 -207
  129. package/dist/chunk-PG56HX5T.js +0 -154
  130. package/dist/chunk-QPDDIHXE.js +0 -501
  131. package/dist/chunk-WIOLLGAD.js +0 -190
  132. package/dist/chunk-WMGIIABP.js +0 -15
  133. package/dist/ledger-B7g7jhqG.d.ts +0 -44
  134. package/dist/plugin/index.d.ts +0 -352
  135. package/dist/plugin/index.js +0 -4264
  136. package/dist/registry-BR4326o0.d.ts +0 -30
  137. package/dist/store-CA-6sKCJ.d.ts +0 -34
  138. package/dist/thread-B9LhXNU0.d.ts +0 -41
  139. package/dist/workgraph/index.d.ts +0 -5
  140. package/dist/workgraph/index.js +0 -23
  141. package/dist/workgraph/ledger.d.ts +0 -2
  142. package/dist/workgraph/ledger.js +0 -25
  143. package/dist/workgraph/registry.d.ts +0 -2
  144. package/dist/workgraph/registry.js +0 -19
  145. package/dist/workgraph/store.d.ts +0 -2
  146. package/dist/workgraph/store.js +0 -25
  147. package/dist/workgraph/thread.d.ts +0 -2
  148. package/dist/workgraph/thread.js +0 -25
  149. package/dist/workgraph/types.d.ts +0 -54
  150. package/dist/workgraph/types.js +0 -7
@@ -10,10 +10,43 @@ var DEFAULT_MODELS = {
10
10
  openclaw: "gpt-4o-mini"
11
11
  };
12
12
  var XAI_BASE_URL = "https://api.x.ai/v1";
13
+ var VAULT_CONFIG_FILE = ".clawvault.json";
14
+ var LLM_MODEL_TIERS = ["background", "default", "complex"];
15
+ function resolveOpenClawHome() {
16
+ return process.env.OPENCLAW_HOME?.trim() || path.join(os.homedir(), ".openclaw");
17
+ }
18
+ function resolveOpenClawGatewayProvider() {
19
+ try {
20
+ const configPath = path.join(resolveOpenClawHome(), "openclaw.json");
21
+ if (!fs.existsSync(configPath)) {
22
+ return null;
23
+ }
24
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
25
+ const enabled = raw.gateway?.http?.endpoints?.chatCompletions?.enabled === true;
26
+ const apiKey = raw.gateway?.auth?.token?.trim();
27
+ const port = raw.gateway?.port;
28
+ if (!enabled || !apiKey || typeof port !== "number" || !Number.isFinite(port) || port <= 0) {
29
+ return null;
30
+ }
31
+ const defaultModel = raw.agents?.defaults?.model?.trim() || DEFAULT_MODELS.openclaw;
32
+ const host = raw.gateway?.bind === "loopback" ? "127.0.0.1" : "127.0.0.1";
33
+ return {
34
+ baseUrl: `http://${host}:${port}/v1`,
35
+ apiKey,
36
+ api: "openai-completions",
37
+ defaultModel
38
+ };
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
13
43
  function resolveOpenClawProvider() {
44
+ const gatewayProvider = resolveOpenClawGatewayProvider();
45
+ if (gatewayProvider) {
46
+ return gatewayProvider;
47
+ }
14
48
  try {
15
- const openclawHome = process.env.OPENCLAW_HOME?.trim() || path.join(os.homedir(), ".openclaw");
16
- const modelsPath = path.join(openclawHome, "agents", "main", "agent", "models.json");
49
+ const modelsPath = path.join(resolveOpenClawHome(), "agents", "main", "agent", "models.json");
17
50
  if (!fs.existsSync(modelsPath)) {
18
51
  return null;
19
52
  }
@@ -57,6 +90,78 @@ function resolveLlmProvider() {
57
90
  }
58
91
  return null;
59
92
  }
93
+ function resolveNearestVaultPath(startPath) {
94
+ let current = path.resolve(startPath);
95
+ while (true) {
96
+ if (fs.existsSync(path.join(current, VAULT_CONFIG_FILE))) {
97
+ return current;
98
+ }
99
+ const parent = path.dirname(current);
100
+ if (parent === current) {
101
+ return null;
102
+ }
103
+ current = parent;
104
+ }
105
+ }
106
+ function resolveVaultPathForModelConfig() {
107
+ const configuredVaultPath = process.env.CLAWVAULT_PATH?.trim();
108
+ if (configuredVaultPath) {
109
+ return path.resolve(configuredVaultPath);
110
+ }
111
+ return resolveNearestVaultPath(process.cwd());
112
+ }
113
+ function readTieredModelConfig() {
114
+ const vaultPath = resolveVaultPathForModelConfig();
115
+ if (!vaultPath) {
116
+ return null;
117
+ }
118
+ const configPath = path.join(vaultPath, VAULT_CONFIG_FILE);
119
+ if (!fs.existsSync(configPath)) {
120
+ return null;
121
+ }
122
+ try {
123
+ const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8"));
124
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
125
+ return null;
126
+ }
127
+ const models = parsed.models;
128
+ if (!models || typeof models !== "object" || Array.isArray(models)) {
129
+ return null;
130
+ }
131
+ const normalized = {};
132
+ for (const tier of LLM_MODEL_TIERS) {
133
+ const candidate = models[tier];
134
+ if (typeof candidate === "string" && candidate.trim()) {
135
+ normalized[tier] = candidate.trim();
136
+ }
137
+ }
138
+ return Object.keys(normalized).length > 0 ? normalized : null;
139
+ } catch {
140
+ return null;
141
+ }
142
+ }
143
+ function resolveTieredModel(tier) {
144
+ const tieredConfig = readTieredModelConfig();
145
+ if (!tieredConfig) {
146
+ return null;
147
+ }
148
+ return tieredConfig[tier] ?? tieredConfig.default ?? null;
149
+ }
150
+ function resolveRequestModel(options, fallbackModel) {
151
+ if (typeof options.model === "string" && options.model.trim()) {
152
+ return options.model.trim();
153
+ }
154
+ const tier = options.tier ?? "default";
155
+ const configuredTierModel = resolveTieredModel(tier);
156
+ if (configuredTierModel) {
157
+ return configuredTierModel;
158
+ }
159
+ const envModel = process.env.CLAWVAULT_MODEL?.trim();
160
+ if (envModel) {
161
+ return envModel;
162
+ }
163
+ return fallbackModel;
164
+ }
60
165
  async function requestLlmCompletion(options) {
61
166
  const provider = options.provider ?? resolveLlmProvider();
62
167
  if (!provider) {
@@ -90,7 +195,7 @@ async function callAnthropic(options, provider) {
90
195
  "anthropic-version": "2023-06-01"
91
196
  },
92
197
  body: JSON.stringify({
93
- model: options.model ?? DEFAULT_MODELS[provider],
198
+ model: resolveRequestModel(options, DEFAULT_MODELS[provider]),
94
199
  temperature: options.temperature ?? 0.1,
95
200
  max_tokens: options.maxTokens ?? 1200,
96
201
  messages: [{ role: "user", content: options.prompt }]
@@ -120,7 +225,7 @@ async function callOpenAI(options, provider) {
120
225
  authorization: `Bearer ${apiKey}`
121
226
  },
122
227
  body: JSON.stringify({
123
- model: options.model ?? DEFAULT_MODELS[provider],
228
+ model: resolveRequestModel(options, DEFAULT_MODELS[provider]),
124
229
  temperature: options.temperature ?? 0.1,
125
230
  max_tokens: options.maxTokens ?? 1200,
126
231
  messages
@@ -150,7 +255,7 @@ async function callXAI(options, provider) {
150
255
  authorization: `Bearer ${apiKey}`
151
256
  },
152
257
  body: JSON.stringify({
153
- model: options.model ?? DEFAULT_MODELS[provider],
258
+ model: resolveRequestModel(options, DEFAULT_MODELS[provider]),
154
259
  temperature: options.temperature ?? 0.1,
155
260
  max_tokens: options.maxTokens ?? 1200,
156
261
  messages
@@ -168,7 +273,7 @@ async function callGemini(options, provider) {
168
273
  return "";
169
274
  }
170
275
  const fetchImpl = options.fetchImpl ?? fetch;
171
- const model = options.model ?? DEFAULT_MODELS[provider];
276
+ const model = resolveRequestModel(options, DEFAULT_MODELS[provider]);
172
277
  const response = await fetchImpl(
173
278
  `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`,
174
279
  {
@@ -207,7 +312,7 @@ async function callOpenClaw(options) {
207
312
  authorization: `Bearer ${config.apiKey}`
208
313
  },
209
314
  body: JSON.stringify({
210
- model: options.model ?? config.defaultModel,
315
+ model: resolveRequestModel(options, config.defaultModel),
211
316
  temperature: options.temperature ?? 0.1,
212
317
  max_tokens: options.maxTokens ?? 1200,
213
318
  messages
@@ -1,22 +1,22 @@
1
1
  import {
2
- loadVaultQmdConfig
3
- } from "./chunk-WIOLLGAD.js";
2
+ DEFAULT_CATEGORIES,
3
+ TYPE_TO_CATEGORY
4
+ } from "./chunk-2CDEETQN.js";
5
+ import {
6
+ loadVaultQmdConfig,
7
+ recoverQmdEmbeddingIfNeeded
8
+ } from "./chunk-FZ5I2NF7.js";
4
9
  import {
5
- QmdUnavailableError,
6
10
  SearchEngine,
7
11
  extractTags,
8
12
  extractWikiLinks,
9
13
  hasQmd,
10
14
  qmdEmbed,
11
15
  qmdUpdate
12
- } from "./chunk-5PJ4STIC.js";
13
- import {
14
- DEFAULT_CATEGORIES,
15
- TYPE_TO_CATEGORY
16
- } from "./chunk-2CDEETQN.js";
16
+ } from "./chunk-PTWPPVC7.js";
17
17
  import {
18
18
  buildOrUpdateMemoryGraphIndex
19
- } from "./chunk-ZZA73MFY.js";
19
+ } from "./chunk-33DOSHTA.js";
20
20
 
21
21
  // src/lib/vault.ts
22
22
  import * as fs from "fs";
@@ -31,17 +31,19 @@ var ClawVault = class {
31
31
  search;
32
32
  initialized = false;
33
33
  constructor(vaultPath) {
34
- if (!hasQmd()) {
35
- const error = new QmdUnavailableError("NOT_INSTALLED");
36
- console.error(error.toUserMessage());
37
- throw error;
34
+ if (typeof vaultPath !== "string" || !vaultPath.trim()) {
35
+ throw new Error(`Invalid vault path: expected a non-empty string, received ${typeof vaultPath}`);
38
36
  }
39
37
  this.config = {
40
38
  path: path.resolve(vaultPath),
41
39
  name: path.basename(vaultPath),
42
40
  categories: DEFAULT_CATEGORIES,
43
41
  qmdCollection: void 0,
44
- qmdRoot: void 0
42
+ qmdRoot: void 0,
43
+ search: {
44
+ backend: "in-process",
45
+ qmdFallback: true
46
+ }
45
47
  };
46
48
  this.search = new SearchEngine();
47
49
  this.applyQmdConfig();
@@ -50,11 +52,6 @@ var ClawVault = class {
50
52
  * Initialize a new vault
51
53
  */
52
54
  async init(options = {}, initFlags) {
53
- if (!hasQmd()) {
54
- const error = new QmdUnavailableError("NOT_INSTALLED");
55
- console.error(error.toUserMessage());
56
- throw error;
57
- }
58
55
  const vaultPath = this.config.path;
59
56
  const flags = initFlags || {};
60
57
  this.config = { ...this.config, ...options };
@@ -95,7 +92,8 @@ var ClawVault = class {
95
92
  categories: this.config.categories,
96
93
  documentCount: 0,
97
94
  qmdCollection: this.getQmdCollection(),
98
- qmdRoot: this.getQmdRoot()
95
+ qmdRoot: this.getQmdRoot(),
96
+ search: this.config.search ?? { backend: "in-process", qmdFallback: true }
99
97
  };
100
98
  fs.writeFileSync(configPath, JSON.stringify(meta, null, 2));
101
99
  if (!flags.skipBases && this.config.categories.includes("tasks")) {
@@ -206,27 +204,41 @@ var ClawVault = class {
206
204
  * Load an existing vault
207
205
  */
208
206
  async load() {
209
- if (!hasQmd()) {
210
- const error = new QmdUnavailableError("NOT_INSTALLED");
211
- console.error(error.toUserMessage());
212
- throw error;
213
- }
214
207
  const vaultPath = this.config.path;
215
208
  const configPath = path.join(vaultPath, CONFIG_FILE);
216
209
  if (!fs.existsSync(configPath)) {
217
210
  throw new Error(`Not a ClawVault: ${vaultPath} (missing ${CONFIG_FILE})`);
218
211
  }
219
212
  const meta = JSON.parse(fs.readFileSync(configPath, "utf-8"));
220
- this.config.name = meta.name;
221
- this.config.categories = meta.categories;
222
- this.config.qmdCollection = meta.qmdCollection;
223
- this.config.qmdRoot = meta.qmdRoot;
213
+ this.config.name = typeof meta.name === "string" ? meta.name : this.config.name;
214
+ this.config.categories = Array.isArray(meta.categories) ? meta.categories : this.config.categories;
215
+ this.config.qmdCollection = typeof meta.qmdCollection === "string" ? meta.qmdCollection : void 0;
216
+ this.config.qmdRoot = typeof meta.qmdRoot === "string" ? meta.qmdRoot : void 0;
217
+ this.config.search = meta.search && typeof meta.search === "object" && !Array.isArray(meta.search) ? meta.search : this.config.search;
224
218
  if (!meta.qmdCollection || !meta.qmdRoot) {
225
219
  meta.qmdCollection = meta.qmdCollection || meta.name;
226
220
  meta.qmdRoot = meta.qmdRoot || this.config.path;
227
221
  fs.writeFileSync(configPath, JSON.stringify(meta, null, 2));
228
222
  }
229
223
  this.applyQmdConfig(meta);
224
+ if (hasQmd()) {
225
+ try {
226
+ const recovery = recoverQmdEmbeddingIfNeeded({
227
+ vaultPath: this.config.path,
228
+ collection: this.getQmdCollection(),
229
+ rootPath: this.getQmdRoot(),
230
+ mode: "marker-only",
231
+ onLog: (message) => console.warn(`[clawvault] ${message}`)
232
+ });
233
+ if (recovery.recovered) {
234
+ console.warn(`[clawvault] qmd embedding recovery finished for "${this.getQmdCollection()}".`);
235
+ }
236
+ } catch (err) {
237
+ console.warn(
238
+ `[clawvault] qmd embedding recovery failed: ${err?.message || "unknown error"}`
239
+ );
240
+ }
241
+ }
230
242
  await this.reindex();
231
243
  this.initialized = true;
232
244
  }
@@ -322,6 +334,28 @@ var ClawVault = class {
322
334
  }
323
335
  return doc;
324
336
  }
337
+ /**
338
+ * Patch an existing document and incrementally refresh index state for that file only.
339
+ */
340
+ async patch(options) {
341
+ const relativePath = this.resolveDocumentRelativePath(options.idOrPath);
342
+ const absolutePath = path.join(this.config.path, relativePath);
343
+ if (!fs.existsSync(absolutePath)) {
344
+ throw new Error(`Document not found: ${options.idOrPath}`);
345
+ }
346
+ const raw = fs.readFileSync(absolutePath, "utf-8");
347
+ const { frontmatter, body } = this.splitFrontmatter(raw);
348
+ const updatedBody = this.applyPatchToBody(body, options);
349
+ if (updatedBody === body) {
350
+ throw new Error("Patch made no changes to the document body.");
351
+ }
352
+ fs.writeFileSync(absolutePath, `${frontmatter}${updatedBody}`);
353
+ const doc = await this.reindexDocument(relativePath);
354
+ if (!doc) {
355
+ throw new Error(`Failed to reload patched document: ${options.idOrPath}`);
356
+ }
357
+ return doc;
358
+ }
325
359
  /**
326
360
  * Quick store to inbox
327
361
  */
@@ -334,19 +368,19 @@ var ClawVault = class {
334
368
  });
335
369
  }
336
370
  /**
337
- * Search the vault (BM25 via qmd)
371
+ * Search the vault (in-process hybrid by default, qmd fallback optional)
338
372
  */
339
373
  async find(query, options = {}) {
340
374
  return this.search.search(query, options);
341
375
  }
342
376
  /**
343
- * Semantic/vector search (via qmd vsearch)
377
+ * Semantic/vector search (hosted embeddings, qmd fallback optional)
344
378
  */
345
379
  async vsearch(query, options = {}) {
346
380
  return this.search.vsearch(query, options);
347
381
  }
348
382
  /**
349
- * Combined search with query expansion (via qmd query)
383
+ * Combined search entrypoint (currently aliases hybrid search)
350
384
  */
351
385
  async query(query, options = {}) {
352
386
  return this.search.query(query, options);
@@ -723,6 +757,142 @@ var ClawVault = class {
723
757
  }
724
758
  return fallback || (/* @__PURE__ */ new Date()).toISOString();
725
759
  }
760
+ async reindexDocument(relativePath) {
761
+ const doc = await this.loadDocument(relativePath);
762
+ if (!doc) return null;
763
+ this.search.addDocument(doc);
764
+ await this.saveIndex();
765
+ await this.syncMemoryGraphIndex();
766
+ return doc;
767
+ }
768
+ resolveDocumentRelativePath(idOrPath) {
769
+ if (typeof idOrPath !== "string" || !idOrPath.trim()) {
770
+ throw new Error("idOrPath is required");
771
+ }
772
+ const trimmed = idOrPath.trim().replace(/^[\\/]+/, "");
773
+ const withExtension = trimmed.endsWith(".md") ? trimmed : `${trimmed}.md`;
774
+ const normalized = path.normalize(withExtension);
775
+ const resolved = path.resolve(this.config.path, normalized);
776
+ const relative2 = path.relative(this.config.path, resolved);
777
+ if (relative2.startsWith("..") || path.isAbsolute(relative2)) {
778
+ throw new Error(`Document path escapes vault: ${idOrPath}`);
779
+ }
780
+ return relative2;
781
+ }
782
+ splitFrontmatter(raw) {
783
+ const match = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
784
+ if (!match) {
785
+ return { frontmatter: "", body: raw };
786
+ }
787
+ const frontmatter = match[0];
788
+ return {
789
+ frontmatter,
790
+ body: raw.slice(frontmatter.length)
791
+ };
792
+ }
793
+ applyPatchToBody(body, options) {
794
+ if (options.mode === "append") {
795
+ if (typeof options.append !== "string" || options.append.length === 0) {
796
+ throw new Error("Append mode requires non-empty append text.");
797
+ }
798
+ if (options.section) {
799
+ return this.patchMarkdownSection(
800
+ body,
801
+ options.section,
802
+ (sectionBody) => this.appendText(sectionBody, options.append)
803
+ );
804
+ }
805
+ return this.appendText(body, options.append);
806
+ }
807
+ if (options.mode === "replace") {
808
+ if (typeof options.replace !== "string" || options.replace.length === 0) {
809
+ throw new Error("Replace mode requires non-empty --replace text.");
810
+ }
811
+ if (typeof options.with !== "string") {
812
+ throw new Error("Replace mode requires --with text.");
813
+ }
814
+ if (options.section) {
815
+ return this.patchMarkdownSection(
816
+ body,
817
+ options.section,
818
+ (sectionBody) => this.replaceAllOccurrences(sectionBody, options.replace, options.with, `section "${options.section}"`)
819
+ );
820
+ }
821
+ return this.replaceAllOccurrences(body, options.replace, options.with, "document");
822
+ }
823
+ if (options.mode === "content") {
824
+ if (typeof options.content !== "string") {
825
+ throw new Error("Content mode requires --content text.");
826
+ }
827
+ if (options.section) {
828
+ return this.patchMarkdownSection(body, options.section, () => options.content);
829
+ }
830
+ return options.content;
831
+ }
832
+ throw new Error(`Unsupported patch mode: ${String(options.mode)}`);
833
+ }
834
+ appendText(existing, addition) {
835
+ if (addition.length === 0) return existing;
836
+ if (existing.length === 0) return addition;
837
+ return existing.endsWith("\n") ? `${existing}${addition}` : `${existing}
838
+ ${addition}`;
839
+ }
840
+ replaceAllOccurrences(input, searchText, replacement, scopeLabel) {
841
+ if (!input.includes(searchText)) {
842
+ throw new Error(`No matches found for "${searchText}" in ${scopeLabel}.`);
843
+ }
844
+ return input.split(searchText).join(replacement);
845
+ }
846
+ patchMarkdownSection(markdown, sectionName, applySectionPatch) {
847
+ const lines = markdown.split(/\r?\n/);
848
+ const normalize2 = (value) => value.replace(/^#+\s*/, "").trim().toLowerCase();
849
+ const targetName = normalize2(sectionName);
850
+ let sectionStart = -1;
851
+ let sectionLevel = 0;
852
+ for (let i = 0; i < lines.length; i += 1) {
853
+ const line = lines[i];
854
+ const match = line.match(/^(#{1,6})\s+(.*?)\s*$/);
855
+ if (!match) continue;
856
+ const [, marks, heading] = match;
857
+ if (normalize2(heading) === targetName) {
858
+ sectionStart = i;
859
+ sectionLevel = marks.length;
860
+ break;
861
+ }
862
+ }
863
+ if (sectionStart < 0) {
864
+ throw new Error(`Section not found: ${sectionName}`);
865
+ }
866
+ let sectionEnd = lines.length;
867
+ for (let i = sectionStart + 1; i < lines.length; i += 1) {
868
+ const match = lines[i].match(/^(#{1,6})\s+(.*?)\s*$/);
869
+ if (!match) continue;
870
+ if (match[1].length <= sectionLevel) {
871
+ sectionEnd = i;
872
+ break;
873
+ }
874
+ }
875
+ const existingSectionBody = lines.slice(sectionStart + 1, sectionEnd).join("\n");
876
+ const updatedSectionBody = applySectionPatch(existingSectionBody);
877
+ const rebuiltSection = updatedSectionBody.length > 0 ? `${lines[sectionStart]}
878
+ ${updatedSectionBody}` : lines[sectionStart];
879
+ const head = lines.slice(0, sectionStart).join("\n");
880
+ const tail = lines.slice(sectionEnd).join("\n");
881
+ if (head.length > 0 && tail.length > 0) {
882
+ return `${head}
883
+ ${rebuiltSection}
884
+ ${tail}`;
885
+ }
886
+ if (head.length > 0) {
887
+ return `${head}
888
+ ${rebuiltSection}`;
889
+ }
890
+ if (tail.length > 0) {
891
+ return `${rebuiltSection}
892
+ ${tail}`;
893
+ }
894
+ return rebuiltSection;
895
+ }
726
896
  /**
727
897
  * Extract the date portion (YYYY-MM-DD) from an ISO date string or Date object.
728
898
  * Provides safe handling for various date formats.
@@ -739,15 +909,17 @@ var ClawVault = class {
739
909
  const explicitRoot = meta?.qmdRoot || this.config.qmdRoot || this.config.path;
740
910
  const qmdConfig = loadVaultQmdConfig(this.config.path);
741
911
  const collection = explicitCollection || qmdConfig.qmdCollection || this.config.name;
742
- const root = explicitRoot || qmdConfig.qmdRoot;
912
+ const root = (typeof explicitRoot === "string" ? explicitRoot : void 0) || qmdConfig.qmdRoot || this.config.path;
743
913
  if (qmdConfig.autoDetected) {
744
914
  console.warn(`[clawvault] Auto-detected qmd collection: ${collection}`);
745
915
  }
746
916
  this.config.qmdCollection = collection;
747
917
  this.config.qmdRoot = root;
918
+ this.config.search = meta?.search && typeof meta.search === "object" && !Array.isArray(meta.search) ? meta.search : this.config.search ?? { backend: "in-process", qmdFallback: true };
748
919
  this.search.setVaultPath(this.config.path);
749
920
  this.search.setCollection(collection);
750
921
  this.search.setCollectionRoot(root);
922
+ this.search.setSearchConfig(this.config.search);
751
923
  }
752
924
  slugify(text) {
753
925
  return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").trim();
@@ -1,10 +1,10 @@
1
- import {
2
- hasQmd,
3
- withQmdIndexArgs
4
- } from "./chunk-5PJ4STIC.js";
5
1
  import {
6
2
  DEFAULT_CATEGORIES
7
3
  } from "./chunk-2CDEETQN.js";
4
+ import {
5
+ hasQmd,
6
+ withQmdIndexArgs
7
+ } from "./chunk-PTWPPVC7.js";
8
8
 
9
9
  // src/commands/setup.ts
10
10
  import * as fs from "fs";
@@ -339,14 +339,15 @@ async function setupCommand(options = {}) {
339
339
  const { collection, root } = getQmdConfig(target.vaultPath);
340
340
  try {
341
341
  execFileSync("qmd", withQmdIndexArgs(["collection", "add", root, "--name", collection, "--mask", "**/*.md"], options.qmdIndexName), {
342
- stdio: "ignore"
342
+ stdio: "ignore",
343
+ shell: process.platform === "win32"
343
344
  });
344
345
  console.log(`\u2713 qmd collection ready: ${collection}`);
345
346
  } catch {
346
347
  console.log("\u2298 qmd collection already exists.");
347
348
  }
348
349
  } else {
349
- console.log("\u2298 qmd not found \u2014 skipping semantic search setup.");
350
+ console.log("\u2298 qmd not found \u2014 skipping optional qmd fallback setup.");
350
351
  }
351
352
  console.log("\nTip: add this to your shell config:");
352
353
  console.log(` export CLAWVAULT_PATH="${target.vaultPath}"`);