clawvault 2.6.0 → 3.0.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 (232) hide show
  1. package/bin/command-registration.test.js +1 -3
  2. package/bin/register-core-commands.js +10 -23
  3. package/bin/register-maintenance-commands.js +3 -20
  4. package/bin/register-query-commands.js +23 -0
  5. package/bin/register-task-commands.js +1 -18
  6. package/bin/register-task-commands.test.js +0 -16
  7. package/bin/register-vault-operations-commands.js +1 -29
  8. package/dist/{chunk-QVMXF7FY.js → chunk-3D6BCTP6.js} +39 -1
  9. package/dist/{chunk-R2MIW5G7.js → chunk-3DHXQHYG.js} +1 -1
  10. package/dist/{chunk-Q2J5YTUF.js → chunk-3NSBOUT3.js} +73 -36
  11. package/dist/chunk-3RG5ZIWI.js +10 -0
  12. package/dist/{chunk-AZYOKJYC.js → chunk-62YTUT6J.js} +2 -2
  13. package/dist/chunk-6U6MK36V.js +205 -0
  14. package/dist/{chunk-4QYGFWRM.js → chunk-7R7O6STJ.js} +4 -4
  15. package/dist/{chunk-VXEOHTSL.js → chunk-C7OK5WKP.js} +4 -4
  16. package/dist/chunk-CMB7UL7C.js +327 -0
  17. package/dist/chunk-DEFFDRVP.js +938 -0
  18. package/dist/{chunk-K3CDT7IH.js → chunk-E7MFQB6D.js} +61 -20
  19. package/dist/{chunk-ME37YNW3.js → chunk-F2JEUD4J.js} +6 -4
  20. package/dist/chunk-GAJV4IGR.js +82 -0
  21. package/dist/chunk-GQSLDZTS.js +560 -0
  22. package/dist/{chunk-4OXMU5S2.js → chunk-GUKMRGM7.js} +1 -1
  23. package/dist/{chunk-YOSEUUNB.js → chunk-H34S76MB.js} +6 -6
  24. package/dist/{chunk-4TE4JMLA.js → chunk-JY6FYXIT.js} +10 -5
  25. package/dist/chunk-K234IDRJ.js +1073 -0
  26. package/dist/{chunk-IEVLHNLU.js → chunk-LNJA2UGL.js} +86 -9
  27. package/dist/{chunk-MFAWT5O5.js → chunk-LYHGEHXG.js} +1 -0
  28. package/dist/chunk-MFM6K7PU.js +374 -0
  29. package/dist/{chunk-QWQ3TIKS.js → chunk-N2AXRYLC.js} +1 -1
  30. package/dist/chunk-PAH27GSN.js +108 -0
  31. package/dist/{chunk-OIWVQYQF.js → chunk-QBLMXKF2.js} +1 -1
  32. package/dist/{chunk-FHFUXL6G.js → chunk-QK3UCXWL.js} +2 -2
  33. package/dist/{chunk-2YDBJS7M.js → chunk-SJSFRIYS.js} +1 -1
  34. package/dist/{chunk-GSD4ALSI.js → chunk-U55BGUAU.js} +2 -2
  35. package/dist/{chunk-PBEE567J.js → chunk-VGLOTGAS.js} +1 -1
  36. package/dist/{chunk-F55HGNU4.js → chunk-WAZ3NLWL.js} +47 -0
  37. package/dist/{chunk-KL4NAOMO.js → chunk-WGRQ6HDV.js} +1 -1
  38. package/dist/{chunk-UEOUADMO.js → chunk-YKTA5JOJ.js} +13 -10
  39. package/dist/{chunk-XAVB4GB4.js → chunk-ZVVFWOLW.js} +4 -4
  40. package/dist/cli/index.cjs +10033 -0
  41. package/dist/cli/index.d.cts +5 -0
  42. package/dist/cli/index.js +20 -18
  43. package/dist/commands/archive.cjs +287 -0
  44. package/dist/commands/archive.d.cts +11 -0
  45. package/dist/commands/archive.js +1 -0
  46. package/dist/commands/backlog.cjs +721 -0
  47. package/dist/commands/backlog.d.cts +53 -0
  48. package/dist/commands/backlog.js +3 -2
  49. package/dist/commands/blocked.cjs +204 -0
  50. package/dist/commands/blocked.d.cts +26 -0
  51. package/dist/commands/blocked.js +3 -2
  52. package/dist/commands/checkpoint.cjs +244 -0
  53. package/dist/commands/checkpoint.d.cts +41 -0
  54. package/dist/commands/checkpoint.js +2 -1
  55. package/dist/commands/compat.cjs +369 -0
  56. package/dist/commands/compat.d.cts +28 -0
  57. package/dist/commands/compat.js +2 -1
  58. package/dist/commands/context.cjs +2989 -0
  59. package/dist/commands/context.d.cts +2 -0
  60. package/dist/commands/context.js +5 -4
  61. package/dist/commands/doctor.cjs +3062 -0
  62. package/dist/commands/doctor.d.cts +21 -0
  63. package/dist/commands/doctor.d.ts +6 -1
  64. package/dist/commands/doctor.js +13 -11
  65. package/dist/commands/embed.cjs +232 -0
  66. package/dist/commands/embed.d.cts +17 -0
  67. package/dist/commands/embed.js +5 -2
  68. package/dist/commands/entities.cjs +141 -0
  69. package/dist/commands/entities.d.cts +7 -0
  70. package/dist/commands/entities.js +1 -0
  71. package/dist/commands/graph.cjs +501 -0
  72. package/dist/commands/graph.d.cts +21 -0
  73. package/dist/commands/graph.js +1 -0
  74. package/dist/commands/inject.cjs +1636 -0
  75. package/dist/commands/inject.d.cts +2 -0
  76. package/dist/commands/inject.d.ts +1 -1
  77. package/dist/commands/inject.js +4 -2
  78. package/dist/commands/kanban.cjs +884 -0
  79. package/dist/commands/kanban.d.cts +63 -0
  80. package/dist/commands/kanban.js +4 -3
  81. package/dist/commands/link.cjs +965 -0
  82. package/dist/commands/link.d.cts +11 -0
  83. package/dist/commands/link.js +1 -0
  84. package/dist/commands/migrate-observations.cjs +362 -0
  85. package/dist/commands/migrate-observations.d.cts +19 -0
  86. package/dist/commands/migrate-observations.js +3 -2
  87. package/dist/commands/observe.cjs +4099 -0
  88. package/dist/commands/observe.d.cts +23 -0
  89. package/dist/commands/observe.d.ts +1 -0
  90. package/dist/commands/observe.js +11 -9
  91. package/dist/commands/project.cjs +1341 -0
  92. package/dist/commands/project.d.cts +85 -0
  93. package/dist/commands/project.js +5 -4
  94. package/dist/commands/rebuild.cjs +3136 -0
  95. package/dist/commands/rebuild.d.cts +11 -0
  96. package/dist/commands/rebuild.js +10 -8
  97. package/dist/commands/recover.cjs +361 -0
  98. package/dist/commands/recover.d.cts +38 -0
  99. package/dist/commands/recover.js +3 -2
  100. package/dist/commands/reflect.cjs +1008 -0
  101. package/dist/commands/reflect.d.cts +11 -0
  102. package/dist/commands/reflect.js +6 -4
  103. package/dist/commands/repair-session.cjs +457 -0
  104. package/dist/commands/repair-session.d.cts +38 -0
  105. package/dist/commands/repair-session.js +1 -0
  106. package/dist/commands/replay.cjs +4103 -0
  107. package/dist/commands/replay.d.cts +16 -0
  108. package/dist/commands/replay.js +12 -10
  109. package/dist/commands/session-recap.cjs +353 -0
  110. package/dist/commands/session-recap.d.cts +27 -0
  111. package/dist/commands/session-recap.js +1 -0
  112. package/dist/commands/setup.cjs +1345 -0
  113. package/dist/commands/setup.d.cts +100 -0
  114. package/dist/commands/setup.d.ts +90 -2
  115. package/dist/commands/setup.js +21 -2
  116. package/dist/commands/shell-init.cjs +75 -0
  117. package/dist/commands/shell-init.d.cts +7 -0
  118. package/dist/commands/shell-init.js +2 -0
  119. package/dist/commands/sleep.cjs +6028 -0
  120. package/dist/commands/sleep.d.cts +36 -0
  121. package/dist/commands/sleep.d.ts +1 -1
  122. package/dist/commands/sleep.js +17 -15
  123. package/dist/commands/status.cjs +2736 -0
  124. package/dist/commands/status.d.cts +52 -0
  125. package/dist/commands/status.js +12 -10
  126. package/dist/commands/tailscale.cjs +1532 -0
  127. package/dist/commands/tailscale.d.cts +52 -0
  128. package/dist/commands/tailscale.js +1 -0
  129. package/dist/commands/task.cjs +1236 -0
  130. package/dist/commands/task.d.cts +97 -0
  131. package/dist/commands/task.js +4 -3
  132. package/dist/commands/template.cjs +457 -0
  133. package/dist/commands/template.d.cts +36 -0
  134. package/dist/commands/template.js +2 -1
  135. package/dist/commands/wake.cjs +2626 -0
  136. package/dist/commands/wake.d.cts +22 -0
  137. package/dist/commands/wake.d.ts +1 -1
  138. package/dist/commands/wake.js +12 -11
  139. package/dist/context-BUGaWpyL.d.cts +46 -0
  140. package/dist/index.cjs +14526 -0
  141. package/dist/index.d.cts +858 -0
  142. package/dist/index.d.ts +192 -7
  143. package/dist/index.js +101 -75
  144. package/dist/{inject-x65KXWPk.d.ts → inject-Bzi5E-By.d.cts} +1 -1
  145. package/dist/inject-Bzi5E-By.d.ts +137 -0
  146. package/dist/lib/auto-linker.cjs +176 -0
  147. package/dist/lib/auto-linker.d.cts +26 -0
  148. package/dist/lib/auto-linker.js +1 -0
  149. package/dist/lib/canvas-layout.cjs +136 -0
  150. package/dist/lib/canvas-layout.d.cts +31 -0
  151. package/dist/lib/canvas-layout.d.ts +16 -100
  152. package/dist/lib/canvas-layout.js +78 -20
  153. package/dist/lib/config.cjs +78 -0
  154. package/dist/lib/config.d.cts +11 -0
  155. package/dist/lib/config.js +1 -0
  156. package/dist/lib/entity-index.cjs +84 -0
  157. package/dist/lib/entity-index.d.cts +26 -0
  158. package/dist/lib/entity-index.js +1 -0
  159. package/dist/lib/project-utils.cjs +864 -0
  160. package/dist/lib/project-utils.d.cts +97 -0
  161. package/dist/lib/project-utils.js +4 -3
  162. package/dist/lib/session-repair.cjs +239 -0
  163. package/dist/lib/session-repair.d.cts +110 -0
  164. package/dist/lib/session-repair.js +1 -0
  165. package/dist/lib/session-utils.cjs +209 -0
  166. package/dist/lib/session-utils.d.cts +63 -0
  167. package/dist/lib/session-utils.js +1 -0
  168. package/dist/lib/tailscale.cjs +1183 -0
  169. package/dist/lib/tailscale.d.cts +225 -0
  170. package/dist/lib/tailscale.js +1 -0
  171. package/dist/lib/task-utils.cjs +1137 -0
  172. package/dist/lib/task-utils.d.cts +208 -0
  173. package/dist/lib/task-utils.js +3 -2
  174. package/dist/lib/template-engine.cjs +47 -0
  175. package/dist/lib/template-engine.d.cts +11 -0
  176. package/dist/lib/template-engine.js +1 -0
  177. package/dist/lib/webdav.cjs +568 -0
  178. package/dist/lib/webdav.d.cts +109 -0
  179. package/dist/lib/webdav.js +1 -0
  180. package/dist/plugin/index.cjs +1907 -0
  181. package/dist/plugin/index.d.cts +36 -0
  182. package/dist/plugin/index.d.ts +36 -0
  183. package/dist/plugin/index.js +572 -0
  184. package/dist/plugin/inject.cjs +356 -0
  185. package/dist/plugin/inject.d.cts +54 -0
  186. package/dist/plugin/inject.d.ts +54 -0
  187. package/dist/plugin/inject.js +17 -0
  188. package/dist/plugin/observe.cjs +631 -0
  189. package/dist/plugin/observe.d.cts +39 -0
  190. package/dist/plugin/observe.d.ts +39 -0
  191. package/dist/plugin/observe.js +18 -0
  192. package/dist/plugin/templates.cjs +593 -0
  193. package/dist/plugin/templates.d.cts +52 -0
  194. package/dist/plugin/templates.d.ts +52 -0
  195. package/dist/plugin/templates.js +25 -0
  196. package/dist/plugin/types.cjs +18 -0
  197. package/dist/plugin/types.d.cts +209 -0
  198. package/dist/plugin/types.d.ts +209 -0
  199. package/dist/plugin/types.js +0 -0
  200. package/dist/plugin/vault.cjs +927 -0
  201. package/dist/plugin/vault.d.cts +68 -0
  202. package/dist/plugin/vault.d.ts +68 -0
  203. package/dist/plugin/vault.js +22 -0
  204. package/dist/{types-C74wgGL1.d.ts → types-Y2_Um2Ls.d.cts} +44 -1
  205. package/dist/types-Y2_Um2Ls.d.ts +205 -0
  206. package/hooks/clawvault/handler.js +70 -7
  207. package/hooks/clawvault/handler.test.js +91 -0
  208. package/openclaw.plugin.json +56 -0
  209. package/package.json +17 -7
  210. package/templates/memory-event.md +67 -0
  211. package/templates/party.md +63 -0
  212. package/templates/primitive-registry.yaml +551 -0
  213. package/templates/run.md +68 -0
  214. package/templates/trigger.md +68 -0
  215. package/templates/workspace.md +50 -0
  216. package/dashboard/lib/graph-diff.js +0 -104
  217. package/dashboard/lib/graph-diff.test.js +0 -75
  218. package/dashboard/lib/vault-parser.js +0 -556
  219. package/dashboard/lib/vault-parser.test.js +0 -254
  220. package/dashboard/public/app.js +0 -796
  221. package/dashboard/public/index.html +0 -52
  222. package/dashboard/public/styles.css +0 -221
  223. package/dashboard/server.js +0 -374
  224. package/dist/chunk-HA5M6KJB.js +0 -33
  225. package/dist/chunk-MAKNAHAW.js +0 -375
  226. package/dist/chunk-MDIH26GC.js +0 -183
  227. package/dist/chunk-MGDEINGP.js +0 -99
  228. package/dist/chunk-RVYA52PY.js +0 -363
  229. package/dist/commands/canvas.d.ts +0 -15
  230. package/dist/commands/canvas.js +0 -199
  231. package/dist/commands/sync-bd.d.ts +0 -10
  232. package/dist/commands/sync-bd.js +0 -9
@@ -0,0 +1,3062 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/commands/doctor.ts
31
+ var doctor_exports = {};
32
+ __export(doctor_exports, {
33
+ doctor: () => doctor
34
+ });
35
+ module.exports = __toCommonJS(doctor_exports);
36
+ var fs8 = __toESM(require("fs"), 1);
37
+ var os2 = __toESM(require("os"), 1);
38
+ var path9 = __toESM(require("path"), 1);
39
+
40
+ // src/lib/vault.ts
41
+ var fs3 = __toESM(require("fs"), 1);
42
+ var path3 = __toESM(require("path"), 1);
43
+ var import_url = require("url");
44
+ var import_gray_matter2 = __toESM(require("gray-matter"), 1);
45
+ var import_glob2 = require("glob");
46
+
47
+ // src/types.ts
48
+ var DEFAULT_CATEGORIES = [
49
+ "rules",
50
+ "preferences",
51
+ "decisions",
52
+ "patterns",
53
+ "people",
54
+ "projects",
55
+ "goals",
56
+ "transcripts",
57
+ "inbox",
58
+ "templates",
59
+ "lessons",
60
+ "agents",
61
+ "commitments",
62
+ "handoffs",
63
+ "research",
64
+ "tasks",
65
+ "backlog"
66
+ ];
67
+ var TYPE_TO_CATEGORY = {
68
+ fact: "facts",
69
+ feeling: "feelings",
70
+ decision: "decisions",
71
+ lesson: "lessons",
72
+ commitment: "commitments",
73
+ preference: "preferences",
74
+ relationship: "people",
75
+ project: "projects"
76
+ };
77
+
78
+ // src/lib/search.ts
79
+ var import_child_process = require("child_process");
80
+ var fs = __toESM(require("fs"), 1);
81
+ var path = __toESM(require("path"), 1);
82
+
83
+ // src/lib/reweave.ts
84
+ var SUPERSEDED_MARKER_RE = /\[superseded\|by=([^\]|]+)\|detected=([^\]]+)\]/;
85
+ function isSuperseded(line) {
86
+ return SUPERSEDED_MARKER_RE.test(line);
87
+ }
88
+
89
+ // src/lib/search.ts
90
+ var QMD_INSTALL_COMMAND = "bun install -g github:tobi/qmd";
91
+ var QMD_NOT_INSTALLED_MESSAGE = `ClawVault requires qmd. Install: ${QMD_INSTALL_COMMAND}`;
92
+ var QMD_INDEX_ENV_VAR = "CLAWVAULT_QMD_INDEX";
93
+ var QmdUnavailableError = class extends Error {
94
+ constructor(message = QMD_NOT_INSTALLED_MESSAGE) {
95
+ super(message);
96
+ this.name = "QmdUnavailableError";
97
+ }
98
+ };
99
+ function ensureJsonArgs(args) {
100
+ return args.includes("--json") ? args : [...args, "--json"];
101
+ }
102
+ function resolveQmdIndexName(indexName) {
103
+ const explicit = indexName?.trim();
104
+ if (explicit) {
105
+ return explicit;
106
+ }
107
+ const fromEnv = process.env[QMD_INDEX_ENV_VAR]?.trim();
108
+ return fromEnv || void 0;
109
+ }
110
+ function withQmdIndexArgs(args, indexName) {
111
+ if (args.includes("--index")) {
112
+ return [...args];
113
+ }
114
+ const resolvedIndexName = resolveQmdIndexName(indexName);
115
+ if (!resolvedIndexName) {
116
+ return [...args];
117
+ }
118
+ return ["--index", resolvedIndexName, ...args];
119
+ }
120
+ function tryParseJson(raw) {
121
+ try {
122
+ return JSON.parse(raw);
123
+ } catch {
124
+ return null;
125
+ }
126
+ }
127
+ function extractJsonPayload(raw) {
128
+ const start = raw.search(/[\[{]/);
129
+ if (start === -1) return null;
130
+ const end = Math.max(raw.lastIndexOf("]"), raw.lastIndexOf("}"));
131
+ if (end <= start) return null;
132
+ return raw.slice(start, end + 1);
133
+ }
134
+ function stripQmdNoise(raw) {
135
+ return raw.split("\n").filter((line) => {
136
+ const t = line.trim();
137
+ if (!t) return true;
138
+ if (t.startsWith("[node-llama-cpp]")) return false;
139
+ if (t.startsWith("Expanding query")) return false;
140
+ if (t.startsWith("Searching ") && t.endsWith("queries...")) return false;
141
+ if (/^[├└─│]/.test(t)) return false;
142
+ return true;
143
+ }).join("\n");
144
+ }
145
+ function parseQmdOutput(raw) {
146
+ const trimmed = stripQmdNoise(raw).trim();
147
+ if (!trimmed) return [];
148
+ const direct = tryParseJson(trimmed);
149
+ const extracted = direct ? null : extractJsonPayload(trimmed);
150
+ const parsed = direct ?? (extracted ? tryParseJson(extracted) : null);
151
+ if (!parsed) {
152
+ throw new Error("qmd returned non-JSON output. Ensure qmd supports --json.");
153
+ }
154
+ if (Array.isArray(parsed)) {
155
+ return parsed;
156
+ }
157
+ if (parsed && typeof parsed === "object") {
158
+ const candidate = parsed.results ?? parsed.items ?? parsed.data;
159
+ if (Array.isArray(candidate)) {
160
+ return candidate;
161
+ }
162
+ }
163
+ throw new Error("qmd returned an unexpected JSON shape.");
164
+ }
165
+ function ensureQmdAvailable() {
166
+ if (!hasQmd()) {
167
+ throw new QmdUnavailableError();
168
+ }
169
+ }
170
+ function execQmd(args, indexName) {
171
+ ensureQmdAvailable();
172
+ const finalArgs = withQmdIndexArgs(ensureJsonArgs(args), indexName);
173
+ try {
174
+ const result = (0, import_child_process.execFileSync)("qmd", finalArgs, {
175
+ encoding: "utf-8",
176
+ stdio: ["ignore", "pipe", "pipe"],
177
+ maxBuffer: 10 * 1024 * 1024
178
+ // 10MB
179
+ });
180
+ return parseQmdOutput(result);
181
+ } catch (err) {
182
+ if (err?.code === "ENOENT") {
183
+ throw new QmdUnavailableError();
184
+ }
185
+ const output = [err?.stdout, err?.stderr].filter(Boolean).join("\n");
186
+ if (output) {
187
+ try {
188
+ return parseQmdOutput(output);
189
+ } catch {
190
+ }
191
+ }
192
+ const message = err?.message ? `qmd failed: ${err.message}` : "qmd failed";
193
+ throw new Error(message);
194
+ }
195
+ }
196
+ function hasQmd() {
197
+ const result = (0, import_child_process.spawnSync)("qmd", ["--version"], { stdio: "ignore" });
198
+ return !result.error;
199
+ }
200
+ function qmdUpdate(collection, indexName) {
201
+ ensureQmdAvailable();
202
+ const args = ["update"];
203
+ if (collection) {
204
+ args.push("-c", collection);
205
+ }
206
+ (0, import_child_process.execFileSync)("qmd", withQmdIndexArgs(args, indexName), { stdio: "inherit" });
207
+ }
208
+ function qmdEmbed(collection, indexName) {
209
+ ensureQmdAvailable();
210
+ const args = ["embed"];
211
+ if (collection) {
212
+ args.push("-c", collection);
213
+ }
214
+ (0, import_child_process.execFileSync)("qmd", withQmdIndexArgs(args, indexName), { stdio: "inherit" });
215
+ }
216
+ function sentenceChunk(text, maxChars = 600, overlapSentences = 1) {
217
+ const sentences = text.split(/(?<=[.!?])\s+|\n{2,}/).map((s) => s.trim()).filter(Boolean);
218
+ if (sentences.length === 0) return text.trim() ? [text] : [];
219
+ const chunks = [];
220
+ let i = 0;
221
+ while (i < sentences.length) {
222
+ const chunkSents = [];
223
+ let chunkLen = 0;
224
+ let j = i;
225
+ while (j < sentences.length && chunkLen + sentences[j].length < maxChars) {
226
+ chunkSents.push(sentences[j]);
227
+ chunkLen += sentences[j].length + 1;
228
+ j++;
229
+ }
230
+ if (chunkSents.length === 0) {
231
+ chunkSents.push(sentences[j].slice(0, maxChars));
232
+ j++;
233
+ }
234
+ chunks.push(chunkSents.join(" "));
235
+ i = Math.max(j - overlapSentences, i + 1);
236
+ }
237
+ return chunks;
238
+ }
239
+ var STOPWORDS = /* @__PURE__ */ new Set([
240
+ "what",
241
+ "when",
242
+ "where",
243
+ "which",
244
+ "that",
245
+ "this",
246
+ "have",
247
+ "from",
248
+ "with",
249
+ "they",
250
+ "been",
251
+ "were",
252
+ "will",
253
+ "about",
254
+ "would",
255
+ "could",
256
+ "should",
257
+ "their",
258
+ "there",
259
+ "does",
260
+ "your",
261
+ "more",
262
+ "some",
263
+ "than",
264
+ "into",
265
+ "also",
266
+ "just",
267
+ "very",
268
+ "much",
269
+ "most",
270
+ "many",
271
+ "only",
272
+ "other",
273
+ "each",
274
+ "every",
275
+ "after",
276
+ "before",
277
+ "did",
278
+ "the",
279
+ "and",
280
+ "for",
281
+ "are",
282
+ "was",
283
+ "not",
284
+ "but",
285
+ "can",
286
+ "had",
287
+ "has",
288
+ "how",
289
+ "who",
290
+ "why",
291
+ "its",
292
+ "you",
293
+ "my",
294
+ "me",
295
+ "is",
296
+ "it",
297
+ "do",
298
+ "so",
299
+ "if",
300
+ "or",
301
+ "an",
302
+ "on",
303
+ "at",
304
+ "by",
305
+ "no",
306
+ "up",
307
+ "to",
308
+ "in",
309
+ "of",
310
+ "am",
311
+ "be"
312
+ ]);
313
+ function tokenize(text) {
314
+ return text.toLowerCase().split(/\s+/).map((w) => w.replace(/^[?.,!"'\-():;[\]{}*]+|[?.,!"'\-():;[\]{}*]+$/g, "")).filter((w) => w.length > 1);
315
+ }
316
+ function queryTerms(query) {
317
+ return tokenize(query).filter((w) => !STOPWORDS.has(w));
318
+ }
319
+ function bm25RankChunks(chunks, terms, max = 5) {
320
+ if (chunks.length === 0) return [];
321
+ const termSet = new Set(terms);
322
+ const scored = chunks.map((text, idx) => {
323
+ const words = new Set(tokenize(text));
324
+ let overlap = 0;
325
+ for (const t of termSet) if (words.has(t)) overlap++;
326
+ return { text, score: overlap, idx };
327
+ });
328
+ scored.sort((a, b) => b.score - a.score);
329
+ const seen = /* @__PURE__ */ new Set();
330
+ const result = [];
331
+ seen.add(0);
332
+ result.push({ text: chunks[0], score: scored.find((s) => s.idx === 0)?.score ?? 0 });
333
+ for (const s of scored) {
334
+ if (result.length >= max) break;
335
+ if (!seen.has(s.idx) && s.score > 0) {
336
+ seen.add(s.idx);
337
+ result.push({ text: s.text, score: s.score });
338
+ }
339
+ }
340
+ return result;
341
+ }
342
+ var MONTH_NAMES = {
343
+ january: 1,
344
+ february: 2,
345
+ march: 3,
346
+ april: 4,
347
+ may: 5,
348
+ june: 6,
349
+ july: 7,
350
+ august: 8,
351
+ september: 9,
352
+ october: 10,
353
+ november: 11,
354
+ december: 12,
355
+ jan: 1,
356
+ feb: 2,
357
+ mar: 3,
358
+ apr: 4,
359
+ jun: 6,
360
+ jul: 7,
361
+ aug: 8,
362
+ sep: 9,
363
+ sept: 9,
364
+ oct: 10,
365
+ nov: 11,
366
+ dec: 12
367
+ };
368
+ var MONTH_RE_PART = Object.keys(MONTH_NAMES).join("|");
369
+ var DATE_ISO_RE = /\b(\d{4})[/-](\d{1,2})[/-](\d{1,2})\b/g;
370
+ var DATE_US_RE = /\b(\d{1,2})\/(\d{1,2})\/(\d{4})\b/g;
371
+ var DATE_MONTH_DAY_YEAR_RE = new RegExp(
372
+ `\\b(${MONTH_RE_PART})\\s+(\\d{1,2})(?:st|nd|rd|th)?,?\\s*(\\d{4})\\b`,
373
+ "gi"
374
+ );
375
+ var DATE_DAY_MONTH_YEAR_RE = new RegExp(
376
+ `\\b(\\d{1,2})(?:st|nd|rd|th)?\\s+(${MONTH_RE_PART}),?\\s*(\\d{4})\\b`,
377
+ "gi"
378
+ );
379
+ var DATE_MONTH_DAY_RE = new RegExp(
380
+ `\\b(${MONTH_RE_PART})\\s+(\\d{1,2})(?:st|nd|rd|th)?\\b`,
381
+ "gi"
382
+ );
383
+ var RELATIVE_AGO_RE = /\b(\d+)\s+(days?|weeks?|months?|years?)\s+ago\b/gi;
384
+ var RELATIVE_IN_RE = /\bin\s+(\d+)\s+(days?|weeks?|months?|years?)\b/gi;
385
+ var DURATION_RE = /(?:for|took|spent|lasted|about|approximately|around)\s+(\d+)\s+(days?|weeks?|months?|years?|hours?|minutes?)/gi;
386
+ function tryParseISODate(y, m, d) {
387
+ const dt = new Date(Date.UTC(y, m - 1, d));
388
+ if (dt.getUTCFullYear() === y && dt.getUTCMonth() === m - 1 && dt.getUTCDate() === d) return dt;
389
+ return null;
390
+ }
391
+ function unitToDays(n, unit) {
392
+ const u = unit.toLowerCase().replace(/s$/, "");
393
+ switch (u) {
394
+ case "day":
395
+ return n;
396
+ case "week":
397
+ return n * 7;
398
+ case "month":
399
+ return n * 30;
400
+ case "year":
401
+ return n * 365;
402
+ default:
403
+ return null;
404
+ }
405
+ }
406
+ function contextSnippet(text, start, end, maxLen = 150) {
407
+ const s = Math.max(0, start - Math.floor(maxLen / 2));
408
+ const e = Math.min(text.length, end + Math.floor(maxLen / 2));
409
+ return text.slice(s, e).replace(/\n/g, " ").trim();
410
+ }
411
+ function isoStr(d) {
412
+ const yy = d.getUTCFullYear();
413
+ const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
414
+ const dd = String(d.getUTCDate()).padStart(2, "0");
415
+ return `${yy}-${mm}-${dd}`;
416
+ }
417
+ function extractDates(text, sessionDateStr) {
418
+ const results = [];
419
+ const sessionDate = sessionDateStr ? new Date(sessionDateStr) : null;
420
+ const seen = /* @__PURE__ */ new Set();
421
+ function push(date, ctx, docId = "") {
422
+ const key = `${date}|${ctx.slice(0, 60)}`;
423
+ if (seen.has(key)) return;
424
+ seen.add(key);
425
+ results.push({ date, context: ctx, documentId: docId });
426
+ }
427
+ for (const m of text.matchAll(DATE_ISO_RE)) {
428
+ const dt = tryParseISODate(+m[1], +m[2], +m[3]);
429
+ if (dt) push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
430
+ }
431
+ for (const m of text.matchAll(DATE_US_RE)) {
432
+ const dt = tryParseISODate(+m[3], +m[1], +m[2]);
433
+ if (dt) push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
434
+ }
435
+ for (const m of text.matchAll(DATE_MONTH_DAY_YEAR_RE)) {
436
+ const mon = MONTH_NAMES[m[1].toLowerCase()];
437
+ if (mon) {
438
+ const dt = tryParseISODate(+m[3], mon, +m[2]);
439
+ if (dt) push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
440
+ }
441
+ }
442
+ for (const m of text.matchAll(DATE_DAY_MONTH_YEAR_RE)) {
443
+ const mon = MONTH_NAMES[m[2].toLowerCase()];
444
+ if (mon) {
445
+ const dt = tryParseISODate(+m[3], mon, +m[1]);
446
+ if (dt) push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
447
+ }
448
+ }
449
+ if (sessionDate) {
450
+ for (const m of text.matchAll(DATE_MONTH_DAY_RE)) {
451
+ const mon = MONTH_NAMES[m[1].toLowerCase()];
452
+ if (mon) {
453
+ const dt = tryParseISODate(sessionDate.getFullYear(), mon, +m[2]);
454
+ if (dt) push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
455
+ }
456
+ }
457
+ }
458
+ if (sessionDate) {
459
+ for (const m of text.matchAll(RELATIVE_AGO_RE)) {
460
+ const days = unitToDays(+m[1], m[2]);
461
+ if (days !== null) {
462
+ const dt = new Date(sessionDate.getTime() - days * 864e5);
463
+ push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
464
+ }
465
+ }
466
+ for (const m of text.matchAll(RELATIVE_IN_RE)) {
467
+ const days = unitToDays(+m[1], m[2]);
468
+ if (days !== null) {
469
+ const dt = new Date(sessionDate.getTime() + days * 864e5);
470
+ push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
471
+ }
472
+ }
473
+ }
474
+ for (const m of text.matchAll(DURATION_RE)) {
475
+ push(`duration:${m[1]} ${m[2]}`, contextSnippet(text, m.index, m.index + m[0].length));
476
+ }
477
+ return results;
478
+ }
479
+ var PREF_PATTERNS = [
480
+ // "I use/prefer/like/love/enjoy X"
481
+ /\bi\s+(?:use|prefer|like|love|enjoy|favor|chose|switched to|started using|always use|usually use)\s+(.{3,60}?)(?:[.,;!?\n]|$)/gi,
482
+ // "my favorite X is Y"
483
+ /\bmy\s+(?:favorite|preferred|go-to|usual)\s+\w+\s+(?:is|are|was)\s+(.{3,60}?)(?:[.,;!?\n]|$)/gi,
484
+ // "I'm a big fan of X"
485
+ /\bi(?:'m| am)\s+(?:a )?(?:big |huge )?fan of\s+(.{3,60}?)(?:[.,;!?\n]|$)/gi,
486
+ // "I switched from X to Y"
487
+ /\bi\s+switched\s+from\s+(.{3,40}?)\s+to\s+(.{3,40}?)(?:[.,;!?\n]|$)/gi
488
+ ];
489
+ function extractPreferences(text, documentId = "") {
490
+ const results = [];
491
+ const seen = /* @__PURE__ */ new Set();
492
+ for (const pattern of PREF_PATTERNS) {
493
+ for (const m of text.matchAll(pattern)) {
494
+ const value = (m[1] || "").trim();
495
+ if (!value || value.length < 3) continue;
496
+ const key = value.toLowerCase();
497
+ if (seen.has(key)) continue;
498
+ seen.add(key);
499
+ const ctx = contextSnippet(text, m.index, m.index + m[0].length, 200);
500
+ let category = "general";
501
+ if (/tool|software|app|editor|ide|framework|library|language/i.test(ctx)) category = "tool";
502
+ else if (/hobby|sport|exercise|game|play/i.test(ctx)) category = "hobby";
503
+ else if (/brand|product|model|device|hardware/i.test(ctx)) category = "brand";
504
+ else if (/food|drink|restaurant|cuisine|recipe/i.test(ctx)) category = "food";
505
+ else if (/music|movie|show|book|podcast|artist|band/i.test(ctx)) category = "entertainment";
506
+ results.push({ category, value, documentId, context: ctx });
507
+ }
508
+ }
509
+ return results;
510
+ }
511
+ var PREFERENCE_Q_RE = /(?:can you (?:recommend|suggest)|any (?:tips|advice|suggestions|recommendations)|what .*(?:recommend|suggest)|what should i|where should i|which .* should i|please (?:recommend|suggest)|based on .* (?:interest|preference|taste)|personalized|tailored to (?:my|me))/i;
512
+ var TEMPORAL_Q_RE = /(?:how many (?:days|weeks|months|years|hours|minutes) (?:passed|did|have|ago|between|since|in total|took)|how long (?:did|was|were|have|has|does)|how long ago|what (?:is the )?order|in order|which .* (?:first|last|earlier|later|before|after|most recent|oldest|newest)|chronological|(?:earlier|later|sooner|newer|older) than)/i;
513
+ var AGGREGATION_Q_RE = /(?:how many|how much|total|all the|count|list all|every|what are all|name all)/i;
514
+ function classifyQuestion(q) {
515
+ if (PREFERENCE_Q_RE.test(q)) return "preference";
516
+ if (TEMPORAL_Q_RE.test(q)) return "temporal";
517
+ if (!TEMPORAL_Q_RE.test(q) && AGGREGATION_Q_RE.test(q)) return "aggregation";
518
+ return "default";
519
+ }
520
+ var SearchEngine = class {
521
+ documents = /* @__PURE__ */ new Map();
522
+ collection = "clawvault";
523
+ vaultPath = "";
524
+ collectionRoot = "";
525
+ qmdIndexName;
526
+ /** v2.7 — Per-document date index built at ingest time */
527
+ dateIndex = /* @__PURE__ */ new Map();
528
+ /** v2.7 — Per-document preference index built at ingest time */
529
+ preferenceIndex = /* @__PURE__ */ new Map();
530
+ /** v2.7 — Per-document chunk cache for BM25 pre-filtering */
531
+ chunkCache = /* @__PURE__ */ new Map();
532
+ /**
533
+ * Set the collection name (usually vault name)
534
+ */
535
+ setCollection(name) {
536
+ this.collection = name;
537
+ }
538
+ /**
539
+ * Set the vault path for file resolution
540
+ */
541
+ setVaultPath(vaultPath) {
542
+ this.vaultPath = vaultPath;
543
+ }
544
+ /**
545
+ * Set the collection root for qmd:// URI resolution
546
+ */
547
+ setCollectionRoot(root) {
548
+ this.collectionRoot = path.resolve(root);
549
+ }
550
+ /**
551
+ * Set qmd index name (defaults to qmd global default when omitted)
552
+ */
553
+ setIndexName(indexName) {
554
+ this.qmdIndexName = indexName;
555
+ }
556
+ /**
557
+ * Add or update a document in the local cache.
558
+ * v2.7: also extracts dates, preferences, and chunks at ingest time.
559
+ * Note: qmd indexing happens via qmd update command
560
+ */
561
+ addDocument(doc) {
562
+ this.documents.set(doc.id, doc);
563
+ if (doc.content) {
564
+ const sessionDate = doc.modified ? isoStr(doc.modified) : void 0;
565
+ const dates = extractDates(doc.content, sessionDate);
566
+ for (const d of dates) d.documentId = doc.id;
567
+ if (dates.length > 0) this.dateIndex.set(doc.id, dates);
568
+ const prefs = extractPreferences(doc.content, doc.id);
569
+ if (prefs.length > 0) this.preferenceIndex.set(doc.id, prefs);
570
+ const chunks = sentenceChunk(doc.content, 600, 1);
571
+ if (chunks.length > 0) this.chunkCache.set(doc.id, chunks);
572
+ }
573
+ }
574
+ /**
575
+ * Remove a document from the local cache
576
+ */
577
+ removeDocument(id) {
578
+ this.documents.delete(id);
579
+ this.dateIndex.delete(id);
580
+ this.preferenceIndex.delete(id);
581
+ this.chunkCache.delete(id);
582
+ }
583
+ /**
584
+ * No-op for qmd - indexing is managed externally
585
+ */
586
+ rebuildIDF() {
587
+ }
588
+ /**
589
+ * BM25 search via qmd
590
+ */
591
+ search(query, options = {}) {
592
+ return this.runQmdQuery("search", query, options);
593
+ }
594
+ /**
595
+ * Vector/semantic search via qmd vsearch
596
+ */
597
+ vsearch(query, options = {}) {
598
+ return this.runQmdQuery("vsearch", query, options);
599
+ }
600
+ /**
601
+ * Combined search with query expansion (qmd query command)
602
+ */
603
+ query(query, options = {}) {
604
+ return this.runQmdQuery("query", query, options);
605
+ }
606
+ runQmdQuery(command, query, options) {
607
+ const {
608
+ limit = 10,
609
+ minScore = 0,
610
+ category,
611
+ tags,
612
+ fullContent = false,
613
+ temporalBoost = false,
614
+ relevanceThreshold,
615
+ thresholdMaxResults = 40
616
+ } = options;
617
+ if (!query.trim()) return [];
618
+ const fetchLimit = relevanceThreshold !== void 0 ? thresholdMaxResults * 2 : limit * 2;
619
+ const args = [
620
+ command,
621
+ query,
622
+ "-n",
623
+ String(fetchLimit),
624
+ "--json"
625
+ ];
626
+ if (this.collection) {
627
+ args.push("-c", this.collection);
628
+ }
629
+ const qmdResults = execQmd(args, this.qmdIndexName);
630
+ const effectiveLimit = relevanceThreshold !== void 0 ? thresholdMaxResults : limit;
631
+ const results = this.convertResults(qmdResults, {
632
+ limit: effectiveLimit,
633
+ minScore: relevanceThreshold !== void 0 ? relevanceThreshold : minScore,
634
+ category,
635
+ tags,
636
+ fullContent,
637
+ temporalBoost
638
+ });
639
+ return results;
640
+ }
641
+ // -------------------------------------------------------------------------
642
+ // v2.7 — New public APIs
643
+ // -------------------------------------------------------------------------
644
+ /**
645
+ * v2.7 — Chunk-level BM25 pre-filtered search. Ranks chunks within each
646
+ * document by keyword relevance before semantic ranking, so relevant
647
+ * content deep in long documents isn't missed.
648
+ *
649
+ * Returns results with snippets from the best-matching chunks.
650
+ */
651
+ chunkPrefilterSearch(query, options = {}) {
652
+ const terms = queryTerms(query);
653
+ const results = this.runQmdQuery("query", query, options);
654
+ for (const r of results) {
655
+ const chunks = this.chunkCache.get(r.document.id);
656
+ if (chunks && chunks.length > 0 && terms.length > 0) {
657
+ const ranked = bm25RankChunks(chunks, terms, 3);
658
+ if (ranked.length > 0 && ranked[0].score > 0) {
659
+ r.snippet = ranked.map((c) => c.text).join("\n...\n").slice(0, 600);
660
+ }
661
+ }
662
+ }
663
+ return results;
664
+ }
665
+ /**
666
+ * v2.7 — Exhaustive threshold-based search for aggregation queries.
667
+ * Keeps pulling results until relevance drops below threshold.
668
+ */
669
+ exhaustiveSearch(query, threshold = 0.01, maxResults = 40) {
670
+ return this.runQmdQuery("query", query, {
671
+ relevanceThreshold: threshold,
672
+ thresholdMaxResults: maxResults,
673
+ fullContent: false
674
+ });
675
+ }
676
+ /**
677
+ * v2.7 — Get all extracted dates, optionally filtered by document ids.
678
+ */
679
+ getDates(documentIds) {
680
+ const all = [];
681
+ const iter = documentIds ? documentIds.map((id) => [id, this.dateIndex.get(id)]).filter(([, v]) => v) : this.dateIndex.entries();
682
+ for (const [, dates] of iter) {
683
+ if (dates) all.push(...dates);
684
+ }
685
+ return all;
686
+ }
687
+ /**
688
+ * v2.7 — Get all extracted preferences, optionally filtered by document ids.
689
+ */
690
+ getPreferences(documentIds) {
691
+ const all = [];
692
+ const iter = documentIds ? documentIds.map((id) => [id, this.preferenceIndex.get(id)]).filter(([, v]) => v) : this.preferenceIndex.entries();
693
+ for (const [, prefs] of iter) {
694
+ if (prefs) all.push(...prefs);
695
+ }
696
+ return all;
697
+ }
698
+ /**
699
+ * v2.7 — Search with automatic strategy selection based on question type.
700
+ * Classifies the query and routes to the appropriate pipeline.
701
+ */
702
+ smartQuery(query, options = {}) {
703
+ const qtype = classifyQuestion(query);
704
+ switch (qtype) {
705
+ case "aggregation":
706
+ return this.exhaustiveSearch(query, 0.01, options.thresholdMaxResults ?? 40);
707
+ case "preference":
708
+ case "temporal":
709
+ default:
710
+ return this.chunkPrefilterSearch(query, { ...options, limit: options.limit ?? 10 });
711
+ }
712
+ }
713
+ /**
714
+ * Convert qmd results to ClawVault SearchResult format
715
+ */
716
+ convertResults(qmdResults, options) {
717
+ const { limit = 10, minScore = 0, category, tags, fullContent = false, temporalBoost = false } = options;
718
+ const results = [];
719
+ const maxScore = qmdResults[0]?.score || 1;
720
+ for (const qr of qmdResults) {
721
+ const filePath = this.qmdUriToPath(qr.file);
722
+ const relativePath = this.vaultPath ? path.relative(this.vaultPath, filePath) : filePath;
723
+ const normalizedRelativePath = relativePath.replace(/\\/g, "/");
724
+ if (normalizedRelativePath.startsWith("ledger/archive/") || normalizedRelativePath.includes("/ledger/archive/")) {
725
+ continue;
726
+ }
727
+ const docId = normalizedRelativePath.replace(/\.md$/, "");
728
+ let doc = this.documents.get(docId) ?? this.documents.get(docId.split("/").join(path.sep));
729
+ const modifiedAt = this.resolveModifiedAt(doc, filePath);
730
+ const parts = normalizedRelativePath.split("/");
731
+ const docCategory = parts.length > 1 ? parts[0] : "root";
732
+ if (category && docCategory !== category) continue;
733
+ if (tags && tags.length > 0 && doc) {
734
+ const docTags = new Set(doc.tags);
735
+ if (!tags.some((t) => docTags.has(t))) continue;
736
+ }
737
+ const normalizedScore = maxScore > 0 ? qr.score / maxScore : 0;
738
+ const finalScore = temporalBoost ? normalizedScore * this.getRecencyFactor(modifiedAt) : normalizedScore;
739
+ if (finalScore < minScore) continue;
740
+ if (!doc) {
741
+ doc = {
742
+ id: docId,
743
+ path: filePath,
744
+ category: docCategory,
745
+ title: qr.title || path.basename(relativePath, ".md"),
746
+ content: "",
747
+ // Content loaded separately if needed
748
+ frontmatter: {},
749
+ links: [],
750
+ tags: [],
751
+ modified: modifiedAt
752
+ };
753
+ }
754
+ results.push({
755
+ document: fullContent ? doc : { ...doc, content: "" },
756
+ score: finalScore,
757
+ snippet: this.stripSupersededFromSnippet(this.cleanSnippet(qr.snippet)),
758
+ matchedTerms: []
759
+ // qmd doesn't provide this
760
+ });
761
+ }
762
+ return results.sort((a, b) => b.score - a.score).slice(0, limit);
763
+ }
764
+ resolveModifiedAt(doc, filePath) {
765
+ if (doc) return doc.modified;
766
+ try {
767
+ return fs.statSync(filePath).mtime;
768
+ } catch {
769
+ return /* @__PURE__ */ new Date(0);
770
+ }
771
+ }
772
+ getRecencyFactor(modifiedAt) {
773
+ const ageMs = Math.max(0, Date.now() - modifiedAt.getTime());
774
+ const ageDays = ageMs / (24 * 60 * 60 * 1e3);
775
+ if (ageDays < 1) return 1;
776
+ if (ageDays <= 7) return 0.9;
777
+ return 0.7;
778
+ }
779
+ /**
780
+ * Convert qmd:// URI to file path
781
+ */
782
+ qmdUriToPath(uri) {
783
+ if (uri.startsWith("qmd://")) {
784
+ const withoutScheme = uri.slice(6);
785
+ const slashIndex = withoutScheme.indexOf("/");
786
+ if (slashIndex > -1) {
787
+ const relativePath = withoutScheme.slice(slashIndex + 1);
788
+ const root = this.collectionRoot || this.vaultPath;
789
+ if (root) {
790
+ return path.join(root, relativePath);
791
+ }
792
+ return relativePath;
793
+ }
794
+ }
795
+ return uri;
796
+ }
797
+ /**
798
+ * v2.8 — Filter superseded observation lines from snippet text.
799
+ * Ensures search results prefer the latest version of knowledge.
800
+ */
801
+ stripSupersededFromSnippet(snippet) {
802
+ if (!snippet) return snippet;
803
+ return snippet.split("\n").filter((line) => !isSuperseded(line)).join("\n");
804
+ }
805
+ /**
806
+ * Clean up qmd snippet format
807
+ */
808
+ cleanSnippet(snippet) {
809
+ if (!snippet) return "";
810
+ return snippet.replace(/@@ [-+]?\d+,?\d* @@ \([^)]+\)/g, "").trim().split("\n").slice(0, 3).join("\n").slice(0, 300);
811
+ }
812
+ /**
813
+ * Get all cached documents
814
+ */
815
+ getAllDocuments() {
816
+ return [...this.documents.values()];
817
+ }
818
+ /**
819
+ * Get document count
820
+ */
821
+ get size() {
822
+ return this.documents.size;
823
+ }
824
+ /**
825
+ * Clear the local document cache and all v2.7 indices
826
+ */
827
+ clear() {
828
+ this.documents.clear();
829
+ this.dateIndex.clear();
830
+ this.preferenceIndex.clear();
831
+ this.chunkCache.clear();
832
+ }
833
+ /**
834
+ * Export documents for persistence
835
+ */
836
+ export() {
837
+ return {
838
+ documents: [...this.documents.values()]
839
+ };
840
+ }
841
+ /**
842
+ * Import from persisted data
843
+ */
844
+ import(data) {
845
+ this.clear();
846
+ for (const doc of data.documents) {
847
+ this.addDocument(doc);
848
+ }
849
+ }
850
+ };
851
+ function extractWikiLinks(content) {
852
+ const matches = content.match(/\[\[([^\]]+)\]\]/g) || [];
853
+ return matches.map((m) => m.slice(2, -2).toLowerCase());
854
+ }
855
+ function extractTags(content) {
856
+ const matches = content.match(/#[\w-]+/g) || [];
857
+ return [...new Set(matches.map((m) => m.slice(1).toLowerCase()))];
858
+ }
859
+
860
+ // src/lib/memory-graph.ts
861
+ var fs2 = __toESM(require("fs"), 1);
862
+ var path2 = __toESM(require("path"), 1);
863
+ var import_gray_matter = __toESM(require("gray-matter"), 1);
864
+ var import_glob = require("glob");
865
+ var MEMORY_GRAPH_SCHEMA_VERSION = 1;
866
+ var GRAPH_INDEX_RELATIVE_PATH = path2.join(".clawvault", "graph-index.json");
867
+ var WIKI_LINK_RE = /\[\[([^\]]+)\]\]/g;
868
+ var HASH_TAG_RE = /(^|\s)#([\w-]+)/g;
869
+ var FRONTMATTER_RELATION_FIELDS = [
870
+ "related",
871
+ "depends_on",
872
+ "dependsOn",
873
+ "blocked_by",
874
+ "blocks",
875
+ "owner",
876
+ "project",
877
+ "people",
878
+ "links"
879
+ ];
880
+ function normalizeRelativePath(value) {
881
+ return value.split(path2.sep).join("/").replace(/^\.\//, "").replace(/^\/+/, "");
882
+ }
883
+ function toNoteKey(relativePath) {
884
+ const normalized = normalizeRelativePath(relativePath);
885
+ return normalized.toLowerCase().endsWith(".md") ? normalized.slice(0, -3) : normalized;
886
+ }
887
+ function toNoteNodeId(noteKey) {
888
+ return `note:${noteKey}`;
889
+ }
890
+ function toTagNodeId(tag) {
891
+ return `tag:${tag.toLowerCase()}`;
892
+ }
893
+ function normalizeUnresolvedKey(raw) {
894
+ const normalized = raw.trim().toLowerCase().replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "").replace(/\.md$/, "").replace(/[^a-z0-9/_-]+/g, "-").replace(/\/+/g, "/").replace(/-+/g, "-").replace(/^[-/]+|[-/]+$/g, "");
895
+ return normalized || "unknown";
896
+ }
897
+ function toUnresolvedNodeId(raw) {
898
+ return `unresolved:${normalizeUnresolvedKey(raw)}`;
899
+ }
900
+ function titleFromNoteKey(noteKey) {
901
+ const basename4 = noteKey.split("/").pop() ?? noteKey;
902
+ return basename4.replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
903
+ }
904
+ function inferNodeType(relativePath, frontmatter) {
905
+ const normalized = normalizeRelativePath(relativePath).toLowerCase();
906
+ const category = normalized.split("/")[0] ?? "note";
907
+ const explicitType = typeof frontmatter.type === "string" ? frontmatter.type.toLowerCase() : "";
908
+ if (category.includes("daily") || explicitType === "daily") return "daily";
909
+ if (category === "observations" || explicitType === "observation") return "observation";
910
+ if (category === "handoffs" || explicitType === "handoff") return "handoff";
911
+ if (category === "decisions" || explicitType === "decision") return "decision";
912
+ if (category === "lessons" || explicitType === "lesson") return "lesson";
913
+ if (category === "projects" || explicitType === "project") return "project";
914
+ if (category === "people" || explicitType === "person") return "person";
915
+ if (category === "commitments" || explicitType === "commitment") return "commitment";
916
+ return "note";
917
+ }
918
+ function ensureClawvaultDir(vaultPath) {
919
+ const dirPath = path2.join(vaultPath, ".clawvault");
920
+ if (!fs2.existsSync(dirPath)) {
921
+ fs2.mkdirSync(dirPath, { recursive: true });
922
+ }
923
+ return dirPath;
924
+ }
925
+ function getGraphIndexPath(vaultPath) {
926
+ return path2.join(vaultPath, GRAPH_INDEX_RELATIVE_PATH);
927
+ }
928
+ function normalizeWikiTarget(target) {
929
+ let value = target.trim();
930
+ if (!value) return "";
931
+ const pipeIndex = value.indexOf("|");
932
+ if (pipeIndex >= 0) {
933
+ value = value.slice(0, pipeIndex);
934
+ }
935
+ const hashIndex = value.indexOf("#");
936
+ if (hashIndex >= 0) {
937
+ value = value.slice(0, hashIndex);
938
+ }
939
+ value = value.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
940
+ if (value.toLowerCase().endsWith(".md")) {
941
+ value = value.slice(0, -3);
942
+ }
943
+ return value.trim();
944
+ }
945
+ function collectTags(frontmatter, markdownContent) {
946
+ const tags = /* @__PURE__ */ new Set();
947
+ const fmTags = frontmatter.tags;
948
+ if (Array.isArray(fmTags)) {
949
+ for (const tag of fmTags) {
950
+ if (typeof tag === "string" && tag.trim()) tags.add(tag.trim().toLowerCase());
951
+ }
952
+ } else if (typeof fmTags === "string") {
953
+ for (const token of fmTags.split(",")) {
954
+ const normalized = token.trim().toLowerCase();
955
+ if (normalized) tags.add(normalized);
956
+ }
957
+ }
958
+ const markdownMatches = markdownContent.matchAll(HASH_TAG_RE);
959
+ for (const match of markdownMatches) {
960
+ const tag = match[2]?.trim().toLowerCase();
961
+ if (tag) tags.add(tag);
962
+ }
963
+ return [...tags].sort((a, b) => a.localeCompare(b));
964
+ }
965
+ function extractWikiTargets(markdownContent) {
966
+ const targets = /* @__PURE__ */ new Set();
967
+ for (const match of markdownContent.matchAll(WIKI_LINK_RE)) {
968
+ const candidate = match[1];
969
+ if (!candidate) continue;
970
+ const normalized = normalizeWikiTarget(candidate);
971
+ if (normalized) targets.add(normalized);
972
+ }
973
+ return [...targets];
974
+ }
975
+ function toStringArray(value) {
976
+ if (typeof value === "string") {
977
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
978
+ }
979
+ if (Array.isArray(value)) {
980
+ return value.flatMap((entry) => typeof entry === "string" ? entry.split(",") : []).map((entry) => entry.trim()).filter(Boolean);
981
+ }
982
+ return [];
983
+ }
984
+ function extractFrontmatterRelations(frontmatter) {
985
+ const relations = [];
986
+ for (const field of FRONTMATTER_RELATION_FIELDS) {
987
+ const raw = frontmatter[field];
988
+ for (const value of toStringArray(raw)) {
989
+ const normalized = normalizeWikiTarget(value);
990
+ if (normalized) relations.push({ field, target: normalized });
991
+ }
992
+ }
993
+ return relations;
994
+ }
995
+ function buildNoteRegistry(relativePaths) {
996
+ const byLowerPath = /* @__PURE__ */ new Map();
997
+ const byLowerBasename = /* @__PURE__ */ new Map();
998
+ for (const relativePath of relativePaths) {
999
+ const noteKey = toNoteKey(relativePath);
1000
+ const lowerKey = noteKey.toLowerCase();
1001
+ if (!byLowerPath.has(lowerKey)) {
1002
+ byLowerPath.set(lowerKey, noteKey);
1003
+ }
1004
+ const base = noteKey.split("/").pop() ?? noteKey;
1005
+ const lowerBase = base.toLowerCase();
1006
+ const existing = byLowerBasename.get(lowerBase) ?? [];
1007
+ existing.push(noteKey);
1008
+ byLowerBasename.set(lowerBase, existing);
1009
+ }
1010
+ return { byLowerPath, byLowerBasename };
1011
+ }
1012
+ function resolveTargetNodeId(rawTarget, registry) {
1013
+ const normalized = normalizeWikiTarget(rawTarget);
1014
+ if (!normalized) {
1015
+ return toUnresolvedNodeId(rawTarget);
1016
+ }
1017
+ const lowerTarget = normalized.toLowerCase();
1018
+ const direct = registry.byLowerPath.get(lowerTarget);
1019
+ if (direct) {
1020
+ return toNoteNodeId(direct);
1021
+ }
1022
+ if (!normalized.includes("/")) {
1023
+ const basenameMatches = registry.byLowerBasename.get(lowerTarget) ?? [];
1024
+ if (basenameMatches.length === 1) {
1025
+ return toNoteNodeId(basenameMatches[0]);
1026
+ }
1027
+ }
1028
+ return toUnresolvedNodeId(normalized);
1029
+ }
1030
+ function createEdgeId(type, source, target, label) {
1031
+ const suffix = label ? `:${label}` : "";
1032
+ return `${type}:${source}->${target}${suffix}`;
1033
+ }
1034
+ function buildFragmentNode(id, title, type, category, pathValue, tags, missing, modifiedAt) {
1035
+ return {
1036
+ id,
1037
+ title,
1038
+ type,
1039
+ category,
1040
+ path: pathValue,
1041
+ tags,
1042
+ missing,
1043
+ degree: 0,
1044
+ modifiedAt
1045
+ };
1046
+ }
1047
+ function parseFileFragment(vaultPath, relativePath, mtimeMs, registry) {
1048
+ const absolutePath = path2.join(vaultPath, relativePath);
1049
+ const raw = fs2.readFileSync(absolutePath, "utf-8");
1050
+ const parsed = (0, import_gray_matter.default)(raw);
1051
+ const frontmatter = parsed.data ?? {};
1052
+ const noteKey = toNoteKey(relativePath);
1053
+ const noteNodeId = toNoteNodeId(noteKey);
1054
+ const noteType = inferNodeType(relativePath, frontmatter);
1055
+ const tags = collectTags(frontmatter, parsed.content);
1056
+ const modifiedAt = new Date(mtimeMs).toISOString();
1057
+ const nodes = /* @__PURE__ */ new Map();
1058
+ const edges = /* @__PURE__ */ new Map();
1059
+ nodes.set(
1060
+ noteNodeId,
1061
+ buildFragmentNode(
1062
+ noteNodeId,
1063
+ typeof frontmatter.title === "string" && frontmatter.title.trim() ? frontmatter.title.trim() : titleFromNoteKey(noteKey),
1064
+ noteType,
1065
+ noteType,
1066
+ normalizeRelativePath(relativePath),
1067
+ tags,
1068
+ false,
1069
+ modifiedAt
1070
+ )
1071
+ );
1072
+ for (const tag of tags) {
1073
+ const tagNodeId = toTagNodeId(tag);
1074
+ if (!nodes.has(tagNodeId)) {
1075
+ nodes.set(tagNodeId, buildFragmentNode(tagNodeId, `#${tag}`, "tag", "tag", null, [], false, null));
1076
+ }
1077
+ const edgeId = createEdgeId("tag", noteNodeId, tagNodeId);
1078
+ edges.set(edgeId, {
1079
+ id: edgeId,
1080
+ source: noteNodeId,
1081
+ target: tagNodeId,
1082
+ type: "tag"
1083
+ });
1084
+ }
1085
+ const wikiTargets = extractWikiTargets(parsed.content);
1086
+ for (const target of wikiTargets) {
1087
+ const targetNodeId = resolveTargetNodeId(target, registry);
1088
+ if (targetNodeId.startsWith("unresolved:") && !nodes.has(targetNodeId)) {
1089
+ nodes.set(
1090
+ targetNodeId,
1091
+ buildFragmentNode(targetNodeId, titleFromNoteKey(normalizeUnresolvedKey(target)), "unresolved", "unresolved", null, [], true, null)
1092
+ );
1093
+ }
1094
+ const edgeId = createEdgeId("wiki_link", noteNodeId, targetNodeId);
1095
+ edges.set(edgeId, {
1096
+ id: edgeId,
1097
+ source: noteNodeId,
1098
+ target: targetNodeId,
1099
+ type: "wiki_link"
1100
+ });
1101
+ }
1102
+ for (const relation of extractFrontmatterRelations(frontmatter)) {
1103
+ const targetNodeId = resolveTargetNodeId(relation.target, registry);
1104
+ if (targetNodeId.startsWith("unresolved:") && !nodes.has(targetNodeId)) {
1105
+ nodes.set(
1106
+ targetNodeId,
1107
+ buildFragmentNode(
1108
+ targetNodeId,
1109
+ titleFromNoteKey(normalizeUnresolvedKey(relation.target)),
1110
+ "unresolved",
1111
+ "unresolved",
1112
+ null,
1113
+ [],
1114
+ true,
1115
+ null
1116
+ )
1117
+ );
1118
+ }
1119
+ const edgeId = createEdgeId("frontmatter_relation", noteNodeId, targetNodeId, relation.field);
1120
+ edges.set(edgeId, {
1121
+ id: edgeId,
1122
+ source: noteNodeId,
1123
+ target: targetNodeId,
1124
+ type: "frontmatter_relation",
1125
+ label: relation.field
1126
+ });
1127
+ }
1128
+ return {
1129
+ relativePath: normalizeRelativePath(relativePath),
1130
+ mtimeMs,
1131
+ nodes: [...nodes.values()],
1132
+ edges: [...edges.values()]
1133
+ };
1134
+ }
1135
+ function combineFragments(fragments, generatedAt) {
1136
+ const nodes = /* @__PURE__ */ new Map();
1137
+ const edges = /* @__PURE__ */ new Map();
1138
+ for (const fragment of Object.values(fragments)) {
1139
+ for (const node of fragment.nodes) {
1140
+ const existing = nodes.get(node.id);
1141
+ if (!existing) {
1142
+ nodes.set(node.id, { ...node, degree: 0 });
1143
+ } else if (node.modifiedAt && (!existing.modifiedAt || node.modifiedAt > existing.modifiedAt)) {
1144
+ nodes.set(node.id, { ...existing, ...node, degree: 0 });
1145
+ }
1146
+ }
1147
+ for (const edge of fragment.edges) {
1148
+ edges.set(edge.id, edge);
1149
+ }
1150
+ }
1151
+ const degreeByNode = /* @__PURE__ */ new Map();
1152
+ for (const edge of edges.values()) {
1153
+ degreeByNode.set(edge.source, (degreeByNode.get(edge.source) ?? 0) + 1);
1154
+ degreeByNode.set(edge.target, (degreeByNode.get(edge.target) ?? 0) + 1);
1155
+ }
1156
+ for (const node of nodes.values()) {
1157
+ node.degree = degreeByNode.get(node.id) ?? 0;
1158
+ }
1159
+ const nodeTypeCounts = {};
1160
+ for (const node of nodes.values()) {
1161
+ nodeTypeCounts[node.type] = (nodeTypeCounts[node.type] ?? 0) + 1;
1162
+ }
1163
+ const edgeTypeCounts = {};
1164
+ for (const edge of edges.values()) {
1165
+ edgeTypeCounts[edge.type] = (edgeTypeCounts[edge.type] ?? 0) + 1;
1166
+ }
1167
+ const sortedNodes = [...nodes.values()].sort((a, b) => a.id.localeCompare(b.id));
1168
+ const sortedEdges = [...edges.values()].sort((a, b) => a.id.localeCompare(b.id));
1169
+ return {
1170
+ schemaVersion: MEMORY_GRAPH_SCHEMA_VERSION,
1171
+ nodes: sortedNodes,
1172
+ edges: sortedEdges,
1173
+ stats: {
1174
+ generatedAt,
1175
+ nodeCount: sortedNodes.length,
1176
+ edgeCount: sortedEdges.length,
1177
+ nodeTypeCounts,
1178
+ edgeTypeCounts
1179
+ }
1180
+ };
1181
+ }
1182
+ function isValidIndex(index) {
1183
+ if (!index || typeof index !== "object") return false;
1184
+ const typed = index;
1185
+ 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");
1186
+ }
1187
+ function loadMemoryGraphIndex(vaultPath) {
1188
+ const indexPath = getGraphIndexPath(path2.resolve(vaultPath));
1189
+ if (!fs2.existsSync(indexPath)) {
1190
+ return null;
1191
+ }
1192
+ try {
1193
+ const parsed = JSON.parse(fs2.readFileSync(indexPath, "utf-8"));
1194
+ if (!isValidIndex(parsed)) {
1195
+ return null;
1196
+ }
1197
+ return parsed;
1198
+ } catch {
1199
+ return null;
1200
+ }
1201
+ }
1202
+ async function buildOrUpdateMemoryGraphIndex(vaultPathInput, options = {}) {
1203
+ const vaultPath = path2.resolve(vaultPathInput);
1204
+ ensureClawvaultDir(vaultPath);
1205
+ const existing = options.forceFull ? null : loadMemoryGraphIndex(vaultPath);
1206
+ const markdownFiles = await (0, import_glob.glob)("**/*.md", {
1207
+ cwd: vaultPath,
1208
+ ignore: ["**/node_modules/**", "**/.git/**", "**/.obsidian/**", "**/.trash/**", "**/ledger/archive/**"]
1209
+ });
1210
+ const normalizedFiles = markdownFiles.map(normalizeRelativePath).sort((a, b) => a.localeCompare(b));
1211
+ const registry = buildNoteRegistry(normalizedFiles);
1212
+ const nextFragments = {};
1213
+ const existingFragments = existing?.files ?? {};
1214
+ const currentFileSet = new Set(normalizedFiles);
1215
+ for (const relativePath of normalizedFiles) {
1216
+ const absolutePath = path2.join(vaultPath, relativePath);
1217
+ const stat = fs2.statSync(absolutePath);
1218
+ const existingFragment = existingFragments[relativePath];
1219
+ if (!options.forceFull && existingFragment && existingFragment.mtimeMs === stat.mtimeMs) {
1220
+ nextFragments[relativePath] = existingFragment;
1221
+ continue;
1222
+ }
1223
+ nextFragments[relativePath] = parseFileFragment(vaultPath, relativePath, stat.mtimeMs, registry);
1224
+ }
1225
+ for (const [relativePath, fragment] of Object.entries(existingFragments)) {
1226
+ if (!currentFileSet.has(relativePath)) {
1227
+ continue;
1228
+ }
1229
+ if (!nextFragments[relativePath]) {
1230
+ nextFragments[relativePath] = fragment;
1231
+ }
1232
+ }
1233
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
1234
+ const graph = combineFragments(nextFragments, generatedAt);
1235
+ const nextIndex = {
1236
+ schemaVersion: MEMORY_GRAPH_SCHEMA_VERSION,
1237
+ vaultPath,
1238
+ generatedAt,
1239
+ files: nextFragments,
1240
+ graph
1241
+ };
1242
+ fs2.writeFileSync(getGraphIndexPath(vaultPath), JSON.stringify(nextIndex, null, 2));
1243
+ return nextIndex;
1244
+ }
1245
+
1246
+ // src/lib/vault.ts
1247
+ var import_meta = {};
1248
+ var CONFIG_FILE = ".clawvault.json";
1249
+ var INDEX_FILE = ".clawvault-index.json";
1250
+ var ClawVault = class {
1251
+ config;
1252
+ search;
1253
+ initialized = false;
1254
+ constructor(vaultPath) {
1255
+ if (!hasQmd()) {
1256
+ throw new QmdUnavailableError();
1257
+ }
1258
+ this.config = {
1259
+ path: path3.resolve(vaultPath),
1260
+ name: path3.basename(vaultPath),
1261
+ categories: DEFAULT_CATEGORIES,
1262
+ qmdCollection: void 0,
1263
+ qmdRoot: void 0
1264
+ };
1265
+ this.search = new SearchEngine();
1266
+ this.applyQmdConfig();
1267
+ }
1268
+ /**
1269
+ * Initialize a new vault
1270
+ */
1271
+ async init(options = {}, initFlags) {
1272
+ if (!hasQmd()) {
1273
+ throw new QmdUnavailableError();
1274
+ }
1275
+ const vaultPath = this.config.path;
1276
+ const flags = initFlags || {};
1277
+ this.config = { ...this.config, ...options };
1278
+ this.applyQmdConfig();
1279
+ if (flags.skipTasks) {
1280
+ this.config.categories = this.config.categories.filter(
1281
+ (c) => !["tasks", "backlog"].includes(c)
1282
+ );
1283
+ }
1284
+ if (!fs3.existsSync(vaultPath)) {
1285
+ fs3.mkdirSync(vaultPath, { recursive: true });
1286
+ }
1287
+ for (const category of this.config.categories) {
1288
+ const catPath = path3.join(vaultPath, category);
1289
+ if (!fs3.existsSync(catPath)) {
1290
+ fs3.mkdirSync(catPath, { recursive: true });
1291
+ }
1292
+ }
1293
+ const ledgerDirs = ["ledger/raw", "ledger/observations", "ledger/reflections"];
1294
+ for (const dir of ledgerDirs) {
1295
+ const dirPath = path3.join(vaultPath, dir);
1296
+ if (!fs3.existsSync(dirPath)) {
1297
+ fs3.mkdirSync(dirPath, { recursive: true });
1298
+ }
1299
+ }
1300
+ await this.createTemplates();
1301
+ const readmePath = path3.join(vaultPath, "README.md");
1302
+ if (!fs3.existsSync(readmePath)) {
1303
+ fs3.writeFileSync(readmePath, this.generateReadme());
1304
+ }
1305
+ await this.createWelcomeNote();
1306
+ const configPath = path3.join(vaultPath, CONFIG_FILE);
1307
+ const meta = {
1308
+ name: this.config.name,
1309
+ version: "1.0.0",
1310
+ created: (/* @__PURE__ */ new Date()).toISOString(),
1311
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
1312
+ categories: this.config.categories,
1313
+ documentCount: 0,
1314
+ qmdCollection: this.getQmdCollection(),
1315
+ qmdRoot: this.getQmdRoot()
1316
+ };
1317
+ fs3.writeFileSync(configPath, JSON.stringify(meta, null, 2));
1318
+ if (!flags.skipBases && this.config.categories.includes("tasks")) {
1319
+ this.createBasesFiles();
1320
+ }
1321
+ if (!flags.skipGraph) {
1322
+ await this.syncMemoryGraphIndex({ forceFull: true });
1323
+ }
1324
+ this.initialized = true;
1325
+ }
1326
+ createBasesFiles() {
1327
+ const vaultPath = this.config.path;
1328
+ const basesFiles = {
1329
+ "all-tasks.base": [
1330
+ "filters:",
1331
+ " and:",
1332
+ ' - file.inFolder("tasks")',
1333
+ ' - status != "done"',
1334
+ "formulas:",
1335
+ " age: (now() - file.ctime).days",
1336
+ ' status_icon: if(status == "blocked", "\u{1F534}", if(status == "in-progress", "\u{1F528}", if(status == "open", "\u26AA", "\u2705")))',
1337
+ "views:",
1338
+ " - type: table",
1339
+ " name: All Active Tasks",
1340
+ " groupBy:",
1341
+ " property: status",
1342
+ " direction: ASC",
1343
+ " order:",
1344
+ " - formula.status_icon",
1345
+ " - file.name",
1346
+ " - status",
1347
+ " - owner",
1348
+ " - project",
1349
+ " - priority",
1350
+ " - blocked_by",
1351
+ " - formula.age",
1352
+ " - type: cards",
1353
+ " name: Task Board",
1354
+ " groupBy:",
1355
+ " property: status",
1356
+ " direction: ASC",
1357
+ " order:",
1358
+ " - file.name",
1359
+ " - owner",
1360
+ " - project",
1361
+ " - priority"
1362
+ ].join("\n"),
1363
+ "blocked.base": [
1364
+ "filters:",
1365
+ " and:",
1366
+ ' - file.inFolder("tasks")',
1367
+ ' - status == "blocked"',
1368
+ "formulas:",
1369
+ " days_blocked: (now() - file.ctime).days",
1370
+ "views:",
1371
+ " - type: table",
1372
+ " name: Blocked Tasks",
1373
+ " order:",
1374
+ " - file.name",
1375
+ " - owner",
1376
+ " - project",
1377
+ " - blocked_by",
1378
+ " - formula.days_blocked",
1379
+ " - priority"
1380
+ ].join("\n"),
1381
+ "by-project.base": [
1382
+ "filters:",
1383
+ " and:",
1384
+ ' - file.inFolder("tasks")',
1385
+ ' - status != "done"',
1386
+ "formulas:",
1387
+ ' status_icon: if(status == "blocked", "\u{1F534}", if(status == "in-progress", "\u{1F528}", "\u26AA"))',
1388
+ "views:",
1389
+ " - type: table",
1390
+ " name: By Project",
1391
+ " groupBy:",
1392
+ " property: project",
1393
+ " direction: ASC",
1394
+ " order:",
1395
+ " - formula.status_icon",
1396
+ " - file.name",
1397
+ " - status",
1398
+ " - owner",
1399
+ " - priority"
1400
+ ].join("\n"),
1401
+ "backlog.base": [
1402
+ "filters:",
1403
+ " and:",
1404
+ ' - file.inFolder("backlog")',
1405
+ "views:",
1406
+ " - type: table",
1407
+ " name: Backlog",
1408
+ " order:",
1409
+ " - file.name",
1410
+ " - source",
1411
+ " - project",
1412
+ " - file.ctime"
1413
+ ].join("\n")
1414
+ };
1415
+ for (const [filename, content] of Object.entries(basesFiles)) {
1416
+ const filePath = path3.join(vaultPath, filename);
1417
+ if (!fs3.existsSync(filePath)) {
1418
+ fs3.writeFileSync(filePath, content);
1419
+ }
1420
+ }
1421
+ }
1422
+ /**
1423
+ * Load an existing vault
1424
+ */
1425
+ async load() {
1426
+ if (!hasQmd()) {
1427
+ throw new QmdUnavailableError();
1428
+ }
1429
+ const vaultPath = this.config.path;
1430
+ const configPath = path3.join(vaultPath, CONFIG_FILE);
1431
+ if (!fs3.existsSync(configPath)) {
1432
+ throw new Error(`Not a ClawVault: ${vaultPath} (missing ${CONFIG_FILE})`);
1433
+ }
1434
+ const meta = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
1435
+ this.config.name = meta.name;
1436
+ this.config.categories = meta.categories;
1437
+ this.config.qmdCollection = meta.qmdCollection;
1438
+ this.config.qmdRoot = meta.qmdRoot;
1439
+ if (!meta.qmdCollection || !meta.qmdRoot) {
1440
+ meta.qmdCollection = meta.qmdCollection || meta.name;
1441
+ meta.qmdRoot = meta.qmdRoot || this.config.path;
1442
+ fs3.writeFileSync(configPath, JSON.stringify(meta, null, 2));
1443
+ }
1444
+ this.applyQmdConfig(meta);
1445
+ await this.reindex();
1446
+ this.initialized = true;
1447
+ }
1448
+ /**
1449
+ * Reindex all documents
1450
+ */
1451
+ async reindex() {
1452
+ this.search.clear();
1453
+ const files = await (0, import_glob2.glob)("**/*.md", {
1454
+ cwd: this.config.path,
1455
+ ignore: ["**/node_modules/**", "**/.*", "**/ledger/archive/**"]
1456
+ });
1457
+ for (const file of files) {
1458
+ const doc = await this.loadDocument(file);
1459
+ if (doc) {
1460
+ this.search.addDocument(doc);
1461
+ }
1462
+ }
1463
+ await this.saveIndex();
1464
+ await this.syncMemoryGraphIndex();
1465
+ return this.search.size;
1466
+ }
1467
+ /**
1468
+ * Load a document from disk
1469
+ */
1470
+ async loadDocument(relativePath) {
1471
+ try {
1472
+ const fullPath = path3.join(this.config.path, relativePath);
1473
+ const content = fs3.readFileSync(fullPath, "utf-8");
1474
+ const { data: frontmatter, content: body } = (0, import_gray_matter2.default)(content);
1475
+ const stats = fs3.statSync(fullPath);
1476
+ const parts = relativePath.split(path3.sep);
1477
+ const category = parts.length > 1 ? parts[0] : "root";
1478
+ const filename = path3.basename(relativePath, ".md");
1479
+ return {
1480
+ id: relativePath.replace(/\.md$/, ""),
1481
+ path: fullPath,
1482
+ category,
1483
+ title: frontmatter.title || filename,
1484
+ content: body,
1485
+ frontmatter,
1486
+ links: extractWikiLinks(body),
1487
+ tags: extractTags(body),
1488
+ modified: stats.mtime
1489
+ };
1490
+ } catch (err) {
1491
+ console.error(`Error loading ${relativePath}:`, err);
1492
+ return null;
1493
+ }
1494
+ }
1495
+ /**
1496
+ * Store a new document
1497
+ */
1498
+ async store(options) {
1499
+ const {
1500
+ category,
1501
+ title,
1502
+ content,
1503
+ frontmatter = {},
1504
+ overwrite = false,
1505
+ qmdUpdate: triggerUpdate = false,
1506
+ qmdEmbed: triggerEmbed = false,
1507
+ qmdIndexName
1508
+ } = options;
1509
+ const filename = this.slugify(title) + ".md";
1510
+ const relativePath = path3.join(category, filename);
1511
+ const fullPath = path3.join(this.config.path, relativePath);
1512
+ if (fs3.existsSync(fullPath) && !overwrite) {
1513
+ throw new Error(`Document already exists: ${relativePath}. Use overwrite: true to replace.`);
1514
+ }
1515
+ const categoryPath = path3.join(this.config.path, category);
1516
+ if (!fs3.existsSync(categoryPath)) {
1517
+ fs3.mkdirSync(categoryPath, { recursive: true });
1518
+ }
1519
+ const fm = {
1520
+ title,
1521
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
1522
+ ...frontmatter
1523
+ };
1524
+ const fileContent = import_gray_matter2.default.stringify(content, fm);
1525
+ fs3.writeFileSync(fullPath, fileContent);
1526
+ const doc = await this.loadDocument(relativePath);
1527
+ if (doc) {
1528
+ this.search.addDocument(doc);
1529
+ await this.saveIndex();
1530
+ await this.syncMemoryGraphIndex();
1531
+ }
1532
+ if (triggerUpdate || triggerEmbed) {
1533
+ qmdUpdate(this.getQmdCollection(), qmdIndexName);
1534
+ if (triggerEmbed) {
1535
+ qmdEmbed(this.getQmdCollection(), qmdIndexName);
1536
+ }
1537
+ }
1538
+ return doc;
1539
+ }
1540
+ /**
1541
+ * Quick store to inbox
1542
+ */
1543
+ async capture(note, title) {
1544
+ const autoTitle = title || `note-${Date.now()}`;
1545
+ return this.store({
1546
+ category: "inbox",
1547
+ title: autoTitle,
1548
+ content: note
1549
+ });
1550
+ }
1551
+ /**
1552
+ * Search the vault (BM25 via qmd)
1553
+ */
1554
+ async find(query, options = {}) {
1555
+ return this.search.search(query, options);
1556
+ }
1557
+ /**
1558
+ * Semantic/vector search (via qmd vsearch)
1559
+ */
1560
+ async vsearch(query, options = {}) {
1561
+ return this.search.vsearch(query, options);
1562
+ }
1563
+ /**
1564
+ * Combined search with query expansion (via qmd query)
1565
+ */
1566
+ async query(query, options = {}) {
1567
+ return this.search.query(query, options);
1568
+ }
1569
+ /**
1570
+ * Get a document by ID or path
1571
+ */
1572
+ async get(idOrPath) {
1573
+ const normalized = idOrPath.replace(/\.md$/, "");
1574
+ const docs = this.search.getAllDocuments();
1575
+ return docs.find((d) => d.id === normalized) || null;
1576
+ }
1577
+ /**
1578
+ * List documents in a category
1579
+ */
1580
+ async list(category) {
1581
+ const docs = this.search.getAllDocuments();
1582
+ if (category) {
1583
+ return docs.filter((d) => d.category === category);
1584
+ }
1585
+ return docs;
1586
+ }
1587
+ /**
1588
+ * Sync vault to another location (for Obsidian on Windows, etc.)
1589
+ */
1590
+ async sync(options) {
1591
+ const { target, deleteOrphans = false, dryRun = false } = options;
1592
+ const result = {
1593
+ copied: [],
1594
+ deleted: [],
1595
+ unchanged: [],
1596
+ errors: []
1597
+ };
1598
+ const sourceFiles = await (0, import_glob2.glob)("**/*.md", {
1599
+ cwd: this.config.path,
1600
+ ignore: ["**/node_modules/**"]
1601
+ });
1602
+ if (!dryRun && !fs3.existsSync(target)) {
1603
+ fs3.mkdirSync(target, { recursive: true });
1604
+ }
1605
+ for (const file of sourceFiles) {
1606
+ const sourcePath = path3.join(this.config.path, file);
1607
+ const targetPath = path3.join(target, file);
1608
+ try {
1609
+ const sourceStats = fs3.statSync(sourcePath);
1610
+ let shouldCopy = true;
1611
+ if (fs3.existsSync(targetPath)) {
1612
+ const targetStats = fs3.statSync(targetPath);
1613
+ if (sourceStats.mtime <= targetStats.mtime) {
1614
+ result.unchanged.push(file);
1615
+ shouldCopy = false;
1616
+ }
1617
+ }
1618
+ if (shouldCopy) {
1619
+ if (!dryRun) {
1620
+ const targetDir = path3.dirname(targetPath);
1621
+ if (!fs3.existsSync(targetDir)) {
1622
+ fs3.mkdirSync(targetDir, { recursive: true });
1623
+ }
1624
+ fs3.copyFileSync(sourcePath, targetPath);
1625
+ }
1626
+ result.copied.push(file);
1627
+ }
1628
+ } catch (err) {
1629
+ result.errors.push(`${file}: ${err}`);
1630
+ }
1631
+ }
1632
+ if (deleteOrphans) {
1633
+ const targetFiles = await (0, import_glob2.glob)("**/*.md", { cwd: target });
1634
+ const sourceSet = new Set(sourceFiles);
1635
+ for (const file of targetFiles) {
1636
+ if (!sourceSet.has(file)) {
1637
+ if (!dryRun) {
1638
+ fs3.unlinkSync(path3.join(target, file));
1639
+ }
1640
+ result.deleted.push(file);
1641
+ }
1642
+ }
1643
+ }
1644
+ return result;
1645
+ }
1646
+ /**
1647
+ * Get vault statistics
1648
+ */
1649
+ async stats() {
1650
+ const docs = this.search.getAllDocuments();
1651
+ const categories = {};
1652
+ const allTags = /* @__PURE__ */ new Set();
1653
+ let totalLinks = 0;
1654
+ for (const doc of docs) {
1655
+ categories[doc.category] = (categories[doc.category] || 0) + 1;
1656
+ totalLinks += doc.links.length;
1657
+ doc.tags.forEach((t) => allTags.add(t));
1658
+ }
1659
+ return {
1660
+ documents: docs.length,
1661
+ categories,
1662
+ links: totalLinks,
1663
+ tags: [...allTags].sort()
1664
+ };
1665
+ }
1666
+ /**
1667
+ * Get all categories
1668
+ */
1669
+ getCategories() {
1670
+ return this.config.categories;
1671
+ }
1672
+ /**
1673
+ * Check if vault is initialized
1674
+ */
1675
+ isInitialized() {
1676
+ return this.initialized;
1677
+ }
1678
+ /**
1679
+ * Get vault path
1680
+ */
1681
+ getPath() {
1682
+ return this.config.path;
1683
+ }
1684
+ /**
1685
+ * Get vault name
1686
+ */
1687
+ getName() {
1688
+ return this.config.name;
1689
+ }
1690
+ /**
1691
+ * Get qmd collection name
1692
+ */
1693
+ getQmdCollection() {
1694
+ return this.config.qmdCollection || this.config.name;
1695
+ }
1696
+ /**
1697
+ * Get qmd collection root
1698
+ */
1699
+ getQmdRoot() {
1700
+ return this.config.qmdRoot || this.config.path;
1701
+ }
1702
+ // === Memory Type System ===
1703
+ /**
1704
+ * Store a memory with type classification
1705
+ * Automatically routes to correct category based on type
1706
+ */
1707
+ async remember(type, title, content, frontmatter = {}) {
1708
+ const category = TYPE_TO_CATEGORY[type];
1709
+ return this.store({
1710
+ category,
1711
+ title,
1712
+ content,
1713
+ frontmatter: { ...frontmatter, memoryType: type }
1714
+ });
1715
+ }
1716
+ // === Handoff System ===
1717
+ /**
1718
+ * Create a session handoff document
1719
+ * Call this before context death or long pauses
1720
+ */
1721
+ async createHandoff(handoff) {
1722
+ const now = /* @__PURE__ */ new Date();
1723
+ const dateStr = now.toISOString().split("T")[0];
1724
+ const timeStr = now.toISOString().split("T")[1].slice(0, 5).replace(":", "");
1725
+ const fullHandoff = {
1726
+ ...handoff,
1727
+ created: now.toISOString()
1728
+ };
1729
+ const content = this.formatHandoff(fullHandoff);
1730
+ const frontmatter = {
1731
+ type: "handoff",
1732
+ workingOn: handoff.workingOn,
1733
+ blocked: handoff.blocked,
1734
+ nextSteps: handoff.nextSteps
1735
+ };
1736
+ if (handoff.sessionKey) frontmatter.sessionKey = handoff.sessionKey;
1737
+ if (handoff.feeling) frontmatter.feeling = handoff.feeling;
1738
+ if (handoff.decisions) frontmatter.decisions = handoff.decisions;
1739
+ if (handoff.openQuestions) frontmatter.openQuestions = handoff.openQuestions;
1740
+ return this.store({
1741
+ category: "handoffs",
1742
+ title: `handoff-${dateStr}-${timeStr}`,
1743
+ content,
1744
+ frontmatter
1745
+ });
1746
+ }
1747
+ /**
1748
+ * Format handoff as readable markdown
1749
+ */
1750
+ formatHandoff(h) {
1751
+ let md = `# Session Handoff
1752
+
1753
+ `;
1754
+ md += `**Created:** ${h.created}
1755
+ `;
1756
+ if (h.sessionKey) md += `**Session:** ${h.sessionKey}
1757
+ `;
1758
+ if (h.feeling) md += `**Feeling:** ${h.feeling}
1759
+ `;
1760
+ md += `
1761
+ `;
1762
+ md += `## Working On
1763
+ `;
1764
+ h.workingOn.forEach((w) => md += `- ${w}
1765
+ `);
1766
+ md += `
1767
+ `;
1768
+ md += `## Blocked
1769
+ `;
1770
+ if (h.blocked.length === 0) md += `- Nothing currently blocked
1771
+ `;
1772
+ else h.blocked.forEach((b) => md += `- ${b}
1773
+ `);
1774
+ md += `
1775
+ `;
1776
+ md += `## Next Steps
1777
+ `;
1778
+ h.nextSteps.forEach((n) => md += `- ${n}
1779
+ `);
1780
+ if (h.decisions && h.decisions.length > 0) {
1781
+ md += `
1782
+ ## Decisions Made
1783
+ `;
1784
+ h.decisions.forEach((d) => md += `- ${d}
1785
+ `);
1786
+ }
1787
+ if (h.openQuestions && h.openQuestions.length > 0) {
1788
+ md += `
1789
+ ## Open Questions
1790
+ `;
1791
+ h.openQuestions.forEach((q) => md += `- ${q}
1792
+ `);
1793
+ }
1794
+ return md;
1795
+ }
1796
+ // === Session Recap (Bootstrap Hook) ===
1797
+ /**
1798
+ * Generate a session recap - who I was
1799
+ * Call this on bootstrap to restore context
1800
+ */
1801
+ async generateRecap(options = {}) {
1802
+ const { handoffLimit = 3, brief = false } = options;
1803
+ const handoffDocs = await this.list("handoffs");
1804
+ const recentHandoffs = handoffDocs.sort((a, b) => b.modified.getTime() - a.modified.getTime()).slice(0, handoffLimit).map((doc) => this.parseHandoff(doc));
1805
+ const projectDocs = await this.list("projects");
1806
+ const activeProjects = projectDocs.filter((d) => d.frontmatter.status !== "completed" && d.frontmatter.status !== "archived").map((d) => d.title);
1807
+ const commitmentDocs = await this.list("commitments");
1808
+ const pendingCommitments = commitmentDocs.filter((d) => d.frontmatter.status !== "done").map((d) => d.title);
1809
+ const decisionDocs = await this.list("decisions");
1810
+ const recentDecisions = decisionDocs.sort((a, b) => b.modified.getTime() - a.modified.getTime()).slice(0, brief ? 3 : 5).map((d) => d.title);
1811
+ const lessonDocs = await this.list("lessons");
1812
+ const recentLessons = lessonDocs.sort((a, b) => b.modified.getTime() - a.modified.getTime()).slice(0, brief ? 3 : 5).map((d) => d.title);
1813
+ let keyRelationships = [];
1814
+ if (!brief) {
1815
+ const peopleDocs = await this.list("people");
1816
+ keyRelationships = peopleDocs.filter((d) => d.frontmatter.importance === "high" || d.frontmatter.role).map((d) => `${d.title}${d.frontmatter.role ? ` (${d.frontmatter.role})` : ""}`);
1817
+ }
1818
+ const feelings = recentHandoffs.map((h) => h.feeling).filter(Boolean);
1819
+ const emotionalArc = feelings.length > 0 ? feelings.join(" \u2192 ") : void 0;
1820
+ return {
1821
+ generated: (/* @__PURE__ */ new Date()).toISOString(),
1822
+ recentHandoffs,
1823
+ activeProjects,
1824
+ pendingCommitments,
1825
+ recentDecisions,
1826
+ recentLessons,
1827
+ keyRelationships,
1828
+ emotionalArc
1829
+ };
1830
+ }
1831
+ /**
1832
+ * Format recap as readable markdown for injection
1833
+ */
1834
+ formatRecap(recap, options = {}) {
1835
+ const { brief = false } = options;
1836
+ let md = `# Who I Was
1837
+
1838
+ `;
1839
+ md += `*Generated: ${recap.generated}*
1840
+
1841
+ `;
1842
+ if (recap.emotionalArc) {
1843
+ md += `**Emotional arc:** ${recap.emotionalArc}
1844
+
1845
+ `;
1846
+ }
1847
+ if (recap.recentHandoffs.length > 0) {
1848
+ md += `## Recent Sessions
1849
+ `;
1850
+ for (const h of recap.recentHandoffs) {
1851
+ if (brief) {
1852
+ md += `- **${h.created.split("T")[0]}:** ${h.workingOn.slice(0, 2).join(", ")}`;
1853
+ if (h.nextSteps.length > 0) md += ` \u2192 ${h.nextSteps[0]}`;
1854
+ md += `
1855
+ `;
1856
+ } else {
1857
+ md += `
1858
+ ### ${h.created.split("T")[0]}
1859
+ `;
1860
+ md += `**Working on:** ${h.workingOn.join(", ")}
1861
+ `;
1862
+ if (h.blocked.length > 0) md += `**Blocked:** ${h.blocked.join(", ")}
1863
+ `;
1864
+ md += `**Next:** ${h.nextSteps.join(", ")}
1865
+ `;
1866
+ }
1867
+ }
1868
+ md += `
1869
+ `;
1870
+ }
1871
+ if (recap.activeProjects.length > 0) {
1872
+ md += `## Active Projects
1873
+ `;
1874
+ recap.activeProjects.forEach((p) => md += `- ${p}
1875
+ `);
1876
+ md += `
1877
+ `;
1878
+ }
1879
+ if (recap.pendingCommitments.length > 0) {
1880
+ md += `## Pending Commitments
1881
+ `;
1882
+ recap.pendingCommitments.forEach((c) => md += `- ${c}
1883
+ `);
1884
+ md += `
1885
+ `;
1886
+ }
1887
+ if (recap.recentDecisions && recap.recentDecisions.length > 0) {
1888
+ md += `## Recent Decisions
1889
+ `;
1890
+ recap.recentDecisions.forEach((d) => md += `- ${d}
1891
+ `);
1892
+ md += `
1893
+ `;
1894
+ }
1895
+ if (recap.recentLessons.length > 0) {
1896
+ md += `## Recent Lessons
1897
+ `;
1898
+ recap.recentLessons.forEach((l) => md += `- ${l}
1899
+ `);
1900
+ md += `
1901
+ `;
1902
+ }
1903
+ if (!brief && recap.keyRelationships.length > 0) {
1904
+ md += `## Key People
1905
+ `;
1906
+ recap.keyRelationships.forEach((r) => md += `- ${r}
1907
+ `);
1908
+ }
1909
+ return md;
1910
+ }
1911
+ /**
1912
+ * Parse a handoff document back into structured form
1913
+ */
1914
+ parseHandoff(doc) {
1915
+ return {
1916
+ created: doc.frontmatter.date || doc.modified.toISOString(),
1917
+ sessionKey: doc.frontmatter.sessionKey,
1918
+ workingOn: doc.frontmatter.workingOn || [],
1919
+ blocked: doc.frontmatter.blocked || [],
1920
+ nextSteps: doc.frontmatter.nextSteps || [],
1921
+ decisions: doc.frontmatter.decisions,
1922
+ openQuestions: doc.frontmatter.openQuestions,
1923
+ feeling: doc.frontmatter.feeling
1924
+ };
1925
+ }
1926
+ // === Private helpers ===
1927
+ applyQmdConfig(meta) {
1928
+ const collection = meta?.qmdCollection || this.config.qmdCollection || this.config.name;
1929
+ const root = meta?.qmdRoot || this.config.qmdRoot || this.config.path;
1930
+ this.config.qmdCollection = collection;
1931
+ this.config.qmdRoot = root;
1932
+ this.search.setVaultPath(this.config.path);
1933
+ this.search.setCollection(collection);
1934
+ this.search.setCollectionRoot(root);
1935
+ }
1936
+ slugify(text) {
1937
+ return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").trim();
1938
+ }
1939
+ async saveIndex() {
1940
+ const indexPath = path3.join(this.config.path, INDEX_FILE);
1941
+ const data = this.search.export();
1942
+ fs3.writeFileSync(indexPath, JSON.stringify(data, null, 2));
1943
+ const configPath = path3.join(this.config.path, CONFIG_FILE);
1944
+ if (fs3.existsSync(configPath)) {
1945
+ const meta = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
1946
+ meta.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
1947
+ meta.documentCount = this.search.size;
1948
+ fs3.writeFileSync(configPath, JSON.stringify(meta, null, 2));
1949
+ }
1950
+ }
1951
+ async createTemplates() {
1952
+ const templatesPath = path3.join(this.config.path, "templates");
1953
+ if (!fs3.existsSync(templatesPath)) {
1954
+ fs3.mkdirSync(templatesPath, { recursive: true });
1955
+ }
1956
+ const moduleDir = path3.dirname((0, import_url.fileURLToPath)(import_meta.url));
1957
+ const candidates = [
1958
+ path3.resolve(moduleDir, "../templates"),
1959
+ path3.resolve(moduleDir, "../../templates")
1960
+ ];
1961
+ const builtinDir = candidates.find((dir) => fs3.existsSync(dir) && fs3.statSync(dir).isDirectory());
1962
+ if (!builtinDir) return;
1963
+ for (const entry of fs3.readdirSync(builtinDir, { withFileTypes: true })) {
1964
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1965
+ if (entry.name === "daily.md") continue;
1966
+ const sourcePath = path3.join(builtinDir, entry.name);
1967
+ const targetPath = path3.join(templatesPath, entry.name);
1968
+ if (!fs3.existsSync(targetPath)) {
1969
+ fs3.copyFileSync(sourcePath, targetPath);
1970
+ }
1971
+ }
1972
+ }
1973
+ async createWelcomeNote() {
1974
+ if (!this.config.categories.includes("inbox")) return;
1975
+ const inboxPath = path3.join(this.config.path, "inbox", "welcome.md");
1976
+ if (fs3.existsSync(inboxPath)) return;
1977
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1978
+ const content = `---
1979
+ title: "Welcome to ${this.config.name}"
1980
+ date: ${now}
1981
+ type: fact
1982
+ tags: [welcome, getting-started]
1983
+ ---
1984
+
1985
+ # Welcome to ${this.config.name}
1986
+
1987
+ Your vault is ready. Here's what you can do:
1988
+
1989
+ ## Quick Start
1990
+
1991
+ - **Capture a thought:** \`clawvault capture "your note here"\`
1992
+ - **Store structured memory:** \`clawvault store --category decisions --title "My Choice" --content "..."\`
1993
+ - **Search your vault:** \`clawvault search "query"\`
1994
+ - **See your knowledge graph:** \`clawvault graph\`
1995
+ - **Get context for a topic:** \`clawvault context "topic"\`
1996
+
1997
+ ## Vault Structure
1998
+
1999
+ Your vault organizes memories by type \u2014 decisions, lessons, people, projects, and more.
2000
+ Each category is a folder. Each memory is a markdown file with frontmatter.
2001
+
2002
+ ## Observational Memory
2003
+
2004
+ When connected to an AI agent (like OpenClaw), your vault can automatically observe
2005
+ conversations and extract important memories \u2014 decisions, lessons, commitments \u2014 without
2006
+ manual effort.
2007
+
2008
+ ## Wiki-Links
2009
+
2010
+ Use \`[[double brackets]]\` to link between notes. Your memory graph tracks these
2011
+ connections, building a knowledge network that grows with you.
2012
+
2013
+ ---
2014
+
2015
+ *Delete this file anytime. It's just here to say hello.*
2016
+ `;
2017
+ fs3.writeFileSync(inboxPath, content);
2018
+ }
2019
+ async syncMemoryGraphIndex(options = {}) {
2020
+ try {
2021
+ await buildOrUpdateMemoryGraphIndex(this.config.path, options);
2022
+ } catch {
2023
+ }
2024
+ }
2025
+ generateReadme() {
2026
+ const coreCategories = this.config.categories.filter((c) => !["templates", "tasks", "backlog"].includes(c));
2027
+ const workCategories = this.config.categories.filter((c) => ["tasks", "backlog"].includes(c));
2028
+ return `# ${this.config.name}
2029
+
2030
+ An elephant never forgets.
2031
+
2032
+ ## Structure
2033
+
2034
+ ### Memory Categories
2035
+ ${coreCategories.map((c) => `- \`${c}/\` \u2014 ${this.getCategoryDescription(c)}`).join("\n")}
2036
+
2037
+ ### Work Tracking
2038
+ ${workCategories.map((c) => `- \`${c}/\` \u2014 ${this.getCategoryDescription(c)}`).join("\n")}
2039
+
2040
+ ### Observational Memory
2041
+ - \`ledger/raw/\` \u2014 Raw session transcripts (source of truth)
2042
+ - \`ledger/observations/\` \u2014 Compressed observations with importance scores
2043
+ - \`ledger/reflections/\` \u2014 Weekly reflection summaries
2044
+
2045
+ ## Quick Reference
2046
+
2047
+ \`\`\`bash
2048
+ # Capture a thought
2049
+ clawvault capture "important insight about X"
2050
+
2051
+ # Store structured memory
2052
+ clawvault store --category decisions --title "Choice" --content "We chose X because..."
2053
+
2054
+ # Search
2055
+ clawvault search "query"
2056
+ clawvault vsearch "semantic query" # vector search
2057
+
2058
+ # Knowledge graph
2059
+ clawvault graph # vault stats
2060
+ clawvault context "topic" # graph-aware context retrieval
2061
+
2062
+ # Session lifecycle
2063
+ clawvault checkpoint --working-on "task"
2064
+ clawvault sleep "what I did" --next "what's next"
2065
+ clawvault wake # restore context on startup
2066
+ \`\`\`
2067
+
2068
+ ---
2069
+
2070
+ *Managed by [ClawVault](https://clawvault.dev)*
2071
+ `;
2072
+ }
2073
+ getCategoryDescription(category) {
2074
+ const descriptions = {
2075
+ // Memory type categories (Benthic's taxonomy)
2076
+ facts: "Raw information, data points, things that are true",
2077
+ feelings: "Emotional states, reactions, energy levels",
2078
+ decisions: "Choices made with context and reasoning",
2079
+ rules: "Injectable operational constraints, guardrails, and runbooks",
2080
+ lessons: "What I learned, insights, patterns observed",
2081
+ commitments: "Promises, goals, obligations to fulfill",
2082
+ preferences: "Likes, dislikes, how I want things",
2083
+ people: "Relationships, one file per person",
2084
+ projects: "Active work, ventures, ongoing efforts",
2085
+ // System categories
2086
+ handoffs: "Session bridges \u2014 what I was doing, what comes next",
2087
+ transcripts: "Session summaries and logs",
2088
+ goals: "Long-term and short-term objectives",
2089
+ patterns: "Recurring behaviors (\u2192 lessons)",
2090
+ inbox: "Quick capture \u2192 process later",
2091
+ templates: "Templates for each document type",
2092
+ agents: "Other agents \u2014 capabilities, trust levels, coordination notes",
2093
+ research: "Deep dives, analysis, reference material",
2094
+ tasks: "Active work items with status and context",
2095
+ backlog: "Future work \u2014 ideas and tasks not yet started"
2096
+ };
2097
+ return descriptions[category] || category;
2098
+ }
2099
+ };
2100
+ async function findVault(startPath = process.cwd()) {
2101
+ let current = path3.resolve(startPath);
2102
+ while (current !== path3.dirname(current)) {
2103
+ const configPath = path3.join(current, CONFIG_FILE);
2104
+ if (fs3.existsSync(configPath)) {
2105
+ const vault = new ClawVault(current);
2106
+ await vault.load();
2107
+ return vault;
2108
+ }
2109
+ current = path3.dirname(current);
2110
+ }
2111
+ return null;
2112
+ }
2113
+
2114
+ // src/lib/backlinks.ts
2115
+ var fs5 = __toESM(require("fs"), 1);
2116
+ var path5 = __toESM(require("path"), 1);
2117
+
2118
+ // src/lib/entity-index.ts
2119
+ var fs4 = __toESM(require("fs"), 1);
2120
+ var path4 = __toESM(require("path"), 1);
2121
+ var import_gray_matter3 = __toESM(require("gray-matter"), 1);
2122
+ function buildEntityIndex(vaultPath) {
2123
+ const entries = /* @__PURE__ */ new Map();
2124
+ const byPath = /* @__PURE__ */ new Map();
2125
+ const entityFolders = ["people", "projects", "agents", "lessons", "decisions", "commitments"];
2126
+ for (const folder of entityFolders) {
2127
+ const folderPath = path4.join(vaultPath, folder);
2128
+ if (!fs4.existsSync(folderPath)) continue;
2129
+ const files = fs4.readdirSync(folderPath).filter((f) => f.endsWith(".md"));
2130
+ for (const file of files) {
2131
+ const filePath = path4.join(folderPath, file);
2132
+ const content = fs4.readFileSync(filePath, "utf-8");
2133
+ const { data: frontmatter } = (0, import_gray_matter3.default)(content);
2134
+ const relativePath = `${folder}/${file.replace(".md", "")}`;
2135
+ const baseName = file.replace(".md", "");
2136
+ const aliases = [baseName];
2137
+ if (frontmatter.title && frontmatter.title.toLowerCase() !== baseName.toLowerCase()) {
2138
+ aliases.push(frontmatter.title);
2139
+ }
2140
+ if (Array.isArray(frontmatter.aliases)) {
2141
+ aliases.push(...frontmatter.aliases);
2142
+ }
2143
+ for (const alias of aliases) {
2144
+ const key = alias.toLowerCase();
2145
+ if (!entries.has(key)) {
2146
+ entries.set(key, relativePath);
2147
+ }
2148
+ }
2149
+ byPath.set(relativePath, { path: relativePath, aliases });
2150
+ }
2151
+ }
2152
+ return { entries, byPath };
2153
+ }
2154
+
2155
+ // src/lib/backlinks.ts
2156
+ var WIKI_LINK_REGEX = /\[\[([^\]]+)\]\]/g;
2157
+ function toVaultId(vaultPath, filePath) {
2158
+ const relative3 = path5.relative(vaultPath, filePath).replace(/\.md$/, "");
2159
+ return relative3.split(path5.sep).join("/");
2160
+ }
2161
+ function normalizeLinkTarget(raw) {
2162
+ let target = raw.trim();
2163
+ if (!target) return "";
2164
+ if (target.startsWith("[[") && target.endsWith("]]")) {
2165
+ target = target.slice(2, -2);
2166
+ }
2167
+ const pipeIndex = target.indexOf("|");
2168
+ if (pipeIndex !== -1) {
2169
+ target = target.slice(0, pipeIndex);
2170
+ }
2171
+ if (target.startsWith("#")) return "";
2172
+ const hashIndex = target.indexOf("#");
2173
+ if (hashIndex !== -1) {
2174
+ target = target.slice(0, hashIndex);
2175
+ }
2176
+ target = target.trim();
2177
+ if (!target) return "";
2178
+ if (target.endsWith(".md")) {
2179
+ target = target.slice(0, -3);
2180
+ }
2181
+ if (target.startsWith("/")) {
2182
+ target = target.slice(1);
2183
+ }
2184
+ return target.replace(/\\/g, "/");
2185
+ }
2186
+ function listMarkdownFiles(vaultPath) {
2187
+ const files = [];
2188
+ const skipDirs = /* @__PURE__ */ new Set(["archive", "templates", "node_modules"]);
2189
+ function walk(dir) {
2190
+ const entries = fs5.readdirSync(dir, { withFileTypes: true });
2191
+ for (const entry of entries) {
2192
+ const fullPath = path5.join(dir, entry.name);
2193
+ if (entry.isDirectory()) {
2194
+ if (entry.name.startsWith(".") || skipDirs.has(entry.name)) continue;
2195
+ walk(fullPath);
2196
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
2197
+ files.push(fullPath);
2198
+ }
2199
+ }
2200
+ }
2201
+ walk(vaultPath);
2202
+ return files;
2203
+ }
2204
+ function buildKnownIds(vaultPath, files) {
2205
+ const ids = /* @__PURE__ */ new Set();
2206
+ const idsLower = /* @__PURE__ */ new Map();
2207
+ for (const file of files) {
2208
+ const id = toVaultId(vaultPath, file);
2209
+ ids.add(id);
2210
+ const lower = id.toLowerCase();
2211
+ if (!idsLower.has(lower)) {
2212
+ idsLower.set(lower, id);
2213
+ }
2214
+ }
2215
+ return { ids, idsLower };
2216
+ }
2217
+ function resolveTarget(target, known, entityIndex) {
2218
+ if (!target) return null;
2219
+ if (known.ids.has(target)) return target;
2220
+ const lower = target.toLowerCase();
2221
+ if (known.idsLower.has(lower)) return known.idsLower.get(lower);
2222
+ if (entityIndex?.entries.has(lower)) return entityIndex.entries.get(lower);
2223
+ return null;
2224
+ }
2225
+ function scanVaultLinks(vaultPath, options = {}) {
2226
+ const files = listMarkdownFiles(vaultPath);
2227
+ const known = buildKnownIds(vaultPath, files);
2228
+ const entityIndex = options.entityIndex ?? buildEntityIndex(vaultPath);
2229
+ const backlinks = /* @__PURE__ */ new Map();
2230
+ const orphans = [];
2231
+ let linkCount = 0;
2232
+ for (const file of files) {
2233
+ const sourceId = toVaultId(vaultPath, file);
2234
+ const content = fs5.readFileSync(file, "utf-8");
2235
+ const matches = content.match(WIKI_LINK_REGEX) || [];
2236
+ linkCount += matches.length;
2237
+ for (const match of matches) {
2238
+ const target = normalizeLinkTarget(match);
2239
+ if (!target) continue;
2240
+ const resolved = resolveTarget(target, known, entityIndex);
2241
+ if (!resolved) {
2242
+ orphans.push({ source: sourceId, target });
2243
+ continue;
2244
+ }
2245
+ if (!backlinks.has(resolved)) {
2246
+ backlinks.set(resolved, /* @__PURE__ */ new Set());
2247
+ }
2248
+ backlinks.get(resolved).add(sourceId);
2249
+ }
2250
+ }
2251
+ const backlinksMap = /* @__PURE__ */ new Map();
2252
+ for (const [target, sources] of backlinks) {
2253
+ backlinksMap.set(target, [...sources].sort());
2254
+ }
2255
+ return { backlinks: backlinksMap, orphans, linkCount };
2256
+ }
2257
+
2258
+ // src/lib/time.ts
2259
+ function formatAge(ms) {
2260
+ if (!Number.isFinite(ms)) return "unknown";
2261
+ const seconds = Math.max(0, Math.floor(ms / 1e3));
2262
+ const minutes = Math.floor(seconds / 60);
2263
+ const hours = Math.floor(minutes / 60);
2264
+ const days = Math.floor(hours / 24);
2265
+ if (days > 0) return `${days}d ${hours % 24}h`;
2266
+ if (hours > 0) return `${hours}h ${minutes % 60}m`;
2267
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
2268
+ return `${seconds}s`;
2269
+ }
2270
+
2271
+ // src/observer/active-session-observer.ts
2272
+ var fs6 = __toESM(require("fs"), 1);
2273
+ var path7 = __toESM(require("path"), 1);
2274
+
2275
+ // src/lib/claude-credentials.ts
2276
+ var EXPIRY_BUFFER_MS = 5 * 60 * 1e3;
2277
+
2278
+ // src/lib/task-utils.ts
2279
+ var import_gray_matter5 = __toESM(require("gray-matter"), 1);
2280
+
2281
+ // src/lib/primitive-templates.ts
2282
+ var import_gray_matter4 = __toESM(require("gray-matter"), 1);
2283
+
2284
+ // src/lib/project-utils.ts
2285
+ var import_gray_matter6 = __toESM(require("gray-matter"), 1);
2286
+
2287
+ // src/lib/session-utils.ts
2288
+ var path6 = __toESM(require("path"), 1);
2289
+ var os = __toESM(require("os"), 1);
2290
+ function validateEnvPath(envValue) {
2291
+ if (!envValue) return null;
2292
+ const trimmed = envValue.trim();
2293
+ if (!trimmed) return null;
2294
+ const resolved = path6.resolve(trimmed);
2295
+ if (!path6.isAbsolute(resolved)) return null;
2296
+ return resolved;
2297
+ }
2298
+ function getOpenClawDir() {
2299
+ const customHome = validateEnvPath(process.env.OPENCLAW_HOME);
2300
+ if (customHome) {
2301
+ return customHome;
2302
+ }
2303
+ return path6.join(os.homedir(), ".openclaw");
2304
+ }
2305
+ function getOpenClawAgentsDir() {
2306
+ const stateDir = validateEnvPath(process.env.OPENCLAW_STATE_DIR);
2307
+ if (stateDir) {
2308
+ return path6.join(stateDir, "agents");
2309
+ }
2310
+ return path6.join(getOpenClawDir(), "agents");
2311
+ }
2312
+ function getSessionsDir(agentId) {
2313
+ return path6.join(getOpenClawAgentsDir(), agentId, "sessions");
2314
+ }
2315
+
2316
+ // src/observer/active-session-observer.ts
2317
+ var ONE_KIB = 1024;
2318
+ var ONE_MIB = ONE_KIB * ONE_KIB;
2319
+ var SMALL_SESSION_THRESHOLD_BYTES = 50 * ONE_KIB;
2320
+ var MEDIUM_SESSION_THRESHOLD_BYTES = 150 * ONE_KIB;
2321
+ var LARGE_SESSION_THRESHOLD_BYTES = 300 * ONE_KIB;
2322
+ var DEFAULT_AGENT_ID = "main";
2323
+ var AGENT_ID_RE = /^[a-zA-Z0-9_-]{1,100}$/;
2324
+ var SESSION_ID_RE = /^[a-zA-Z0-9._-]{1,200}$/;
2325
+ var CURSOR_FILE_NAME = "observe-cursors.json";
2326
+ var STALE_CURSOR_THRESHOLD_MS = 12 * 60 * 60 * 1e3;
2327
+ function isFiniteNonNegative(value) {
2328
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
2329
+ }
2330
+ function getCursorPath(vaultPath) {
2331
+ return path7.join(vaultPath, ".clawvault", CURSOR_FILE_NAME);
2332
+ }
2333
+ function getScaledObservationThresholdBytes(fileSizeBytes) {
2334
+ if (fileSizeBytes < ONE_MIB) {
2335
+ return SMALL_SESSION_THRESHOLD_BYTES;
2336
+ }
2337
+ if (fileSizeBytes <= 5 * ONE_MIB) {
2338
+ return MEDIUM_SESSION_THRESHOLD_BYTES;
2339
+ }
2340
+ return LARGE_SESSION_THRESHOLD_BYTES;
2341
+ }
2342
+ function parseCursorStore(raw) {
2343
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
2344
+ return {};
2345
+ }
2346
+ const input = raw;
2347
+ const store = {};
2348
+ for (const [sessionId, value] of Object.entries(input)) {
2349
+ if (!SESSION_ID_RE.test(sessionId)) continue;
2350
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
2351
+ const entry = value;
2352
+ if (!isFiniteNonNegative(entry.lastObservedOffset)) continue;
2353
+ if (!isFiniteNonNegative(entry.lastFileSize)) continue;
2354
+ if (typeof entry.lastObservedAt !== "string" || !entry.lastObservedAt.trim()) continue;
2355
+ if (typeof entry.sessionKey !== "string" || !entry.sessionKey.trim()) continue;
2356
+ store[sessionId] = {
2357
+ lastObservedOffset: entry.lastObservedOffset,
2358
+ lastObservedAt: entry.lastObservedAt,
2359
+ sessionKey: entry.sessionKey,
2360
+ lastFileSize: entry.lastFileSize
2361
+ };
2362
+ }
2363
+ return store;
2364
+ }
2365
+ function loadObserveCursorStore(vaultPath) {
2366
+ const cursorPath = getCursorPath(vaultPath);
2367
+ if (!fs6.existsSync(cursorPath)) {
2368
+ return {};
2369
+ }
2370
+ try {
2371
+ const raw = JSON.parse(fs6.readFileSync(cursorPath, "utf-8"));
2372
+ return parseCursorStore(raw);
2373
+ } catch {
2374
+ return {};
2375
+ }
2376
+ }
2377
+ function parseAgentIdFromSessionKey(sessionKey) {
2378
+ const match = /^agent:([^:]+):/.exec(sessionKey);
2379
+ if (!match?.[1]) {
2380
+ return null;
2381
+ }
2382
+ const candidate = match[1].trim();
2383
+ if (!AGENT_ID_RE.test(candidate)) {
2384
+ return null;
2385
+ }
2386
+ return candidate;
2387
+ }
2388
+ function resolveSessionFileForCursor(sessionId, cursor, sessionsDirOverride) {
2389
+ const candidates = /* @__PURE__ */ new Set();
2390
+ if (sessionsDirOverride?.trim()) {
2391
+ candidates.add(path7.join(path7.resolve(sessionsDirOverride.trim()), `${sessionId}.jsonl`));
2392
+ }
2393
+ const agentId = parseAgentIdFromSessionKey(cursor.sessionKey) ?? DEFAULT_AGENT_ID;
2394
+ candidates.add(path7.join(getSessionsDir(agentId), `${sessionId}.jsonl`));
2395
+ for (const filePath of candidates) {
2396
+ try {
2397
+ const stat = fs6.statSync(filePath);
2398
+ if (stat.isFile()) {
2399
+ return filePath;
2400
+ }
2401
+ } catch {
2402
+ continue;
2403
+ }
2404
+ }
2405
+ return null;
2406
+ }
2407
+ function getObserverStaleness(vaultPath, options = {}) {
2408
+ const nowDate = options.now ? options.now() : /* @__PURE__ */ new Date();
2409
+ const nowMs = nowDate.getTime();
2410
+ if (!Number.isFinite(nowMs)) {
2411
+ return {
2412
+ staleCount: 0,
2413
+ oldestMs: 0,
2414
+ newestMs: 0
2415
+ };
2416
+ }
2417
+ const cursorStore = loadObserveCursorStore(path7.resolve(vaultPath));
2418
+ let staleCount = 0;
2419
+ let oldestMs = 0;
2420
+ let newestMs = Number.POSITIVE_INFINITY;
2421
+ for (const [sessionId, cursor] of Object.entries(cursorStore)) {
2422
+ const observedAtMs = Date.parse(cursor.lastObservedAt);
2423
+ if (!Number.isFinite(observedAtMs)) {
2424
+ continue;
2425
+ }
2426
+ const ageMs = Math.max(0, nowMs - observedAtMs);
2427
+ if (ageMs <= STALE_CURSOR_THRESHOLD_MS) {
2428
+ continue;
2429
+ }
2430
+ const sessionFilePath = resolveSessionFileForCursor(sessionId, cursor, options.sessionsDir);
2431
+ if (!sessionFilePath) {
2432
+ continue;
2433
+ }
2434
+ let sessionStat;
2435
+ try {
2436
+ sessionStat = fs6.statSync(sessionFilePath);
2437
+ } catch {
2438
+ continue;
2439
+ }
2440
+ if (!sessionStat.isFile()) {
2441
+ continue;
2442
+ }
2443
+ const newBytes = sessionStat.size - cursor.lastFileSize;
2444
+ if (newBytes <= 0) {
2445
+ continue;
2446
+ }
2447
+ const processThreshold = getScaledObservationThresholdBytes(sessionStat.size);
2448
+ if (newBytes < processThreshold) {
2449
+ continue;
2450
+ }
2451
+ staleCount += 1;
2452
+ oldestMs = Math.max(oldestMs, ageMs);
2453
+ newestMs = Math.min(newestMs, ageMs);
2454
+ }
2455
+ return {
2456
+ staleCount,
2457
+ oldestMs: staleCount > 0 ? oldestMs : 0,
2458
+ newestMs: staleCount > 0 ? newestMs : 0
2459
+ };
2460
+ }
2461
+
2462
+ // src/commands/compat.ts
2463
+ var fs7 = __toESM(require("fs"), 1);
2464
+ var path8 = __toESM(require("path"), 1);
2465
+ var import_gray_matter7 = __toESM(require("gray-matter"), 1);
2466
+ var import_child_process2 = require("child_process");
2467
+ var import_url2 = require("url");
2468
+ var import_meta2 = {};
2469
+ var REQUIRED_HOOK_EVENTS = ["gateway:startup", "command:new", "session:start"];
2470
+ var REQUIRED_HOOK_BIN = "clawvault";
2471
+ function readOptionalFile(filePath) {
2472
+ try {
2473
+ if (!fs7.existsSync(filePath)) return null;
2474
+ return fs7.readFileSync(filePath, "utf-8");
2475
+ } catch {
2476
+ return null;
2477
+ }
2478
+ }
2479
+ function findPackageRoot() {
2480
+ let dir = path8.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
2481
+ while (dir !== path8.dirname(dir)) {
2482
+ if (fs7.existsSync(path8.join(dir, "package.json"))) {
2483
+ return dir;
2484
+ }
2485
+ dir = path8.dirname(dir);
2486
+ }
2487
+ return path8.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
2488
+ }
2489
+ function resolveOpenClawHooksDir() {
2490
+ const candidates = [
2491
+ path8.join(process.env.HOME || "", ".openclaw", "hooks", "clawvault"),
2492
+ path8.join(process.env.OPENCLAW_HOME || "", "hooks", "clawvault"),
2493
+ path8.join(process.env.OPENCLAW_STATE_DIR || "", "hooks", "clawvault")
2494
+ ].filter((p) => p && !p.startsWith(path8.sep + "hooks"));
2495
+ for (const candidate of candidates) {
2496
+ if (fs7.existsSync(candidate)) {
2497
+ return candidate;
2498
+ }
2499
+ }
2500
+ return null;
2501
+ }
2502
+ function resolveProjectFile(relativePath, baseDir) {
2503
+ if (baseDir) {
2504
+ return path8.resolve(baseDir, relativePath);
2505
+ }
2506
+ const fromCwd = path8.resolve(process.cwd(), relativePath);
2507
+ if (fs7.existsSync(fromCwd)) {
2508
+ return fromCwd;
2509
+ }
2510
+ if (relativePath.startsWith("hooks/clawvault/")) {
2511
+ const hooksDir = resolveOpenClawHooksDir();
2512
+ if (hooksDir) {
2513
+ const hookRelative = relativePath.replace("hooks/clawvault/", "");
2514
+ const fromHooks = path8.resolve(hooksDir, hookRelative);
2515
+ if (fs7.existsSync(fromHooks)) {
2516
+ return fromHooks;
2517
+ }
2518
+ const fromNestedHooks = path8.resolve(hooksDir, "hooks", "clawvault", hookRelative);
2519
+ if (fs7.existsSync(fromNestedHooks)) {
2520
+ return fromNestedHooks;
2521
+ }
2522
+ }
2523
+ }
2524
+ return path8.resolve(findPackageRoot(), relativePath);
2525
+ }
2526
+ function checkOpenClawCli() {
2527
+ const result = (0, import_child_process2.spawnSync)("openclaw", ["--version"], { stdio: "ignore" });
2528
+ if (result.error) {
2529
+ return {
2530
+ label: "openclaw CLI available",
2531
+ status: "warn",
2532
+ detail: "openclaw binary not found",
2533
+ hint: "Install OpenClaw CLI to enable hook runtime validation."
2534
+ };
2535
+ }
2536
+ if (typeof result.status === "number" && result.status !== 0) {
2537
+ return {
2538
+ label: "openclaw CLI available",
2539
+ status: "warn",
2540
+ detail: `openclaw --version exited with code ${result.status}`,
2541
+ hint: "Ensure OpenClaw CLI is installed and runnable in PATH."
2542
+ };
2543
+ }
2544
+ if (typeof result.signal === "string" && result.signal.length > 0) {
2545
+ return {
2546
+ label: "openclaw CLI available",
2547
+ status: "warn",
2548
+ detail: `openclaw --version terminated by signal ${result.signal}`,
2549
+ hint: "Ensure OpenClaw CLI can execute normally in PATH."
2550
+ };
2551
+ }
2552
+ return { label: "openclaw CLI available", status: "ok" };
2553
+ }
2554
+ function checkPackageHookRegistration(options) {
2555
+ let packageRaw = readOptionalFile(resolveProjectFile("package.json", options.baseDir));
2556
+ if (packageRaw && !options.baseDir) {
2557
+ try {
2558
+ const parsed = JSON.parse(packageRaw);
2559
+ if (!parsed.openclaw?.hooks) {
2560
+ const fallbackPath = path8.resolve(findPackageRoot(), "package.json");
2561
+ const fallbackRaw = readOptionalFile(fallbackPath);
2562
+ if (fallbackRaw) packageRaw = fallbackRaw;
2563
+ }
2564
+ } catch {
2565
+ }
2566
+ }
2567
+ if (!packageRaw) {
2568
+ return {
2569
+ label: "package hook registration",
2570
+ status: "error",
2571
+ detail: "package.json not found"
2572
+ };
2573
+ }
2574
+ try {
2575
+ const parsed = JSON.parse(packageRaw);
2576
+ const registeredHooks = parsed.openclaw?.hooks ?? [];
2577
+ if (registeredHooks.includes("./hooks/clawvault")) {
2578
+ return {
2579
+ label: "package hook registration",
2580
+ status: "ok",
2581
+ detail: "./hooks/clawvault"
2582
+ };
2583
+ }
2584
+ return {
2585
+ label: "package hook registration",
2586
+ status: "error",
2587
+ detail: "Missing ./hooks/clawvault in package openclaw.hooks"
2588
+ };
2589
+ } catch (err) {
2590
+ return {
2591
+ label: "package hook registration",
2592
+ status: "error",
2593
+ detail: err?.message || "Unable to parse package.json"
2594
+ };
2595
+ }
2596
+ }
2597
+ function checkHookManifest(options) {
2598
+ const hookRaw = readOptionalFile(resolveProjectFile("hooks/clawvault/HOOK.md", options.baseDir));
2599
+ if (!hookRaw) {
2600
+ return {
2601
+ label: "hook manifest",
2602
+ status: "error",
2603
+ detail: "HOOK.md not found"
2604
+ };
2605
+ }
2606
+ try {
2607
+ const parsed = (0, import_gray_matter7.default)(hookRaw);
2608
+ const openclaw = parsed.data?.metadata?.openclaw;
2609
+ const events = Array.isArray(openclaw?.events) ? openclaw?.events ?? [] : [];
2610
+ const missingEvents = REQUIRED_HOOK_EVENTS.filter((event) => !events.includes(event));
2611
+ if (missingEvents.length === 0) {
2612
+ return {
2613
+ label: "hook manifest events",
2614
+ status: "ok",
2615
+ detail: events.join(", ")
2616
+ };
2617
+ }
2618
+ return {
2619
+ label: "hook manifest events",
2620
+ status: "error",
2621
+ detail: `Missing events: ${missingEvents.join(", ")}`
2622
+ };
2623
+ } catch (err) {
2624
+ return {
2625
+ label: "hook manifest events",
2626
+ status: "error",
2627
+ detail: err?.message || "Unable to parse HOOK.md frontmatter"
2628
+ };
2629
+ }
2630
+ }
2631
+ function checkHookManifestRequirements(options) {
2632
+ const hookRaw = readOptionalFile(resolveProjectFile("hooks/clawvault/HOOK.md", options.baseDir));
2633
+ if (!hookRaw) {
2634
+ return {
2635
+ label: "hook manifest requirements",
2636
+ status: "error",
2637
+ detail: "HOOK.md not found"
2638
+ };
2639
+ }
2640
+ try {
2641
+ const parsed = (0, import_gray_matter7.default)(hookRaw);
2642
+ const requiresBins = parsed.data?.metadata?.openclaw?.requires?.bins;
2643
+ const bins = Array.isArray(requiresBins) ? requiresBins : [];
2644
+ if (bins.includes(REQUIRED_HOOK_BIN)) {
2645
+ return {
2646
+ label: "hook manifest requirements",
2647
+ status: "ok",
2648
+ detail: `bins: ${bins.join(", ")}`
2649
+ };
2650
+ }
2651
+ return {
2652
+ label: "hook manifest requirements",
2653
+ status: "warn",
2654
+ detail: `Missing required hook bin "${REQUIRED_HOOK_BIN}"`,
2655
+ hint: 'Add metadata.openclaw.requires.bins: ["clawvault"] to hooks/clawvault/HOOK.md.'
2656
+ };
2657
+ } catch (err) {
2658
+ return {
2659
+ label: "hook manifest requirements",
2660
+ status: "error",
2661
+ detail: err?.message || "Unable to parse HOOK.md frontmatter"
2662
+ };
2663
+ }
2664
+ }
2665
+ function checkHookHandlerSafety(options) {
2666
+ const handlerRaw = readOptionalFile(resolveProjectFile("hooks/clawvault/handler.js", options.baseDir));
2667
+ if (!handlerRaw) {
2668
+ return {
2669
+ label: "hook handler script",
2670
+ status: "error",
2671
+ detail: "handler.js not found"
2672
+ };
2673
+ }
2674
+ const usesExecFileSync = handlerRaw.includes("execFileSync");
2675
+ const usesExecSync = /\bexecSync\b/.test(handlerRaw);
2676
+ const enablesShell = /\bshell\s*:\s*true\b/.test(handlerRaw);
2677
+ const delegatesAutoProfile = /['"]--profile['"]\s*,\s*['"]auto['"]/.test(handlerRaw);
2678
+ const violations = [];
2679
+ if (!usesExecFileSync || usesExecSync) {
2680
+ violations.push("execFileSync-only execution path");
2681
+ }
2682
+ if (enablesShell) {
2683
+ violations.push("shell:false execution option");
2684
+ }
2685
+ if (!delegatesAutoProfile) {
2686
+ violations.push("shared context profile delegation (--profile auto)");
2687
+ }
2688
+ if (violations.length > 0) {
2689
+ return {
2690
+ label: "hook handler safety",
2691
+ status: "warn",
2692
+ detail: `Missing conventions: ${violations.join(", ")}`,
2693
+ hint: "Use execFileSync (no shell), avoid execSync, and delegate profile inference via --profile auto."
2694
+ };
2695
+ }
2696
+ return { label: "hook handler safety", status: "ok" };
2697
+ }
2698
+ function checkSkillMetadata(options) {
2699
+ const skillRaw = readOptionalFile(resolveProjectFile("SKILL.md", options.baseDir));
2700
+ if (!skillRaw) {
2701
+ return {
2702
+ label: "skill metadata",
2703
+ status: "warn",
2704
+ detail: "SKILL.md not found",
2705
+ hint: "Ensure SKILL.md is present for OpenClaw skill distribution."
2706
+ };
2707
+ }
2708
+ let hasOpenClawMetadata = false;
2709
+ let parseError;
2710
+ try {
2711
+ const parsed = (0, import_gray_matter7.default)(skillRaw);
2712
+ const frontmatter = parsed.data ?? {};
2713
+ const metadata = frontmatter.metadata && typeof frontmatter.metadata === "object" && !Array.isArray(frontmatter.metadata) ? frontmatter.metadata : void 0;
2714
+ hasOpenClawMetadata = Boolean(
2715
+ metadata && typeof metadata.openclaw === "object" && metadata.openclaw !== null || typeof frontmatter.openclaw === "object" && frontmatter.openclaw !== null
2716
+ );
2717
+ } catch {
2718
+ parseError = "Unable to parse SKILL.md frontmatter";
2719
+ hasOpenClawMetadata = false;
2720
+ }
2721
+ if (!hasOpenClawMetadata) {
2722
+ hasOpenClawMetadata = /"openclaw"\s*:/.test(skillRaw);
2723
+ }
2724
+ if (!hasOpenClawMetadata) {
2725
+ const detail = parseError ? `${parseError} (or missing metadata.openclaw)` : "Missing metadata.openclaw in SKILL.md";
2726
+ return {
2727
+ label: "skill metadata",
2728
+ status: "warn",
2729
+ detail,
2730
+ hint: "Add metadata.openclaw to SKILL.md frontmatter for OpenClaw compatibility."
2731
+ };
2732
+ }
2733
+ return { label: "skill metadata", status: "ok" };
2734
+ }
2735
+ function checkOpenClawCompatibility(options = {}) {
2736
+ const checks = [
2737
+ checkOpenClawCli(),
2738
+ checkPackageHookRegistration(options),
2739
+ checkHookManifest(options),
2740
+ checkHookManifestRequirements(options),
2741
+ checkHookHandlerSafety(options),
2742
+ checkSkillMetadata(options)
2743
+ ];
2744
+ const warnings = checks.filter((check) => check.status === "warn").length;
2745
+ const errors = checks.filter((check) => check.status === "error").length;
2746
+ return {
2747
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2748
+ checks,
2749
+ warnings,
2750
+ errors
2751
+ };
2752
+ }
2753
+
2754
+ // src/commands/doctor.ts
2755
+ var CLAWVAULT_DIR = ".clawvault";
2756
+ var CHECKPOINT_FILE = "last-checkpoint.json";
2757
+ var DAY_MS = 24 * 60 * 60 * 1e3;
2758
+ var ACTIVE_USE_DAYS = 7;
2759
+ function daysSince(date, now = Date.now()) {
2760
+ return Math.max(0, Math.floor((now - date.getTime()) / DAY_MS));
2761
+ }
2762
+ function describeAge(date, now = Date.now()) {
2763
+ return formatAge(now - date.getTime());
2764
+ }
2765
+ function loadCheckpointTimestamp(vaultPath) {
2766
+ const checkpointPath = path9.join(vaultPath, CLAWVAULT_DIR, CHECKPOINT_FILE);
2767
+ if (!fs8.existsSync(checkpointPath)) {
2768
+ return {};
2769
+ }
2770
+ try {
2771
+ const data = JSON.parse(fs8.readFileSync(checkpointPath, "utf-8"));
2772
+ return { timestamp: data.timestamp };
2773
+ } catch (err) {
2774
+ return { error: err?.message || "Failed to parse checkpoint" };
2775
+ }
2776
+ }
2777
+ function getShellConfigPaths(shellPath) {
2778
+ const home = os2.homedir();
2779
+ const shellName = shellPath ? path9.basename(shellPath) : "bash";
2780
+ if (shellName === "zsh") {
2781
+ return [path9.join(home, ".zshenv"), path9.join(home, ".zshrc"), path9.join(home, ".zprofile")];
2782
+ }
2783
+ if (shellName === "fish") {
2784
+ return [path9.join(home, ".config", "fish", "config.fish")];
2785
+ }
2786
+ return [path9.join(home, ".bashrc"), path9.join(home, ".bash_profile"), path9.join(home, ".profile")];
2787
+ }
2788
+ function hasClawvaultPathConfig(paths) {
2789
+ for (const filePath of paths) {
2790
+ if (!fs8.existsSync(filePath)) continue;
2791
+ try {
2792
+ const content = fs8.readFileSync(filePath, "utf-8");
2793
+ if (/CLAWVAULT_PATH\s*=/.test(content)) {
2794
+ return true;
2795
+ }
2796
+ } catch {
2797
+ }
2798
+ }
2799
+ return false;
2800
+ }
2801
+ async function resolveVault(vaultPath) {
2802
+ if (vaultPath) {
2803
+ const vault = new ClawVault(path9.resolve(vaultPath));
2804
+ await vault.load();
2805
+ return vault;
2806
+ }
2807
+ const envPath = process.env.CLAWVAULT_PATH;
2808
+ if (envPath) {
2809
+ const vault = new ClawVault(path9.resolve(envPath));
2810
+ await vault.load();
2811
+ return vault;
2812
+ }
2813
+ const found = await findVault();
2814
+ if (!found) {
2815
+ throw new Error("No ClawVault found. Run `clawvault init` first.");
2816
+ }
2817
+ return found;
2818
+ }
2819
+ async function doctor(options) {
2820
+ const vaultPath = typeof options === "string" ? options : options?.vaultPath;
2821
+ const _fix = typeof options === "object" ? options?.fix : false;
2822
+ const checks = [];
2823
+ let warnings = 0;
2824
+ let errors = 0;
2825
+ const compatReport = checkOpenClawCompatibility();
2826
+ if (compatReport.errors > 0) {
2827
+ checks.push({
2828
+ label: "OpenClaw compatibility",
2829
+ status: "error",
2830
+ detail: `${compatReport.errors} error(s), ${compatReport.warnings} warning(s)`,
2831
+ hint: "Run `clawvault compat` for full compatibility diagnostics."
2832
+ });
2833
+ errors++;
2834
+ } else if (compatReport.warnings > 0) {
2835
+ checks.push({
2836
+ label: "OpenClaw compatibility",
2837
+ status: "warn",
2838
+ detail: `${compatReport.warnings} warning(s)`,
2839
+ hint: "Run `clawvault compat` for full compatibility diagnostics."
2840
+ });
2841
+ warnings++;
2842
+ } else {
2843
+ checks.push({
2844
+ label: "OpenClaw compatibility",
2845
+ status: "ok"
2846
+ });
2847
+ }
2848
+ if (hasQmd()) {
2849
+ checks.push({ label: "qmd installed", status: "ok" });
2850
+ } else {
2851
+ checks.push({
2852
+ label: "qmd installed",
2853
+ status: "error",
2854
+ hint: "Install qmd to enable ClawVault commands."
2855
+ });
2856
+ errors++;
2857
+ }
2858
+ const shellConfigs = getShellConfigPaths(process.env.SHELL).filter(fs8.existsSync);
2859
+ if (hasClawvaultPathConfig(shellConfigs)) {
2860
+ checks.push({
2861
+ label: "CLAWVAULT_PATH in shell config",
2862
+ status: "ok",
2863
+ detail: shellConfigs.map((p) => path9.basename(p)).join(", ")
2864
+ });
2865
+ } else {
2866
+ checks.push({
2867
+ label: "CLAWVAULT_PATH in shell config",
2868
+ status: "warn",
2869
+ hint: "Run `clawvault shell-init` and add it to your shell rc."
2870
+ });
2871
+ warnings++;
2872
+ }
2873
+ if (!hasQmd()) {
2874
+ return { vaultPath, checks, warnings, errors };
2875
+ }
2876
+ let vault;
2877
+ try {
2878
+ vault = await resolveVault(vaultPath);
2879
+ checks.push({ label: "vault found", status: "ok", detail: vault.getPath() });
2880
+ } catch (err) {
2881
+ checks.push({
2882
+ label: "vault found",
2883
+ status: "error",
2884
+ detail: err?.message || "Unable to locate vault"
2885
+ });
2886
+ errors++;
2887
+ return { vaultPath, checks, warnings, errors };
2888
+ }
2889
+ const qmdCollection = vault.getQmdCollection();
2890
+ const qmdRoot = vault.getQmdRoot();
2891
+ const stats = await vault.stats();
2892
+ const documents = await vault.list();
2893
+ const handoffs = await vault.list("handoffs");
2894
+ const inbox = await vault.list("inbox");
2895
+ if (qmdCollection) {
2896
+ checks.push({ label: "qmd collection configured", status: "ok", detail: qmdCollection });
2897
+ } else {
2898
+ checks.push({
2899
+ label: "qmd collection configured",
2900
+ status: "warn",
2901
+ hint: "Set qmd collection in .clawvault.json"
2902
+ });
2903
+ warnings++;
2904
+ }
2905
+ const latestDoc = documents.slice().sort((a, b) => b.modified.getTime() - a.modified.getTime())[0];
2906
+ const latestDocAge = latestDoc ? daysSince(latestDoc.modified) : null;
2907
+ const graphIndex = loadMemoryGraphIndex(vault.getPath());
2908
+ if (!graphIndex) {
2909
+ checks.push({
2910
+ label: "memory graph index",
2911
+ status: "warn",
2912
+ detail: "No graph index found",
2913
+ hint: "Run `clawvault graph --refresh` to build .clawvault/graph-index.json."
2914
+ });
2915
+ warnings++;
2916
+ } else {
2917
+ const generatedAt = new Date(graphIndex.generatedAt);
2918
+ const generatedAge = describeAge(generatedAt);
2919
+ const latestDocIsNewer = latestDoc ? latestDoc.modified.getTime() > generatedAt.getTime() + 1e3 : false;
2920
+ if (latestDocIsNewer) {
2921
+ checks.push({
2922
+ label: "memory graph index",
2923
+ status: "warn",
2924
+ detail: `Stale graph index (generated ${generatedAge} ago)`,
2925
+ hint: "Run `clawvault graph --refresh` to resync index."
2926
+ });
2927
+ warnings++;
2928
+ } else {
2929
+ checks.push({
2930
+ label: "memory graph index",
2931
+ status: "ok",
2932
+ detail: `${graphIndex.graph.stats.nodeCount} nodes, ${graphIndex.graph.stats.edgeCount} edges`
2933
+ });
2934
+ }
2935
+ }
2936
+ let lastHandoffAge = null;
2937
+ if (handoffs.length === 0) {
2938
+ checks.push({
2939
+ label: "recent handoff",
2940
+ status: "warn",
2941
+ hint: "Run `clawvault sleep` at the end of sessions."
2942
+ });
2943
+ warnings++;
2944
+ } else {
2945
+ const latestHandoff = handoffs.slice().sort((a, b) => b.modified.getTime() - a.modified.getTime())[0];
2946
+ lastHandoffAge = daysSince(latestHandoff.modified);
2947
+ const ageLabel = describeAge(latestHandoff.modified);
2948
+ if (lastHandoffAge > 1) {
2949
+ checks.push({
2950
+ label: "recent handoff",
2951
+ status: "warn",
2952
+ detail: `Last handoff ${ageLabel} ago`,
2953
+ hint: "Run `clawvault sleep` before long pauses."
2954
+ });
2955
+ warnings++;
2956
+ } else {
2957
+ checks.push({
2958
+ label: "recent handoff",
2959
+ status: "ok",
2960
+ detail: `Last handoff ${ageLabel} ago`
2961
+ });
2962
+ }
2963
+ }
2964
+ const checkpointInfo = loadCheckpointTimestamp(vault.getPath());
2965
+ const activeUse = latestDocAge !== null && latestDocAge <= ACTIVE_USE_DAYS || lastHandoffAge !== null && lastHandoffAge <= ACTIVE_USE_DAYS;
2966
+ if (checkpointInfo.error) {
2967
+ checks.push({
2968
+ label: "checkpoint freshness",
2969
+ status: "warn",
2970
+ detail: checkpointInfo.error
2971
+ });
2972
+ warnings++;
2973
+ } else if (!checkpointInfo.timestamp) {
2974
+ const status = activeUse ? "warn" : "ok";
2975
+ if (status === "warn") warnings++;
2976
+ checks.push({
2977
+ label: "checkpoint freshness",
2978
+ status,
2979
+ detail: activeUse ? "No checkpoint found" : "No checkpoint found (vault appears inactive)",
2980
+ hint: activeUse ? "Run `clawvault checkpoint` during heavy work." : void 0
2981
+ });
2982
+ } else {
2983
+ const checkpointDate = new Date(checkpointInfo.timestamp);
2984
+ const checkpointAge = daysSince(checkpointDate);
2985
+ const ageLabel = describeAge(checkpointDate);
2986
+ if (activeUse && checkpointAge > 1) {
2987
+ checks.push({
2988
+ label: "checkpoint freshness",
2989
+ status: "warn",
2990
+ detail: `Last checkpoint ${ageLabel} ago`,
2991
+ hint: "Checkpoint at least once per active day."
2992
+ });
2993
+ warnings++;
2994
+ } else {
2995
+ checks.push({
2996
+ label: "checkpoint freshness",
2997
+ status: "ok",
2998
+ detail: `Last checkpoint ${ageLabel} ago`
2999
+ });
3000
+ }
3001
+ }
3002
+ const observerStaleness = getObserverStaleness(vault.getPath());
3003
+ if (observerStaleness.staleCount > 0) {
3004
+ checks.push({
3005
+ label: "observer freshness",
3006
+ status: "warn",
3007
+ detail: `${observerStaleness.staleCount} stale session cursor(s); oldest ${formatAge(observerStaleness.oldestMs)} ago`,
3008
+ hint: "Run `clawvault observe --cron` and verify cron/hook scheduling."
3009
+ });
3010
+ warnings++;
3011
+ } else {
3012
+ checks.push({
3013
+ label: "observer freshness",
3014
+ status: "ok"
3015
+ });
3016
+ }
3017
+ const linkScan = scanVaultLinks(vault.getPath());
3018
+ if (linkScan.orphans.length > 20) {
3019
+ checks.push({
3020
+ label: "orphan links",
3021
+ status: "warn",
3022
+ detail: `${linkScan.orphans.length} orphan link(s)`,
3023
+ hint: "Run `clawvault link --orphans` to review."
3024
+ });
3025
+ warnings++;
3026
+ } else {
3027
+ checks.push({
3028
+ label: "orphan links",
3029
+ status: "ok",
3030
+ detail: `${linkScan.orphans.length} orphan link(s)`
3031
+ });
3032
+ }
3033
+ if (inbox.length > 5) {
3034
+ checks.push({
3035
+ label: "inbox backlog",
3036
+ status: "warn",
3037
+ detail: `${inbox.length} inbox item(s) pending`,
3038
+ hint: "Process inbox items to keep memory tidy."
3039
+ });
3040
+ warnings++;
3041
+ } else {
3042
+ checks.push({
3043
+ label: "inbox backlog",
3044
+ status: "ok",
3045
+ detail: `${inbox.length} inbox item(s) pending`
3046
+ });
3047
+ }
3048
+ if (stats.documents < 5) {
3049
+ checks.push({
3050
+ label: "vault activity",
3051
+ status: "warn",
3052
+ detail: `${stats.documents} total documents`,
3053
+ hint: "Start capturing decisions, lessons, and projects."
3054
+ });
3055
+ warnings++;
3056
+ }
3057
+ return { vaultPath: vault.getPath(), qmdCollection, qmdRoot, checks, warnings, errors };
3058
+ }
3059
+ // Annotate the CommonJS export names for ESM import in node:
3060
+ 0 && (module.exports = {
3061
+ doctor
3062
+ });