@teammates/recall 0.1.0 → 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/dist/indexer.js CHANGED
@@ -1,10 +1,10 @@
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 (MEMORIES.md + memory/*.md) into Vectra.
7
- * One index per teammate, stored at .teammates/.index/<name>/
6
+ * Indexes teammate memory files (WISDOM.md + memory/*.md) into Vectra.
7
+ * One index per teammate, stored at .teammates/<name>/.index/
8
8
  */
9
9
  export class Indexer {
10
10
  _config;
@@ -13,8 +13,9 @@ export class Indexer {
13
13
  this._config = config;
14
14
  this._embeddings = new LocalEmbeddings(config.model);
15
15
  }
16
- get indexRoot() {
17
- return path.join(this._config.teammatesDir, ".index");
16
+ /** Get the index path for a specific teammate */
17
+ indexPath(teammate) {
18
+ return path.join(this._config.teammatesDir, teammate, ".index");
18
19
  }
19
20
  /**
20
21
  * Discover all teammate directories (folders containing SOUL.md).
@@ -44,22 +45,26 @@ export class Indexer {
44
45
  async collectFiles(teammate) {
45
46
  const teammateDir = path.join(this._config.teammatesDir, teammate);
46
47
  const files = [];
47
- // MEMORIES.md
48
- const memoriesPath = path.join(teammateDir, "MEMORIES.md");
48
+ // WISDOM.md
49
+ const wisdomPath = path.join(teammateDir, "WISDOM.md");
49
50
  try {
50
- await fs.access(memoriesPath);
51
- files.push({ uri: `${teammate}/MEMORIES.md`, absolutePath: memoriesPath });
51
+ await fs.access(wisdomPath);
52
+ files.push({ uri: `${teammate}/WISDOM.md`, absolutePath: wisdomPath });
52
53
  }
53
54
  catch {
54
- // No MEMORIES.md
55
+ // No WISDOM.md
55
56
  }
56
- // memory/*.md (daily logs)
57
+ // memory/*.md — typed memories only (skip raw daily logs, they're in prompt context)
57
58
  const memoryDir = path.join(teammateDir, "memory");
58
59
  try {
59
60
  const memoryEntries = await fs.readdir(memoryDir);
60
61
  for (const entry of memoryEntries) {
61
62
  if (!entry.endsWith(".md"))
62
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;
63
68
  files.push({
64
69
  uri: `${teammate}/memory/${entry}`,
65
70
  absolutePath: path.join(memoryDir, entry),
@@ -69,6 +74,38 @@ export class Indexer {
69
74
  catch {
70
75
  // No memory/ directory
71
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
+ }
72
109
  return { teammate, files };
73
110
  }
74
111
  /**
@@ -78,7 +115,7 @@ export class Indexer {
78
115
  const { files } = await this.collectFiles(teammate);
79
116
  if (files.length === 0)
80
117
  return 0;
81
- const indexPath = path.join(this.indexRoot, teammate);
118
+ const indexPath = this.indexPath(teammate);
82
119
  const index = new LocalDocumentIndex({
83
120
  folderPath: indexPath,
84
121
  embeddings: this._embeddings,
@@ -119,7 +156,7 @@ export class Indexer {
119
156
  const text = await fs.readFile(absolutePath, "utf-8");
120
157
  if (text.trim().length === 0)
121
158
  return;
122
- const indexPath = path.join(this.indexRoot, teammate);
159
+ const indexPath = this.indexPath(teammate);
123
160
  const index = new LocalDocumentIndex({
124
161
  folderPath: indexPath,
125
162
  embeddings: this._embeddings,
@@ -137,7 +174,7 @@ export class Indexer {
137
174
  const { files } = await this.collectFiles(teammate);
138
175
  if (files.length === 0)
139
176
  return 0;
140
- const indexPath = path.join(this.indexRoot, teammate);
177
+ const indexPath = this.indexPath(teammate);
141
178
  const index = new LocalDocumentIndex({
142
179
  folderPath: indexPath,
143
180
  embeddings: this._embeddings,
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,208 @@
1
+ import { mkdir, rm, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { Indexer } from "./indexer.js";
6
+ // Stub embeddings — we don't want to load the real model in tests
7
+ class StubEmbeddings {
8
+ maxTokens = 256;
9
+ async createEmbeddings(inputs) {
10
+ const texts = Array.isArray(inputs) ? inputs : [inputs];
11
+ return {
12
+ status: "success",
13
+ output: texts.map(() => new Array(384).fill(0).map(() => Math.random())),
14
+ };
15
+ }
16
+ }
17
+ // Create an Indexer with stubbed embeddings
18
+ function createIndexer(teammatesDir) {
19
+ const indexer = new Indexer({ teammatesDir });
20
+ // Swap out the real embeddings with our stub
21
+ indexer._embeddings = new StubEmbeddings();
22
+ return indexer;
23
+ }
24
+ let testDir;
25
+ beforeEach(async () => {
26
+ testDir = join(tmpdir(), `recall-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
27
+ await mkdir(testDir, { recursive: true });
28
+ });
29
+ afterEach(async () => {
30
+ await rm(testDir, { recursive: true, force: true });
31
+ });
32
+ describe("Indexer", () => {
33
+ describe("discoverTeammates", () => {
34
+ it("finds directories containing SOUL.md", async () => {
35
+ const beacon = join(testDir, "beacon");
36
+ const scribe = join(testDir, "scribe");
37
+ const notTeammate = join(testDir, "random");
38
+ await mkdir(beacon, { recursive: true });
39
+ await mkdir(scribe, { recursive: true });
40
+ await mkdir(notTeammate, { recursive: true });
41
+ await writeFile(join(beacon, "SOUL.md"), "# Beacon");
42
+ await writeFile(join(scribe, "SOUL.md"), "# Scribe");
43
+ // notTeammate has no SOUL.md
44
+ const indexer = createIndexer(testDir);
45
+ const teammates = await indexer.discoverTeammates();
46
+ expect(teammates).toContain("beacon");
47
+ expect(teammates).toContain("scribe");
48
+ expect(teammates).not.toContain("random");
49
+ });
50
+ it("ignores dot-prefixed directories", async () => {
51
+ const hidden = join(testDir, ".tmp");
52
+ await mkdir(hidden, { recursive: true });
53
+ await writeFile(join(hidden, "SOUL.md"), "# Hidden");
54
+ const indexer = createIndexer(testDir);
55
+ const teammates = await indexer.discoverTeammates();
56
+ expect(teammates).not.toContain(".tmp");
57
+ expect(teammates).toHaveLength(0);
58
+ });
59
+ it("returns empty array when no teammates exist", async () => {
60
+ const indexer = createIndexer(testDir);
61
+ const teammates = await indexer.discoverTeammates();
62
+ expect(teammates).toEqual([]);
63
+ });
64
+ });
65
+ describe("collectFiles", () => {
66
+ it("collects WISDOM.md", async () => {
67
+ const beacon = join(testDir, "beacon");
68
+ await mkdir(beacon, { recursive: true });
69
+ await writeFile(join(beacon, "WISDOM.md"), "# Wisdom");
70
+ const indexer = createIndexer(testDir);
71
+ const { files } = await indexer.collectFiles("beacon");
72
+ expect(files).toHaveLength(1);
73
+ expect(files[0].uri).toBe("beacon/WISDOM.md");
74
+ });
75
+ it("collects typed memory files from memory/", async () => {
76
+ const memDir = join(testDir, "beacon", "memory");
77
+ await mkdir(memDir, { recursive: true });
78
+ await writeFile(join(memDir, "feedback_testing.md"), "# Feedback");
79
+ await writeFile(join(memDir, "project_goals.md"), "# Goals");
80
+ const indexer = createIndexer(testDir);
81
+ const { files } = await indexer.collectFiles("beacon");
82
+ const uris = files.map((f) => f.uri);
83
+ expect(uris).toContain("beacon/memory/feedback_testing.md");
84
+ expect(uris).toContain("beacon/memory/project_goals.md");
85
+ });
86
+ it("skips daily logs (YYYY-MM-DD.md pattern)", async () => {
87
+ const memDir = join(testDir, "beacon", "memory");
88
+ await mkdir(memDir, { recursive: true });
89
+ await writeFile(join(memDir, "2026-03-14.md"), "# Day 1");
90
+ await writeFile(join(memDir, "2026-03-15.md"), "# Day 2");
91
+ await writeFile(join(memDir, "feedback_testing.md"), "# Feedback");
92
+ const indexer = createIndexer(testDir);
93
+ const { files } = await indexer.collectFiles("beacon");
94
+ const uris = files.map((f) => f.uri);
95
+ expect(uris).not.toContain("beacon/memory/2026-03-14.md");
96
+ expect(uris).not.toContain("beacon/memory/2026-03-15.md");
97
+ expect(uris).toContain("beacon/memory/feedback_testing.md");
98
+ });
99
+ it("collects weekly summaries from memory/weekly/", async () => {
100
+ const weeklyDir = join(testDir, "beacon", "memory", "weekly");
101
+ await mkdir(weeklyDir, { recursive: true });
102
+ await writeFile(join(weeklyDir, "2026-W10.md"), "# Week 10");
103
+ await writeFile(join(weeklyDir, "2026-W11.md"), "# Week 11");
104
+ const indexer = createIndexer(testDir);
105
+ const { files } = await indexer.collectFiles("beacon");
106
+ const uris = files.map((f) => f.uri);
107
+ expect(uris).toContain("beacon/memory/weekly/2026-W10.md");
108
+ expect(uris).toContain("beacon/memory/weekly/2026-W11.md");
109
+ });
110
+ it("collects monthly summaries from memory/monthly/", async () => {
111
+ const monthlyDir = join(testDir, "beacon", "memory", "monthly");
112
+ await mkdir(monthlyDir, { recursive: true });
113
+ await writeFile(join(monthlyDir, "2025-12.md"), "# Dec 2025");
114
+ const indexer = createIndexer(testDir);
115
+ const { files } = await indexer.collectFiles("beacon");
116
+ const uris = files.map((f) => f.uri);
117
+ expect(uris).toContain("beacon/memory/monthly/2025-12.md");
118
+ });
119
+ it("skips non-md files", async () => {
120
+ const memDir = join(testDir, "beacon", "memory");
121
+ await mkdir(memDir, { recursive: true });
122
+ await writeFile(join(memDir, "notes.txt"), "not markdown");
123
+ await writeFile(join(memDir, "feedback_test.md"), "# Feedback");
124
+ const indexer = createIndexer(testDir);
125
+ const { files } = await indexer.collectFiles("beacon");
126
+ expect(files).toHaveLength(1);
127
+ expect(files[0].uri).toBe("beacon/memory/feedback_test.md");
128
+ });
129
+ it("returns empty files when teammate has no content", async () => {
130
+ await mkdir(join(testDir, "beacon"), { recursive: true });
131
+ const indexer = createIndexer(testDir);
132
+ const { files } = await indexer.collectFiles("beacon");
133
+ expect(files).toEqual([]);
134
+ });
135
+ });
136
+ describe("indexPath", () => {
137
+ it("returns correct path under teammate directory", () => {
138
+ const indexer = createIndexer(testDir);
139
+ const p = indexer.indexPath("beacon");
140
+ expect(p).toBe(join(testDir, "beacon", ".index"));
141
+ });
142
+ });
143
+ describe("indexTeammate", () => {
144
+ it("creates an index and returns file count", async () => {
145
+ const beacon = join(testDir, "beacon");
146
+ const memDir = join(beacon, "memory");
147
+ await mkdir(memDir, { recursive: true });
148
+ await writeFile(join(beacon, "WISDOM.md"), "# Wisdom content");
149
+ await writeFile(join(memDir, "feedback_test.md"), "# Feedback content");
150
+ const indexer = createIndexer(testDir);
151
+ const count = await indexer.indexTeammate("beacon");
152
+ expect(count).toBe(2);
153
+ });
154
+ it("returns 0 when no files to index", async () => {
155
+ await mkdir(join(testDir, "beacon"), { recursive: true });
156
+ const indexer = createIndexer(testDir);
157
+ const count = await indexer.indexTeammate("beacon");
158
+ expect(count).toBe(0);
159
+ });
160
+ it("skips empty files", async () => {
161
+ const beacon = join(testDir, "beacon");
162
+ await mkdir(beacon, { recursive: true });
163
+ await writeFile(join(beacon, "WISDOM.md"), " "); // whitespace only
164
+ const indexer = createIndexer(testDir);
165
+ const count = await indexer.indexTeammate("beacon");
166
+ expect(count).toBe(0);
167
+ });
168
+ });
169
+ describe("indexAll", () => {
170
+ it("indexes all discovered teammates", async () => {
171
+ const beacon = join(testDir, "beacon");
172
+ const scribe = join(testDir, "scribe");
173
+ await mkdir(beacon, { recursive: true });
174
+ await mkdir(scribe, { recursive: true });
175
+ await writeFile(join(beacon, "SOUL.md"), "# Beacon");
176
+ await writeFile(join(beacon, "WISDOM.md"), "# Beacon wisdom");
177
+ await writeFile(join(scribe, "SOUL.md"), "# Scribe");
178
+ const indexer = createIndexer(testDir);
179
+ const results = await indexer.indexAll();
180
+ expect(results.get("beacon")).toBe(1); // WISDOM.md only (SOUL.md not collected)
181
+ expect(results.get("scribe")).toBe(0); // no indexable files
182
+ });
183
+ });
184
+ describe("syncTeammate", () => {
185
+ it("falls back to full index when no index exists", async () => {
186
+ const beacon = join(testDir, "beacon");
187
+ await mkdir(beacon, { recursive: true });
188
+ await writeFile(join(beacon, "WISDOM.md"), "# Wisdom");
189
+ const indexer = createIndexer(testDir);
190
+ const count = await indexer.syncTeammate("beacon");
191
+ expect(count).toBe(1);
192
+ });
193
+ it("upserts files into existing index", async () => {
194
+ const beacon = join(testDir, "beacon");
195
+ const memDir = join(beacon, "memory");
196
+ await mkdir(memDir, { recursive: true });
197
+ await writeFile(join(beacon, "WISDOM.md"), "# Wisdom");
198
+ const indexer = createIndexer(testDir);
199
+ // First build the index
200
+ await indexer.indexTeammate("beacon");
201
+ // Add a new file
202
+ await writeFile(join(memDir, "project_goals.md"), "# Goals");
203
+ // Sync should pick up the new file
204
+ const count = await indexer.syncTeammate("beacon");
205
+ expect(count).toBe(2); // WISDOM + project_goals
206
+ });
207
+ });
208
+ });
package/dist/search.d.ts CHANGED
@@ -13,14 +13,24 @@ export interface SearchOptions {
13
13
  model?: string;
14
14
  /** Skip auto-sync before searching (default: false) */
15
15
  skipSync?: boolean;
16
+ /** Number of recent weekly summaries to always include (default: 2) */
17
+ recencyDepth?: number;
18
+ /** Relevance boost multiplier for typed memories over episodic summaries (default: 1.2) */
19
+ typedMemoryBoost?: number;
16
20
  }
17
21
  export interface SearchResult {
18
22
  teammate: string;
19
23
  uri: string;
20
24
  text: string;
21
25
  score: number;
26
+ /** Content type: "typed_memory", "weekly", "monthly", or "other" */
27
+ contentType?: string;
22
28
  }
23
29
  /**
24
- * Search teammate memories using semantic + keyword search.
30
+ * Search teammate memories using multi-pass retrieval.
31
+ *
32
+ * Pass 1 (Recency): Always returns the N most recent weekly summaries.
33
+ * Pass 2 (Semantic): Query-driven search across all indexed content.
34
+ * Results are merged, deduped, and typed memories get a relevance boost.
25
35
  */
26
36
  export declare function search(query: string, options: SearchOptions): Promise<SearchResult[]>;
package/dist/search.js CHANGED
@@ -1,20 +1,46 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
1
3
  import { LocalDocumentIndex } from "vectra";
2
4
  import { LocalEmbeddings } from "./embeddings.js";
3
5
  import { Indexer } from "./indexer.js";
4
- import * as path from "node:path";
5
- import * as fs from "node:fs/promises";
6
6
  /**
7
- * Search teammate memories using semantic + keyword search.
7
+ * Classify a URI into a content type for priority scoring.
8
+ */
9
+ function classifyUri(uri) {
10
+ if (uri.includes("/memory/weekly/"))
11
+ return "weekly";
12
+ if (uri.includes("/memory/monthly/"))
13
+ return "monthly";
14
+ // Typed memories are in memory/ but not daily logs (YYYY-MM-DD) and not in subdirs
15
+ const memoryMatch = uri.match(/\/memory\/([^/]+)\.md$/);
16
+ if (memoryMatch) {
17
+ const stem = memoryMatch[1];
18
+ if (/^\d{4}-\d{2}-\d{2}$/.test(stem))
19
+ return "daily";
20
+ return "typed_memory";
21
+ }
22
+ return "other";
23
+ }
24
+ /**
25
+ * Search teammate memories using multi-pass retrieval.
26
+ *
27
+ * Pass 1 (Recency): Always returns the N most recent weekly summaries.
28
+ * Pass 2 (Semantic): Query-driven search across all indexed content.
29
+ * Results are merged, deduped, and typed memories get a relevance boost.
8
30
  */
9
31
  export async function search(query, options) {
10
- const indexRoot = path.join(options.teammatesDir, ".index");
11
32
  const embeddings = new LocalEmbeddings(options.model);
33
+ const indexer = new Indexer({
34
+ teammatesDir: options.teammatesDir,
35
+ model: options.model,
36
+ });
12
37
  const maxResults = options.maxResults ?? 5;
13
38
  const maxChunks = options.maxChunks ?? 3;
14
39
  const maxTokens = options.maxTokens ?? 500;
40
+ const recencyDepth = options.recencyDepth ?? 2;
41
+ const typedMemoryBoost = options.typedMemoryBoost ?? 1.2;
15
42
  // Auto-sync: upsert any new/changed files before searching
16
43
  if (!options.skipSync) {
17
- const indexer = new Indexer({ teammatesDir: options.teammatesDir, model: options.model });
18
44
  if (options.teammate) {
19
45
  await indexer.syncTeammate(options.teammate);
20
46
  }
@@ -28,24 +54,47 @@ export async function search(query, options) {
28
54
  teammates = [options.teammate];
29
55
  }
30
56
  else {
57
+ teammates = await indexer.discoverTeammates();
58
+ }
59
+ const allResults = [];
60
+ const seenUris = new Set();
61
+ // ── Pass 1: Recency (recent weekly summaries, always included) ───
62
+ for (const teammate of teammates) {
63
+ const weeklyDir = path.join(options.teammatesDir, teammate, "memory", "weekly");
31
64
  try {
32
- const entries = await fs.readdir(indexRoot, { withFileTypes: true });
33
- teammates = entries
34
- .filter((e) => e.isDirectory())
35
- .map((e) => e.name);
65
+ const entries = await fs.readdir(weeklyDir);
66
+ const weeklyFiles = entries
67
+ .filter((e) => e.endsWith(".md"))
68
+ .sort()
69
+ .reverse()
70
+ .slice(0, recencyDepth);
71
+ for (const file of weeklyFiles) {
72
+ const uri = `${teammate}/memory/weekly/${file}`;
73
+ const text = await fs.readFile(path.join(weeklyDir, file), "utf-8");
74
+ if (text.trim().length === 0)
75
+ continue;
76
+ seenUris.add(uri);
77
+ allResults.push({
78
+ teammate,
79
+ uri,
80
+ text: text.slice(0, maxTokens * 4), // rough token estimate
81
+ score: 0.9, // high base score for recency results
82
+ contentType: "weekly",
83
+ });
84
+ }
36
85
  }
37
86
  catch {
38
- return [];
87
+ // No weekly/ directory for this teammate
39
88
  }
40
89
  }
41
- const allResults = [];
90
+ // ── Pass 2: Semantic (query-driven across all indexed content) ───
42
91
  for (const teammate of teammates) {
43
- const indexPath = path.join(indexRoot, teammate);
92
+ const indexPath = indexer.indexPath(teammate);
44
93
  try {
45
94
  await fs.access(indexPath);
46
95
  }
47
96
  catch {
48
- continue; // No index for this teammate
97
+ continue;
49
98
  }
50
99
  const index = new LocalDocumentIndex({
51
100
  folderPath: indexPath,
@@ -58,18 +107,28 @@ export async function search(query, options) {
58
107
  maxChunks,
59
108
  });
60
109
  for (const doc of docs) {
110
+ if (seenUris.has(doc.uri))
111
+ continue; // dedup with recency pass
112
+ seenUris.add(doc.uri);
61
113
  const sections = await doc.renderSections(maxTokens, 1);
114
+ const contentType = classifyUri(doc.uri);
62
115
  for (const section of sections) {
116
+ let score = section.score;
117
+ // Apply type-based priority boost for typed memories
118
+ if (contentType === "typed_memory") {
119
+ score *= typedMemoryBoost;
120
+ }
63
121
  allResults.push({
64
122
  teammate,
65
123
  uri: doc.uri,
66
124
  text: section.text,
67
- score: section.score,
125
+ score,
126
+ contentType,
68
127
  });
69
128
  }
70
129
  }
71
130
  }
72
131
  // Sort by score descending, return top results
73
132
  allResults.sort((a, b) => b.score - a.score);
74
- return allResults.slice(0, maxResults);
133
+ return allResults.slice(0, maxResults + recencyDepth); // allow extra slots for recency results
75
134
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from "vitest";
2
+ // classifyUri is not exported, so we test it indirectly via a re-implementation
3
+ // or we can import the module and test the search function behavior.
4
+ // Since classifyUri is a pure function, let's extract and test its logic.
5
+ describe("classifyUri", () => {
6
+ // Re-implement the classification logic for unit testing
7
+ function classifyUri(uri) {
8
+ if (uri.includes("/memory/weekly/"))
9
+ return "weekly";
10
+ if (uri.includes("/memory/monthly/"))
11
+ return "monthly";
12
+ const memoryMatch = uri.match(/\/memory\/([^/]+)\.md$/);
13
+ if (memoryMatch) {
14
+ const stem = memoryMatch[1];
15
+ if (/^\d{4}-\d{2}-\d{2}$/.test(stem))
16
+ return "daily";
17
+ return "typed_memory";
18
+ }
19
+ return "other";
20
+ }
21
+ it("classifies weekly summaries", () => {
22
+ expect(classifyUri("beacon/memory/weekly/2026-W10.md")).toBe("weekly");
23
+ });
24
+ it("classifies monthly summaries", () => {
25
+ expect(classifyUri("beacon/memory/monthly/2025-12.md")).toBe("monthly");
26
+ });
27
+ it("classifies typed memories", () => {
28
+ expect(classifyUri("beacon/memory/feedback_testing.md")).toBe("typed_memory");
29
+ expect(classifyUri("beacon/memory/project_goals.md")).toBe("typed_memory");
30
+ });
31
+ it("classifies daily logs", () => {
32
+ expect(classifyUri("beacon/memory/2026-03-14.md")).toBe("daily");
33
+ expect(classifyUri("beacon/memory/2026-01-01.md")).toBe("daily");
34
+ });
35
+ it("classifies WISDOM.md as other", () => {
36
+ expect(classifyUri("beacon/WISDOM.md")).toBe("other");
37
+ });
38
+ it("classifies non-memory paths as other", () => {
39
+ expect(classifyUri("beacon/SOUL.md")).toBe("other");
40
+ expect(classifyUri("beacon/notes/todo.md")).toBe("other");
41
+ });
42
+ });
package/package.json CHANGED
@@ -1,34 +1,40 @@
1
- {
2
- "name": "@teammates/recall",
3
- "version": "0.1.0",
4
- "description": "Local semantic memory search for teammates. Indexes MEMORIES.md and daily logs using Vectra + transformers.js.",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "bin": {
9
- "teammates-recall": "dist/cli.js"
10
- },
11
- "scripts": {
12
- "build": "tsc",
13
- "dev": "tsc --watch"
14
- },
15
- "keywords": [
16
- "teammates",
17
- "ai",
18
- "memory",
19
- "vector-search",
20
- "embeddings"
21
- ],
22
- "license": "MIT",
23
- "dependencies": {
24
- "@huggingface/transformers": "^3.0.0",
25
- "vectra": "^0.9.0"
26
- },
27
- "devDependencies": {
28
- "@types/node": "^20.0.0",
29
- "typescript": "^5.5.0"
30
- },
31
- "engines": {
32
- "node": ">=20.0.0"
33
- }
34
- }
1
+ {
2
+ "name": "@teammates/recall",
3
+ "version": "0.2.0",
4
+ "description": "Local semantic memory search for teammates. Indexes WISDOM.md and memory files using Vectra + transformers.js.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "teammates-recall": "dist/cli.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsc --watch",
14
+ "typecheck": "tsc --noEmit",
15
+ "test": "vitest run",
16
+ "test:coverage": "vitest run --coverage",
17
+ "test:watch": "vitest"
18
+ },
19
+ "keywords": [
20
+ "teammates",
21
+ "ai",
22
+ "memory",
23
+ "vector-search",
24
+ "embeddings"
25
+ ],
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "@huggingface/transformers": "^3.0.0",
29
+ "vectra": "^0.12.3"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^25.5.0",
33
+ "@vitest/coverage-v8": "^4.1.0",
34
+ "typescript": "^5.5.0",
35
+ "vitest": "^4.1.0"
36
+ },
37
+ "engines": {
38
+ "node": ">=20.0.0"
39
+ }
40
+ }