clawvault 3.3.0 → 3.4.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 +26 -26
- package/bin/command-registration.test.js +2 -1
- package/bin/help-contract.test.js +2 -0
- package/bin/register-maintenance-commands.js +27 -1
- package/bin/register-query-commands.js +35 -0
- package/bin/register-query-commands.test.js +15 -0
- package/dist/chunk-35JCYSRR.js +158 -0
- package/dist/{chunk-TDWFBDAQ.js → chunk-D5U3Q4N5.js} +7 -151
- package/dist/{chunk-YCUVAOFC.js → chunk-DCF4KMFD.js} +4 -4
- package/dist/chunk-NSXYM6EZ.js +255 -0
- package/dist/chunk-PLNK37JD.js +2223 -0
- package/dist/chunk-RL2L6I6K.js +223 -0
- package/dist/chunk-YTRZNA64.js +37 -0
- package/dist/cli/index.js +6 -5
- package/dist/commands/entities.d.ts +8 -1
- package/dist/commands/entities.js +44 -1
- package/dist/commands/link.js +5 -5
- package/dist/commands/maintain.js +2 -1
- package/dist/commands/recall.d.ts +14 -0
- package/dist/commands/recall.js +15 -0
- package/dist/index.d.ts +59 -1
- package/dist/index.js +56 -11
- package/dist/openclaw-plugin--gqA2BZw.d.ts +267 -0
- package/dist/openclaw-plugin.d.ts +4 -8
- package/dist/openclaw-plugin.js +16 -10
- package/dist/types-CbL-wIKi.d.ts +36 -0
- package/openclaw.plugin.json +39 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -267,23 +267,24 @@ clawvault setup --theme neural --canvas --bases
|
|
|
267
267
|
|
|
268
268
|
## OpenClaw Integration
|
|
269
269
|
|
|
270
|
-
|
|
270
|
+
ClawVault integrates with OpenClaw as a plugin package (not the deprecated `openclaw hooks install/enable` flow):
|
|
271
271
|
|
|
272
272
|
```bash
|
|
273
|
-
# Install
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
# Install ClawVault
|
|
274
|
+
npm install -g clawvault
|
|
275
|
+
|
|
276
|
+
# Add ClawVault package path to plugins.load.paths in openclaw.json
|
|
277
|
+
# (You can use `npm root -g` to locate node_modules)
|
|
276
278
|
|
|
277
|
-
#
|
|
278
|
-
|
|
279
|
-
|
|
279
|
+
# Enable plugin + memory slot in openclaw.json:
|
|
280
|
+
# plugins.slots.memory = "clawvault"
|
|
281
|
+
# plugins.entries.clawvault.enabled = true
|
|
282
|
+
|
|
283
|
+
# Verify runtime assumptions
|
|
280
284
|
clawvault compat
|
|
281
285
|
```
|
|
282
286
|
|
|
283
|
-
The
|
|
284
|
-
- Detects context death and injects recovery alerts
|
|
285
|
-
- Auto-checkpoints before session resets
|
|
286
|
-
- Provides `--profile auto` for context queries
|
|
287
|
+
The plugin provides lifecycle hooks, memory tools, and protocol-safe messaging behavior for OpenClaw sessions.
|
|
287
288
|
|
|
288
289
|
### MEMORY.md vs Vault
|
|
289
290
|
|
|
@@ -351,27 +352,26 @@ clawvault compat
|
|
|
351
352
|
|
|
352
353
|
## OpenClaw Setup (Canonical)
|
|
353
354
|
|
|
354
|
-
|
|
355
|
+
Use this sequence for plugin-based OpenClaw integration:
|
|
355
356
|
|
|
356
357
|
```bash
|
|
357
358
|
# Install CLI
|
|
358
359
|
npm install -g clawvault
|
|
359
360
|
|
|
360
|
-
#
|
|
361
|
-
|
|
362
|
-
openclaw hooks enable clawvault
|
|
361
|
+
# Locate global node_modules
|
|
362
|
+
npm root -g
|
|
363
363
|
|
|
364
|
-
#
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
clawvault
|
|
364
|
+
# In openclaw.json:
|
|
365
|
+
# - add <npm-root>/clawvault to plugins.load.paths
|
|
366
|
+
# - set plugins.slots.memory = "clawvault"
|
|
367
|
+
# - set plugins.entries.clawvault.enabled = true
|
|
368
|
+
# - set plugins.entries.clawvault.config.vaultPath as needed
|
|
369
369
|
```
|
|
370
370
|
|
|
371
371
|
Important:
|
|
372
372
|
|
|
373
|
-
- `clawhub install clawvault` installs skill guidance, but does not
|
|
374
|
-
- After
|
|
373
|
+
- `clawhub install clawvault` installs skill guidance, but does not configure OpenClaw plugin loading.
|
|
374
|
+
- After changing plugin config, restart the OpenClaw gateway process.
|
|
375
375
|
|
|
376
376
|
## Minimal AGENTS.md Additions
|
|
377
377
|
|
|
@@ -488,10 +488,10 @@ vault/
|
|
|
488
488
|
- `qmd` fallback errors:
|
|
489
489
|
- `qmd` is optional; in-process BM25 search is available without it
|
|
490
490
|
- if you want fallback compatibility, ensure `qmd --version` works in the same shell
|
|
491
|
-
-
|
|
492
|
-
-
|
|
493
|
-
-
|
|
494
|
-
- verify
|
|
491
|
+
- Plugin not active in OpenClaw:
|
|
492
|
+
- verify `plugins.load.paths` includes the ClawVault package path
|
|
493
|
+
- verify `plugins.slots.memory` is `clawvault`
|
|
494
|
+
- verify `plugins.entries.clawvault.enabled` is `true`
|
|
495
495
|
- OpenClaw integration drift:
|
|
496
496
|
- run `clawvault compat`
|
|
497
497
|
- Session transcript corruption:
|
|
@@ -49,7 +49,7 @@ describe('CLI command registration modules', () => {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
const names = listCommandNames(program);
|
|
52
|
-
expect(names).toEqual(expect.arrayContaining(['search', 'vsearch', 'context', 'inject', 'observe', 'reflect', 'session-recap']));
|
|
52
|
+
expect(names).toEqual(expect.arrayContaining(['search', 'vsearch', 'context', 'recall', 'inject', 'observe', 'reflect', 'session-recap']));
|
|
53
53
|
|
|
54
54
|
const contextCommand = program.commands.find((command) => command.name() === 'context');
|
|
55
55
|
const profileOption = contextCommand?.options.find((option) => option.flags.includes('--profile <profile>'));
|
|
@@ -127,6 +127,7 @@ describe('CLI command registration modules', () => {
|
|
|
127
127
|
'compat',
|
|
128
128
|
'graph',
|
|
129
129
|
'entities',
|
|
130
|
+
'entity',
|
|
130
131
|
'link',
|
|
131
132
|
'rebuild',
|
|
132
133
|
'archive',
|
|
@@ -6,6 +6,7 @@ describe('CLI help contract', () => {
|
|
|
6
6
|
const help = registerAllCommandModules().helpInformation();
|
|
7
7
|
expect(help).toContain('init');
|
|
8
8
|
expect(help).toContain('context');
|
|
9
|
+
expect(help).toContain('recall');
|
|
9
10
|
expect(help).toContain('inject');
|
|
10
11
|
expect(help).toContain('doctor');
|
|
11
12
|
expect(help).toContain('benchmark');
|
|
@@ -22,6 +23,7 @@ describe('CLI help contract', () => {
|
|
|
22
23
|
expect(help).toContain('template');
|
|
23
24
|
expect(help).toContain('config');
|
|
24
25
|
expect(help).toContain('route');
|
|
26
|
+
expect(help).toContain('entity');
|
|
25
27
|
});
|
|
26
28
|
|
|
27
29
|
it('documents context/compat/inject/project help details', () => {
|
|
@@ -174,11 +174,37 @@ export function registerMaintenanceCommands(program, { chalk }) {
|
|
|
174
174
|
.command('entities')
|
|
175
175
|
.description('List all linkable entities in the vault')
|
|
176
176
|
.option('-v, --vault <path>', 'Vault path')
|
|
177
|
+
.option('--refresh', 'Regenerate entity profiles before listing')
|
|
177
178
|
.option('--json', 'Output as JSON')
|
|
178
179
|
.action(async (options) => {
|
|
179
180
|
try {
|
|
180
181
|
const { entitiesCommand } = await import('../dist/commands/entities.js');
|
|
181
|
-
await entitiesCommand({
|
|
182
|
+
await entitiesCommand({
|
|
183
|
+
json: options.json,
|
|
184
|
+
vaultPath: options.vault,
|
|
185
|
+
refresh: options.refresh
|
|
186
|
+
});
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// === ENTITY ===
|
|
194
|
+
program
|
|
195
|
+
.command('entity <name>')
|
|
196
|
+
.description('Show synthesized profile for one entity')
|
|
197
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
198
|
+
.option('--refresh', 'Regenerate entity profiles before lookup')
|
|
199
|
+
.option('--json', 'Output as JSON')
|
|
200
|
+
.action(async (name, options) => {
|
|
201
|
+
try {
|
|
202
|
+
const { entityCommand } = await import('../dist/commands/entities.js');
|
|
203
|
+
await entityCommand(name, {
|
|
204
|
+
json: options.json,
|
|
205
|
+
vaultPath: options.vault,
|
|
206
|
+
refresh: options.refresh
|
|
207
|
+
});
|
|
182
208
|
} catch (err) {
|
|
183
209
|
console.error(chalk.red(`Error: ${err.message}`));
|
|
184
210
|
process.exit(1);
|
|
@@ -202,6 +202,41 @@ export function registerQueryCommands(
|
|
|
202
202
|
}
|
|
203
203
|
});
|
|
204
204
|
|
|
205
|
+
// === INJECT ===
|
|
206
|
+
program
|
|
207
|
+
.command('recall <query>')
|
|
208
|
+
.description('Recall memory context with strategy classification (quick|entity|temporal|verification|relationship)')
|
|
209
|
+
.option('-n, --limit <n>', 'Max results (default: 6)', '6')
|
|
210
|
+
.option('--strategy <strategy>', 'Override strategy (quick|entity|temporal|verification|relationship)')
|
|
211
|
+
.option('--json', 'Output as JSON')
|
|
212
|
+
.option('--no-sources', 'Hide source paths in recall context')
|
|
213
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
214
|
+
.action(async (query, options) => {
|
|
215
|
+
try {
|
|
216
|
+
const parsedLimit = Number.parseInt(options.limit, 10);
|
|
217
|
+
if (!Number.isFinite(parsedLimit) || parsedLimit <= 0) {
|
|
218
|
+
throw new Error(`Invalid --limit value: ${options.limit}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const allowedStrategies = new Set(['quick', 'entity', 'temporal', 'verification', 'relationship']);
|
|
222
|
+
if (options.strategy && !allowedStrategies.has(options.strategy)) {
|
|
223
|
+
throw new Error(`Invalid --strategy value: ${options.strategy}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const { recallCommand } = await import('../dist/commands/recall.js');
|
|
227
|
+
await recallCommand(query, {
|
|
228
|
+
vaultPath: resolveVaultPath(options.vault),
|
|
229
|
+
limit: parsedLimit,
|
|
230
|
+
strategy: options.strategy,
|
|
231
|
+
json: options.json,
|
|
232
|
+
includeSources: options.sources
|
|
233
|
+
});
|
|
234
|
+
} catch (err) {
|
|
235
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
205
240
|
// === INJECT ===
|
|
206
241
|
program
|
|
207
242
|
.command('inject <message>')
|
|
@@ -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');
|
|
@@ -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
|
+
};
|
|
@@ -5,6 +5,13 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
readInboxItems
|
|
7
7
|
} from "./chunk-2PKBIKDH.js";
|
|
8
|
+
import {
|
|
9
|
+
buildHeuristicSurveyRecommendations,
|
|
10
|
+
classifyInboxItemHeuristic,
|
|
11
|
+
extractHeuristicInsights,
|
|
12
|
+
normalizeForDedup,
|
|
13
|
+
similarityScore
|
|
14
|
+
} from "./chunk-35JCYSRR.js";
|
|
8
15
|
import {
|
|
9
16
|
resolveVaultPath
|
|
10
17
|
} from "./chunk-GJO3CFUN.js";
|
|
@@ -173,157 +180,6 @@ function writeMaintenanceState(vaultPath, state) {
|
|
|
173
180
|
import * as path5 from "path";
|
|
174
181
|
import matter from "gray-matter";
|
|
175
182
|
|
|
176
|
-
// src/lib/maintenance/heuristics.ts
|
|
177
|
-
var CATEGORY_PATTERNS = [
|
|
178
|
-
{
|
|
179
|
-
category: "decisions",
|
|
180
|
-
patterns: [
|
|
181
|
-
/\b(decid(?:e|ed|ing|ion)|chose|selected|opted|trade[- ]?off)\b/i,
|
|
182
|
-
/\b(approved|agreed|consensus)\b/i
|
|
183
|
-
]
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
category: "lessons",
|
|
187
|
-
patterns: [
|
|
188
|
-
/\b(learn(?:ed|ing|t)|lesson|insight|realized|retrospective)\b/i,
|
|
189
|
-
/\b(next time|note to self|mistake)\b/i
|
|
190
|
-
]
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
category: "commitments",
|
|
194
|
-
patterns: [
|
|
195
|
-
/\b(todo|task|action item|follow[- ]?up|deadline|due)\b/i,
|
|
196
|
-
/\b(i will|we will|must|need to)\b/i
|
|
197
|
-
]
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
category: "people",
|
|
201
|
-
patterns: [
|
|
202
|
-
/\b(met with|talked to|spoke with|emailed|called|messaged)\b/i,
|
|
203
|
-
/\b(client|customer|partner|colleague|contact)\b/i
|
|
204
|
-
]
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
category: "projects",
|
|
208
|
-
patterns: [
|
|
209
|
-
/\b(project|feature|release|deployment|deploy|service|api|repo)\b/i,
|
|
210
|
-
/\b(shipped|launched|merged|rolled out)\b/i
|
|
211
|
-
]
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
category: "preferences",
|
|
215
|
-
patterns: [
|
|
216
|
-
/\b(prefer(?:s|red|ence)?|like(?:s|d)?|dislike|style|convention)\b/i,
|
|
217
|
-
/\b(always use|never use|default to)\b/i
|
|
218
|
-
]
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
category: "facts",
|
|
222
|
-
patterns: [
|
|
223
|
-
/\b(is|are|was|were|has|have|contains|includes)\b/i
|
|
224
|
-
]
|
|
225
|
-
}
|
|
226
|
-
];
|
|
227
|
-
function splitSentences(content) {
|
|
228
|
-
return content.split(/\r?\n|(?<=[.?!])\s+/).map((sentence) => sentence.trim()).filter(Boolean);
|
|
229
|
-
}
|
|
230
|
-
function compactWhitespace(value) {
|
|
231
|
-
return value.toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
232
|
-
}
|
|
233
|
-
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
234
|
-
"the",
|
|
235
|
-
"a",
|
|
236
|
-
"an",
|
|
237
|
-
"and",
|
|
238
|
-
"or",
|
|
239
|
-
"to",
|
|
240
|
-
"of",
|
|
241
|
-
"for",
|
|
242
|
-
"in",
|
|
243
|
-
"on",
|
|
244
|
-
"is",
|
|
245
|
-
"are",
|
|
246
|
-
"was",
|
|
247
|
-
"were",
|
|
248
|
-
"be",
|
|
249
|
-
"this",
|
|
250
|
-
"that",
|
|
251
|
-
"it",
|
|
252
|
-
"with",
|
|
253
|
-
"as",
|
|
254
|
-
"at",
|
|
255
|
-
"by",
|
|
256
|
-
"from",
|
|
257
|
-
"we",
|
|
258
|
-
"i",
|
|
259
|
-
"you",
|
|
260
|
-
"they",
|
|
261
|
-
"he",
|
|
262
|
-
"she"
|
|
263
|
-
]);
|
|
264
|
-
function classifyInboxItemHeuristic(item) {
|
|
265
|
-
const sample = `${item.title}
|
|
266
|
-
${item.content.slice(0, 2400)}`;
|
|
267
|
-
for (const rule of CATEGORY_PATTERNS) {
|
|
268
|
-
if (rule.patterns.some((pattern) => pattern.test(sample))) {
|
|
269
|
-
return rule.category;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return "inbox";
|
|
273
|
-
}
|
|
274
|
-
function normalizeForDedup(content) {
|
|
275
|
-
return compactWhitespace(content.replace(/\[\[[^\]]+\]\]/g, ""));
|
|
276
|
-
}
|
|
277
|
-
function wordSet(content) {
|
|
278
|
-
const words = compactWhitespace(content).split(" ").filter(Boolean);
|
|
279
|
-
const filtered = words.filter((word) => !STOP_WORDS.has(word) && word.length > 2);
|
|
280
|
-
return new Set(filtered);
|
|
281
|
-
}
|
|
282
|
-
function similarityScore(left, right) {
|
|
283
|
-
const leftWords = wordSet(left);
|
|
284
|
-
const rightWords = wordSet(right);
|
|
285
|
-
if (leftWords.size === 0 || rightWords.size === 0) {
|
|
286
|
-
return 0;
|
|
287
|
-
}
|
|
288
|
-
let intersection = 0;
|
|
289
|
-
for (const word of leftWords) {
|
|
290
|
-
if (rightWords.has(word)) {
|
|
291
|
-
intersection += 1;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
const union = leftWords.size + rightWords.size - intersection;
|
|
295
|
-
return union === 0 ? 0 : intersection / union;
|
|
296
|
-
}
|
|
297
|
-
function extractHeuristicInsights(content) {
|
|
298
|
-
const sentences = splitSentences(content);
|
|
299
|
-
const facts = sentences.filter((line) => /\b(is|are|was|were|has|have|contains|includes)\b/i.test(line)).slice(0, 8);
|
|
300
|
-
const decisions = sentences.filter((line) => /\b(decid(?:e|ed|ing|ion)|chose|selected|opted|agreed)\b/i.test(line)).slice(0, 8);
|
|
301
|
-
const lessons = sentences.filter((line) => /\b(learn(?:ed|ing|t)|lesson|insight|realized|next time|mistake)\b/i.test(line)).slice(0, 8);
|
|
302
|
-
if (facts.length === 0 && sentences.length > 0) {
|
|
303
|
-
facts.push(sentences[0]);
|
|
304
|
-
}
|
|
305
|
-
return { facts, decisions, lessons };
|
|
306
|
-
}
|
|
307
|
-
function buildHeuristicSurveyRecommendations(params) {
|
|
308
|
-
const recommendations = [];
|
|
309
|
-
if (params.inboxCount > 20) {
|
|
310
|
-
recommendations.push(`Inbox backlog is high (${params.inboxCount}); run \`clawvault maintain --worker curator\` more frequently.`);
|
|
311
|
-
}
|
|
312
|
-
if (params.linkedRatio < 0.25) {
|
|
313
|
-
recommendations.push("Graph connectivity is low; add wiki-links between related notes to improve context traversal.");
|
|
314
|
-
}
|
|
315
|
-
if ((params.categoryCounts.lessons ?? 0) < 5) {
|
|
316
|
-
recommendations.push("Lessons are sparse; run distillation on long-form captures to keep reusable learnings explicit.");
|
|
317
|
-
}
|
|
318
|
-
if ((params.categoryCounts.decisions ?? 0) < 5) {
|
|
319
|
-
recommendations.push("Decision coverage is light; capture major choices and rationale in decisions/.");
|
|
320
|
-
}
|
|
321
|
-
if (recommendations.length === 0) {
|
|
322
|
-
recommendations.push("Vault health looks balanced. Keep regular maintenance cadence and continue linking related notes.");
|
|
323
|
-
}
|
|
324
|
-
return recommendations;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
183
|
// src/lib/maintenance/worker-utils.ts
|
|
328
184
|
import * as fs4 from "fs";
|
|
329
185
|
import * as path4 from "path";
|
|
@@ -9,22 +9,22 @@ import {
|
|
|
9
9
|
} from "./chunk-OFOCU2V4.js";
|
|
10
10
|
import {
|
|
11
11
|
registerMaintainCommand
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-D5U3Q4N5.js";
|
|
13
13
|
import {
|
|
14
14
|
registerObserveCommand
|
|
15
15
|
} from "./chunk-BLQXXX7Q.js";
|
|
16
16
|
import {
|
|
17
17
|
registerContextCommand
|
|
18
18
|
} from "./chunk-GFCHWMGD.js";
|
|
19
|
-
import {
|
|
20
|
-
ClawVault
|
|
21
|
-
} from "./chunk-ECGJYWNA.js";
|
|
22
19
|
import {
|
|
23
20
|
registerEmbedCommand
|
|
24
21
|
} from "./chunk-T7E764W3.js";
|
|
25
22
|
import {
|
|
26
23
|
registerInboxCommand
|
|
27
24
|
} from "./chunk-HEHO7SMV.js";
|
|
25
|
+
import {
|
|
26
|
+
ClawVault
|
|
27
|
+
} from "./chunk-ECGJYWNA.js";
|
|
28
28
|
import {
|
|
29
29
|
resolveVaultPath
|
|
30
30
|
} from "./chunk-GJO3CFUN.js";
|