clawvault 3.1.0 → 3.2.1
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.
- package/README.md +422 -141
- package/bin/clawvault.js +10 -2
- package/bin/command-registration.test.js +3 -1
- package/bin/command-runtime.js +9 -1
- package/bin/register-core-commands.js +23 -28
- package/bin/register-maintenance-commands.js +39 -3
- package/bin/register-query-commands.js +58 -29
- package/bin/register-tailscale-commands.js +106 -0
- package/bin/register-task-commands.js +18 -1
- package/bin/register-task-commands.test.js +16 -0
- package/bin/register-vault-operations-commands.js +29 -1
- package/bin/register-workgraph-commands.js +451 -0
- package/dashboard/lib/graph-diff.js +104 -0
- package/dashboard/lib/graph-diff.test.js +75 -0
- package/dashboard/lib/vault-parser.js +556 -0
- package/dashboard/lib/vault-parser.test.js +254 -0
- package/dashboard/public/app.js +796 -0
- package/dashboard/public/index.html +52 -0
- package/dashboard/public/styles.css +221 -0
- package/dashboard/server.js +374 -0
- package/dist/{chunk-C7OK5WKP.js → chunk-2JQ3O2YL.js} +4 -4
- package/dist/{chunk-VR5NE7PZ.js → chunk-2RAZ4ZFE.js} +1 -1
- package/dist/{chunk-F2JEUD4J.js → chunk-4ITRXIVT.js} +5 -7
- package/dist/{chunk-GUKMRGM7.js → chunk-4OXMU5S2.js} +1 -1
- package/dist/chunk-5PJ4STIC.js +465 -0
- package/dist/{chunk-62YTUT6J.js → chunk-AZYOKJYC.js} +2 -2
- package/dist/chunk-BSJ6RIT7.js +447 -0
- package/dist/chunk-ECRZL5XR.js +50 -0
- package/dist/chunk-ERNE2FZ5.js +189 -0
- package/dist/{chunk-WAZ3NLWL.js → chunk-F55HGNU4.js} +0 -47
- package/dist/{chunk-VGLOTGAS.js → chunk-FAKNOB7Y.js} +2 -2
- package/dist/{chunk-QK3UCXWL.js → chunk-FHFUXL6G.js} +2 -2
- package/dist/chunk-GNJL4YGR.js +79 -0
- package/dist/chunk-HR4KN6S2.js +152 -0
- package/dist/{chunk-OZ7RIXTO.js → chunk-IIOU45CK.js} +1 -1
- package/dist/chunk-IJBFGPCS.js +33 -0
- package/dist/chunk-IVRIKYFE.js +520 -0
- package/dist/chunk-K7PNYS45.js +93 -0
- package/dist/chunk-MDIH26GC.js +183 -0
- package/dist/{chunk-LYHGEHXG.js → chunk-MFAWT5O5.js} +0 -1
- package/dist/{chunk-H34S76MB.js → chunk-MNPUYCHQ.js} +6 -6
- package/dist/chunk-NTOPJI7W.js +207 -0
- package/dist/{chunk-QBLMXKF2.js → chunk-OIWVQYQF.js} +1 -1
- package/dist/chunk-PG56HX5T.js +154 -0
- package/dist/{chunk-LNJA2UGL.js → chunk-PI4WMLMG.js} +7 -84
- package/dist/chunk-QMHPQYUV.js +363 -0
- package/dist/{chunk-H62BP7RI.js → chunk-QPDDIHXE.js} +209 -43
- package/dist/{chunk-N2AXRYLC.js → chunk-QWQ3TIKS.js} +1 -1
- package/dist/{chunk-3DHXQHYG.js → chunk-R2MIW5G7.js} +1 -1
- package/dist/{chunk-SJSFRIYS.js → chunk-S5OJEGFG.js} +2 -2
- package/dist/chunk-SS4B7P7V.js +99 -0
- package/dist/chunk-TIGW564L.js +628 -0
- package/dist/chunk-U67V476Y.js +35 -0
- package/dist/{chunk-JY6FYXIT.js → chunk-UCQAOZHW.js} +6 -11
- package/dist/{chunk-ITPEXLHA.js → chunk-URXDAUVH.js} +24 -5
- package/dist/chunk-WIOLLGAD.js +190 -0
- package/dist/{chunk-3WRJEKN4.js → chunk-WJVWINEM.js} +72 -8
- package/dist/chunk-WMGIIABP.js +15 -0
- package/dist/{chunk-33UGEQRT.js → chunk-X3SPPUFG.js} +151 -64
- package/dist/{chunk-3NSBOUT3.js → chunk-Y3TIJEBP.js} +314 -79
- package/dist/chunk-Y6VJKXGL.js +373 -0
- package/dist/{chunk-LI4O6NVK.js → chunk-YDWHS4LJ.js} +49 -9
- package/dist/{chunk-U55BGUAU.js → chunk-YNIPYN4F.js} +5 -5
- package/dist/chunk-YXQCA6B7.js +226 -0
- package/dist/cli/index.js +26 -22
- package/dist/commands/archive.js +3 -3
- package/dist/commands/backlog.js +3 -3
- package/dist/commands/blocked.js +3 -3
- package/dist/commands/canvas.d.ts +15 -0
- package/dist/commands/canvas.js +200 -0
- package/dist/commands/checkpoint.js +2 -2
- package/dist/commands/compat.js +2 -2
- package/dist/commands/context.js +7 -5
- package/dist/commands/doctor.d.ts +11 -7
- package/dist/commands/doctor.js +16 -14
- package/dist/commands/embed.js +5 -6
- package/dist/commands/entities.js +2 -2
- package/dist/commands/graph.js +3 -3
- package/dist/commands/inject.d.ts +1 -1
- package/dist/commands/inject.js +4 -5
- package/dist/commands/kanban.js +4 -4
- package/dist/commands/link.js +2 -2
- package/dist/commands/migrate-observations.js +4 -4
- package/dist/commands/observe.d.ts +0 -1
- package/dist/commands/observe.js +13 -12
- package/dist/commands/project.js +5 -5
- package/dist/commands/rebuild-embeddings.d.ts +21 -0
- package/dist/commands/rebuild-embeddings.js +91 -0
- package/dist/commands/rebuild.js +12 -11
- package/dist/commands/recover.js +3 -3
- package/dist/commands/reflect.js +6 -7
- package/dist/commands/repair-session.js +1 -1
- package/dist/commands/replay.js +14 -14
- package/dist/commands/session-recap.js +1 -1
- package/dist/commands/setup.d.ts +2 -89
- package/dist/commands/setup.js +3 -21
- package/dist/commands/shell-init.js +1 -1
- package/dist/commands/sleep.d.ts +1 -1
- package/dist/commands/sleep.js +18 -17
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +40 -30
- package/dist/commands/sync-bd.d.ts +10 -0
- package/dist/commands/sync-bd.js +10 -0
- package/dist/commands/tailscale.d.ts +52 -0
- package/dist/commands/tailscale.js +26 -0
- package/dist/commands/task.js +4 -4
- package/dist/commands/template.js +2 -2
- package/dist/commands/wake.d.ts +1 -1
- package/dist/commands/wake.js +11 -10
- package/dist/index.d.ts +334 -191
- package/dist/index.js +432 -108
- package/dist/{inject-Bzi5E-By.d.ts → inject-DYUrDqQO.d.ts} +3 -3
- package/dist/ledger-B7g7jhqG.d.ts +44 -0
- package/dist/lib/auto-linker.js +1 -1
- package/dist/lib/canvas-layout.d.ts +115 -0
- package/dist/lib/canvas-layout.js +35 -0
- package/dist/lib/config.d.ts +27 -3
- package/dist/lib/config.js +4 -2
- package/dist/lib/entity-index.js +1 -1
- package/dist/lib/project-utils.js +4 -4
- package/dist/lib/session-repair.js +1 -1
- package/dist/lib/session-utils.js +1 -1
- package/dist/lib/tailscale.d.ts +225 -0
- package/dist/lib/tailscale.js +50 -0
- package/dist/lib/task-utils.js +3 -3
- package/dist/lib/template-engine.js +1 -1
- package/dist/lib/webdav.d.ts +109 -0
- package/dist/lib/webdav.js +35 -0
- package/dist/plugin/index.d.ts +344 -28
- package/dist/plugin/index.js +3919 -227
- package/dist/registry-BR4326o0.d.ts +30 -0
- package/dist/store-CA-6sKCJ.d.ts +34 -0
- package/dist/thread-B9LhXNU0.d.ts +41 -0
- package/dist/{types-Y2_Um2Ls.d.ts → types-BbWJoC1c.d.ts} +1 -44
- package/dist/workgraph/index.d.ts +5 -0
- package/dist/workgraph/index.js +23 -0
- package/dist/workgraph/ledger.d.ts +2 -0
- package/dist/workgraph/ledger.js +25 -0
- package/dist/workgraph/registry.d.ts +2 -0
- package/dist/workgraph/registry.js +19 -0
- package/dist/workgraph/store.d.ts +2 -0
- package/dist/workgraph/store.js +25 -0
- package/dist/workgraph/thread.d.ts +2 -0
- package/dist/workgraph/thread.js +25 -0
- package/dist/workgraph/types.d.ts +54 -0
- package/dist/workgraph/types.js +7 -0
- package/hooks/clawvault/HOOK.md +113 -0
- package/hooks/clawvault/handler.js +1559 -0
- package/hooks/clawvault/handler.test.js +510 -0
- package/hooks/clawvault/openclaw.plugin.json +72 -0
- package/openclaw.plugin.json +235 -30
- package/package.json +20 -20
- package/dist/chunk-3RG5ZIWI.js +0 -10
- package/dist/chunk-3ZIH425O.js +0 -871
- package/dist/chunk-6U6MK36V.js +0 -205
- package/dist/chunk-CMB7UL7C.js +0 -327
- package/dist/chunk-D2H45LON.js +0 -1074
- package/dist/chunk-E7MFQB6D.js +0 -163
- package/dist/chunk-GQSLDZTS.js +0 -560
- package/dist/chunk-MFM6K7PU.js +0 -374
- package/dist/chunk-MXSSG3QU.js +0 -42
- package/dist/chunk-OCGVIN3L.js +0 -88
- package/dist/chunk-PAH27GSN.js +0 -108
- package/dist/chunk-YCUNCH2I.js +0 -78
- package/dist/cli/index.cjs +0 -8584
- package/dist/cli/index.d.cts +0 -5
- package/dist/commands/archive.cjs +0 -287
- package/dist/commands/archive.d.cts +0 -11
- package/dist/commands/backlog.cjs +0 -721
- package/dist/commands/backlog.d.cts +0 -53
- package/dist/commands/blocked.cjs +0 -204
- package/dist/commands/blocked.d.cts +0 -26
- package/dist/commands/checkpoint.cjs +0 -244
- package/dist/commands/checkpoint.d.cts +0 -41
- package/dist/commands/compat.cjs +0 -294
- package/dist/commands/compat.d.cts +0 -28
- package/dist/commands/context.cjs +0 -2990
- package/dist/commands/context.d.cts +0 -2
- package/dist/commands/doctor.cjs +0 -2986
- package/dist/commands/doctor.d.cts +0 -21
- package/dist/commands/embed.cjs +0 -232
- package/dist/commands/embed.d.cts +0 -17
- package/dist/commands/entities.cjs +0 -141
- package/dist/commands/entities.d.cts +0 -7
- package/dist/commands/graph.cjs +0 -501
- package/dist/commands/graph.d.cts +0 -21
- package/dist/commands/inject.cjs +0 -1636
- package/dist/commands/inject.d.cts +0 -2
- package/dist/commands/kanban.cjs +0 -884
- package/dist/commands/kanban.d.cts +0 -63
- package/dist/commands/link.cjs +0 -965
- package/dist/commands/link.d.cts +0 -11
- package/dist/commands/migrate-observations.cjs +0 -362
- package/dist/commands/migrate-observations.d.cts +0 -19
- package/dist/commands/observe.cjs +0 -4099
- package/dist/commands/observe.d.cts +0 -23
- package/dist/commands/project.cjs +0 -1341
- package/dist/commands/project.d.cts +0 -85
- package/dist/commands/rebuild.cjs +0 -3136
- package/dist/commands/rebuild.d.cts +0 -11
- package/dist/commands/recover.cjs +0 -361
- package/dist/commands/recover.d.cts +0 -38
- package/dist/commands/reflect.cjs +0 -1008
- package/dist/commands/reflect.d.cts +0 -11
- package/dist/commands/repair-session.cjs +0 -457
- package/dist/commands/repair-session.d.cts +0 -38
- package/dist/commands/replay.cjs +0 -4103
- package/dist/commands/replay.d.cts +0 -16
- package/dist/commands/session-recap.cjs +0 -353
- package/dist/commands/session-recap.d.cts +0 -27
- package/dist/commands/setup.cjs +0 -1278
- package/dist/commands/setup.d.cts +0 -99
- package/dist/commands/shell-init.cjs +0 -75
- package/dist/commands/shell-init.d.cts +0 -7
- package/dist/commands/sleep.cjs +0 -6029
- package/dist/commands/sleep.d.cts +0 -36
- package/dist/commands/status.cjs +0 -2737
- package/dist/commands/status.d.cts +0 -52
- package/dist/commands/task.cjs +0 -1236
- package/dist/commands/task.d.cts +0 -97
- package/dist/commands/template.cjs +0 -457
- package/dist/commands/template.d.cts +0 -36
- package/dist/commands/wake.cjs +0 -2627
- package/dist/commands/wake.d.cts +0 -22
- package/dist/context-BUGaWpyL.d.cts +0 -46
- package/dist/index.cjs +0 -12373
- package/dist/index.d.cts +0 -854
- package/dist/inject-Bzi5E-By.d.cts +0 -137
- package/dist/lib/auto-linker.cjs +0 -176
- package/dist/lib/auto-linker.d.cts +0 -26
- package/dist/lib/config.cjs +0 -78
- package/dist/lib/config.d.cts +0 -11
- package/dist/lib/entity-index.cjs +0 -84
- package/dist/lib/entity-index.d.cts +0 -26
- package/dist/lib/project-utils.cjs +0 -864
- package/dist/lib/project-utils.d.cts +0 -97
- package/dist/lib/session-repair.cjs +0 -239
- package/dist/lib/session-repair.d.cts +0 -110
- package/dist/lib/session-utils.cjs +0 -209
- package/dist/lib/session-utils.d.cts +0 -63
- package/dist/lib/task-utils.cjs +0 -1137
- package/dist/lib/task-utils.d.cts +0 -208
- package/dist/lib/template-engine.cjs +0 -47
- package/dist/lib/template-engine.d.cts +0 -11
- package/dist/plugin/index.cjs +0 -1907
- package/dist/plugin/index.d.cts +0 -36
- package/dist/plugin/inject.cjs +0 -356
- package/dist/plugin/inject.d.cts +0 -54
- package/dist/plugin/inject.d.ts +0 -54
- package/dist/plugin/inject.js +0 -17
- package/dist/plugin/observe.cjs +0 -631
- package/dist/plugin/observe.d.cts +0 -39
- package/dist/plugin/observe.d.ts +0 -39
- package/dist/plugin/observe.js +0 -18
- package/dist/plugin/templates.cjs +0 -593
- package/dist/plugin/templates.d.cts +0 -52
- package/dist/plugin/templates.d.ts +0 -52
- package/dist/plugin/templates.js +0 -25
- package/dist/plugin/types.cjs +0 -18
- package/dist/plugin/types.d.cts +0 -209
- package/dist/plugin/types.d.ts +0 -209
- package/dist/plugin/types.js +0 -0
- package/dist/plugin/vault.cjs +0 -927
- package/dist/plugin/vault.d.cts +0 -68
- package/dist/plugin/vault.d.ts +0 -68
- package/dist/plugin/vault.js +0 -22
- package/dist/types-Y2_Um2Ls.d.cts +0 -205
- package/templates/memory-event.md +0 -67
- package/templates/party.md +0 -63
- package/templates/primitive-registry.yaml +0 -551
- package/templates/run.md +0 -68
- package/templates/trigger.md +0 -68
- package/templates/workspace.md +0 -50
package/dist/chunk-D2H45LON.js
DELETED
|
@@ -1,1074 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DATE_HEADING_RE,
|
|
3
|
-
parseObservationMarkdown
|
|
4
|
-
} from "./chunk-QK3UCXWL.js";
|
|
5
|
-
import {
|
|
6
|
-
listObservationFiles
|
|
7
|
-
} from "./chunk-Z2XBWN7A.js";
|
|
8
|
-
|
|
9
|
-
// src/lib/reweave.ts
|
|
10
|
-
import * as fs from "fs";
|
|
11
|
-
var SUPERSEDED_MARKER_RE = /\[superseded\|by=([^\]|]+)\|detected=([^\]]+)\]/;
|
|
12
|
-
function isSuperseded(line) {
|
|
13
|
-
return SUPERSEDED_MARKER_RE.test(line);
|
|
14
|
-
}
|
|
15
|
-
function getSupersessionInfo(line) {
|
|
16
|
-
const m = line.match(SUPERSEDED_MARKER_RE);
|
|
17
|
-
if (!m) return null;
|
|
18
|
-
return { supersededBy: m[1], detectedAt: m[2] };
|
|
19
|
-
}
|
|
20
|
-
function makeSupersededMarker(supersedingDate, detectedAt) {
|
|
21
|
-
return ` [superseded|by=${supersedingDate}|detected=${detectedAt}]`;
|
|
22
|
-
}
|
|
23
|
-
function extractEntities(content) {
|
|
24
|
-
const normalized = content.toLowerCase().replace(/['']/g, "'");
|
|
25
|
-
const quoted = [];
|
|
26
|
-
for (const m of normalized.matchAll(/[""]([^""]+)[""]/g)) {
|
|
27
|
-
quoted.push(m[1].trim());
|
|
28
|
-
}
|
|
29
|
-
for (const m of normalized.matchAll(/"([^"]+)"/g)) {
|
|
30
|
-
quoted.push(m[1].trim());
|
|
31
|
-
}
|
|
32
|
-
const patterns = [
|
|
33
|
-
/(\w[\w\s]{1,30}?)\s+(?:is|are|was|were|changed to|switched to|moved to|updated to|now uses?|now lives?|now works?)\s+/gi,
|
|
34
|
-
/(?:uses?|prefers?|likes?|lives? (?:in|at)|works? (?:at|for)|drives?|owns?)\s+([\w\s]{2,30})/gi,
|
|
35
|
-
/(\w[\w\s]{1,20}?)'s\s+(\w[\w\s]{1,20})/gi
|
|
36
|
-
];
|
|
37
|
-
const phrases = [...quoted];
|
|
38
|
-
for (const pat of patterns) {
|
|
39
|
-
for (const m of content.matchAll(pat)) {
|
|
40
|
-
if (m[1]) phrases.push(m[1].trim().toLowerCase());
|
|
41
|
-
if (m[2]) phrases.push(m[2].trim().toLowerCase());
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
const stopwords = /* @__PURE__ */ new Set([
|
|
45
|
-
"the",
|
|
46
|
-
"a",
|
|
47
|
-
"an",
|
|
48
|
-
"is",
|
|
49
|
-
"are",
|
|
50
|
-
"was",
|
|
51
|
-
"were",
|
|
52
|
-
"be",
|
|
53
|
-
"been",
|
|
54
|
-
"being",
|
|
55
|
-
"have",
|
|
56
|
-
"has",
|
|
57
|
-
"had",
|
|
58
|
-
"do",
|
|
59
|
-
"does",
|
|
60
|
-
"did",
|
|
61
|
-
"will",
|
|
62
|
-
"would",
|
|
63
|
-
"could",
|
|
64
|
-
"should",
|
|
65
|
-
"may",
|
|
66
|
-
"might",
|
|
67
|
-
"shall",
|
|
68
|
-
"can",
|
|
69
|
-
"need",
|
|
70
|
-
"dare",
|
|
71
|
-
"ought",
|
|
72
|
-
"used",
|
|
73
|
-
"to",
|
|
74
|
-
"of",
|
|
75
|
-
"in",
|
|
76
|
-
"for",
|
|
77
|
-
"on",
|
|
78
|
-
"with",
|
|
79
|
-
"at",
|
|
80
|
-
"by",
|
|
81
|
-
"from",
|
|
82
|
-
"as",
|
|
83
|
-
"into",
|
|
84
|
-
"through",
|
|
85
|
-
"during",
|
|
86
|
-
"before",
|
|
87
|
-
"after",
|
|
88
|
-
"above",
|
|
89
|
-
"below",
|
|
90
|
-
"between",
|
|
91
|
-
"out",
|
|
92
|
-
"off",
|
|
93
|
-
"over",
|
|
94
|
-
"under",
|
|
95
|
-
"again",
|
|
96
|
-
"further",
|
|
97
|
-
"then",
|
|
98
|
-
"once",
|
|
99
|
-
"and",
|
|
100
|
-
"but",
|
|
101
|
-
"or",
|
|
102
|
-
"nor",
|
|
103
|
-
"not",
|
|
104
|
-
"so",
|
|
105
|
-
"yet",
|
|
106
|
-
"both",
|
|
107
|
-
"either",
|
|
108
|
-
"neither",
|
|
109
|
-
"each",
|
|
110
|
-
"every",
|
|
111
|
-
"all",
|
|
112
|
-
"any",
|
|
113
|
-
"few",
|
|
114
|
-
"more",
|
|
115
|
-
"most",
|
|
116
|
-
"other",
|
|
117
|
-
"some",
|
|
118
|
-
"such",
|
|
119
|
-
"no",
|
|
120
|
-
"only",
|
|
121
|
-
"own",
|
|
122
|
-
"same",
|
|
123
|
-
"than",
|
|
124
|
-
"too",
|
|
125
|
-
"very",
|
|
126
|
-
"just",
|
|
127
|
-
"because",
|
|
128
|
-
"that",
|
|
129
|
-
"this",
|
|
130
|
-
"these",
|
|
131
|
-
"those",
|
|
132
|
-
"it",
|
|
133
|
-
"its",
|
|
134
|
-
"he",
|
|
135
|
-
"she",
|
|
136
|
-
"they",
|
|
137
|
-
"we",
|
|
138
|
-
"you",
|
|
139
|
-
"i",
|
|
140
|
-
"me",
|
|
141
|
-
"my",
|
|
142
|
-
"his",
|
|
143
|
-
"her",
|
|
144
|
-
"our",
|
|
145
|
-
"your",
|
|
146
|
-
"their",
|
|
147
|
-
"pedro",
|
|
148
|
-
"clawdious"
|
|
149
|
-
]);
|
|
150
|
-
const words = normalized.replace(/[^a-z0-9\s'-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopwords.has(w));
|
|
151
|
-
return [.../* @__PURE__ */ new Set([...phrases, ...words])].filter(Boolean);
|
|
152
|
-
}
|
|
153
|
-
function entitySimilarity(a, b) {
|
|
154
|
-
if (a.length === 0 || b.length === 0) return 0;
|
|
155
|
-
const setA = new Set(a);
|
|
156
|
-
const setB = new Set(b);
|
|
157
|
-
let overlap = 0;
|
|
158
|
-
for (const item of setA) {
|
|
159
|
-
if (setB.has(item)) overlap++;
|
|
160
|
-
}
|
|
161
|
-
const union = (/* @__PURE__ */ new Set([...a, ...b])).size;
|
|
162
|
-
return union > 0 ? overlap / union : 0;
|
|
163
|
-
}
|
|
164
|
-
function isKnowledgeUpdate(older, newer, threshold = 0.3) {
|
|
165
|
-
const updateableTypes = /* @__PURE__ */ new Set(["fact", "preference", "decision", "commitment", "project", "relationship"]);
|
|
166
|
-
if (!updateableTypes.has(older.type) && !updateableTypes.has(newer.type)) {
|
|
167
|
-
return { isUpdate: false, reason: "non-updateable types" };
|
|
168
|
-
}
|
|
169
|
-
const olderEntities = extractEntities(older.content);
|
|
170
|
-
const newerEntities = extractEntities(newer.content);
|
|
171
|
-
const similarity = entitySimilarity(olderEntities, newerEntities);
|
|
172
|
-
if (similarity < threshold) {
|
|
173
|
-
return { isUpdate: false, reason: `low entity similarity: ${similarity.toFixed(2)}` };
|
|
174
|
-
}
|
|
175
|
-
const normalizeContent = (s) => s.toLowerCase().replace(/\s+/g, " ").trim();
|
|
176
|
-
if (normalizeContent(older.content) === normalizeContent(newer.content)) {
|
|
177
|
-
return { isUpdate: false, reason: "identical content" };
|
|
178
|
-
}
|
|
179
|
-
return {
|
|
180
|
-
isUpdate: true,
|
|
181
|
-
reason: `entity overlap ${similarity.toFixed(2)}: entities=[${olderEntities.slice(0, 3).join(", ")}]`
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
function loadObservations(vaultPath, since) {
|
|
185
|
-
const files = listObservationFiles(vaultPath, {
|
|
186
|
-
fromDate: since
|
|
187
|
-
});
|
|
188
|
-
return files.map((f) => {
|
|
189
|
-
const content = fs.readFileSync(f.path, "utf-8");
|
|
190
|
-
const records = parseObservationMarkdown(content);
|
|
191
|
-
return { file: f, records };
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
function reweave(options) {
|
|
195
|
-
const { vaultPath, since, dryRun = false, similarityThreshold = 0.3 } = options;
|
|
196
|
-
const allObsFiles = loadObservations(vaultPath);
|
|
197
|
-
const newObsFiles = since ? allObsFiles.filter((f) => f.file.date >= since) : allObsFiles;
|
|
198
|
-
const allRecordsWithFile = [];
|
|
199
|
-
for (const { file, records } of allObsFiles) {
|
|
200
|
-
for (let i = 0; i < records.length; i++) {
|
|
201
|
-
allRecordsWithFile.push({ record: records[i], file, lineIndex: i });
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
allRecordsWithFile.sort((a, b) => a.record.date.localeCompare(b.record.date));
|
|
205
|
-
const supersessions = [];
|
|
206
|
-
const detectedAt = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
207
|
-
for (const { file: newFile, records: newRecords } of newObsFiles) {
|
|
208
|
-
for (const newRec of newRecords) {
|
|
209
|
-
if (isSuperseded(newRec.rawLine)) continue;
|
|
210
|
-
for (const { record: oldRec, file: oldFile } of allRecordsWithFile) {
|
|
211
|
-
if (oldRec.date >= newRec.date && oldFile.path === newFile.path) continue;
|
|
212
|
-
if (oldRec.date > newRec.date) continue;
|
|
213
|
-
if (isSuperseded(oldRec.rawLine)) continue;
|
|
214
|
-
const { isUpdate, reason } = isKnowledgeUpdate(oldRec, newRec, similarityThreshold);
|
|
215
|
-
if (isUpdate) {
|
|
216
|
-
supersessions.push({
|
|
217
|
-
oldObservation: oldRec,
|
|
218
|
-
newObservation: newRec,
|
|
219
|
-
oldFile: oldFile.path,
|
|
220
|
-
newFile: newFile.path,
|
|
221
|
-
reason,
|
|
222
|
-
detectedAt
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (!dryRun && supersessions.length > 0) {
|
|
229
|
-
applySupersessions(supersessions, detectedAt);
|
|
230
|
-
}
|
|
231
|
-
return {
|
|
232
|
-
filesScanned: allObsFiles.length,
|
|
233
|
-
observationsChecked: allRecordsWithFile.length,
|
|
234
|
-
supersessions,
|
|
235
|
-
dryRun
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
function applySupersessions(supersessions, detectedAt) {
|
|
239
|
-
const byFile = /* @__PURE__ */ new Map();
|
|
240
|
-
for (const s of supersessions) {
|
|
241
|
-
const existing = byFile.get(s.oldFile) ?? [];
|
|
242
|
-
existing.push(s);
|
|
243
|
-
byFile.set(s.oldFile, existing);
|
|
244
|
-
}
|
|
245
|
-
for (const [filePath, records] of byFile) {
|
|
246
|
-
let content = fs.readFileSync(filePath, "utf-8");
|
|
247
|
-
for (const s of records) {
|
|
248
|
-
const oldLine = s.oldObservation.rawLine;
|
|
249
|
-
if (content.includes(oldLine) && !isSuperseded(oldLine)) {
|
|
250
|
-
const marker = makeSupersededMarker(s.newObservation.date, detectedAt);
|
|
251
|
-
content = content.replace(oldLine, oldLine + marker);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
function filterSuperseded(lines) {
|
|
258
|
-
return lines.filter((line) => !isSuperseded(line));
|
|
259
|
-
}
|
|
260
|
-
function stripSupersededObservations(markdown) {
|
|
261
|
-
const lines = markdown.split("\n");
|
|
262
|
-
const result = [];
|
|
263
|
-
for (const line of lines) {
|
|
264
|
-
if (DATE_HEADING_RE.test(line) || line.trim() === "") {
|
|
265
|
-
result.push(line);
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
if (isSuperseded(line)) continue;
|
|
269
|
-
result.push(line);
|
|
270
|
-
}
|
|
271
|
-
return result.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// src/lib/search.ts
|
|
275
|
-
import { execFileSync, spawnSync } from "child_process";
|
|
276
|
-
import * as fs2 from "fs";
|
|
277
|
-
import * as path from "path";
|
|
278
|
-
var QMD_INSTALL_URL = "https://github.com/tobi/qmd";
|
|
279
|
-
var QMD_INSTALL_COMMAND = "bun install -g github:tobi/qmd";
|
|
280
|
-
var QMD_NOT_INSTALLED_MESSAGE = `ClawVault requires qmd. Install: ${QMD_INSTALL_COMMAND}`;
|
|
281
|
-
var QMD_INDEX_ENV_VAR = "CLAWVAULT_QMD_INDEX";
|
|
282
|
-
var QmdUnavailableError = class extends Error {
|
|
283
|
-
constructor(message = QMD_NOT_INSTALLED_MESSAGE) {
|
|
284
|
-
super(message);
|
|
285
|
-
this.name = "QmdUnavailableError";
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
function ensureJsonArgs(args) {
|
|
289
|
-
return args.includes("--json") ? args : [...args, "--json"];
|
|
290
|
-
}
|
|
291
|
-
function resolveQmdIndexName(indexName) {
|
|
292
|
-
const explicit = indexName?.trim();
|
|
293
|
-
if (explicit) {
|
|
294
|
-
return explicit;
|
|
295
|
-
}
|
|
296
|
-
const fromEnv = process.env[QMD_INDEX_ENV_VAR]?.trim();
|
|
297
|
-
return fromEnv || void 0;
|
|
298
|
-
}
|
|
299
|
-
function withQmdIndexArgs(args, indexName) {
|
|
300
|
-
if (args.includes("--index")) {
|
|
301
|
-
return [...args];
|
|
302
|
-
}
|
|
303
|
-
const resolvedIndexName = resolveQmdIndexName(indexName);
|
|
304
|
-
if (!resolvedIndexName) {
|
|
305
|
-
return [...args];
|
|
306
|
-
}
|
|
307
|
-
return ["--index", resolvedIndexName, ...args];
|
|
308
|
-
}
|
|
309
|
-
function tryParseJson(raw) {
|
|
310
|
-
try {
|
|
311
|
-
return JSON.parse(raw);
|
|
312
|
-
} catch {
|
|
313
|
-
return null;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
function extractJsonPayload(raw) {
|
|
317
|
-
const start = raw.search(/[\[{]/);
|
|
318
|
-
if (start === -1) return null;
|
|
319
|
-
const end = Math.max(raw.lastIndexOf("]"), raw.lastIndexOf("}"));
|
|
320
|
-
if (end <= start) return null;
|
|
321
|
-
return raw.slice(start, end + 1);
|
|
322
|
-
}
|
|
323
|
-
function stripQmdNoise(raw) {
|
|
324
|
-
return raw.split("\n").filter((line) => {
|
|
325
|
-
const t = line.trim();
|
|
326
|
-
if (!t) return true;
|
|
327
|
-
if (t.startsWith("[node-llama-cpp]")) return false;
|
|
328
|
-
if (t.startsWith("Expanding query")) return false;
|
|
329
|
-
if (t.startsWith("Searching ") && t.endsWith("queries...")) return false;
|
|
330
|
-
if (/^[├└─│]/.test(t)) return false;
|
|
331
|
-
return true;
|
|
332
|
-
}).join("\n");
|
|
333
|
-
}
|
|
334
|
-
function parseQmdOutput(raw) {
|
|
335
|
-
const trimmed = stripQmdNoise(raw).trim();
|
|
336
|
-
if (!trimmed) return [];
|
|
337
|
-
if (trimmed.startsWith("No results") || trimmed.startsWith("No matches")) return [];
|
|
338
|
-
const direct = tryParseJson(trimmed);
|
|
339
|
-
const extracted = direct ? null : extractJsonPayload(trimmed);
|
|
340
|
-
const parsed = direct ?? (extracted ? tryParseJson(extracted) : null);
|
|
341
|
-
if (!parsed) {
|
|
342
|
-
throw new Error("qmd returned non-JSON output. Ensure qmd supports --json.");
|
|
343
|
-
}
|
|
344
|
-
if (Array.isArray(parsed)) {
|
|
345
|
-
return parsed;
|
|
346
|
-
}
|
|
347
|
-
if (parsed && typeof parsed === "object") {
|
|
348
|
-
const candidate = parsed.results ?? parsed.items ?? parsed.data;
|
|
349
|
-
if (Array.isArray(candidate)) {
|
|
350
|
-
return candidate;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
throw new Error("qmd returned an unexpected JSON shape.");
|
|
354
|
-
}
|
|
355
|
-
function ensureQmdAvailable() {
|
|
356
|
-
if (!hasQmd()) {
|
|
357
|
-
throw new QmdUnavailableError();
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
function execQmd(args, indexName) {
|
|
361
|
-
ensureQmdAvailable();
|
|
362
|
-
const finalArgs = withQmdIndexArgs(ensureJsonArgs(args), indexName);
|
|
363
|
-
try {
|
|
364
|
-
const result = execFileSync("qmd", finalArgs, {
|
|
365
|
-
encoding: "utf-8",
|
|
366
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
367
|
-
maxBuffer: 10 * 1024 * 1024
|
|
368
|
-
// 10MB
|
|
369
|
-
});
|
|
370
|
-
return parseQmdOutput(result);
|
|
371
|
-
} catch (err) {
|
|
372
|
-
if (err?.code === "ENOENT") {
|
|
373
|
-
throw new QmdUnavailableError();
|
|
374
|
-
}
|
|
375
|
-
const output = [err?.stdout, err?.stderr].filter(Boolean).join("\n");
|
|
376
|
-
if (output) {
|
|
377
|
-
try {
|
|
378
|
-
return parseQmdOutput(output);
|
|
379
|
-
} catch {
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
const message = err?.message ? `qmd failed: ${err.message}` : "qmd failed";
|
|
383
|
-
throw new Error(message);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
function hasQmd() {
|
|
387
|
-
const result = spawnSync("qmd", ["--version"], { stdio: "ignore" });
|
|
388
|
-
return !result.error;
|
|
389
|
-
}
|
|
390
|
-
function qmdUpdate(collection, indexName) {
|
|
391
|
-
ensureQmdAvailable();
|
|
392
|
-
const args = ["update"];
|
|
393
|
-
if (collection) {
|
|
394
|
-
args.push("-c", collection);
|
|
395
|
-
}
|
|
396
|
-
execFileSync("qmd", withQmdIndexArgs(args, indexName), { stdio: "inherit" });
|
|
397
|
-
}
|
|
398
|
-
function qmdEmbed(collection, indexName) {
|
|
399
|
-
ensureQmdAvailable();
|
|
400
|
-
const args = ["embed"];
|
|
401
|
-
if (collection) {
|
|
402
|
-
args.push("-c", collection);
|
|
403
|
-
}
|
|
404
|
-
execFileSync("qmd", withQmdIndexArgs(args, indexName), { stdio: "inherit" });
|
|
405
|
-
}
|
|
406
|
-
function sentenceChunk(text, maxChars = 600, overlapSentences = 1) {
|
|
407
|
-
const sentences = text.split(/(?<=[.!?])\s+|\n{2,}/).map((s) => s.trim()).filter(Boolean);
|
|
408
|
-
if (sentences.length === 0) return text.trim() ? [text] : [];
|
|
409
|
-
const chunks = [];
|
|
410
|
-
let i = 0;
|
|
411
|
-
while (i < sentences.length) {
|
|
412
|
-
const chunkSents = [];
|
|
413
|
-
let chunkLen = 0;
|
|
414
|
-
let j = i;
|
|
415
|
-
while (j < sentences.length && chunkLen + sentences[j].length < maxChars) {
|
|
416
|
-
chunkSents.push(sentences[j]);
|
|
417
|
-
chunkLen += sentences[j].length + 1;
|
|
418
|
-
j++;
|
|
419
|
-
}
|
|
420
|
-
if (chunkSents.length === 0) {
|
|
421
|
-
chunkSents.push(sentences[j].slice(0, maxChars));
|
|
422
|
-
j++;
|
|
423
|
-
}
|
|
424
|
-
chunks.push(chunkSents.join(" "));
|
|
425
|
-
i = Math.max(j - overlapSentences, i + 1);
|
|
426
|
-
}
|
|
427
|
-
return chunks;
|
|
428
|
-
}
|
|
429
|
-
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
430
|
-
"what",
|
|
431
|
-
"when",
|
|
432
|
-
"where",
|
|
433
|
-
"which",
|
|
434
|
-
"that",
|
|
435
|
-
"this",
|
|
436
|
-
"have",
|
|
437
|
-
"from",
|
|
438
|
-
"with",
|
|
439
|
-
"they",
|
|
440
|
-
"been",
|
|
441
|
-
"were",
|
|
442
|
-
"will",
|
|
443
|
-
"about",
|
|
444
|
-
"would",
|
|
445
|
-
"could",
|
|
446
|
-
"should",
|
|
447
|
-
"their",
|
|
448
|
-
"there",
|
|
449
|
-
"does",
|
|
450
|
-
"your",
|
|
451
|
-
"more",
|
|
452
|
-
"some",
|
|
453
|
-
"than",
|
|
454
|
-
"into",
|
|
455
|
-
"also",
|
|
456
|
-
"just",
|
|
457
|
-
"very",
|
|
458
|
-
"much",
|
|
459
|
-
"most",
|
|
460
|
-
"many",
|
|
461
|
-
"only",
|
|
462
|
-
"other",
|
|
463
|
-
"each",
|
|
464
|
-
"every",
|
|
465
|
-
"after",
|
|
466
|
-
"before",
|
|
467
|
-
"did",
|
|
468
|
-
"the",
|
|
469
|
-
"and",
|
|
470
|
-
"for",
|
|
471
|
-
"are",
|
|
472
|
-
"was",
|
|
473
|
-
"not",
|
|
474
|
-
"but",
|
|
475
|
-
"can",
|
|
476
|
-
"had",
|
|
477
|
-
"has",
|
|
478
|
-
"how",
|
|
479
|
-
"who",
|
|
480
|
-
"why",
|
|
481
|
-
"its",
|
|
482
|
-
"you",
|
|
483
|
-
"my",
|
|
484
|
-
"me",
|
|
485
|
-
"is",
|
|
486
|
-
"it",
|
|
487
|
-
"do",
|
|
488
|
-
"so",
|
|
489
|
-
"if",
|
|
490
|
-
"or",
|
|
491
|
-
"an",
|
|
492
|
-
"on",
|
|
493
|
-
"at",
|
|
494
|
-
"by",
|
|
495
|
-
"no",
|
|
496
|
-
"up",
|
|
497
|
-
"to",
|
|
498
|
-
"in",
|
|
499
|
-
"of",
|
|
500
|
-
"am",
|
|
501
|
-
"be"
|
|
502
|
-
]);
|
|
503
|
-
function tokenize(text) {
|
|
504
|
-
return text.toLowerCase().split(/\s+/).map((w) => w.replace(/^[?.,!"'\-():;[\]{}*]+|[?.,!"'\-():;[\]{}*]+$/g, "")).filter((w) => w.length > 1);
|
|
505
|
-
}
|
|
506
|
-
function queryTerms(query) {
|
|
507
|
-
return tokenize(query).filter((w) => !STOPWORDS.has(w));
|
|
508
|
-
}
|
|
509
|
-
function bm25RankChunks(chunks, terms, max = 5) {
|
|
510
|
-
if (chunks.length === 0) return [];
|
|
511
|
-
const termSet = new Set(terms);
|
|
512
|
-
const scored = chunks.map((text, idx) => {
|
|
513
|
-
const words = new Set(tokenize(text));
|
|
514
|
-
let overlap = 0;
|
|
515
|
-
for (const t of termSet) if (words.has(t)) overlap++;
|
|
516
|
-
return { text, score: overlap, idx };
|
|
517
|
-
});
|
|
518
|
-
scored.sort((a, b) => b.score - a.score);
|
|
519
|
-
const seen = /* @__PURE__ */ new Set();
|
|
520
|
-
const result = [];
|
|
521
|
-
seen.add(0);
|
|
522
|
-
result.push({ text: chunks[0], score: scored.find((s) => s.idx === 0)?.score ?? 0 });
|
|
523
|
-
for (const s of scored) {
|
|
524
|
-
if (result.length >= max) break;
|
|
525
|
-
if (!seen.has(s.idx) && s.score > 0) {
|
|
526
|
-
seen.add(s.idx);
|
|
527
|
-
result.push({ text: s.text, score: s.score });
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
return result;
|
|
531
|
-
}
|
|
532
|
-
var MONTH_NAMES = {
|
|
533
|
-
january: 1,
|
|
534
|
-
february: 2,
|
|
535
|
-
march: 3,
|
|
536
|
-
april: 4,
|
|
537
|
-
may: 5,
|
|
538
|
-
june: 6,
|
|
539
|
-
july: 7,
|
|
540
|
-
august: 8,
|
|
541
|
-
september: 9,
|
|
542
|
-
october: 10,
|
|
543
|
-
november: 11,
|
|
544
|
-
december: 12,
|
|
545
|
-
jan: 1,
|
|
546
|
-
feb: 2,
|
|
547
|
-
mar: 3,
|
|
548
|
-
apr: 4,
|
|
549
|
-
jun: 6,
|
|
550
|
-
jul: 7,
|
|
551
|
-
aug: 8,
|
|
552
|
-
sep: 9,
|
|
553
|
-
sept: 9,
|
|
554
|
-
oct: 10,
|
|
555
|
-
nov: 11,
|
|
556
|
-
dec: 12
|
|
557
|
-
};
|
|
558
|
-
var MONTH_RE_PART = Object.keys(MONTH_NAMES).join("|");
|
|
559
|
-
var DATE_ISO_RE = /\b(\d{4})[/-](\d{1,2})[/-](\d{1,2})\b/g;
|
|
560
|
-
var DATE_US_RE = /\b(\d{1,2})\/(\d{1,2})\/(\d{4})\b/g;
|
|
561
|
-
var DATE_MONTH_DAY_YEAR_RE = new RegExp(
|
|
562
|
-
`\\b(${MONTH_RE_PART})\\s+(\\d{1,2})(?:st|nd|rd|th)?,?\\s*(\\d{4})\\b`,
|
|
563
|
-
"gi"
|
|
564
|
-
);
|
|
565
|
-
var DATE_DAY_MONTH_YEAR_RE = new RegExp(
|
|
566
|
-
`\\b(\\d{1,2})(?:st|nd|rd|th)?\\s+(${MONTH_RE_PART}),?\\s*(\\d{4})\\b`,
|
|
567
|
-
"gi"
|
|
568
|
-
);
|
|
569
|
-
var DATE_MONTH_DAY_RE = new RegExp(
|
|
570
|
-
`\\b(${MONTH_RE_PART})\\s+(\\d{1,2})(?:st|nd|rd|th)?\\b`,
|
|
571
|
-
"gi"
|
|
572
|
-
);
|
|
573
|
-
var RELATIVE_AGO_RE = /\b(\d+)\s+(days?|weeks?|months?|years?)\s+ago\b/gi;
|
|
574
|
-
var RELATIVE_IN_RE = /\bin\s+(\d+)\s+(days?|weeks?|months?|years?)\b/gi;
|
|
575
|
-
var DURATION_RE = /(?:for|took|spent|lasted|about|approximately|around)\s+(\d+)\s+(days?|weeks?|months?|years?|hours?|minutes?)/gi;
|
|
576
|
-
function tryParseISODate(y, m, d) {
|
|
577
|
-
const dt = new Date(Date.UTC(y, m - 1, d));
|
|
578
|
-
if (dt.getUTCFullYear() === y && dt.getUTCMonth() === m - 1 && dt.getUTCDate() === d) return dt;
|
|
579
|
-
return null;
|
|
580
|
-
}
|
|
581
|
-
function unitToDays(n, unit) {
|
|
582
|
-
const u = unit.toLowerCase().replace(/s$/, "");
|
|
583
|
-
switch (u) {
|
|
584
|
-
case "day":
|
|
585
|
-
return n;
|
|
586
|
-
case "week":
|
|
587
|
-
return n * 7;
|
|
588
|
-
case "month":
|
|
589
|
-
return n * 30;
|
|
590
|
-
case "year":
|
|
591
|
-
return n * 365;
|
|
592
|
-
default:
|
|
593
|
-
return null;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
function contextSnippet(text, start, end, maxLen = 150) {
|
|
597
|
-
const s = Math.max(0, start - Math.floor(maxLen / 2));
|
|
598
|
-
const e = Math.min(text.length, end + Math.floor(maxLen / 2));
|
|
599
|
-
return text.slice(s, e).replace(/\n/g, " ").trim();
|
|
600
|
-
}
|
|
601
|
-
function isoStr(d) {
|
|
602
|
-
const yy = d.getUTCFullYear();
|
|
603
|
-
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
604
|
-
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
605
|
-
return `${yy}-${mm}-${dd}`;
|
|
606
|
-
}
|
|
607
|
-
function extractDates(text, sessionDateStr) {
|
|
608
|
-
const results = [];
|
|
609
|
-
const sessionDate = sessionDateStr ? new Date(sessionDateStr) : null;
|
|
610
|
-
const seen = /* @__PURE__ */ new Set();
|
|
611
|
-
function push(date, ctx, docId = "") {
|
|
612
|
-
const key = `${date}|${ctx.slice(0, 60)}`;
|
|
613
|
-
if (seen.has(key)) return;
|
|
614
|
-
seen.add(key);
|
|
615
|
-
results.push({ date, context: ctx, documentId: docId });
|
|
616
|
-
}
|
|
617
|
-
for (const m of text.matchAll(DATE_ISO_RE)) {
|
|
618
|
-
const dt = tryParseISODate(+m[1], +m[2], +m[3]);
|
|
619
|
-
if (dt) push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
|
|
620
|
-
}
|
|
621
|
-
for (const m of text.matchAll(DATE_US_RE)) {
|
|
622
|
-
const dt = tryParseISODate(+m[3], +m[1], +m[2]);
|
|
623
|
-
if (dt) push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
|
|
624
|
-
}
|
|
625
|
-
for (const m of text.matchAll(DATE_MONTH_DAY_YEAR_RE)) {
|
|
626
|
-
const mon = MONTH_NAMES[m[1].toLowerCase()];
|
|
627
|
-
if (mon) {
|
|
628
|
-
const dt = tryParseISODate(+m[3], mon, +m[2]);
|
|
629
|
-
if (dt) push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
for (const m of text.matchAll(DATE_DAY_MONTH_YEAR_RE)) {
|
|
633
|
-
const mon = MONTH_NAMES[m[2].toLowerCase()];
|
|
634
|
-
if (mon) {
|
|
635
|
-
const dt = tryParseISODate(+m[3], mon, +m[1]);
|
|
636
|
-
if (dt) push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
if (sessionDate) {
|
|
640
|
-
for (const m of text.matchAll(DATE_MONTH_DAY_RE)) {
|
|
641
|
-
const mon = MONTH_NAMES[m[1].toLowerCase()];
|
|
642
|
-
if (mon) {
|
|
643
|
-
const dt = tryParseISODate(sessionDate.getFullYear(), mon, +m[2]);
|
|
644
|
-
if (dt) push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
if (sessionDate) {
|
|
649
|
-
for (const m of text.matchAll(RELATIVE_AGO_RE)) {
|
|
650
|
-
const days = unitToDays(+m[1], m[2]);
|
|
651
|
-
if (days !== null) {
|
|
652
|
-
const dt = new Date(sessionDate.getTime() - days * 864e5);
|
|
653
|
-
push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
for (const m of text.matchAll(RELATIVE_IN_RE)) {
|
|
657
|
-
const days = unitToDays(+m[1], m[2]);
|
|
658
|
-
if (days !== null) {
|
|
659
|
-
const dt = new Date(sessionDate.getTime() + days * 864e5);
|
|
660
|
-
push(isoStr(dt), contextSnippet(text, m.index, m.index + m[0].length));
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
for (const m of text.matchAll(DURATION_RE)) {
|
|
665
|
-
push(`duration:${m[1]} ${m[2]}`, contextSnippet(text, m.index, m.index + m[0].length));
|
|
666
|
-
}
|
|
667
|
-
return results;
|
|
668
|
-
}
|
|
669
|
-
var PREF_PATTERNS = [
|
|
670
|
-
// "I use/prefer/like/love/enjoy X"
|
|
671
|
-
/\bi\s+(?:use|prefer|like|love|enjoy|favor|chose|switched to|started using|always use|usually use)\s+(.{3,60}?)(?:[.,;!?\n]|$)/gi,
|
|
672
|
-
// "my favorite X is Y"
|
|
673
|
-
/\bmy\s+(?:favorite|preferred|go-to|usual)\s+\w+\s+(?:is|are|was)\s+(.{3,60}?)(?:[.,;!?\n]|$)/gi,
|
|
674
|
-
// "I'm a big fan of X"
|
|
675
|
-
/\bi(?:'m| am)\s+(?:a )?(?:big |huge )?fan of\s+(.{3,60}?)(?:[.,;!?\n]|$)/gi,
|
|
676
|
-
// "I switched from X to Y"
|
|
677
|
-
/\bi\s+switched\s+from\s+(.{3,40}?)\s+to\s+(.{3,40}?)(?:[.,;!?\n]|$)/gi
|
|
678
|
-
];
|
|
679
|
-
function extractPreferences(text, documentId = "") {
|
|
680
|
-
const results = [];
|
|
681
|
-
const seen = /* @__PURE__ */ new Set();
|
|
682
|
-
for (const pattern of PREF_PATTERNS) {
|
|
683
|
-
for (const m of text.matchAll(pattern)) {
|
|
684
|
-
const value = (m[1] || "").trim();
|
|
685
|
-
if (!value || value.length < 3) continue;
|
|
686
|
-
const key = value.toLowerCase();
|
|
687
|
-
if (seen.has(key)) continue;
|
|
688
|
-
seen.add(key);
|
|
689
|
-
const ctx = contextSnippet(text, m.index, m.index + m[0].length, 200);
|
|
690
|
-
let category = "general";
|
|
691
|
-
if (/tool|software|app|editor|ide|framework|library|language/i.test(ctx)) category = "tool";
|
|
692
|
-
else if (/hobby|sport|exercise|game|play/i.test(ctx)) category = "hobby";
|
|
693
|
-
else if (/brand|product|model|device|hardware/i.test(ctx)) category = "brand";
|
|
694
|
-
else if (/food|drink|restaurant|cuisine|recipe/i.test(ctx)) category = "food";
|
|
695
|
-
else if (/music|movie|show|book|podcast|artist|band/i.test(ctx)) category = "entertainment";
|
|
696
|
-
results.push({ category, value, documentId, context: ctx });
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
return results;
|
|
700
|
-
}
|
|
701
|
-
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;
|
|
702
|
-
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;
|
|
703
|
-
var AGGREGATION_Q_RE = /(?:how many|how much|total|all the|count|list all|every|what are all|name all)/i;
|
|
704
|
-
function classifyQuestion(q) {
|
|
705
|
-
if (PREFERENCE_Q_RE.test(q)) return "preference";
|
|
706
|
-
if (TEMPORAL_Q_RE.test(q)) return "temporal";
|
|
707
|
-
if (!TEMPORAL_Q_RE.test(q) && AGGREGATION_Q_RE.test(q)) return "aggregation";
|
|
708
|
-
return "default";
|
|
709
|
-
}
|
|
710
|
-
var SearchEngine = class {
|
|
711
|
-
documents = /* @__PURE__ */ new Map();
|
|
712
|
-
collection = "clawvault";
|
|
713
|
-
vaultPath = "";
|
|
714
|
-
collectionRoot = "";
|
|
715
|
-
qmdIndexName;
|
|
716
|
-
/** v2.7 — Per-document date index built at ingest time */
|
|
717
|
-
dateIndex = /* @__PURE__ */ new Map();
|
|
718
|
-
/** v2.7 — Per-document preference index built at ingest time */
|
|
719
|
-
preferenceIndex = /* @__PURE__ */ new Map();
|
|
720
|
-
/** v2.7 — Per-document chunk cache for BM25 pre-filtering */
|
|
721
|
-
chunkCache = /* @__PURE__ */ new Map();
|
|
722
|
-
/**
|
|
723
|
-
* Set the collection name (usually vault name)
|
|
724
|
-
*/
|
|
725
|
-
setCollection(name) {
|
|
726
|
-
this.collection = name;
|
|
727
|
-
}
|
|
728
|
-
/**
|
|
729
|
-
* Set the vault path for file resolution
|
|
730
|
-
*/
|
|
731
|
-
setVaultPath(vaultPath) {
|
|
732
|
-
this.vaultPath = vaultPath;
|
|
733
|
-
}
|
|
734
|
-
/**
|
|
735
|
-
* Set the collection root for qmd:// URI resolution
|
|
736
|
-
*/
|
|
737
|
-
setCollectionRoot(root) {
|
|
738
|
-
this.collectionRoot = path.resolve(root);
|
|
739
|
-
}
|
|
740
|
-
/**
|
|
741
|
-
* Set qmd index name (defaults to qmd global default when omitted)
|
|
742
|
-
*/
|
|
743
|
-
setIndexName(indexName) {
|
|
744
|
-
this.qmdIndexName = indexName;
|
|
745
|
-
}
|
|
746
|
-
/**
|
|
747
|
-
* Add or update a document in the local cache.
|
|
748
|
-
* v2.7: also extracts dates, preferences, and chunks at ingest time.
|
|
749
|
-
* Note: qmd indexing happens via qmd update command
|
|
750
|
-
*/
|
|
751
|
-
addDocument(doc) {
|
|
752
|
-
this.documents.set(doc.id, doc);
|
|
753
|
-
if (doc.content) {
|
|
754
|
-
const sessionDate = doc.modified ? isoStr(doc.modified) : void 0;
|
|
755
|
-
const dates = extractDates(doc.content, sessionDate);
|
|
756
|
-
for (const d of dates) d.documentId = doc.id;
|
|
757
|
-
if (dates.length > 0) this.dateIndex.set(doc.id, dates);
|
|
758
|
-
const prefs = extractPreferences(doc.content, doc.id);
|
|
759
|
-
if (prefs.length > 0) this.preferenceIndex.set(doc.id, prefs);
|
|
760
|
-
const chunks = sentenceChunk(doc.content, 600, 1);
|
|
761
|
-
if (chunks.length > 0) this.chunkCache.set(doc.id, chunks);
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
/**
|
|
765
|
-
* Remove a document from the local cache
|
|
766
|
-
*/
|
|
767
|
-
removeDocument(id) {
|
|
768
|
-
this.documents.delete(id);
|
|
769
|
-
this.dateIndex.delete(id);
|
|
770
|
-
this.preferenceIndex.delete(id);
|
|
771
|
-
this.chunkCache.delete(id);
|
|
772
|
-
}
|
|
773
|
-
/**
|
|
774
|
-
* No-op for qmd - indexing is managed externally
|
|
775
|
-
*/
|
|
776
|
-
rebuildIDF() {
|
|
777
|
-
}
|
|
778
|
-
/**
|
|
779
|
-
* BM25 search via qmd
|
|
780
|
-
*/
|
|
781
|
-
search(query, options = {}) {
|
|
782
|
-
return this.runQmdQuery("search", query, options);
|
|
783
|
-
}
|
|
784
|
-
/**
|
|
785
|
-
* Vector/semantic search via qmd vsearch
|
|
786
|
-
*/
|
|
787
|
-
vsearch(query, options = {}) {
|
|
788
|
-
return this.runQmdQuery("vsearch", query, options);
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Combined search with query expansion (qmd query command)
|
|
792
|
-
*/
|
|
793
|
-
query(query, options = {}) {
|
|
794
|
-
return this.runQmdQuery("query", query, options);
|
|
795
|
-
}
|
|
796
|
-
runQmdQuery(command, query, options) {
|
|
797
|
-
const {
|
|
798
|
-
limit = 10,
|
|
799
|
-
minScore = 0,
|
|
800
|
-
category,
|
|
801
|
-
tags,
|
|
802
|
-
fullContent = false,
|
|
803
|
-
temporalBoost = false,
|
|
804
|
-
relevanceThreshold,
|
|
805
|
-
thresholdMaxResults = 40
|
|
806
|
-
} = options;
|
|
807
|
-
if (!query.trim()) return [];
|
|
808
|
-
const fetchLimit = relevanceThreshold !== void 0 ? thresholdMaxResults * 2 : limit * 2;
|
|
809
|
-
const args = [
|
|
810
|
-
command,
|
|
811
|
-
query,
|
|
812
|
-
"-n",
|
|
813
|
-
String(fetchLimit),
|
|
814
|
-
"--json"
|
|
815
|
-
];
|
|
816
|
-
if (this.collection) {
|
|
817
|
-
args.push("-c", this.collection);
|
|
818
|
-
}
|
|
819
|
-
const qmdResults = execQmd(args, this.qmdIndexName);
|
|
820
|
-
const effectiveLimit = relevanceThreshold !== void 0 ? thresholdMaxResults : limit;
|
|
821
|
-
const results = this.convertResults(qmdResults, {
|
|
822
|
-
limit: effectiveLimit,
|
|
823
|
-
minScore: relevanceThreshold !== void 0 ? relevanceThreshold : minScore,
|
|
824
|
-
category,
|
|
825
|
-
tags,
|
|
826
|
-
fullContent,
|
|
827
|
-
temporalBoost
|
|
828
|
-
});
|
|
829
|
-
return results;
|
|
830
|
-
}
|
|
831
|
-
// -------------------------------------------------------------------------
|
|
832
|
-
// v2.7 — New public APIs
|
|
833
|
-
// -------------------------------------------------------------------------
|
|
834
|
-
/**
|
|
835
|
-
* v2.7 — Chunk-level BM25 pre-filtered search. Ranks chunks within each
|
|
836
|
-
* document by keyword relevance before semantic ranking, so relevant
|
|
837
|
-
* content deep in long documents isn't missed.
|
|
838
|
-
*
|
|
839
|
-
* Returns results with snippets from the best-matching chunks.
|
|
840
|
-
*/
|
|
841
|
-
chunkPrefilterSearch(query, options = {}) {
|
|
842
|
-
const terms = queryTerms(query);
|
|
843
|
-
const results = this.runQmdQuery("query", query, options);
|
|
844
|
-
for (const r of results) {
|
|
845
|
-
const chunks = this.chunkCache.get(r.document.id);
|
|
846
|
-
if (chunks && chunks.length > 0 && terms.length > 0) {
|
|
847
|
-
const ranked = bm25RankChunks(chunks, terms, 3);
|
|
848
|
-
if (ranked.length > 0 && ranked[0].score > 0) {
|
|
849
|
-
r.snippet = ranked.map((c) => c.text).join("\n...\n").slice(0, 600);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
return results;
|
|
854
|
-
}
|
|
855
|
-
/**
|
|
856
|
-
* v2.7 — Exhaustive threshold-based search for aggregation queries.
|
|
857
|
-
* Keeps pulling results until relevance drops below threshold.
|
|
858
|
-
*/
|
|
859
|
-
exhaustiveSearch(query, threshold = 0.01, maxResults = 40) {
|
|
860
|
-
return this.runQmdQuery("query", query, {
|
|
861
|
-
relevanceThreshold: threshold,
|
|
862
|
-
thresholdMaxResults: maxResults,
|
|
863
|
-
fullContent: false
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
/**
|
|
867
|
-
* v2.7 — Get all extracted dates, optionally filtered by document ids.
|
|
868
|
-
*/
|
|
869
|
-
getDates(documentIds) {
|
|
870
|
-
const all = [];
|
|
871
|
-
const iter = documentIds ? documentIds.map((id) => [id, this.dateIndex.get(id)]).filter(([, v]) => v) : this.dateIndex.entries();
|
|
872
|
-
for (const [, dates] of iter) {
|
|
873
|
-
if (dates) all.push(...dates);
|
|
874
|
-
}
|
|
875
|
-
return all;
|
|
876
|
-
}
|
|
877
|
-
/**
|
|
878
|
-
* v2.7 — Get all extracted preferences, optionally filtered by document ids.
|
|
879
|
-
*/
|
|
880
|
-
getPreferences(documentIds) {
|
|
881
|
-
const all = [];
|
|
882
|
-
const iter = documentIds ? documentIds.map((id) => [id, this.preferenceIndex.get(id)]).filter(([, v]) => v) : this.preferenceIndex.entries();
|
|
883
|
-
for (const [, prefs] of iter) {
|
|
884
|
-
if (prefs) all.push(...prefs);
|
|
885
|
-
}
|
|
886
|
-
return all;
|
|
887
|
-
}
|
|
888
|
-
/**
|
|
889
|
-
* v2.7 — Search with automatic strategy selection based on question type.
|
|
890
|
-
* Classifies the query and routes to the appropriate pipeline.
|
|
891
|
-
*/
|
|
892
|
-
smartQuery(query, options = {}) {
|
|
893
|
-
const qtype = classifyQuestion(query);
|
|
894
|
-
switch (qtype) {
|
|
895
|
-
case "aggregation":
|
|
896
|
-
return this.exhaustiveSearch(query, 0.01, options.thresholdMaxResults ?? 40);
|
|
897
|
-
case "preference":
|
|
898
|
-
case "temporal":
|
|
899
|
-
default:
|
|
900
|
-
return this.chunkPrefilterSearch(query, { ...options, limit: options.limit ?? 10 });
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
/**
|
|
904
|
-
* Convert qmd results to ClawVault SearchResult format
|
|
905
|
-
*/
|
|
906
|
-
convertResults(qmdResults, options) {
|
|
907
|
-
const { limit = 10, minScore = 0, category, tags, fullContent = false, temporalBoost = false } = options;
|
|
908
|
-
const results = [];
|
|
909
|
-
const maxScore = qmdResults[0]?.score || 1;
|
|
910
|
-
for (const qr of qmdResults) {
|
|
911
|
-
const filePath = this.qmdUriToPath(qr.file);
|
|
912
|
-
const relativePath = this.vaultPath ? path.relative(this.vaultPath, filePath) : filePath;
|
|
913
|
-
const normalizedRelativePath = relativePath.replace(/\\/g, "/");
|
|
914
|
-
if (normalizedRelativePath.startsWith("ledger/archive/") || normalizedRelativePath.includes("/ledger/archive/")) {
|
|
915
|
-
continue;
|
|
916
|
-
}
|
|
917
|
-
const docId = normalizedRelativePath.replace(/\.md$/, "");
|
|
918
|
-
let doc = this.documents.get(docId) ?? this.documents.get(docId.split("/").join(path.sep));
|
|
919
|
-
const modifiedAt = this.resolveModifiedAt(doc, filePath);
|
|
920
|
-
const parts = normalizedRelativePath.split("/");
|
|
921
|
-
const docCategory = parts.length > 1 ? parts[0] : "root";
|
|
922
|
-
if (category && docCategory !== category) continue;
|
|
923
|
-
if (tags && tags.length > 0 && doc) {
|
|
924
|
-
const docTags = new Set(doc.tags);
|
|
925
|
-
if (!tags.some((t) => docTags.has(t))) continue;
|
|
926
|
-
}
|
|
927
|
-
const normalizedScore = maxScore > 0 ? qr.score / maxScore : 0;
|
|
928
|
-
const finalScore = temporalBoost ? normalizedScore * this.getRecencyFactor(modifiedAt) : normalizedScore;
|
|
929
|
-
if (finalScore < minScore) continue;
|
|
930
|
-
if (!doc) {
|
|
931
|
-
doc = {
|
|
932
|
-
id: docId,
|
|
933
|
-
path: filePath,
|
|
934
|
-
category: docCategory,
|
|
935
|
-
title: qr.title || path.basename(relativePath, ".md"),
|
|
936
|
-
content: "",
|
|
937
|
-
// Content loaded separately if needed
|
|
938
|
-
frontmatter: {},
|
|
939
|
-
links: [],
|
|
940
|
-
tags: [],
|
|
941
|
-
modified: modifiedAt
|
|
942
|
-
};
|
|
943
|
-
}
|
|
944
|
-
results.push({
|
|
945
|
-
document: fullContent ? doc : { ...doc, content: "" },
|
|
946
|
-
score: finalScore,
|
|
947
|
-
snippet: this.stripSupersededFromSnippet(this.cleanSnippet(qr.snippet)),
|
|
948
|
-
matchedTerms: []
|
|
949
|
-
// qmd doesn't provide this
|
|
950
|
-
});
|
|
951
|
-
}
|
|
952
|
-
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
953
|
-
}
|
|
954
|
-
resolveModifiedAt(doc, filePath) {
|
|
955
|
-
if (doc) return doc.modified;
|
|
956
|
-
try {
|
|
957
|
-
return fs2.statSync(filePath).mtime;
|
|
958
|
-
} catch {
|
|
959
|
-
return /* @__PURE__ */ new Date(0);
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
getRecencyFactor(modifiedAt) {
|
|
963
|
-
const ageMs = Math.max(0, Date.now() - modifiedAt.getTime());
|
|
964
|
-
const ageDays = ageMs / (24 * 60 * 60 * 1e3);
|
|
965
|
-
if (ageDays < 1) return 1;
|
|
966
|
-
if (ageDays <= 7) return 0.9;
|
|
967
|
-
return 0.7;
|
|
968
|
-
}
|
|
969
|
-
/**
|
|
970
|
-
* Convert qmd:// URI to file path
|
|
971
|
-
*/
|
|
972
|
-
qmdUriToPath(uri) {
|
|
973
|
-
if (uri.startsWith("qmd://")) {
|
|
974
|
-
const withoutScheme = uri.slice(6);
|
|
975
|
-
const slashIndex = withoutScheme.indexOf("/");
|
|
976
|
-
if (slashIndex > -1) {
|
|
977
|
-
const relativePath = withoutScheme.slice(slashIndex + 1);
|
|
978
|
-
const root = this.collectionRoot || this.vaultPath;
|
|
979
|
-
if (root) {
|
|
980
|
-
return path.join(root, relativePath);
|
|
981
|
-
}
|
|
982
|
-
return relativePath;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
return uri;
|
|
986
|
-
}
|
|
987
|
-
/**
|
|
988
|
-
* v2.8 — Filter superseded observation lines from snippet text.
|
|
989
|
-
* Ensures search results prefer the latest version of knowledge.
|
|
990
|
-
*/
|
|
991
|
-
stripSupersededFromSnippet(snippet) {
|
|
992
|
-
if (!snippet) return snippet;
|
|
993
|
-
return snippet.split("\n").filter((line) => !isSuperseded(line)).join("\n");
|
|
994
|
-
}
|
|
995
|
-
/**
|
|
996
|
-
* Clean up qmd snippet format
|
|
997
|
-
*/
|
|
998
|
-
cleanSnippet(snippet) {
|
|
999
|
-
if (!snippet) return "";
|
|
1000
|
-
return snippet.replace(/@@ [-+]?\d+,?\d* @@ \([^)]+\)/g, "").trim().split("\n").slice(0, 3).join("\n").slice(0, 300);
|
|
1001
|
-
}
|
|
1002
|
-
/**
|
|
1003
|
-
* Get all cached documents
|
|
1004
|
-
*/
|
|
1005
|
-
getAllDocuments() {
|
|
1006
|
-
return [...this.documents.values()];
|
|
1007
|
-
}
|
|
1008
|
-
/**
|
|
1009
|
-
* Get document count
|
|
1010
|
-
*/
|
|
1011
|
-
get size() {
|
|
1012
|
-
return this.documents.size;
|
|
1013
|
-
}
|
|
1014
|
-
/**
|
|
1015
|
-
* Clear the local document cache and all v2.7 indices
|
|
1016
|
-
*/
|
|
1017
|
-
clear() {
|
|
1018
|
-
this.documents.clear();
|
|
1019
|
-
this.dateIndex.clear();
|
|
1020
|
-
this.preferenceIndex.clear();
|
|
1021
|
-
this.chunkCache.clear();
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Export documents for persistence
|
|
1025
|
-
*/
|
|
1026
|
-
export() {
|
|
1027
|
-
return {
|
|
1028
|
-
documents: [...this.documents.values()]
|
|
1029
|
-
};
|
|
1030
|
-
}
|
|
1031
|
-
/**
|
|
1032
|
-
* Import from persisted data
|
|
1033
|
-
*/
|
|
1034
|
-
import(data) {
|
|
1035
|
-
this.clear();
|
|
1036
|
-
for (const doc of data.documents) {
|
|
1037
|
-
this.addDocument(doc);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
};
|
|
1041
|
-
function extractWikiLinks(content) {
|
|
1042
|
-
const matches = content.match(/\[\[([^\]]+)\]\]/g) || [];
|
|
1043
|
-
return matches.map((m) => m.slice(2, -2).toLowerCase());
|
|
1044
|
-
}
|
|
1045
|
-
function extractTags(content) {
|
|
1046
|
-
const matches = content.match(/#[\w-]+/g) || [];
|
|
1047
|
-
return [...new Set(matches.map((m) => m.slice(1).toLowerCase()))];
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
export {
|
|
1051
|
-
isSuperseded,
|
|
1052
|
-
getSupersessionInfo,
|
|
1053
|
-
extractEntities,
|
|
1054
|
-
entitySimilarity,
|
|
1055
|
-
isKnowledgeUpdate,
|
|
1056
|
-
reweave,
|
|
1057
|
-
filterSuperseded,
|
|
1058
|
-
stripSupersededObservations,
|
|
1059
|
-
QMD_INSTALL_URL,
|
|
1060
|
-
QMD_INSTALL_COMMAND,
|
|
1061
|
-
QmdUnavailableError,
|
|
1062
|
-
withQmdIndexArgs,
|
|
1063
|
-
hasQmd,
|
|
1064
|
-
qmdUpdate,
|
|
1065
|
-
qmdEmbed,
|
|
1066
|
-
sentenceChunk,
|
|
1067
|
-
bm25RankChunks,
|
|
1068
|
-
extractDates,
|
|
1069
|
-
extractPreferences,
|
|
1070
|
-
classifyQuestion,
|
|
1071
|
-
SearchEngine,
|
|
1072
|
-
extractWikiLinks,
|
|
1073
|
-
extractTags
|
|
1074
|
-
};
|