clawvault 3.2.1 → 3.4.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 (161) hide show
  1. package/README.md +56 -16
  2. package/bin/clawvault.js +0 -2
  3. package/bin/command-registration.test.js +15 -2
  4. package/bin/help-contract.test.js +16 -0
  5. package/bin/register-core-commands.js +88 -0
  6. package/bin/register-core-commands.test.js +80 -0
  7. package/bin/register-maintenance-commands.js +84 -7
  8. package/bin/register-query-commands.js +45 -28
  9. package/bin/register-query-commands.test.js +15 -0
  10. package/bin/test-helpers/cli-command-fixtures.js +1 -0
  11. package/dist/chunk-2PKBIKDH.js +130 -0
  12. package/dist/{chunk-U67V476Y.js → chunk-2ZDO52B4.js} +18 -1
  13. package/dist/{chunk-ZZA73MFY.js → chunk-33DOSHTA.js} +176 -36
  14. package/dist/chunk-35JCYSRR.js +158 -0
  15. package/dist/{chunk-AZYOKJYC.js → chunk-4PY655YM.js} +13 -1
  16. package/dist/{chunk-2JQ3O2YL.js → chunk-5EFSWZO6.js} +3 -3
  17. package/dist/{chunk-Y3TIJEBP.js → chunk-7SWP5FKU.js} +34 -613
  18. package/dist/{chunk-4VQTUVH7.js → chunk-7YZWHM36.js} +52 -26
  19. package/dist/{chunk-URXDAUVH.js → chunk-AXSJIFOJ.js} +174 -1
  20. package/dist/{chunk-4ITRXIVT.js → chunk-BLQXXX7Q.js} +6 -6
  21. package/dist/chunk-CSHO3PJB.js +684 -0
  22. package/dist/chunk-D5U3Q4N5.js +872 -0
  23. package/dist/chunk-DCF4KMFD.js +158 -0
  24. package/dist/{chunk-S5OJEGFG.js → chunk-DOIUYIXV.js} +2 -2
  25. package/dist/{chunk-YXQCA6B7.js → chunk-DVOUSOR3.js} +112 -7
  26. package/dist/{chunk-YDWHS4LJ.js → chunk-ECGJYWNA.js} +205 -33
  27. package/dist/{chunk-QMHPQYUV.js → chunk-EL6UBSX5.js} +7 -6
  28. package/dist/chunk-FZ5I2NF7.js +352 -0
  29. package/dist/{chunk-WJVWINEM.js → chunk-GFCHWMGD.js} +55 -6
  30. package/dist/{chunk-GNJL4YGR.js → chunk-GJO3CFUN.js} +30 -6
  31. package/dist/chunk-H3JZIB5O.js +322 -0
  32. package/dist/chunk-HEHO7SMV.js +51 -0
  33. package/dist/{chunk-UCQAOZHW.js → chunk-HGDDW24U.js} +3 -3
  34. package/dist/chunk-J3YUXVID.js +907 -0
  35. package/dist/{chunk-Y6VJKXGL.js → chunk-KCYWJDDW.js} +1 -1
  36. package/dist/{chunk-P5EPF6MB.js → chunk-MW5C6ZQA.js} +110 -13
  37. package/dist/chunk-NSXYM6EZ.js +255 -0
  38. package/dist/{chunk-YNIPYN4F.js → chunk-OFOCU2V4.js} +6 -5
  39. package/dist/{chunk-42MXU7A6.js → chunk-P62WHA27.js} +58 -47
  40. package/dist/chunk-PTWPPVC7.js +972 -0
  41. package/dist/{chunk-FAKNOB7Y.js → chunk-QFWERBDP.js} +2 -2
  42. package/dist/chunk-QYQAGBTM.js +2097 -0
  43. package/dist/chunk-RL2L6I6K.js +223 -0
  44. package/dist/{chunk-IIOU45CK.js → chunk-S7N7HI5E.js} +2 -2
  45. package/dist/{chunk-ECRZL5XR.js → chunk-T7E764W3.js} +23 -7
  46. package/dist/{chunk-MNPUYCHQ.js → chunk-TWMI3SNN.js} +6 -5
  47. package/dist/{chunk-2RAZ4ZFE.js → chunk-VBILES4B.js} +1 -1
  48. package/dist/{chunk-PI4WMLMG.js → chunk-VXAGOLDP.js} +1 -1
  49. package/dist/{chunk-SS4B7P7V.js → chunk-YIDV4VV2.js} +1 -1
  50. package/dist/chunk-YTRZNA64.js +37 -0
  51. package/dist/chunk-ZKWPCBYT.js +600 -0
  52. package/dist/cli/index.js +28 -21
  53. package/dist/commands/archive.js +3 -3
  54. package/dist/commands/backlog.js +1 -1
  55. package/dist/commands/benchmark.d.ts +12 -0
  56. package/dist/commands/benchmark.js +12 -0
  57. package/dist/commands/blocked.js +1 -1
  58. package/dist/commands/canvas.js +2 -2
  59. package/dist/commands/checkpoint.js +1 -1
  60. package/dist/commands/compat.js +1 -1
  61. package/dist/commands/context.js +8 -7
  62. package/dist/commands/doctor.d.ts +8 -3
  63. package/dist/commands/doctor.js +8 -22
  64. package/dist/commands/embed.js +6 -5
  65. package/dist/commands/entities.d.ts +8 -1
  66. package/dist/commands/entities.js +46 -3
  67. package/dist/commands/graph.js +4 -4
  68. package/dist/commands/inbox.d.ts +23 -0
  69. package/dist/commands/inbox.js +11 -0
  70. package/dist/commands/inject.d.ts +1 -1
  71. package/dist/commands/inject.js +5 -5
  72. package/dist/commands/kanban.js +1 -1
  73. package/dist/commands/link.js +5 -5
  74. package/dist/commands/maintain.d.ts +32 -0
  75. package/dist/commands/maintain.js +13 -0
  76. package/dist/commands/migrate-observations.js +3 -3
  77. package/dist/commands/observe.js +11 -10
  78. package/dist/commands/project.js +2 -2
  79. package/dist/commands/rebuild-embeddings.js +48 -17
  80. package/dist/commands/rebuild.js +9 -8
  81. package/dist/commands/recall.d.ts +14 -0
  82. package/dist/commands/recall.js +15 -0
  83. package/dist/commands/recover.js +1 -1
  84. package/dist/commands/reflect.js +6 -6
  85. package/dist/commands/repair-session.js +1 -1
  86. package/dist/commands/replay.js +10 -9
  87. package/dist/commands/session-recap.js +1 -1
  88. package/dist/commands/setup.js +4 -3
  89. package/dist/commands/shell-init.js +1 -1
  90. package/dist/commands/sleep.d.ts +1 -1
  91. package/dist/commands/sleep.js +20 -18
  92. package/dist/commands/status.js +40 -26
  93. package/dist/commands/sync-bd.js +3 -3
  94. package/dist/commands/tailscale.js +3 -3
  95. package/dist/commands/task.js +1 -1
  96. package/dist/commands/template.js +1 -1
  97. package/dist/commands/wake.d.ts +1 -1
  98. package/dist/commands/wake.js +10 -9
  99. package/dist/index.d.ts +233 -16
  100. package/dist/index.js +325 -111
  101. package/dist/{inject-DYUrDqQO.d.ts → inject-DEb_jpLi.d.ts} +3 -1
  102. package/dist/lib/auto-linker.js +2 -2
  103. package/dist/lib/canvas-layout.js +1 -1
  104. package/dist/lib/config.js +2 -2
  105. package/dist/lib/entity-index.js +1 -1
  106. package/dist/lib/project-utils.js +2 -2
  107. package/dist/lib/session-repair.js +1 -1
  108. package/dist/lib/session-utils.js +1 -1
  109. package/dist/lib/tailscale.js +1 -1
  110. package/dist/lib/task-utils.js +1 -1
  111. package/dist/lib/template-engine.js +1 -1
  112. package/dist/lib/webdav.js +1 -1
  113. package/dist/onnxruntime_binding-5QEF3SUC.node +0 -0
  114. package/dist/onnxruntime_binding-BKPKNEGC.node +0 -0
  115. package/dist/onnxruntime_binding-FMOXGIUT.node +0 -0
  116. package/dist/onnxruntime_binding-OI2KMXC5.node +0 -0
  117. package/dist/onnxruntime_binding-UX44MLAZ.node +0 -0
  118. package/dist/onnxruntime_binding-Y2W7N7WY.node +0 -0
  119. package/dist/openclaw-plugin--gqA2BZw.d.ts +267 -0
  120. package/dist/openclaw-plugin.d.ts +4 -0
  121. package/dist/openclaw-plugin.js +20 -0
  122. package/dist/transformers.node-A2ZRORSQ.js +46775 -0
  123. package/dist/types-CbL-wIKi.d.ts +36 -0
  124. package/dist/{types-BbWJoC1c.d.ts → types-DslKvCaj.d.ts} +51 -1
  125. package/hooks/clawvault/HOOK.md +25 -8
  126. package/hooks/clawvault/handler.js +215 -78
  127. package/hooks/clawvault/handler.test.js +109 -43
  128. package/hooks/clawvault/integrity.js +112 -0
  129. package/hooks/clawvault/integrity.test.js +32 -0
  130. package/hooks/clawvault/openclaw.plugin.json +133 -15
  131. package/openclaw.plugin.json +161 -194
  132. package/package.json +8 -5
  133. package/bin/register-workgraph-commands.js +0 -451
  134. package/dist/chunk-5PJ4STIC.js +0 -465
  135. package/dist/chunk-ERNE2FZ5.js +0 -189
  136. package/dist/chunk-HR4KN6S2.js +0 -152
  137. package/dist/chunk-IJBFGPCS.js +0 -33
  138. package/dist/chunk-K7PNYS45.js +0 -93
  139. package/dist/chunk-NTOPJI7W.js +0 -207
  140. package/dist/chunk-PG56HX5T.js +0 -154
  141. package/dist/chunk-QPDDIHXE.js +0 -501
  142. package/dist/chunk-WIOLLGAD.js +0 -190
  143. package/dist/chunk-WMGIIABP.js +0 -15
  144. package/dist/ledger-B7g7jhqG.d.ts +0 -44
  145. package/dist/plugin/index.d.ts +0 -352
  146. package/dist/plugin/index.js +0 -4264
  147. package/dist/registry-BR4326o0.d.ts +0 -30
  148. package/dist/store-CA-6sKCJ.d.ts +0 -34
  149. package/dist/thread-B9LhXNU0.d.ts +0 -41
  150. package/dist/workgraph/index.d.ts +0 -5
  151. package/dist/workgraph/index.js +0 -23
  152. package/dist/workgraph/ledger.d.ts +0 -2
  153. package/dist/workgraph/ledger.js +0 -25
  154. package/dist/workgraph/registry.d.ts +0 -2
  155. package/dist/workgraph/registry.js +0 -19
  156. package/dist/workgraph/store.d.ts +0 -2
  157. package/dist/workgraph/store.js +0 -25
  158. package/dist/workgraph/thread.d.ts +0 -2
  159. package/dist/workgraph/thread.js +0 -25
  160. package/dist/workgraph/types.d.ts +0 -54
  161. package/dist/workgraph/types.js +0 -7
@@ -20,6 +20,21 @@ function buildProgram() {
20
20
  }
21
21
 
22
22
  describe('register-query-commands', () => {
23
+ it('registers recall command with strategy options', () => {
24
+ const program = buildProgram();
25
+ const recallCommand = program.commands.find((command) => command.name() === 'recall');
26
+ expect(recallCommand).toBeDefined();
27
+
28
+ const flags = recallCommand?.options.map((option) => option.flags) ?? [];
29
+ expect(flags).toEqual(expect.arrayContaining([
30
+ '-n, --limit <n>',
31
+ '--strategy <strategy>',
32
+ '--json',
33
+ '--no-sources',
34
+ '-v, --vault <path>'
35
+ ]));
36
+ });
37
+
23
38
  it('documents inject command options and config-backed defaults', () => {
24
39
  const program = buildProgram();
25
40
  const injectCommand = program.commands.find((command) => command.name() === 'inject');
@@ -30,6 +30,7 @@ export function stubResolveVaultPath(value) {
30
30
  export function createVaultStub(overrides = {}) {
31
31
  return {
32
32
  store: async () => ({}),
33
+ patch: async () => ({}),
33
34
  capture: async () => ({}),
34
35
  find: async () => [],
35
36
  vsearch: async () => [],
@@ -0,0 +1,130 @@
1
+ // src/lib/inbox.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { createHash } from "crypto";
5
+ import matter from "gray-matter";
6
+ import { globSync } from "glob";
7
+ function normalizeContentForHash(content) {
8
+ return content.replace(/\r\n/g, "\n").trim();
9
+ }
10
+ function hashContent(content) {
11
+ return createHash("sha256").update(normalizeContentForHash(content)).digest("hex");
12
+ }
13
+ function ensureInboxDir(vaultPath) {
14
+ const inboxPath = path.join(path.resolve(vaultPath), "inbox");
15
+ fs.mkdirSync(inboxPath, { recursive: true });
16
+ return inboxPath;
17
+ }
18
+ function toSlug(value) {
19
+ const normalized = value.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").trim();
20
+ return normalized || "capture";
21
+ }
22
+ function compactTimestamp(date) {
23
+ return date.toISOString().replace(/[-:]/g, "").replace(/\..+$/, "").replace("T", "t");
24
+ }
25
+ function deriveTitle(content) {
26
+ const firstLine = content.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
27
+ if (!firstLine) {
28
+ return "Inbox Capture";
29
+ }
30
+ return firstLine.slice(0, 80);
31
+ }
32
+ function addInboxItem(vaultPath, rawContent, options = {}) {
33
+ const content = rawContent.replace(/\r\n/g, "\n").trim();
34
+ if (!content) {
35
+ throw new Error("Inbox content is empty.");
36
+ }
37
+ const now = options.now ? options.now() : /* @__PURE__ */ new Date();
38
+ const source = options.source?.trim() || "manual";
39
+ const title = options.title?.trim() || deriveTitle(content);
40
+ const hash = hashContent(content);
41
+ const baseSlug = toSlug(title);
42
+ const timestamp = compactTimestamp(now);
43
+ const inboxDir = ensureInboxDir(vaultPath);
44
+ let fileName = `${baseSlug}-${timestamp}-${hash.slice(0, 8)}.md`;
45
+ let fullPath = path.join(inboxDir, fileName);
46
+ let counter = 1;
47
+ while (fs.existsSync(fullPath)) {
48
+ fileName = `${baseSlug}-${timestamp}-${hash.slice(0, 8)}-${counter}.md`;
49
+ fullPath = path.join(inboxDir, fileName);
50
+ counter += 1;
51
+ }
52
+ const doc = matter.stringify(`${content}
53
+ `, {
54
+ title,
55
+ date: now.toISOString().split("T")[0],
56
+ capturedAt: now.toISOString(),
57
+ source,
58
+ type: "inbox",
59
+ status: "pending",
60
+ hash
61
+ });
62
+ fs.writeFileSync(fullPath, doc, "utf-8");
63
+ return {
64
+ id: `inbox/${fileName.replace(/\.md$/, "")}`,
65
+ hash,
66
+ title,
67
+ path: fullPath,
68
+ relativePath: path.join("inbox", fileName),
69
+ source
70
+ };
71
+ }
72
+ function parseInboxFile(vaultPath, fullPath) {
73
+ const stats = fs.statSync(fullPath);
74
+ const relativePath = path.relative(path.resolve(vaultPath), fullPath);
75
+ const ext = path.extname(fullPath).toLowerCase();
76
+ const raw = fs.readFileSync(fullPath, "utf-8");
77
+ if (ext === ".md") {
78
+ const parsed = matter(raw);
79
+ const title = typeof parsed.data.title === "string" ? parsed.data.title : path.basename(fullPath, ext);
80
+ const capturedAt = typeof parsed.data.capturedAt === "string" ? new Date(parsed.data.capturedAt) : stats.mtime;
81
+ const content2 = parsed.content.trim();
82
+ return {
83
+ id: relativePath.replace(/\\/g, "/").replace(/\.md$/, ""),
84
+ hash: hashContent(content2),
85
+ title,
86
+ content: content2,
87
+ path: fullPath,
88
+ relativePath: relativePath.replace(/\\/g, "/"),
89
+ capturedAt,
90
+ frontmatter: parsed.data
91
+ };
92
+ }
93
+ const content = raw.trim();
94
+ return {
95
+ id: relativePath.replace(/\\/g, "/"),
96
+ hash: hashContent(content),
97
+ title: path.basename(fullPath),
98
+ content,
99
+ path: fullPath,
100
+ relativePath: relativePath.replace(/\\/g, "/"),
101
+ capturedAt: stats.mtime,
102
+ frontmatter: {}
103
+ };
104
+ }
105
+ function readInboxItems(vaultPath, options = {}) {
106
+ const inboxDir = path.join(path.resolve(vaultPath), "inbox");
107
+ if (!fs.existsSync(inboxDir)) {
108
+ return [];
109
+ }
110
+ const ignore = ["**/processed/**", "**/merged/**"];
111
+ if (!options.includeArchived) {
112
+ ignore.push("**/archive/**");
113
+ }
114
+ const files = globSync("**/*", {
115
+ cwd: inboxDir,
116
+ nodir: true,
117
+ absolute: true,
118
+ ignore
119
+ });
120
+ const items = files.map((filePath) => parseInboxFile(vaultPath, filePath)).sort((left, right) => left.capturedAt.getTime() - right.capturedAt.getTime());
121
+ if (options.limit && options.limit > 0) {
122
+ return items.slice(0, options.limit);
123
+ }
124
+ return items;
125
+ }
126
+
127
+ export {
128
+ addInboxItem,
129
+ readInboxItems
130
+ };
@@ -4,7 +4,21 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __getProtoOf = Object.getPrototypeOf;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __commonJS = (cb, mod) => function __require() {
7
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
+ }) : x)(function(x) {
10
+ if (typeof require !== "undefined") return require.apply(this, arguments);
11
+ throw Error('Dynamic require of "' + x + '" is not supported');
12
+ });
13
+ var __glob = (map) => (path) => {
14
+ var fn = map[path];
15
+ if (fn) return fn();
16
+ throw new Error("Module not found in bundle: " + path);
17
+ };
18
+ var __esm = (fn, res) => function __init() {
19
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
20
+ };
21
+ var __commonJS = (cb, mod) => function __require2() {
8
22
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
23
  };
10
24
  var __export = (target, all) => {
@@ -29,6 +43,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
43
  ));
30
44
 
31
45
  export {
46
+ __require,
47
+ __glob,
48
+ __esm,
32
49
  __commonJS,
33
50
  __export,
34
51
  __toESM
@@ -1,11 +1,123 @@
1
1
  // src/lib/memory-graph.ts
2
2
  import * as fs from "fs";
3
- import * as path from "path";
3
+ import * as path2 from "path";
4
4
  import matter from "gray-matter";
5
5
  import { glob } from "glob";
6
+
7
+ // src/lib/wiki-links.ts
8
+ import * as path from "path";
9
+ function stripInlineCode(line) {
10
+ let result = "";
11
+ let cursor = 0;
12
+ while (cursor < line.length) {
13
+ if (line[cursor] !== "`") {
14
+ result += line[cursor];
15
+ cursor += 1;
16
+ continue;
17
+ }
18
+ let fenceLength = 1;
19
+ while (cursor + fenceLength < line.length && line[cursor + fenceLength] === "`") {
20
+ fenceLength += 1;
21
+ }
22
+ const fence = "`".repeat(fenceLength);
23
+ const closeIndex = line.indexOf(fence, cursor + fenceLength);
24
+ if (closeIndex === -1) {
25
+ result += fence;
26
+ cursor += fenceLength;
27
+ continue;
28
+ }
29
+ cursor = closeIndex + fenceLength;
30
+ }
31
+ return result;
32
+ }
33
+ function parseFence(line) {
34
+ const match = line.match(/^[ \t]{0,3}(`{3,}|~{3,})/);
35
+ if (!match) return null;
36
+ const marker = match[1][0];
37
+ if (marker !== "`" && marker !== "~") return null;
38
+ return { marker, length: match[1].length };
39
+ }
40
+ function isFenceClose(line, fence) {
41
+ const re = new RegExp(`^[ \\t]{0,3}${fence.marker}{${fence.length},}[ \\t]*$`);
42
+ return re.test(line);
43
+ }
44
+ function stripMarkdownCode(markdownContent) {
45
+ const lines = markdownContent.split("\n");
46
+ const visibleLines = [];
47
+ let activeFence = null;
48
+ for (const line of lines) {
49
+ if (activeFence) {
50
+ if (isFenceClose(line, activeFence)) {
51
+ activeFence = null;
52
+ }
53
+ visibleLines.push("");
54
+ continue;
55
+ }
56
+ const openingFence = parseFence(line);
57
+ if (openingFence) {
58
+ activeFence = openingFence;
59
+ visibleLines.push("");
60
+ continue;
61
+ }
62
+ if (/^(?: {4}|\t)/.test(line)) {
63
+ visibleLines.push("");
64
+ continue;
65
+ }
66
+ visibleLines.push(stripInlineCode(line));
67
+ }
68
+ return visibleLines.join("\n");
69
+ }
70
+ function extractRawWikiLinks(markdownContent) {
71
+ const content = stripMarkdownCode(markdownContent);
72
+ const links = [];
73
+ let cursor = 0;
74
+ while (cursor < content.length) {
75
+ const start = content.indexOf("[[", cursor);
76
+ if (start === -1) break;
77
+ const end = content.indexOf("]]", start + 2);
78
+ if (end === -1) break;
79
+ const candidate = content.slice(start + 2, end).trim();
80
+ if (candidate) {
81
+ links.push(candidate);
82
+ }
83
+ cursor = end + 2;
84
+ }
85
+ return links;
86
+ }
87
+ function normalizeWikiLinkTarget(rawTarget) {
88
+ let value = rawTarget.trim();
89
+ if (!value) return "";
90
+ if (value.startsWith("[[") && value.endsWith("]]")) {
91
+ value = value.slice(2, -2).trim();
92
+ }
93
+ const pipeIndex = value.indexOf("|");
94
+ if (pipeIndex >= 0) {
95
+ value = value.slice(0, pipeIndex);
96
+ }
97
+ if (value.startsWith("#")) {
98
+ return "";
99
+ }
100
+ const hashIndex = value.indexOf("#");
101
+ if (hashIndex >= 0) {
102
+ value = value.slice(0, hashIndex);
103
+ }
104
+ value = value.trim().replace(/\\/g, "/").replace(/^\/+/, "");
105
+ if (!value) return "";
106
+ value = value.split("/").map((segment) => segment.trim()).join("/");
107
+ const normalizedPath = path.posix.normalize(value);
108
+ if (!normalizedPath || normalizedPath === ".") {
109
+ return "";
110
+ }
111
+ value = normalizedPath;
112
+ if (value.toLowerCase().endsWith(".md")) {
113
+ value = value.slice(0, -3);
114
+ }
115
+ return value.trim();
116
+ }
117
+
118
+ // src/lib/memory-graph.ts
6
119
  var MEMORY_GRAPH_SCHEMA_VERSION = 1;
7
- var GRAPH_INDEX_RELATIVE_PATH = path.join(".clawvault", "graph-index.json");
8
- var WIKI_LINK_RE = /\[\[([^\]]+)\]\]/g;
120
+ var GRAPH_INDEX_RELATIVE_PATH = path2.join(".clawvault", "graph-index.json");
9
121
  var HASH_TAG_RE = /(^|\s)#([\w-]+)/g;
10
122
  var FRONTMATTER_RELATION_FIELDS = [
11
123
  "related",
@@ -19,7 +131,7 @@ var FRONTMATTER_RELATION_FIELDS = [
19
131
  "links"
20
132
  ];
21
133
  function normalizeRelativePath(value) {
22
- return value.split(path.sep).join("/").replace(/^\.\//, "").replace(/^\/+/, "");
134
+ return value.split(path2.sep).join("/").replace(/^\.\//, "").replace(/^\/+/, "");
23
135
  }
24
136
  function toNoteKey(relativePath) {
25
137
  const normalized = normalizeRelativePath(relativePath);
@@ -57,31 +169,14 @@ function inferNodeType(relativePath, frontmatter) {
57
169
  return "note";
58
170
  }
59
171
  function ensureClawvaultDir(vaultPath) {
60
- const dirPath = path.join(vaultPath, ".clawvault");
172
+ const dirPath = path2.join(vaultPath, ".clawvault");
61
173
  if (!fs.existsSync(dirPath)) {
62
174
  fs.mkdirSync(dirPath, { recursive: true });
63
175
  }
64
176
  return dirPath;
65
177
  }
66
178
  function getGraphIndexPath(vaultPath) {
67
- return path.join(vaultPath, GRAPH_INDEX_RELATIVE_PATH);
68
- }
69
- function normalizeWikiTarget(target) {
70
- let value = target.trim();
71
- if (!value) return "";
72
- const pipeIndex = value.indexOf("|");
73
- if (pipeIndex >= 0) {
74
- value = value.slice(0, pipeIndex);
75
- }
76
- const hashIndex = value.indexOf("#");
77
- if (hashIndex >= 0) {
78
- value = value.slice(0, hashIndex);
79
- }
80
- value = value.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
81
- if (value.toLowerCase().endsWith(".md")) {
82
- value = value.slice(0, -3);
83
- }
84
- return value.trim();
179
+ return path2.join(vaultPath, GRAPH_INDEX_RELATIVE_PATH);
85
180
  }
86
181
  function collectTags(frontmatter, markdownContent) {
87
182
  const tags = /* @__PURE__ */ new Set();
@@ -105,14 +200,15 @@ function collectTags(frontmatter, markdownContent) {
105
200
  }
106
201
  function extractWikiTargets(markdownContent) {
107
202
  const targets = /* @__PURE__ */ new Set();
108
- for (const match of markdownContent.matchAll(WIKI_LINK_RE)) {
109
- const candidate = match[1];
110
- if (!candidate) continue;
203
+ for (const candidate of extractRawWikiLinks(markdownContent)) {
111
204
  const normalized = normalizeWikiTarget(candidate);
112
205
  if (normalized) targets.add(normalized);
113
206
  }
114
207
  return [...targets];
115
208
  }
209
+ function normalizeWikiTarget(target) {
210
+ return normalizeWikiLinkTarget(target);
211
+ }
116
212
  function toStringArray(value) {
117
213
  if (typeof value === "string") {
118
214
  return value.split(",").map((entry) => entry.trim()).filter(Boolean);
@@ -150,15 +246,57 @@ function buildNoteRegistry(relativePaths) {
150
246
  }
151
247
  return { byLowerPath, byLowerBasename };
152
248
  }
153
- function resolveTargetNodeId(rawTarget, registry) {
249
+ function normalizeLookupPath(candidate) {
250
+ const normalized = normalizeWikiTarget(candidate);
251
+ if (!normalized) return "";
252
+ const resolved = path2.posix.normalize(normalized).replace(/^\/+/, "");
253
+ if (!resolved || resolved === "." || resolved.startsWith("../")) {
254
+ return "";
255
+ }
256
+ return resolved;
257
+ }
258
+ function buildTargetLookupCandidates(target, sourceNoteKey) {
259
+ const candidates = [];
260
+ const sourceDir = path2.posix.dirname(sourceNoteKey);
261
+ const hasSourceDir = sourceDir !== ".";
262
+ const isRelativeTarget = target.startsWith("./") || target.startsWith("../");
263
+ const addCandidate = (candidate) => {
264
+ const normalized = normalizeLookupPath(candidate);
265
+ if (!normalized || candidates.includes(normalized)) return;
266
+ candidates.push(normalized);
267
+ };
268
+ if (isRelativeTarget) {
269
+ if (hasSourceDir) {
270
+ addCandidate(path2.posix.join(sourceDir, target));
271
+ } else {
272
+ addCandidate(target);
273
+ }
274
+ if (target.startsWith("./")) {
275
+ addCandidate(target.slice(2));
276
+ }
277
+ return candidates;
278
+ }
279
+ if (!target.includes("/")) {
280
+ if (hasSourceDir) {
281
+ addCandidate(`${sourceDir}/${target}`);
282
+ }
283
+ addCandidate(target);
284
+ return candidates;
285
+ }
286
+ addCandidate(target);
287
+ return candidates;
288
+ }
289
+ function resolveTargetNodeId(rawTarget, registry, sourceNoteKey) {
154
290
  const normalized = normalizeWikiTarget(rawTarget);
155
291
  if (!normalized) {
156
292
  return toUnresolvedNodeId(rawTarget);
157
293
  }
158
294
  const lowerTarget = normalized.toLowerCase();
159
- const direct = registry.byLowerPath.get(lowerTarget);
160
- if (direct) {
161
- return toNoteNodeId(direct);
295
+ for (const candidate of buildTargetLookupCandidates(normalized, sourceNoteKey)) {
296
+ const direct = registry.byLowerPath.get(candidate.toLowerCase());
297
+ if (direct) {
298
+ return toNoteNodeId(direct);
299
+ }
162
300
  }
163
301
  if (!normalized.includes("/")) {
164
302
  const basenameMatches = registry.byLowerBasename.get(lowerTarget) ?? [];
@@ -186,7 +324,7 @@ function buildFragmentNode(id, title, type, category, pathValue, tags, missing,
186
324
  };
187
325
  }
188
326
  function parseFileFragment(vaultPath, relativePath, mtimeMs, registry) {
189
- const absolutePath = path.join(vaultPath, relativePath);
327
+ const absolutePath = path2.join(vaultPath, relativePath);
190
328
  const raw = fs.readFileSync(absolutePath, "utf-8");
191
329
  const parsed = matter(raw);
192
330
  const frontmatter = parsed.data ?? {};
@@ -225,7 +363,7 @@ function parseFileFragment(vaultPath, relativePath, mtimeMs, registry) {
225
363
  }
226
364
  const wikiTargets = extractWikiTargets(parsed.content);
227
365
  for (const target of wikiTargets) {
228
- const targetNodeId = resolveTargetNodeId(target, registry);
366
+ const targetNodeId = resolveTargetNodeId(target, registry, noteKey);
229
367
  if (targetNodeId.startsWith("unresolved:") && !nodes.has(targetNodeId)) {
230
368
  nodes.set(
231
369
  targetNodeId,
@@ -241,7 +379,7 @@ function parseFileFragment(vaultPath, relativePath, mtimeMs, registry) {
241
379
  });
242
380
  }
243
381
  for (const relation of extractFrontmatterRelations(frontmatter)) {
244
- const targetNodeId = resolveTargetNodeId(relation.target, registry);
382
+ const targetNodeId = resolveTargetNodeId(relation.target, registry, noteKey);
245
383
  if (targetNodeId.startsWith("unresolved:") && !nodes.has(targetNodeId)) {
246
384
  nodes.set(
247
385
  targetNodeId,
@@ -326,7 +464,7 @@ function isValidIndex(index) {
326
464
  return typed.schemaVersion === MEMORY_GRAPH_SCHEMA_VERSION && typeof typed.vaultPath === "string" && typeof typed.generatedAt === "string" && Boolean(typed.files && typeof typed.files === "object") && Boolean(typed.graph && typeof typed.graph === "object");
327
465
  }
328
466
  function loadMemoryGraphIndex(vaultPath) {
329
- const indexPath = getGraphIndexPath(path.resolve(vaultPath));
467
+ const indexPath = getGraphIndexPath(path2.resolve(vaultPath));
330
468
  if (!fs.existsSync(indexPath)) {
331
469
  return null;
332
470
  }
@@ -341,7 +479,7 @@ function loadMemoryGraphIndex(vaultPath) {
341
479
  }
342
480
  }
343
481
  async function buildOrUpdateMemoryGraphIndex(vaultPathInput, options = {}) {
344
- const vaultPath = path.resolve(vaultPathInput);
482
+ const vaultPath = path2.resolve(vaultPathInput);
345
483
  ensureClawvaultDir(vaultPath);
346
484
  const existing = options.forceFull ? null : loadMemoryGraphIndex(vaultPath);
347
485
  const markdownFiles = await glob("**/*.md", {
@@ -354,7 +492,7 @@ async function buildOrUpdateMemoryGraphIndex(vaultPathInput, options = {}) {
354
492
  const existingFragments = existing?.files ?? {};
355
493
  const currentFileSet = new Set(normalizedFiles);
356
494
  for (const relativePath of normalizedFiles) {
357
- const absolutePath = path.join(vaultPath, relativePath);
495
+ const absolutePath = path2.join(vaultPath, relativePath);
358
496
  const stat = fs.statSync(absolutePath);
359
497
  const existingFragment = existingFragments[relativePath];
360
498
  if (!options.forceFull && existingFragment && existingFragment.mtimeMs === stat.mtimeMs) {
@@ -391,6 +529,8 @@ async function getMemoryGraph(vaultPath, options = {}) {
391
529
  }
392
530
 
393
531
  export {
532
+ extractRawWikiLinks,
533
+ normalizeWikiLinkTarget,
394
534
  MEMORY_GRAPH_SCHEMA_VERSION,
395
535
  loadMemoryGraphIndex,
396
536
  buildOrUpdateMemoryGraphIndex,
@@ -0,0 +1,158 @@
1
+ // src/lib/maintenance/heuristics.ts
2
+ var CATEGORY_PATTERNS = [
3
+ {
4
+ category: "decisions",
5
+ patterns: [
6
+ /\b(decid(?:e|ed|ing|ion)|chose|selected|opted|trade[- ]?off)\b/i,
7
+ /\b(approved|agreed|consensus)\b/i
8
+ ]
9
+ },
10
+ {
11
+ category: "lessons",
12
+ patterns: [
13
+ /\b(learn(?:ed|ing|t)|lesson|insight|realized|retrospective)\b/i,
14
+ /\b(next time|note to self|mistake)\b/i
15
+ ]
16
+ },
17
+ {
18
+ category: "commitments",
19
+ patterns: [
20
+ /\b(todo|task|action item|follow[- ]?up|deadline|due)\b/i,
21
+ /\b(i will|we will|must|need to)\b/i
22
+ ]
23
+ },
24
+ {
25
+ category: "people",
26
+ patterns: [
27
+ /\b(met with|talked to|spoke with|emailed|called|messaged)\b/i,
28
+ /\b(client|customer|partner|colleague|contact)\b/i
29
+ ]
30
+ },
31
+ {
32
+ category: "projects",
33
+ patterns: [
34
+ /\b(project|feature|release|deployment|deploy|service|api|repo)\b/i,
35
+ /\b(shipped|launched|merged|rolled out)\b/i
36
+ ]
37
+ },
38
+ {
39
+ category: "preferences",
40
+ patterns: [
41
+ /\b(prefer(?:s|red|ence)?|like(?:s|d)?|dislike|style|convention)\b/i,
42
+ /\b(always use|never use|default to)\b/i
43
+ ]
44
+ },
45
+ {
46
+ category: "facts",
47
+ patterns: [
48
+ /\b(is|are|was|were|has|have|contains|includes)\b/i
49
+ ]
50
+ }
51
+ ];
52
+ function splitSentences(content) {
53
+ return content.split(/\r?\n|(?<=[.?!])\s+/).map((sentence) => sentence.trim()).filter(Boolean);
54
+ }
55
+ function compactWhitespace(value) {
56
+ return value.toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
57
+ }
58
+ var STOP_WORDS = /* @__PURE__ */ new Set([
59
+ "the",
60
+ "a",
61
+ "an",
62
+ "and",
63
+ "or",
64
+ "to",
65
+ "of",
66
+ "for",
67
+ "in",
68
+ "on",
69
+ "is",
70
+ "are",
71
+ "was",
72
+ "were",
73
+ "be",
74
+ "this",
75
+ "that",
76
+ "it",
77
+ "with",
78
+ "as",
79
+ "at",
80
+ "by",
81
+ "from",
82
+ "we",
83
+ "i",
84
+ "you",
85
+ "they",
86
+ "he",
87
+ "she"
88
+ ]);
89
+ function classifyInboxItemHeuristic(item) {
90
+ const sample = `${item.title}
91
+ ${item.content.slice(0, 2400)}`;
92
+ for (const rule of CATEGORY_PATTERNS) {
93
+ if (rule.patterns.some((pattern) => pattern.test(sample))) {
94
+ return rule.category;
95
+ }
96
+ }
97
+ return "inbox";
98
+ }
99
+ function normalizeForDedup(content) {
100
+ return compactWhitespace(content.replace(/\[\[[^\]]+\]\]/g, ""));
101
+ }
102
+ function wordSet(content) {
103
+ const words = compactWhitespace(content).split(" ").filter(Boolean);
104
+ const filtered = words.filter((word) => !STOP_WORDS.has(word) && word.length > 2);
105
+ return new Set(filtered);
106
+ }
107
+ function similarityScore(left, right) {
108
+ const leftWords = wordSet(left);
109
+ const rightWords = wordSet(right);
110
+ if (leftWords.size === 0 || rightWords.size === 0) {
111
+ return 0;
112
+ }
113
+ let intersection = 0;
114
+ for (const word of leftWords) {
115
+ if (rightWords.has(word)) {
116
+ intersection += 1;
117
+ }
118
+ }
119
+ const union = leftWords.size + rightWords.size - intersection;
120
+ return union === 0 ? 0 : intersection / union;
121
+ }
122
+ function extractHeuristicInsights(content) {
123
+ const sentences = splitSentences(content);
124
+ const facts = sentences.filter((line) => /\b(is|are|was|were|has|have|contains|includes)\b/i.test(line)).slice(0, 8);
125
+ const decisions = sentences.filter((line) => /\b(decid(?:e|ed|ing|ion)|chose|selected|opted|agreed)\b/i.test(line)).slice(0, 8);
126
+ const lessons = sentences.filter((line) => /\b(learn(?:ed|ing|t)|lesson|insight|realized|next time|mistake)\b/i.test(line)).slice(0, 8);
127
+ if (facts.length === 0 && sentences.length > 0) {
128
+ facts.push(sentences[0]);
129
+ }
130
+ return { facts, decisions, lessons };
131
+ }
132
+ function buildHeuristicSurveyRecommendations(params) {
133
+ const recommendations = [];
134
+ if (params.inboxCount > 20) {
135
+ recommendations.push(`Inbox backlog is high (${params.inboxCount}); run \`clawvault maintain --worker curator\` more frequently.`);
136
+ }
137
+ if (params.linkedRatio < 0.25) {
138
+ recommendations.push("Graph connectivity is low; add wiki-links between related notes to improve context traversal.");
139
+ }
140
+ if ((params.categoryCounts.lessons ?? 0) < 5) {
141
+ recommendations.push("Lessons are sparse; run distillation on long-form captures to keep reusable learnings explicit.");
142
+ }
143
+ if ((params.categoryCounts.decisions ?? 0) < 5) {
144
+ recommendations.push("Decision coverage is light; capture major choices and rationale in decisions/.");
145
+ }
146
+ if (recommendations.length === 0) {
147
+ recommendations.push("Vault health looks balanced. Keep regular maintenance cadence and continue linking related notes.");
148
+ }
149
+ return recommendations;
150
+ }
151
+
152
+ export {
153
+ classifyInboxItemHeuristic,
154
+ normalizeForDedup,
155
+ similarityScore,
156
+ extractHeuristicInsights,
157
+ buildHeuristicSurveyRecommendations
158
+ };
@@ -18,6 +18,18 @@ function extractTitle(content) {
18
18
  function isDateSlug(slug) {
19
19
  return /^\d{4}-\d{2}-\d{2}$/.test(slug);
20
20
  }
21
+ function buildProjectSlug(title) {
22
+ const direct = slugify(title);
23
+ if (direct) {
24
+ return direct;
25
+ }
26
+ let hash = 0;
27
+ for (const char of title) {
28
+ hash = (hash << 5) - hash + char.charCodeAt(0);
29
+ hash |= 0;
30
+ }
31
+ return `project-${Math.abs(hash).toString(36)}`;
32
+ }
21
33
  function normalizeStringArray(value) {
22
34
  return value.map((item) => item.trim()).filter(Boolean);
23
35
  }
@@ -242,7 +254,7 @@ function readProject(vaultPath, slug) {
242
254
  }
243
255
  function createProject(vaultPath, title, options = {}) {
244
256
  ensureProjectsDir(vaultPath);
245
- const slug = slugify(title);
257
+ const slug = buildProjectSlug(title);
246
258
  const projectPath = getProjectPath(vaultPath, slug);
247
259
  if (fs.existsSync(projectPath)) {
248
260
  throw new Error(`Project already exists: ${slug}`);