#!/usr/bin/env node
import { createRequire as __baroCreateRequire } from "module";
const require = __baroCreateRequire(import.meta.url);

// ../baro-memory/dist/vectra-store.js
import { LocalIndex } from "vectra";
import { readFileSync, writeFileSync, mkdirSync, existsSync, renameSync, rmSync, readdirSync, lstatSync } from "fs";
import { join } from "path";
import { tmpdir, homedir } from "os";
var MAX_CONTENT_CHARS = 4e3;
var MAX_CACHE_FILE_BYTES = 5 * 1024 * 1024;
var MAX_TOTAL_CACHE_BYTES = 50 * 1024 * 1024;
var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
var ALLOWED_SESSION_PARENTS = [".baro", "baro-memory", "tmp"];
var DEFAULTS = {
  embeddingModel: "Xenova/all-MiniLM-L6-v2",
  defaultMinSimilarity: 0.3,
  defaultMaxResults: 10,
  disabled: false,
  sessionPath: ""
};
async function createMemoryStore(config) {
  const cfg = { ...DEFAULTS, ...config };
  if (cfg.defaultMinSimilarity < 0 || cfg.defaultMinSimilarity > 1) {
    cfg.defaultMinSimilarity = DEFAULTS.defaultMinSimilarity;
  }
  if (cfg.defaultMaxResults < 1) {
    cfg.defaultMaxResults = DEFAULTS.defaultMaxResults;
  }
  if (cfg.disabled) {
    return new NoOpMemoryStore();
  }
  const sessionPath = cfg.sessionPath || join(tmpdir(), `baro-memory-${process.pid}-${Date.now()}`);
  validateSessionPath(sessionPath);
  mkdirSync(sessionPath, { recursive: true });
  const indexPath = join(sessionPath, "index");
  mkdirSync(indexPath, { recursive: true });
  const index = new LocalIndex(indexPath);
  if (!await index.isIndexCreated()) {
    await index.createIndex({ version: 1 });
  }
  const transformers = await import("@xenova/transformers");
  transformers.env.cacheDir = process.env.TRANSFORMERS_CACHE || join(homedir(), ".baro", "models");
  const { pipeline } = transformers;
  const extractor = await pipeline("feature-extraction", cfg.embeddingModel);
  return new VectraMemoryStore(index, extractor, sessionPath, cfg);
}
function validateSessionPath(sessionPath) {
  const resolved = join(sessionPath);
  if (resolved.includes("..")) {
    throw new Error(`Invalid session path (contains ..): ${resolved}`);
  }
  const dangerous = ["/etc", "/usr", "/bin", "/sbin", "/var/run", "/System", "/Library"];
  for (const d of dangerous) {
    if (resolved.startsWith(d + "/") || resolved === d) {
      throw new Error(`Invalid session path (sensitive directory): ${resolved}`);
    }
  }
  const normalizedPath = resolved.toLowerCase();
  const isSafe = ALLOWED_SESSION_PARENTS.some((p) => normalizedPath.includes(p)) || normalizedPath.startsWith(tmpdir().toLowerCase());
  if (!isSafe) {
    throw new Error(`Invalid session path (must be under ~/.baro, tmpdir, or contain 'baro-memory'): ${resolved}`);
  }
}
async function embed(extractor, text) {
  if (!text || !text.trim()) {
    throw new Error("Cannot embed empty text");
  }
  const output = await extractor(text, { pooling: "mean", normalize: true });
  if (!output?.data) {
    throw new Error("Embedding model returned no data");
  }
  return Array.from(output.data);
}
var VectraMemoryStore = class {
  index;
  extractor;
  sessionPath;
  cachePath;
  lockPath;
  config;
  constructor(index, extractor, sessionPath, config) {
    this.index = index;
    this.extractor = extractor;
    this.sessionPath = sessionPath;
    this.cachePath = join(sessionPath, "cache.json");
    this.lockPath = join(sessionPath, "cache.lock");
    this.config = config;
  }
  // ── Semantic memory ──────────────────────────────────────────
  async remember(finding) {
    try {
      if (!finding.content?.trim())
        return false;
      const id = this.generateId(finding);
      const vector = await embed(this.extractor, finding.content);
      const metadata = {
        tool: finding.tool,
        agentId: finding.agentId,
        storyId: finding.storyId ?? "",
        filePath: finding.filePath ?? "",
        pattern: finding.pattern ?? "",
        command: finding.command ?? "",
        tags: finding.tags?.join(",") ?? "",
        content: finding.content.slice(0, MAX_CONTENT_CHARS)
      };
      await this.index.upsertItem({ id, vector, metadata });
      return true;
    } catch {
      return false;
    }
  }
  async recall(query, options) {
    try {
      const maxResults = options?.maxResults ?? this.config.defaultMaxResults;
      const minSimilarity = options?.minSimilarity ?? this.config.defaultMinSimilarity;
      const excludeAgent = options?.excludeAgent;
      const filterByTool = options?.filterByTool;
      if (!query?.trim())
        return [];
      const vector = await embed(this.extractor, query);
      const fetchK = Math.min(Math.max(maxResults * 3, 30), 200);
      const results = await this.index.queryItems(vector, fetchK);
      const output = [];
      for (const result of results) {
        if (result.score < minSimilarity)
          continue;
        if (excludeAgent && result.item.metadata.agentId === excludeAgent)
          continue;
        if (filterByTool?.length && !filterByTool.includes(result.item.metadata.tool))
          continue;
        output.push({
          id: result.item.id,
          content: result.item.metadata.content,
          metadata: {
            tool: result.item.metadata.tool,
            agentId: result.item.metadata.agentId,
            storyId: result.item.metadata.storyId,
            filePath: result.item.metadata.filePath,
            pattern: result.item.metadata.pattern,
            command: result.item.metadata.command,
            tags: result.item.metadata.tags
          },
          similarity: result.score
        });
        if (output.length >= maxResults)
          break;
      }
      return output;
    } catch {
      return [];
    }
  }
  async gatherContext(storyId, hints, maxChars = 2e4) {
    const query = hints.join(" ");
    if (!query.trim())
      return null;
    const results = await this.recall(query, {
      maxResults: 20,
      minSimilarity: 0.3,
      excludeAgent: storyId
    });
    if (results.length === 0)
      return null;
    const lines = [
      "## Codebase context (from parallel agents)",
      "",
      "Other agents in this run discovered the following.",
      "Use this directly without re-reading files.",
      ""
    ];
    let totalChars = 0;
    for (const result of results) {
      const header = `[${result.metadata.agentId}] ${result.metadata.tool}${result.metadata.filePath ? ` ${result.metadata.filePath}` : ""} (relevance: ${Math.round(result.similarity * 100)}%)`;
      const entry = `${header}
${result.content}
`;
      if (totalChars + entry.length > maxChars) {
        lines.push("[...truncated...]");
        break;
      }
      lines.push(entry);
      totalChars += entry.length;
    }
    return lines.join("\n");
  }
  // ── File cache ───────────────────────────────────────────────
  // Simple JSON file (not vectorized -- exact key-value lookup).
  // NOTE: Multi-process writes use merge-on-write to reduce data loss.
  // This is best-effort — not ACID. For guaranteed consistency, use SQLite.
  async cacheFile(path, content, agentId) {
    if (content.length > MAX_CACHE_FILE_BYTES)
      return;
    const cache = this.loadCache();
    const existing = cache[path];
    if (!existing || existing.content !== content) {
      cache[path] = { path, content, readByAgent: agentId, timestamp: Date.now() };
      this.evictIfNeeded(cache);
      this.saveCache(cache);
    }
  }
  async getCachedFile(path) {
    const cache = this.loadCache();
    return cache[path]?.content ?? null;
  }
  async hasFile(path) {
    const cache = this.loadCache();
    return path in cache;
  }
  async getCachedPaths() {
    const cache = this.loadCache();
    return Object.keys(cache);
  }
  // ── Stats ────────────────────────────────────────────────────
  async getStats() {
    try {
      const items = await this.index.listItems();
      const tools = /* @__PURE__ */ new Set();
      const agents = /* @__PURE__ */ new Set();
      for (const item of items) {
        tools.add(item.metadata.tool);
        agents.add(item.metadata.agentId);
      }
      const cache = this.loadCache();
      let cacheSizeBytes = 0;
      for (const entry of Object.values(cache)) {
        cacheSizeBytes += entry.content.length;
      }
      return {
        totalFindings: items.length,
        uniqueTools: tools.size,
        uniqueAgents: agents.size,
        toolsList: Array.from(tools),
        agentsList: Array.from(agents),
        cachedFiles: Object.keys(cache).length,
        cacheSizeBytes
      };
    } catch {
      return {
        totalFindings: 0,
        uniqueTools: 0,
        uniqueAgents: 0,
        toolsList: [],
        agentsList: [],
        cachedFiles: 0,
        cacheSizeBytes: 0
      };
    }
  }
  async close() {
    try {
      rmSync(this.lockPath, { force: true });
    } catch {
    }
  }
  // ── Private helpers ──────────────────────────────────────────
  /**
   * Evict oldest cache entries until total size is under MAX_TOTAL_CACHE_BYTES.
   * LRU-style: removes entries with oldest timestamps first.
   */
  evictIfNeeded(cache) {
    let totalBytes = 0;
    for (const entry of Object.values(cache)) {
      totalBytes += entry.content.length;
    }
    if (totalBytes <= MAX_TOTAL_CACHE_BYTES)
      return;
    const entries = Object.entries(cache).sort((a, b) => a[1].timestamp - b[1].timestamp);
    for (const [key, entry] of entries) {
      if (totalBytes <= MAX_TOTAL_CACHE_BYTES)
        break;
      totalBytes -= entry.content.length;
      delete cache[key];
    }
  }
  generateId(finding) {
    const parts = [finding.agentId, finding.tool];
    if (finding.filePath)
      parts.push(finding.filePath);
    else if (finding.pattern)
      parts.push(finding.pattern);
    else if (finding.command)
      parts.push(finding.command);
    else {
      let hash = 0;
      const str = finding.content.slice(0, 100);
      for (let i = 0; i < str.length; i++) {
        hash = (hash << 5) - hash + str.charCodeAt(i) | 0;
      }
      parts.push(Math.abs(hash).toString(36));
    }
    return parts.join(":");
  }
  /**
   * Load cache from disk. Reads are not locked — if another process is
   * mid-write using the atomic rename strategy, we'll either get the old
   * complete version or the new complete version (never a partial write).
   */
  loadCache() {
    try {
      if (existsSync(this.cachePath)) {
        const raw = readFileSync(this.cachePath, "utf-8");
        if (raw.trim()) {
          return JSON.parse(raw);
        }
      }
    } catch {
    }
    return {};
  }
  /**
   * Save cache atomically (write to PID-scoped tmp file, then rename).
   * The rename is atomic on POSIX, so concurrent readers see either the
   * old or new version — never a partial write.
   */
  saveCache(cache) {
    try {
      writeFileSync(this.lockPath, String(process.pid), "utf-8");
      const tmp = this.cachePath + `.${process.pid}.tmp`;
      writeFileSync(tmp, JSON.stringify(cache), "utf-8");
      renameSync(tmp, this.cachePath);
    } catch {
      try {
        writeFileSync(this.cachePath, JSON.stringify(cache), "utf-8");
      } catch {
      }
    } finally {
      try {
        if (existsSync(this.lockPath))
          rmSync(this.lockPath);
      } catch {
      }
    }
  }
};
var NoOpMemoryStore = class {
  async remember(_finding) {
    return false;
  }
  async recall(_query, _options) {
    return [];
  }
  async gatherContext(_storyId, _hints) {
    return null;
  }
  async cacheFile(_path, _content, _agentId) {
  }
  async getCachedFile(_path) {
    return null;
  }
  async hasFile(_path) {
    return false;
  }
  async getCachedPaths() {
    return [];
  }
  async getStats() {
    return {
      totalFindings: 0,
      uniqueTools: 0,
      uniqueAgents: 0,
      toolsList: [],
      agentsList: [],
      cachedFiles: 0,
      cacheSizeBytes: 0
    };
  }
  async close() {
  }
};

// ../baro-orchestrator/scripts/baro-memory.ts
var args = process.argv.slice(2);
var command = args[0];
function getFlag(name) {
  const idx = args.indexOf(`--${name}`);
  if (idx < 0 || idx + 1 >= args.length) return void 0;
  const value = args[idx + 1];
  if (value.startsWith("--")) return void 0;
  return value;
}
function getPositionalArg(index) {
  const val = args[index];
  if (!val || val.startsWith("--")) return void 0;
  return val;
}
async function main() {
  const sessionPath = getFlag("path") || process.env.BARO_MEMORY_PATH;
  if (!sessionPath) {
    console.error(
      "Error: No memory session path found.\n\nSet BARO_MEMORY_PATH environment variable or pass --path <dir>.\nThis is normally set automatically by the baro orchestrator.\n"
    );
    process.exit(1);
  }
  let store = null;
  try {
    store = await createMemoryStore({ sessionPath });
    switch (command) {
      case "query": {
        const query = getPositionalArg(1);
        if (!query) {
          console.error("Usage: baro-memory query <text> [--top N] [--agent id]");
          process.exit(1);
        }
        const topRaw = getFlag("top");
        const top = topRaw ? parseInt(topRaw, 10) : 5;
        if (isNaN(top) || top < 1) {
          console.error("Error: --top must be a positive integer");
          process.exit(1);
        }
        const agent = getFlag("agent");
        const results = await store.recall(query, {
          maxResults: top,
          minSimilarity: 0.3,
          excludeAgent: agent
        });
        if (results.length === 0) {
          console.log("No relevant findings found.");
        } else {
          console.log(`Found ${results.length} relevant findings:
`);
          for (const r of results) {
            console.log(`[${r.metadata.tool}] ${r.metadata.agentId} (similarity: ${r.similarity.toFixed(2)})`);
            if (r.metadata.filePath) console.log(`  File: ${r.metadata.filePath}`);
            console.log(`  ${r.content.slice(0, 200)}${r.content.length > 200 ? "..." : ""}`);
            console.log();
          }
        }
        break;
      }
      case "store": {
        const content = getPositionalArg(1);
        if (!content) {
          console.error("Usage: baro-memory store <content> --tool <tool> [--file <path>] [--agent <id>]");
          process.exit(1);
        }
        const tool = getFlag("tool") || "Bash";
        const file = getFlag("file");
        const agent = getFlag("agent") || "manual";
        const stored = await store.remember({
          tool,
          agentId: agent,
          content,
          filePath: file
        });
        if (stored) {
          console.log(`Stored: ${tool} ${file || ""} from ${agent}`);
        } else {
          console.error("Failed to store finding (empty content or embedding error)");
          process.exit(1);
        }
        break;
      }
      case "cache": {
        const sub = getPositionalArg(1);
        if (sub === "list") {
          const paths = await store.getCachedPaths();
          if (paths.length === 0) {
            console.log("No cached files.");
          } else {
            console.log(`Cached files (${paths.length}):`);
            for (const p of paths) {
              console.log(`  ${p}`);
            }
          }
        } else if (sub === "get") {
          const path = getPositionalArg(2);
          if (!path) {
            console.error("Usage: baro-memory cache get <path>");
            process.exit(1);
          }
          const content = await store.getCachedFile(path);
          if (content) {
            console.log(content);
          } else {
            console.error(`Not cached: ${path}`);
            process.exit(1);
          }
        } else {
          console.error("Usage: baro-memory cache [list|get <path>]");
          process.exit(1);
        }
        break;
      }
      case "stats": {
        const memStats = await store.getStats();
        console.log("Memory Stats:");
        console.log(`  Findings: ${memStats.totalFindings}`);
        console.log(`  Cached files: ${memStats.cachedFiles}`);
        console.log(`  Cache size: ${memStats.cacheSizeBytes} bytes`);
        console.log(`  Tools: ${memStats.toolsList.join(", ") || "(none)"}`);
        console.log(`  Agents: ${memStats.agentsList.join(", ") || "(none)"}`);
        break;
      }
      default:
        console.log("baro-memory - Shared semantic memory for baro agents\n");
        console.log("Commands:");
        console.log("  query <text> [--top N] [--agent id]  Search for relevant findings");
        console.log("  store <content> --tool <tool>         Store a finding");
        console.log("  cache list                            List cached files");
        console.log("  cache get <path>                      Get cached file content");
        console.log("  stats                                 Show memory statistics");
        console.log("");
        console.log("Environment:");
        console.log("  BARO_MEMORY_PATH  Session memory directory (set by orchestrator)");
    }
  } finally {
    if (store) await store.close();
  }
}
main().catch((err) => {
  console.error(`baro-memory error: ${err.message || err}`);
  process.exit(1);
});
//# sourceMappingURL=baro-memory.mjs.map