import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
import {
  ARTIFACT_TYPES,
  CONVERSATION_HEADING,
  VaultWriter,
  bareSessionId,
  buildClassificationPrompt,
  buildExtractionPrompt,
  buildSummaryPrompt,
  buildTitlePrompt,
  callout,
  extractJson,
  extractSection,
  formatNoteForPrompt,
  formatNotesForPrompt,
  formatSporeBody,
  indexNote,
  isActiveSpore,
  loadPrompt,
  rebuildIndex,
  require_gray_matter,
  sessionNoteId,
  stripReasoningTokens,
  supersedeSpore,
  supersededIdsSchema
} from "./chunk-ALBVNGCF.js";
import {
  generateEmbedding
} from "./chunk-RGVBGTD6.js";
import {
  stripFrontmatter
} from "./chunk-RY76WEN3.js";
import {
  initFts
} from "./chunk-6FQISQNA.js";
import {
  external_exports,
  require_dist
} from "./chunk-JSK7L46L.js";
import {
  AgentRegistry,
  claudeCodeAdapter,
  createPerProjectAdapter
} from "./chunk-RNWALAFP.js";
import {
  AI_RESPONSE_PREVIEW_CHARS,
  CHARS_PER_TOKEN,
  COMMAND_PREVIEW_CHARS,
  CURATION_CLUSTER_SIMILARITY,
  DIGEST_LLM_REQUEST_TIMEOUT_MS,
  DIGEST_SUBSTRATE_TYPE_WEIGHTS,
  DIGEST_TIER_MIN_CONTEXT,
  EMBEDDING_INPUT_LIMIT,
  LLM_REASONING_MODE,
  PROMPT_PREVIEW_CHARS,
  SUPERSESSION_MAX_TOKENS,
  estimateTokens
} from "./chunk-WBLTISAK.js";
import {
  __toESM
} from "./chunk-PZUWP5VK.js";

// src/daemon/digest.ts
var import_yaml = __toESM(require_dist(), 1);
import fs2 from "fs";
import path2 from "path";
import crypto from "crypto";

// src/daemon/trace.ts
import fs from "fs";
import path from "path";
function readLastTimestamp(filePath) {
  let content;
  try {
    content = fs.readFileSync(filePath, "utf-8").trim();
  } catch {
    return null;
  }
  if (!content) return null;
  const lines = content.split("\n");
  const lastLine = lines[lines.length - 1];
  try {
    const record = JSON.parse(lastLine);
    return record.timestamp ?? null;
  } catch {
    return null;
  }
}
function appendTraceRecord(filePath, record) {
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
  fs.appendFileSync(filePath, JSON.stringify(record) + "\n", "utf-8");
}

// src/daemon/digest.ts
var PREVIOUS_EXTRACT_OVERHEAD_TOKENS = 50;
var CONTEXT_SAFETY_MARGIN = 0.7;
var EXTRACT_TYPE = "extract";
var DigestEngine = class {
  vaultDir;
  index;
  llm;
  config;
  log;
  lastCycleTimestampCache = void 0;
  cycleInProgress = false;
  /** Hooks that run before each digest cycle (e.g., consolidation). */
  prePassHooks = [];
  /** Hooks that run after each successful digest cycle. */
  postPassHooks = [];
  constructor(engineConfig) {
    this.vaultDir = engineConfig.vaultDir;
    this.index = engineConfig.index;
    this.llm = engineConfig.llmProvider;
    this.config = engineConfig.config;
    this.log = engineConfig.log ?? (() => {
    });
  }
  /** Register a hook that runs before each digest cycle. Best-effort — errors are logged, not thrown. */
  registerPrePass(name, fn) {
    this.prePassHooks.push({ name, fn });
  }
  /** Register a hook that runs after each successful digest cycle. Best-effort — errors are logged, not thrown. */
  registerPostPass(name, fn) {
    this.postPassHooks.push({ name, fn });
  }
  /**
   * Query index for recent vault notes to feed into the digest.
   * Filters out extract notes (our own output) and caps at max_notes_per_cycle.
   */
  discoverSubstrate(lastCycleTimestamp) {
    const maxNotes = this.config.digest.substrate.max_notes_per_cycle;
    const notes = lastCycleTimestamp ? this.index.query({ updatedSince: lastCycleTimestamp, limit: maxNotes }) : this.index.query({ limit: maxNotes });
    const filtered = notes.filter((n) => n.type !== EXTRACT_TYPE).filter((n) => {
      if (n.type !== "spore") return true;
      const status = n.frontmatter.status;
      return !status || status === "active";
    });
    filtered.sort((a, b) => {
      const weightA = DIGEST_SUBSTRATE_TYPE_WEIGHTS[a.type] ?? 0;
      const weightB = DIGEST_SUBSTRATE_TYPE_WEIGHTS[b.type] ?? 0;
      if (weightB !== weightA) return weightB - weightA;
      return b.created.localeCompare(a.created);
    });
    return filtered.slice(0, maxNotes);
  }
  /**
   * Filter configured tiers by the context window available.
   * Only tiers whose minimum context requirement is met are eligible.
   */
  getEligibleTiers() {
    const contextWindow = this.config.digest.intelligence.context_window;
    return this.config.digest.tiers.filter((tier) => {
      const minContext = DIGEST_TIER_MIN_CONTEXT[tier];
      return minContext !== void 0 && minContext <= contextWindow;
    });
  }
  /**
   * Format notes compactly for inclusion in the digest prompt.
   * Stops adding notes once the token budget is exceeded.
   */
  formatSubstrate(notes, tokenBudget) {
    const charBudget = tokenBudget * CHARS_PER_TOKEN;
    const parts = [];
    let usedChars = 0;
    for (const note of notes) {
      const entry = `### [${note.type}] ${note.id} \u2014 "${note.title}"
${note.content}`;
      if (usedChars + entry.length > charBudget && parts.length > 0) break;
      parts.push(entry);
      usedChars += entry.length;
    }
    return parts.join("\n\n");
  }
  /**
   * Read a previously generated extract for a given tier.
   * Returns the body (stripped of YAML frontmatter), or null if not found.
   */
  readPreviousExtract(tier) {
    const extractPath = path2.join(this.vaultDir, "digest", `extract-${tier}.md`);
    let content;
    try {
      content = fs2.readFileSync(extractPath, "utf-8");
    } catch {
      return null;
    }
    return stripFrontmatter(content).body;
  }
  /**
   * Write a digest extract to the vault with YAML frontmatter.
   * Uses atomic write pattern (temp file + rename).
   */
  writeExtract(tier, body, cycleId, model, substrateCount) {
    const digestDir = path2.join(this.vaultDir, "digest");
    fs2.mkdirSync(digestDir, { recursive: true });
    const frontmatter = {
      type: EXTRACT_TYPE,
      tier,
      generated: (/* @__PURE__ */ new Date()).toISOString(),
      cycle_id: cycleId,
      substrate_count: substrateCount,
      model
    };
    const fmYaml = import_yaml.default.stringify(frontmatter, {
      defaultStringType: "QUOTE_DOUBLE",
      defaultKeyType: "PLAIN"
    }).trim();
    const file = `---
${fmYaml}
---

${body}
`;
    const fullPath = path2.join(digestDir, `extract-${tier}.md`);
    const tmpPath = `${fullPath}.tmp`;
    fs2.writeFileSync(tmpPath, file, "utf-8");
    fs2.renameSync(tmpPath, fullPath);
  }
  /**
   * Append a digest cycle result as a JSON line to trace.jsonl.
   */
  appendTrace(record) {
    const tracePath = path2.join(this.vaultDir, "digest", "trace.jsonl");
    appendTraceRecord(tracePath, record);
    this.lastCycleTimestampCache = record.timestamp;
  }
  /**
   * Read the last cycle timestamp from trace.jsonl.
   * Cached in memory after first read — subsequent calls are O(1).
   */
  getLastCycleTimestamp() {
    if (this.lastCycleTimestampCache !== void 0) return this.lastCycleTimestampCache;
    const tracePath = path2.join(this.vaultDir, "digest", "trace.jsonl");
    this.lastCycleTimestampCache = readLastTimestamp(tracePath);
    return this.lastCycleTimestampCache;
  }
  /**
   * Run a full digest cycle: discover substrate, generate extracts for each tier.
   * Returns the cycle result, or null if no substrate was found.
   */
  async runCycle(opts) {
    if (this.cycleInProgress) {
      this.log("debug", "Cycle already in progress \u2014 skipping");
      return null;
    }
    this.cycleInProgress = true;
    try {
      if (this.llm.ensureLoaded) {
        const { context_window: contextWindow, gpu_kv_cache: gpuKvCache } = this.config.digest.intelligence;
        this.log("debug", "Verifying digest model", { contextWindow, gpuKvCache });
        await this.llm.ensureLoaded(contextWindow, gpuKvCache);
      }
      for (const hook of this.prePassHooks) {
        try {
          await hook.fn();
        } catch (err) {
          this.log("warn", `Pre-pass hook "${hook.name}" failed`, { error: err.message });
        }
      }
      return await this.runCycleInternal(opts);
    } finally {
      this.cycleInProgress = false;
    }
  }
  async runCycleInternal(opts) {
    const startTime = Date.now();
    const fullReprocess = opts?.fullReprocess ?? false;
    const lastTimestamp = fullReprocess ? null : this.getLastCycleTimestamp();
    const substrate = this.discoverSubstrate(lastTimestamp);
    this.log("debug", "Discovering substrate", { lastTimestamp: lastTimestamp ?? "full reprocess", substrateCount: substrate.length });
    if (substrate.length === 0) {
      this.log("debug", "No substrate found \u2014 skipping cycle");
      return null;
    }
    this.log("info", `Starting digest cycle`, { substrateCount: substrate.length, fullReprocess });
    const cycleId = crypto.randomUUID();
    const allEligible = this.getEligibleTiers();
    const eligibleTiers = opts?.tiers ? allEligible.filter((t) => opts.tiers.includes(t)) : allEligible;
    this.log("debug", `Eligible tiers: [${eligibleTiers.join(", ")}]`);
    const tiersGenerated = [];
    let totalTokensUsed = 0;
    let model = "";
    const typeToKey = {
      session: "sessions",
      spore: "spores",
      plan: "plans",
      artifact: "artifacts",
      "team-member": "team"
    };
    const substrateIndex = {
      sessions: [],
      spores: [],
      plans: [],
      artifacts: [],
      team: []
    };
    for (const note of substrate) {
      const key = typeToKey[note.type];
      if (key) {
        substrateIndex[key].push(note.id);
      }
    }
    const cycleTimestamp = (/* @__PURE__ */ new Date()).toISOString();
    const systemPrompt = loadPrompt("digest-system");
    for (const tier of eligibleTiers) {
      const tierPrompt = loadPrompt(`digest-${tier}`);
      const previousExtract = opts?.cleanSlate ? null : this.readPreviousExtract(tier);
      const contextWindow = this.config.digest.intelligence.context_window;
      const systemPromptTokens = estimateTokens(systemPrompt);
      const tierPromptTokens = estimateTokens(tierPrompt);
      const previousExtractTokens = previousExtract ? estimateTokens(previousExtract) + PREVIOUS_EXTRACT_OVERHEAD_TOKENS : 0;
      const availableTokens = Math.floor(contextWindow * CONTEXT_SAFETY_MARGIN);
      const substrateBudget = availableTokens - tier - systemPromptTokens - tierPromptTokens - previousExtractTokens;
      if (substrateBudget <= 0) continue;
      const formattedSubstrate = this.formatSubstrate(substrate, substrateBudget);
      const promptParts = [tierPrompt];
      if (previousExtract) {
        promptParts.push("", "## Previous Synthesis", "", previousExtract);
      }
      promptParts.push("", "## New Substrate", "", formattedSubstrate);
      promptParts.push(
        "",
        "---",
        "Produce your updated synthesis now. Stay within the token budget specified above."
      );
      const userPrompt = promptParts.join("\n");
      const promptTokens = estimateTokens(systemPrompt + userPrompt);
      this.log("debug", `Tier ${tier}: sending LLM request`, { promptTokens, maxTokens: tier, substrateBudget });
      try {
        const tierStart = Date.now();
        const digestConfig = this.config.digest.intelligence;
        const opts2 = {
          maxTokens: tier,
          timeoutMs: DIGEST_LLM_REQUEST_TIMEOUT_MS,
          contextLength: contextWindow,
          reasoning: LLM_REASONING_MODE,
          systemPrompt,
          keepAlive: digestConfig.keep_alive ?? void 0
        };
        const response = await this.llm.summarize(userPrompt, opts2);
        const tierDuration = Date.now() - tierStart;
        const extractText = stripReasoningTokens(response.text);
        model = response.model;
        const responseTokens = estimateTokens(extractText);
        totalTokensUsed += promptTokens + responseTokens;
        this.log("info", `Tier ${tier}: completed`, { durationMs: tierDuration, responseTokens, model: response.model });
        this.writeExtract(tier, extractText, cycleId, response.model, substrate.length);
        tiersGenerated.push(tier);
      } catch (err) {
        this.log("warn", `Tier ${tier}: failed`, { error: err.message });
      }
    }
    const result = {
      cycleId,
      timestamp: cycleTimestamp,
      substrate: substrateIndex,
      tiersGenerated,
      model,
      durationMs: Date.now() - startTime,
      tokensUsed: totalTokensUsed
    };
    this.appendTrace(result);
    for (const hook of this.postPassHooks) {
      try {
        await hook.fn(result);
      } catch (err) {
        this.log("warn", `Post-pass hook "${hook.name}" failed`, { error: err.message });
      }
    }
    return result;
  }
};
var MS_PER_SECOND = 1e3;
var Metabolism = class {
  state = "active";
  currentIntervalMs;
  cooldownStep = 0;
  lastSubstrateTime;
  timer = null;
  activeIntervalMs;
  cooldownIntervalsMs;
  dormancyThresholdMs;
  constructor(config) {
    this.activeIntervalMs = config.active_interval * MS_PER_SECOND;
    this.cooldownIntervalsMs = config.cooldown_intervals.map((s) => s * MS_PER_SECOND);
    this.dormancyThresholdMs = config.dormancy_threshold * MS_PER_SECOND;
    this.currentIntervalMs = this.activeIntervalMs;
    this.lastSubstrateTime = Date.now();
  }
  /** Reset to active state when new substrate is found. */
  onSubstrateFound() {
    this.state = "active";
    this.cooldownStep = 0;
    this.currentIntervalMs = this.activeIntervalMs;
    this.lastSubstrateTime = Date.now();
  }
  /** Advance cooldown when a cycle finds no new substrate. */
  onEmptyCycle() {
    if (this.state === "dormant") return;
    this.state = "cooling";
    if (this.cooldownStep < this.cooldownIntervalsMs.length) {
      this.currentIntervalMs = this.cooldownIntervalsMs[this.cooldownStep];
      this.cooldownStep++;
    }
    this.checkDormancy();
  }
  /** Enter dormant state if enough time has elapsed since last substrate. */
  checkDormancy() {
    const elapsed = Date.now() - this.lastSubstrateTime;
    if (elapsed >= this.dormancyThresholdMs) {
      this.state = "dormant";
    }
  }
  /** Return to active from any state, resetting timers and rescheduling immediately. */
  activate() {
    this.onSubstrateFound();
    if (this.callback) {
      this.reschedule();
    }
  }
  /** Set lastSubstrateTime explicitly (for testing). */
  markLastSubstrate(time) {
    this.lastSubstrateTime = time;
  }
  /** Begin scheduling digest cycles with adaptive intervals. */
  start(callback) {
    this.callback = callback;
    this.reschedule();
  }
  /** Stop the timer. */
  stop() {
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
  }
  callback = null;
  reschedule() {
    this.stop();
    if (!this.callback) return;
    const cb = this.callback;
    const schedule = () => {
      this.timer = setTimeout(async () => {
        await cb();
        schedule();
      }, this.currentIntervalMs);
      this.timer.unref();
    };
    schedule();
  }
};

// src/daemon/processor.ts
var SUMMARIZATION_FAILED_MARKER = "summarization failed";
var ClassificationResponseSchema = external_exports.object({
  artifacts: external_exports.array(external_exports.object({
    source_path: external_exports.string(),
    artifact_type: external_exports.enum(ARTIFACT_TYPES),
    title: external_exports.string(),
    tags: external_exports.array(external_exports.string()).default([])
  })).default([])
});
var BufferProcessor = class {
  constructor(backend, contextWindow = 8192, captureConfig) {
    this.backend = backend;
    this.contextWindow = contextWindow;
    this.extractionMaxTokens = captureConfig?.extraction_max_tokens ?? 2048;
    this.summaryMaxTokens = captureConfig?.summary_max_tokens ?? 512;
    this.titleMaxTokens = captureConfig?.title_max_tokens ?? 32;
    this.classificationMaxTokens = captureConfig?.classification_max_tokens ?? 1024;
  }
  extractionMaxTokens;
  summaryMaxTokens;
  titleMaxTokens;
  classificationMaxTokens;
  truncateForContext(data, maxTokens) {
    const available = this.contextWindow - maxTokens;
    const dataTokens = estimateTokens(data);
    if (dataTokens <= available) return data;
    const charBudget = available * CHARS_PER_TOKEN;
    return data.slice(0, charBudget);
  }
  async process(events, sessionId) {
    const rawPrompt = this.buildPromptForExtraction(events, sessionId);
    const prompt = this.truncateForContext(rawPrompt, this.extractionMaxTokens);
    try {
      const response = await this.backend.summarize(prompt, { maxTokens: this.extractionMaxTokens, reasoning: LLM_REASONING_MODE });
      const parsed = extractJson(response.text);
      return {
        summary: parsed.summary,
        observations: parsed.observations ?? [],
        degraded: false
      };
    } catch (error) {
      return {
        summary: `LLM processing failed for session ${sessionId}. ${events.length} events captured. Error: ${error.message}`,
        observations: [],
        degraded: true
      };
    }
  }
  buildPromptForExtraction(events, sessionId) {
    const toolSummary = this.summarizeEvents(events);
    return buildExtractionPrompt(sessionId, events.length, toolSummary, this.extractionMaxTokens);
  }
  async summarizeSession(conversationMarkdown, sessionId, user) {
    const truncatedContent = this.truncateForContext(conversationMarkdown, this.summaryMaxTokens);
    const summaryPrompt = buildSummaryPrompt(sessionId, user ?? "unknown", truncatedContent, this.summaryMaxTokens);
    let summaryText;
    try {
      const response = await this.backend.summarize(summaryPrompt, { maxTokens: this.summaryMaxTokens, reasoning: LLM_REASONING_MODE });
      summaryText = stripReasoningTokens(response.text);
    } catch (error) {
      summaryText = `Session ${sessionId} \u2014 ${SUMMARIZATION_FAILED_MARKER}: ${error.message}`;
    }
    const titlePrompt = buildTitlePrompt(summaryText, sessionId);
    let title;
    try {
      const response = await this.backend.summarize(titlePrompt, { maxTokens: this.titleMaxTokens, reasoning: LLM_REASONING_MODE });
      title = stripReasoningTokens(response.text).trim();
    } catch {
      title = `Session ${sessionId}`;
    }
    return { summary: summaryText, title };
  }
  async classifyArtifacts(candidates, sessionId) {
    if (candidates.length === 0) return [];
    const prompt = this.buildPromptForClassification(candidates, sessionId);
    const response = await this.backend.summarize(prompt, { maxTokens: this.classificationMaxTokens, reasoning: LLM_REASONING_MODE });
    const raw = extractJson(response.text);
    const parsed = ClassificationResponseSchema.parse(raw);
    return parsed.artifacts;
  }
  buildPromptForClassification(candidates, sessionId) {
    return buildClassificationPrompt(sessionId, candidates, this.classificationMaxTokens);
  }
  summarizeEvents(events) {
    const toolCounts = /* @__PURE__ */ new Map();
    const filesAccessed = /* @__PURE__ */ new Set();
    const prompts = [];
    const aiResponses = [];
    for (const event of events) {
      if (event.type === "user_prompt") {
        const prompt = String(event.prompt ?? "");
        if (prompt) prompts.push(prompt.slice(0, PROMPT_PREVIEW_CHARS));
        continue;
      }
      if (event.type === "ai_response") {
        const content = String(event.content ?? "");
        if (content) aiResponses.push(content.slice(0, AI_RESPONSE_PREVIEW_CHARS));
        continue;
      }
      const tool = String(event.tool_name ?? event.tool ?? "unknown");
      toolCounts.set(tool, (toolCounts.get(tool) ?? 0) + 1);
      const input = event.tool_input ?? event.input;
      if (input?.path) filesAccessed.add(String(input.path));
      if (input?.file_path) filesAccessed.add(String(input.file_path));
      if (input?.command) filesAccessed.add(`[cmd] ${String(input.command).slice(0, COMMAND_PREVIEW_CHARS)}`);
    }
    const lines = [];
    if (prompts.length > 0) {
      lines.push("### User Prompts");
      for (const p of prompts) {
        lines.push(`- "${p}"`);
      }
    }
    lines.push("\n### Tool Usage");
    for (const [tool, count] of toolCounts) {
      lines.push(`- ${tool}: ${count} calls`);
    }
    if (filesAccessed.size > 0) {
      lines.push("\n### Files Accessed");
      for (const file of filesAccessed) {
        lines.push(`- ${file}`);
      }
    }
    if (aiResponses.length > 0) {
      lines.push("\n### AI Responses");
      for (const r of aiResponses) {
        lines.push(`- "${r}"`);
      }
    }
    return lines.join("\n");
  }
};

// src/capture/transcript-miner.ts
var TranscriptMiner = class {
  registry;
  constructor(config) {
    this.registry = new AgentRegistry(config?.additionalAdapters);
  }
  /**
   * Extract all conversation turns for a session.
   * Convenience wrapper — delegates to getAllTurnsWithSource.
   */
  getAllTurns(sessionId) {
    return this.getAllTurnsWithSource(sessionId).turns;
  }
  /**
   * Extract turns using the hook-provided transcript path first (fast, no scanning),
   * then fall back to adapter registry scanning if the path isn't provided.
   */
  getAllTurnsWithSource(sessionId, transcriptPath) {
    if (transcriptPath) {
      const result2 = this.registry.parseTurnsFromPath(transcriptPath);
      if (result2) return result2;
    }
    const result = this.registry.getTranscriptTurns(sessionId);
    if (result) return result;
    return { turns: [], source: "none" };
  }
};
function extractTurnsFromBuffer(events) {
  const turns = [];
  let current = null;
  for (const event of events) {
    const type = event.type;
    if (type === "user_prompt") {
      if (current) turns.push(current);
      current = {
        prompt: String(event.prompt ?? "").slice(0, PROMPT_PREVIEW_CHARS),
        toolCount: 0,
        timestamp: String(event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString())
      };
    } else if (type === "tool_use") {
      if (current) current.toolCount++;
    }
  }
  if (current) turns.push(current);
  return turns;
}

// src/vault/observations.ts
function writeObservationNotes(observations, sessionId, writer, index, vaultDir) {
  const results = [];
  for (const obs of observations) {
    const obsId = `${obs.type}-${sessionId.slice(-6)}-${Date.now()}`;
    const body = formatSporeBody({
      title: obs.title,
      observationType: obs.type,
      content: obs.content,
      sessionId,
      root_cause: obs.root_cause,
      fix: obs.fix,
      rationale: obs.rationale,
      alternatives_rejected: obs.alternatives_rejected,
      gained: obs.gained,
      sacrificed: obs.sacrificed,
      tags: obs.tags
    });
    const relativePath = writer.writeSpore({
      id: obsId,
      observation_type: obs.type,
      session: sessionNoteId(sessionId),
      tags: obs.tags,
      content: body
    });
    indexNote(index, vaultDir, relativePath);
    results.push({ id: obsId, path: relativePath, observation: obs });
  }
  return results;
}

// src/services/vault-ops.ts
import fs3 from "fs";
import path3 from "path";

// src/intelligence/batch.ts
var LLM_BATCH_CONCURRENCY = 3;
var EMBEDDING_BATCH_CONCURRENCY = 4;
async function batchExecute(items, fn, options) {
  const { concurrency, onProgress } = options;
  let succeeded = 0;
  let failed = 0;
  const results = [];
  for (let i = 0; i < items.length; i += concurrency) {
    const batch = items.slice(i, i + concurrency);
    const settled = await Promise.allSettled(batch.map(fn));
    for (const result of settled) {
      if (result.status === "fulfilled") {
        succeeded++;
        results.push({ status: "fulfilled", value: result.value });
      } else {
        failed++;
        results.push({ status: "rejected", reason: result.reason?.message ?? String(result.reason) });
      }
    }
    onProgress?.(succeeded + failed, items.length);
  }
  return { succeeded, failed, results };
}

// src/services/vault-ops.ts
var import_gray_matter = __toESM(require_gray_matter(), 1);
async function runRebuild(ctx, embeddingProvider, onProgress) {
  const { index, vaultDir } = ctx;
  initFts(index);
  const ftsCount = rebuildIndex(index, vaultDir);
  if (!ctx.vectorIndex) {
    return { ftsCount, embeddedCount: 0, failedCount: 0, skippedCount: 0 };
  }
  const allNotes = index.query({});
  const activeNotes = allNotes.filter((n) => {
    const status = n.frontmatter?.status;
    return status !== "superseded" && status !== "archived";
  });
  const skippedCount = allNotes.length - activeNotes.length;
  const vec = ctx.vectorIndex;
  const result = await batchExecute(
    activeNotes,
    async (note) => {
      const text = `${note.title}
${note.content}`.slice(0, EMBEDDING_INPUT_LIMIT);
      const emb = await generateEmbedding(embeddingProvider, text);
      vec.upsert(note.id, emb.embedding, {
        type: note.type,
        session_id: note.frontmatter?.session ?? ""
      });
    },
    {
      concurrency: EMBEDDING_BATCH_CONCURRENCY,
      onProgress
    }
  );
  return {
    ftsCount,
    embeddedCount: result.succeeded,
    failedCount: result.failed,
    skippedCount
  };
}
async function runDigest(ctx, llmProvider, options) {
  const { config, vaultDir, index } = ctx;
  const log = ctx.log ? (level, message, data) => ctx.log(level, message, data) : () => {
  };
  const engine = new DigestEngine({
    vaultDir,
    index,
    llmProvider,
    config,
    log
  });
  const opts = {};
  const isReprocess = options?.full || options?.tier !== void 0;
  if (isReprocess) {
    opts.fullReprocess = true;
    opts.cleanSlate = true;
  }
  if (options?.tier !== void 0) {
    const eligible = engine.getEligibleTiers();
    if (!eligible.includes(options.tier)) {
      throw new Error(`Tier ${options.tier} is not eligible. Eligible tiers: [${eligible.join(", ")}]`);
    }
    opts.tiers = [options.tier];
  }
  return engine.runCycle(opts);
}
var CURATION_EMBEDDING_BATCH_SIZE = 10;
function cosineSimilarity(a, b) {
  let dot = 0, normA = 0, normB = 0;
  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }
  return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
function updateCentroid(spores) {
  if (spores.length === 0) return [];
  const dim = spores[0].embedding.length;
  const centroid = new Array(dim).fill(0);
  for (const s of spores) {
    for (let i = 0; i < dim; i++) {
      centroid[i] += s.embedding[i];
    }
  }
  for (let i = 0; i < dim; i++) {
    centroid[i] /= spores.length;
  }
  return centroid;
}
function clusterSpores(spores) {
  const clusters = [];
  for (const spore of spores) {
    let bestCluster = null;
    let bestSimilarity = -1;
    for (const cluster of clusters) {
      const sim = cosineSimilarity(spore.embedding, cluster.centroid);
      if (sim > bestSimilarity) {
        bestSimilarity = sim;
        bestCluster = cluster;
      }
    }
    if (bestCluster !== null && bestSimilarity >= CURATION_CLUSTER_SIMILARITY) {
      bestCluster.spores.push(spore);
      bestCluster.centroid = updateCentroid(bestCluster.spores);
    } else {
      clusters.push({ spores: [spore], centroid: [...spore.embedding] });
    }
  }
  return clusters;
}
async function runCuration(deps, dryRun) {
  const { index, vectorIndex, llmProvider, embeddingProvider, vaultDir } = deps;
  const log = deps.log ?? (() => {
  });
  const allSpores = index.query({ type: "spore" });
  const activeSpores = allSpores.filter((n) => isActiveSpore(n.frontmatter));
  if (activeSpores.length === 0) {
    return { scanned: 0, clustersEvaluated: 0, superseded: 0 };
  }
  const sporesWithEmbeddings = [];
  let embedFailures = 0;
  for (let i = 0; i < activeSpores.length; i += CURATION_EMBEDDING_BATCH_SIZE) {
    const batch = activeSpores.slice(i, i + CURATION_EMBEDDING_BATCH_SIZE);
    const results = await Promise.allSettled(
      batch.map(async (spore) => {
        const text = spore.content.slice(0, EMBEDDING_INPUT_LIMIT);
        const result = await generateEmbedding(embeddingProvider, text);
        return { spore, embedding: result.embedding };
      })
    );
    for (const result of results) {
      if (result.status === "fulfilled") {
        const { spore, embedding } = result.value;
        sporesWithEmbeddings.push({
          id: spore.id,
          path: spore.path,
          title: spore.title,
          content: spore.content,
          created: spore.created,
          frontmatter: spore.frontmatter,
          embedding
        });
      } else {
        embedFailures++;
      }
    }
  }
  if (embedFailures > 0) {
    log("warn", `${embedFailures} spore(s) could not be embedded and were skipped`);
  }
  const byType = /* @__PURE__ */ new Map();
  for (const spore of sporesWithEmbeddings) {
    const obsType = spore.frontmatter["observation_type"] ?? "unknown";
    if (!byType.has(obsType)) byType.set(obsType, []);
    byType.get(obsType).push(spore);
  }
  const template = loadPrompt("supersession");
  let totalClusters = 0;
  let totalSuperseded = 0;
  for (const [obsType, typeSpores] of byType) {
    const clusters = clusterSpores(typeSpores);
    const multiSpore = clusters.filter((c) => c.spores.length >= 2);
    if (multiSpore.length === 0) continue;
    log("info", `Type: ${obsType} \u2014 ${typeSpores.length} spores, ${multiSpore.length} cluster(s) to evaluate`);
    totalClusters += multiSpore.length;
    for (const cluster of multiSpore) {
      const sorted = [...cluster.spores].sort((a, b) => a.created.localeCompare(b.created));
      const newest = sorted[sorted.length - 1];
      const candidates = sorted.slice(0, sorted.length - 1);
      const newSporeText = formatNoteForPrompt(newest);
      const candidatesText = formatNotesForPrompt(candidates);
      const prompt = template.replace("{{new_spore}}", newSporeText).replace("{{candidates}}", candidatesText);
      let responseText;
      try {
        const response = await llmProvider.summarize(prompt, {
          maxTokens: SUPERSESSION_MAX_TOKENS,
          reasoning: LLM_REASONING_MODE
        });
        responseText = stripReasoningTokens(response.text);
      } catch (err) {
        log("warn", `LLM call failed for cluster in ${obsType}: ${String(err)}`);
        continue;
      }
      let rawIds;
      try {
        rawIds = JSON.parse(responseText);
      } catch {
        log("warn", `Could not parse LLM response for cluster in ${obsType}`);
        continue;
      }
      const parsed = supersededIdsSchema.safeParse(rawIds);
      if (!parsed.success) {
        log("warn", `LLM response schema invalid for cluster in ${obsType}`);
        continue;
      }
      const candidateMap = new Map(candidates.map((c) => [c.id, c]));
      const validIds = parsed.data.filter((id) => candidateMap.has(id));
      if (validIds.length === 0) continue;
      for (const id of validIds) {
        const candidate = candidateMap.get(id);
        if (dryRun) {
          log("info", `[dry-run] Would supersede: ${candidate.title} (${id}) by ${newest.title} (${newest.id})`);
          totalSuperseded++;
          continue;
        }
        const wrote = supersedeSpore(id, newest.id, candidate.path, { index, vectorIndex, vaultDir });
        if (!wrote) {
          log("warn", `File not found for ${id}, skipping write`);
          continue;
        }
        log("info", `Superseded: ${candidate.title} (${id}) by ${newest.title} (${newest.id})`);
        totalSuperseded++;
      }
    }
  }
  return {
    scanned: activeSpores.length,
    clustersEvaluated: totalClusters,
    superseded: totalSuperseded
  };
}
function updateTitleAndSummary(body, newTitle, newNarrative) {
  let updated = body.replace(/^# .*/m, `# ${newTitle}`);
  const summaryCallout = callout("abstract", "Summary", newNarrative);
  updated = updated.replace(/> \[!abstract\] Summary\n(?:> .*\n?)*/m, summaryCallout + "\n");
  return updated;
}
async function runReprocess(ctx, llmProvider, embeddingProvider, options, onProgress) {
  const { vaultDir, config, index } = ctx;
  const log = ctx.log ?? (() => {
  });
  const sessionFilter = options?.session;
  const dateFilter = options?.date;
  const failedOnly = options?.failed ?? false;
  const skipLlm = options?.indexOnly ?? false;
  const effectiveLlm = skipLlm ? null : llmProvider;
  const processor = effectiveLlm ? new BufferProcessor(effectiveLlm, config.intelligence.llm.context_window, config.capture) : null;
  const writer = new VaultWriter(vaultDir);
  const miner = new TranscriptMiner({
    additionalAdapters: config.capture.transcript_paths.map(
      (p) => createPerProjectAdapter(p, claudeCodeAdapter.parseTurns)
    )
  });
  const sessionsDir = path3.join(vaultDir, "sessions");
  if (!fs3.existsSync(sessionsDir)) {
    return { sessionsFound: 0, sessionsProcessed: 0, observationsExtracted: 0, summariesRegenerated: 0, embeddingsQueued: 0 };
  }
  const sessionFiles = [];
  for (const dateDir of fs3.readdirSync(sessionsDir)) {
    if (dateFilter && dateDir !== dateFilter) continue;
    const datePath = path3.join(sessionsDir, dateDir);
    if (!fs3.statSync(datePath).isDirectory()) continue;
    for (const file of fs3.readdirSync(datePath)) {
      if (!file.startsWith("session-") || !file.endsWith(".md")) continue;
      const sessionId = file.replace("session-", "").replace(".md", "");
      if (sessionFilter && !sessionId.includes(sessionFilter)) continue;
      sessionFiles.push({ relativePath: path3.join("sessions", dateDir, file), sessionId });
    }
  }
  if (sessionFiles.length === 0) {
    return { sessionsFound: 0, sessionsProcessed: 0, observationsExtracted: 0, summariesRegenerated: 0, embeddingsQueued: 0 };
  }
  const tasks = [];
  for (const { relativePath, sessionId } of sessionFiles) {
    const rawContent = fs3.readFileSync(path3.join(vaultDir, relativePath), "utf-8");
    const hasFailed = rawContent.includes(SUMMARIZATION_FAILED_MARKER);
    if (failedOnly && !hasFailed) continue;
    const { data: frontmatter, content: body } = (0, import_gray_matter.default)(rawContent);
    const bare = bareSessionId(sessionId);
    const turnsResult = miner.getAllTurnsWithSource(bare);
    const conversationSection = extractSection(body, CONVERSATION_HEADING);
    const fmEnd = rawContent.indexOf("---", 4);
    const frontmatterBlock = rawContent.slice(0, fmEnd + 3);
    const batchEvents = turnsResult && turnsResult.turns.length > 0 ? turnsResult.turns.map((t) => ({
      type: "turn",
      prompt: t.prompt,
      tool_count: t.toolCount,
      response: t.aiResponse ?? "",
      timestamp: t.timestamp
    })) : null;
    tasks.push({ relativePath, sessionId, bare, frontmatter, frontmatterBlock, body, conversationSection, batchEvents, turnCount: turnsResult?.turns.length ?? 0, hasFailed });
  }
  if (tasks.length === 0) {
    return { sessionsFound: sessionFiles.length, sessionsProcessed: 0, observationsExtracted: 0, summariesRegenerated: 0, embeddingsQueued: 0 };
  }
  log("info", `Reprocessing ${tasks.length} session(s)`, { filters: { session: sessionFilter, date: dateFilter, failed: failedOnly, indexOnly: skipLlm } });
  let embeddingsQueued = 0;
  const embedPending = [];
  const fireEmbed = (id, text, metadata) => {
    if (!ctx.vectorIndex) return;
    embeddingsQueued++;
    const vec = ctx.vectorIndex;
    const p = generateEmbedding(embeddingProvider, text).then((emb) => {
      vec.upsert(id, emb.embedding, metadata);
    }).catch((err) => {
      log("warn", `Embedding failed for ${id}`, { error: err.message });
    });
    embedPending.push(p);
  };
  let totalObservations = 0;
  const extractionResult = await batchExecute(
    tasks,
    async (task) => {
      let obs = 0;
      if (processor && task.batchEvents) {
        const result = await processor.process(task.batchEvents, task.bare);
        if (result.observations.length > 0) {
          writeObservationNotes(result.observations, task.bare, writer, index, vaultDir);
          obs = result.observations.length;
          for (const o of result.observations) {
            fireEmbed(
              `${o.type}-${task.bare.slice(-6)}-${Date.now()}`,
              `${o.title}
${o.content}`.slice(0, EMBEDDING_INPUT_LIMIT),
              { type: "spore", session_id: task.bare }
            );
          }
        }
      }
      indexNote(index, vaultDir, task.relativePath);
      const embText = `${task.frontmatter.title ?? ""}
${task.frontmatter.summary ?? ""}`.slice(0, EMBEDDING_INPUT_LIMIT);
      if (embText.trim()) {
        fireEmbed(sessionNoteId(task.bare), embText, { type: "session", session_id: task.bare });
      }
      return obs;
    },
    {
      concurrency: LLM_BATCH_CONCURRENCY,
      onProgress: (done, total) => onProgress?.("extraction", done, total)
    }
  );
  for (const r of extractionResult.results) {
    if (r.status === "fulfilled") totalObservations += r.value;
  }
  let summarized = 0;
  if (processor) {
    const summarizableTasks = tasks.filter((t) => t.conversationSection);
    if (summarizableTasks.length > 0) {
      const summaryResult = await batchExecute(
        summarizableTasks,
        async (task) => {
          const user = typeof task.frontmatter.user === "string" ? task.frontmatter.user : void 0;
          const result = await processor.summarizeSession(task.conversationSection, task.bare, user);
          if (result.summary.includes(SUMMARIZATION_FAILED_MARKER)) {
            log("warn", `Summarization failed for ${task.sessionId.slice(0, 12)}`);
            return false;
          }
          const updatedBody = updateTitleAndSummary(task.body, result.title, result.summary);
          fs3.writeFileSync(path3.join(vaultDir, task.relativePath), task.frontmatterBlock + updatedBody);
          indexNote(index, vaultDir, task.relativePath);
          return true;
        },
        {
          concurrency: LLM_BATCH_CONCURRENCY,
          onProgress: (done, total) => onProgress?.("summarization", done, total)
        }
      );
      for (const r of summaryResult.results) {
        if (r.status === "fulfilled" && r.value) summarized++;
      }
    }
  }
  await Promise.allSettled(embedPending);
  log("info", "Reprocess completed", {
    sessions: tasks.length,
    observations: totalObservations,
    summaries: summarized,
    embeddings: embeddingsQueued
  });
  return {
    sessionsFound: sessionFiles.length,
    sessionsProcessed: tasks.length,
    observationsExtracted: totalObservations,
    summariesRegenerated: summarized,
    embeddingsQueued
  };
}

export {
  readLastTimestamp,
  appendTraceRecord,
  DigestEngine,
  Metabolism,
  SUMMARIZATION_FAILED_MARKER,
  BufferProcessor,
  TranscriptMiner,
  extractTurnsFromBuffer,
  writeObservationNotes,
  runRebuild,
  runDigest,
  runCuration,
  runReprocess
};
//# sourceMappingURL=chunk-PQWQC3RF.js.map