clawvault 2.6.3 → 2.6.5

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 (127) hide show
  1. package/README.md +351 -21
  2. package/bin/clawvault.js +8 -2
  3. package/bin/command-runtime.js +9 -1
  4. package/bin/register-maintenance-commands.js +19 -0
  5. package/bin/register-query-commands.js +58 -6
  6. package/bin/register-workgraph-commands.js +451 -0
  7. package/dist/chunk-2GKPENIR.js +66 -0
  8. package/dist/{chunk-VXEOHTSL.js → chunk-2JQ3O2YL.js} +1 -1
  9. package/dist/{chunk-VR5NE7PZ.js → chunk-2RAZ4ZFE.js} +1 -1
  10. package/dist/chunk-2ZDO52B4.js +52 -0
  11. package/dist/{chunk-ZZA73MFY.js → chunk-33DOSHTA.js} +176 -36
  12. package/dist/chunk-4BQTQMJP.js +93 -0
  13. package/dist/{chunk-MAKNAHAW.js → chunk-5PJ4STIC.js} +98 -8
  14. package/dist/{chunk-RVYA52PY.js → chunk-5UM4PMMM.js} +1 -1
  15. package/dist/{chunk-4VQTUVH7.js → chunk-7YZWHM36.js} +52 -26
  16. package/dist/{chunk-4VRIMU4O.js → chunk-A4EAUO7T.js} +5 -5
  17. package/dist/{chunk-R6SXNSFD.js → chunk-BV5KWZKR.js} +3 -3
  18. package/dist/chunk-FBITHIZF.js +351 -0
  19. package/dist/{chunk-Q2J5YTUF.js → chunk-FUSLEY6L.js} +751 -34
  20. package/dist/chunk-GNJL4YGR.js +79 -0
  21. package/dist/{chunk-42MXU7A6.js → chunk-K4GFGKFD.js} +51 -47
  22. package/dist/{chunk-PBEE567J.js → chunk-KSZROBFH.js} +2 -2
  23. package/dist/chunk-L4HSSQ6T.js +152 -0
  24. package/dist/{chunk-PZ2AUU2W.js → chunk-LMKQ7NIF.js} +206 -37
  25. package/dist/{chunk-6546Q4OR.js → chunk-M5O6FQ66.js} +6 -6
  26. package/dist/chunk-MM6QGW3P.js +207 -0
  27. package/dist/{chunk-T76H47ZS.js → chunk-MNPUYCHQ.js} +1 -1
  28. package/dist/{chunk-P5EPF6MB.js → chunk-MW5C6ZQA.js} +110 -13
  29. package/dist/{chunk-OZ7RIXTO.js → chunk-QSRRMEYM.js} +2 -2
  30. package/dist/chunk-RHISK3SZ.js +189 -0
  31. package/dist/{chunk-3BTHWPMB.js → chunk-S5OJEGFG.js} +2 -2
  32. package/dist/{chunk-MGDEINGP.js → chunk-SS4B7P7V.js} +1 -1
  33. package/dist/{chunk-ME37YNW3.js → chunk-SV7T4HRE.js} +4 -4
  34. package/dist/{chunk-IEVLHNLU.js → chunk-T3FKSZSN.js} +3 -3
  35. package/dist/{chunk-DTEHFAL7.js → chunk-TS6NDVOU.js} +2 -2
  36. package/dist/chunk-U4O6C46S.js +154 -0
  37. package/dist/{chunk-ITPEXLHA.js → chunk-URXDAUVH.js} +24 -5
  38. package/dist/chunk-WMGIIABP.js +15 -0
  39. package/dist/{chunk-QVMXF7FY.js → chunk-X3SPPUFG.js} +50 -0
  40. package/dist/{chunk-THRJVD4L.js → chunk-Y6VJKXGL.js} +1 -1
  41. package/dist/{chunk-RCBMXTWS.js → chunk-YD7SVXTF.js} +39 -7
  42. package/dist/{chunk-HIHOUSXS.js → chunk-YXQCA6B7.js} +105 -1
  43. package/dist/cli/index.js +20 -18
  44. package/dist/commands/archive.js +3 -2
  45. package/dist/commands/backlog.js +1 -0
  46. package/dist/commands/blocked.js +1 -0
  47. package/dist/commands/canvas.js +2 -1
  48. package/dist/commands/checkpoint.js +1 -0
  49. package/dist/commands/compat.js +2 -1
  50. package/dist/commands/context.js +6 -4
  51. package/dist/commands/doctor.d.ts +10 -1
  52. package/dist/commands/doctor.js +13 -10
  53. package/dist/commands/embed.js +5 -3
  54. package/dist/commands/entities.js +2 -1
  55. package/dist/commands/graph.js +4 -3
  56. package/dist/commands/inject.d.ts +1 -1
  57. package/dist/commands/inject.js +5 -4
  58. package/dist/commands/kanban.js +1 -0
  59. package/dist/commands/link.js +5 -4
  60. package/dist/commands/migrate-observations.js +3 -2
  61. package/dist/commands/observe.js +9 -7
  62. package/dist/commands/project.js +1 -0
  63. package/dist/commands/rebuild-embeddings.d.ts +21 -0
  64. package/dist/commands/rebuild-embeddings.js +91 -0
  65. package/dist/commands/rebuild.js +6 -4
  66. package/dist/commands/recover.js +1 -0
  67. package/dist/commands/reflect.js +5 -4
  68. package/dist/commands/repair-session.js +1 -0
  69. package/dist/commands/replay.js +7 -6
  70. package/dist/commands/session-recap.js +1 -0
  71. package/dist/commands/setup.js +3 -2
  72. package/dist/commands/shell-init.js +2 -0
  73. package/dist/commands/sleep.d.ts +1 -1
  74. package/dist/commands/sleep.js +10 -8
  75. package/dist/commands/status.js +13 -82
  76. package/dist/commands/sync-bd.js +3 -2
  77. package/dist/commands/tailscale.js +3 -2
  78. package/dist/commands/task.js +1 -0
  79. package/dist/commands/template.js +1 -0
  80. package/dist/commands/wake.d.ts +1 -1
  81. package/dist/commands/wake.js +5 -3
  82. package/dist/index.d.ts +254 -10
  83. package/dist/index.js +288 -155
  84. package/dist/{inject-x65KXWPk.d.ts → inject-DYUrDqQO.d.ts} +2 -2
  85. package/dist/ledger-B7g7jhqG.d.ts +44 -0
  86. package/dist/lib/auto-linker.js +2 -1
  87. package/dist/lib/canvas-layout.js +1 -0
  88. package/dist/lib/config.d.ts +27 -3
  89. package/dist/lib/config.js +4 -1
  90. package/dist/lib/entity-index.js +1 -0
  91. package/dist/lib/project-utils.js +1 -0
  92. package/dist/lib/session-repair.js +1 -0
  93. package/dist/lib/session-utils.js +1 -0
  94. package/dist/lib/tailscale.js +1 -0
  95. package/dist/lib/task-utils.js +1 -0
  96. package/dist/lib/template-engine.js +1 -0
  97. package/dist/lib/webdav.js +1 -0
  98. package/dist/onnxruntime_binding-5QEF3SUC.node +0 -0
  99. package/dist/onnxruntime_binding-BKPKNEGC.node +0 -0
  100. package/dist/onnxruntime_binding-FMOXGIUT.node +0 -0
  101. package/dist/onnxruntime_binding-OI2KMXC5.node +0 -0
  102. package/dist/onnxruntime_binding-UX44MLAZ.node +0 -0
  103. package/dist/onnxruntime_binding-Y2W7N7WY.node +0 -0
  104. package/dist/registry-BR4326o0.d.ts +30 -0
  105. package/dist/store-CA-6sKCJ.d.ts +34 -0
  106. package/dist/thread-B9LhXNU0.d.ts +41 -0
  107. package/dist/transformers.node-A2ZRORSQ.js +46775 -0
  108. package/dist/{types-C74wgGL1.d.ts → types-BbWJoC1c.d.ts} +1 -1
  109. package/dist/workgraph/index.d.ts +5 -0
  110. package/dist/workgraph/index.js +23 -0
  111. package/dist/workgraph/ledger.d.ts +2 -0
  112. package/dist/workgraph/ledger.js +25 -0
  113. package/dist/workgraph/registry.d.ts +2 -0
  114. package/dist/workgraph/registry.js +19 -0
  115. package/dist/workgraph/store.d.ts +2 -0
  116. package/dist/workgraph/store.js +25 -0
  117. package/dist/workgraph/thread.d.ts +2 -0
  118. package/dist/workgraph/thread.js +25 -0
  119. package/dist/workgraph/types.d.ts +54 -0
  120. package/dist/workgraph/types.js +7 -0
  121. package/hooks/clawvault/handler.js +714 -2
  122. package/hooks/clawvault/handler.test.js +153 -0
  123. package/hooks/clawvault/openclaw.plugin.json +72 -0
  124. package/openclaw.plugin.json +14 -2
  125. package/package.json +5 -4
  126. package/dist/chunk-4QYGFWRM.js +0 -88
  127. package/dist/chunk-MXSSG3QU.js +0 -42
@@ -1,11 +1,123 @@
1
1
  // src/lib/memory-graph.ts
2
2
  import * as fs from "fs";
3
- import * as path from "path";
3
+ import * as path2 from "path";
4
4
  import matter from "gray-matter";
5
5
  import { glob } from "glob";
6
+
7
+ // src/lib/wiki-links.ts
8
+ import * as path from "path";
9
+ function stripInlineCode(line) {
10
+ let result = "";
11
+ let cursor = 0;
12
+ while (cursor < line.length) {
13
+ if (line[cursor] !== "`") {
14
+ result += line[cursor];
15
+ cursor += 1;
16
+ continue;
17
+ }
18
+ let fenceLength = 1;
19
+ while (cursor + fenceLength < line.length && line[cursor + fenceLength] === "`") {
20
+ fenceLength += 1;
21
+ }
22
+ const fence = "`".repeat(fenceLength);
23
+ const closeIndex = line.indexOf(fence, cursor + fenceLength);
24
+ if (closeIndex === -1) {
25
+ result += fence;
26
+ cursor += fenceLength;
27
+ continue;
28
+ }
29
+ cursor = closeIndex + fenceLength;
30
+ }
31
+ return result;
32
+ }
33
+ function parseFence(line) {
34
+ const match = line.match(/^[ \t]{0,3}(`{3,}|~{3,})/);
35
+ if (!match) return null;
36
+ const marker = match[1][0];
37
+ if (marker !== "`" && marker !== "~") return null;
38
+ return { marker, length: match[1].length };
39
+ }
40
+ function isFenceClose(line, fence) {
41
+ const re = new RegExp(`^[ \\t]{0,3}${fence.marker}{${fence.length},}[ \\t]*$`);
42
+ return re.test(line);
43
+ }
44
+ function stripMarkdownCode(markdownContent) {
45
+ const lines = markdownContent.split("\n");
46
+ const visibleLines = [];
47
+ let activeFence = null;
48
+ for (const line of lines) {
49
+ if (activeFence) {
50
+ if (isFenceClose(line, activeFence)) {
51
+ activeFence = null;
52
+ }
53
+ visibleLines.push("");
54
+ continue;
55
+ }
56
+ const openingFence = parseFence(line);
57
+ if (openingFence) {
58
+ activeFence = openingFence;
59
+ visibleLines.push("");
60
+ continue;
61
+ }
62
+ if (/^(?: {4}|\t)/.test(line)) {
63
+ visibleLines.push("");
64
+ continue;
65
+ }
66
+ visibleLines.push(stripInlineCode(line));
67
+ }
68
+ return visibleLines.join("\n");
69
+ }
70
+ function extractRawWikiLinks(markdownContent) {
71
+ const content = stripMarkdownCode(markdownContent);
72
+ const links = [];
73
+ let cursor = 0;
74
+ while (cursor < content.length) {
75
+ const start = content.indexOf("[[", cursor);
76
+ if (start === -1) break;
77
+ const end = content.indexOf("]]", start + 2);
78
+ if (end === -1) break;
79
+ const candidate = content.slice(start + 2, end).trim();
80
+ if (candidate) {
81
+ links.push(candidate);
82
+ }
83
+ cursor = end + 2;
84
+ }
85
+ return links;
86
+ }
87
+ function normalizeWikiLinkTarget(rawTarget) {
88
+ let value = rawTarget.trim();
89
+ if (!value) return "";
90
+ if (value.startsWith("[[") && value.endsWith("]]")) {
91
+ value = value.slice(2, -2).trim();
92
+ }
93
+ const pipeIndex = value.indexOf("|");
94
+ if (pipeIndex >= 0) {
95
+ value = value.slice(0, pipeIndex);
96
+ }
97
+ if (value.startsWith("#")) {
98
+ return "";
99
+ }
100
+ const hashIndex = value.indexOf("#");
101
+ if (hashIndex >= 0) {
102
+ value = value.slice(0, hashIndex);
103
+ }
104
+ value = value.trim().replace(/\\/g, "/").replace(/^\/+/, "");
105
+ if (!value) return "";
106
+ value = value.split("/").map((segment) => segment.trim()).join("/");
107
+ const normalizedPath = path.posix.normalize(value);
108
+ if (!normalizedPath || normalizedPath === ".") {
109
+ return "";
110
+ }
111
+ value = normalizedPath;
112
+ if (value.toLowerCase().endsWith(".md")) {
113
+ value = value.slice(0, -3);
114
+ }
115
+ return value.trim();
116
+ }
117
+
118
+ // src/lib/memory-graph.ts
6
119
  var MEMORY_GRAPH_SCHEMA_VERSION = 1;
7
- var GRAPH_INDEX_RELATIVE_PATH = path.join(".clawvault", "graph-index.json");
8
- var WIKI_LINK_RE = /\[\[([^\]]+)\]\]/g;
120
+ var GRAPH_INDEX_RELATIVE_PATH = path2.join(".clawvault", "graph-index.json");
9
121
  var HASH_TAG_RE = /(^|\s)#([\w-]+)/g;
10
122
  var FRONTMATTER_RELATION_FIELDS = [
11
123
  "related",
@@ -19,7 +131,7 @@ var FRONTMATTER_RELATION_FIELDS = [
19
131
  "links"
20
132
  ];
21
133
  function normalizeRelativePath(value) {
22
- return value.split(path.sep).join("/").replace(/^\.\//, "").replace(/^\/+/, "");
134
+ return value.split(path2.sep).join("/").replace(/^\.\//, "").replace(/^\/+/, "");
23
135
  }
24
136
  function toNoteKey(relativePath) {
25
137
  const normalized = normalizeRelativePath(relativePath);
@@ -57,31 +169,14 @@ function inferNodeType(relativePath, frontmatter) {
57
169
  return "note";
58
170
  }
59
171
  function ensureClawvaultDir(vaultPath) {
60
- const dirPath = path.join(vaultPath, ".clawvault");
172
+ const dirPath = path2.join(vaultPath, ".clawvault");
61
173
  if (!fs.existsSync(dirPath)) {
62
174
  fs.mkdirSync(dirPath, { recursive: true });
63
175
  }
64
176
  return dirPath;
65
177
  }
66
178
  function getGraphIndexPath(vaultPath) {
67
- return path.join(vaultPath, GRAPH_INDEX_RELATIVE_PATH);
68
- }
69
- function normalizeWikiTarget(target) {
70
- let value = target.trim();
71
- if (!value) return "";
72
- const pipeIndex = value.indexOf("|");
73
- if (pipeIndex >= 0) {
74
- value = value.slice(0, pipeIndex);
75
- }
76
- const hashIndex = value.indexOf("#");
77
- if (hashIndex >= 0) {
78
- value = value.slice(0, hashIndex);
79
- }
80
- value = value.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
81
- if (value.toLowerCase().endsWith(".md")) {
82
- value = value.slice(0, -3);
83
- }
84
- return value.trim();
179
+ return path2.join(vaultPath, GRAPH_INDEX_RELATIVE_PATH);
85
180
  }
86
181
  function collectTags(frontmatter, markdownContent) {
87
182
  const tags = /* @__PURE__ */ new Set();
@@ -105,14 +200,15 @@ function collectTags(frontmatter, markdownContent) {
105
200
  }
106
201
  function extractWikiTargets(markdownContent) {
107
202
  const targets = /* @__PURE__ */ new Set();
108
- for (const match of markdownContent.matchAll(WIKI_LINK_RE)) {
109
- const candidate = match[1];
110
- if (!candidate) continue;
203
+ for (const candidate of extractRawWikiLinks(markdownContent)) {
111
204
  const normalized = normalizeWikiTarget(candidate);
112
205
  if (normalized) targets.add(normalized);
113
206
  }
114
207
  return [...targets];
115
208
  }
209
+ function normalizeWikiTarget(target) {
210
+ return normalizeWikiLinkTarget(target);
211
+ }
116
212
  function toStringArray(value) {
117
213
  if (typeof value === "string") {
118
214
  return value.split(",").map((entry) => entry.trim()).filter(Boolean);
@@ -150,15 +246,57 @@ function buildNoteRegistry(relativePaths) {
150
246
  }
151
247
  return { byLowerPath, byLowerBasename };
152
248
  }
153
- function resolveTargetNodeId(rawTarget, registry) {
249
+ function normalizeLookupPath(candidate) {
250
+ const normalized = normalizeWikiTarget(candidate);
251
+ if (!normalized) return "";
252
+ const resolved = path2.posix.normalize(normalized).replace(/^\/+/, "");
253
+ if (!resolved || resolved === "." || resolved.startsWith("../")) {
254
+ return "";
255
+ }
256
+ return resolved;
257
+ }
258
+ function buildTargetLookupCandidates(target, sourceNoteKey) {
259
+ const candidates = [];
260
+ const sourceDir = path2.posix.dirname(sourceNoteKey);
261
+ const hasSourceDir = sourceDir !== ".";
262
+ const isRelativeTarget = target.startsWith("./") || target.startsWith("../");
263
+ const addCandidate = (candidate) => {
264
+ const normalized = normalizeLookupPath(candidate);
265
+ if (!normalized || candidates.includes(normalized)) return;
266
+ candidates.push(normalized);
267
+ };
268
+ if (isRelativeTarget) {
269
+ if (hasSourceDir) {
270
+ addCandidate(path2.posix.join(sourceDir, target));
271
+ } else {
272
+ addCandidate(target);
273
+ }
274
+ if (target.startsWith("./")) {
275
+ addCandidate(target.slice(2));
276
+ }
277
+ return candidates;
278
+ }
279
+ if (!target.includes("/")) {
280
+ if (hasSourceDir) {
281
+ addCandidate(`${sourceDir}/${target}`);
282
+ }
283
+ addCandidate(target);
284
+ return candidates;
285
+ }
286
+ addCandidate(target);
287
+ return candidates;
288
+ }
289
+ function resolveTargetNodeId(rawTarget, registry, sourceNoteKey) {
154
290
  const normalized = normalizeWikiTarget(rawTarget);
155
291
  if (!normalized) {
156
292
  return toUnresolvedNodeId(rawTarget);
157
293
  }
158
294
  const lowerTarget = normalized.toLowerCase();
159
- const direct = registry.byLowerPath.get(lowerTarget);
160
- if (direct) {
161
- return toNoteNodeId(direct);
295
+ for (const candidate of buildTargetLookupCandidates(normalized, sourceNoteKey)) {
296
+ const direct = registry.byLowerPath.get(candidate.toLowerCase());
297
+ if (direct) {
298
+ return toNoteNodeId(direct);
299
+ }
162
300
  }
163
301
  if (!normalized.includes("/")) {
164
302
  const basenameMatches = registry.byLowerBasename.get(lowerTarget) ?? [];
@@ -186,7 +324,7 @@ function buildFragmentNode(id, title, type, category, pathValue, tags, missing,
186
324
  };
187
325
  }
188
326
  function parseFileFragment(vaultPath, relativePath, mtimeMs, registry) {
189
- const absolutePath = path.join(vaultPath, relativePath);
327
+ const absolutePath = path2.join(vaultPath, relativePath);
190
328
  const raw = fs.readFileSync(absolutePath, "utf-8");
191
329
  const parsed = matter(raw);
192
330
  const frontmatter = parsed.data ?? {};
@@ -225,7 +363,7 @@ function parseFileFragment(vaultPath, relativePath, mtimeMs, registry) {
225
363
  }
226
364
  const wikiTargets = extractWikiTargets(parsed.content);
227
365
  for (const target of wikiTargets) {
228
- const targetNodeId = resolveTargetNodeId(target, registry);
366
+ const targetNodeId = resolveTargetNodeId(target, registry, noteKey);
229
367
  if (targetNodeId.startsWith("unresolved:") && !nodes.has(targetNodeId)) {
230
368
  nodes.set(
231
369
  targetNodeId,
@@ -241,7 +379,7 @@ function parseFileFragment(vaultPath, relativePath, mtimeMs, registry) {
241
379
  });
242
380
  }
243
381
  for (const relation of extractFrontmatterRelations(frontmatter)) {
244
- const targetNodeId = resolveTargetNodeId(relation.target, registry);
382
+ const targetNodeId = resolveTargetNodeId(relation.target, registry, noteKey);
245
383
  if (targetNodeId.startsWith("unresolved:") && !nodes.has(targetNodeId)) {
246
384
  nodes.set(
247
385
  targetNodeId,
@@ -326,7 +464,7 @@ function isValidIndex(index) {
326
464
  return typed.schemaVersion === MEMORY_GRAPH_SCHEMA_VERSION && typeof typed.vaultPath === "string" && typeof typed.generatedAt === "string" && Boolean(typed.files && typeof typed.files === "object") && Boolean(typed.graph && typeof typed.graph === "object");
327
465
  }
328
466
  function loadMemoryGraphIndex(vaultPath) {
329
- const indexPath = getGraphIndexPath(path.resolve(vaultPath));
467
+ const indexPath = getGraphIndexPath(path2.resolve(vaultPath));
330
468
  if (!fs.existsSync(indexPath)) {
331
469
  return null;
332
470
  }
@@ -341,7 +479,7 @@ function loadMemoryGraphIndex(vaultPath) {
341
479
  }
342
480
  }
343
481
  async function buildOrUpdateMemoryGraphIndex(vaultPathInput, options = {}) {
344
- const vaultPath = path.resolve(vaultPathInput);
482
+ const vaultPath = path2.resolve(vaultPathInput);
345
483
  ensureClawvaultDir(vaultPath);
346
484
  const existing = options.forceFull ? null : loadMemoryGraphIndex(vaultPath);
347
485
  const markdownFiles = await glob("**/*.md", {
@@ -354,7 +492,7 @@ async function buildOrUpdateMemoryGraphIndex(vaultPathInput, options = {}) {
354
492
  const existingFragments = existing?.files ?? {};
355
493
  const currentFileSet = new Set(normalizedFiles);
356
494
  for (const relativePath of normalizedFiles) {
357
- const absolutePath = path.join(vaultPath, relativePath);
495
+ const absolutePath = path2.join(vaultPath, relativePath);
358
496
  const stat = fs.statSync(absolutePath);
359
497
  const existingFragment = existingFragments[relativePath];
360
498
  if (!options.forceFull && existingFragment && existingFragment.mtimeMs === stat.mtimeMs) {
@@ -391,6 +529,8 @@ async function getMemoryGraph(vaultPath, options = {}) {
391
529
  }
392
530
 
393
531
  export {
532
+ extractRawWikiLinks,
533
+ normalizeWikiLinkTarget,
394
534
  MEMORY_GRAPH_SCHEMA_VERSION,
395
535
  loadMemoryGraphIndex,
396
536
  buildOrUpdateMemoryGraphIndex,
@@ -0,0 +1,93 @@
1
+ import {
2
+ __export
3
+ } from "./chunk-2ZDO52B4.js";
4
+
5
+ // src/workgraph/ledger.ts
6
+ var ledger_exports = {};
7
+ __export(ledger_exports, {
8
+ activityOf: () => activityOf,
9
+ allClaims: () => allClaims,
10
+ append: () => append,
11
+ currentOwner: () => currentOwner,
12
+ historyOf: () => historyOf,
13
+ isClaimed: () => isClaimed,
14
+ ledgerPath: () => ledgerPath,
15
+ readAll: () => readAll,
16
+ readSince: () => readSince,
17
+ recent: () => recent
18
+ });
19
+ import fs from "fs";
20
+ import path from "path";
21
+ var LEDGER_FILE = ".clawvault/ledger.jsonl";
22
+ function ledgerPath(vaultPath) {
23
+ return path.join(vaultPath, LEDGER_FILE);
24
+ }
25
+ function append(vaultPath, actor, op, target, type, data) {
26
+ const entry = {
27
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
28
+ actor,
29
+ op,
30
+ target,
31
+ ...type ? { type } : {},
32
+ ...data && Object.keys(data).length > 0 ? { data } : {}
33
+ };
34
+ const lPath = ledgerPath(vaultPath);
35
+ const dir = path.dirname(lPath);
36
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
37
+ fs.appendFileSync(lPath, JSON.stringify(entry) + "\n", "utf-8");
38
+ return entry;
39
+ }
40
+ function readAll(vaultPath) {
41
+ const lPath = ledgerPath(vaultPath);
42
+ if (!fs.existsSync(lPath)) return [];
43
+ const lines = fs.readFileSync(lPath, "utf-8").split("\n").filter(Boolean);
44
+ return lines.map((line) => JSON.parse(line));
45
+ }
46
+ function readSince(vaultPath, since) {
47
+ return readAll(vaultPath).filter((e) => e.ts >= since);
48
+ }
49
+ function currentOwner(vaultPath, target) {
50
+ const entries = readAll(vaultPath).filter((e) => e.target === target);
51
+ let owner = null;
52
+ for (const e of entries) {
53
+ if (e.op === "claim") owner = e.actor;
54
+ if (e.op === "release" || e.op === "done" || e.op === "cancel") owner = null;
55
+ }
56
+ return owner;
57
+ }
58
+ function isClaimed(vaultPath, target) {
59
+ return currentOwner(vaultPath, target) !== null;
60
+ }
61
+ function historyOf(vaultPath, target) {
62
+ return readAll(vaultPath).filter((e) => e.target === target);
63
+ }
64
+ function activityOf(vaultPath, actor) {
65
+ return readAll(vaultPath).filter((e) => e.actor === actor);
66
+ }
67
+ function allClaims(vaultPath) {
68
+ const claims = /* @__PURE__ */ new Map();
69
+ const entries = readAll(vaultPath);
70
+ for (const e of entries) {
71
+ if (e.op === "claim") claims.set(e.target, e.actor);
72
+ if (e.op === "release" || e.op === "done" || e.op === "cancel") claims.delete(e.target);
73
+ }
74
+ return claims;
75
+ }
76
+ function recent(vaultPath, count = 20) {
77
+ const all = readAll(vaultPath);
78
+ return all.slice(-count);
79
+ }
80
+
81
+ export {
82
+ ledgerPath,
83
+ append,
84
+ readAll,
85
+ readSince,
86
+ currentOwner,
87
+ isClaimed,
88
+ historyOf,
89
+ activityOf,
90
+ allClaims,
91
+ recent,
92
+ ledger_exports
93
+ };
@@ -4,12 +4,57 @@ import * as fs from "fs";
4
4
  import * as path from "path";
5
5
  var QMD_INSTALL_URL = "https://github.com/tobi/qmd";
6
6
  var QMD_INSTALL_COMMAND = "bun install -g github:tobi/qmd";
7
- var QMD_NOT_INSTALLED_MESSAGE = `ClawVault requires qmd. Install: ${QMD_INSTALL_COMMAND}`;
8
7
  var QMD_INDEX_ENV_VAR = "CLAWVAULT_QMD_INDEX";
8
+ var QMD_ERROR_MESSAGES = {
9
+ NOT_INSTALLED: {
10
+ code: "NOT_INSTALLED",
11
+ message: "qmd is not installed",
12
+ hint: `Install qmd to enable ClawVault search and indexing:
13
+ ${QMD_INSTALL_COMMAND}
14
+
15
+ For more information: ${QMD_INSTALL_URL}`
16
+ },
17
+ NOT_CONFIGURED: {
18
+ code: "NOT_CONFIGURED",
19
+ message: "qmd collection is not configured",
20
+ hint: "Run `clawvault doctor` to diagnose configuration issues, or `clawvault migrate` to fix common setup problems."
21
+ },
22
+ COLLECTION_NOT_FOUND: {
23
+ code: "COLLECTION_NOT_FOUND",
24
+ message: "qmd collection not found",
25
+ hint: "The configured qmd collection does not exist. Run `clawvault migrate` to recreate it, or `qmd collection add <name> <path>` manually."
26
+ },
27
+ EXECUTION_FAILED: {
28
+ code: "EXECUTION_FAILED",
29
+ message: "qmd command failed",
30
+ hint: "Run `clawvault doctor` to diagnose qmd issues."
31
+ }
32
+ };
9
33
  var QmdUnavailableError = class extends Error {
10
- constructor(message = QMD_NOT_INSTALLED_MESSAGE) {
11
- super(message);
34
+ code;
35
+ hint;
36
+ constructor(code = "NOT_INSTALLED", additionalContext) {
37
+ const details = QMD_ERROR_MESSAGES[code];
38
+ const fullMessage = additionalContext ? `${details.message}: ${additionalContext}` : details.message;
39
+ super(fullMessage);
12
40
  this.name = "QmdUnavailableError";
41
+ this.code = code;
42
+ this.hint = details.hint;
43
+ }
44
+ toUserMessage() {
45
+ return `Error: ${this.message}
46
+
47
+ ${this.hint}`;
48
+ }
49
+ };
50
+ function getQmdErrorDetails(code) {
51
+ return QMD_ERROR_MESSAGES[code];
52
+ }
53
+ var QmdConfigurationError = class extends Error {
54
+ constructor(message, hint) {
55
+ super(message);
56
+ this.hint = hint;
57
+ this.name = "QmdConfigurationError";
13
58
  }
14
59
  };
15
60
  function ensureJsonArgs(args) {
@@ -80,9 +125,39 @@ function parseQmdOutput(raw) {
80
125
  }
81
126
  function ensureQmdAvailable() {
82
127
  if (!hasQmd()) {
83
- throw new QmdUnavailableError();
128
+ throw new QmdUnavailableError("NOT_INSTALLED");
84
129
  }
85
130
  }
131
+ function detectQmdError(output, args) {
132
+ const lowerOutput = output.toLowerCase();
133
+ if (lowerOutput.includes("missing required arguments") || lowerOutput.includes("unknown option")) {
134
+ return new QmdConfigurationError(
135
+ 'qmd does not support the search command with the expected arguments. This may indicate an incompatible qmd version or a different tool named "qmd".',
136
+ `Ensure you have the correct qmd installed: ${QMD_INSTALL_COMMAND}`
137
+ );
138
+ }
139
+ if (lowerOutput.includes("collection not found") || lowerOutput.includes("no collection")) {
140
+ const collectionArg = args.findIndex((a) => a === "-c");
141
+ const collectionName = collectionArg >= 0 && args[collectionArg + 1] ? args[collectionArg + 1] : "unknown";
142
+ return new QmdConfigurationError(
143
+ `qmd collection "${collectionName}" not found.`,
144
+ 'Run `qmd update -c <collection>` to create the collection, or check your vault\'s .clawvault.json "name" field.'
145
+ );
146
+ }
147
+ if (lowerOutput.includes("no index") || lowerOutput.includes("index not found")) {
148
+ return new QmdConfigurationError(
149
+ "qmd index not found. The vault may not be indexed yet.",
150
+ "Run `clawvault rebuild` or `qmd update` to build the search index."
151
+ );
152
+ }
153
+ if (lowerOutput.includes("embedding") && (lowerOutput.includes("not found") || lowerOutput.includes("missing"))) {
154
+ return new QmdConfigurationError(
155
+ "qmd embeddings not found. Vector search requires embeddings to be generated.",
156
+ "Run `clawvault embed` or `qmd embed` to generate embeddings for semantic search."
157
+ );
158
+ }
159
+ return null;
160
+ }
86
161
  function execQmd(args, indexName) {
87
162
  ensureQmdAvailable();
88
163
  const finalArgs = withQmdIndexArgs(ensureJsonArgs(args), indexName);
@@ -96,17 +171,24 @@ function execQmd(args, indexName) {
96
171
  return parseQmdOutput(result);
97
172
  } catch (err) {
98
173
  if (err?.code === "ENOENT") {
99
- throw new QmdUnavailableError();
174
+ throw new QmdUnavailableError("NOT_INSTALLED");
100
175
  }
101
176
  const output = [err?.stdout, err?.stderr].filter(Boolean).join("\n");
177
+ const detectedError = detectQmdError(output, finalArgs);
178
+ if (detectedError) {
179
+ throw detectedError;
180
+ }
102
181
  if (output) {
103
182
  try {
104
183
  return parseQmdOutput(output);
105
184
  } catch {
106
185
  }
186
+ if (output.includes("collection not found") || output.includes("no such collection")) {
187
+ throw new QmdUnavailableError("COLLECTION_NOT_FOUND", output.trim());
188
+ }
107
189
  }
108
- const message = err?.message ? `qmd failed: ${err.message}` : "qmd failed";
109
- throw new Error(message);
190
+ const errorDetail = err?.message || "unknown error";
191
+ throw new QmdUnavailableError("EXECUTION_FAILED", errorDetail);
110
192
  }
111
193
  }
112
194
  function hasQmd() {
@@ -131,7 +213,7 @@ function qmdEmbed(collection, indexName) {
131
213
  }
132
214
  var SearchEngine = class {
133
215
  documents = /* @__PURE__ */ new Map();
134
- collection = "clawvault";
216
+ collection = "";
135
217
  vaultPath = "";
136
218
  collectionRoot = "";
137
219
  qmdIndexName;
@@ -141,6 +223,12 @@ var SearchEngine = class {
141
223
  setCollection(name) {
142
224
  this.collection = name;
143
225
  }
226
+ /**
227
+ * Get the current collection name
228
+ */
229
+ getCollection() {
230
+ return this.collection;
231
+ }
144
232
  /**
145
233
  * Set the vault path for file resolution
146
234
  */
@@ -365,6 +453,8 @@ export {
365
453
  QMD_INSTALL_URL,
366
454
  QMD_INSTALL_COMMAND,
367
455
  QmdUnavailableError,
456
+ getQmdErrorDetails,
457
+ QmdConfigurationError,
368
458
  withQmdIndexArgs,
369
459
  hasQmd,
370
460
  qmdUpdate,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  hasQmd,
3
3
  withQmdIndexArgs
4
- } from "./chunk-MAKNAHAW.js";
4
+ } from "./chunk-5PJ4STIC.js";
5
5
  import {
6
6
  DEFAULT_CATEGORIES
7
7
  } from "./chunk-2CDEETQN.js";
@@ -1,13 +1,16 @@
1
1
  import {
2
2
  buildEntityIndex
3
3
  } from "./chunk-J7ZWCI2C.js";
4
+ import {
5
+ extractRawWikiLinks,
6
+ normalizeWikiLinkTarget
7
+ } from "./chunk-33DOSHTA.js";
4
8
 
5
9
  // src/lib/backlinks.ts
6
10
  import * as fs from "fs";
7
11
  import * as path from "path";
8
12
  var CLAWVAULT_DIR = ".clawvault";
9
13
  var BACKLINKS_FILE = "backlinks.json";
10
- var WIKI_LINK_REGEX = /\[\[([^\]]+)\]\]/g;
11
14
  function ensureClawvaultDir(vaultPath) {
12
15
  const dir = path.join(vaultPath, CLAWVAULT_DIR);
13
16
  if (!fs.existsSync(dir)) {
@@ -20,29 +23,47 @@ function toVaultId(vaultPath, filePath) {
20
23
  return relative2.split(path.sep).join("/");
21
24
  }
22
25
  function normalizeLinkTarget(raw) {
23
- let target = raw.trim();
24
- if (!target) return "";
25
- if (target.startsWith("[[") && target.endsWith("]]")) {
26
- target = target.slice(2, -2);
27
- }
28
- const pipeIndex = target.indexOf("|");
29
- if (pipeIndex !== -1) {
30
- target = target.slice(0, pipeIndex);
31
- }
32
- if (target.startsWith("#")) return "";
33
- const hashIndex = target.indexOf("#");
34
- if (hashIndex !== -1) {
35
- target = target.slice(0, hashIndex);
26
+ return normalizeWikiLinkTarget(raw);
27
+ }
28
+ function normalizeLookupCandidate(value) {
29
+ const normalized = normalizeLinkTarget(value);
30
+ if (!normalized) return "";
31
+ const resolved = path.posix.normalize(normalized).replace(/^\/+/, "");
32
+ if (!resolved || resolved === "." || resolved.startsWith("../")) {
33
+ return "";
36
34
  }
37
- target = target.trim();
38
- if (!target) return "";
39
- if (target.endsWith(".md")) {
40
- target = target.slice(0, -3);
35
+ return resolved;
36
+ }
37
+ function buildLookupCandidates(target, sourceId) {
38
+ const candidates = [];
39
+ const sourceDir = path.posix.dirname(sourceId);
40
+ const hasSourceDir = sourceDir !== ".";
41
+ const isRelativeTarget = target.startsWith("./") || target.startsWith("../");
42
+ const addCandidate = (candidate) => {
43
+ const normalized = normalizeLookupCandidate(candidate);
44
+ if (!normalized || candidates.includes(normalized)) return;
45
+ candidates.push(normalized);
46
+ };
47
+ if (isRelativeTarget) {
48
+ if (hasSourceDir) {
49
+ addCandidate(path.posix.join(sourceDir, target));
50
+ } else {
51
+ addCandidate(target);
52
+ }
53
+ if (target.startsWith("./")) {
54
+ addCandidate(target.slice(2));
55
+ }
56
+ return candidates;
41
57
  }
42
- if (target.startsWith("/")) {
43
- target = target.slice(1);
58
+ if (!target.includes("/")) {
59
+ if (hasSourceDir) {
60
+ addCandidate(`${sourceDir}/${target}`);
61
+ }
62
+ addCandidate(target);
63
+ return candidates;
44
64
  }
45
- return target.replace(/\\/g, "/");
65
+ addCandidate(target);
66
+ return candidates;
46
67
  }
47
68
  function listMarkdownFiles(vaultPath) {
48
69
  const files = [];
@@ -75,11 +96,16 @@ function buildKnownIds(vaultPath, files) {
75
96
  }
76
97
  return { ids, idsLower };
77
98
  }
78
- function resolveTarget(target, known, entityIndex) {
99
+ function resolveTarget(target, sourceId, known, entityIndex) {
79
100
  if (!target) return null;
80
- if (known.ids.has(target)) return target;
101
+ for (const candidate of buildLookupCandidates(target, sourceId)) {
102
+ if (known.ids.has(candidate)) return candidate;
103
+ const lowerCandidate = candidate.toLowerCase();
104
+ if (known.idsLower.has(lowerCandidate)) {
105
+ return known.idsLower.get(lowerCandidate);
106
+ }
107
+ }
81
108
  const lower = target.toLowerCase();
82
- if (known.idsLower.has(lower)) return known.idsLower.get(lower);
83
109
  if (entityIndex?.entries.has(lower)) return entityIndex.entries.get(lower);
84
110
  return null;
85
111
  }
@@ -93,12 +119,12 @@ function scanVaultLinks(vaultPath, options = {}) {
93
119
  for (const file of files) {
94
120
  const sourceId = toVaultId(vaultPath, file);
95
121
  const content = fs.readFileSync(file, "utf-8");
96
- const matches = content.match(WIKI_LINK_REGEX) || [];
122
+ const matches = extractRawWikiLinks(content);
97
123
  linkCount += matches.length;
98
124
  for (const match of matches) {
99
125
  const target = normalizeLinkTarget(match);
100
126
  if (!target) continue;
101
- const resolved = resolveTarget(target, known, entityIndex);
127
+ const resolved = resolveTarget(target, sourceId, known, entityIndex);
102
128
  if (!resolved) {
103
129
  orphans.push({ source: sourceId, target });
104
130
  continue;