@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/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
+ }