baro-ai 0.51.1 → 0.51.3
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/baro-memory.mjs +43 -1
- package/dist/baro-memory.mjs.map +1 -1
- package/dist/cli.mjs +43 -1
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/baro-memory.mjs
CHANGED
|
@@ -4,7 +4,7 @@ const require = __baroCreateRequire(import.meta.url);
|
|
|
4
4
|
|
|
5
5
|
// ../baro-memory/dist/vectra-store.js
|
|
6
6
|
import { LocalIndex } from "vectra";
|
|
7
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, renameSync, rmSync, readdirSync, lstatSync } from "fs";
|
|
7
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, renameSync, rmSync, readdirSync, statSync, lstatSync } from "fs";
|
|
8
8
|
import { join } from "path";
|
|
9
9
|
import { tmpdir, homedir } from "os";
|
|
10
10
|
var MAX_CONTENT_CHARS = 4e3;
|
|
@@ -73,9 +73,15 @@ async function embed(extractor, text) {
|
|
|
73
73
|
return Array.from(output.data);
|
|
74
74
|
}
|
|
75
75
|
var VectraMemoryStore = class {
|
|
76
|
+
// Not readonly: the reader re-instantiates the index to pick up writes
|
|
77
|
+
// made by other processes (see refreshIndexIfChanged).
|
|
76
78
|
index;
|
|
77
79
|
extractor;
|
|
78
80
|
sessionPath;
|
|
81
|
+
indexPath;
|
|
82
|
+
indexFilePath;
|
|
83
|
+
/** mtimeMs of index.json last time we (re)loaded; -1 = never seen. */
|
|
84
|
+
lastIndexMtimeMs = -1;
|
|
79
85
|
cachePath;
|
|
80
86
|
lockPath;
|
|
81
87
|
config;
|
|
@@ -83,10 +89,44 @@ var VectraMemoryStore = class {
|
|
|
83
89
|
this.index = index;
|
|
84
90
|
this.extractor = extractor;
|
|
85
91
|
this.sessionPath = sessionPath;
|
|
92
|
+
this.indexPath = join(sessionPath, "index");
|
|
93
|
+
this.indexFilePath = join(this.indexPath, "index.json");
|
|
86
94
|
this.cachePath = join(sessionPath, "cache.json");
|
|
87
95
|
this.lockPath = join(sessionPath, "cache.lock");
|
|
88
96
|
this.config = config;
|
|
89
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Pick up cross-process writes to the shared on-disk index.
|
|
100
|
+
*
|
|
101
|
+
* Vectra's LocalIndex loads the whole index into a private in-memory
|
|
102
|
+
* `_data` field on first query and never reloads it. Findings written by
|
|
103
|
+
* story agents in SEPARATE processes (via the baro-memory CLI) land on
|
|
104
|
+
* disk but stay invisible to this long-lived reader — so recall/getStats
|
|
105
|
+
* see a frozen (usually empty) snapshot and always return 0 (issue #51).
|
|
106
|
+
*
|
|
107
|
+
* There is no public reload on LocalIndex, so we detect a change via the
|
|
108
|
+
* index file's mtime and swap in a fresh LocalIndex, which lazily loads
|
|
109
|
+
* the current on-disk data on its next query. Throttled by mtime so we
|
|
110
|
+
* don't re-read every call — cheap relative to an LLM turn. Read paths
|
|
111
|
+
* only; writers (remember/upsertItem) already refresh Vectra's `_data`
|
|
112
|
+
* via beginUpdate, and reloading mid-write could clobber a pending batch.
|
|
113
|
+
*
|
|
114
|
+
* Never throws: on any stat/instantiation error we keep the current
|
|
115
|
+
* index and degrade gracefully.
|
|
116
|
+
*/
|
|
117
|
+
refreshIndexIfChanged() {
|
|
118
|
+
try {
|
|
119
|
+
if (!existsSync(this.indexFilePath))
|
|
120
|
+
return;
|
|
121
|
+
const mtimeMs = statSync(this.indexFilePath).mtimeMs;
|
|
122
|
+
if (mtimeMs === this.lastIndexMtimeMs)
|
|
123
|
+
return;
|
|
124
|
+
const newIndex = new LocalIndex(this.indexPath);
|
|
125
|
+
this.lastIndexMtimeMs = mtimeMs;
|
|
126
|
+
this.index = newIndex;
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
}
|
|
90
130
|
// ── Semantic memory ──────────────────────────────────────────
|
|
91
131
|
async remember(finding) {
|
|
92
132
|
try {
|
|
@@ -118,6 +158,7 @@ var VectraMemoryStore = class {
|
|
|
118
158
|
const filterByTool = options?.filterByTool;
|
|
119
159
|
if (!query?.trim())
|
|
120
160
|
return [];
|
|
161
|
+
this.refreshIndexIfChanged();
|
|
121
162
|
const vector = await embed(this.extractor, query);
|
|
122
163
|
const fetchK = Math.min(Math.max(maxResults * 3, 30), 200);
|
|
123
164
|
const results = await this.index.queryItems(vector, fetchK);
|
|
@@ -214,6 +255,7 @@ ${result.content}
|
|
|
214
255
|
// ── Stats ────────────────────────────────────────────────────
|
|
215
256
|
async getStats() {
|
|
216
257
|
try {
|
|
258
|
+
this.refreshIndexIfChanged();
|
|
217
259
|
const items = await this.index.listItems();
|
|
218
260
|
const tools = /* @__PURE__ */ new Set();
|
|
219
261
|
const agents = /* @__PURE__ */ new Set();
|
package/dist/baro-memory.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../baro-memory/src/vectra-store.ts","../../baro-orchestrator/scripts/baro-memory.ts"],"sourcesContent":["/**\n * Vectra-backed semantic memory store for baro agents.\n *\n * Architecture:\n * - Vectra LocalIndex for vector storage + similarity search (persisted to disk)\n * - @xenova/transformers ONNX model for embedding generation (CPU-only)\n * - Separate cache.json for file content dedup (not vectorized)\n *\n * Cross-process sharing: Vectra reads/writes index.json on every operation,\n * so the orchestrator's writes are immediately visible to CLI invocations.\n *\n * ID strategy: IDs are deterministic (agent:tool:file/pattern/command).\n * This means repeated reads of the same file by the same agent upsert\n * (update in place) rather than accumulate duplicate entries.\n */\n\nimport { LocalIndex } from 'vectra'\nimport type { QueryResult } from 'vectra'\nimport { readFileSync, writeFileSync, mkdirSync, existsSync, renameSync, rmSync, readdirSync, statSync, lstatSync } from 'fs'\nimport { join } from 'path'\nimport { tmpdir, homedir } from 'os'\n\nimport type {\n CachedFile,\n Finding,\n FindingMetadata,\n MemoryStore,\n MemoryStats,\n MemoryStoreConfig,\n RecalledFinding,\n RecallOptions,\n} from './types.js'\n\n// ── Constants ────────────────────────────────────────────────────────────\n\n/** Maximum characters stored per finding content. */\nconst MAX_CONTENT_CHARS = 4000\n\n/** Maximum bytes for a single cached file (5MB). */\nconst MAX_CACHE_FILE_BYTES = 5 * 1024 * 1024\n\n/** Maximum total cache size (50MB). Beyond this, oldest entries are evicted. */\nconst MAX_TOTAL_CACHE_BYTES = 50 * 1024 * 1024\n\n/** Stale session threshold: 24 hours. */\nconst SESSION_TTL_MS = 24 * 60 * 60 * 1000\n\n/** Allowed parent directories for session paths (prevent path traversal). */\nconst ALLOWED_SESSION_PARENTS = ['.baro', 'baro-memory', 'tmp']\n\n// ── Defaults ─────────────────────────────────────────────────────────────\n\nconst DEFAULTS = {\n embeddingModel: 'Xenova/all-MiniLM-L6-v2',\n defaultMinSimilarity: 0.3,\n defaultMaxResults: 10,\n disabled: false,\n sessionPath: '',\n} satisfies Required<MemoryStoreConfig>\n\n// ── Types ────────────────────────────────────────────────────────────────\n\n/** Typed embedding pipeline from @xenova/transformers. */\ntype EmbeddingPipeline = (\n text: string,\n opts: { pooling: string; normalize: boolean },\n) => Promise<{ data: ArrayLike<number> }>\n\n/** Metadata stored alongside each vector in Vectra. */\ninterface VectraItemMetadata {\n [key: string]: string // Index signature for Vectra compatibility\n tool: string\n agentId: string\n storyId: string\n filePath: string\n pattern: string\n command: string\n tags: string\n /** The original text content (stored for retrieval). */\n content: string\n}\n\n// ── Factory ──────────────────────────────────────────────────────────────\n\n/**\n * Create a memory store backed by Vectra (local vector DB).\n *\n * - If `sessionPath` is set, the index + cache persist there.\n * - If not set, a temp directory is used (ephemeral, single-process).\n * - If `disabled`, returns a no-op store.\n *\n * @param config - Optional configuration\n * @returns MemoryStore instance\n */\nexport async function createMemoryStore(\n config?: MemoryStoreConfig,\n): Promise<MemoryStore> {\n const cfg = { ...DEFAULTS, ...config }\n\n // Validate config\n if (cfg.defaultMinSimilarity < 0 || cfg.defaultMinSimilarity > 1) {\n cfg.defaultMinSimilarity = DEFAULTS.defaultMinSimilarity\n }\n if (cfg.defaultMaxResults < 1) {\n cfg.defaultMaxResults = DEFAULTS.defaultMaxResults\n }\n\n if (cfg.disabled) {\n return new NoOpMemoryStore()\n }\n\n // Resolve and validate session path (prevent path traversal)\n const sessionPath = cfg.sessionPath || join(tmpdir(), `baro-memory-${process.pid}-${Date.now()}`)\n validateSessionPath(sessionPath)\n mkdirSync(sessionPath, { recursive: true })\n\n // Initialize Vectra index\n const indexPath = join(sessionPath, 'index')\n mkdirSync(indexPath, { recursive: true })\n const index = new LocalIndex<VectraItemMetadata>(indexPath)\n\n if (!(await index.isIndexCreated())) {\n await index.createIndex({ version: 1 })\n }\n\n // Load ONNX embedding model (cached after first load by transformers.js).\n // Pin the cache to a writable, persistent baro-owned dir: the default is\n // `node_modules/@xenova/transformers/.cache`, which on a global install\n // (e.g. /usr/local/lib) can be read-only — the model would fail to\n // download. ~/.baro/models is user-writable and survives across runs so\n // MiniLM is fetched only once. Override with TRANSFORMERS_CACHE if set.\n const transformers = await import('@xenova/transformers')\n transformers.env.cacheDir = process.env.TRANSFORMERS_CACHE || join(homedir(), '.baro', 'models')\n const { pipeline } = transformers\n const extractor = await pipeline('feature-extraction', cfg.embeddingModel) as unknown as EmbeddingPipeline\n\n return new VectraMemoryStore(index, extractor, sessionPath, cfg)\n}\n\n/**\n * Validate that sessionPath is safe (not a sensitive system directory).\n * Prevents path traversal attacks via BARO_MEMORY_PATH env var.\n */\nfunction validateSessionPath(sessionPath: string): void {\n const resolved = join(sessionPath) // normalize\n // Reject paths containing '..' traversal\n if (resolved.includes('..')) {\n throw new Error(`Invalid session path (contains ..): ${resolved}`)\n }\n // Reject obvious sensitive directories\n const dangerous = ['/etc', '/usr', '/bin', '/sbin', '/var/run', '/System', '/Library']\n for (const d of dangerous) {\n if (resolved.startsWith(d + '/') || resolved === d) {\n throw new Error(`Invalid session path (sensitive directory): ${resolved}`)\n }\n }\n // Must contain a baro-related segment or be in tmpdir\n const normalizedPath = resolved.toLowerCase()\n const isSafe = ALLOWED_SESSION_PARENTS.some(p => normalizedPath.includes(p)) ||\n normalizedPath.startsWith(tmpdir().toLowerCase())\n if (!isSafe) {\n throw new Error(\n `Invalid session path (must be under ~/.baro, tmpdir, or contain 'baro-memory'): ${resolved}`\n )\n }\n}\n\n/**\n * Prune stale session directories older than SESSION_TTL_MS.\n * Call on orchestrator startup to prevent unbounded growth.\n * Uses lstatSync to avoid following symlinks (prevents symlink attacks).\n */\nexport function pruneOldSessions(sessionsDir: string): void {\n try {\n if (!existsSync(sessionsDir)) return\n const now = Date.now()\n for (const entry of readdirSync(sessionsDir)) {\n if (!entry.startsWith('run-')) continue\n const entryPath = join(sessionsDir, entry)\n try {\n const stat = lstatSync(entryPath)\n // Skip symlinks entirely (potential attack vector)\n if (stat.isSymbolicLink()) continue\n if (stat.isDirectory() && now - stat.mtimeMs > SESSION_TTL_MS) {\n rmSync(entryPath, { recursive: true, force: true })\n }\n } catch { /* skip entries we can't stat */ }\n }\n } catch { /* non-critical — don't crash if cleanup fails */ }\n}\n\n// ── Embedding helper ─────────────────────────────────────────────────────\n\n/**\n * Generate a normalized embedding vector for text.\n * @throws Error if embedding generation fails (empty text, model error)\n */\nasync function embed(extractor: EmbeddingPipeline, text: string): Promise<number[]> {\n if (!text || !text.trim()) {\n throw new Error('Cannot embed empty text')\n }\n const output = await extractor(text, { pooling: 'mean', normalize: true })\n if (!output?.data) {\n throw new Error('Embedding model returned no data')\n }\n return Array.from(output.data) as number[]\n}\n\n// ── VectraMemoryStore ────────────────────────────────────────────────────\n\nclass VectraMemoryStore implements MemoryStore {\n private readonly index: LocalIndex<VectraItemMetadata>\n private readonly extractor: EmbeddingPipeline\n private readonly sessionPath: string\n private readonly cachePath: string\n private readonly lockPath: string\n private readonly config: Required<MemoryStoreConfig>\n\n constructor(\n index: LocalIndex<VectraItemMetadata>,\n extractor: EmbeddingPipeline,\n sessionPath: string,\n config: Required<MemoryStoreConfig>,\n ) {\n this.index = index\n this.extractor = extractor\n this.sessionPath = sessionPath\n this.cachePath = join(sessionPath, 'cache.json')\n this.lockPath = join(sessionPath, 'cache.lock')\n this.config = config\n }\n\n // ── Semantic memory ──────────────────────────────────────────\n\n async remember(finding: Finding): Promise<boolean> {\n try {\n if (!finding.content?.trim()) return false\n\n const id = this.generateId(finding)\n const vector = await embed(this.extractor, finding.content)\n const metadata: VectraItemMetadata = {\n tool: finding.tool,\n agentId: finding.agentId,\n storyId: finding.storyId ?? '',\n filePath: finding.filePath ?? '',\n pattern: finding.pattern ?? '',\n command: finding.command ?? '',\n tags: finding.tags?.join(',') ?? '',\n content: finding.content.slice(0, MAX_CONTENT_CHARS),\n }\n\n await this.index.upsertItem({ id, vector, metadata })\n return true\n } catch {\n // Graceful degradation: don't crash if embedding/storage fails\n return false\n }\n }\n\n async recall(query: string, options?: RecallOptions): Promise<RecalledFinding[]> {\n try {\n const maxResults = options?.maxResults ?? this.config.defaultMaxResults\n const minSimilarity = options?.minSimilarity ?? this.config.defaultMinSimilarity\n const excludeAgent = options?.excludeAgent\n const filterByTool = options?.filterByTool\n\n if (!query?.trim()) return []\n\n const vector = await embed(this.extractor, query)\n\n // Query more than needed so we can post-filter\n const fetchK = Math.min(Math.max(maxResults * 3, 30), 200)\n const results: QueryResult<VectraItemMetadata>[] = await this.index.queryItems(vector, fetchK)\n\n // Post-filter and collect\n const output: RecalledFinding[] = []\n\n for (const result of results) {\n if (result.score < minSimilarity) continue\n if (excludeAgent && result.item.metadata.agentId === excludeAgent) continue\n if (filterByTool?.length && !filterByTool.includes(result.item.metadata.tool)) continue\n\n output.push({\n id: result.item.id,\n content: result.item.metadata.content,\n metadata: {\n tool: result.item.metadata.tool,\n agentId: result.item.metadata.agentId,\n storyId: result.item.metadata.storyId,\n filePath: result.item.metadata.filePath,\n pattern: result.item.metadata.pattern,\n command: result.item.metadata.command,\n tags: result.item.metadata.tags,\n },\n similarity: result.score,\n })\n\n if (output.length >= maxResults) break\n }\n\n return output\n } catch {\n // Graceful degradation: return empty on failure\n return []\n }\n }\n\n async gatherContext(\n storyId: string,\n hints: string[],\n maxChars: number = 20000,\n ): Promise<string | null> {\n const query = hints.join(' ')\n if (!query.trim()) return null\n\n const results = await this.recall(query, {\n maxResults: 20,\n minSimilarity: 0.3,\n excludeAgent: storyId,\n })\n\n if (results.length === 0) return null\n\n const lines: string[] = [\n '## Codebase context (from parallel agents)',\n '',\n 'Other agents in this run discovered the following.',\n 'Use this directly without re-reading files.',\n '',\n ]\n\n let totalChars = 0\n for (const result of results) {\n const header = `[${result.metadata.agentId}] ${result.metadata.tool}${\n result.metadata.filePath ? ` ${result.metadata.filePath}` : ''\n } (relevance: ${Math.round(result.similarity * 100)}%)`\n\n const entry = `${header}\\n${result.content}\\n`\n\n if (totalChars + entry.length > maxChars) {\n lines.push('[...truncated...]')\n break\n }\n\n lines.push(entry)\n totalChars += entry.length\n }\n\n return lines.join('\\n')\n }\n\n // ── File cache ───────────────────────────────────────────────\n // Simple JSON file (not vectorized -- exact key-value lookup).\n // NOTE: Multi-process writes use merge-on-write to reduce data loss.\n // This is best-effort — not ACID. For guaranteed consistency, use SQLite.\n\n async cacheFile(path: string, content: string, agentId: string): Promise<void> {\n // Skip excessively large files\n if (content.length > MAX_CACHE_FILE_BYTES) return\n\n // Merge-on-write: reload fresh state before modifying (reduces race window)\n const cache = this.loadCache()\n const existing = cache[path]\n if (!existing || existing.content !== content) {\n cache[path] = { path, content, readByAgent: agentId, timestamp: Date.now() }\n // Evict oldest entries if total cache exceeds limit\n this.evictIfNeeded(cache)\n this.saveCache(cache)\n }\n }\n\n async getCachedFile(path: string): Promise<string | null> {\n const cache = this.loadCache()\n return cache[path]?.content ?? null\n }\n\n async hasFile(path: string): Promise<boolean> {\n const cache = this.loadCache()\n return path in cache\n }\n\n async getCachedPaths(): Promise<string[]> {\n const cache = this.loadCache()\n return Object.keys(cache)\n }\n\n // ── Stats ────────────────────────────────────────────────────\n\n async getStats(): Promise<MemoryStats> {\n try {\n const items = await this.index.listItems<VectraItemMetadata>()\n const tools = new Set<string>()\n const agents = new Set<string>()\n\n for (const item of items) {\n tools.add(item.metadata.tool)\n agents.add(item.metadata.agentId)\n }\n\n const cache = this.loadCache()\n let cacheSizeBytes = 0\n for (const entry of Object.values(cache)) {\n cacheSizeBytes += entry.content.length\n }\n\n return {\n totalFindings: items.length,\n uniqueTools: tools.size,\n uniqueAgents: agents.size,\n toolsList: Array.from(tools),\n agentsList: Array.from(agents),\n cachedFiles: Object.keys(cache).length,\n cacheSizeBytes,\n }\n } catch {\n return {\n totalFindings: 0, uniqueTools: 0, uniqueAgents: 0,\n toolsList: [], agentsList: [], cachedFiles: 0, cacheSizeBytes: 0,\n }\n }\n }\n\n async close(): Promise<void> {\n // Vectra auto-persists; clean up lockfile if present\n try { rmSync(this.lockPath, { force: true }) } catch {}\n }\n\n // ── Private helpers ──────────────────────────────────────────\n\n /**\n * Evict oldest cache entries until total size is under MAX_TOTAL_CACHE_BYTES.\n * LRU-style: removes entries with oldest timestamps first.\n */\n private evictIfNeeded(cache: Record<string, CachedFile>): void {\n let totalBytes = 0\n for (const entry of Object.values(cache)) {\n totalBytes += entry.content.length\n }\n if (totalBytes <= MAX_TOTAL_CACHE_BYTES) return\n\n // Sort by timestamp ascending (oldest first)\n const entries = Object.entries(cache).sort((a, b) => a[1].timestamp - b[1].timestamp)\n for (const [key, entry] of entries) {\n if (totalBytes <= MAX_TOTAL_CACHE_BYTES) break\n totalBytes -= entry.content.length\n delete cache[key]\n }\n }\n\n private generateId(finding: Finding): string {\n const parts = [finding.agentId, finding.tool]\n if (finding.filePath) parts.push(finding.filePath)\n else if (finding.pattern) parts.push(finding.pattern)\n else if (finding.command) parts.push(finding.command)\n else {\n // For generic findings (no file/pattern/command), use a short\n // content hash to maintain deterministic dedup while avoiding\n // collisions. Same content = same ID = upsert (not duplicate).\n let hash = 0\n const str = finding.content.slice(0, 100)\n for (let i = 0; i < str.length; i++) {\n hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0\n }\n parts.push(Math.abs(hash).toString(36))\n }\n return parts.join(':')\n }\n\n /**\n * Load cache from disk. Reads are not locked — if another process is\n * mid-write using the atomic rename strategy, we'll either get the old\n * complete version or the new complete version (never a partial write).\n */\n private loadCache(): Record<string, CachedFile> {\n try {\n if (existsSync(this.cachePath)) {\n const raw = readFileSync(this.cachePath, 'utf-8')\n if (raw.trim()) {\n return JSON.parse(raw)\n }\n }\n } catch {\n // Corrupted or being written -- return empty (safe default)\n }\n return {}\n }\n\n /**\n * Save cache atomically (write to PID-scoped tmp file, then rename).\n * The rename is atomic on POSIX, so concurrent readers see either the\n * old or new version — never a partial write.\n */\n private saveCache(cache: Record<string, CachedFile>): void {\n try {\n // Write lockfile (advisory — best effort)\n writeFileSync(this.lockPath, String(process.pid), 'utf-8')\n\n const tmp = this.cachePath + `.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(cache), 'utf-8')\n renameSync(tmp, this.cachePath)\n } catch {\n // Fallback: direct write (may be read mid-write by other processes)\n try {\n writeFileSync(this.cachePath, JSON.stringify(cache), 'utf-8')\n } catch { /* disk full or permission denied -- data loss accepted */ }\n } finally {\n try { if (existsSync(this.lockPath)) rmSync(this.lockPath) } catch {}\n }\n }\n}\n\n// ── NoOp store ───────────────────────────────────────────────────────────\n\n/**\n * No-op implementation for when memory is disabled.\n */\nclass NoOpMemoryStore implements MemoryStore {\n async remember(_finding: Finding): Promise<boolean> { return false }\n async recall(_query: string, _options?: RecallOptions): Promise<RecalledFinding[]> { return [] }\n async gatherContext(_storyId: string, _hints: string[]): Promise<string | null> { return null }\n async cacheFile(_path: string, _content: string, _agentId: string): Promise<void> {}\n async getCachedFile(_path: string): Promise<string | null> { return null }\n async hasFile(_path: string): Promise<boolean> { return false }\n async getCachedPaths(): Promise<string[]> { return [] }\n async getStats(): Promise<MemoryStats> {\n return {\n totalFindings: 0, uniqueTools: 0, uniqueAgents: 0,\n toolsList: [], agentsList: [], cachedFiles: 0, cacheSizeBytes: 0,\n }\n }\n async close(): Promise<void> {}\n}\n","/**\n * baro-memory CLI - Query and store findings in the shared Vectra memory.\n *\n * Connects to the same Vectra index as the orchestrator via the\n * BARO_MEMORY_PATH environment variable. Story agents can use this\n * mid-flight to query context and store findings for sibling agents.\n *\n * Usage:\n * baro-memory query \"JWT authentication\" [--top 5] [--agent story-1]\n * baro-memory store \"found auth pattern\" --tool Read --file src/auth.ts --agent story-1\n * baro-memory cache list\n * baro-memory cache get src/auth.ts\n * baro-memory stats\n *\n * Environment:\n * BARO_MEMORY_PATH - Path to the shared session memory directory.\n * Set automatically by the baro orchestrator.\n */\n\nimport { createMemoryStore, type MemoryStore } from \"@baro/memory\"\n\nconst args = process.argv.slice(2)\nconst command = args[0]\n\nfunction getFlag(name: string): string | undefined {\n const idx = args.indexOf(`--${name}`)\n if (idx < 0 || idx + 1 >= args.length) return undefined\n const value = args[idx + 1]\n // Don't treat the next flag as a value\n if (value.startsWith('--')) return undefined\n return value\n}\n\nfunction getPositionalArg(index: number): string | undefined {\n const val = args[index]\n if (!val || val.startsWith('--')) return undefined\n return val\n}\n\nasync function main() {\n // Read session path from env (set by orchestrator) or --path flag\n const sessionPath = getFlag(\"path\") || process.env.BARO_MEMORY_PATH\n\n if (!sessionPath) {\n console.error(\n \"Error: No memory session path found.\\n\\n\" +\n \"Set BARO_MEMORY_PATH environment variable or pass --path <dir>.\\n\" +\n \"This is normally set automatically by the baro orchestrator.\\n\"\n )\n process.exit(1)\n }\n\n let store: MemoryStore | null = null\n\n try {\n store = await createMemoryStore({ sessionPath })\n\n switch (command) {\n case \"query\": {\n const query = getPositionalArg(1)\n if (!query) {\n console.error(\"Usage: baro-memory query <text> [--top N] [--agent id]\")\n process.exit(1)\n }\n const topRaw = getFlag(\"top\")\n const top = topRaw ? parseInt(topRaw, 10) : 5\n if (isNaN(top) || top < 1) {\n console.error(\"Error: --top must be a positive integer\")\n process.exit(1)\n }\n const agent = getFlag(\"agent\")\n\n const results = await store.recall(query, {\n maxResults: top,\n minSimilarity: 0.3,\n excludeAgent: agent,\n })\n\n if (results.length === 0) {\n console.log(\"No relevant findings found.\")\n } else {\n console.log(`Found ${results.length} relevant findings:\\n`)\n for (const r of results) {\n console.log(`[${r.metadata.tool}] ${r.metadata.agentId} (similarity: ${r.similarity.toFixed(2)})`)\n if (r.metadata.filePath) console.log(` File: ${r.metadata.filePath}`)\n console.log(` ${r.content.slice(0, 200)}${r.content.length > 200 ? '...' : ''}`)\n console.log()\n }\n }\n break\n }\n\n case \"store\": {\n const content = getPositionalArg(1)\n if (!content) {\n console.error(\"Usage: baro-memory store <content> --tool <tool> [--file <path>] [--agent <id>]\")\n process.exit(1)\n }\n const tool = getFlag(\"tool\") || \"Bash\"\n const file = getFlag(\"file\")\n const agent = getFlag(\"agent\") || \"manual\"\n\n const stored = await store.remember({\n tool,\n agentId: agent,\n content,\n filePath: file,\n })\n\n if (stored) {\n console.log(`Stored: ${tool} ${file || \"\"} from ${agent}`)\n } else {\n console.error(\"Failed to store finding (empty content or embedding error)\")\n process.exit(1)\n }\n break\n }\n\n case \"cache\": {\n const sub = getPositionalArg(1)\n if (sub === \"list\") {\n const paths = await store.getCachedPaths()\n if (paths.length === 0) {\n console.log(\"No cached files.\")\n } else {\n console.log(`Cached files (${paths.length}):`)\n for (const p of paths) {\n console.log(` ${p}`)\n }\n }\n } else if (sub === \"get\") {\n const path = getPositionalArg(2)\n if (!path) {\n console.error(\"Usage: baro-memory cache get <path>\")\n process.exit(1)\n }\n const content = await store.getCachedFile(path)\n if (content) {\n console.log(content)\n } else {\n console.error(`Not cached: ${path}`)\n process.exit(1)\n }\n } else {\n console.error(\"Usage: baro-memory cache [list|get <path>]\")\n process.exit(1)\n }\n break\n }\n\n case \"stats\": {\n const memStats = await store.getStats()\n console.log(\"Memory Stats:\")\n console.log(` Findings: ${memStats.totalFindings}`)\n console.log(` Cached files: ${memStats.cachedFiles}`)\n console.log(` Cache size: ${memStats.cacheSizeBytes} bytes`)\n console.log(` Tools: ${memStats.toolsList.join(\", \") || \"(none)\"}`)\n console.log(` Agents: ${memStats.agentsList.join(\", \") || \"(none)\"}`)\n break\n }\n\n default:\n console.log(\"baro-memory - Shared semantic memory for baro agents\\n\")\n console.log(\"Commands:\")\n console.log(\" query <text> [--top N] [--agent id] Search for relevant findings\")\n console.log(\" store <content> --tool <tool> Store a finding\")\n console.log(\" cache list List cached files\")\n console.log(\" cache get <path> Get cached file content\")\n console.log(\" stats Show memory statistics\")\n console.log(\"\")\n console.log(\"Environment:\")\n console.log(\" BARO_MEMORY_PATH Session memory directory (set by orchestrator)\")\n }\n } finally {\n // Always close store to release resources\n if (store) await store.close()\n }\n}\n\nmain().catch(err => {\n console.error(`baro-memory error: ${err.message || err}`)\n process.exit(1)\n})\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../baro-memory/src/vectra-store.ts","../../baro-orchestrator/scripts/baro-memory.ts"],"sourcesContent":["/**\n * Vectra-backed semantic memory store for baro agents.\n *\n * Architecture:\n * - Vectra LocalIndex for vector storage + similarity search (persisted to disk)\n * - @xenova/transformers ONNX model for embedding generation (CPU-only)\n * - Separate cache.json for file content dedup (not vectorized)\n *\n * Cross-process sharing: Vectra reads/writes index.json on every operation,\n * so the orchestrator's writes are immediately visible to CLI invocations.\n *\n * ID strategy: IDs are deterministic (agent:tool:file/pattern/command).\n * This means repeated reads of the same file by the same agent upsert\n * (update in place) rather than accumulate duplicate entries.\n */\n\nimport { LocalIndex } from 'vectra'\nimport type { QueryResult } from 'vectra'\nimport { readFileSync, writeFileSync, mkdirSync, existsSync, renameSync, rmSync, readdirSync, statSync, lstatSync } from 'fs'\nimport { join } from 'path'\nimport { tmpdir, homedir } from 'os'\n\nimport type {\n CachedFile,\n Finding,\n FindingMetadata,\n MemoryStore,\n MemoryStats,\n MemoryStoreConfig,\n RecalledFinding,\n RecallOptions,\n} from './types.js'\n\n// ── Constants ────────────────────────────────────────────────────────────\n\n/** Maximum characters stored per finding content. */\nconst MAX_CONTENT_CHARS = 4000\n\n/** Maximum bytes for a single cached file (5MB). */\nconst MAX_CACHE_FILE_BYTES = 5 * 1024 * 1024\n\n/** Maximum total cache size (50MB). Beyond this, oldest entries are evicted. */\nconst MAX_TOTAL_CACHE_BYTES = 50 * 1024 * 1024\n\n/** Stale session threshold: 24 hours. */\nconst SESSION_TTL_MS = 24 * 60 * 60 * 1000\n\n/** Allowed parent directories for session paths (prevent path traversal). */\nconst ALLOWED_SESSION_PARENTS = ['.baro', 'baro-memory', 'tmp']\n\n// ── Defaults ─────────────────────────────────────────────────────────────\n\nconst DEFAULTS = {\n embeddingModel: 'Xenova/all-MiniLM-L6-v2',\n defaultMinSimilarity: 0.3,\n defaultMaxResults: 10,\n disabled: false,\n sessionPath: '',\n} satisfies Required<MemoryStoreConfig>\n\n// ── Types ────────────────────────────────────────────────────────────────\n\n/** Typed embedding pipeline from @xenova/transformers. */\ntype EmbeddingPipeline = (\n text: string,\n opts: { pooling: string; normalize: boolean },\n) => Promise<{ data: ArrayLike<number> }>\n\n/** Metadata stored alongside each vector in Vectra. */\ninterface VectraItemMetadata {\n [key: string]: string // Index signature for Vectra compatibility\n tool: string\n agentId: string\n storyId: string\n filePath: string\n pattern: string\n command: string\n tags: string\n /** The original text content (stored for retrieval). */\n content: string\n}\n\n// ── Factory ──────────────────────────────────────────────────────────────\n\n/**\n * Create a memory store backed by Vectra (local vector DB).\n *\n * - If `sessionPath` is set, the index + cache persist there.\n * - If not set, a temp directory is used (ephemeral, single-process).\n * - If `disabled`, returns a no-op store.\n *\n * @param config - Optional configuration\n * @returns MemoryStore instance\n */\nexport async function createMemoryStore(\n config?: MemoryStoreConfig,\n): Promise<MemoryStore> {\n const cfg = { ...DEFAULTS, ...config }\n\n // Validate config\n if (cfg.defaultMinSimilarity < 0 || cfg.defaultMinSimilarity > 1) {\n cfg.defaultMinSimilarity = DEFAULTS.defaultMinSimilarity\n }\n if (cfg.defaultMaxResults < 1) {\n cfg.defaultMaxResults = DEFAULTS.defaultMaxResults\n }\n\n if (cfg.disabled) {\n return new NoOpMemoryStore()\n }\n\n // Resolve and validate session path (prevent path traversal)\n const sessionPath = cfg.sessionPath || join(tmpdir(), `baro-memory-${process.pid}-${Date.now()}`)\n validateSessionPath(sessionPath)\n mkdirSync(sessionPath, { recursive: true })\n\n // Initialize Vectra index\n const indexPath = join(sessionPath, 'index')\n mkdirSync(indexPath, { recursive: true })\n const index = new LocalIndex<VectraItemMetadata>(indexPath)\n\n if (!(await index.isIndexCreated())) {\n await index.createIndex({ version: 1 })\n }\n\n // Load ONNX embedding model (cached after first load by transformers.js).\n // Pin the cache to a writable, persistent baro-owned dir: the default is\n // `node_modules/@xenova/transformers/.cache`, which on a global install\n // (e.g. /usr/local/lib) can be read-only — the model would fail to\n // download. ~/.baro/models is user-writable and survives across runs so\n // MiniLM is fetched only once. Override with TRANSFORMERS_CACHE if set.\n const transformers = await import('@xenova/transformers')\n transformers.env.cacheDir = process.env.TRANSFORMERS_CACHE || join(homedir(), '.baro', 'models')\n const { pipeline } = transformers\n const extractor = await pipeline('feature-extraction', cfg.embeddingModel) as unknown as EmbeddingPipeline\n\n return new VectraMemoryStore(index, extractor, sessionPath, cfg)\n}\n\n/**\n * Validate that sessionPath is safe (not a sensitive system directory).\n * Prevents path traversal attacks via BARO_MEMORY_PATH env var.\n */\nfunction validateSessionPath(sessionPath: string): void {\n const resolved = join(sessionPath) // normalize\n // Reject paths containing '..' traversal\n if (resolved.includes('..')) {\n throw new Error(`Invalid session path (contains ..): ${resolved}`)\n }\n // Reject obvious sensitive directories\n const dangerous = ['/etc', '/usr', '/bin', '/sbin', '/var/run', '/System', '/Library']\n for (const d of dangerous) {\n if (resolved.startsWith(d + '/') || resolved === d) {\n throw new Error(`Invalid session path (sensitive directory): ${resolved}`)\n }\n }\n // Must contain a baro-related segment or be in tmpdir\n const normalizedPath = resolved.toLowerCase()\n const isSafe = ALLOWED_SESSION_PARENTS.some(p => normalizedPath.includes(p)) ||\n normalizedPath.startsWith(tmpdir().toLowerCase())\n if (!isSafe) {\n throw new Error(\n `Invalid session path (must be under ~/.baro, tmpdir, or contain 'baro-memory'): ${resolved}`\n )\n }\n}\n\n/**\n * Prune stale session directories older than SESSION_TTL_MS.\n * Call on orchestrator startup to prevent unbounded growth.\n * Uses lstatSync to avoid following symlinks (prevents symlink attacks).\n */\nexport function pruneOldSessions(sessionsDir: string): void {\n try {\n if (!existsSync(sessionsDir)) return\n const now = Date.now()\n for (const entry of readdirSync(sessionsDir)) {\n if (!entry.startsWith('run-')) continue\n const entryPath = join(sessionsDir, entry)\n try {\n const stat = lstatSync(entryPath)\n // Skip symlinks entirely (potential attack vector)\n if (stat.isSymbolicLink()) continue\n if (stat.isDirectory() && now - stat.mtimeMs > SESSION_TTL_MS) {\n rmSync(entryPath, { recursive: true, force: true })\n }\n } catch { /* skip entries we can't stat */ }\n }\n } catch { /* non-critical — don't crash if cleanup fails */ }\n}\n\n// ── Embedding helper ─────────────────────────────────────────────────────\n\n/**\n * Generate a normalized embedding vector for text.\n * @throws Error if embedding generation fails (empty text, model error)\n */\nasync function embed(extractor: EmbeddingPipeline, text: string): Promise<number[]> {\n if (!text || !text.trim()) {\n throw new Error('Cannot embed empty text')\n }\n const output = await extractor(text, { pooling: 'mean', normalize: true })\n if (!output?.data) {\n throw new Error('Embedding model returned no data')\n }\n return Array.from(output.data) as number[]\n}\n\n// ── VectraMemoryStore ────────────────────────────────────────────────────\n\nclass VectraMemoryStore implements MemoryStore {\n // Not readonly: the reader re-instantiates the index to pick up writes\n // made by other processes (see refreshIndexIfChanged).\n private index: LocalIndex<VectraItemMetadata>\n private readonly extractor: EmbeddingPipeline\n private readonly sessionPath: string\n private readonly indexPath: string\n private readonly indexFilePath: string\n /** mtimeMs of index.json last time we (re)loaded; -1 = never seen. */\n private lastIndexMtimeMs = -1\n private readonly cachePath: string\n private readonly lockPath: string\n private readonly config: Required<MemoryStoreConfig>\n\n constructor(\n index: LocalIndex<VectraItemMetadata>,\n extractor: EmbeddingPipeline,\n sessionPath: string,\n config: Required<MemoryStoreConfig>,\n ) {\n this.index = index\n this.extractor = extractor\n this.sessionPath = sessionPath\n this.indexPath = join(sessionPath, 'index')\n // Vectra persists the index to <folder>/index.json.\n this.indexFilePath = join(this.indexPath, 'index.json')\n this.cachePath = join(sessionPath, 'cache.json')\n this.lockPath = join(sessionPath, 'cache.lock')\n this.config = config\n }\n\n /**\n * Pick up cross-process writes to the shared on-disk index.\n *\n * Vectra's LocalIndex loads the whole index into a private in-memory\n * `_data` field on first query and never reloads it. Findings written by\n * story agents in SEPARATE processes (via the baro-memory CLI) land on\n * disk but stay invisible to this long-lived reader — so recall/getStats\n * see a frozen (usually empty) snapshot and always return 0 (issue #51).\n *\n * There is no public reload on LocalIndex, so we detect a change via the\n * index file's mtime and swap in a fresh LocalIndex, which lazily loads\n * the current on-disk data on its next query. Throttled by mtime so we\n * don't re-read every call — cheap relative to an LLM turn. Read paths\n * only; writers (remember/upsertItem) already refresh Vectra's `_data`\n * via beginUpdate, and reloading mid-write could clobber a pending batch.\n *\n * Never throws: on any stat/instantiation error we keep the current\n * index and degrade gracefully.\n */\n private refreshIndexIfChanged(): void {\n try {\n if (!existsSync(this.indexFilePath)) return\n const mtimeMs = statSync(this.indexFilePath).mtimeMs\n if (mtimeMs === this.lastIndexMtimeMs) return\n // Create the new index BEFORE advancing the mtime watermark: if\n // instantiation throws, we keep the old index AND the old mtime,\n // so the next call retries instead of locking onto a stale\n // snapshot for this mtime (Greptile #52 P2).\n const newIndex = new LocalIndex<VectraItemMetadata>(this.indexPath)\n this.lastIndexMtimeMs = mtimeMs\n this.index = newIndex\n } catch {\n // Keep the existing index on any error.\n }\n }\n\n // ── Semantic memory ──────────────────────────────────────────\n\n async remember(finding: Finding): Promise<boolean> {\n try {\n if (!finding.content?.trim()) return false\n\n const id = this.generateId(finding)\n const vector = await embed(this.extractor, finding.content)\n const metadata: VectraItemMetadata = {\n tool: finding.tool,\n agentId: finding.agentId,\n storyId: finding.storyId ?? '',\n filePath: finding.filePath ?? '',\n pattern: finding.pattern ?? '',\n command: finding.command ?? '',\n tags: finding.tags?.join(',') ?? '',\n content: finding.content.slice(0, MAX_CONTENT_CHARS),\n }\n\n await this.index.upsertItem({ id, vector, metadata })\n return true\n } catch {\n // Graceful degradation: don't crash if embedding/storage fails\n return false\n }\n }\n\n async recall(query: string, options?: RecallOptions): Promise<RecalledFinding[]> {\n try {\n const maxResults = options?.maxResults ?? this.config.defaultMaxResults\n const minSimilarity = options?.minSimilarity ?? this.config.defaultMinSimilarity\n const excludeAgent = options?.excludeAgent\n const filterByTool = options?.filterByTool\n\n if (!query?.trim()) return []\n\n // Reload the on-disk index if other processes wrote to it.\n this.refreshIndexIfChanged()\n\n const vector = await embed(this.extractor, query)\n\n // Query more than needed so we can post-filter\n const fetchK = Math.min(Math.max(maxResults * 3, 30), 200)\n const results: QueryResult<VectraItemMetadata>[] = await this.index.queryItems(vector, fetchK)\n\n // Post-filter and collect\n const output: RecalledFinding[] = []\n\n for (const result of results) {\n if (result.score < minSimilarity) continue\n if (excludeAgent && result.item.metadata.agentId === excludeAgent) continue\n if (filterByTool?.length && !filterByTool.includes(result.item.metadata.tool)) continue\n\n output.push({\n id: result.item.id,\n content: result.item.metadata.content,\n metadata: {\n tool: result.item.metadata.tool,\n agentId: result.item.metadata.agentId,\n storyId: result.item.metadata.storyId,\n filePath: result.item.metadata.filePath,\n pattern: result.item.metadata.pattern,\n command: result.item.metadata.command,\n tags: result.item.metadata.tags,\n },\n similarity: result.score,\n })\n\n if (output.length >= maxResults) break\n }\n\n return output\n } catch {\n // Graceful degradation: return empty on failure\n return []\n }\n }\n\n async gatherContext(\n storyId: string,\n hints: string[],\n maxChars: number = 20000,\n ): Promise<string | null> {\n const query = hints.join(' ')\n if (!query.trim()) return null\n\n const results = await this.recall(query, {\n maxResults: 20,\n minSimilarity: 0.3,\n excludeAgent: storyId,\n })\n\n if (results.length === 0) return null\n\n const lines: string[] = [\n '## Codebase context (from parallel agents)',\n '',\n 'Other agents in this run discovered the following.',\n 'Use this directly without re-reading files.',\n '',\n ]\n\n let totalChars = 0\n for (const result of results) {\n const header = `[${result.metadata.agentId}] ${result.metadata.tool}${\n result.metadata.filePath ? ` ${result.metadata.filePath}` : ''\n } (relevance: ${Math.round(result.similarity * 100)}%)`\n\n const entry = `${header}\\n${result.content}\\n`\n\n if (totalChars + entry.length > maxChars) {\n lines.push('[...truncated...]')\n break\n }\n\n lines.push(entry)\n totalChars += entry.length\n }\n\n return lines.join('\\n')\n }\n\n // ── File cache ───────────────────────────────────────────────\n // Simple JSON file (not vectorized -- exact key-value lookup).\n // NOTE: Multi-process writes use merge-on-write to reduce data loss.\n // This is best-effort — not ACID. For guaranteed consistency, use SQLite.\n\n async cacheFile(path: string, content: string, agentId: string): Promise<void> {\n // Skip excessively large files\n if (content.length > MAX_CACHE_FILE_BYTES) return\n\n // Merge-on-write: reload fresh state before modifying (reduces race window)\n const cache = this.loadCache()\n const existing = cache[path]\n if (!existing || existing.content !== content) {\n cache[path] = { path, content, readByAgent: agentId, timestamp: Date.now() }\n // Evict oldest entries if total cache exceeds limit\n this.evictIfNeeded(cache)\n this.saveCache(cache)\n }\n }\n\n async getCachedFile(path: string): Promise<string | null> {\n const cache = this.loadCache()\n return cache[path]?.content ?? null\n }\n\n async hasFile(path: string): Promise<boolean> {\n const cache = this.loadCache()\n return path in cache\n }\n\n async getCachedPaths(): Promise<string[]> {\n const cache = this.loadCache()\n return Object.keys(cache)\n }\n\n // ── Stats ────────────────────────────────────────────────────\n\n async getStats(): Promise<MemoryStats> {\n try {\n // Reload the on-disk index if other processes wrote to it.\n this.refreshIndexIfChanged()\n const items = await this.index.listItems<VectraItemMetadata>()\n const tools = new Set<string>()\n const agents = new Set<string>()\n\n for (const item of items) {\n tools.add(item.metadata.tool)\n agents.add(item.metadata.agentId)\n }\n\n const cache = this.loadCache()\n let cacheSizeBytes = 0\n for (const entry of Object.values(cache)) {\n cacheSizeBytes += entry.content.length\n }\n\n return {\n totalFindings: items.length,\n uniqueTools: tools.size,\n uniqueAgents: agents.size,\n toolsList: Array.from(tools),\n agentsList: Array.from(agents),\n cachedFiles: Object.keys(cache).length,\n cacheSizeBytes,\n }\n } catch {\n return {\n totalFindings: 0, uniqueTools: 0, uniqueAgents: 0,\n toolsList: [], agentsList: [], cachedFiles: 0, cacheSizeBytes: 0,\n }\n }\n }\n\n async close(): Promise<void> {\n // Vectra auto-persists; clean up lockfile if present\n try { rmSync(this.lockPath, { force: true }) } catch {}\n }\n\n // ── Private helpers ──────────────────────────────────────────\n\n /**\n * Evict oldest cache entries until total size is under MAX_TOTAL_CACHE_BYTES.\n * LRU-style: removes entries with oldest timestamps first.\n */\n private evictIfNeeded(cache: Record<string, CachedFile>): void {\n let totalBytes = 0\n for (const entry of Object.values(cache)) {\n totalBytes += entry.content.length\n }\n if (totalBytes <= MAX_TOTAL_CACHE_BYTES) return\n\n // Sort by timestamp ascending (oldest first)\n const entries = Object.entries(cache).sort((a, b) => a[1].timestamp - b[1].timestamp)\n for (const [key, entry] of entries) {\n if (totalBytes <= MAX_TOTAL_CACHE_BYTES) break\n totalBytes -= entry.content.length\n delete cache[key]\n }\n }\n\n private generateId(finding: Finding): string {\n const parts = [finding.agentId, finding.tool]\n if (finding.filePath) parts.push(finding.filePath)\n else if (finding.pattern) parts.push(finding.pattern)\n else if (finding.command) parts.push(finding.command)\n else {\n // For generic findings (no file/pattern/command), use a short\n // content hash to maintain deterministic dedup while avoiding\n // collisions. Same content = same ID = upsert (not duplicate).\n let hash = 0\n const str = finding.content.slice(0, 100)\n for (let i = 0; i < str.length; i++) {\n hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0\n }\n parts.push(Math.abs(hash).toString(36))\n }\n return parts.join(':')\n }\n\n /**\n * Load cache from disk. Reads are not locked — if another process is\n * mid-write using the atomic rename strategy, we'll either get the old\n * complete version or the new complete version (never a partial write).\n */\n private loadCache(): Record<string, CachedFile> {\n try {\n if (existsSync(this.cachePath)) {\n const raw = readFileSync(this.cachePath, 'utf-8')\n if (raw.trim()) {\n return JSON.parse(raw)\n }\n }\n } catch {\n // Corrupted or being written -- return empty (safe default)\n }\n return {}\n }\n\n /**\n * Save cache atomically (write to PID-scoped tmp file, then rename).\n * The rename is atomic on POSIX, so concurrent readers see either the\n * old or new version — never a partial write.\n */\n private saveCache(cache: Record<string, CachedFile>): void {\n try {\n // Write lockfile (advisory — best effort)\n writeFileSync(this.lockPath, String(process.pid), 'utf-8')\n\n const tmp = this.cachePath + `.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(cache), 'utf-8')\n renameSync(tmp, this.cachePath)\n } catch {\n // Fallback: direct write (may be read mid-write by other processes)\n try {\n writeFileSync(this.cachePath, JSON.stringify(cache), 'utf-8')\n } catch { /* disk full or permission denied -- data loss accepted */ }\n } finally {\n try { if (existsSync(this.lockPath)) rmSync(this.lockPath) } catch {}\n }\n }\n}\n\n// ── NoOp store ───────────────────────────────────────────────────────────\n\n/**\n * No-op implementation for when memory is disabled.\n */\nclass NoOpMemoryStore implements MemoryStore {\n async remember(_finding: Finding): Promise<boolean> { return false }\n async recall(_query: string, _options?: RecallOptions): Promise<RecalledFinding[]> { return [] }\n async gatherContext(_storyId: string, _hints: string[]): Promise<string | null> { return null }\n async cacheFile(_path: string, _content: string, _agentId: string): Promise<void> {}\n async getCachedFile(_path: string): Promise<string | null> { return null }\n async hasFile(_path: string): Promise<boolean> { return false }\n async getCachedPaths(): Promise<string[]> { return [] }\n async getStats(): Promise<MemoryStats> {\n return {\n totalFindings: 0, uniqueTools: 0, uniqueAgents: 0,\n toolsList: [], agentsList: [], cachedFiles: 0, cacheSizeBytes: 0,\n }\n }\n async close(): Promise<void> {}\n}\n","/**\n * baro-memory CLI - Query and store findings in the shared Vectra memory.\n *\n * Connects to the same Vectra index as the orchestrator via the\n * BARO_MEMORY_PATH environment variable. Story agents can use this\n * mid-flight to query context and store findings for sibling agents.\n *\n * Usage:\n * baro-memory query \"JWT authentication\" [--top 5] [--agent story-1]\n * baro-memory store \"found auth pattern\" --tool Read --file src/auth.ts --agent story-1\n * baro-memory cache list\n * baro-memory cache get src/auth.ts\n * baro-memory stats\n *\n * Environment:\n * BARO_MEMORY_PATH - Path to the shared session memory directory.\n * Set automatically by the baro orchestrator.\n */\n\nimport { createMemoryStore, type MemoryStore } from \"@baro/memory\"\n\nconst args = process.argv.slice(2)\nconst command = args[0]\n\nfunction getFlag(name: string): string | undefined {\n const idx = args.indexOf(`--${name}`)\n if (idx < 0 || idx + 1 >= args.length) return undefined\n const value = args[idx + 1]\n // Don't treat the next flag as a value\n if (value.startsWith('--')) return undefined\n return value\n}\n\nfunction getPositionalArg(index: number): string | undefined {\n const val = args[index]\n if (!val || val.startsWith('--')) return undefined\n return val\n}\n\nasync function main() {\n // Read session path from env (set by orchestrator) or --path flag\n const sessionPath = getFlag(\"path\") || process.env.BARO_MEMORY_PATH\n\n if (!sessionPath) {\n console.error(\n \"Error: No memory session path found.\\n\\n\" +\n \"Set BARO_MEMORY_PATH environment variable or pass --path <dir>.\\n\" +\n \"This is normally set automatically by the baro orchestrator.\\n\"\n )\n process.exit(1)\n }\n\n let store: MemoryStore | null = null\n\n try {\n store = await createMemoryStore({ sessionPath })\n\n switch (command) {\n case \"query\": {\n const query = getPositionalArg(1)\n if (!query) {\n console.error(\"Usage: baro-memory query <text> [--top N] [--agent id]\")\n process.exit(1)\n }\n const topRaw = getFlag(\"top\")\n const top = topRaw ? parseInt(topRaw, 10) : 5\n if (isNaN(top) || top < 1) {\n console.error(\"Error: --top must be a positive integer\")\n process.exit(1)\n }\n const agent = getFlag(\"agent\")\n\n const results = await store.recall(query, {\n maxResults: top,\n minSimilarity: 0.3,\n excludeAgent: agent,\n })\n\n if (results.length === 0) {\n console.log(\"No relevant findings found.\")\n } else {\n console.log(`Found ${results.length} relevant findings:\\n`)\n for (const r of results) {\n console.log(`[${r.metadata.tool}] ${r.metadata.agentId} (similarity: ${r.similarity.toFixed(2)})`)\n if (r.metadata.filePath) console.log(` File: ${r.metadata.filePath}`)\n console.log(` ${r.content.slice(0, 200)}${r.content.length > 200 ? '...' : ''}`)\n console.log()\n }\n }\n break\n }\n\n case \"store\": {\n const content = getPositionalArg(1)\n if (!content) {\n console.error(\"Usage: baro-memory store <content> --tool <tool> [--file <path>] [--agent <id>]\")\n process.exit(1)\n }\n const tool = getFlag(\"tool\") || \"Bash\"\n const file = getFlag(\"file\")\n const agent = getFlag(\"agent\") || \"manual\"\n\n const stored = await store.remember({\n tool,\n agentId: agent,\n content,\n filePath: file,\n })\n\n if (stored) {\n console.log(`Stored: ${tool} ${file || \"\"} from ${agent}`)\n } else {\n console.error(\"Failed to store finding (empty content or embedding error)\")\n process.exit(1)\n }\n break\n }\n\n case \"cache\": {\n const sub = getPositionalArg(1)\n if (sub === \"list\") {\n const paths = await store.getCachedPaths()\n if (paths.length === 0) {\n console.log(\"No cached files.\")\n } else {\n console.log(`Cached files (${paths.length}):`)\n for (const p of paths) {\n console.log(` ${p}`)\n }\n }\n } else if (sub === \"get\") {\n const path = getPositionalArg(2)\n if (!path) {\n console.error(\"Usage: baro-memory cache get <path>\")\n process.exit(1)\n }\n const content = await store.getCachedFile(path)\n if (content) {\n console.log(content)\n } else {\n console.error(`Not cached: ${path}`)\n process.exit(1)\n }\n } else {\n console.error(\"Usage: baro-memory cache [list|get <path>]\")\n process.exit(1)\n }\n break\n }\n\n case \"stats\": {\n const memStats = await store.getStats()\n console.log(\"Memory Stats:\")\n console.log(` Findings: ${memStats.totalFindings}`)\n console.log(` Cached files: ${memStats.cachedFiles}`)\n console.log(` Cache size: ${memStats.cacheSizeBytes} bytes`)\n console.log(` Tools: ${memStats.toolsList.join(\", \") || \"(none)\"}`)\n console.log(` Agents: ${memStats.agentsList.join(\", \") || \"(none)\"}`)\n break\n }\n\n default:\n console.log(\"baro-memory - Shared semantic memory for baro agents\\n\")\n console.log(\"Commands:\")\n console.log(\" query <text> [--top N] [--agent id] Search for relevant findings\")\n console.log(\" store <content> --tool <tool> Store a finding\")\n console.log(\" cache list List cached files\")\n console.log(\" cache get <path> Get cached file content\")\n console.log(\" stats Show memory statistics\")\n console.log(\"\")\n console.log(\"Environment:\")\n console.log(\" BARO_MEMORY_PATH Session memory directory (set by orchestrator)\")\n }\n } finally {\n // Always close store to release resources\n if (store) await store.close()\n }\n}\n\nmain().catch(err => {\n console.error(`baro-memory error: ${err.message || err}`)\n process.exit(1)\n})\n"],"mappings":"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","names":[]}
|
package/dist/cli.mjs
CHANGED
|
@@ -22243,7 +22243,7 @@ var require_websocket_server = __commonJS({
|
|
|
22243
22243
|
|
|
22244
22244
|
// ../baro-memory/dist/vectra-store.js
|
|
22245
22245
|
import { LocalIndex } from "vectra";
|
|
22246
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, renameSync, rmSync, readdirSync, lstatSync } from "fs";
|
|
22246
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, renameSync, rmSync, readdirSync, statSync as statSync2, lstatSync } from "fs";
|
|
22247
22247
|
import { join as join2 } from "path";
|
|
22248
22248
|
import { tmpdir, homedir } from "os";
|
|
22249
22249
|
async function createMemoryStore(config) {
|
|
@@ -22338,9 +22338,15 @@ var init_vectra_store = __esm({
|
|
|
22338
22338
|
sessionPath: ""
|
|
22339
22339
|
};
|
|
22340
22340
|
VectraMemoryStore = class {
|
|
22341
|
+
// Not readonly: the reader re-instantiates the index to pick up writes
|
|
22342
|
+
// made by other processes (see refreshIndexIfChanged).
|
|
22341
22343
|
index;
|
|
22342
22344
|
extractor;
|
|
22343
22345
|
sessionPath;
|
|
22346
|
+
indexPath;
|
|
22347
|
+
indexFilePath;
|
|
22348
|
+
/** mtimeMs of index.json last time we (re)loaded; -1 = never seen. */
|
|
22349
|
+
lastIndexMtimeMs = -1;
|
|
22344
22350
|
cachePath;
|
|
22345
22351
|
lockPath;
|
|
22346
22352
|
config;
|
|
@@ -22348,10 +22354,44 @@ var init_vectra_store = __esm({
|
|
|
22348
22354
|
this.index = index;
|
|
22349
22355
|
this.extractor = extractor;
|
|
22350
22356
|
this.sessionPath = sessionPath;
|
|
22357
|
+
this.indexPath = join2(sessionPath, "index");
|
|
22358
|
+
this.indexFilePath = join2(this.indexPath, "index.json");
|
|
22351
22359
|
this.cachePath = join2(sessionPath, "cache.json");
|
|
22352
22360
|
this.lockPath = join2(sessionPath, "cache.lock");
|
|
22353
22361
|
this.config = config;
|
|
22354
22362
|
}
|
|
22363
|
+
/**
|
|
22364
|
+
* Pick up cross-process writes to the shared on-disk index.
|
|
22365
|
+
*
|
|
22366
|
+
* Vectra's LocalIndex loads the whole index into a private in-memory
|
|
22367
|
+
* `_data` field on first query and never reloads it. Findings written by
|
|
22368
|
+
* story agents in SEPARATE processes (via the baro-memory CLI) land on
|
|
22369
|
+
* disk but stay invisible to this long-lived reader — so recall/getStats
|
|
22370
|
+
* see a frozen (usually empty) snapshot and always return 0 (issue #51).
|
|
22371
|
+
*
|
|
22372
|
+
* There is no public reload on LocalIndex, so we detect a change via the
|
|
22373
|
+
* index file's mtime and swap in a fresh LocalIndex, which lazily loads
|
|
22374
|
+
* the current on-disk data on its next query. Throttled by mtime so we
|
|
22375
|
+
* don't re-read every call — cheap relative to an LLM turn. Read paths
|
|
22376
|
+
* only; writers (remember/upsertItem) already refresh Vectra's `_data`
|
|
22377
|
+
* via beginUpdate, and reloading mid-write could clobber a pending batch.
|
|
22378
|
+
*
|
|
22379
|
+
* Never throws: on any stat/instantiation error we keep the current
|
|
22380
|
+
* index and degrade gracefully.
|
|
22381
|
+
*/
|
|
22382
|
+
refreshIndexIfChanged() {
|
|
22383
|
+
try {
|
|
22384
|
+
if (!existsSync2(this.indexFilePath))
|
|
22385
|
+
return;
|
|
22386
|
+
const mtimeMs = statSync2(this.indexFilePath).mtimeMs;
|
|
22387
|
+
if (mtimeMs === this.lastIndexMtimeMs)
|
|
22388
|
+
return;
|
|
22389
|
+
const newIndex = new LocalIndex(this.indexPath);
|
|
22390
|
+
this.lastIndexMtimeMs = mtimeMs;
|
|
22391
|
+
this.index = newIndex;
|
|
22392
|
+
} catch {
|
|
22393
|
+
}
|
|
22394
|
+
}
|
|
22355
22395
|
// ── Semantic memory ──────────────────────────────────────────
|
|
22356
22396
|
async remember(finding) {
|
|
22357
22397
|
try {
|
|
@@ -22383,6 +22423,7 @@ var init_vectra_store = __esm({
|
|
|
22383
22423
|
const filterByTool = options?.filterByTool;
|
|
22384
22424
|
if (!query?.trim())
|
|
22385
22425
|
return [];
|
|
22426
|
+
this.refreshIndexIfChanged();
|
|
22386
22427
|
const vector = await embed(this.extractor, query);
|
|
22387
22428
|
const fetchK = Math.min(Math.max(maxResults * 3, 30), 200);
|
|
22388
22429
|
const results = await this.index.queryItems(vector, fetchK);
|
|
@@ -22479,6 +22520,7 @@ ${result.content}
|
|
|
22479
22520
|
// ── Stats ────────────────────────────────────────────────────
|
|
22480
22521
|
async getStats() {
|
|
22481
22522
|
try {
|
|
22523
|
+
this.refreshIndexIfChanged();
|
|
22482
22524
|
const items = await this.index.listItems();
|
|
22483
22525
|
const tools = /* @__PURE__ */ new Set();
|
|
22484
22526
|
const agents = /* @__PURE__ */ new Set();
|