import {
  discoverMemoryExtensions,
  renderExtensionsBlock,
  resolveExtensionsRoot
} from "./chunk-EJI5XIBB.js";
import {
  countRecallTokenOverlap,
  normalizeRecallTokens
} from "./chunk-DT5TVLJE.js";
import {
  StorageManager
} from "./chunk-DCE6SQLA.js";
import {
  isSemanticConsolidationLlmOperator
} from "./chunk-G7D6GZ5J.js";
import {
  readEnvVar,
  resolveHomeDir
} from "./chunk-MARWOCVP.js";
import {
  log
} from "./chunk-2ODBA7MQ.js";

// src/connectors/codex-materialize-runner.ts
import path2 from "path";
import { existsSync as existsSync2 } from "fs";

// src/connectors/codex-materialize.ts
import {
  createHash
} from "crypto";
import {
  existsSync,
  mkdirSync,
  readdirSync,
  readFileSync,
  renameSync,
  rmSync,
  statSync,
  unlinkSync,
  writeFileSync
} from "fs";
import path from "path";
var MATERIALIZE_VERSION = 1;
var SENTINEL_FILE = ".remnic-managed";
var TMP_DIR = ".remnic-tmp";
var OWNED_FILES = /* @__PURE__ */ new Set([
  "memory_summary.md",
  "MEMORY.md",
  "raw_memories.md"
]);
var ROLLOUT_SUBDIR = "rollout_summaries";
function materializeForNamespace(namespace, options) {
  const logger = options.logger ?? {
    info: (msg) => log.info(`[codex-materialize] ${msg}`),
    warn: (msg) => log.warn(`[codex-materialize] ${msg}`),
    debug: (msg) => log.debug(`[codex-materialize] ${msg}`)
  };
  const codexHome = resolveCodexHome(options.codexHome);
  const memoriesDir = path.join(codexHome, "memories");
  const now = options.now ?? /* @__PURE__ */ new Date();
  const maxSummaryTokens = typeof options.maxSummaryTokens === "number" && options.maxSummaryTokens >= 0 ? options.maxSummaryTokens : 4500;
  const rolloutRetentionDays = typeof options.rolloutRetentionDays === "number" && options.rolloutRetentionDays >= 0 ? options.rolloutRetentionDays : 30;
  const sentinelPath = path.join(memoriesDir, SENTINEL_FILE);
  const existingSentinel = readSentinel(sentinelPath);
  if (!existingSentinel) {
    if (existsSync(memoriesDir)) {
      logger.warn(
        `sentinel ${SENTINEL_FILE} missing in ${memoriesDir}; skipping materialization to preserve hand-edits`
      );
    } else {
      logger.debug?.(
        `skipping materialization \u2014 ${memoriesDir} does not exist (user not opted in)`
      );
    }
    return {
      namespace,
      memoriesDir,
      wrote: false,
      skippedNoSentinel: true,
      skippedIdempotent: false,
      filesWritten: [],
      contentHash: ""
    };
  }
  mkdirSync(memoriesDir, { recursive: true });
  const memories = [...options.memories];
  const rolloutsSupplied = options.rolloutSummaries !== void 0;
  const rolloutSummaries = options.rolloutSummaries ?? [];
  const retainedRollouts = pruneRollouts(rolloutSummaries, rolloutRetentionDays, now);
  const dedupedRollouts = [];
  const seenNames = /* @__PURE__ */ new Map();
  const parseTs = (value) => {
    if (!value) return Number.NEGATIVE_INFINITY;
    const parsed = Date.parse(value);
    return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
  };
  for (const r of retainedRollouts) {
    const name = `${sanitizeSlug(r.slug)}.md`;
    const existingIdx = seenNames.get(name);
    if (existingIdx === void 0) {
      seenNames.set(name, dedupedRollouts.length);
      dedupedRollouts.push(r);
      continue;
    }
    const existing = dedupedRollouts[existingIdx];
    if (parseTs(r.updatedAt) > parseTs(existing.updatedAt)) {
      dedupedRollouts[existingIdx] = r;
    }
  }
  const memorySummary = renderMemorySummary({
    namespace,
    memories,
    rolloutSummaries: dedupedRollouts,
    maxTokens: maxSummaryTokens
  });
  const memoryMd = renderMemoryMd({
    namespace,
    memories,
    rolloutSummaries: dedupedRollouts
  });
  const validation = validateMemoryMd(memoryMd);
  if (!validation.valid) {
    const reason = validation.errors.join("; ");
    logger.warn(`MEMORY.md failed schema validation: ${reason}`);
    throw new Error(`codex-materialize: MEMORY.md schema validation failed: ${reason}`);
  }
  const rawMemories = renderRawMemories({ memories });
  const rolloutFiles = dedupedRollouts.map((r) => ({
    name: `${sanitizeSlug(r.slug)}.md`,
    body: renderRolloutSummary(r)
  }));
  const hash = computeContentHash({
    namespace,
    memorySummary,
    memoryMd,
    rawMemories,
    rolloutFiles
  });
  if (existingSentinel.content_hash === hash) {
    const requiredFiles = [
      path.join(memoriesDir, "memory_summary.md"),
      path.join(memoriesDir, "MEMORY.md"),
      path.join(memoriesDir, "raw_memories.md"),
      ...rolloutFiles.map((r) => path.join(memoriesDir, ROLLOUT_SUBDIR, r.name))
    ];
    const allPresent = requiredFiles.every((f) => existsSync(f));
    if (allPresent) {
      logger.debug?.(`no-op materialization for namespace=${namespace} (hash unchanged)`);
      return {
        namespace,
        memoriesDir,
        wrote: false,
        skippedNoSentinel: false,
        skippedIdempotent: true,
        filesWritten: [],
        contentHash: hash
      };
    }
    logger.debug?.(
      `hash unchanged for namespace=${namespace} but managed file missing \u2014 forcing rewrite`
    );
  }
  const runTag = `${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
  const tmpDir = path.join(memoriesDir, `${TMP_DIR}-${runTag}`);
  const TMP_STALE_MS = 60 * 60 * 1e3;
  const wallClockMs = Date.now();
  try {
    for (const entry of readdirSync(memoriesDir, { withFileTypes: true })) {
      if (!entry.isDirectory()) continue;
      if (!entry.name.startsWith(TMP_DIR)) continue;
      const stalePath = path.join(memoriesDir, entry.name);
      try {
        const stat = statSync(stalePath);
        if (wallClockMs - stat.mtimeMs < TMP_STALE_MS) continue;
        rmSync(stalePath, { recursive: true, force: true });
      } catch {
      }
    }
  } catch {
  }
  mkdirSync(tmpDir, { recursive: true });
  mkdirSync(path.join(tmpDir, ROLLOUT_SUBDIR), { recursive: true });
  const filesWritten = [];
  writeFileSync(path.join(tmpDir, "memory_summary.md"), memorySummary);
  filesWritten.push("memory_summary.md");
  writeFileSync(path.join(tmpDir, "MEMORY.md"), memoryMd);
  filesWritten.push("MEMORY.md");
  writeFileSync(path.join(tmpDir, "raw_memories.md"), rawMemories);
  filesWritten.push("raw_memories.md");
  for (const rollout of rolloutFiles) {
    writeFileSync(path.join(tmpDir, ROLLOUT_SUBDIR, rollout.name), rollout.body);
    filesWritten.push(path.join(ROLLOUT_SUBDIR, rollout.name));
  }
  for (const rel of ["memory_summary.md", "MEMORY.md", "raw_memories.md"]) {
    const src = path.join(tmpDir, rel);
    const dest = path.join(memoriesDir, rel);
    renameSync(src, dest);
  }
  const destRolloutsDir = path.join(memoriesDir, ROLLOUT_SUBDIR);
  mkdirSync(destRolloutsDir, { recursive: true });
  if (rolloutsSupplied) {
    const retainedRolloutNames = new Set(rolloutFiles.map((r) => r.name));
    try {
      for (const entry of readdirSync(destRolloutsDir, { withFileTypes: true })) {
        if (!entry.isFile()) continue;
        if (!entry.name.endsWith(".md")) continue;
        if (retainedRolloutNames.has(entry.name)) continue;
        try {
          unlinkSync(path.join(destRolloutsDir, entry.name));
        } catch {
        }
      }
    } catch {
    }
  }
  for (const rollout of rolloutFiles) {
    const src = path.join(tmpDir, ROLLOUT_SUBDIR, rollout.name);
    const dest = path.join(destRolloutsDir, rollout.name);
    renameSync(src, dest);
  }
  const sentinel = {
    version: MATERIALIZE_VERSION,
    namespace,
    updated_at: now.toISOString(),
    content_hash: hash
  };
  writeFileSync(sentinelPath, `${JSON.stringify(sentinel, null, 2)}
`);
  try {
    rmSync(tmpDir, { recursive: true, force: true });
  } catch {
  }
  logger.info(
    `materialized namespace=${namespace} files=${filesWritten.length} hash=${hash.slice(0, 12)}`
  );
  return {
    namespace,
    memoriesDir,
    wrote: true,
    skippedNoSentinel: false,
    skippedIdempotent: false,
    filesWritten,
    contentHash: hash
  };
}
function ensureSentinel(memoriesDir, namespace, now = /* @__PURE__ */ new Date()) {
  mkdirSync(memoriesDir, { recursive: true });
  const sentinelPath = path.join(memoriesDir, SENTINEL_FILE);
  if (existsSync(sentinelPath)) return;
  const sentinel = {
    version: MATERIALIZE_VERSION,
    namespace,
    updated_at: now.toISOString(),
    content_hash: ""
  };
  writeFileSync(sentinelPath, `${JSON.stringify(sentinel, null, 2)}
`);
}
function renderMemorySummary(ctx) {
  const lines = [];
  lines.push("# Memory Summary");
  lines.push("");
  lines.push(`_namespace: ${ctx.namespace}_`);
  lines.push(`_source: remnic_`);
  lines.push("");
  const highValue = selectSummaryMemories(ctx.memories, 12);
  if (highValue.length > 0) {
    lines.push("## Top memories");
    lines.push("");
    for (const mem of highValue) {
      lines.push(`- ${oneLineSummary(mem)}`);
    }
    lines.push("");
  }
  if (ctx.rolloutSummaries.length > 0) {
    lines.push("## Recent rollouts");
    lines.push("");
    const sorted = [...ctx.rolloutSummaries].sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "")).slice(0, 5);
    for (const r of sorted) {
      const when = r.updatedAt ? ` (${r.updatedAt})` : "";
      lines.push(`- ${r.slug}${when}`);
    }
    lines.push("");
  }
  const full = lines.join("\n").replace(/\n+$/u, "\n");
  return truncateToTokenBudget(full, ctx.maxTokens);
}
function renderMemoryMd(ctx) {
  const lines = [];
  lines.push(`# Task Group: ${ctx.namespace}`);
  lines.push(`scope: ${ctx.namespace}`);
  lines.push(`applies_to: cwd=*; reuse_rule=namespace-match`);
  lines.push("");
  const byCategory = groupMemoriesByCategory(ctx.memories);
  let taskIndex = 1;
  if (byCategory.size === 0) {
    lines.push(`## Task ${taskIndex}: baseline \u2014 no memories yet`);
    lines.push("");
    lines.push("### rollout_summary_files");
    for (const r of ctx.rolloutSummaries) {
      lines.push(
        `- rollout_summaries/${sanitizeSlug(r.slug)}.md (cwd=${r.cwd ?? "*"}, rollout_path=${r.rolloutPath ?? ""}, updated_at=${r.updatedAt ?? ""}, thread_id=${r.threadId ?? ""})`
      );
    }
    if (ctx.rolloutSummaries.length === 0) {
      lines.push("- (none)");
    }
    lines.push("");
    lines.push("### keywords");
    lines.push(`- ${ctx.namespace}`);
    lines.push("");
    taskIndex += 1;
  } else {
    for (const [category, mems] of byCategory) {
      lines.push(`## Task ${taskIndex}: ${category} memories, outcome=surface-to-codex`);
      lines.push("");
      lines.push("### rollout_summary_files");
      const relevantRollouts = ctx.rolloutSummaries.slice(0, 5);
      if (relevantRollouts.length === 0) {
        lines.push("- (none)");
      } else {
        for (const r of relevantRollouts) {
          lines.push(
            `- rollout_summaries/${sanitizeSlug(r.slug)}.md (cwd=${r.cwd ?? "*"}, rollout_path=${r.rolloutPath ?? ""}, updated_at=${r.updatedAt ?? ""}, thread_id=${r.threadId ?? ""})`
          );
        }
      }
      lines.push("");
      lines.push("### keywords");
      const keywords = collectKeywords(mems, category, ctx.namespace);
      lines.push(`- ${keywords.join(", ")}`);
      lines.push("");
      taskIndex += 1;
    }
  }
  lines.push("## User preferences");
  const prefs = pickCategory(ctx.memories, ["preference"]);
  if (prefs.length === 0) {
    lines.push("- (none recorded)");
  } else {
    for (const pref of prefs.slice(0, 20)) {
      lines.push(`- ${oneLineSummary(pref)}`);
    }
  }
  lines.push("");
  lines.push("## Reusable knowledge");
  const knowledge = pickCategory(ctx.memories, ["fact", "decision", "principle", "rule", "skill"]);
  if (knowledge.length === 0) {
    lines.push("- (none recorded)");
  } else {
    for (const mem of knowledge.slice(0, 30)) {
      lines.push(`- ${oneLineSummary(mem)}`);
    }
  }
  lines.push("");
  lines.push("## Failures and how to do differently");
  const corrections = pickCategory(ctx.memories, ["correction"]);
  if (corrections.length === 0) {
    lines.push("- (none recorded)");
  } else {
    for (const mem of corrections.slice(0, 20)) {
      lines.push(`- ${oneLineSummary(mem)}`);
    }
  }
  lines.push("");
  return lines.join("\n");
}
function renderRawMemories(ctx) {
  const sorted = [...ctx.memories].sort((a, b) => {
    const aUpdated = a.frontmatter.updated ?? a.frontmatter.created ?? "";
    const bUpdated = b.frontmatter.updated ?? b.frontmatter.created ?? "";
    return bUpdated.localeCompare(aUpdated);
  });
  const lines = ["# Raw Memories", "", "_source: remnic \u2014 latest first_", ""];
  for (const mem of sorted) {
    const fm = mem.frontmatter;
    const id = fm.id ?? "unknown";
    const category = fm.category ?? "unknown";
    const updated = fm.updated ?? fm.created ?? "";
    lines.push(`## ${id} (${category}, updated=${updated})`);
    lines.push("");
    lines.push(mem.content.trim());
    lines.push("");
  }
  return lines.join("\n");
}
function renderRolloutSummary(input) {
  const lines = [];
  lines.push(`# Rollout Summary: ${input.slug}`);
  lines.push("");
  const meta = [];
  if (input.cwd) meta.push(`cwd=${input.cwd}`);
  if (input.rolloutPath) meta.push(`rollout_path=${input.rolloutPath}`);
  if (input.updatedAt) meta.push(`updated_at=${input.updatedAt}`);
  if (input.threadId) meta.push(`thread_id=${input.threadId}`);
  if (meta.length > 0) {
    lines.push(`_${meta.join("; ")}_`);
    lines.push("");
  }
  if (input.keywords && input.keywords.length > 0) {
    lines.push(`**keywords:** ${input.keywords.join(", ")}`);
    lines.push("");
  }
  lines.push(input.body.trim());
  lines.push("");
  return lines.join("\n");
}
function validateMemoryMd(content) {
  const errors = [];
  const lines = content.split(/\r?\n/u);
  const taskGroupIndex = lines.findIndex((l) => /^#\s+Task Group:\s+\S+/u.test(l));
  if (taskGroupIndex === -1) {
    errors.push("missing `# Task Group:` header");
  } else {
    const tail = lines.slice(taskGroupIndex + 1, taskGroupIndex + 5);
    if (!tail.some((l) => /^scope:\s*\S+/u.test(l))) {
      errors.push("missing `scope:` line under Task Group header");
    }
    if (!tail.some((l) => /^applies_to:\s*\S+/u.test(l))) {
      errors.push("missing `applies_to:` line under Task Group header");
    }
  }
  const taskHeaders = lines.filter((l) => /^##\s+Task\s+\d+:/u.test(l));
  if (taskHeaders.length === 0) {
    errors.push("at least one `## Task N:` section is required");
  }
  const sectionRegex = /^##\s+/u;
  for (let i = 0; i < lines.length; i++) {
    if (!/^##\s+Task\s+\d+:/u.test(lines[i])) continue;
    let hasRollout = false;
    let hasKeywords = false;
    for (let j = i + 1; j < lines.length; j++) {
      if (sectionRegex.test(lines[j])) break;
      if (/^###\s+rollout_summary_files\s*$/u.test(lines[j])) hasRollout = true;
      if (/^###\s+keywords\s*$/u.test(lines[j])) hasKeywords = true;
    }
    if (!hasRollout) errors.push(`task block at line ${i + 1} missing \`### rollout_summary_files\``);
    if (!hasKeywords) errors.push(`task block at line ${i + 1} missing \`### keywords\``);
  }
  const requiredSections = [
    /^##\s+User preferences\s*$/u,
    /^##\s+Reusable knowledge\s*$/u,
    /^##\s+Failures and how to do differently\s*$/u
  ];
  for (const re of requiredSections) {
    if (!lines.some((l) => re.test(l))) {
      errors.push(`missing required section: ${re.source}`);
    }
  }
  return { valid: errors.length === 0, errors };
}
function resolveCodexHome(override) {
  if (override && override.trim().length > 0) return override;
  const fromEnv = readEnvVar("CODEX_HOME");
  if (fromEnv && fromEnv.trim().length > 0) return fromEnv;
  return path.join(resolveHomeDir(), ".codex");
}
function readSentinel(sentinelPath) {
  if (!existsSync(sentinelPath)) return null;
  try {
    const raw = readFileSync(sentinelPath, "utf-8");
    const parsed = JSON.parse(raw);
    if (typeof parsed !== "object" || parsed === null) return null;
    return {
      version: typeof parsed.version === "number" ? parsed.version : MATERIALIZE_VERSION,
      namespace: typeof parsed.namespace === "string" ? parsed.namespace : "",
      updated_at: typeof parsed.updated_at === "string" ? parsed.updated_at : "",
      content_hash: typeof parsed.content_hash === "string" ? parsed.content_hash : ""
    };
  } catch {
    return null;
  }
}
function selectSummaryMemories(memories, limit) {
  const scored = memories.filter((m) => !m.frontmatter.status || m.frontmatter.status === "active").map((m) => {
    const confidence = typeof m.frontmatter.confidence === "number" ? m.frontmatter.confidence : 0;
    const importance = typeof m.frontmatter.importance === "object" && m.frontmatter.importance !== null && typeof m.frontmatter.importance.score === "number" ? m.frontmatter.importance.score ?? 0 : 0;
    const updated = m.frontmatter.updated ?? m.frontmatter.created ?? "";
    return { memory: m, score: importance * 2 + confidence, updated };
  });
  scored.sort((a, b) => {
    if (b.score !== a.score) return b.score - a.score;
    return b.updated.localeCompare(a.updated);
  });
  return scored.slice(0, limit).map((s) => s.memory);
}
function oneLineSummary(memory) {
  const raw = memory.content.replace(/\s+/gu, " ").trim();
  if (raw.length <= 160) return raw;
  return `${raw.slice(0, 157)}...`;
}
function groupMemoriesByCategory(memories) {
  const map = /* @__PURE__ */ new Map();
  for (const memory of memories) {
    if (memory.frontmatter.status && memory.frontmatter.status !== "active") continue;
    const category = memory.frontmatter.category ?? "unknown";
    const list = map.get(category) ?? [];
    list.push(memory);
    map.set(category, list);
  }
  return map;
}
function pickCategory(memories, categories) {
  const allowed = new Set(categories);
  return memories.filter(
    (m) => (!m.frontmatter.status || m.frontmatter.status === "active") && allowed.has(m.frontmatter.category ?? "")
  );
}
function collectKeywords(memories, category, namespace) {
  const keywords = /* @__PURE__ */ new Set();
  keywords.add(category);
  keywords.add(namespace);
  for (const mem of memories.slice(0, 10)) {
    for (const tag of mem.frontmatter.tags ?? []) {
      if (typeof tag === "string" && tag.trim().length > 0) keywords.add(tag.trim());
    }
  }
  return [...keywords].slice(0, 16);
}
function pruneRollouts(rollouts, retentionDays, now) {
  if (retentionDays < 0) return rollouts;
  const cutoffMs = now.getTime() - retentionDays * 24 * 60 * 60 * 1e3;
  return rollouts.filter((r) => {
    if (!r.updatedAt) return true;
    const t = Date.parse(r.updatedAt);
    if (!Number.isFinite(t)) return true;
    return t >= cutoffMs;
  });
}
function sanitizeSlug(slug) {
  return slug.toLowerCase().replace(/[^a-z0-9._-]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 96) || "rollout";
}
function approximateTokenCount(text) {
  const trimmed = text.trim();
  if (trimmed.length === 0) return 0;
  return trimmed.split(/\s+/u).length;
}
function truncateToTokenBudget(text, maxTokens) {
  if (maxTokens <= 0) return "";
  if (approximateTokenCount(text) <= maxTokens) return text;
  const lineMarker = "_[truncated for summary budget]_";
  const tailMarker = "[truncated]";
  const lineMarkerTokens = approximateTokenCount(lineMarker);
  const tailMarkerTokens = approximateTokenCount(tailMarker);
  const lines = text.split(/\r?\n/u);
  const lineBudget = Math.max(0, maxTokens - lineMarkerTokens);
  while (lines.length > 0 && approximateTokenCount(lines.join("\n")) > lineBudget) {
    lines.pop();
  }
  lines.push(lineMarker);
  let result = lines.join("\n");
  if (approximateTokenCount(result) > maxTokens) {
    const tokens = result.split(/\s+/u);
    const keep = Math.max(0, maxTokens - tailMarkerTokens);
    result = keep > 0 ? `${tokens.slice(0, keep).join(" ")} ${tailMarker}` : tailMarker;
  }
  return result;
}
function computeContentHash(input) {
  const hash = createHash("sha256");
  hash.update(`v${MATERIALIZE_VERSION}
`);
  hash.update(`namespace=${input.namespace}
`);
  hash.update("---memory_summary---\n");
  hash.update(input.memorySummary);
  hash.update("\n---memory_md---\n");
  hash.update(input.memoryMd);
  hash.update("\n---raw_memories---\n");
  hash.update(input.rawMemories);
  const sortedRollouts = [...input.rolloutFiles].sort((a, b) => a.name.localeCompare(b.name));
  for (const r of sortedRollouts) {
    hash.update(`
---rollout:${r.name}---
`);
    hash.update(r.body);
  }
  return hash.digest("hex");
}
function describeMemoriesDir(memoriesDir) {
  if (!existsSync(memoriesDir)) return null;
  const sentinelPath = path.join(memoriesDir, SENTINEL_FILE);
  const sentinel = readSentinel(sentinelPath);
  const files = [];
  for (const entry of readdirSync(memoriesDir, { withFileTypes: true })) {
    if (entry.isFile() && OWNED_FILES.has(entry.name)) files.push(entry.name);
  }
  const rolloutsDir = path.join(memoriesDir, ROLLOUT_SUBDIR);
  if (existsSync(rolloutsDir)) {
    try {
      for (const entry of readdirSync(rolloutsDir, { withFileTypes: true })) {
        if (entry.isFile() && entry.name.endsWith(".md")) {
          files.push(path.join(ROLLOUT_SUBDIR, entry.name));
        }
      }
    } catch {
    }
  }
  return {
    exists: true,
    hasSentinel: sentinel !== null,
    files: files.sort(),
    sentinel
  };
}

// src/connectors/codex-materialize-runner.ts
async function runCodexMaterialize(options) {
  const cfg = options.config;
  if (!cfg.codexMaterializeMemories) {
    log.debug(`[codex-materialize] skipped \u2014 codexMaterializeMemories=false`);
    return null;
  }
  if (options.reason === "session_end" && cfg.codexMaterializeOnSessionEnd === false) {
    log.debug(
      `[codex-materialize] skipped \u2014 session-end disabled via codexMaterializeOnSessionEnd=false`
    );
    return null;
  }
  const namespace = resolveNamespace(options.namespace, cfg);
  const memoryDir = options.memoryDir ?? cfg.memoryDir;
  if (!memoryDir) {
    log.warn(`[codex-materialize] skipped \u2014 no memoryDir available`);
    return null;
  }
  let memories;
  if (options.memories) {
    memories = options.memories;
  } else {
    const nsDir = resolveNamespaceDir(memoryDir, namespace, cfg);
    const storage = new StorageManager(nsDir);
    try {
      memories = await storage.readAllMemories();
    } catch (error) {
      log.warn(
        `[codex-materialize] skipped \u2014 failed to read memories from ${nsDir}: ${error instanceof Error ? error.message : String(error)}`
      );
      return null;
    }
  }
  const result = materializeForNamespace(namespace, {
    memories,
    codexHome: options.codexHome,
    maxSummaryTokens: cfg.codexMaterializeMaxSummaryTokens,
    rolloutRetentionDays: cfg.codexMaterializeRolloutRetentionDays,
    rolloutSummaries: options.rolloutSummaries,
    now: options.now
  });
  if (options.reason) {
    log.debug(
      `[codex-materialize] ran reason=${options.reason} wrote=${result.wrote} files=${result.filesWritten.length}`
    );
  }
  return result;
}
async function runPostConsolidationMaterialize(logPrefix, options) {
  if (!options.config.codexMaterializeMemories) return null;
  if (!options.config.codexMaterializeOnConsolidation) return null;
  try {
    return await runCodexMaterialize({
      config: options.config,
      namespace: options.namespace,
      memories: options.memories,
      memoryDir: options.memoryDir,
      codexHome: options.codexHome,
      rolloutSummaries: options.rolloutSummaries,
      now: options.now,
      reason: "consolidation"
    });
  } catch (error) {
    log.warn(
      `${logPrefix} Codex materialize post-hook failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`
    );
    return null;
  }
}
function resolveNamespace(override, cfg) {
  const requested = (override ?? cfg.codexMaterializeNamespace ?? "auto").trim();
  if (requested.length === 0 || requested === "auto") {
    return cfg.defaultNamespace && cfg.defaultNamespace.length > 0 ? cfg.defaultNamespace : "default";
  }
  return requested;
}
function resolveNamespaceDir(memoryDir, namespace, cfg) {
  if (!cfg.namespacesEnabled) return memoryDir;
  const ns = namespace || cfg.defaultNamespace || "default";
  const namespacedRoot = path2.join(memoryDir, "namespaces", ns);
  if (ns === cfg.defaultNamespace) {
    return existsSync2(namespacedRoot) ? namespacedRoot : memoryDir;
  }
  return namespacedRoot;
}

// src/semantic-consolidation.ts
function findSimilarClusters(memories, config) {
  const excluded = new Set(config.excludeCategories);
  const byCategory = /* @__PURE__ */ new Map();
  for (const m of memories) {
    const cat = m.frontmatter.category;
    if (excluded.has(cat)) continue;
    if (m.frontmatter.status && m.frontmatter.status !== "active") continue;
    const list = byCategory.get(cat) ?? [];
    list.push(m);
    byCategory.set(cat, list);
  }
  const clusters = [];
  let totalCandidates = 0;
  for (const [category, mems] of byCategory) {
    if (totalCandidates >= config.maxPerRun) break;
    const tokenized = mems.map((m) => ({
      memory: m,
      tokens: new Set(normalizeRecallTokens(m.content, []))
    }));
    const clustered = /* @__PURE__ */ new Set();
    for (let i = 0; i < tokenized.length && totalCandidates < config.maxPerRun; i++) {
      if (clustered.has(tokenized[i].memory.frontmatter.id)) continue;
      const cluster = [tokenized[i].memory];
      let totalOverlap = 0;
      let comparisons = 0;
      for (let j = i + 1; j < tokenized.length; j++) {
        if (clustered.has(tokenized[j].memory.frontmatter.id)) continue;
        const aTokens = tokenized[i].tokens;
        const bTokens = tokenized[j].tokens;
        if (aTokens.size === 0 || bTokens.size === 0) continue;
        const overlap = countRecallTokenOverlap(aTokens, [...bTokens].join(" "));
        const maxTokens = Math.max(aTokens.size, bTokens.size);
        const score = maxTokens > 0 ? overlap / maxTokens : 0;
        if (score >= config.threshold) {
          cluster.push(tokenized[j].memory);
          totalOverlap += score;
          comparisons++;
          if (totalCandidates + cluster.length >= config.maxPerRun) break;
        }
      }
      if (cluster.length >= config.minClusterSize) {
        for (const m of cluster) clustered.add(m.frontmatter.id);
        clusters.push({
          category,
          memories: cluster,
          overlapScore: comparisons > 0 ? totalOverlap / comparisons : 0
        });
        totalCandidates += cluster.length;
      }
    }
  }
  return clusters;
}
function buildConsolidationPrompt(cluster) {
  const memoryTexts = cluster.memories.map(
    (m, i) => `Memory ${i + 1} (${m.frontmatter.id}, created ${m.frontmatter.created}):
${m.content}`
  ).join("\n\n");
  return `You are a memory consolidation system. The following ${cluster.memories.length} memories in the "${cluster.category}" category contain overlapping information.

Synthesize them into ONE canonical memory that:
1. Preserves ALL unique information from every source memory
2. Removes redundancy and repetition
3. Uses clear, concise language
4. Maintains the same category and tone
5. Does NOT add information that isn't in the sources

${memoryTexts}

Write ONLY the consolidated memory content (no metadata, no explanation, no preamble):`;
}
function parseConsolidationResponse(response) {
  return response.trim();
}
function chooseConsolidationOperator(cluster) {
  if (cluster.memories.length <= 1) return "update";
  return "merge";
}
function buildOperatorAwareConsolidationPrompt(cluster) {
  const memoryTexts = cluster.memories.map(
    (m, i) => `Memory ${i + 1} (${m.frontmatter.id}, created ${m.frontmatter.created}):
${m.content}`
  ).join("\n\n");
  return `You are a memory consolidation system.  The following ${cluster.memories.length} memories in the "${cluster.category}" category contain overlapping information.

Pick exactly ONE consolidation operator for this cluster and return a JSON object.

Operator vocabulary:
  - "merge"  \u2014 multiple distinct source memories overlap and should be collapsed into one canonical memory (most common).
  - "update" \u2014 one source memory carries a stale value that a newer source supersedes within the same logical fact.
  - "split"  \u2014 a single logical source really encodes multiple distinct facts that should be separated (rare; if you pick split, still emit ONE canonical body \u2014 the write path will chunk it later).

Output JSON ONLY, no prose before or after.  The "operator" key MUST be set to exactly one of the three strings "merge", "update", or "split" \u2014 never a pipe-separated placeholder like "merge|update|split".  Example shape:
  {
    "operator": "merge",
    "output": "<the canonical memory text>"
  }

The "output" value must:
1. Preserve ALL unique information from every source memory
2. Remove redundancy and repetition
3. Use clear, concise language
4. Match the "${cluster.category}" category and tone
5. NOT add information that isn't in the sources

${memoryTexts}

Return ONLY the JSON object:`;
}
function parseOperatorAwareConsolidationResponse(response, cluster) {
  const fallback = {
    operator: chooseConsolidationOperator(cluster),
    output: response.trim()
  };
  const trimmed = response.trim();
  if (trimmed.length === 0) return fallback;
  const fenced = /^```(?:json)?\s*([\s\S]*?)```\s*$/u.exec(trimmed);
  const payload = fenced ? fenced[1].trim() : trimmed;
  const parsed = findLastJsonObjectWithOperator(payload);
  if (parsed === void 0) return fallback;
  if (typeof parsed !== "object" || parsed === null) return fallback;
  const obj = parsed;
  const rawOperator = typeof obj.operator === "string" ? obj.operator.trim().toLowerCase() : "";
  const rawOutput = typeof obj.output === "string" ? obj.output : "";
  const operator = isSemanticConsolidationLlmOperator(rawOperator) ? rawOperator : chooseConsolidationOperator(cluster);
  const output = rawOutput.trim().length > 0 ? rawOutput.trim() : response.trim();
  return { operator, output };
}
function findLastJsonObjectWithOperator(text) {
  let searchFrom = 0;
  let last = void 0;
  while (searchFrom < text.length) {
    const start = text.indexOf("{", searchFrom);
    if (start < 0) return last;
    let depth = 0;
    let inString = false;
    let escape = false;
    let closed = false;
    let endIdx = -1;
    for (let i = start; i < text.length; i++) {
      const ch = text[i];
      if (inString) {
        if (escape) {
          escape = false;
        } else if (ch === "\\") {
          escape = true;
        } else if (ch === '"') {
          inString = false;
        }
        continue;
      }
      if (ch === '"') {
        inString = true;
      } else if (ch === "{") {
        depth += 1;
      } else if (ch === "}") {
        depth -= 1;
        if (depth === 0) {
          closed = true;
          endIdx = i;
          break;
        }
      }
    }
    if (!closed) return last;
    const slice = text.slice(start, endIdx + 1);
    try {
      const parsed = JSON.parse(slice);
      if (typeof parsed === "object" && parsed !== null && "operator" in parsed) {
        last = parsed;
      }
    } catch {
    }
    searchFrom = endIdx + 1;
  }
  return last;
}
async function buildExtensionsBlockForConsolidation(config) {
  if (!config.memoryExtensionsEnabled) return "";
  const root = resolveExtensionsRoot(config);
  const extensions = await discoverMemoryExtensions(root, log);
  if (extensions.length === 0) return "";
  return renderExtensionsBlock(extensions);
}
async function materializeAfterSemanticConsolidation(options) {
  return runPostConsolidationMaterialize("[semantic-consolidation]", options);
}

export {
  MATERIALIZE_VERSION,
  SENTINEL_FILE,
  materializeForNamespace,
  ensureSentinel,
  describeMemoriesDir,
  runCodexMaterialize,
  runPostConsolidationMaterialize,
  findSimilarClusters,
  buildConsolidationPrompt,
  parseConsolidationResponse,
  chooseConsolidationOperator,
  buildOperatorAwareConsolidationPrompt,
  parseOperatorAwareConsolidationResponse,
  buildExtensionsBlockForConsolidation,
  materializeAfterSemanticConsolidation
};
//# sourceMappingURL=chunk-55FXRRSJ.js.map