clawmem 0.1.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.
- package/AGENTS.md +660 -0
- package/CLAUDE.md +660 -0
- package/LICENSE +21 -0
- package/README.md +993 -0
- package/SKILL.md +717 -0
- package/bin/clawmem +75 -0
- package/package.json +72 -0
- package/src/amem.ts +797 -0
- package/src/beads.ts +263 -0
- package/src/clawmem.ts +1849 -0
- package/src/collections.ts +405 -0
- package/src/config.ts +178 -0
- package/src/consolidation.ts +123 -0
- package/src/directory-context.ts +248 -0
- package/src/errors.ts +41 -0
- package/src/formatter.ts +427 -0
- package/src/graph-traversal.ts +247 -0
- package/src/hooks/context-surfacing.ts +317 -0
- package/src/hooks/curator-nudge.ts +89 -0
- package/src/hooks/decision-extractor.ts +639 -0
- package/src/hooks/feedback-loop.ts +214 -0
- package/src/hooks/handoff-generator.ts +345 -0
- package/src/hooks/postcompact-inject.ts +226 -0
- package/src/hooks/precompact-extract.ts +314 -0
- package/src/hooks/pretool-inject.ts +79 -0
- package/src/hooks/session-bootstrap.ts +324 -0
- package/src/hooks/staleness-check.ts +130 -0
- package/src/hooks.ts +367 -0
- package/src/indexer.ts +327 -0
- package/src/intent.ts +294 -0
- package/src/limits.ts +26 -0
- package/src/llm.ts +1175 -0
- package/src/mcp.ts +2138 -0
- package/src/memory.ts +336 -0
- package/src/mmr.ts +93 -0
- package/src/observer.ts +269 -0
- package/src/openclaw/engine.ts +283 -0
- package/src/openclaw/index.ts +221 -0
- package/src/openclaw/plugin.json +83 -0
- package/src/openclaw/shell.ts +207 -0
- package/src/openclaw/tools.ts +304 -0
- package/src/profile.ts +346 -0
- package/src/promptguard.ts +218 -0
- package/src/retrieval-gate.ts +106 -0
- package/src/search-utils.ts +127 -0
- package/src/server.ts +783 -0
- package/src/splitter.ts +325 -0
- package/src/store.ts +4062 -0
- package/src/validation.ts +67 -0
- package/src/watcher.ts +58 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawMem Search Utilities — Shared enrichment and fusion functions
|
|
3
|
+
*
|
|
4
|
+
* Consolidates duplicated code from clawmem.ts, mcp.ts, and context-surfacing.ts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Store, SearchResult } from "./store.ts";
|
|
8
|
+
import type { EnrichedResult } from "./memory.ts";
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Result Enrichment
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Join search results with SAME metadata from the documents table.
|
|
16
|
+
* Adds content_type, modified_at, access_count, confidence to each result.
|
|
17
|
+
*/
|
|
18
|
+
export function enrichResults(
|
|
19
|
+
store: Store,
|
|
20
|
+
results: SearchResult[],
|
|
21
|
+
_query: string
|
|
22
|
+
): EnrichedResult[] {
|
|
23
|
+
return results.map(r => {
|
|
24
|
+
const row = store.db.prepare(`
|
|
25
|
+
SELECT content_type, modified_at, access_count, confidence, domain, workstream, tags,
|
|
26
|
+
quality_score, pinned, last_accessed_at, duplicate_count, revision_count
|
|
27
|
+
FROM documents
|
|
28
|
+
WHERE active = 1 AND (collection || '/' || path) = ?
|
|
29
|
+
LIMIT 1
|
|
30
|
+
`).get(r.displayPath) as any | null;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
...r,
|
|
34
|
+
contentType: row?.content_type ?? "note",
|
|
35
|
+
modifiedAt: row?.modified_at ?? r.modifiedAt,
|
|
36
|
+
accessCount: row?.access_count ?? 0,
|
|
37
|
+
confidence: row?.confidence ?? 0.5,
|
|
38
|
+
qualityScore: row?.quality_score ?? 0.5,
|
|
39
|
+
pinned: !!(row?.pinned),
|
|
40
|
+
lastAccessedAt: row?.last_accessed_at ?? null,
|
|
41
|
+
duplicateCount: row?.duplicate_count ?? 1,
|
|
42
|
+
revisionCount: row?.revision_count ?? 1,
|
|
43
|
+
} as EnrichedResult;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// Ranked Result Type (for RRF)
|
|
49
|
+
// =============================================================================
|
|
50
|
+
|
|
51
|
+
export type RankedResult = {
|
|
52
|
+
file: string;
|
|
53
|
+
displayPath: string;
|
|
54
|
+
title: string;
|
|
55
|
+
body: string;
|
|
56
|
+
score: number;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// Reciprocal Rank Fusion
|
|
61
|
+
// =============================================================================
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Merge multiple ranked result lists using Reciprocal Rank Fusion.
|
|
65
|
+
* k=60 is the standard RRF constant. Top-rank bonuses reward results
|
|
66
|
+
* that appear at rank 0 (+0.05) or rank 1-2 (+0.02).
|
|
67
|
+
*/
|
|
68
|
+
export function reciprocalRankFusion(
|
|
69
|
+
resultLists: RankedResult[][],
|
|
70
|
+
weights: number[],
|
|
71
|
+
k: number = 60
|
|
72
|
+
): RankedResult[] {
|
|
73
|
+
// Validate weights match result lists when explicitly provided
|
|
74
|
+
if (weights.length > 0 && weights.length !== resultLists.length) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`weights length (${weights.length}) must match resultLists length (${resultLists.length})`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Validate k is finite and positive
|
|
81
|
+
if (!Number.isFinite(k) || k <= 0) k = 60;
|
|
82
|
+
|
|
83
|
+
// Validate all weights are finite and non-negative
|
|
84
|
+
for (let w = 0; w < weights.length; w++) {
|
|
85
|
+
if (!Number.isFinite(weights[w]) || weights[w]! < 0) {
|
|
86
|
+
weights[w] = 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const scores = new Map<string, { score: number; result: RankedResult }>();
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < resultLists.length; i++) {
|
|
93
|
+
const list = resultLists[i]!;
|
|
94
|
+
const weight = weights[i] ?? 1;
|
|
95
|
+
if (weight === 0) continue; // Skip zero-weight lists entirely
|
|
96
|
+
for (let rank = 0; rank < list.length; rank++) {
|
|
97
|
+
const r = list[rank]!;
|
|
98
|
+
const existing = scores.get(r.file);
|
|
99
|
+
const rrfScore = weight / (k + rank + 1);
|
|
100
|
+
const bonus = rank === 0 ? 0.05 : rank <= 2 ? 0.02 : 0;
|
|
101
|
+
const total = rrfScore + bonus;
|
|
102
|
+
|
|
103
|
+
if (existing) {
|
|
104
|
+
existing.score += total;
|
|
105
|
+
} else {
|
|
106
|
+
scores.set(r.file, { score: total, result: r });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return [...scores.values()]
|
|
112
|
+
.sort((a, b) => b.score - a.score)
|
|
113
|
+
.map(v => ({ ...v.result, score: v.score }));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Convert a SearchResult to a RankedResult for use in RRF.
|
|
118
|
+
*/
|
|
119
|
+
export function toRanked(r: SearchResult): RankedResult {
|
|
120
|
+
return {
|
|
121
|
+
file: r.filepath,
|
|
122
|
+
displayPath: r.displayPath,
|
|
123
|
+
title: r.title,
|
|
124
|
+
body: r.body || "",
|
|
125
|
+
score: r.score,
|
|
126
|
+
};
|
|
127
|
+
}
|