@teammates/recall 0.1.1 → 0.2.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 +7 -5
- package/dist/cli.js +123 -12
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +270 -0
- package/dist/index.d.ts +1 -1
- package/dist/indexer.d.ts +1 -1
- package/dist/indexer.js +45 -9
- package/dist/indexer.test.d.ts +1 -0
- package/dist/indexer.test.js +208 -0
- package/dist/search.d.ts +11 -1
- package/dist/search.js +75 -6
- package/dist/search.test.d.ts +1 -0
- package/dist/search.test.js +42 -0
- package/package.json +12 -6
- package/src/cli.test.ts +324 -0
- package/src/cli.ts +145 -13
- package/src/embeddings.ts +4 -2
- package/src/index.ts +1 -1
- package/src/indexer.test.ts +262 -0
- package/src/indexer.ts +43 -10
- package/src/search.test.ts +49 -0
- package/src/search.ts +87 -8
- package/vitest.config.ts +12 -0
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @teammates/recall
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Part of the [teammates](https://github.com/Stevenic/teammates) monorepo.
|
|
4
|
+
|
|
5
|
+
Local semantic memory search for teammates. Indexes `WISDOM.md` and memory files (`memory/*.md` — daily logs and typed memories) using [Vectra](https://github.com/Stevenic/vectra) for vector search and [transformers.js](https://huggingface.co/docs/transformers.js) for embeddings.
|
|
4
6
|
|
|
5
7
|
**Zero cloud dependencies.** Everything runs locally — embeddings are generated on-device, indexes are stored as local files.
|
|
6
8
|
|
|
@@ -88,9 +90,9 @@ teammates-recall status --dir ./.teammates
|
|
|
88
90
|
## How It Works
|
|
89
91
|
|
|
90
92
|
1. **Discovers** teammate directories (any folder under `.teammates/` with a `SOUL.md`)
|
|
91
|
-
2. **Collects** memory files: `
|
|
93
|
+
2. **Collects** memory files: `WISDOM.md` + `memory/*.md` (daily logs and typed memories)
|
|
92
94
|
3. **Chunks and embeds** text using transformers.js (`Xenova/all-MiniLM-L6-v2`, 384-dim vectors)
|
|
93
|
-
4. **Stores** the index at `.teammates
|
|
95
|
+
4. **Stores** the index at `.teammates/<teammate>/.index/` (gitignored)
|
|
94
96
|
5. **Searches** using Vectra's semantic similarity matching
|
|
95
97
|
|
|
96
98
|
## Auto-Sync
|
|
@@ -117,7 +119,7 @@ The `--json` flag returns structured results that agents can parse:
|
|
|
117
119
|
[
|
|
118
120
|
{
|
|
119
121
|
"teammate": "atlas",
|
|
120
|
-
"uri": "atlas/
|
|
122
|
+
"uri": "atlas/WISDOM.md",
|
|
121
123
|
"text": "### 2026-01-15: JWT Auth Pattern\n...",
|
|
122
124
|
"score": 0.847
|
|
123
125
|
}
|
|
@@ -165,4 +167,4 @@ Default: `Xenova/all-MiniLM-L6-v2` (~23 MB, 384 dimensions)
|
|
|
165
167
|
|
|
166
168
|
## Storage
|
|
167
169
|
|
|
168
|
-
Indexes live at `.teammates
|
|
170
|
+
Indexes live at `.teammates/<teammate>/.index/` and are gitignored. They're derived from the markdown source files and can be rebuilt at any time with `teammates-recall index`.
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import { watch as fsWatch } from "node:fs";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
4
5
|
import { Indexer } from "./indexer.js";
|
|
5
6
|
import { search } from "./search.js";
|
|
6
7
|
const HELP = `
|
|
@@ -12,15 +13,20 @@ Usage:
|
|
|
12
13
|
teammates-recall add <file> [options] Add a single file to a teammate's index
|
|
13
14
|
teammates-recall search <query> [options] Search teammate memories (auto-syncs)
|
|
14
15
|
teammates-recall status [options] Show index status
|
|
16
|
+
teammates-recall watch [options] Watch for changes and auto-sync
|
|
15
17
|
|
|
16
18
|
Options:
|
|
17
|
-
--dir <path>
|
|
18
|
-
--teammate <name>
|
|
19
|
-
--results <n>
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--
|
|
23
|
-
--
|
|
19
|
+
--dir <path> Path to .teammates directory (default: ./.teammates)
|
|
20
|
+
--teammate <name> Limit to a specific teammate
|
|
21
|
+
--results <n> Max results (default: 5)
|
|
22
|
+
--max-chunks <n> Max chunks per document (default: 3)
|
|
23
|
+
--max-tokens <n> Max tokens per section (default: 500)
|
|
24
|
+
--recency-depth <n> Number of recent weekly summaries to include (default: 2)
|
|
25
|
+
--typed-memory-boost <n> Relevance boost for typed memories (default: 1.2)
|
|
26
|
+
--model <name> Embedding model (default: Xenova/all-MiniLM-L6-v2)
|
|
27
|
+
--no-sync Skip auto-sync before search
|
|
28
|
+
--json Output as JSON
|
|
29
|
+
--help Show this help
|
|
24
30
|
`.trim();
|
|
25
31
|
function parseArgs(argv) {
|
|
26
32
|
const args = {
|
|
@@ -34,17 +40,24 @@ function parseArgs(argv) {
|
|
|
34
40
|
};
|
|
35
41
|
let i = 0;
|
|
36
42
|
// Skip node and script path
|
|
37
|
-
while (i < argv.length &&
|
|
43
|
+
while (i < argv.length &&
|
|
44
|
+
(argv[i].includes("node") ||
|
|
45
|
+
argv[i].includes("teammates-recall") ||
|
|
46
|
+
argv[i].endsWith(".js"))) {
|
|
38
47
|
i++;
|
|
39
48
|
}
|
|
40
49
|
if (i < argv.length && !argv[i].startsWith("-")) {
|
|
41
50
|
args.command = argv[i++];
|
|
42
51
|
}
|
|
43
52
|
// For search, next non-flag arg is the query; for add, it's the file path
|
|
44
|
-
if (args.command === "search" &&
|
|
53
|
+
if (args.command === "search" &&
|
|
54
|
+
i < argv.length &&
|
|
55
|
+
!argv[i].startsWith("-")) {
|
|
45
56
|
args.query = argv[i++];
|
|
46
57
|
}
|
|
47
|
-
else if (args.command === "add" &&
|
|
58
|
+
else if (args.command === "add" &&
|
|
59
|
+
i < argv.length &&
|
|
60
|
+
!argv[i].startsWith("-")) {
|
|
48
61
|
args.file = argv[i++];
|
|
49
62
|
}
|
|
50
63
|
while (i < argv.length) {
|
|
@@ -62,6 +75,18 @@ function parseArgs(argv) {
|
|
|
62
75
|
case "--model":
|
|
63
76
|
args.model = argv[i++];
|
|
64
77
|
break;
|
|
78
|
+
case "--max-chunks":
|
|
79
|
+
args.maxChunks = parseInt(argv[i++], 10);
|
|
80
|
+
break;
|
|
81
|
+
case "--max-tokens":
|
|
82
|
+
args.maxTokens = parseInt(argv[i++], 10);
|
|
83
|
+
break;
|
|
84
|
+
case "--recency-depth":
|
|
85
|
+
args.recencyDepth = parseInt(argv[i++], 10);
|
|
86
|
+
break;
|
|
87
|
+
case "--typed-memory-boost":
|
|
88
|
+
args.typedMemoryBoost = parseFloat(argv[i++]);
|
|
89
|
+
break;
|
|
65
90
|
case "--no-sync":
|
|
66
91
|
args.sync = false;
|
|
67
92
|
break;
|
|
@@ -157,7 +182,11 @@ async function cmdAdd(args) {
|
|
|
157
182
|
const indexer = new Indexer({ teammatesDir, model: args.model });
|
|
158
183
|
await indexer.upsertFile(args.teammate, args.file);
|
|
159
184
|
if (args.json) {
|
|
160
|
-
console.log(JSON.stringify({
|
|
185
|
+
console.log(JSON.stringify({
|
|
186
|
+
teammate: args.teammate,
|
|
187
|
+
file: args.file,
|
|
188
|
+
status: "ok",
|
|
189
|
+
}));
|
|
161
190
|
}
|
|
162
191
|
else {
|
|
163
192
|
console.log(`Added ${args.file} to ${args.teammate}'s index`);
|
|
@@ -174,6 +203,10 @@ async function cmdSearch(args) {
|
|
|
174
203
|
teammatesDir,
|
|
175
204
|
teammate: args.teammate,
|
|
176
205
|
maxResults: args.results,
|
|
206
|
+
maxChunks: args.maxChunks,
|
|
207
|
+
maxTokens: args.maxTokens,
|
|
208
|
+
recencyDepth: args.recencyDepth,
|
|
209
|
+
typedMemoryBoost: args.typedMemoryBoost,
|
|
177
210
|
model: args.model,
|
|
178
211
|
skipSync: !args.sync,
|
|
179
212
|
});
|
|
@@ -220,6 +253,81 @@ async function cmdStatus(args) {
|
|
|
220
253
|
}
|
|
221
254
|
}
|
|
222
255
|
}
|
|
256
|
+
async function cmdWatch(args) {
|
|
257
|
+
const teammatesDir = await resolveTeammatesDir(args.dir);
|
|
258
|
+
const indexer = new Indexer({ teammatesDir, model: args.model });
|
|
259
|
+
// Initial sync
|
|
260
|
+
console.error("Initial sync...");
|
|
261
|
+
const results = await indexer.syncAll();
|
|
262
|
+
for (const [teammate, count] of results) {
|
|
263
|
+
console.error(` ${teammate}: ${count} files`);
|
|
264
|
+
}
|
|
265
|
+
console.error("Watching for changes...");
|
|
266
|
+
if (args.json) {
|
|
267
|
+
console.log(JSON.stringify({ status: "watching", dir: teammatesDir }));
|
|
268
|
+
}
|
|
269
|
+
// Debounce: collect changes, sync after 2s of quiet
|
|
270
|
+
let syncTimer = null;
|
|
271
|
+
const pendingTeammates = new Set();
|
|
272
|
+
const scheduleSync = (teammate) => {
|
|
273
|
+
pendingTeammates.add(teammate);
|
|
274
|
+
if (syncTimer)
|
|
275
|
+
clearTimeout(syncTimer);
|
|
276
|
+
syncTimer = setTimeout(async () => {
|
|
277
|
+
for (const t of pendingTeammates) {
|
|
278
|
+
try {
|
|
279
|
+
const count = await indexer.syncTeammate(t);
|
|
280
|
+
if (args.json) {
|
|
281
|
+
console.log(JSON.stringify({ event: "sync", teammate: t, files: count }));
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
console.error(` synced ${t}: ${count} files`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
289
|
+
console.error(` error syncing ${t}: ${msg}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
pendingTeammates.clear();
|
|
293
|
+
}, 2000);
|
|
294
|
+
};
|
|
295
|
+
// Watch each teammate's directory for changes
|
|
296
|
+
const watchers = [];
|
|
297
|
+
const teammates = await indexer.discoverTeammates();
|
|
298
|
+
for (const teammate of teammates) {
|
|
299
|
+
const teammateDir = path.join(teammatesDir, teammate);
|
|
300
|
+
try {
|
|
301
|
+
const watcher = fsWatch(teammateDir, { recursive: true }, (_eventType, filename) => {
|
|
302
|
+
if (!filename)
|
|
303
|
+
return;
|
|
304
|
+
// Only care about .md files, skip .index/
|
|
305
|
+
if (!filename.endsWith(".md") || filename.includes(".index"))
|
|
306
|
+
return;
|
|
307
|
+
scheduleSync(teammate);
|
|
308
|
+
});
|
|
309
|
+
watchers.push(watcher);
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
console.error(` warning: could not watch ${teammate}/`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Keep alive until killed
|
|
316
|
+
const shutdown = () => {
|
|
317
|
+
if (syncTimer)
|
|
318
|
+
clearTimeout(syncTimer);
|
|
319
|
+
for (const w of watchers)
|
|
320
|
+
w.close();
|
|
321
|
+
if (args.json) {
|
|
322
|
+
console.log(JSON.stringify({ status: "stopped" }));
|
|
323
|
+
}
|
|
324
|
+
process.exit(0);
|
|
325
|
+
};
|
|
326
|
+
process.on("SIGTERM", shutdown);
|
|
327
|
+
process.on("SIGINT", shutdown);
|
|
328
|
+
// Block forever
|
|
329
|
+
await new Promise(() => { });
|
|
330
|
+
}
|
|
223
331
|
async function main() {
|
|
224
332
|
const args = parseArgs(process.argv);
|
|
225
333
|
switch (args.command) {
|
|
@@ -238,6 +346,9 @@ async function main() {
|
|
|
238
346
|
case "status":
|
|
239
347
|
await cmdStatus(args);
|
|
240
348
|
break;
|
|
349
|
+
case "watch":
|
|
350
|
+
await cmdWatch(args);
|
|
351
|
+
break;
|
|
241
352
|
default:
|
|
242
353
|
console.log(HELP);
|
|
243
354
|
process.exit(args.command ? 1 : 0);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.test.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
function parseArgs(argv) {
|
|
3
|
+
const args = {
|
|
4
|
+
command: "",
|
|
5
|
+
query: "",
|
|
6
|
+
file: "",
|
|
7
|
+
dir: "./.teammates",
|
|
8
|
+
results: 5,
|
|
9
|
+
json: false,
|
|
10
|
+
sync: true,
|
|
11
|
+
};
|
|
12
|
+
let i = 0;
|
|
13
|
+
while (i < argv.length &&
|
|
14
|
+
(argv[i].includes("node") ||
|
|
15
|
+
argv[i].includes("teammates-recall") ||
|
|
16
|
+
argv[i].endsWith(".js"))) {
|
|
17
|
+
i++;
|
|
18
|
+
}
|
|
19
|
+
if (i < argv.length && !argv[i].startsWith("-")) {
|
|
20
|
+
args.command = argv[i++];
|
|
21
|
+
}
|
|
22
|
+
if (args.command === "search" &&
|
|
23
|
+
i < argv.length &&
|
|
24
|
+
!argv[i].startsWith("-")) {
|
|
25
|
+
args.query = argv[i++];
|
|
26
|
+
}
|
|
27
|
+
else if (args.command === "add" &&
|
|
28
|
+
i < argv.length &&
|
|
29
|
+
!argv[i].startsWith("-")) {
|
|
30
|
+
args.file = argv[i++];
|
|
31
|
+
}
|
|
32
|
+
while (i < argv.length) {
|
|
33
|
+
const arg = argv[i++];
|
|
34
|
+
switch (arg) {
|
|
35
|
+
case "--dir":
|
|
36
|
+
args.dir = argv[i++];
|
|
37
|
+
break;
|
|
38
|
+
case "--teammate":
|
|
39
|
+
args.teammate = argv[i++];
|
|
40
|
+
break;
|
|
41
|
+
case "--results":
|
|
42
|
+
args.results = parseInt(argv[i++], 10);
|
|
43
|
+
break;
|
|
44
|
+
case "--model":
|
|
45
|
+
args.model = argv[i++];
|
|
46
|
+
break;
|
|
47
|
+
case "--max-chunks":
|
|
48
|
+
args.maxChunks = parseInt(argv[i++], 10);
|
|
49
|
+
break;
|
|
50
|
+
case "--max-tokens":
|
|
51
|
+
args.maxTokens = parseInt(argv[i++], 10);
|
|
52
|
+
break;
|
|
53
|
+
case "--recency-depth":
|
|
54
|
+
args.recencyDepth = parseInt(argv[i++], 10);
|
|
55
|
+
break;
|
|
56
|
+
case "--typed-memory-boost":
|
|
57
|
+
args.typedMemoryBoost = parseFloat(argv[i++]);
|
|
58
|
+
break;
|
|
59
|
+
case "--no-sync":
|
|
60
|
+
args.sync = false;
|
|
61
|
+
break;
|
|
62
|
+
case "--json":
|
|
63
|
+
args.json = true;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return args;
|
|
68
|
+
}
|
|
69
|
+
describe("parseArgs", () => {
|
|
70
|
+
it("parses search command with query", () => {
|
|
71
|
+
const args = parseArgs(["node", "cli.js", "search", "hello world"]);
|
|
72
|
+
expect(args.command).toBe("search");
|
|
73
|
+
expect(args.query).toBe("hello world");
|
|
74
|
+
});
|
|
75
|
+
it("parses add command with file path", () => {
|
|
76
|
+
const args = parseArgs([
|
|
77
|
+
"node",
|
|
78
|
+
"cli.js",
|
|
79
|
+
"add",
|
|
80
|
+
"memory/foo.md",
|
|
81
|
+
"--teammate",
|
|
82
|
+
"beacon",
|
|
83
|
+
]);
|
|
84
|
+
expect(args.command).toBe("add");
|
|
85
|
+
expect(args.file).toBe("memory/foo.md");
|
|
86
|
+
expect(args.teammate).toBe("beacon");
|
|
87
|
+
});
|
|
88
|
+
it("parses index command", () => {
|
|
89
|
+
const args = parseArgs(["node", "cli.js", "index"]);
|
|
90
|
+
expect(args.command).toBe("index");
|
|
91
|
+
});
|
|
92
|
+
it("parses sync command", () => {
|
|
93
|
+
const args = parseArgs(["node", "cli.js", "sync"]);
|
|
94
|
+
expect(args.command).toBe("sync");
|
|
95
|
+
});
|
|
96
|
+
it("parses status command", () => {
|
|
97
|
+
const args = parseArgs(["node", "cli.js", "status"]);
|
|
98
|
+
expect(args.command).toBe("status");
|
|
99
|
+
});
|
|
100
|
+
it("parses watch command", () => {
|
|
101
|
+
const args = parseArgs(["node", "cli.js", "watch"]);
|
|
102
|
+
expect(args.command).toBe("watch");
|
|
103
|
+
});
|
|
104
|
+
it("defaults dir to ./.teammates", () => {
|
|
105
|
+
const args = parseArgs(["node", "cli.js", "index"]);
|
|
106
|
+
expect(args.dir).toBe("./.teammates");
|
|
107
|
+
});
|
|
108
|
+
it("parses --dir flag", () => {
|
|
109
|
+
const args = parseArgs([
|
|
110
|
+
"node",
|
|
111
|
+
"cli.js",
|
|
112
|
+
"index",
|
|
113
|
+
"--dir",
|
|
114
|
+
"/path/to/.teammates",
|
|
115
|
+
]);
|
|
116
|
+
expect(args.dir).toBe("/path/to/.teammates");
|
|
117
|
+
});
|
|
118
|
+
it("parses --teammate flag", () => {
|
|
119
|
+
const args = parseArgs([
|
|
120
|
+
"node",
|
|
121
|
+
"cli.js",
|
|
122
|
+
"search",
|
|
123
|
+
"query",
|
|
124
|
+
"--teammate",
|
|
125
|
+
"scribe",
|
|
126
|
+
]);
|
|
127
|
+
expect(args.teammate).toBe("scribe");
|
|
128
|
+
});
|
|
129
|
+
it("parses --results flag", () => {
|
|
130
|
+
const args = parseArgs([
|
|
131
|
+
"node",
|
|
132
|
+
"cli.js",
|
|
133
|
+
"search",
|
|
134
|
+
"query",
|
|
135
|
+
"--results",
|
|
136
|
+
"10",
|
|
137
|
+
]);
|
|
138
|
+
expect(args.results).toBe(10);
|
|
139
|
+
});
|
|
140
|
+
it("parses --model flag", () => {
|
|
141
|
+
const args = parseArgs([
|
|
142
|
+
"node",
|
|
143
|
+
"cli.js",
|
|
144
|
+
"index",
|
|
145
|
+
"--model",
|
|
146
|
+
"custom/model",
|
|
147
|
+
]);
|
|
148
|
+
expect(args.model).toBe("custom/model");
|
|
149
|
+
});
|
|
150
|
+
it("parses --json flag", () => {
|
|
151
|
+
const args = parseArgs(["node", "cli.js", "status", "--json"]);
|
|
152
|
+
expect(args.json).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
it("defaults json to false", () => {
|
|
155
|
+
const args = parseArgs(["node", "cli.js", "status"]);
|
|
156
|
+
expect(args.json).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
it("parses --no-sync flag", () => {
|
|
159
|
+
const args = parseArgs(["node", "cli.js", "search", "query", "--no-sync"]);
|
|
160
|
+
expect(args.sync).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
it("defaults sync to true", () => {
|
|
163
|
+
const args = parseArgs(["node", "cli.js", "search", "query"]);
|
|
164
|
+
expect(args.sync).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
it("defaults results to 5", () => {
|
|
167
|
+
const args = parseArgs(["node", "cli.js", "search", "query"]);
|
|
168
|
+
expect(args.results).toBe(5);
|
|
169
|
+
});
|
|
170
|
+
it("returns empty command for no args", () => {
|
|
171
|
+
const args = parseArgs(["node", "cli.js"]);
|
|
172
|
+
expect(args.command).toBe("");
|
|
173
|
+
});
|
|
174
|
+
it("handles multiple flags together", () => {
|
|
175
|
+
const args = parseArgs([
|
|
176
|
+
"node",
|
|
177
|
+
"cli.js",
|
|
178
|
+
"search",
|
|
179
|
+
"my query",
|
|
180
|
+
"--dir",
|
|
181
|
+
"/tmp/.teammates",
|
|
182
|
+
"--teammate",
|
|
183
|
+
"beacon",
|
|
184
|
+
"--results",
|
|
185
|
+
"3",
|
|
186
|
+
"--json",
|
|
187
|
+
"--no-sync",
|
|
188
|
+
]);
|
|
189
|
+
expect(args.command).toBe("search");
|
|
190
|
+
expect(args.query).toBe("my query");
|
|
191
|
+
expect(args.dir).toBe("/tmp/.teammates");
|
|
192
|
+
expect(args.teammate).toBe("beacon");
|
|
193
|
+
expect(args.results).toBe(3);
|
|
194
|
+
expect(args.json).toBe(true);
|
|
195
|
+
expect(args.sync).toBe(false);
|
|
196
|
+
});
|
|
197
|
+
it("parses --max-chunks flag", () => {
|
|
198
|
+
const args = parseArgs([
|
|
199
|
+
"node",
|
|
200
|
+
"cli.js",
|
|
201
|
+
"search",
|
|
202
|
+
"query",
|
|
203
|
+
"--max-chunks",
|
|
204
|
+
"7",
|
|
205
|
+
]);
|
|
206
|
+
expect(args.maxChunks).toBe(7);
|
|
207
|
+
});
|
|
208
|
+
it("parses --max-tokens flag", () => {
|
|
209
|
+
const args = parseArgs([
|
|
210
|
+
"node",
|
|
211
|
+
"cli.js",
|
|
212
|
+
"search",
|
|
213
|
+
"query",
|
|
214
|
+
"--max-tokens",
|
|
215
|
+
"1000",
|
|
216
|
+
]);
|
|
217
|
+
expect(args.maxTokens).toBe(1000);
|
|
218
|
+
});
|
|
219
|
+
it("parses --recency-depth flag", () => {
|
|
220
|
+
const args = parseArgs([
|
|
221
|
+
"node",
|
|
222
|
+
"cli.js",
|
|
223
|
+
"search",
|
|
224
|
+
"query",
|
|
225
|
+
"--recency-depth",
|
|
226
|
+
"4",
|
|
227
|
+
]);
|
|
228
|
+
expect(args.recencyDepth).toBe(4);
|
|
229
|
+
});
|
|
230
|
+
it("parses --typed-memory-boost flag", () => {
|
|
231
|
+
const args = parseArgs([
|
|
232
|
+
"node",
|
|
233
|
+
"cli.js",
|
|
234
|
+
"search",
|
|
235
|
+
"query",
|
|
236
|
+
"--typed-memory-boost",
|
|
237
|
+
"1.5",
|
|
238
|
+
]);
|
|
239
|
+
expect(args.typedMemoryBoost).toBe(1.5);
|
|
240
|
+
});
|
|
241
|
+
it("handles multiple new search flags together", () => {
|
|
242
|
+
const args = parseArgs([
|
|
243
|
+
"node",
|
|
244
|
+
"cli.js",
|
|
245
|
+
"search",
|
|
246
|
+
"my query",
|
|
247
|
+
"--max-chunks",
|
|
248
|
+
"5",
|
|
249
|
+
"--max-tokens",
|
|
250
|
+
"800",
|
|
251
|
+
"--recency-depth",
|
|
252
|
+
"3",
|
|
253
|
+
"--typed-memory-boost",
|
|
254
|
+
"2.0",
|
|
255
|
+
]);
|
|
256
|
+
expect(args.command).toBe("search");
|
|
257
|
+
expect(args.query).toBe("my query");
|
|
258
|
+
expect(args.maxChunks).toBe(5);
|
|
259
|
+
expect(args.maxTokens).toBe(800);
|
|
260
|
+
expect(args.recencyDepth).toBe(3);
|
|
261
|
+
expect(args.typedMemoryBoost).toBe(2.0);
|
|
262
|
+
});
|
|
263
|
+
it("leaves new flags undefined when not provided", () => {
|
|
264
|
+
const args = parseArgs(["node", "cli.js", "search", "query"]);
|
|
265
|
+
expect(args.maxChunks).toBeUndefined();
|
|
266
|
+
expect(args.maxTokens).toBeUndefined();
|
|
267
|
+
expect(args.recencyDepth).toBeUndefined();
|
|
268
|
+
expect(args.typedMemoryBoost).toBeUndefined();
|
|
269
|
+
});
|
|
270
|
+
});
|
package/dist/index.d.ts
CHANGED
package/dist/indexer.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ interface TeammateFiles {
|
|
|
12
12
|
}[];
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Indexes teammate memory files (
|
|
15
|
+
* Indexes teammate memory files (WISDOM.md + memory/*.md) into Vectra.
|
|
16
16
|
* One index per teammate, stored at .teammates/<name>/.index/
|
|
17
17
|
*/
|
|
18
18
|
export declare class Indexer {
|
package/dist/indexer.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { LocalDocumentIndex } from "vectra";
|
|
2
|
-
import { LocalEmbeddings } from "./embeddings.js";
|
|
3
1
|
import * as fs from "node:fs/promises";
|
|
4
2
|
import * as path from "node:path";
|
|
3
|
+
import { LocalDocumentIndex } from "vectra";
|
|
4
|
+
import { LocalEmbeddings } from "./embeddings.js";
|
|
5
5
|
/**
|
|
6
|
-
* Indexes teammate memory files (
|
|
6
|
+
* Indexes teammate memory files (WISDOM.md + memory/*.md) into Vectra.
|
|
7
7
|
* One index per teammate, stored at .teammates/<name>/.index/
|
|
8
8
|
*/
|
|
9
9
|
export class Indexer {
|
|
@@ -45,22 +45,26 @@ export class Indexer {
|
|
|
45
45
|
async collectFiles(teammate) {
|
|
46
46
|
const teammateDir = path.join(this._config.teammatesDir, teammate);
|
|
47
47
|
const files = [];
|
|
48
|
-
//
|
|
49
|
-
const
|
|
48
|
+
// WISDOM.md
|
|
49
|
+
const wisdomPath = path.join(teammateDir, "WISDOM.md");
|
|
50
50
|
try {
|
|
51
|
-
await fs.access(
|
|
52
|
-
files.push({ uri: `${teammate}/
|
|
51
|
+
await fs.access(wisdomPath);
|
|
52
|
+
files.push({ uri: `${teammate}/WISDOM.md`, absolutePath: wisdomPath });
|
|
53
53
|
}
|
|
54
54
|
catch {
|
|
55
|
-
// No
|
|
55
|
+
// No WISDOM.md
|
|
56
56
|
}
|
|
57
|
-
// memory/*.md (daily logs)
|
|
57
|
+
// memory/*.md — typed memories only (skip raw daily logs, they're in prompt context)
|
|
58
58
|
const memoryDir = path.join(teammateDir, "memory");
|
|
59
59
|
try {
|
|
60
60
|
const memoryEntries = await fs.readdir(memoryDir);
|
|
61
61
|
for (const entry of memoryEntries) {
|
|
62
62
|
if (!entry.endsWith(".md"))
|
|
63
63
|
continue;
|
|
64
|
+
const stem = path.basename(entry, ".md");
|
|
65
|
+
// Skip daily logs (YYYY-MM-DD) — they're already in prompt context
|
|
66
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(stem))
|
|
67
|
+
continue;
|
|
64
68
|
files.push({
|
|
65
69
|
uri: `${teammate}/memory/${entry}`,
|
|
66
70
|
absolutePath: path.join(memoryDir, entry),
|
|
@@ -70,6 +74,38 @@ export class Indexer {
|
|
|
70
74
|
catch {
|
|
71
75
|
// No memory/ directory
|
|
72
76
|
}
|
|
77
|
+
// memory/weekly/*.md — weekly summaries (primary episodic search surface)
|
|
78
|
+
const weeklyDir = path.join(memoryDir, "weekly");
|
|
79
|
+
try {
|
|
80
|
+
const weeklyEntries = await fs.readdir(weeklyDir);
|
|
81
|
+
for (const entry of weeklyEntries) {
|
|
82
|
+
if (!entry.endsWith(".md"))
|
|
83
|
+
continue;
|
|
84
|
+
files.push({
|
|
85
|
+
uri: `${teammate}/memory/weekly/${entry}`,
|
|
86
|
+
absolutePath: path.join(weeklyDir, entry),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// No weekly/ directory
|
|
92
|
+
}
|
|
93
|
+
// memory/monthly/*.md — monthly summaries (long-term episodic context)
|
|
94
|
+
const monthlyDir = path.join(memoryDir, "monthly");
|
|
95
|
+
try {
|
|
96
|
+
const monthlyEntries = await fs.readdir(monthlyDir);
|
|
97
|
+
for (const entry of monthlyEntries) {
|
|
98
|
+
if (!entry.endsWith(".md"))
|
|
99
|
+
continue;
|
|
100
|
+
files.push({
|
|
101
|
+
uri: `${teammate}/memory/monthly/${entry}`,
|
|
102
|
+
absolutePath: path.join(monthlyDir, entry),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// No monthly/ directory
|
|
108
|
+
}
|
|
73
109
|
return { teammate, files };
|
|
74
110
|
}
|
|
75
111
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|