claude-launchpad 0.6.1 → 0.7.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 +54 -20
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-6ZVXZ4EF.js +101 -0
- package/dist/chunk-6ZVXZ4EF.js.map +1 -0
- package/dist/chunk-CSLWJEGD.js +25 -0
- package/dist/chunk-CSLWJEGD.js.map +1 -0
- package/dist/chunk-EBM7RBPB.js +35 -0
- package/dist/chunk-EBM7RBPB.js.map +1 -0
- package/dist/chunk-IILH26C7.js +258 -0
- package/dist/chunk-IILH26C7.js.map +1 -0
- package/dist/chunk-JE3BZ5S4.js +311 -0
- package/dist/chunk-JE3BZ5S4.js.map +1 -0
- package/dist/chunk-NAW47BYA.js +25 -0
- package/dist/chunk-NAW47BYA.js.map +1 -0
- package/dist/chunk-TALTTAMW.js +390 -0
- package/dist/chunk-TALTTAMW.js.map +1 -0
- package/dist/cli.js +348 -212
- package/dist/cli.js.map +1 -1
- package/dist/commands/memory/server.js +685 -0
- package/dist/commands/memory/server.js.map +1 -0
- package/dist/context-LNUZ4GCF.js +316 -0
- package/dist/context-LNUZ4GCF.js.map +1 -0
- package/dist/extract-NVAXO5CK.js +217 -0
- package/dist/extract-NVAXO5CK.js.map +1 -0
- package/dist/install-65P6LMUN.js +230 -0
- package/dist/install-65P6LMUN.js.map +1 -0
- package/dist/stats-FYAK7KZW.js +73 -0
- package/dist/stats-FYAK7KZW.js.map +1 -0
- package/dist/tui-R25NTQ4K.js +1100 -0
- package/dist/tui-R25NTQ4K.js.map +1 -0
- package/package.json +19 -4
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
SCORING_WEIGHTS
|
|
4
|
+
} from "./chunk-IILH26C7.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/memory/utils/git-context.ts
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
var cached = null;
|
|
9
|
+
function getGitContext() {
|
|
10
|
+
if (cached) return cached;
|
|
11
|
+
let branch = null;
|
|
12
|
+
let recentFiles = [];
|
|
13
|
+
try {
|
|
14
|
+
branch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8", timeout: 3e3 }).trim() || null;
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const raw = execSync("git diff --name-only HEAD~5", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
19
|
+
recentFiles = raw ? raw.split("\n").filter(Boolean) : [];
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
cached = { branch, recentFiles };
|
|
23
|
+
return cached;
|
|
24
|
+
}
|
|
25
|
+
function computeContextScore(storedContext, currentContext, query) {
|
|
26
|
+
if (!storedContext) return 0;
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(storedContext);
|
|
30
|
+
} catch {
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
const branchScore = parsed.branch && currentContext.branch && parsed.branch === currentContext.branch ? 1 : 0;
|
|
34
|
+
let fileScore = 0;
|
|
35
|
+
if (parsed.files?.length && currentContext.recentFiles.length) {
|
|
36
|
+
const stored = new Set(parsed.files);
|
|
37
|
+
const current = new Set(currentContext.recentFiles);
|
|
38
|
+
let intersection = 0;
|
|
39
|
+
for (const f of stored) {
|
|
40
|
+
if (current.has(f)) intersection++;
|
|
41
|
+
}
|
|
42
|
+
const union = stored.size + current.size - intersection;
|
|
43
|
+
fileScore = union > 0 ? intersection / union : 0;
|
|
44
|
+
}
|
|
45
|
+
let intentScore = 0;
|
|
46
|
+
if (parsed.intent && query) {
|
|
47
|
+
const intentWords = new Set(parsed.intent.toLowerCase().split(/\s+/).filter(Boolean));
|
|
48
|
+
const queryWords = new Set(query.toLowerCase().split(/\s+/).filter(Boolean));
|
|
49
|
+
let overlap = 0;
|
|
50
|
+
for (const w of intentWords) {
|
|
51
|
+
if (queryWords.has(w)) overlap++;
|
|
52
|
+
}
|
|
53
|
+
const union = intentWords.size + queryWords.size - overlap;
|
|
54
|
+
intentScore = union > 0 ? overlap / union : 0;
|
|
55
|
+
}
|
|
56
|
+
return branchScore * 0.4 + fileScore * 0.4 + intentScore * 0.2;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/commands/memory/services/retrieval-service.ts
|
|
60
|
+
var STALENESS_THRESHOLDS = {
|
|
61
|
+
working: 1,
|
|
62
|
+
episodic: 7,
|
|
63
|
+
pattern: 14,
|
|
64
|
+
semantic: 30,
|
|
65
|
+
procedural: 90
|
|
66
|
+
};
|
|
67
|
+
var RetrievalService = class {
|
|
68
|
+
#deps;
|
|
69
|
+
#gitContext;
|
|
70
|
+
constructor(deps) {
|
|
71
|
+
this.#deps = deps;
|
|
72
|
+
this.#gitContext = deps.gitContext ?? getGitContext();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* FTS5 keyword search with multi-signal scoring, context awareness,
|
|
76
|
+
* and relation-based graph expansion.
|
|
77
|
+
*/
|
|
78
|
+
async search(input) {
|
|
79
|
+
if (input.id) {
|
|
80
|
+
const memory = this.#deps.memoryRepo.getById(input.id);
|
|
81
|
+
if (!memory) return [];
|
|
82
|
+
this.#deps.memoryRepo.incrementAccess(input.id);
|
|
83
|
+
return [{
|
|
84
|
+
memory,
|
|
85
|
+
score: 1,
|
|
86
|
+
explanation: "Direct lookup by ID"
|
|
87
|
+
}];
|
|
88
|
+
}
|
|
89
|
+
const ftsResults = this.#deps.searchRepo.searchFts({
|
|
90
|
+
query: input.query,
|
|
91
|
+
limit: input.limit * 3,
|
|
92
|
+
type: input.type,
|
|
93
|
+
minImportance: input.min_importance,
|
|
94
|
+
project: input.project
|
|
95
|
+
});
|
|
96
|
+
const textScores = ftsOnlyScores(ftsResults);
|
|
97
|
+
if (textScores.size === 0) return [];
|
|
98
|
+
const candidates = [];
|
|
99
|
+
for (const [memoryId, textScore] of textScores) {
|
|
100
|
+
const memory = this.#deps.memoryRepo.getById(memoryId);
|
|
101
|
+
if (!memory) continue;
|
|
102
|
+
if (input.type && memory.type !== input.type) continue;
|
|
103
|
+
if (memory.importance < input.min_importance) continue;
|
|
104
|
+
if (input.tags?.length) {
|
|
105
|
+
const memTags = new Set(memory.tags);
|
|
106
|
+
if (!input.tags.every((t) => memTags.has(t))) continue;
|
|
107
|
+
}
|
|
108
|
+
const parts = computeScoreParts(memory, textScore, this.#gitContext, input.query);
|
|
109
|
+
const composite = computeComposite(parts);
|
|
110
|
+
candidates.push({ memory, composite, parts });
|
|
111
|
+
}
|
|
112
|
+
candidates.sort((a, b) => b.composite - a.composite);
|
|
113
|
+
let topN = candidates.slice(0, input.limit);
|
|
114
|
+
const seenIds = new Set(topN.map((c) => c.memory.id));
|
|
115
|
+
const expanded = this.#expandWithRelations(topN, input.limit, seenIds, input.min_importance);
|
|
116
|
+
if (expanded.length > 0) {
|
|
117
|
+
topN = [...topN, ...expanded].sort((a, b) => b.composite - a.composite).slice(0, input.limit);
|
|
118
|
+
}
|
|
119
|
+
return topN.map(({ memory, composite, parts }) => {
|
|
120
|
+
this.#deps.memoryRepo.incrementAccess(memory.id);
|
|
121
|
+
return {
|
|
122
|
+
memory,
|
|
123
|
+
score: composite,
|
|
124
|
+
explanation: parts.relationSource ? `Related (${parts.relationSource.type}) to: ${parts.relationSource.title ?? parts.relationSource.id}` : buildExplanation(parts, memory)
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Smart session loading: returns context-matched, recent, and related memories
|
|
130
|
+
* for session start context injection.
|
|
131
|
+
*/
|
|
132
|
+
loadSessionContext(input) {
|
|
133
|
+
const contextBudget = Math.floor(input.limit * 0.4);
|
|
134
|
+
const recentBudget = Math.floor(input.limit * 0.4);
|
|
135
|
+
const relatedBudget = input.limit - contextBudget - recentBudget;
|
|
136
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
137
|
+
const contextMatched = this.#loadContextMatched(contextBudget, input.project, input.type, seenIds);
|
|
138
|
+
const recent = this.#loadRecent(recentBudget, input.project, input.type, seenIds);
|
|
139
|
+
const sourceIds = [...contextMatched, ...recent].map((r) => r.result.memory.id);
|
|
140
|
+
const related = this.#loadRelatedExpansion(sourceIds, relatedBudget, seenIds);
|
|
141
|
+
const all = [...contextMatched, ...recent, ...related];
|
|
142
|
+
for (const entry of all) {
|
|
143
|
+
this.#deps.memoryRepo.incrementAccess(entry.result.memory.id);
|
|
144
|
+
}
|
|
145
|
+
return all;
|
|
146
|
+
}
|
|
147
|
+
#loadContextMatched(budget, project, type, seenIds) {
|
|
148
|
+
if (budget <= 0) return [];
|
|
149
|
+
const candidates = this.#deps.memoryRepo.getRecent(budget * 5, project, type);
|
|
150
|
+
const scored = [];
|
|
151
|
+
for (const m of candidates) {
|
|
152
|
+
const ctxScore = computeContextScore(m.context, this.#gitContext, "");
|
|
153
|
+
if (ctxScore <= 0.1) continue;
|
|
154
|
+
const composite = ctxScore * 0.5 + m.importance * 0.3 + computeRecencyScore(m.updatedAt) * 0.2;
|
|
155
|
+
scored.push({ memory: m, ctxScore, composite });
|
|
156
|
+
}
|
|
157
|
+
scored.sort((a, b) => b.composite - a.composite);
|
|
158
|
+
const results = [];
|
|
159
|
+
for (const s of scored.slice(0, budget)) {
|
|
160
|
+
seenIds?.add(s.memory.id);
|
|
161
|
+
results.push({ section: "context", result: { memory: s.memory, score: s.composite, explanation: "Context match" } });
|
|
162
|
+
}
|
|
163
|
+
return results;
|
|
164
|
+
}
|
|
165
|
+
#loadRecent(budget, project, type, seenIds) {
|
|
166
|
+
if (budget <= 0) return [];
|
|
167
|
+
const candidates = this.#deps.memoryRepo.getRecent(budget * 2, project, type);
|
|
168
|
+
const results = [];
|
|
169
|
+
for (const m of candidates) {
|
|
170
|
+
if (seenIds?.has(m.id)) continue;
|
|
171
|
+
const score = m.importance * 0.4 + computeRecencyScore(m.updatedAt) * 0.6;
|
|
172
|
+
seenIds?.add(m.id);
|
|
173
|
+
results.push({ section: "recent", result: { memory: m, score, explanation: "Recent" } });
|
|
174
|
+
if (results.length >= budget) break;
|
|
175
|
+
}
|
|
176
|
+
return results;
|
|
177
|
+
}
|
|
178
|
+
#loadRelatedExpansion(sourceIds, budget, seenIds) {
|
|
179
|
+
if (budget <= 0) return [];
|
|
180
|
+
const results = [];
|
|
181
|
+
for (const srcId of sourceIds) {
|
|
182
|
+
const relations = this.#deps.relationRepo.getByMemory(srcId);
|
|
183
|
+
for (const rel of relations) {
|
|
184
|
+
const otherId = rel.sourceId === srcId ? rel.targetId : rel.sourceId;
|
|
185
|
+
if (otherId === srcId || seenIds.has(otherId)) continue;
|
|
186
|
+
const other = this.#deps.memoryRepo.getById(otherId);
|
|
187
|
+
if (!other) continue;
|
|
188
|
+
const src = this.#deps.memoryRepo.getById(srcId);
|
|
189
|
+
const weight = RELATION_TYPE_WEIGHTS[rel.relationType] ?? 0.5;
|
|
190
|
+
seenIds.add(otherId);
|
|
191
|
+
results.push({
|
|
192
|
+
section: "related",
|
|
193
|
+
result: {
|
|
194
|
+
memory: other,
|
|
195
|
+
score: other.importance * weight,
|
|
196
|
+
explanation: `Related (${rel.relationType}) to: ${src?.title ?? srcId}`
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
if (results.length >= budget) return results;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return results;
|
|
203
|
+
}
|
|
204
|
+
#expandWithRelations(topResults, limit, seenIds, minImportance) {
|
|
205
|
+
if (topResults.length >= limit) return [];
|
|
206
|
+
const expanded = [];
|
|
207
|
+
const remaining = limit - topResults.length;
|
|
208
|
+
const sourcesToExpand = topResults.slice(0, 5);
|
|
209
|
+
for (const parent of sourcesToExpand) {
|
|
210
|
+
const relations = this.#deps.relationRepo.getByMemory(parent.memory.id);
|
|
211
|
+
for (const rel of relations) {
|
|
212
|
+
const otherId = rel.sourceId === parent.memory.id ? rel.targetId : rel.sourceId;
|
|
213
|
+
if (otherId === parent.memory.id || seenIds.has(otherId)) continue;
|
|
214
|
+
const other = this.#deps.memoryRepo.getById(otherId);
|
|
215
|
+
if (!other || other.importance < minImportance) continue;
|
|
216
|
+
const weight = RELATION_TYPE_WEIGHTS[rel.relationType] ?? 0.5;
|
|
217
|
+
const composite = parent.composite * 0.7 * weight;
|
|
218
|
+
const parts = {
|
|
219
|
+
textScore: 0,
|
|
220
|
+
importanceScore: other.importance,
|
|
221
|
+
recencyScore: computeRecencyScore(other.updatedAt),
|
|
222
|
+
accessScore: computeAccessScore(other.accessCount, other.lastAccessed),
|
|
223
|
+
contextScore: 0,
|
|
224
|
+
relationSource: { id: parent.memory.id, title: parent.memory.title, type: rel.relationType }
|
|
225
|
+
};
|
|
226
|
+
seenIds.add(otherId);
|
|
227
|
+
expanded.push({ memory: other, composite, parts });
|
|
228
|
+
if (expanded.length >= remaining) return expanded;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return expanded;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
var RELATION_TYPE_WEIGHTS = {
|
|
235
|
+
depends_on: 1,
|
|
236
|
+
extends: 0.9,
|
|
237
|
+
implements: 0.9,
|
|
238
|
+
relates_to: 0.7,
|
|
239
|
+
derived_from: 0.6,
|
|
240
|
+
contradicts: 0.3
|
|
241
|
+
};
|
|
242
|
+
function ftsOnlyScores(ftsResults) {
|
|
243
|
+
const scores = /* @__PURE__ */ new Map();
|
|
244
|
+
for (const r of ftsResults) {
|
|
245
|
+
const score = Math.min(1, -r.rank / 20);
|
|
246
|
+
scores.set(r.memoryId, score);
|
|
247
|
+
}
|
|
248
|
+
return scores;
|
|
249
|
+
}
|
|
250
|
+
function computeScoreParts(memory, textScore, gitContext, query) {
|
|
251
|
+
return {
|
|
252
|
+
textScore,
|
|
253
|
+
importanceScore: memory.importance,
|
|
254
|
+
recencyScore: computeRecencyScore(memory.updatedAt),
|
|
255
|
+
accessScore: computeAccessScore(memory.accessCount, memory.lastAccessed),
|
|
256
|
+
contextScore: computeContextScore(memory.context, gitContext, query)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function computeComposite(parts) {
|
|
260
|
+
return parts.textScore * SCORING_WEIGHTS.text + parts.importanceScore * SCORING_WEIGHTS.importance + parts.recencyScore * SCORING_WEIGHTS.recency + parts.accessScore * SCORING_WEIGHTS.access + parts.contextScore * SCORING_WEIGHTS.context;
|
|
261
|
+
}
|
|
262
|
+
function computeRecencyScore(updatedAt) {
|
|
263
|
+
const ageMs = Date.now() - new Date(updatedAt).getTime();
|
|
264
|
+
const ageDays = ageMs / (1e3 * 60 * 60 * 24);
|
|
265
|
+
if (ageDays < 0) return 1;
|
|
266
|
+
return Math.exp(-ageDays * Math.LN2 / 30);
|
|
267
|
+
}
|
|
268
|
+
function computeAccessScore(accessCount, lastAccessed) {
|
|
269
|
+
if (accessCount <= 0) return 0;
|
|
270
|
+
const countScore = Math.min(1, Math.log10(accessCount + 1) / Math.log10(31));
|
|
271
|
+
if (!lastAccessed) return countScore * 0.5;
|
|
272
|
+
const ageDays = (Date.now() - new Date(lastAccessed).getTime()) / 864e5;
|
|
273
|
+
const recency = Math.exp(-ageDays * Math.LN2 / 30);
|
|
274
|
+
return countScore * (0.5 + 0.5 * recency);
|
|
275
|
+
}
|
|
276
|
+
function buildExplanation(parts, memory) {
|
|
277
|
+
const factors = [];
|
|
278
|
+
if (parts.textScore > 0.7) {
|
|
279
|
+
factors.push(`High text match (${(parts.textScore * 100).toFixed(0)}%)`);
|
|
280
|
+
} else if (parts.textScore > 0.3) {
|
|
281
|
+
factors.push(`Moderate text match (${(parts.textScore * 100).toFixed(0)}%)`);
|
|
282
|
+
}
|
|
283
|
+
if (memory.importance > 0.7) {
|
|
284
|
+
factors.push("High importance");
|
|
285
|
+
}
|
|
286
|
+
if (parts.recencyScore > 0.8) {
|
|
287
|
+
factors.push("Very recent");
|
|
288
|
+
} else if (parts.recencyScore > 0.5) {
|
|
289
|
+
factors.push("Recent");
|
|
290
|
+
}
|
|
291
|
+
if (memory.accessCount > 10) {
|
|
292
|
+
factors.push(`Frequently accessed (${memory.accessCount}x)`);
|
|
293
|
+
} else if (memory.accessCount > 3) {
|
|
294
|
+
factors.push(`Accessed ${memory.accessCount}x`);
|
|
295
|
+
}
|
|
296
|
+
if (parts.contextScore > 0.3) {
|
|
297
|
+
factors.push("Context match");
|
|
298
|
+
}
|
|
299
|
+
const ageDays = Math.floor((Date.now() - new Date(memory.createdAt).getTime()) / 864e5);
|
|
300
|
+
const stalenessThreshold = STALENESS_THRESHOLDS[memory.type];
|
|
301
|
+
if (stalenessThreshold !== void 0 && ageDays > stalenessThreshold) {
|
|
302
|
+
factors.push(`${ageDays}d old - verify before acting`);
|
|
303
|
+
}
|
|
304
|
+
return factors.length > 0 ? factors.join(" + ") : "Matched query";
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export {
|
|
308
|
+
getGitContext,
|
|
309
|
+
RetrievalService
|
|
310
|
+
};
|
|
311
|
+
//# sourceMappingURL=chunk-JE3BZ5S4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/memory/utils/git-context.ts","../src/commands/memory/services/retrieval-service.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\n\nexport interface GitContext {\n readonly branch: string | null;\n readonly recentFiles: readonly string[];\n}\n\nlet cached: GitContext | null = null;\n\nexport function getGitContext(): GitContext {\n if (cached) return cached;\n\n let branch: string | null = null;\n let recentFiles: string[] = [];\n\n try {\n branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8', timeout: 3000 }).trim() || null;\n } catch { /* not in a git repo or git not available */ }\n\n try {\n const raw = execSync('git diff --name-only HEAD~5', { encoding: 'utf-8', timeout: 3000 }).trim();\n recentFiles = raw ? raw.split('\\n').filter(Boolean) : [];\n } catch { /* shallow clone or no commits */ }\n\n cached = { branch, recentFiles };\n return cached;\n}\n\nexport function clearGitContextCache(): void {\n cached = null;\n}\n\nexport function computeContextScore(\n storedContext: string | null,\n currentContext: GitContext,\n query: string,\n): number {\n if (!storedContext) return 0;\n\n let parsed: { files?: string[]; branch?: string; intent?: string };\n try {\n parsed = JSON.parse(storedContext) as { files?: string[]; branch?: string; intent?: string };\n } catch {\n return 0;\n }\n\n // Branch match (weight 0.4)\n const branchScore = (parsed.branch && currentContext.branch && parsed.branch === currentContext.branch) ? 1.0 : 0;\n\n // File overlap — Jaccard similarity (weight 0.4)\n let fileScore = 0;\n if (parsed.files?.length && currentContext.recentFiles.length) {\n const stored = new Set(parsed.files);\n const current = new Set(currentContext.recentFiles);\n let intersection = 0;\n for (const f of stored) {\n if (current.has(f)) intersection++;\n }\n const union = stored.size + current.size - intersection;\n fileScore = union > 0 ? intersection / union : 0;\n }\n\n // Intent keyword overlap with query (weight 0.2)\n let intentScore = 0;\n if (parsed.intent && query) {\n const intentWords = new Set(parsed.intent.toLowerCase().split(/\\s+/).filter(Boolean));\n const queryWords = new Set(query.toLowerCase().split(/\\s+/).filter(Boolean));\n let overlap = 0;\n for (const w of intentWords) {\n if (queryWords.has(w)) overlap++;\n }\n const union = intentWords.size + queryWords.size - overlap;\n intentScore = union > 0 ? overlap / union : 0;\n }\n\n return branchScore * 0.4 + fileScore * 0.4 + intentScore * 0.2;\n}\n","import type { Memory, MemoryType, SearchResult, SearchInput, RelationType } from '../types.js';\nimport type { MemoryRepo } from '../storage/memory-repo.js';\nimport type { RelationRepo } from '../storage/relation-repo.js';\nimport type { SearchRepo } from '../storage/search-repo.js';\nimport { SCORING_WEIGHTS } from '../config.js';\nimport { type GitContext, getGitContext, computeContextScore } from '../utils/git-context.js';\n\nconst STALENESS_THRESHOLDS = {\n working: 1,\n episodic: 7,\n pattern: 14,\n semantic: 30,\n procedural: 90,\n} as const;\n\nexport interface RetrievalDeps {\n readonly memoryRepo: MemoryRepo;\n readonly relationRepo: RelationRepo;\n readonly searchRepo: SearchRepo;\n readonly gitContext?: GitContext;\n}\n\nexport class RetrievalService {\n readonly #deps: RetrievalDeps;\n readonly #gitContext: GitContext;\n\n constructor(deps: RetrievalDeps) {\n this.#deps = deps;\n this.#gitContext = deps.gitContext ?? getGitContext();\n }\n\n /**\n * FTS5 keyword search with multi-signal scoring, context awareness,\n * and relation-based graph expansion.\n */\n async search(input: SearchInput): Promise<readonly SearchResult[]> {\n // Direct ID lookup\n if (input.id) {\n const memory = this.#deps.memoryRepo.getById(input.id);\n if (!memory) return [];\n this.#deps.memoryRepo.incrementAccess(input.id);\n return [{\n memory,\n score: 1.0,\n explanation: 'Direct lookup by ID',\n }];\n }\n\n // Phase 1: FTS5 keyword search\n const ftsResults = this.#deps.searchRepo.searchFts({\n query: input.query,\n limit: input.limit * 3,\n type: input.type,\n minImportance: input.min_importance,\n project: input.project,\n });\n\n const textScores = ftsOnlyScores(ftsResults);\n if (textScores.size === 0) return [];\n\n // Phase 2: Load memories and compute composite scores\n const candidates: { memory: Memory; composite: number; parts: ScoreParts }[] = [];\n\n for (const [memoryId, textScore] of textScores) {\n const memory = this.#deps.memoryRepo.getById(memoryId);\n if (!memory) continue;\n if (input.type && memory.type !== input.type) continue;\n if (memory.importance < input.min_importance) continue;\n if (input.tags?.length) {\n const memTags = new Set(memory.tags);\n if (!input.tags.every(t => memTags.has(t))) continue;\n }\n\n const parts = computeScoreParts(memory, textScore, this.#gitContext, input.query);\n const composite = computeComposite(parts);\n candidates.push({ memory, composite, parts });\n }\n\n // Phase 3: Sort by composite score\n candidates.sort((a, b) => b.composite - a.composite);\n let topN = candidates.slice(0, input.limit);\n\n // Phase 4: Relation expansion — surface connected memories\n const seenIds = new Set(topN.map(c => c.memory.id));\n const expanded = this.#expandWithRelations(topN, input.limit, seenIds, input.min_importance);\n if (expanded.length > 0) {\n topN = [...topN, ...expanded].sort((a, b) => b.composite - a.composite).slice(0, input.limit);\n }\n\n // Phase 5: Build results with explanations, increment access\n return topN.map(({ memory, composite, parts }) => {\n this.#deps.memoryRepo.incrementAccess(memory.id);\n return {\n memory,\n score: composite,\n explanation: parts.relationSource\n ? `Related (${parts.relationSource.type}) to: ${parts.relationSource.title ?? parts.relationSource.id}`\n : buildExplanation(parts, memory),\n };\n });\n }\n\n /**\n * Smart session loading: returns context-matched, recent, and related memories\n * for session start context injection.\n */\n loadSessionContext(input: {\n readonly limit: number;\n readonly project?: string;\n readonly type?: MemoryType;\n }): readonly SessionContextResult[] {\n const contextBudget = Math.floor(input.limit * 0.4);\n const recentBudget = Math.floor(input.limit * 0.4);\n const relatedBudget = input.limit - contextBudget - recentBudget;\n const seenIds = new Set<string>();\n\n const contextMatched = this.#loadContextMatched(contextBudget, input.project, input.type, seenIds);\n const recent = this.#loadRecent(recentBudget, input.project, input.type, seenIds);\n\n const sourceIds = [...contextMatched, ...recent].map(r => r.result.memory.id);\n const related = this.#loadRelatedExpansion(sourceIds, relatedBudget, seenIds);\n\n const all = [...contextMatched, ...recent, ...related];\n for (const entry of all) {\n this.#deps.memoryRepo.incrementAccess(entry.result.memory.id);\n }\n return all;\n }\n\n #loadContextMatched(\n budget: number, project?: string, type?: MemoryType, seenIds?: Set<string>,\n ): SessionContextResult[] {\n if (budget <= 0) return [];\n const candidates = this.#deps.memoryRepo.getRecent(budget * 5, project, type);\n const scored: { memory: Memory; ctxScore: number; composite: number }[] = [];\n\n for (const m of candidates) {\n const ctxScore = computeContextScore(m.context, this.#gitContext, '');\n if (ctxScore <= 0.1) continue;\n const composite = ctxScore * 0.5 + m.importance * 0.3 + computeRecencyScore(m.updatedAt) * 0.2;\n scored.push({ memory: m, ctxScore, composite });\n }\n\n scored.sort((a, b) => b.composite - a.composite);\n const results: SessionContextResult[] = [];\n for (const s of scored.slice(0, budget)) {\n seenIds?.add(s.memory.id);\n results.push({ section: 'context', result: { memory: s.memory, score: s.composite, explanation: 'Context match' } });\n }\n return results;\n }\n\n #loadRecent(\n budget: number, project?: string, type?: MemoryType, seenIds?: Set<string>,\n ): SessionContextResult[] {\n if (budget <= 0) return [];\n const candidates = this.#deps.memoryRepo.getRecent(budget * 2, project, type);\n const results: SessionContextResult[] = [];\n\n for (const m of candidates) {\n if (seenIds?.has(m.id)) continue;\n const score = m.importance * 0.4 + computeRecencyScore(m.updatedAt) * 0.6;\n seenIds?.add(m.id);\n results.push({ section: 'recent', result: { memory: m, score, explanation: 'Recent' } });\n if (results.length >= budget) break;\n }\n return results;\n }\n\n #loadRelatedExpansion(\n sourceIds: readonly string[], budget: number, seenIds: Set<string>,\n ): SessionContextResult[] {\n if (budget <= 0) return [];\n const results: SessionContextResult[] = [];\n\n for (const srcId of sourceIds) {\n const relations = this.#deps.relationRepo.getByMemory(srcId);\n for (const rel of relations) {\n const otherId = rel.sourceId === srcId ? rel.targetId : rel.sourceId;\n if (otherId === srcId || seenIds.has(otherId)) continue;\n\n const other = this.#deps.memoryRepo.getById(otherId);\n if (!other) continue;\n\n const src = this.#deps.memoryRepo.getById(srcId);\n const weight = RELATION_TYPE_WEIGHTS[rel.relationType] ?? 0.5;\n seenIds.add(otherId);\n results.push({\n section: 'related',\n result: {\n memory: other,\n score: other.importance * weight,\n explanation: `Related (${rel.relationType}) to: ${src?.title ?? srcId}`,\n },\n });\n if (results.length >= budget) return results;\n }\n }\n return results;\n }\n\n #expandWithRelations(\n topResults: readonly { memory: Memory; composite: number; parts: ScoreParts }[],\n limit: number,\n seenIds: Set<string>,\n minImportance: number,\n ): { memory: Memory; composite: number; parts: ScoreParts }[] {\n if (topResults.length >= limit) return [];\n\n const expanded: { memory: Memory; composite: number; parts: ScoreParts }[] = [];\n const remaining = limit - topResults.length;\n const sourcesToExpand = topResults.slice(0, 5);\n\n for (const parent of sourcesToExpand) {\n const relations = this.#deps.relationRepo.getByMemory(parent.memory.id);\n for (const rel of relations) {\n const otherId = rel.sourceId === parent.memory.id ? rel.targetId : rel.sourceId;\n if (otherId === parent.memory.id || seenIds.has(otherId)) continue;\n\n const other = this.#deps.memoryRepo.getById(otherId);\n if (!other || other.importance < minImportance) continue;\n\n const weight = RELATION_TYPE_WEIGHTS[rel.relationType] ?? 0.5;\n const composite = parent.composite * 0.7 * weight;\n const parts: ScoreParts = {\n textScore: 0,\n importanceScore: other.importance,\n recencyScore: computeRecencyScore(other.updatedAt),\n accessScore: computeAccessScore(other.accessCount, other.lastAccessed),\n contextScore: 0,\n relationSource: { id: parent.memory.id, title: parent.memory.title, type: rel.relationType },\n };\n\n seenIds.add(otherId);\n expanded.push({ memory: other, composite, parts });\n if (expanded.length >= remaining) return expanded;\n }\n }\n\n return expanded;\n }\n}\n\n// ── Relation Expansion ──────────────────────────────────────\n\nconst RELATION_TYPE_WEIGHTS: Record<RelationType, number> = {\n depends_on: 1.0,\n extends: 0.9,\n implements: 0.9,\n relates_to: 0.7,\n derived_from: 0.6,\n contradicts: 0.3,\n};\n\n// ── FTS5-Only Fallback ───────────────────────────────────────\n\nfunction ftsOnlyScores(ftsResults: readonly { memoryId: string; rank: number }[]): Map<string, number> {\n const scores = new Map<string, number>();\n for (const r of ftsResults) {\n const score = Math.min(1, -r.rank / 20);\n scores.set(r.memoryId, score);\n }\n return scores;\n}\n\n// ── Scoring ───────────────────────────────────────────────────\n\ninterface ScoreParts {\n readonly textScore: number;\n readonly importanceScore: number;\n readonly recencyScore: number;\n readonly accessScore: number;\n readonly contextScore: number;\n readonly relationSource?: { readonly id: string; readonly title: string | null; readonly type: RelationType };\n}\n\nfunction computeScoreParts(\n memory: Memory,\n textScore: number,\n gitContext: GitContext,\n query: string,\n): ScoreParts {\n return {\n textScore,\n importanceScore: memory.importance,\n recencyScore: computeRecencyScore(memory.updatedAt),\n accessScore: computeAccessScore(memory.accessCount, memory.lastAccessed),\n contextScore: computeContextScore(memory.context, gitContext, query),\n };\n}\n\nfunction computeComposite(parts: ScoreParts): number {\n return (\n parts.textScore * SCORING_WEIGHTS.text +\n parts.importanceScore * SCORING_WEIGHTS.importance +\n parts.recencyScore * SCORING_WEIGHTS.recency +\n parts.accessScore * SCORING_WEIGHTS.access +\n parts.contextScore * SCORING_WEIGHTS.context\n );\n}\n\n/**\n * Recency score: 1.0 for today, decays exponentially.\n * Uses a 30-day half-life.\n */\nfunction computeRecencyScore(updatedAt: string): number {\n const ageMs = Date.now() - new Date(updatedAt).getTime();\n const ageDays = ageMs / (1000 * 60 * 60 * 24);\n if (ageDays < 0) return 1.0;\n return Math.exp(-ageDays * Math.LN2 / 30);\n}\n\n/**\n * Access score: logarithmic count x recency of last access.\n */\nfunction computeAccessScore(accessCount: number, lastAccessed: string | null): number {\n if (accessCount <= 0) return 0;\n const countScore = Math.min(1.0, Math.log10(accessCount + 1) / Math.log10(31));\n if (!lastAccessed) return countScore * 0.5;\n const ageDays = (Date.now() - new Date(lastAccessed).getTime()) / 86_400_000;\n const recency = Math.exp(-ageDays * Math.LN2 / 30);\n return countScore * (0.5 + 0.5 * recency);\n}\n\n// ── Explanation ───────────────────────────────────────────────\n\nfunction buildExplanation(parts: ScoreParts, memory: Memory): string {\n const factors: string[] = [];\n\n if (parts.textScore > 0.7) {\n factors.push(`High text match (${(parts.textScore * 100).toFixed(0)}%)`);\n } else if (parts.textScore > 0.3) {\n factors.push(`Moderate text match (${(parts.textScore * 100).toFixed(0)}%)`);\n }\n\n if (memory.importance > 0.7) {\n factors.push('High importance');\n }\n\n if (parts.recencyScore > 0.8) {\n factors.push('Very recent');\n } else if (parts.recencyScore > 0.5) {\n factors.push('Recent');\n }\n\n if (memory.accessCount > 10) {\n factors.push(`Frequently accessed (${memory.accessCount}x)`);\n } else if (memory.accessCount > 3) {\n factors.push(`Accessed ${memory.accessCount}x`);\n }\n\n if (parts.contextScore > 0.3) {\n factors.push('Context match');\n }\n\n const ageDays = Math.floor((Date.now() - new Date(memory.createdAt).getTime()) / 86_400_000);\n const stalenessThreshold = STALENESS_THRESHOLDS[memory.type];\n if (stalenessThreshold !== undefined && ageDays > stalenessThreshold) {\n factors.push(`${ageDays}d old - verify before acting`);\n }\n\n return factors.length > 0 ? factors.join(' + ') : 'Matched query';\n}\n\n// ── Types ────────────────────────────────────────────────────\n\nexport interface SessionContextResult {\n readonly section: 'context' | 'recent' | 'related';\n readonly result: SearchResult;\n}\n\n// Exported for testing\nexport { computeRecencyScore, computeAccessScore, computeComposite, computeScoreParts, ftsOnlyScores };\nexport type { ScoreParts };\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/memory/utils/project.ts
|
|
4
|
+
import { readFileSync, existsSync } from "fs";
|
|
5
|
+
import { join, basename } from "path";
|
|
6
|
+
function detectProject(cwd) {
|
|
7
|
+
const pkgPath = join(cwd, "package.json");
|
|
8
|
+
if (existsSync(pkgPath)) {
|
|
9
|
+
try {
|
|
10
|
+
const raw = readFileSync(pkgPath, "utf-8");
|
|
11
|
+
const pkg = JSON.parse(raw);
|
|
12
|
+
if (pkg.name && typeof pkg.name === "string") {
|
|
13
|
+
return pkg.name;
|
|
14
|
+
}
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const dirName = basename(cwd);
|
|
19
|
+
return dirName || null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
detectProject
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=chunk-NAW47BYA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/memory/utils/project.ts"],"sourcesContent":["import { readFileSync, existsSync } from 'node:fs';\nimport { join, basename } from 'node:path';\n\n/**\n * Detect the current project name from the working directory.\n * Resolution order:\n * 1. package.json \"name\" field\n * 2. Directory basename\n *\n * Returns null if detection fails (shouldn't happen in practice).\n */\nexport function detectProject(cwd: string): string | null {\n // Try package.json name\n const pkgPath = join(cwd, 'package.json');\n if (existsSync(pkgPath)) {\n try {\n const raw = readFileSync(pkgPath, 'utf-8');\n const pkg = JSON.parse(raw) as { name?: string };\n if (pkg.name && typeof pkg.name === 'string') {\n return pkg.name;\n }\n } catch { /* fall through */ }\n }\n\n // Fall back to directory name\n const dirName = basename(cwd);\n return dirName || null;\n}\n"],"mappings":";;;AAAA,SAAS,cAAc,kBAAkB;AACzC,SAAS,MAAM,gBAAgB;AAUxB,SAAS,cAAc,KAA4B;AAExD,QAAM,UAAU,KAAK,KAAK,cAAc;AACxC,MAAI,WAAW,OAAO,GAAG;AACvB,QAAI;AACF,YAAM,MAAM,aAAa,SAAS,OAAO;AACzC,YAAM,MAAM,KAAK,MAAM,GAAG;AAC1B,UAAI,IAAI,QAAQ,OAAO,IAAI,SAAS,UAAU;AAC5C,eAAO,IAAI;AAAA,MACb;AAAA,IACF,QAAQ;AAAA,IAAqB;AAAA,EAC/B;AAGA,QAAM,UAAU,SAAS,GAAG;AAC5B,SAAO,WAAW;AACpB;","names":[]}
|