clawvault 3.2.0 → 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 (112) hide show
  1. package/README.md +54 -14
  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-2JQ3O2YL.js → chunk-5EFSWZO6.js} +3 -3
  12. package/dist/{chunk-77Q5CSPJ.js → chunk-7SWP5FKU.js} +33 -701
  13. package/dist/{chunk-URXDAUVH.js → chunk-AXSJIFOJ.js} +174 -1
  14. package/dist/{chunk-23YDQ3QU.js → chunk-BLQXXX7Q.js} +6 -6
  15. package/dist/chunk-CSHO3PJB.js +684 -0
  16. package/dist/{chunk-SLXOR3CC.js → chunk-DOIUYIXV.js} +2 -2
  17. package/dist/{chunk-NCKFNBHJ.js → chunk-DVOUSOR3.js} +79 -5
  18. package/dist/{chunk-CLJTREDS.js → chunk-ECGJYWNA.js} +193 -41
  19. package/dist/{chunk-BUEW6IIK.js → chunk-EL6UBSX5.js} +5 -5
  20. package/dist/{chunk-6FH3IULF.js → chunk-FZ5I2NF7.js} +1 -1
  21. package/dist/{chunk-ZN54U2OZ.js → chunk-GFCHWMGD.js} +3 -3
  22. package/dist/{chunk-GNJL4YGR.js → chunk-GJO3CFUN.js} +30 -6
  23. package/dist/chunk-H3JZIB5O.js +322 -0
  24. package/dist/chunk-HEHO7SMV.js +51 -0
  25. package/dist/{chunk-STCQGCEQ.js → chunk-HGDDW24U.js} +3 -3
  26. package/dist/chunk-J3YUXVID.js +907 -0
  27. package/dist/{chunk-Y6VJKXGL.js → chunk-KCYWJDDW.js} +1 -1
  28. package/dist/{chunk-W4SPAEE7.js → chunk-OFOCU2V4.js} +5 -4
  29. package/dist/chunk-PTWPPVC7.js +972 -0
  30. package/dist/{chunk-QSHD36LH.js → chunk-QFWERBDP.js} +2 -2
  31. package/dist/{chunk-QSRRMEYM.js → chunk-S7N7HI5E.js} +1 -1
  32. package/dist/{chunk-PBACDKKP.js → chunk-T7E764W3.js} +3 -3
  33. package/dist/chunk-TDWFBDAQ.js +1016 -0
  34. package/dist/{chunk-ESVS6K2B.js → chunk-TWMI3SNN.js} +6 -5
  35. package/dist/{chunk-2RAZ4ZFE.js → chunk-VBILES4B.js} +1 -1
  36. package/dist/{chunk-ESFLMDRB.js → chunk-VXAGOLDP.js} +3 -3
  37. package/dist/chunk-YCUVAOFC.js +158 -0
  38. package/dist/{chunk-SS4B7P7V.js → chunk-YIDV4VV2.js} +1 -1
  39. package/dist/chunk-ZKWPCBYT.js +600 -0
  40. package/dist/cli/index.js +24 -24
  41. package/dist/commands/archive.js +2 -2
  42. package/dist/commands/benchmark.d.ts +12 -0
  43. package/dist/commands/benchmark.js +12 -0
  44. package/dist/commands/context.js +6 -5
  45. package/dist/commands/doctor.d.ts +8 -3
  46. package/dist/commands/doctor.js +6 -20
  47. package/dist/commands/embed.js +5 -4
  48. package/dist/commands/entities.js +1 -1
  49. package/dist/commands/graph.js +2 -2
  50. package/dist/commands/inbox.d.ts +23 -0
  51. package/dist/commands/inbox.js +11 -0
  52. package/dist/commands/inject.d.ts +1 -1
  53. package/dist/commands/inject.js +3 -3
  54. package/dist/commands/link.js +6 -6
  55. package/dist/commands/maintain.d.ts +32 -0
  56. package/dist/commands/maintain.js +12 -0
  57. package/dist/commands/migrate-observations.js +2 -2
  58. package/dist/commands/observe.js +9 -8
  59. package/dist/commands/rebuild-embeddings.js +47 -16
  60. package/dist/commands/rebuild.js +7 -6
  61. package/dist/commands/reflect.js +5 -5
  62. package/dist/commands/replay.js +8 -7
  63. package/dist/commands/setup.js +3 -2
  64. package/dist/commands/sleep.d.ts +1 -1
  65. package/dist/commands/sleep.js +17 -15
  66. package/dist/commands/status.js +26 -24
  67. package/dist/commands/sync-bd.js +2 -2
  68. package/dist/commands/tailscale.js +2 -2
  69. package/dist/commands/wake.d.ts +1 -1
  70. package/dist/commands/wake.js +8 -7
  71. package/dist/index.d.ts +168 -16
  72. package/dist/index.js +271 -108
  73. package/dist/{inject-DYUrDqQO.d.ts → inject-DEb_jpLi.d.ts} +3 -1
  74. package/dist/lib/config.js +1 -1
  75. package/dist/{types-BbWJoC1c.d.ts → types-DslKvCaj.d.ts} +51 -1
  76. package/hooks/clawvault/HOOK.md +22 -5
  77. package/hooks/clawvault/handler.js +213 -78
  78. package/hooks/clawvault/handler.test.js +109 -43
  79. package/hooks/clawvault/integrity.js +112 -0
  80. package/hooks/clawvault/integrity.test.js +32 -0
  81. package/hooks/clawvault/openclaw.plugin.json +133 -15
  82. package/openclaw.plugin.json +126 -20
  83. package/package.json +2 -2
  84. package/bin/register-workgraph-commands.js +0 -1368
  85. package/dist/chunk-33VSQP4J.js +0 -37
  86. package/dist/chunk-4BQTQMJP.js +0 -93
  87. package/dist/chunk-EK6S23ZB.js +0 -469
  88. package/dist/chunk-GAOWA7GR.js +0 -501
  89. package/dist/chunk-GGA32J2R.js +0 -784
  90. package/dist/chunk-MM6QGW3P.js +0 -207
  91. package/dist/chunk-QVEERJSP.js +0 -152
  92. package/dist/chunk-U4O6C46S.js +0 -154
  93. package/dist/chunk-VSL7KY3M.js +0 -189
  94. package/dist/chunk-WMGIIABP.js +0 -15
  95. package/dist/commands/workgraph.d.ts +0 -124
  96. package/dist/commands/workgraph.js +0 -38
  97. package/dist/ledger-B7g7jhqG.d.ts +0 -44
  98. package/dist/registry-BR4326o0.d.ts +0 -30
  99. package/dist/store-CA-6sKCJ.d.ts +0 -34
  100. package/dist/thread-B9LhXNU0.d.ts +0 -41
  101. package/dist/workgraph/index.d.ts +0 -5
  102. package/dist/workgraph/index.js +0 -23
  103. package/dist/workgraph/ledger.d.ts +0 -2
  104. package/dist/workgraph/ledger.js +0 -25
  105. package/dist/workgraph/registry.d.ts +0 -2
  106. package/dist/workgraph/registry.js +0 -19
  107. package/dist/workgraph/store.d.ts +0 -2
  108. package/dist/workgraph/store.js +0 -25
  109. package/dist/workgraph/thread.d.ts +0 -2
  110. package/dist/workgraph/thread.js +0 -25
  111. package/dist/workgraph/types.d.ts +0 -54
  112. package/dist/workgraph/types.js +0 -7
@@ -10,6 +10,8 @@ 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"];
13
15
  function resolveOpenClawHome() {
14
16
  return process.env.OPENCLAW_HOME?.trim() || path.join(os.homedir(), ".openclaw");
15
17
  }
@@ -88,6 +90,78 @@ function resolveLlmProvider() {
88
90
  }
89
91
  return null;
90
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
+ }
91
165
  async function requestLlmCompletion(options) {
92
166
  const provider = options.provider ?? resolveLlmProvider();
93
167
  if (!provider) {
@@ -121,7 +195,7 @@ async function callAnthropic(options, provider) {
121
195
  "anthropic-version": "2023-06-01"
122
196
  },
123
197
  body: JSON.stringify({
124
- model: options.model ?? DEFAULT_MODELS[provider],
198
+ model: resolveRequestModel(options, DEFAULT_MODELS[provider]),
125
199
  temperature: options.temperature ?? 0.1,
126
200
  max_tokens: options.maxTokens ?? 1200,
127
201
  messages: [{ role: "user", content: options.prompt }]
@@ -151,7 +225,7 @@ async function callOpenAI(options, provider) {
151
225
  authorization: `Bearer ${apiKey}`
152
226
  },
153
227
  body: JSON.stringify({
154
- model: options.model ?? DEFAULT_MODELS[provider],
228
+ model: resolveRequestModel(options, DEFAULT_MODELS[provider]),
155
229
  temperature: options.temperature ?? 0.1,
156
230
  max_tokens: options.maxTokens ?? 1200,
157
231
  messages
@@ -181,7 +255,7 @@ async function callXAI(options, provider) {
181
255
  authorization: `Bearer ${apiKey}`
182
256
  },
183
257
  body: JSON.stringify({
184
- model: options.model ?? DEFAULT_MODELS[provider],
258
+ model: resolveRequestModel(options, DEFAULT_MODELS[provider]),
185
259
  temperature: options.temperature ?? 0.1,
186
260
  max_tokens: options.maxTokens ?? 1200,
187
261
  messages
@@ -199,7 +273,7 @@ async function callGemini(options, provider) {
199
273
  return "";
200
274
  }
201
275
  const fetchImpl = options.fetchImpl ?? fetch;
202
- const model = options.model ?? DEFAULT_MODELS[provider];
276
+ const model = resolveRequestModel(options, DEFAULT_MODELS[provider]);
203
277
  const response = await fetchImpl(
204
278
  `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`,
205
279
  {
@@ -238,7 +312,7 @@ async function callOpenClaw(options) {
238
312
  authorization: `Bearer ${config.apiKey}`
239
313
  },
240
314
  body: JSON.stringify({
241
- model: options.model ?? config.defaultModel,
315
+ model: resolveRequestModel(options, config.defaultModel),
242
316
  temperature: options.temperature ?? 0.1,
243
317
  max_tokens: options.maxTokens ?? 1200,
244
318
  messages
@@ -1,20 +1,19 @@
1
+ import {
2
+ DEFAULT_CATEGORIES,
3
+ TYPE_TO_CATEGORY
4
+ } from "./chunk-2CDEETQN.js";
1
5
  import {
2
6
  loadVaultQmdConfig,
3
7
  recoverQmdEmbeddingIfNeeded
4
- } from "./chunk-6FH3IULF.js";
8
+ } from "./chunk-FZ5I2NF7.js";
5
9
  import {
6
- QmdUnavailableError,
7
10
  SearchEngine,
8
11
  extractTags,
9
12
  extractWikiLinks,
10
13
  hasQmd,
11
14
  qmdEmbed,
12
15
  qmdUpdate
13
- } from "./chunk-EK6S23ZB.js";
14
- import {
15
- DEFAULT_CATEGORIES,
16
- TYPE_TO_CATEGORY
17
- } from "./chunk-2CDEETQN.js";
16
+ } from "./chunk-PTWPPVC7.js";
18
17
  import {
19
18
  buildOrUpdateMemoryGraphIndex
20
19
  } from "./chunk-33DOSHTA.js";
@@ -35,17 +34,16 @@ var ClawVault = class {
35
34
  if (typeof vaultPath !== "string" || !vaultPath.trim()) {
36
35
  throw new Error(`Invalid vault path: expected a non-empty string, received ${typeof vaultPath}`);
37
36
  }
38
- if (!hasQmd()) {
39
- const error = new QmdUnavailableError("NOT_INSTALLED");
40
- console.error(error.toUserMessage());
41
- throw error;
42
- }
43
37
  this.config = {
44
38
  path: path.resolve(vaultPath),
45
39
  name: path.basename(vaultPath),
46
40
  categories: DEFAULT_CATEGORIES,
47
41
  qmdCollection: void 0,
48
- qmdRoot: void 0
42
+ qmdRoot: void 0,
43
+ search: {
44
+ backend: "in-process",
45
+ qmdFallback: true
46
+ }
49
47
  };
50
48
  this.search = new SearchEngine();
51
49
  this.applyQmdConfig();
@@ -54,11 +52,6 @@ var ClawVault = class {
54
52
  * Initialize a new vault
55
53
  */
56
54
  async init(options = {}, initFlags) {
57
- if (!hasQmd()) {
58
- const error = new QmdUnavailableError("NOT_INSTALLED");
59
- console.error(error.toUserMessage());
60
- throw error;
61
- }
62
55
  const vaultPath = this.config.path;
63
56
  const flags = initFlags || {};
64
57
  this.config = { ...this.config, ...options };
@@ -99,7 +92,8 @@ var ClawVault = class {
99
92
  categories: this.config.categories,
100
93
  documentCount: 0,
101
94
  qmdCollection: this.getQmdCollection(),
102
- qmdRoot: this.getQmdRoot()
95
+ qmdRoot: this.getQmdRoot(),
96
+ search: this.config.search ?? { backend: "in-process", qmdFallback: true }
103
97
  };
104
98
  fs.writeFileSync(configPath, JSON.stringify(meta, null, 2));
105
99
  if (!flags.skipBases && this.config.categories.includes("tasks")) {
@@ -210,11 +204,6 @@ var ClawVault = class {
210
204
  * Load an existing vault
211
205
  */
212
206
  async load() {
213
- if (!hasQmd()) {
214
- const error = new QmdUnavailableError("NOT_INSTALLED");
215
- console.error(error.toUserMessage());
216
- throw error;
217
- }
218
207
  const vaultPath = this.config.path;
219
208
  const configPath = path.join(vaultPath, CONFIG_FILE);
220
209
  if (!fs.existsSync(configPath)) {
@@ -225,27 +214,30 @@ var ClawVault = class {
225
214
  this.config.categories = Array.isArray(meta.categories) ? meta.categories : this.config.categories;
226
215
  this.config.qmdCollection = typeof meta.qmdCollection === "string" ? meta.qmdCollection : void 0;
227
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;
228
218
  if (!meta.qmdCollection || !meta.qmdRoot) {
229
219
  meta.qmdCollection = meta.qmdCollection || meta.name;
230
220
  meta.qmdRoot = meta.qmdRoot || this.config.path;
231
221
  fs.writeFileSync(configPath, JSON.stringify(meta, null, 2));
232
222
  }
233
223
  this.applyQmdConfig(meta);
234
- try {
235
- const recovery = recoverQmdEmbeddingIfNeeded({
236
- vaultPath: this.config.path,
237
- collection: this.getQmdCollection(),
238
- rootPath: this.getQmdRoot(),
239
- mode: "marker-only",
240
- onLog: (message) => console.warn(`[clawvault] ${message}`)
241
- });
242
- if (recovery.recovered) {
243
- console.warn(`[clawvault] qmd embedding recovery finished for "${this.getQmdCollection()}".`);
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
+ );
244
240
  }
245
- } catch (err) {
246
- console.warn(
247
- `[clawvault] qmd embedding recovery failed: ${err?.message || "unknown error"}`
248
- );
249
241
  }
250
242
  await this.reindex();
251
243
  this.initialized = true;
@@ -342,6 +334,28 @@ var ClawVault = class {
342
334
  }
343
335
  return doc;
344
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
+ }
345
359
  /**
346
360
  * Quick store to inbox
347
361
  */
@@ -354,19 +368,19 @@ var ClawVault = class {
354
368
  });
355
369
  }
356
370
  /**
357
- * Search the vault (BM25 via qmd)
371
+ * Search the vault (in-process hybrid by default, qmd fallback optional)
358
372
  */
359
373
  async find(query, options = {}) {
360
374
  return this.search.search(query, options);
361
375
  }
362
376
  /**
363
- * Semantic/vector search (via qmd vsearch)
377
+ * Semantic/vector search (hosted embeddings, qmd fallback optional)
364
378
  */
365
379
  async vsearch(query, options = {}) {
366
380
  return this.search.vsearch(query, options);
367
381
  }
368
382
  /**
369
- * Combined search with query expansion (via qmd query)
383
+ * Combined search entrypoint (currently aliases hybrid search)
370
384
  */
371
385
  async query(query, options = {}) {
372
386
  return this.search.query(query, options);
@@ -743,6 +757,142 @@ var ClawVault = class {
743
757
  }
744
758
  return fallback || (/* @__PURE__ */ new Date()).toISOString();
745
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
+ }
746
896
  /**
747
897
  * Extract the date portion (YYYY-MM-DD) from an ISO date string or Date object.
748
898
  * Provides safe handling for various date formats.
@@ -765,9 +915,11 @@ var ClawVault = class {
765
915
  }
766
916
  this.config.qmdCollection = collection;
767
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 };
768
919
  this.search.setVaultPath(this.config.path);
769
920
  this.search.setCollection(collection);
770
921
  this.search.setCollectionRoot(root);
922
+ this.search.setSearchConfig(this.config.search);
771
923
  }
772
924
  slugify(text) {
773
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-EK6S23ZB.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";
@@ -347,7 +347,7 @@ async function setupCommand(options = {}) {
347
347
  console.log("\u2298 qmd collection already exists.");
348
348
  }
349
349
  } else {
350
- console.log("\u2298 qmd not found \u2014 skipping semantic search setup.");
350
+ console.log("\u2298 qmd not found \u2014 skipping optional qmd fallback setup.");
351
351
  }
352
352
  console.log("\nTip: add this to your shell config:");
353
353
  console.log(` export CLAWVAULT_PATH="${target.vaultPath}"`);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  withQmdIndexArgs
3
- } from "./chunk-EK6S23ZB.js";
3
+ } from "./chunk-PTWPPVC7.js";
4
4
 
5
5
  // src/lib/qmd-collections.ts
6
6
  import { execFileSync } from "child_process";
@@ -1,9 +1,9 @@
1
- import {
2
- ClawVault
3
- } from "./chunk-CLJTREDS.js";
4
1
  import {
5
2
  FactStore
6
3
  } from "./chunk-BSJ6RIT7.js";
4
+ import {
5
+ ClawVault
6
+ } from "./chunk-ECGJYWNA.js";
7
7
  import {
8
8
  parseObservationMarkdown
9
9
  } from "./chunk-FHFUXL6G.js";
@@ -44,9 +44,31 @@ function resolveAgentVaultPath(agentVaults, agentId) {
44
44
  if (!agentPath || typeof agentPath !== "string") return null;
45
45
  return validateVaultPath(agentPath);
46
46
  }
47
+ function toNonEmptyPathString(value) {
48
+ if (typeof value !== "string") return null;
49
+ const trimmed = value.trim();
50
+ return trimmed ? trimmed : null;
51
+ }
52
+ function coercePathCandidate(value) {
53
+ const direct = toNonEmptyPathString(value);
54
+ if (direct) return direct;
55
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
56
+ return null;
57
+ }
58
+ const record = value;
59
+ const candidates = [record.explicitPath, record.vaultPath, record.path, record.vault];
60
+ for (const candidate of candidates) {
61
+ const normalized = toNonEmptyPathString(candidate);
62
+ if (normalized) {
63
+ return normalized;
64
+ }
65
+ }
66
+ return null;
67
+ }
47
68
  function resolveVaultPath(options = {}) {
48
- if (options.explicitPath) {
49
- return path.resolve(options.explicitPath);
69
+ const explicitPath = coercePathCandidate(options.explicitPath);
70
+ if (explicitPath) {
71
+ return path.resolve(explicitPath);
50
72
  }
51
73
  if (options.agentId && options.pluginConfig?.agentVaults) {
52
74
  const agentVaultPath = resolveAgentVaultPath(
@@ -57,12 +79,14 @@ function resolveVaultPath(options = {}) {
57
79
  return agentVaultPath;
58
80
  }
59
81
  }
60
- if (options.pluginConfig?.vaultPath) {
61
- const validated = validateVaultPath(options.pluginConfig.vaultPath);
82
+ const configuredVaultPath = coercePathCandidate(options.pluginConfig?.vaultPath);
83
+ if (configuredVaultPath) {
84
+ const validated = validateVaultPath(configuredVaultPath);
62
85
  if (validated) return validated;
63
86
  }
64
- if (process.env.CLAWVAULT_PATH) {
65
- return path.resolve(process.env.CLAWVAULT_PATH);
87
+ const envVaultPath = toNonEmptyPathString(process.env.CLAWVAULT_PATH);
88
+ if (envVaultPath) {
89
+ return path.resolve(envVaultPath);
66
90
  }
67
91
  const discovered = findNearestVaultPath(options.cwd ?? process.cwd());
68
92
  if (discovered) {