@teammates/recall 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/README.md +156 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +249 -0
- package/dist/embeddings.d.ts +12 -0
- package/dist/embeddings.js +35 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/indexer.d.ts +54 -0
- package/dist/indexer.js +172 -0
- package/dist/search.d.ts +26 -0
- package/dist/search.js +75 -0
- package/package.json +34 -0
- package/src/cli.ts +275 -0
- package/src/embeddings.ts +43 -0
- package/src/index.ts +3 -0
- package/src/indexer.ts +203 -0
- package/src/search.ts +107 -0
- package/tsconfig.json +18 -0
package/src/search.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { LocalDocumentIndex } from "vectra";
|
|
2
|
+
import { LocalEmbeddings } from "./embeddings.js";
|
|
3
|
+
import { Indexer } from "./indexer.js";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import * as fs from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
export interface SearchOptions {
|
|
8
|
+
/** Path to the .teammates directory */
|
|
9
|
+
teammatesDir: string;
|
|
10
|
+
/** Teammate name to search (searches all if omitted) */
|
|
11
|
+
teammate?: string;
|
|
12
|
+
/** Max results per teammate (default: 5) */
|
|
13
|
+
maxResults?: number;
|
|
14
|
+
/** Max chunks per document (default: 3) */
|
|
15
|
+
maxChunks?: number;
|
|
16
|
+
/** Max tokens per section (default: 500) */
|
|
17
|
+
maxTokens?: number;
|
|
18
|
+
/** Embedding model name */
|
|
19
|
+
model?: string;
|
|
20
|
+
/** Skip auto-sync before searching (default: false) */
|
|
21
|
+
skipSync?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SearchResult {
|
|
25
|
+
teammate: string;
|
|
26
|
+
uri: string;
|
|
27
|
+
text: string;
|
|
28
|
+
score: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Search teammate memories using semantic + keyword search.
|
|
33
|
+
*/
|
|
34
|
+
export async function search(
|
|
35
|
+
query: string,
|
|
36
|
+
options: SearchOptions
|
|
37
|
+
): Promise<SearchResult[]> {
|
|
38
|
+
const indexRoot = path.join(options.teammatesDir, ".index");
|
|
39
|
+
const embeddings = new LocalEmbeddings(options.model);
|
|
40
|
+
const maxResults = options.maxResults ?? 5;
|
|
41
|
+
const maxChunks = options.maxChunks ?? 3;
|
|
42
|
+
const maxTokens = options.maxTokens ?? 500;
|
|
43
|
+
|
|
44
|
+
// Auto-sync: upsert any new/changed files before searching
|
|
45
|
+
if (!options.skipSync) {
|
|
46
|
+
const indexer = new Indexer({ teammatesDir: options.teammatesDir, model: options.model });
|
|
47
|
+
if (options.teammate) {
|
|
48
|
+
await indexer.syncTeammate(options.teammate);
|
|
49
|
+
} else {
|
|
50
|
+
await indexer.syncAll();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Determine which teammates to search
|
|
55
|
+
let teammates: string[];
|
|
56
|
+
if (options.teammate) {
|
|
57
|
+
teammates = [options.teammate];
|
|
58
|
+
} else {
|
|
59
|
+
try {
|
|
60
|
+
const entries = await fs.readdir(indexRoot, { withFileTypes: true });
|
|
61
|
+
teammates = entries
|
|
62
|
+
.filter((e) => e.isDirectory())
|
|
63
|
+
.map((e) => e.name);
|
|
64
|
+
} catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const allResults: SearchResult[] = [];
|
|
70
|
+
|
|
71
|
+
for (const teammate of teammates) {
|
|
72
|
+
const indexPath = path.join(indexRoot, teammate);
|
|
73
|
+
try {
|
|
74
|
+
await fs.access(indexPath);
|
|
75
|
+
} catch {
|
|
76
|
+
continue; // No index for this teammate
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const index = new LocalDocumentIndex({
|
|
80
|
+
folderPath: indexPath,
|
|
81
|
+
embeddings,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (!(await index.isIndexCreated())) continue;
|
|
85
|
+
|
|
86
|
+
const docs = await index.queryDocuments(query, {
|
|
87
|
+
maxDocuments: maxResults,
|
|
88
|
+
maxChunks,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
for (const doc of docs) {
|
|
92
|
+
const sections = await doc.renderSections(maxTokens, 1);
|
|
93
|
+
for (const section of sections) {
|
|
94
|
+
allResults.push({
|
|
95
|
+
teammate,
|
|
96
|
+
uri: doc.uri,
|
|
97
|
+
text: section.text,
|
|
98
|
+
score: section.score,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Sort by score descending, return top results
|
|
105
|
+
allResults.sort((a, b) => b.score - a.score);
|
|
106
|
+
return allResults.slice(0, maxResults);
|
|
107
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"rootDir": "src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"resolveJsonModule": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|