#!/usr/bin/env node
'use strict';
// SigMap — standalone bundle (generated by scripts/bundle.js)
// Copy this single file to any project: node gen-context.js --setup
// No src/ directory required. Node.js 18+ only. Zero npm dependencies.

const __factories = {};
const __cache = {};
function __require(key) {
  if (Object.prototype.hasOwnProperty.call(__cache, key)) {
    return __cache[key].exports;
  }
  if (!Object.prototype.hasOwnProperty.call(__factories, key)) {
    throw new Error('[sigmap] bundled module not found: ' + key);
  }
  const module = { exports: {} };
  __cache[key] = module;
  __factories[key](module, module.exports);
  return module.exports;
}


// ── ./src/config/defaults ──
__factories["./src/config/defaults"] = function(module, exports) {
  
  /**
   * Default configuration values for SigMap.
   * All keys documented here. Override via gen-context.config.json.
   */
  const DEFAULTS = {
    // Primary output file (used when outputs includes 'copilot')
    output: '.github/copilot-instructions.md',
  
    // Output targets: 'copilot' | 'claude' | 'cursor' | 'windsurf'
    outputs: ['copilot'],

    // Adapter targets (v3.0+): replaces 'outputs'. Same names, adds 'openai' | 'gemini' | 'codex'.
    // Old 'outputs' config key is still accepted and silently maps to 'adapters'.
    adapters: null, // null means: use 'outputs' for backward compat
  
    // Directories to scan (relative to project root)
    srcDirs: [
      'src', 'app', 'lib', 'packages', 'services', 'api',
      'server', 'client', 'web', 'frontend', 'backend',
      'desktop', 'mobile', 'shared', 'common', 'core',
      'workers', 'functions', 'lambda', 'cmd',
    ],
  
    // Directory/file names to exclude entirely
    exclude: [
      'node_modules', '.git', 'dist', 'build', 'out',
      '__pycache__', '.next', 'coverage', 'target', 'vendor',
      '.context',
    ],
  
    // Maximum directory depth to recurse
    maxDepth: 6,
  
    // Maximum signatures extracted per file
    maxSigsPerFile: 25,
  
    // Maximum tokens in final output before budget enforcement kicks in.
    // Used only when autoMaxTokens is false, or as a floor for auto-scaling.
    maxTokens: 6000,

    // Automatically scale the token budget based on repo size.
    autoMaxTokens: true,

    // Fraction of source files to target for inclusion (0.0–1.0).
    coverageTarget: 0.80,

    // Model context window size (tokens). Used to compute the hard cap.
    modelContextLimit: 128000,

    // Fraction of the model context window reserved for SigMap output.
    maxTokensHeadroom: 0.20,

    // Scan signatures for secrets and redact matches
    secretScan: true,
  
    // Auto-detect monorepo packages and write per-package output files
    monorepo: false,
  
    // Sort recently git-committed files higher in output
    diffPriority: true,
  
    // Debounce delay (ms) between file-system events and regeneration in watch mode
    watchDebounce: 300,
  
    // Append model routing hints section to the context output
    // Routes files to fast/balanced/powerful model tiers based on complexity
    routing: false,
  
    // Output format: 'default' (markdown only) | 'cache' (also write Anthropic prompt-cache JSON)
    format: 'default',
  
    // Append run metrics to .context/usage.ndjson after each generate
    tracking: false,
  
    // MCP server configuration
    mcp: {
      autoRegister: true,
    },

    // Context strategy controls how the output is split and injected.
    //
    // 'full'        — single file, all signatures (default, works everywhere)
    //
    // 'per-module'  — one output file per top-level srcDir, e.g.
    //                 .github/context-server.md, .github/context-web.md
    //                 Best when IDEs can choose which file to inject per workspace.
    //                 Zero context lost — all files are still covered.
    //                 ~70% token reduction per question.
    //
    // 'hot-cold'    — two files:
    //                 hot: .github/copilot-instructions.md  (recently changed files only)
    //                 cold: .github/context-cold.md         (all other files, MCP on-demand)
    //                 Best when MCP is available (Claude Code, Cursor).
    //                 ~90% token reduction but cold-file context requires MCP to retrieve.
    strategy: 'full',

    // For 'hot-cold' strategy: how many recent git commits count as "hot"
    hotCommits: 10,
  };
  
  module.exports = { DEFAULTS };
  
};

// ── ./src/config/loader ──
__factories["./src/config/loader"] = function(module, exports) {

  const fs = require('fs');
  const path = require('path');
  const { DEFAULTS } = __require('./src/config/defaults');

  // Keys that are valid in gen-context.config.json
  const KNOWN_KEYS = new Set(Object.keys(DEFAULTS));

  // Common top-level folder names that reliably hold source code
  const COMMON_CODE_DIRS = new Set([
    'src', 'app', 'lib', 'packages', 'services', 'api', 'core', 'cmd',
    'internal', 'pkg', 'handlers', 'controllers', 'models', 'views',
    'components', 'pages', 'routes', 'middleware', 'utils', 'helpers',
    'modules', 'plugins', 'extensions', 'adapters', 'drivers',
    'hooks', 'composables', 'stores', 'features', 'domain', 'infra',
    'infrastructure', 'application', 'data', 'Sources', 'Tests',
  ]);

  const SUPPORTED_CODE_EXTS = new Set([
    '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
    '.py', '.pyw', '.java', '.kt', '.kts', '.go', '.rs', '.cs',
    '.cpp', '.c', '.h', '.hpp', '.cc', '.rb', '.rake', '.php',
    '.swift', '.dart', '.scala', '.sc', '.vue', '.svelte',
    '.html', '.htm', '.css', '.scss', '.sass', '.less',
    '.yml', '.yaml', '.sh', '.bash', '.zsh', '.fish',
    '.sql', '.graphql', '.gql', '.tf', '.tfvars', '.proto',
    '.toml', '.properties', '.xml', '.md',
  ]);

  function detectAutoSrcDirs(cwd, excludeList) {
    const excludeSet = new Set(excludeList || []);
    const candidates = new Set(DEFAULTS.srcDirs);

    // Manifest-based detection
    const pkgPath = path.join(cwd, 'package.json');
    if (fs.existsSync(pkgPath)) {
      try {
        const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
        const allDeps = { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies };
        if (allDeps.react || allDeps.next)
          for (const d of ['src', 'app', 'pages', 'components', 'hooks', 'lib', 'utils']) candidates.add(d);
        if (allDeps['@angular/core'])
          for (const d of ['src', 'projects', 'apps', 'libs']) candidates.add(d);
        if (allDeps['@nestjs/core'])
          for (const d of ['src', 'libs', 'apps']) candidates.add(d);
        if (allDeps.vue)
          for (const d of ['src', 'components', 'views', 'stores', 'composables', 'plugins']) candidates.add(d);
        if (allDeps.svelte || allDeps['@sveltejs/kit'])
          for (const d of ['src', 'lib', 'routes']) candidates.add(d);
        if (allDeps.nx || allDeps.turbo || allDeps.lerna || pkg.workspaces)
          for (const d of ['packages', 'apps', 'libs', 'services']) candidates.add(d);
      } catch (_) {}
    }
    if (fs.existsSync(path.join(cwd, 'pyproject.toml')) || fs.existsSync(path.join(cwd, 'requirements.txt')) || fs.existsSync(path.join(cwd, 'setup.py')))
      for (const d of ['src', 'app', 'apps', 'tests', 'examples', 'instance', 'blueprints']) candidates.add(d);
    if (fs.existsSync(path.join(cwd, 'Gemfile')))
      for (const d of ['app', 'lib', 'config', 'db', 'spec', 'test']) candidates.add(d);
    if (fs.existsSync(path.join(cwd, 'composer.json')))
      for (const d of ['app', 'resources', 'routes', 'database', 'tests']) candidates.add(d);
    if (fs.existsSync(path.join(cwd, 'go.mod')))
      for (const d of ['cmd', 'internal', 'pkg', 'api', 'handler', 'handlers', 'middleware', 'service']) candidates.add(d);
    if (fs.existsSync(path.join(cwd, 'Cargo.toml')))
      for (const d of ['src', 'crates', 'examples', 'tests', 'benches']) candidates.add(d);
    if (fs.existsSync(path.join(cwd, 'pubspec.yaml')))
      for (const d of ['lib', 'test', 'integration_test', 'example', 'bin']) candidates.add(d);
    if (fs.existsSync(path.join(cwd, 'Package.swift')))
      for (const d of ['Sources', 'Tests']) candidates.add(d);

    // Top-level directory scan
    try {
      const entries = fs.readdirSync(cwd, { withFileTypes: true });
      for (const entry of entries) {
        if (!entry.isDirectory() || entry.name.startsWith('.') || excludeSet.has(entry.name)) continue;
        const lname = entry.name.toLowerCase();
        if (COMMON_CODE_DIRS.has(entry.name) || COMMON_CODE_DIRS.has(lname)) { candidates.add(entry.name); continue; }
        const dirPath = path.join(cwd, entry.name);
        try {
          const subs = fs.readdirSync(dirPath, { withFileTypes: true });
          if (subs.some(s => s.isFile() && (SUPPORTED_CODE_EXTS.has(path.extname(s.name).toLowerCase()) || s.name === 'Dockerfile'))) {
            candidates.add(entry.name); continue;
          }
          if (subs.some(s => s.isDirectory() && ['src', 'lib', 'main', 'java', 'kotlin', 'scala', 'python'].includes(s.name)))
            candidates.add(entry.name);
        } catch (_) {}
      }
    } catch (_) {}

    return Array.from(candidates).filter(d => {
      try { return fs.statSync(path.join(cwd, d)).isDirectory(); } catch (_) { return false; }
    });
  }

  const BASE_CONFIG_TTL_MS = 60 * 60 * 1000;

  function loadBaseConfig(extendsVal, cwd) {
    if (!extendsVal || typeof extendsVal !== 'string') return {};
    if (extendsVal.startsWith('https://') || extendsVal.startsWith('http://')) {
      const cacheDir  = path.join(cwd, '.context', 'config-cache');
      const cacheKey  = Buffer.from(extendsVal).toString('base64').replace(/[^a-zA-Z0-9_-]/g, '_');
      const cachePath = path.join(cacheDir, `${cacheKey}.json`);
      if (fs.existsSync(cachePath)) {
        const age = Date.now() - fs.statSync(cachePath).mtimeMs;
        if (age < BASE_CONFIG_TTL_MS) {
          try { return JSON.parse(fs.readFileSync(cachePath, 'utf8')); } catch (_) {}
        }
      }
      try {
        const { execSync } = require('child_process');
        const proto = extendsVal.startsWith('https') ? 'https' : 'http';
        const out = execSync(
          `node -e "const h=require('${proto}');let d='';h.get(${JSON.stringify(extendsVal)},r=>{r.on('data',c=>d+=c);r.on('end',()=>process.stdout.write(d))}).on('error',()=>process.exit(1))"`,
          { timeout: 10000, encoding: 'utf8' }
        );
        const parsed = JSON.parse(out);
        if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
        fs.writeFileSync(cachePath, JSON.stringify(parsed), 'utf8');
        return parsed;
      } catch (err) {
        process.stderr.write(`[sigmap] config extends: could not fetch ${extendsVal}: ${err.message}\n`);
        if (fs.existsSync(cachePath)) {
          try { return JSON.parse(fs.readFileSync(cachePath, 'utf8')); } catch (_) {}
        }
        return {};
      }
    }
    const absPath = path.resolve(cwd, extendsVal);
    try { return JSON.parse(fs.readFileSync(absPath, 'utf8')); }
    catch (err) {
      process.stderr.write(`[sigmap] config extends: could not load ${absPath}: ${err.message}\n`);
      return {};
    }
  }

  /**
   * Load and merge configuration for a given working directory.
   *
   * @param {string} cwd - Project root directory
   * @returns {object} Merged config (DEFAULTS + user overrides)
   */
  function loadConfig(cwd) {
    const configPath = path.join(cwd, 'gen-context.config.json');
    if (!fs.existsSync(configPath)) {
      const cfg = deepClone(DEFAULTS);
      const detected = detectAutoSrcDirs(cwd, cfg.exclude);
      if (detected.length > 0) cfg.srcDirs = detected;
      return cfg;
    }

    let userConfig;
    try {
      const raw = fs.readFileSync(configPath, 'utf8');
      userConfig = JSON.parse(raw);
    } catch (err) {
      console.warn(`[sigmap] config parse error in ${configPath}: ${err.message}`);
      const cfg = deepClone(DEFAULTS);
      const detected = detectAutoSrcDirs(cwd, cfg.exclude);
      if (detected.length > 0) cfg.srcDirs = detected;
      return cfg;
    }

    // Warn on unknown keys (helps catch typos)
    for (const key of Object.keys(userConfig)) {
      if (key.startsWith('_') || key === 'extends') continue;
      if (!KNOWN_KEYS.has(key)) {
        console.warn(`[sigmap] unknown config key: "${key}" (ignored)`);
      }
    }

    // Deep merge: DEFAULTS → base (extends) → user config
    const baseConfig = loadBaseConfig(userConfig.extends, cwd);
    const merged = deepClone(DEFAULTS);

    for (const key of Object.keys(baseConfig)) {
      if (key.startsWith('_') || key === 'extends') continue;
      if (!KNOWN_KEYS.has(key)) continue;
      const val = baseConfig[key];
      if (val !== null && typeof val === 'object' && !Array.isArray(val) &&
          typeof merged[key] === 'object' && !Array.isArray(merged[key])) {
        merged[key] = Object.assign({}, merged[key], val);
      } else {
        merged[key] = val;
      }
    }

    for (const key of Object.keys(userConfig)) {
      if (key.startsWith('_') || key === 'extends') continue;
      if (!KNOWN_KEYS.has(key)) continue;
      const val = userConfig[key];
      if (val !== null && typeof val === 'object' && !Array.isArray(val) &&
          typeof merged[key] === 'object' && !Array.isArray(merged[key])) {
        merged[key] = Object.assign({}, merged[key], val);
      } else {
        merged[key] = val;
      }
    }

    // If user didn't specify srcDirs, auto-detect; fall back to DEFAULTS if nothing found
    if (!Array.isArray(userConfig.srcDirs)) {
      const detected = detectAutoSrcDirs(cwd, merged.exclude);
      merged.srcDirs = detected.length > 0 ? detected : deepClone(DEFAULTS.srcDirs);
    }

    // Backward compat (v3.0+): if user specified 'adapters', use it as 'outputs' too.
    // If user specified only 'outputs' (old configs), mirror to 'adapters'.
    if (merged.adapters && !Array.isArray(merged.adapters)) merged.adapters = null;
    if (!merged.adapters && Array.isArray(merged.outputs)) {
      merged.adapters = merged.outputs.slice();
    } else if (Array.isArray(merged.adapters) && !userConfig.outputs) {
      merged.outputs = merged.adapters.filter((a) => ['copilot','claude','cursor','windsurf','codex'].includes(a));
    }
    return merged;
  }

  function deepClone(obj) {
    return JSON.parse(JSON.stringify(obj));
  }

  module.exports = { loadConfig, loadBaseConfig, detectAutoSrcDirs };

};

// ── ./src/extractors/cpp ──
__factories["./src/extractors/cpp"] = function(module, exports) {
  
  /**
   * Extract signatures from C/C++ source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const stripped = src
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Classes and structs
    const classRe = /^(?:class|struct)\s+(\w+)(?:\s*:\s*(?:public|protected|private)\s+[\w:]+)?\s*\{/gm;
    for (const m of stripped.matchAll(classRe)) {
      const kind = m[0].trimStart().startsWith('class') ? 'class' : 'struct';
      sigs.push(`${kind} ${m[1]}`);
      const block = extractBlock(stripped, m.index + m[0].length);
      for (const meth of extractMembers(block)) sigs.push(`  ${meth}`);
    }
  
    // Top-level function declarations/definitions (not inside a class)
    for (const m of stripped.matchAll(/^(?!class|struct|if|for|while|switch)([\w:*&<> ]+?)\s+(\w+)\s*\(([^)]*)\)\s*(?:const\s*)?\{/gm)) {
      if (m[2].startsWith('_')) continue;
      const ret = normalizeType(m[1]);
      const retStr = ret ? ` \u2192 ${ret}` : '';
      sigs.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  function extractBlock(src, startIndex) {
    let depth = 1, i = startIndex;
    const end = Math.min(src.length, startIndex + 4000);
    while (i < end && depth > 0) {
      if (src[i] === '{') depth++;
      else if (src[i] === '}') depth--;
      i++;
    }
    return src.slice(startIndex, i - 1);
  }
  
  function extractMembers(block) {
    const members = [];
    const methodRe = /^\s+(?:virtual\s+|static\s+|inline\s+)?(?!private:|protected:|public:)([\w:*&<> ]+?)\s+(\w+)\s*\(([^)]*)\)\s*(?:const\s*)?(?:override\s*)?(?:=\s*0\s*)?;/gm;
    for (const m of block.matchAll(methodRe)) {
      if (m[2].startsWith('_')) continue;
      const ret = normalizeType(m[1]);
      const retStr = ret ? ` \u2192 ${ret}` : '';
      members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
    }
    return members.slice(0, 8);
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim().replace(/\s+/g, ' ');
  }

  function normalizeType(type) {
    if (!type) return '';
    return type.trim().replace(/\s+/g, ' ').slice(0, 30);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/csharp ──
__factories["./src/extractors/csharp"] = function(module, exports) {
  
  /**
   * Extract signatures from C# source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const stripped = src
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Classes and interfaces
    const typeRe = /^\s*(?:public\s+|internal\s+|protected\s+)?(?:abstract\s+|sealed\s+|static\s+)?(class|interface|enum|record|struct)\s+(\w+)(?:<[^{]*>)?(?:\s*:\s*[\w<>, .]+)?\s*\{/gm;
    for (const m of stripped.matchAll(typeRe)) {
      sigs.push(`${m[1]} ${m[2]}`);
      const block = extractBlock(stripped, m.index + m[0].length);
      for (const meth of extractMembers(block)) sigs.push(`  ${meth}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  function extractBlock(src, startIndex) {
    let depth = 1, i = startIndex;
    const end = Math.min(src.length, startIndex + 5000);
    while (i < end && depth > 0) {
      if (src[i] === '{') depth++;
      else if (src[i] === '}') depth--;
      i++;
    }
    return src.slice(startIndex, i - 1);
  }
  
  function extractMembers(block) {
    const members = [];
    const methodRe = /^\s+(?:public|internal|protected)\s+(?:static\s+|virtual\s+|override\s+|async\s+)*(?:where\s+\w+\s*:\s*[^\n]+\s+)?([\w<>\[\]?., ]+)\s+(\w+)\s*\(([^)]*)\)/gm;
    for (const m of block.matchAll(methodRe)) {
      const ret = normalizeType(m[1]);
      const retStr = ret ? ` \u2192 ${ret}` : '';
      members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
    }
    return members.slice(0, 8);
  }

  function normalizeParams(params) {
    if (!params) return '';
    return params.trim().replace(/\s+/g, ' ');
  }

  function normalizeType(type) {
    if (!type) return '';
    return type.trim().replace(/\s+/g, ' ').slice(0, 30);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/css ──
__factories["./src/extractors/css"] = function(module, exports) {
  
  /**
   * Extract signatures from CSS/SCSS/SASS/Less source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const stripped = src
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // CSS custom properties (variables)
    const rootMatch = stripped.match(/:root\s*\{([^}]*)\}/);
    if (rootMatch) {
      for (const m of rootMatch[1].matchAll(/(--[\w-]+)\s*:/g)) {
        sigs.push(`var ${m[1]}`);
      }
    }
  
    // SCSS/Less variables
    for (const m of stripped.matchAll(/^(\$[\w-]+)\s*:/gm)) {
      sigs.push(`$var ${m[1]}`);
    }
  
    // SCSS mixins
    for (const m of stripped.matchAll(/^@mixin\s+([\w-]+)(?:\s*\(([^)]*)\))?/gm)) {
      const params = m[2] ? `(${m[2].trim()})` : '';
      sigs.push(`@mixin ${m[1]}${params}`);
    }
  
    // SCSS functions
    for (const m of stripped.matchAll(/^@function\s+([\w-]+)\s*\(([^)]*)\)/gm)) {
      sigs.push(`@function ${m[1]}(${m[2].trim()})`);
    }
  
    // Key class names (top-level)
    const classNames = new Set();
    for (const m of stripped.matchAll(/^\.([\w-]+)(?=[^{]*\{)/gm)) {
      classNames.add(m[1]);
      if (classNames.size >= 10) break;
    }
    for (const name of classNames) sigs.push(`.${name}`);
  
    return sigs.slice(0, 25);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/dart ──
__factories["./src/extractors/dart"] = function(module, exports) {
  
  /**
   * Extract signatures from Dart source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const stripped = src
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Classes and abstract classes
    for (const m of stripped.matchAll(/^(?:abstract\s+)?class\s+(\w+)(?:<[^{]*>)?(?:\s+extends\s+[\w<>, ]+)?(?:\s+(?:implements|with|on)\s+[\w<>, ]+)?\s*\{/gm)) {
      const abs = m[0].trimStart().startsWith('abstract') ? 'abstract ' : '';
      sigs.push(`${abs}class ${m[1]}`);
      const block = extractBlock(stripped, m.index + m[0].length);
      for (const meth of extractMembers(block)) sigs.push(`  ${meth}`);
    }
  
    // Top-level functions
    // Top-level functions — capture return type (prefix before name) and show as suffix
    for (const m of stripped.matchAll(/^((?:Future<[\w<>?,\s]*>|[\w<>?]+))\s+(\w+)\s*\(([^)]*)\)/gm)) {
      if (m[2].startsWith('_')) continue;
      const retStr = (m[1] && m[1] !== 'void') ? ` \u2192 ${m[1].replace(/\s+/g, '').slice(0, 25)}` : '';
      sigs.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  function extractBlock(src, startIndex) {
    let depth = 1, i = startIndex;
    const end = Math.min(src.length, startIndex + 4000);
    while (i < end && depth > 0) {
      if (src[i] === '{') depth++;
      else if (src[i] === '}') depth--;
      i++;
    }
    return src.slice(startIndex, i - 1);
  }
  
  function extractMembers(block) {
    const members = [];
    for (const m of block.matchAll(/^\s+(?:@override\s+)?(?:@\w+\s+)*((?:Future<[\w<>?,\s]*>|[\w<>?]+))\s+(\w+)\s*\(([^)]*)\)/gm)) {
      if (m[2].startsWith('_')) continue;
      const retStr = (m[1] && m[1] !== 'void') ? ` \u2192 ${m[1].replace(/\s+/g, '').slice(0, 25)}` : '';
      members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
    }
    return members.slice(0, 8);
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim().replace(/\{[^}]*\}/g, '').replace(/\s+/g, ' ').trim();
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/dockerfile ──
__factories["./src/extractors/dockerfile"] = function(module, exports) {
  
  /**
   * Extract signatures from Dockerfiles.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const lines = src.split('\n').filter((l) => l.trim() && !l.trimStart().startsWith('#'));
  
    // FROM stages
    for (const line of lines) {
      const m = line.match(/^FROM\s+([^\s]+)(?:\s+AS\s+(\w+))?/i);
      if (m) sigs.push(`FROM ${m[1]}${m[2] ? ` AS ${m[2]}` : ''}`);
    }
  
    // EXPOSE ports
    const exposePorts = [];
    for (const line of lines) {
      const m = line.match(/^EXPOSE\s+([\d\s/]+)/i);
      if (m) exposePorts.push(...m[1].trim().split(/\s+/));
    }
    if (exposePorts.length > 0) sigs.push(`EXPOSE ${exposePorts.join(' ')}`);
  
    // ENTRYPOINT and CMD
    for (const line of lines) {
      if (/^ENTRYPOINT\s+/i.test(line)) sigs.push(line.trim());
      if (/^CMD\s+/i.test(line)) sigs.push(line.trim());
    }
  
    // ENV variables
    for (const line of lines) {
      const m = line.match(/^ENV\s+([\w]+)/i);
      if (m) sigs.push(`ENV ${m[1]}`);
    }
  
    // ARG variables
    for (const line of lines) {
      const m = line.match(/^ARG\s+([\w]+)/i);
      if (m) sigs.push(`ARG ${m[1]}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/go ──
__factories["./src/extractors/go"] = function(module, exports) {
  
  /**
   * Extract signatures from Go source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const stripped = src
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Structs
    for (const m of stripped.matchAll(/^type\s+(\w+)\s+struct\s*\{/gm)) {
      sigs.push(`type ${m[1]} struct`);
    }
  
    // Interfaces
    for (const m of stripped.matchAll(/^type\s+(\w+)\s+interface\s*\{/gm)) {
      sigs.push(`type ${m[1]} interface`);
      const block = extractBlock(stripped, m.index + m[0].length);
      for (const method of extractInterfaceMethods(block)) sigs.push(`  ${method}`);
    }
  
    // Functions and methods
    // Functions and methods — capture return type between ) and {
    for (const m of stripped.matchAll(/^func\s+(?:\((\w+)\s+[\w*]+\)\s+)?(\w+)\s*\(([^)]*)\)([^{]*)\{/gm)) {
      const receiver = m[1] ? `(${m[1]}) ` : '';
      const retType = m[4] ? m[4].trim().replace(/\s+/g, ' ') : '';
      const retStr = retType ? ` \u2192 ${retType.slice(0, 30)}` : '';
      sigs.push(`func ${receiver}${m[2]}(${normalizeParams(m[3])})${retStr}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  function extractBlock(src, startIndex) {
    let depth = 1, i = startIndex;
    const end = Math.min(src.length, startIndex + 2000);
    while (i < end && depth > 0) {
      if (src[i] === '{') depth++;
      else if (src[i] === '}') depth--;
      i++;
    }
    return src.slice(startIndex, i - 1);
  }
  
  function extractInterfaceMethods(block) {
    const methods = [];
    for (const m of block.matchAll(/^\s+(\w+)\s*\(([^)]*)\)([^\n]*)/gm)) {
      const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
      const retStr = retType ? ` \u2192 ${retType.slice(0, 30)}` : '';
      methods.push(`${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
    return methods.slice(0, 8);
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim().replace(/\s+/g, ' ');
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/gdscript ──
__factories["./src/extractors/gdscript"] = function(module, exports) {

  /**
   * Extract signatures from Godot GDScript source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */

  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];

    const stripped = src.replace(/#.*$/gm, '');

    let className = null;
    let baseName = null;
    const addedClasses = new Set();

    const cm = stripped.match(/^class_name\s+(\w+)(?:\s+extends\s+([\w.]+))?/m);
    if (cm) {
      className = cm[1];
      if (cm[2]) baseName = cm[2];
    }
    if (!baseName) {
      const em = stripped.match(/^extends\s+([\w."/]+)/m);
      if (em) baseName = em[1];
    }

    if (className) {
      sigs.push(baseName ? `class ${className}(${baseName})` : `class ${className}`);
      addedClasses.add(className);
    } else if (baseName) {
      sigs.push(`extends ${baseName}`);
    }

    const indent = (className || baseName) ? '  ' : '';

    for (const m of stripped.matchAll(/^signal\s+(\w+)(?:\s*\(([^)]*)\))?/gm)) {
      sigs.push(`${indent}signal ${m[1]}(${normalizeParams(m[2] || '')})`);
    }

    for (const m of stripped.matchAll(/^enum\s+(\w+)\s*\{([^}]*)\}/gm)) {
      const members = m[2]
        .split(',')
        .map((s) => s.trim().split(/\s*=/)[0].trim())
        .filter(Boolean);
      sigs.push(`${indent}enum ${m[1]} { ${members.slice(0, 6).join(', ')} }`);
    }

    let constCount = 0;
    for (const m of stripped.matchAll(/^(?:const|var)\s+([A-Z_]\w*)\s*(?::\s*\w+)?\s*=/gm)) {
      if (constCount++ > 15) break;
      sigs.push(`${indent}const ${m[1]}`);
    }

    for (const m of stripped.matchAll(/^(?:static\s+)?func\s+(_\w+|\w+)\s*\(([^)]*)\)(?:\s*->\s*(\w+))?/gm)) {
      if (m[1].startsWith('_')) continue;
      const retStr = m[3] ? ` → ${m[3]}` : '';
      sigs.push(`${indent}func ${m[1]}(${normalizeParams(m[2])})${retStr}`);
      if (sigs.length > 30) break;
    }

    return sigs;
  }

  function normalizeParams(raw) {
    if (!raw) return '';
    return raw
      .split(',')
      .map((p) => p.trim())
      .join(', ');
  }

  module.exports = { extract };

};

// ── ./src/extractors/html ──
__factories["./src/extractors/html"] = function(module, exports) {
  
  /**
   * Extract signatures from HTML files.
   * Focuses on id/class attributes, forms, and script tags.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    // Page title
    const titleMatch = src.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
    if (titleMatch) sigs.push(`title: ${titleMatch[1].trim()}`);
  
    // Forms with id/action
    for (const m of src.matchAll(/<form\s+([^>]*)>/gi)) {
      const attrs = m[1];
      const id = attrs.match(/id=["']?(\w+)/i);
      const action = attrs.match(/action=["']?([^"'\s>]+)/i);
      if (id) sigs.push(`form#${id[1]}${action ? ` action="${action[1]}"` : ''}`);
    }
  
    // Elements with id
    for (const m of src.matchAll(/<(\w+)\s+[^>]*id=["'](\w+)["'][^>]*>/gi)) {
      if (['html', 'head', 'body', 'script', 'style', 'link', 'meta'].includes(m[1].toLowerCase())) continue;
      sigs.push(`${m[1]}#${m[2]}`);
    }
  
    // Data attributes (data-component, data-controller etc)
    for (const m of src.matchAll(/data-(?:component|controller|view|page)=["'](\w[\w-]*)/gi)) {
      sigs.push(`data-${m[0].match(/data-(\w[\w-]*)/i)[1]}: ${m[1]}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/java ──
__factories["./src/extractors/java"] = function(module, exports) {
  
  /**
   * Extract signatures from Java source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const stripped = src
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Classes and interfaces
    const typeRegex = /^(?:public\s+|protected\s+)?(?:abstract\s+|final\s+)?(class|interface|enum)\s+(\w+)(?:\s+extends\s+[\w<>, .]+)?(?:\s+implements\s+[\w<>, .]+)?\s*\{/gm;
    for (const m of stripped.matchAll(typeRegex)) {
      sigs.push(`${m[1]} ${m[2]}`);
      const block = extractBlock(stripped, m.index + m[0].length);
      for (const meth of extractMembers(block)) sigs.push(`  ${meth}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  function extractBlock(src, startIndex) {
    let depth = 1;
    let i = startIndex;
    const end = Math.min(src.length, startIndex + 5000);
    while (i < end && depth > 0) {
      if (src[i] === '{') depth++;
      else if (src[i] === '}') depth--;
      i++;
    }
    return src.slice(startIndex, i - 1);
  }
  
  function extractMembers(block) {
    const members = [];
    const methodRe = /^\s+(?:public|protected)\s+(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:<[^>]+>\s+)?([\w<>\[\], ?.]+)\s+(\w+)\s*\(([^)]*)\)/gm;
    for (const m of block.matchAll(methodRe)) {
      const ret = normalizeType(m[1]);
      const retStr = ret ? ` \u2192 ${ret}` : '';
      members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
    }
    return members.slice(0, 8);
  }

  function normalizeParams(params) {
    if (!params) return '';
    return params.trim().replace(/\s+/g, ' ');
  }

  function normalizeType(type) {
    if (!type) return '';
    return type.trim().replace(/\s+/g, ' ').slice(0, 30);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/javascript ──
__factories["./src/extractors/javascript"] = function(module, exports) {
  
  /**
   * Extract signatures from JavaScript source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
    const returnHints = buildReturnHints(src);
  
    const stripped = src
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Classes
    const classRegex = /^(export\s+(?:default\s+)?)?class\s+(\w+)(?:\s+extends\s+[\w.]+)?\s*\{/gm;
    for (const m of stripped.matchAll(classRegex)) {
      const prefix = m[1] ? m[1].trim() + ' ' : '';
      sigs.push(`${prefix}class ${m[2]}`);
      const block = extractBlock(stripped, m.index + m[0].length);
      for (const meth of extractClassMembers(block, returnHints)) sigs.push(`  ${meth}`);
    }
  
    // Exported named functions
    for (const m of stripped.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/gm)) {
      const asyncKw = /export\s+async/.test(m[0]) ? 'async ' : '';
      const retStr = formatReturnHint(returnHints.get(m[1]));
      sigs.push(`export ${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
  
    // Exported arrow functions
    for (const m of stripped.matchAll(/^export\s+const\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*=>/gm)) {
      const asyncKw = m[0].includes('async') ? 'async ' : '';
      const retStr = formatReturnHint(returnHints.get(m[1]));
      sigs.push(`export const ${m[1]} = ${asyncKw}(${normalizeParams(m[2])}) =>${retStr}`);
    }
  
    // module.exports = { ... }
    const moduleExports = stripped.match(/^module\.exports\s*=\s*\{([^}]+)\}/m);
    if (moduleExports) {
      const names = moduleExports[1].split(',').map((s) => s.trim()).filter(Boolean);
      if (names.length > 0) sigs.push(`module.exports = { ${names.join(', ')} }`);
    }
  
    // Top-level named functions (non-exported)
    for (const m of stripped.matchAll(/^(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/gm)) {
      const asyncKw = m[0].startsWith('async') ? 'async ' : '';
      const retStr = formatReturnHint(returnHints.get(m[1]));
      sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  function extractBlock(src, startIndex) {
    let depth = 1;
    let i = startIndex;
    const end = Math.min(src.length, startIndex + 4000);
    while (i < end && depth > 0) {
      if (src[i] === '{') depth++;
      else if (src[i] === '}') depth--;
      i++;
    }
    return src.slice(startIndex, i - 1);
  }
  
  function extractClassMembers(block, returnHints) {
    const members = [];
    for (const m of block.matchAll(/^\s+(?:static\s+|async\s+|get\s+|set\s+)*(\w+)\s*\(([^)]*)\)\s*\{/gm)) {
      if (/^_/.test(m[1])) continue;
      if (m[1] === 'constructor') { members.push(`constructor(${normalizeParams(m[2])})`); continue; }
      const isAsync = m[0].includes('async ') ? 'async ' : '';
      const isStatic = m[0].includes('static ') ? 'static ' : '';
      const retStr = formatReturnHint(returnHints.get(m[1]));
      members.push(`${isStatic}${isAsync}${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
    return members.slice(0, 8);
  }

  function buildReturnHints(src) {
    const hints = new Map();
    for (const m of src.matchAll(/\/\*\*[\s\S]*?@returns?\s+\{([^}]+)\}[\s\S]*?\*\/\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/g)) {
      hints.set(m[2], normalizeType(m[1]));
    }
    for (const m of src.matchAll(/\/\*\*[\s\S]*?@returns?\s+\{([^}]+)\}[\s\S]*?\*\/\s*export\s+const\s+(\w+)\s*=\s*(?:async\s+)?\(/g)) {
      hints.set(m[2], normalizeType(m[1]));
    }
    for (const m of src.matchAll(/\/\*\*[\s\S]*?@returns?\s+\{([^}]+)\}[\s\S]*?\*\/\s*(?:static\s+|async\s+|get\s+|set\s+)*(\w+)\s*\(/g)) {
      hints.set(m[2], normalizeType(m[1]));
    }
    return hints;
  }

  function normalizeType(type) {
    if (!type) return '';
    return type.trim().replace(/\s+/g, ' ').slice(0, 25);
  }

  function formatReturnHint(type) {
    return type ? ` \u2192 ${type}` : '';
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim().replace(/\s+/g, ' ');
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/kotlin ──
__factories["./src/extractors/kotlin"] = function(module, exports) {
  
  /**
   * Extract signatures from Kotlin source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const stripped = src
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Classes, objects, interfaces
    for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:data\s+|sealed\s+|abstract\s+|open\s+)?(class|object|interface)\s+(\w+)(?:[^{]*)\{/gm)) {
      sigs.push(`${m[1]} ${m[2]}`);
      const block = extractBlock(stripped, m.index + m[0].length);
      for (const meth of extractMembers(block)) sigs.push(`  ${meth}`);
    }
  
    // Top-level functions
    // Top-level functions — capture `: RetType` after params
    for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:suspend\s+)?fun\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)(?:\s*:\s*([^\n{=]+))?/gm)) {
      const suspend = m[0].includes('suspend') ? 'suspend ' : '';
      const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
      const retStr = retType ? ` \u2192 ${retType.slice(0, 25)}` : '';
      sigs.push(`${suspend}fun ${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  function extractBlock(src, startIndex) {
    let depth = 1, i = startIndex;
    const end = Math.min(src.length, startIndex + 4000);
    while (i < end && depth > 0) {
      if (src[i] === '{') depth++;
      else if (src[i] === '}') depth--;
      i++;
    }
    return src.slice(startIndex, i - 1);
  }
  
  function extractMembers(block) {
    const members = [];
    for (const m of block.matchAll(/^\s+(?:public\s+|internal\s+|override\s+)?(?:suspend\s+)?fun\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)(?:\s*:\s*([^\n{=]+))?/gm)) {
      if (m[1].startsWith('_')) continue;
      const suspend = m[0].includes('suspend') ? 'suspend ' : '';
      const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
      const retStr = retType ? ` \u2192 ${retType.slice(0, 25)}` : '';
      members.push(`${suspend}fun ${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
    return members.slice(0, 8);
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim()
      .split(',')
      .map((p) => p.trim().split(':')[0].trim())
      .filter(Boolean)
      .join(', ');
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/php ──
__factories["./src/extractors/php"] = function(module, exports) {
  
  /**
   * Extract signatures from PHP source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const stripped = src
      .replace(/\/\/.*$/gm, '')
      .replace(/#.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Classes and interfaces
    const typeRe = /^(?:abstract\s+)?(?:class|interface|trait)\s+(\w+)(?:\s+extends\s+\w+)?(?:\s+implements\s+[\w, ]+)?\s*\{/gm;
    for (const m of stripped.matchAll(typeRe)) {
      const kind = m[0].trimStart().startsWith('interface') ? 'interface' :
        m[0].trimStart().startsWith('trait') ? 'trait' : 'class';
      sigs.push(`${kind} ${m[1]}`);
      const block = extractBlock(stripped, m.index + m[0].length);
      for (const meth of extractMembers(block)) sigs.push(`  ${meth}`);
    }
  
    // Top-level functions
    for (const m of stripped.matchAll(/^function\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*([^\n{]+))?/gm)) {
      const ret = normalizeType(m[3]);
      const retStr = ret ? ` \u2192 ${ret}` : '';
      sigs.push(`function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  function extractBlock(src, startIndex) {
    let depth = 1, i = startIndex;
    const end = Math.min(src.length, startIndex + 4000);
    while (i < end && depth > 0) {
      if (src[i] === '{') depth++;
      else if (src[i] === '}') depth--;
      i++;
    }
    return src.slice(startIndex, i - 1);
  }
  
  function extractMembers(block) {
    const members = [];
    const methodRe = /^\s+(?:public|protected)\s+(?:static\s+)?function\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*([^\n{]+))?/gm;
    for (const m of block.matchAll(methodRe)) {
      if (m[1].startsWith('_')) continue;
      const isStatic = m[0].includes('static ') ? 'static ' : '';
      const ret = normalizeType(m[3]);
      const retStr = ret ? ` \u2192 ${ret}` : '';
      members.push(`${isStatic}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
    return members.slice(0, 8);
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim().replace(/\s+/g, ' ');
  }

  function normalizeType(type) {
    if (!type) return '';
    return type.trim().replace(/[;\s]+$/g, '').replace(/\s+/g, ' ').slice(0, 25);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/python ──
__factories["./src/extractors/python"] = function(module, exports) {
  
  /**
   * Extract signatures from Python source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    // noComments: strip only # comments, keep docstrings (needed for @decorator detection)
    const noComments = src.replace(/#.*$/gm, '');
    // stripped: also strip docstrings (safe for regex matching)
    const stripped = noComments
      .replace(/"""[\s\S]*?"""/g, '')
      .replace(/'''[\s\S]*?'''/g, '');
  
    // Classes
    for (const m of stripped.matchAll(/^class\s+(\w+)(?:\s*\(([^)]*)\))?\s*:/gm)) {
      const className = m[1];
      const baseName = m[2] ? m[2].trim() : '';
      const bodyStart = m.index + m[0].length;
  
      // Try @dataclass collapse
      const dcFields = tryExtractDataclassFields(stripped, m.index);
      if (dcFields !== null) {
        sigs.push(`@dataclass ${className}(${dcFields})`);
        continue;
      }
  
      // Try BaseModel/BaseSettings collapse
      if (/(BaseModel|BaseSettings)/.test(baseName)) {
        const bmFields = tryExtractBaseModelFields(stripped, bodyStart);
        if (bmFields) {
          sigs.push(`class ${className}(${baseName}) ${bmFields}`);
          continue;
        }
      }
  
      const baseStr = baseName ? `(${baseName})` : '';
      sigs.push(`class ${className}${baseStr}`);
  
      // Class-level ALL_CAPS constants
      for (const c of extractClassConstants(stripped, bodyStart)) {
        sigs.push(`  ${c}`);
      }
  
      // Methods
      const methods = extractClassMethods(stripped, bodyStart);
      for (const meth of methods) sigs.push(`  ${meth}`);
    }
  
    // Top-level functions
    for (const m of stripped.matchAll(/^((?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*[^:]+)?)\s*:/gm)) {
      if (/^_/.test(m[2])) continue;
      const asyncKw = m[1].trimStart().startsWith('async') ? 'async ' : '';
      const params = normalizeParams(m[3]);
      const retType = extractReturnType(m[1] + ':');
      const retStr = retType ? ` \u2192 ${retType}` : '';
      sigs.push(`${asyncKw}def ${m[2]}(${params})${retStr}`);
    }

    // FastAPI router endpoints: @router.METHOD("path") + async def name(...)
    const lines = noComments.split('\n');
    for (let i = 0; i < lines.length - 1; i++) {
      const decLine = lines[i].trim();
      const rm = decLine.match(/^@\w+\.(get|post|put|patch|delete|head)\s*\(\s*['"]([^'"]+)['"]/);
      if (!rm) continue;
      // Find the next non-empty, non-decorator line that starts the function
      for (let j = i + 1; j < Math.min(i + 6, lines.length); j++) {
        const fl = lines[j].trim();
        const fm = fl.match(/^(?:async\s+)?def\s+(\w+)/);
        if (fm) {
          sigs.push(`${rm[1].toUpperCase()} ${rm[2]}  →  ${fm[1]}()`);
          break;
        }
        if (fl && !fl.startsWith('@') && !fl.startsWith('#')) break;
      }
    }
  
    return sigs.slice(0, 30);
  }
  
  function extractClassMethods(stripped, startIndex) {
    const methods = [];
    const lines = stripped.slice(startIndex).split('\n');
    for (const line of lines) {
      if (line.trim() === '') continue;
      const indent = line.match(/^(\s+)/);
      if (!indent) break;
      const m = line.match(/^\s+(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*([^:]+))?\s*:/);
      if (m) {
        if (m[1].startsWith('__') && m[1] !== '__init__') continue;
        if (m[1].startsWith('_') && !m[1].startsWith('__')) continue;
        const asyncKw = line.trimStart().startsWith('async') ? 'async ' : '';
        const params = normalizeParams(m[2]).replace(/^self,?\s*/, '').replace(/^cls,?\s*/, '');
        const retType = m[3] ? m[3].trim().replace(/Optional\[([^\]]+)\]/, '$1|None').slice(0, 30) : '';
        const retStr = retType ? ` \u2192 ${retType}` : '';
        methods.push(`${asyncKw}def ${m[1]}(${params})${retStr}`);
      }
    }
    return methods.slice(0, 8);
  }
  
  function tryExtractDataclassFields(stripped, classIndex) {
    const before = stripped.slice(Math.max(0, classIndex - 120), classIndex);
    if (!/@dataclass/.test(before)) return null;
    const lines = stripped.slice(classIndex).split('\n');
    const fields = [];
    for (const line of lines.slice(1)) {
      if (line.trim() === '') continue;
      if (!line.match(/^\s+/)) break;
      const f = line.match(/^\s+(\w+)\s*:\s*([^=\n]+?)(?:\s*=.*)?$/);
      if (!f) break;
      const isOptional = f[2].includes('Optional') || /=\s*None/.test(line);
      fields.push(isOptional ? `${f[1]}?` : f[1]);
    }
    return fields.length ? fields.join(', ') : null;
  }
  
  function tryExtractBaseModelFields(stripped, bodyStart) {
    const lines = stripped.slice(bodyStart, bodyStart + 800).split('\n');
    const fields = [];
    let foundFirst = false;
    for (const line of lines) {
      if (line.trim() === '') continue;
      if (foundFirst && !/^\s/.test(line)) break;
      if (!line.match(/^\s{4}\w/)) continue;
      foundFirst = true;
      const f = line.match(/^\s{4}(\w+)\s*(?::\s*([^=\n]+?))?(?:\s*=\s*(.*))?$/);
      if (!f || f[1].startsWith('_') || f[1] === 'class' || f[1] === 'def') continue;
      const isOptional = (f[2] || '').includes('Optional') || f[3] !== undefined;
      fields.push(isOptional ? `${f[1]}?` : `${f[1]}*`);
    }
    return fields.length ? `{${fields.slice(0, 6).join(', ')}}` : null;
  }
  
  function extractClassConstants(stripped, startIndex) {
    const lines = stripped.slice(startIndex).split('\n');
    const consts = [];
    for (const line of lines) {
      if (!line.match(/^\s+/)) break;
      const c = line.match(/^\s+([A-Z][A-Z0-9_]{2,})\s*=\s*(.+)/);
      if (!c) continue;
      let val = c[2].trim();
      const items = (val.match(/"([^"]+)"/g) || val.match(/'([^']+)'/g) || []);
      if (items.length > 3) val = `[${items[0].replace(/['"]/g, '')}..${items[items.length - 1].replace(/['"]/g, '')}]`;
      if (val.length > 40) val = val.slice(0, 37) + '...';
      consts.push(`${c[1]}=${val}`);
    }
    return consts.slice(0, 3);
  }
  
  function extractReturnType(sigLine) {
    const m = sigLine.match(/->\s*([^:]+):/);
    if (!m) return '';
    let rt = m[1].trim();
    rt = rt.replace(/Optional\[([^\]]+)\]/, '$1|None');
    return rt.length > 30 ? rt.slice(0, 27) + '...' : rt;
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim()
      .split(',')
      .map((p) => {
        const part = p.trim();
        if (!part) return '';
        const stars = part.match(/^(\*{1,2})/)?.[1] || '';
        const rest = part.slice(stars.length);
        const eqIdx = rest.indexOf('=');
        const noDefault = eqIdx !== -1 ? rest.slice(0, eqIdx).trim() : rest.trim();
        const clean = noDefault
          .replace(/Optional\[([^\]]+)\]/, '$1?')
          .replace(/Union\[([^\]]+),\s*None\]/, '$1?');
        return stars + clean;
      })
      .filter((p) => p && p !== 'self' && p !== 'cls')
      .join(', ');
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/ruby ──
__factories["./src/extractors/ruby"] = function(module, exports) {
  
  /**
   * Extract signatures from Ruby source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const stripped = src.replace(/#.*$/gm, '');
  
    // Modules and classes
    for (const m of stripped.matchAll(/^(?:module|class)\s+([\w:]+)(?:\s*<\s*[\w:]+)?\s*$/gm)) {
      const kind = m[0].trimStart().startsWith('module') ? 'module' : 'class';
      sigs.push(`${kind} ${m[1]}`);
    }
  
    // Public methods (not private/protected)
    for (const m of stripped.matchAll(/^\s+def\s+(?:self\.)?(\w+)(?:\s*\(([^)]*)\))?/gm)) {
      if (m[1].startsWith('_')) continue;
      const params = m[2] ? `(${normalizeParams(m[2])})` : '';
      const selfPrefix = m[0].includes('self.') ? 'self.' : '';
      const retStr = extractReturnHint(stripped, m.index);
      sigs.push(`  def ${selfPrefix}${m[1]}${params}${retStr}`);
    }
  
    // Top-level def
    for (const m of stripped.matchAll(/^def\s+(\w+)(?:\s*\(([^)]*)\))?/gm)) {
      if (m[1].startsWith('_')) continue;
      const params = m[2] ? `(${normalizeParams(m[2])})` : '';
      const retStr = extractReturnHint(stripped, m.index);
      sigs.push(`def ${m[1]}${params}${retStr}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim().replace(/\s+/g, ' ');
  }

  function extractReturnHint(stripped, index) {
    const start = Math.max(0, index - 180);
    const before = stripped.slice(start, index);
    const m = before.match(/sig\s*\{[\s\S]*?returns\(([^)]+)\)[\s\S]*?\}\s*$/);
    if (!m) return '';
    const type = m[1].trim().replace(/\s+/g, ' ').slice(0, 25);
    return type ? ` \u2192 ${type}` : '';
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/rust ──
__factories["./src/extractors/rust"] = function(module, exports) {
  
  /**
   * Extract signatures from Rust source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const stripped = src
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Structs
    for (const m of stripped.matchAll(/^pub\s+struct\s+(\w+)(?:<[^{]*>)?/gm)) {
      sigs.push(`pub struct ${m[1]}`);
    }
  
    // Enums
    for (const m of stripped.matchAll(/^pub\s+enum\s+(\w+)(?:<[^{]*>)?/gm)) {
      sigs.push(`pub enum ${m[1]}`);
    }
  
    // Traits
    for (const m of stripped.matchAll(/^pub\s+trait\s+(\w+)(?:<[^{]*>)?/gm)) {
      sigs.push(`pub trait ${m[1]}`);
    }
  
    // impl blocks
    for (const m of stripped.matchAll(/^impl(?:<[^>]*>)?\s+(?:[\w:]+\s+for\s+)?(\w+)(?:<[^{]*>)?\s*\{/gm)) {
      sigs.push(`impl ${m[1]}`);
      const block = extractBlock(stripped, m.index + m[0].length);
      for (const fn of extractMethods(block)) sigs.push(`  ${fn}`);
    }
  
    // Top-level pub fns
    // Top-level pub fns — capture everything after ) up to { or ; for return type
    for (const m of stripped.matchAll(/^pub(?:\s+async)?\s+fn\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{;]*)/gm)) {
      const asyncKw = m[0].includes('async') ? 'async ' : '';
      const retStr = extractReturnType(m[3]);
      sigs.push(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  function extractBlock(src, startIndex) {
    let depth = 1, i = startIndex;
    const end = Math.min(src.length, startIndex + 5000);
    while (i < end && depth > 0) {
      if (src[i] === '{') depth++;
      else if (src[i] === '}') depth--;
      i++;
    }
    return src.slice(startIndex, i - 1);
  }
  
  function extractMethods(block) {
    const methods = [];
    for (const m of block.matchAll(/^\s+pub(?:\s+async)?\s+fn\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{;]*)/gm)) {
      const asyncKw = m[0].includes('async') ? 'async ' : '';
      const retStr = extractReturnType(m[3]);
      methods.push(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
    return methods.slice(0, 8);
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim().replace(/\s+/g, ' ');
  }
  
    function extractReturnType(afterParen) {
      if (!afterParen) return '';
      const m = afterParen.match(/->\s*([^{;]+)/);
      if (!m) return '';
      const rt = m[1].trim().replace(/\s+/g, ' ');
      return ` \u2192 ${rt.length > 30 ? rt.slice(0, 27) + '...' : rt}`;
    }

  module.exports = { extract };
  
};

// ── ./src/extractors/scala ──
__factories["./src/extractors/scala"] = function(module, exports) {
  
  /**
   * Extract signatures from Scala source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const stripped = src
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Classes, traits, objects
    const typeRe = /^(?:case\s+)?(?:class|trait|object)\s+(\w+)(?:\[[\w, ]+\])?(?:[^{]*)\{/gm;
    for (const m of stripped.matchAll(typeRe)) {
      const kind = m[0].trimStart().startsWith('case class') ? 'case class' :
        m[0].trimStart().startsWith('trait') ? 'trait' :
        m[0].trimStart().startsWith('object') ? 'object' : 'class';
      sigs.push(`${kind} ${m[1]}`);
      const block = extractBlock(stripped, m.index + m[0].length);
      for (const fn of extractMembers(block)) sigs.push(`  ${fn}`);
    }
  
    // Top-level defs
    for (const m of stripped.matchAll(/^def\s+(\w+)(?:\[[\w, ]+\])?\s*(?:\(([^)]*)\))?(?:\s*:\s*([^=\n{]+))?/gm)) {
      if (m[1].startsWith('_')) continue;
      const params = m[2] ? `(${normalizeParams(m[2])})` : '';
      const ret = normalizeType(m[3]);
      const retStr = ret ? ` \u2192 ${ret}` : '';
      sigs.push(`def ${m[1]}${params}${retStr}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  function extractBlock(src, startIndex) {
    let depth = 1, i = startIndex;
    const end = Math.min(src.length, startIndex + 4000);
    while (i < end && depth > 0) {
      if (src[i] === '{') depth++;
      else if (src[i] === '}') depth--;
      i++;
    }
    return src.slice(startIndex, i - 1);
  }
  
  function extractMembers(block) {
    const members = [];
    for (const m of block.matchAll(/^\s+def\s+(\w+)(?:\[[\w, ]+\])?\s*(?:\(([^)]*)\))?(?:\s*:\s*([^=\n{]+))?/gm)) {
      if (m[1].startsWith('_')) continue;
      const params = m[2] ? `(${normalizeParams(m[2])})` : '';
      const ret = normalizeType(m[3]);
      const retStr = ret ? ` \u2192 ${ret}` : '';
      members.push(`def ${m[1]}${params}${retStr}`);
    }
    return members.slice(0, 8);
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim()
      .split(',')
      .map((p) => p.trim().split(':')[0].trim())
      .filter(Boolean)
      .join(', ');
  }

  function normalizeType(type) {
    if (!type) return '';
    return type.trim().replace(/\s+/g, ' ').slice(0, 25);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/shell ──
__factories["./src/extractors/shell"] = function(module, exports) {
  
  /**
   * Extract signatures from shell scripts (bash, zsh, fish).
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const stripped = src.replace(/#.*$/gm, '');
  
    // Function definitions (bash: name() { and function name {)
    for (const m of stripped.matchAll(/^(?:function\s+)?([\w:-]+)\s*\(\s*\)\s*\{/gm)) {
      if (m[1].startsWith('_')) continue;
      sigs.push(`function ${m[1]}()`);
    }
  
    // Main entry point patterns
    if (/^\s*main\s*\(/m.test(src) || /^\s*main\s+"?\$@/m.test(src)) {
      sigs.push('main "$@"');
    }
  
    // Exported variables
    for (const m of stripped.matchAll(/^export\s+([\w]+)=/gm)) {
      sigs.push(`export ${m[1]}`);
    }
  
    // Script description (first non-comment line after shebang)
    const lines = src.split('\n');
    for (const line of lines.slice(0, 5)) {
      if (line.startsWith('#!')) continue;
      if (line.startsWith('#')) {
        const desc = line.replace(/^#+\s*/, '').trim();
        if (desc) { sigs.unshift(`# ${desc}`); break; }
      }
    }
  
    return sigs.slice(0, 25);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/svelte ──
__factories["./src/extractors/svelte"] = function(module, exports) {
  
  /**
   * Extract signatures from Svelte components.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    // Extract <script> block
    const scriptMatch = src.match(/<script(?:\s[^>]*)?>(?:\s*)([\s\S]*?)<\/script>/i);
    if (!scriptMatch) return sigs;
  
    const script = scriptMatch[1]
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Exported props (writable)
    for (const m of script.matchAll(/^\s+export\s+let\s+(\w+)(?:\s*=\s*[^;]+)?;/gm)) {
      sigs.push(`export let ${m[1]}`);
    }
  
    // Exported functions
    for (const m of script.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
      const asyncKw = m[0].includes('async') ? 'async ' : '';
      const retStr = m[3] ? ` \u2192 ${normalizeType(m[3])}` : '';
      sigs.push(`export ${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
  
    // Top-level functions
    for (const m of script.matchAll(/^(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
      if (m[1].startsWith('_')) continue;
      const asyncKw = m[0].startsWith('async') ? 'async ' : '';
      const retStr = m[3] ? ` \u2192 ${normalizeType(m[3])}` : '';
      sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
  
    // Reactive declarations $:
    for (const m of script.matchAll(/^\s+\$:\s+(\w+)\s*=/gm)) {
      sigs.push(`$: ${m[1]}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim().replace(/\s+/g, ' ');
  }

  function normalizeType(type) {
    if (!type) return '';
    return type.trim().replace(/[;\s]+$/g, '').replace(/\s+/g, ' ').slice(0, 25);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/swift ──
__factories["./src/extractors/swift"] = function(module, exports) {
  
  /**
   * Extract signatures from Swift source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const stripped = src
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Classes, structs, protocols, enums
    const typeRe = /^(?:public\s+|internal\s+|open\s+)?(?:final\s+)?(class|struct|protocol|enum|actor)\s+(\w+)(?:<[^{]*>)?(?:\s*:\s*[\w, <>.]+)?\s*\{/gm;
    for (const m of stripped.matchAll(typeRe)) {
      sigs.push(`${m[1]} ${m[2]}`);
      const block = extractBlock(stripped, m.index + m[0].length);
      for (const fn of extractMembers(block)) sigs.push(`  ${fn}`);
    }
  
    // Top-level public functions
    // Top-level public functions — capture everything after ) to end of line for arrow type
    for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:static\s+)?(?:async\s+)?func\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{\n]*)/gm)) {
      const asyncKw = m[0].includes('async') ? 'async ' : '';
      const retStr = extractArrowType(m[3]);
      sigs.push(`${asyncKw}func ${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
  
    return sigs.slice(0, 25);
  }
  
  function extractBlock(src, startIndex) {
    let depth = 1, i = startIndex;
    const end = Math.min(src.length, startIndex + 4000);
    while (i < end && depth > 0) {
      if (src[i] === '{') depth++;
      else if (src[i] === '}') depth--;
      i++;
    }
    return src.slice(startIndex, i - 1);
  }
  
  function extractMembers(block) {
    const members = [];
    for (const m of block.matchAll(/^\s+(?:public\s+|internal\s+|open\s+)?(?:static\s+|class\s+)?(?:mutating\s+)?(?:async\s+)?func\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{\n]*)/gm)) {
      if (m[1].startsWith('_')) continue;
      const asyncKw = m[0].includes('async') ? 'async ' : '';
      const retStr = extractArrowType(m[3]);
      members.push(`${asyncKw}func ${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
    return members.slice(0, 8);
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim()
      .split(',')
      .map((p) => p.trim().split(':')[0].trim())
      .filter(Boolean)
      .join(', ');
  }
  
    function extractArrowType(str) {
      if (!str) return '';
      const m = str.match(/->\s*([^\n{]+)/);
      if (!m) return '';
      const rt = m[1].trim().replace(/\s+/g, ' ');
      return ` \u2192 ${rt.length > 25 ? rt.slice(0, 22) + '...' : rt}`;
    }

  module.exports = { extract };
  
};

// ── ./src/extractors/typescript ──
__factories["./src/extractors/typescript"] = function(module, exports) {
  
  /**
   * Extract signatures from TypeScript source code.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    // Strip single-line comments
    const stripped = src
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Exported interfaces
    for (const m of stripped.matchAll(/^export\s+interface\s+(\w+)(?:<[^{]*>)?\s*(?:extends\s+[^{]+)?\{/gm)) {
      sigs.push(`export interface ${m[1]}`);
      // Collect members
      const start = m.index + m[0].length;
      const block = extractBlock(stripped, start);
      const members = extractInterfaceMembers(block);
      for (const mem of members) sigs.push(`  ${mem}`);
    }
  
    // Exported type aliases
    for (const m of stripped.matchAll(/^export\s+type\s+(\w+)(?:<[^=]*>)?\s*=/gm)) {
      sigs.push(`export type ${m[1]}`);
    }
  
    // Exported enums
    for (const m of stripped.matchAll(/^export\s+(?:const\s+)?enum\s+(\w+)\s*\{/gm)) {
      sigs.push(`export enum ${m[1]}`);
    }
  
    // Classes (exported and internal)
    const classRegex = /^(export\s+)?(abstract\s+)?class\s+(\w+)(?:<[^{]*>)?(?:\s+extends\s+[\w<>, .]+)?(?:\s+implements\s+[\w<> ,]+)?\s*\{/gm;
    for (const m of stripped.matchAll(classRegex)) {
      const prefix = m[1] ? 'export ' : '';
      const abs = m[2] ? 'abstract ' : '';
      sigs.push(`${prefix}${abs}class ${m[3]}`);
      const start = m.index + m[0].length;
      const block = extractBlock(stripped, start);
      const methods = extractClassMembers(block);
      for (const meth of methods) sigs.push(`  ${meth}`);
    }
  
    // Exported top-level functions (not methods)
    for (const m of stripped.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)(?:\s*:\s*[^{]+)?\s*\{/gm)) {
      const asyncKw = /export\s+async/.test(m[0]) ? 'async ' : '';
      const params = normalizeParams(m[2]);
      const retMatch = m[0].match(/\)\s*:\s*([^{]+)\s*\{/);
      const retType = retMatch ? retMatch[1].trim().replace(/\s+/g, ' ').slice(0, 30) : '';
      const retStr = retType ? ` \u2192 ${retType}` : '';
      sigs.push(`export ${asyncKw}function ${m[1]}(${params})${retStr}`);
    }
  
    // Exported arrow functions / const functions
    for (const m of stripped.matchAll(/^export\s+const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*[^=>{]+)?\s*=>/gm)) {
      const asyncKw = /=\s*async\s+/.test(m[0]) ? 'async ' : '';
      const params = normalizeParams(m[2]);
      sigs.push(`export const ${m[1]} = ${asyncKw}(${params}) =>`);
    }

    // Zustand stores: export const useXxxStore = create<State>()(...)
    for (const m of stripped.matchAll(/^export\s+const\s+(use\w+Store)\s*=\s*create(?:<[^>]*>)?\s*\(/gm)) {
      // Extract the State interface name from create<StateName>
      const stateType = m[0].match(/create<([\w]+)>/)?.[1] || '';
      sigs.push(`export const ${m[1]} = create<${stateType}>(...)`);
      // Extract action/state keys from the embedded interface in same file
      const ifaceRe = new RegExp(`interface\\s+${stateType}\\s*\\{([\\s\\S]*?)\\}`);
      const ifm = stripped.match(ifaceRe);
      if (ifm) {
        for (const fm of ifm[1].matchAll(/^\s+(\w+)\s*(?:\([^)]*\))?\s*:/gm)) {
          sigs.push(`  ${fm[1]}`);
        }
      }
    }

    // API client objects: export default { method: async (...) => ... }
    // Pattern: const xxxApi = { methodName: async ... }
    for (const m of stripped.matchAll(/^(?:export\s+default\s+|const\s+)(\w*[Aa]pi\w*)\s*=\s*\{/gm)) {
      const apiName = m[1];
      const start = m.index + m[0].length;
      const block = extractBlock(stripped, start);
      const methods = [];
      for (const mm of block.matchAll(/^\s+(\w+)\s*:\s*(?:async\s+)?(?:\([^)]*\)|\w+)\s*=>/gm)) {
        methods.push(mm[1]);
      }
      if (methods.length) sigs.push(`${apiName}: { ${methods.join(', ')} }`);
    }
  
    return sigs.slice(0, 35);
  }
  
  function extractBlock(src, startIndex) {
    let depth = 1;
    let i = startIndex;
    const end = Math.min(src.length, startIndex + 4000);
    while (i < end && depth > 0) {
      if (src[i] === '{') depth++;
      else if (src[i] === '}') depth--;
      i++;
    }
    return src.slice(startIndex, i - 1);
  }
  
  function extractInterfaceMembers(block) {
    const members = [];
    for (const m of block.matchAll(/^\s+(readonly\s+)?(\w+)(\??):\s*([^;]+);/gm)) {
      const readonly = m[1] ? 'readonly ' : '';
      const optional = m[3] ? '?' : '';
      const typeStr = m[4].trim().replace(/\s+/g, ' ').slice(0, 35);
      members.push(`${readonly}${m[2]}${optional}: ${typeStr}`);
    }
    for (const m of block.matchAll(/^\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)\s*:/gm)) {
      members.push(`${m[1]}(${normalizeParams(m[2])})`);
    }
    return members.slice(0, 8);
  }
  
  function extractClassMembers(block) {
    const members = [];
    // Public methods (skip private/protected/_ prefixed)
    const methodRe = /^\s+(?:public\s+|static\s+|async\s+|override\s+)*(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)(?:\s*:\s*[^{;]+)?\s*\{/gm;
    for (const m of block.matchAll(methodRe)) {
      if (/^(private|protected|_)/.test(m[1])) continue;
      if (m[1] === 'constructor') { members.push(`constructor(${normalizeParams(m[2])})`); continue; }
      const isAsync = m[0].includes('async ') ? 'async ' : '';
      const isStatic = m[0].includes('static ') ? 'static ' : '';
      const retMatch = m[0].match(/\)\s*:\s*([^{;]+)\s*\{/);
      const retType = retMatch ? retMatch[1].trim().replace(/\s+/g, ' ').slice(0, 20) : '';
      const retStr = retType ? ` \u2192 ${retType}` : '';
      members.push(`${isStatic}${isAsync}${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
    return members.slice(0, 8);
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim().replace(/\s+/g, ' ').replace(/:[^,)]+/g, '').trim();
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/graphql ──
__factories["./src/extractors/graphql"] = function(module, exports) {
  
  'use strict';
  
  /**
   * Extract signatures from GraphQL schema / operation files.
   * Captures type, interface, enum, input, union, scalar, query, mutation,
   * subscription, fragment definitions.
   *
   * @param {string} src - Raw GraphQL content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    // Strip comments (# style)
    const stripped = src.replace(/#[^\n]*/g, '');
  
    // Schema type definitions: type Foo [implements Bar] { ... }
    for (const m of stripped.matchAll(
      /\b(type|interface|input)\s+(\w+)(?:\s+implements\s+([\w\s&]+))?\s*\{/g
    )) {
      const implements_ = m[3] ? ` implements ${m[3].trim().replace(/\s+/g, ' ')}` : '';
      sigs.push(`${m[1]} ${m[2]}${implements_}`);
    }
  
    // enum
    for (const m of stripped.matchAll(/\benum\s+(\w+)\s*\{/g)) {
      sigs.push(`enum ${m[1]}`);
    }
  
    // union
    for (const m of stripped.matchAll(/\bunion\s+(\w+)\s*=/g)) {
      sigs.push(`union ${m[1]}`);
    }
  
    // scalar
    for (const m of stripped.matchAll(/\bscalar\s+(\w+)/g)) {
      sigs.push(`scalar ${m[1]}`);
    }
  
    // extend type / extend interface
    for (const m of stripped.matchAll(/\bextend\s+(type|interface)\s+(\w+)/g)) {
      sigs.push(`extend ${m[1]} ${m[2]}`);
    }
  
    // Query / Mutation / Subscription operations
    for (const m of stripped.matchAll(
      /\b(query|mutation|subscription)\s+(\w+)\s*(?:\([^)]*\))?\s*\{/g
    )) {
      sigs.push(`${m[1]} ${m[2]}`);
    }
  
    // Named fragments
    for (const m of stripped.matchAll(/\bfragment\s+(\w+)\s+on\s+(\w+)/g)) {
      sigs.push(`fragment ${m[1]} on ${m[2]}`);
    }
  
    // Top-level schema { query: ... }
    if (/\bschema\s*\{/.test(stripped)) {
      sigs.push('schema { ... }');
    }
  
    return sigs;
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/protobuf ──
__factories["./src/extractors/protobuf"] = function(module, exports) {
  
  'use strict';
  
  /**
   * Extract signatures from Protocol Buffer (.proto) files.
   * Captures message, enum, service, rpc, oneof, extend definitions.
   *
   * @param {string} src - Raw .proto content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    // Strip single-line and block comments
    const stripped = src
      .replace(/\/\/[^\n]*/g, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // syntax / package / option (top-level metadata)
    const syntaxM = stripped.match(/\bsyntax\s*=\s*"([^"]+)"/);
    if (syntaxM) sigs.push(`syntax = "${syntaxM[1]}"`);
  
    const pkgM = stripped.match(/\bpackage\s+([\w.]+)\s*;/);
    if (pkgM) sigs.push(`package ${pkgM[1]}`);
  
    // message <Name> { ... }
    for (const m of stripped.matchAll(/\bmessage\s+(\w+)\s*\{/g)) {
      sigs.push(`message ${m[1]}`);
    }
  
    // enum <Name> { ... }
    for (const m of stripped.matchAll(/\benum\s+(\w+)\s*\{/g)) {
      sigs.push(`enum ${m[1]}`);
    }
  
    // service <Name> { ... }
    for (const m of stripped.matchAll(/\bservice\s+(\w+)\s*\{/g)) {
      sigs.push(`service ${m[1]}`);
    }
  
    // rpc <Name>(<Request>) returns (<Response>)
    for (const m of stripped.matchAll(
      /\brpc\s+(\w+)\s*\(\s*(stream\s+)?(\w+)\s*\)\s+returns\s*\(\s*(stream\s+)?(\w+)\s*\)/g
    )) {
      const req = `${m[2] || ''}${m[3]}`.trim();
      const res = `${m[4] || ''}${m[5]}`.trim();
      sigs.push(`rpc ${m[1]}(${req}) returns (${res})`);
    }
  
    // oneof <name>
    for (const m of stripped.matchAll(/\boneof\s+(\w+)\s*\{/g)) {
      sigs.push(`oneof ${m[1]}`);
    }
  
    // extend <TypeName>
    for (const m of stripped.matchAll(/\bextend\s+([\w.]+)\s*\{/g)) {
      sigs.push(`extend ${m[1]}`);
    }
  
    return sigs;
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/sql ──
__factories["./src/extractors/sql"] = function(module, exports) {
  
  'use strict';
  
  /**
   * Extract signatures from SQL source files.
   * Captures CREATE TABLE, VIEW, INDEX, FUNCTION, PROCEDURE, TRIGGER, TYPE, SEQUENCE.
   *
   * @param {string} src - Raw SQL content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    // Strip single-line comments and block comments
    const stripped = src
      .replace(/--[^\n]*/g, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // CREATE TABLE [IF NOT EXISTS] <name> / CREATE [TEMP] TABLE ...
    for (const m of stripped.matchAll(
      /CREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP(?:ORARY)?\s+)?TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"[\w.]+)/gi
    )) {
      sigs.push(`TABLE ${_cleanName(m[1])}`);
    }
  
    // CREATE VIEW / MATERIALIZED VIEW
    for (const m of stripped.matchAll(
      /CREATE\s+(?:OR\s+REPLACE\s+)?(?:MATERIALIZED\s+)?VIEW\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"[\w.]+)/gi
    )) {
      sigs.push(`VIEW ${_cleanName(m[1])}`);
    }
  
    // CREATE INDEX / UNIQUE INDEX
    for (const m of stripped.matchAll(
      /CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:CONCURRENTLY\s+)?(?:IF\s+NOT\s+EXISTS\s+)?([`"[\w.]+)\s+ON\s+([`"[\w.]+)/gi
    )) {
      sigs.push(`INDEX ${_cleanName(m[1])} ON ${_cleanName(m[2])}`);
    }
  
    // CREATE FUNCTION / CREATE OR REPLACE FUNCTION
    for (const m of stripped.matchAll(
      /CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+([`"[\w.]+)\s*\(([^)]*)\)/gi
    )) {
      const params = _normalizeParams(m[2]);
      sigs.push(`FUNCTION ${_cleanName(m[1])}(${params})`);
    }
  
    // CREATE PROCEDURE
    for (const m of stripped.matchAll(
      /CREATE\s+(?:OR\s+REPLACE\s+)?PROCEDURE\s+([`"[\w.]+)\s*\(([^)]*)\)/gi
    )) {
      const params = _normalizeParams(m[2]);
      sigs.push(`PROCEDURE ${_cleanName(m[1])}(${params})`);
    }
  
    // CREATE TRIGGER
    for (const m of stripped.matchAll(
      /CREATE\s+(?:OR\s+REPLACE\s+)?(?:CONSTRAINT\s+)?TRIGGER\s+([`"[\w.]+)/gi
    )) {
      sigs.push(`TRIGGER ${_cleanName(m[1])}`);
    }
  
    // CREATE TYPE (composite, enum, domain)
    for (const m of stripped.matchAll(
      /CREATE\s+(?:OR\s+REPLACE\s+)?TYPE\s+([`"[\w.]+)/gi
    )) {
      sigs.push(`TYPE ${_cleanName(m[1])}`);
    }
  
    // CREATE SEQUENCE
    for (const m of stripped.matchAll(
      /CREATE\s+(?:OR\s+REPLACE\s+)?SEQUENCE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"[\w.]+)/gi
    )) {
      sigs.push(`SEQUENCE ${_cleanName(m[1])}`);
    }
  
    return sigs;
  }
  
  function _cleanName(raw) {
    return raw.replace(/^[`"[]|[`"\]]+$/g, '').trim();
  }
  
  function _normalizeParams(raw) {
    if (!raw || !raw.trim()) return '';
    return raw.trim()
      .split(',')
      .map((p) => p.trim().replace(/\s+/g, ' ').split(' ').slice(0, 2).join(' '))
      .filter(Boolean)
      .join(', ');
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/terraform ──
__factories["./src/extractors/terraform"] = function(module, exports) {
  
  'use strict';
  
  /**
   * Extract signatures from Terraform (.tf / .tfvars) configuration files.
   * Captures resource, data, module, variable, output, locals, provider,
   * terraform blocks, and moved/import blocks.
   *
   * @param {string} src - Raw Terraform content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    // Strip single-line comments
    const stripped = src
      .replace(/\/\/[^\n]*/g, '')
      .replace(/#[^\n]*/g, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // resource "<type>" "<name>" { ... }
    for (const m of stripped.matchAll(/\bresource\s+"([^"]+)"\s+"([^"]+)"\s*\{/g)) {
      sigs.push(`resource "${m[1]}" "${m[2]}"`);
    }
  
    // data "<type>" "<name>" { ... }
    for (const m of stripped.matchAll(/\bdata\s+"([^"]+)"\s+"([^"]+)"\s*\{/g)) {
      sigs.push(`data "${m[1]}" "${m[2]}"`);
    }
  
    // module "<name>" { ... }
    for (const m of stripped.matchAll(/\bmodule\s+"([^"]+)"\s*\{/g)) {
      sigs.push(`module "${m[1]}"`);
    }
  
    // variable "<name>" { ... }
    for (const m of stripped.matchAll(/\bvariable\s+"([^"]+)"\s*\{/g)) {
      sigs.push(`variable "${m[1]}"`);
    }
  
    // output "<name>" { ... }
    for (const m of stripped.matchAll(/\boutput\s+"([^"]+)"\s*\{/g)) {
      sigs.push(`output "${m[1]}"`);
    }
  
    // provider "<name>" { ... }
    for (const m of stripped.matchAll(/\bprovider\s+"([^"]+)"\s*\{/g)) {
      sigs.push(`provider "${m[1]}"`);
    }
  
    // locals { ... } (just mark presence; key names too noisy to enumerate)
    if (/\blocals\s*\{/.test(stripped)) {
      sigs.push('locals { ... }');
    }
  
    // terraform { required_providers / backend }
    if (/\bterraform\s*\{/.test(stripped)) {
      sigs.push('terraform { ... }');
    }
  
    // moved block
    for (const m of stripped.matchAll(/\bmoved\s*\{[\s\S]*?from\s*=\s*([^\n]+)/g)) {
      sigs.push(`moved from ${m[1].trim()}`);
    }
  
    // import block (Terraform 1.5+)
    for (const m of stripped.matchAll(/\bimport\s*\{[\s\S]*?to\s*=\s*([^\n]+)/g)) {
      sigs.push(`import to ${m[1].trim()}`);
    }
  
    return sigs;
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/toml ──
__factories["./src/extractors/toml"] = function(module, exports) {
  
  'use strict';
  
  /**
   * Extract signatures from TOML configuration files.
   * Focuses on section/table names and high-value keys.
   *
   * @param {string} src - Raw TOML content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    // Remove # comments while preserving values before comment markers.
    const stripped = src.replace(/\s+#.*$/gm, '');
  
    // [section] and [[array.section]]
    for (const m of stripped.matchAll(/^\s*(\[\[?[^\]]+\]\]?)\s*$/gm)) {
      sigs.push(`table ${m[1].trim()}`);
    }
  
    // Key-value lines (top-level and nested) — keep key names only.
    for (const m of stripped.matchAll(/^\s*([A-Za-z0-9_.-]+)\s*=\s*(.+)$/gm)) {
      const key = m[1].trim();
      const value = m[2].trim();
  
      // Prefer common metadata/config keys for compact, useful output.
      if (/^(name|version|description|authors|license|requires-python|dependencies|optional-dependencies|scripts|tool\.|build-system\.|project\.)/.test(key)) {
        sigs.push(`key ${key}`);
        continue;
      }
  
      // Include booleans and simple scalar keys as generic config signal.
      if (/^(true|false|"[^"]*"|'[^']*'|[0-9._-]+)$/.test(value)) {
        sigs.push(`key ${key}`);
      }
    }
  
    return Array.from(new Set(sigs)).slice(0, 40);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/properties ──
__factories["./src/extractors/properties"] = function(module, exports) {
  
  'use strict';
  
  /**
   * Extract signatures from .properties configuration files.
   * Captures key names, grouped by prefixes where possible.
   *
   * @param {string} src - Raw properties content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const lines = src.split(/\r?\n/);
    for (const line of lines) {
      const trimmed = line.trim();
      if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('!')) continue;
  
      const idxEq = trimmed.indexOf('=');
      const idxColon = trimmed.indexOf(':');
      const idx = idxEq >= 0 && idxColon >= 0 ? Math.min(idxEq, idxColon) : Math.max(idxEq, idxColon);
      if (idx <= 0) continue;
  
      const key = trimmed.slice(0, idx).trim();
      if (!key) continue;
  
      const parts = key.split('.').filter(Boolean);
      if (parts.length >= 2) {
        sigs.push(`group ${parts[0]}.${parts[1]}`);
      }
      sigs.push(`key ${key}`);
    }
  
    return Array.from(new Set(sigs)).slice(0, 50);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/xml ──
__factories["./src/extractors/xml"] = function(module, exports) {
  
  'use strict';
  
  /**
   * Lightweight XML config extractor.
   * Captures root tags, key config tags, and id/name/class attributes.
   *
   * @param {string} src - Raw XML content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    // Remove comments and XML declaration for simpler scanning.
    const stripped = src
      .replace(/<!--[\s\S]*?-->/g, '')
      .replace(/<\?xml[\s\S]*?\?>/gi, '');
  
    // Root element (first opening tag).
    const root = stripped.match(/<\s*([A-Za-z_][\w:.-]*)\b[^>]*>/);
    if (root) sigs.push(`root ${root[1]}`);
  
    // High-value config-like tags.
    const tagRe = /<\s*([A-Za-z_][\w:.-]*)\b([^>]*)>/g;
    for (const m of stripped.matchAll(tagRe)) {
      const tag = m[1];
      const attrs = m[2] || '';
  
      if (/^(bean|beans|route|routes|property|properties|dependency|dependencies|plugin|plugins|configuration|settings|profile|profiles|module|modules)$/i.test(tag)) {
        sigs.push(`tag ${tag}`);
      }
  
      const id = attrs.match(/\bid\s*=\s*"([^"]+)"/i);
      if (id) sigs.push(`${tag}#${id[1]}`);
  
      const name = attrs.match(/\bname\s*=\s*"([^"]+)"/i);
      if (name) sigs.push(`${tag}[name=${name[1]}]`);
  
      const cls = attrs.match(/\bclass\s*=\s*"([^"]+)"/i);
      if (cls) sigs.push(`${tag} -> ${cls[1]}`);
    }
  
    return Array.from(new Set(sigs)).slice(0, 50);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/markdown ──
__factories["./src/extractors/markdown"] = function(module, exports) {
  
  'use strict';
  
  /**
   * Lightweight markdown technical indexer.
   * Captures headings and fenced code block language hints only.
   *
   * @param {string} src - Raw markdown content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    // Headings: # .. ######
    for (const m of src.matchAll(/^(#{1,6})\s+(.+)$/gm)) {
      const level = m[1].length;
      const title = m[2].trim().replace(/\s+/g, ' ');
      if (title) sigs.push(`h${level} ${title}`);
    }
  
    // Fenced code blocks: ```lang
    for (const m of src.matchAll(/^```\s*([A-Za-z0-9_+-]*)\s*$/gm)) {
      const lang = m[1] ? m[1].toLowerCase() : 'plain';
      sigs.push(`code-fence ${lang}`);
    }
  
    return Array.from(new Set(sigs)).slice(0, 40);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/typescript_react ──
__factories["./src/extractors/typescript_react"] = function(module, exports) {
  
  'use strict';
  
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
    const stripped = src
      .replace(/\/\*[\s\S]*?\*\//g, '')
      .replace(/\/\/.*$/gm, '');
    const compRe = /(?:export\s+)?(?:const|function)\s+([A-Z]\w*)\s*(?:<[^>]*>)?\s*\(\s*(?:props|{\s*[^}]*})?/g;
    for (const m of stripped.matchAll(compRe)) {
      sigs.push(`component ${m[1]}`);
    }
    const propsRe = /interface\s+(\w*Props)\s*(?:<[^>]*>)?\s*{/g;
    for (const m of stripped.matchAll(propsRe)) {
      sigs.push(`props ${m[1]}`);
    }
    const hookRe = /use([A-Z]\w*)\s*(?:<[^>]*>)?\s*\(/g;
    const hooks = new Set();
    for (const m of stripped.matchAll(hookRe)) {
      hooks.add(m[1]);
    }
    for (const h of hooks) {
      sigs.push(`hook use${h}`);
    }
    const exportRe = /export\s+(?:const|function|default|interface|type)\s+([A-Z]\w*)/g;
    for (const m of stripped.matchAll(exportRe)) {
      sigs.push(`export ${m[1]}`);
    }
    const handlerRe = /on([A-Z]\w+)\s*=\s*{?\s*\(?[a-zA-Z_$]/g;
    const handlers = new Set();
    for (const m of stripped.matchAll(handlerRe)) {
      handlers.add(m[1]);
    }
    for (const h of handlers) {
      sigs.push(`handler on${h}`);
    }
    return Array.from(new Set(sigs)).slice(0, 50);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/vue_sfc ──
__factories["./src/extractors/vue_sfc"] = function(module, exports) {
  
  'use strict';
  
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
    const scriptMatch = src.match(/<script[^>]*>([\s\S]*?)<\/script>/);
    if (!scriptMatch) return sigs;
    const script = scriptMatch[1];
    const nameRe = /(?:name\s*:\s*['"`]([a-zA-Z0-9]+)['"`]|export\s+default\s+defineComponent\s*\(\s*{\s*name\s*:\s*['"`]([a-zA-Z0-9]+)['"`])/;
    const nameMatch = script.match(nameRe);
    if (nameMatch) {
      sigs.push(`component ${nameMatch[1] || nameMatch[2]}`);
    }
    const propsRe = /props\s*:\s*{([^}]*)}/;
    const propsMatch = script.match(propsRe);
    if (propsMatch) {
      const propLines = propsMatch[1].split(',');
      for (const line of propLines) {
        const propName = line.trim().match(/([a-zA-Z_$]\w*)/);
        if (propName) sigs.push(`prop ${propName[1]}`);
      }
    }
    const definePropsRe = /defineProps\s*(?:<([^>]+)>)?\s*\(/;
    if (definePropsRe.test(script)) {
      sigs.push('props composition-api');
    }
    const emitsRe = /emits\s*:\s*\[([^\]]+)\]/;
    const emitsMatch = script.match(emitsRe);
    if (emitsMatch) {
      const emitNames = emitsMatch[1].split(',').map(e => e.trim().replace(/['"`]/g, ''));
      for (const e of emitNames) {
        if (e) sigs.push(`emit ${e}`);
      }
    }
    const defineEmitsRe = /defineEmits\s*(?:<([^>]+)>)?\s*\(/;
    if (defineEmitsRe.test(script)) {
      sigs.push('emits composition-api');
    }
    const lifecycleHooks = ['setup', 'created', 'mounted', 'updated', 'unmounted'];
    for (const hook of lifecycleHooks) {
      if (new RegExp(`\\b${hook}\\s*\\(`).test(script)) {
        sigs.push(`lifecycle ${hook}`);
      }
    }
    const namedSlotRe = /<slot\s+name=['"]([a-zA-Z_$]\w*)['"][^>]*>/g;
    const templateSlotRe = /<template\s+#([a-zA-Z_$]\w*)|v-slot:([a-zA-Z_$]\w*)/g;
    const slots = new Set();
    for (const m of src.matchAll(namedSlotRe)) {
      if (m[1]) slots.add(m[1]);
    }
    for (const m of src.matchAll(templateSlotRe)) {
      const slotName = m[1] || m[2];
      if (slotName) slots.add(slotName);
    }
    for (const s of slots) {
      sigs.push(`slot ${s}`);
    }
    return Array.from(new Set(sigs)).slice(0, 50);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/python_dataclass ──
__factories["./src/extractors/python_dataclass"] = function(module, exports) {
  
  'use strict';
  
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
    const dataclassRe = /@dataclass(?:\([^)]*\))?[\s\n]+class\s+([A-Z]\w*)/g;
    for (const m of src.matchAll(dataclassRe)) {
      sigs.push(`dataclass ${m[1]}`);
    }
    const pydanticRe = /class\s+([A-Z]\w*)\s*\([^)]*BaseModel[^)]*\)/g;
    for (const m of src.matchAll(pydanticRe)) {
      sigs.push(`model ${m[1]}`);
    }
    if (/model_validate|field_validator|computed_field/.test(src)) {
      sigs.push('pydantic v2+');
    }
    const sqlalchemyRe = /class\s+([A-Z]\w*)\s*\([^)]*(?:Base|declarative_base)[^)]*\)/g;
    for (const m of src.matchAll(sqlalchemyRe)) {
      sigs.push(`orm ${m[1]}`);
    }
    const fieldRe = /^\s+([a-z_]\w*)\s*:\s*([A-Z]\w*|List|Dict|Optional|Union)[^=]*/gm;
    const fields = new Set();
    for (const m of src.matchAll(fieldRe)) {
      if (fields.size < 15) fields.add(m[1]);
    }
    for (const f of fields) {
      sigs.push(`field ${f}`);
    }
    const columnRe = /([a-z_]\w*)\s*=\s*Column\s*\([^)]*\)/g;
    for (const m of src.matchAll(columnRe)) {
      sigs.push(`column ${m[1]}`);
    }
    const relRe = /(?:ForeignKey|relationship)\s*\(\s*['"]([a-zA-Z_]\w*)['"]/g;
    for (const m of src.matchAll(relRe)) {
      sigs.push(`relation ${m[1]}`);
    }
    const validatorRe = /@(?:validator|field_validator)\s*\(\s*['"]?([a-z_]\w*)(?:['"]|,|\s|\))/g;
    const validators = new Set();
    for (const m of src.matchAll(validatorRe)) {
      validators.add(m[1]);
    }
    for (const v of validators) {
      sigs.push(`validator ${v}`);
    }
    if (/class\s+Config\s*:/.test(src)) {
      sigs.push('config-class');
    }
    return Array.from(new Set(sigs)).slice(0, 50);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/patterns ──
__factories["./src/extractors/patterns"] = function(module, exports) {
  
  'use strict';
  
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];

    // Service container / factory patterns
    const containerRe = /(?:class|function|const)\s+([A-Z]\w*(?:Container|Factory|Registry|Provider|Injector))\b/g;
    for (const m of src.matchAll(containerRe)) {
      sigs.push(`di-container ${m[1]}`);
    }

    // Service decorators: @Injectable, @Service, @Singleton, @Provide
    const decoratorRe = /@(?:Injectable|Service|Singleton|Provide|Module|Component)\s*(?:\([^)]*\))?\s*(?:class|export\s+class|const\s+)\s+([A-Z]\w*)/g;
    for (const m of src.matchAll(decoratorRe)) {
      sigs.push(`service-decorated ${m[1]}`);
    }

    // Dependency injection via constructor: constructor(private readonly ...: Service)
    const ctorDiRe = /constructor\s*\([^)]*(?:private|protected)?\s+(?:readonly\s+)?([a-z_]\w*)\s*:\s*([A-Z]\w*)/g;
    const diServices = new Set();
    for (const m of src.matchAll(ctorDiRe)) {
      diServices.add(m[1]);
    }
    if (diServices.size > 0) {
      sigs.push(`di-injection ${diServices.size} params`);
    }

    // Repository pattern: extends Repository, implements IRepository
    const repoRe = /class\s+([A-Z]\w*(?:Repository|Repo|DataAccess))\b/g;
    for (const m of src.matchAll(repoRe)) {
      sigs.push(`repo ${m[1]}`);
    }

    // Service layer: @Service or ServiceImpl pattern
    const serviceRe = /(?:export\s+)?class\s+([A-Z]\w*Service\b)/g;
    for (const m of src.matchAll(serviceRe)) {
      sigs.push(`service ${m[1]}`);
    }

    // Middleware detection: app.use(), router.use(), middleware function
    if (/app\.use\s*\(|router\.use\s*\(|\.use\s*\(\s*function|middleware|app\.get\s*\(\s*['"`]\/[^'"`]*['"`]/.test(src)) {
      sigs.push('middleware-present');
    }

    // Export type/interface followed by class implementing it
    const exportTypeRe = /export\s+(?:type|interface)\s+([A-Z]\w*)/g;
    const exportedTypes = new Set();
    for (const m of src.matchAll(exportTypeRe)) {
      exportedTypes.add(m[1]);
    }

    // Check if exported types have implementations
    for (const type of exportedTypes) {
      const implRe = new RegExp(`class\\s+([A-Z]\\w*)\\s+(?:extends|implements)\\s+(?:.*\\s+)?${type}\\b`, 'i');
      if (implRe.test(src)) {
        sigs.push(`type-impl ${type}`);
      }
    }

    // Unchecked nulls / optional without validation
    if (/\?\s*{|Optional\s*<|\?\s*\.get\(\)|\?\s*\[|\?\s*\.length|\.split\(\)\.filter\(Boolean\)|if\s*\(\s*!.*\).*throw/.test(src)) {
      sigs.push('unsafe-null-check');
    }

    // Missing validation (direct use of user input)
    const userInputRe = /(?:request|input|params|body|query|args)\s*\[\s*['"][^'"]*['"]\s*\]|req(?:uest)?\..*\s*==|params\.split|String\(.*\)\.toLowerCase/;
    if (userInputRe.test(src)) {
      sigs.push('unsafe-input-validation');
    }

    // Weak error handling (catch {} or empty catch)
    if (/catch\s*\(\s*\)\s*\{|\}\s*catch\s*\{(\s*\/\/|\s*\})/.test(src)) {
      sigs.push('weak-error-handling');
    }

    // Direct error exposure
    if (/throw\s+new\s+Error\(|console\s*\.\s*error|res\.status\(500\)\.send\(err\)/.test(src)) {
      sigs.push('unsafe-error-exposure');
    }

    // Heavy imports
    const importRe = /(?:import|require)\s+(?:{[^}]*}|[a-zA-Z_]\w*)\s+from\s+['"]\.?\.?\/[^'"]+['"]/g;
    const importCount = (src.match(importRe) || []).length;
    if (importCount > 5) {
      sigs.push(`heavy-imports ${importCount}`);
    }

    // Controller/Handler layer
    const controllerRe = /(?:class|export)\s+([A-Z]\w*(?:Controller|Handler|Route))\b/g;
    for (const m of src.matchAll(controllerRe)) {
      sigs.push(`controller ${m[1]}`);
    }

    // Use case / Domain logic
    if (/UseCase|Command|Query|UseCase\b|Interactor/.test(src)) {
      sigs.push('domain-usecase');
    }

    return Array.from(new Set(sigs)).slice(0, 60);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/deps ──
__factories["./src/extractors/deps"] = function(module, exports) {
  
  'use strict';
  
  const PYTHON_STDLIB = new Set([
    'os', 'sys', 're', 'json', 'time', 'threading', 'logging', 'typing',
    'dataclasses', 'datetime', 'uuid', 'pathlib', 'collections', 'functools',
    'itertools', 'math', 'random', 'string', 'struct', 'io', 'copy', 'pprint',
    'traceback', 'inspect', 'abc', 'enum', 'contextlib', 'weakref', 'gc',
    'socket', 'ssl', 'http', 'urllib', 'email', 'html', 'xml', 'csv', 'sqlite3',
    'argparse', 'subprocess', 'shutil', 'tempfile', 'glob', 'fnmatch', 'stat',
    'hashlib', 'hmac', 'base64', 'binascii', 'codecs', 'unicodedata', 'locale',
    'decimal', 'fractions', 'numbers', 'cmath', 'heapq', 'bisect', 'array',
    'queue', 'asyncio', 'concurrent', 'multiprocessing', 'signal', 'mmap',
    'builtins', 'warnings', 'operator', 'textwrap', 'difflib', 'readline',
  ]);
  
  function extractPythonDeps(src) {
    const deps = new Set();
    for (const m of src.matchAll(/^from\s+([\w.]+)\s+import/gm)) {
      const mod = m[1];
      const root = mod.replace(/^\.+/, '').split('.')[0];
      if (mod.startsWith('.') || (root && !PYTHON_STDLIB.has(root))) {
        deps.add(root || mod);
      }
    }
    for (const m of src.matchAll(/^import\s+([\w.]+)/gm)) {
      const root = m[1].split('.')[0];
      if (root && !PYTHON_STDLIB.has(root)) deps.add(root);
    }
    return [...deps].filter(Boolean).slice(0, 5);
  }
  
  function extractTSDeps(src) {
    // Strip single-line comments to avoid matching commented-out imports
    const stripped = src.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
    const deps = new Set();
    for (const m of stripped.matchAll(/from\s+['"](\.[\/\w.-]+)['"]/g)) {
      const clean = m[1]
        .replace(/^\.\.\//, '')
        .replace(/^\.\//, '')
        .replace(/\.\w+$/, '');
      if (clean) deps.add(clean);
    }
    return [...deps].slice(0, 5);
  }
  
  function buildReverseDepMap(forwardMap) {
    const reverse = new Map();
    if (!forwardMap || typeof forwardMap.entries !== 'function') return reverse;
    for (const [file, deps] of forwardMap.entries()) {
      if (!Array.isArray(deps)) continue;
      for (const dep of deps) {
        if (!reverse.has(dep)) reverse.set(dep, []);
        reverse.get(dep).push(file);
      }
    }
    return reverse;
  }

  const R_BASE_PKGS = new Set([
    'base', 'stats', 'utils', 'graphics', 'grDevices', 'methods', 'datasets',
    'parallel', 'splines', 'stats4', 'tools', 'tcltk', 'grid', 'compiler',
  ]);

  function extractRDeps(src) {
    const deps = new Set();
    const stripped = (src || '').replace(/#.*$/gm, '');
    for (const m of stripped.matchAll(/\b(?:library|require)\s*\(\s*["']?([\w.]+)["']?\s*\)/g)) {
      if (m[1] && !R_BASE_PKGS.has(m[1])) deps.add(m[1]);
    }
    for (const m of stripped.matchAll(/\brequireNamespace\s*\(\s*["']([\w.]+)["']/g)) {
      if (m[1] && !R_BASE_PKGS.has(m[1])) deps.add(m[1]);
    }
    for (const m of stripped.matchAll(/\b([A-Za-z][\w.]*)::[A-Za-z]/g)) {
      if (m[1] && !R_BASE_PKGS.has(m[1])) deps.add(m[1]);
    }
    return [...deps].slice(0, 5);
  }

  module.exports = { extractPythonDeps, extractTSDeps, extractRDeps, buildReverseDepMap };
  
};

// ── ./src/extractors/vue ──
__factories["./src/extractors/vue"] = function(module, exports) {
  
  /**
   * Extract signatures from Vue single-file components.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    // Extract component name from filename hint if present or defineComponent
    const nameMatch = src.match(/name\s*:\s*['"](\w+)['"]/);
    if (nameMatch) sigs.push(`component ${nameMatch[1]}`);
  
    // Extract <script> block
    const scriptMatch = src.match(/<script(?:\s[^>]*)?>(?:\s*)([\s\S]*?)<\/script>/i);
    if (!scriptMatch) return sigs;
  
    const script = scriptMatch[1]
      .replace(/\/\/.*$/gm, '')
      .replace(/\/\*[\s\S]*?\*\//g, '');
  
    // Props
    const propsMatch = script.match(/props\s*:\s*(\{[\s\S]*?\})/);
    if (propsMatch) {
      const propNames = [];
      for (const m of propsMatch[1].matchAll(/^\s+(\w+)\s*:/gm)) {
        propNames.push(m[1]);
      }
      if (propNames.length > 0) sigs.push(`props: [${propNames.join(', ')}]`);
    }
  
    // Methods in options API
    const methodsMatch = script.match(/methods\s*:\s*\{([\s\S]*?)\},?\s*(?:computed|watch|mounted|created|data|\})/);
    if (methodsMatch) {
      for (const m of methodsMatch[1].matchAll(/^\s+(?:async\s+)?(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
        if (m[1].startsWith('_')) continue;
        const asyncKw = m[0].includes('async') ? 'async ' : '';
        const retStr = m[3] ? ` \u2192 ${normalizeType(m[3])}` : '';
        sigs.push(`  ${asyncKw}${m[1]}(${normalizeParams(m[2])})${retStr}`);
      }
    }

    // Top-level functions in <script> (e.g., composition API helpers)
    for (const m of script.matchAll(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
      if (m[1].startsWith('_')) continue;
      const asyncKw = m[0].includes('async') ? 'async ' : '';
      const retStr = m[3] ? ` \u2192 ${normalizeType(m[3])}` : '';
      sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
    }
  
    // defineProps (Composition API)
    const definePropsMatch = script.match(/defineProps(?:<[^>]*>)?\s*\(\s*(\{[\s\S]*?\})\s*\)/);
    if (definePropsMatch) {
      const propNames = [];
      for (const m of definePropsMatch[1].matchAll(/^\s+(\w+)\s*:/gm)) {
        propNames.push(m[1]);
      }
      if (propNames.length > 0) sigs.push(`defineProps: [${propNames.join(', ')}]`);
    }
  
    // Emits
    const emitsMatch = script.match(/(?:defineEmits|emits)\s*(?::\s*|\(\s*)(\[[\s\S]*?\])/);
    if (emitsMatch) sigs.push(`emits: ${emitsMatch[1].replace(/\s+/g, ' ')}`);
  
    return sigs.slice(0, 25);
  }
  
  function normalizeParams(params) {
    if (!params) return '';
    return params.trim().replace(/\s+/g, ' ');
  }

  function normalizeType(type) {
    if (!type) return '';
    return type.trim().replace(/[;\s]+$/g, '').replace(/\s+/g, ' ').slice(0, 25);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/yaml ──
__factories["./src/extractors/yaml"] = function(module, exports) {
  
  /**
   * Extract signatures from YAML configuration files.
   * @param {string} src - Raw file content
   * @returns {string[]} Array of signature strings
   */
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
  
    const lines = src.split('\n');
  
    // Top-level keys (no leading whitespace)
    const topKeys = [];
    for (const line of lines) {
      if (/^#/.test(line)) continue;
      const m = line.match(/^([\w-]+)\s*:/);
      if (m) topKeys.push(m[1]);
    }
    if (topKeys.length > 0) sigs.push(`keys: [${topKeys.slice(0, 12).join(', ')}]`);
  
    // GitHub Actions: jobs
    let inJobs = false;
    for (const line of lines) {
      if (/^jobs\s*:/.test(line)) { inJobs = true; continue; }
      if (inJobs && /^[a-z]/.test(line) && !line.startsWith('jobs')) inJobs = false;
      if (inJobs) {
        const m = line.match(/^  ([\w-]+)\s*:/);
        if (m) sigs.push(`job: ${m[1]}`);
      }
    }
  
    // Docker Compose: services
    let inServices = false;
    for (const line of lines) {
      if (/^services\s*:/.test(line)) { inServices = true; continue; }
      if (inServices && /^[a-z]/.test(line) && !line.startsWith('services')) inServices = false;
      if (inServices) {
        const m = line.match(/^  ([\w-]+)\s*:/);
        if (m) sigs.push(`service: ${m[1]}`);
      }
    }
  
    // OpenAPI paths
    let inPaths = false;
    for (const line of lines) {
      if (/^paths\s*:/.test(line)) { inPaths = true; continue; }
      if (inPaths && /^[a-z]/.test(line) && !line.startsWith('paths')) inPaths = false;
      if (inPaths) {
        const m = line.match(/^  (\/[\w/{}-]*)\s*:/);
        if (m) sigs.push(`path: ${m[1]}`);
      }
    }
  
    return sigs.slice(0, 25);
  }
  
  module.exports = { extract };
  
};

// ── ./src/extractors/todos ──
__factories["./src/extractors/todos"] = function(module, exports) {

  'use strict';

  /**
   * Extract TODO/FIXME/HACK/XXX comments from source text.
   * @param {string} src - Raw file content
   * @returns {{line:number, tag:string, text:string}[]}
   */
  function extractTodos(src) {
    if (!src || typeof src !== 'string') return [];
    const todos = [];
    const lines = src.split('\n');

    for (let i = 0; i < lines.length; i++) {
      const m = lines[i].match(/(?:\/\/|#)\s*(TODO|FIXME|HACK|XXX)\s*:?\s*(.+)/i);
      if (!m) continue;
      todos.push({
        line: i + 1,
        tag: m[1].toUpperCase(),
        text: m[2].trim().slice(0, 70),
      });
    }

    return todos;
  }

  module.exports = { extractTodos };

};

// ── ./src/extractors/coverage ──
__factories["./src/extractors/coverage"] = function(module, exports) {

  'use strict';

  const fs = require('fs');
  const path = require('path');

  function walkFiles(dir) {
    let out = [];
    let entries;
    try {
      entries = fs.readdirSync(dir, { withFileTypes: true });
    } catch (_) {
      return out;
    }
    for (const entry of entries) {
      const full = path.join(dir, entry.name);
      if (entry.isDirectory()) out = out.concat(walkFiles(full));
      else if (entry.isFile()) out.push(full);
    }
    return out;
  }

  function buildTestIndex(cwd, testDirs) {
    const dirs = Array.isArray(testDirs) && testDirs.length ? testDirs : ['tests', 'test', '__tests__', 'spec'];
    const names = new Set();

    for (const dir of dirs) {
      const abs = path.join(cwd, dir);
      if (!fs.existsSync(abs)) continue;
      for (const file of walkFiles(abs)) {
        let src = '';
        try {
          src = fs.readFileSync(file, 'utf8');
        } catch (_) {
          continue;
        }

        for (const m of src.matchAll(/\b(?:test_|it\(|test\(|describe\()\s*['"`]?([\w_]+)/g)) {
          if (m[1] && m[1].length >= 3) names.add(m[1].toLowerCase());
        }

        for (const m of src.matchAll(/\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g)) {
          if (m[1] && m[1].length >= 4) names.add(m[1].toLowerCase());
        }
      }
    }

    return names;
  }

  function isTested(funcName, testIndex) {
    if (!funcName || funcName.length < 3 || !testIndex || testIndex.size === 0) return false;
    const lower = funcName.toLowerCase();
    if (testIndex.has(lower) || testIndex.has(`test_${lower}`)) return true;
    return false;
  }

  module.exports = { buildTestIndex, isTested };

};

// ── ./src/extractors/prdiff ──
__factories["./src/extractors/prdiff"] = function(module, exports) {

  'use strict';

  /**
   * Compare signature arrays and produce compact diff markers.
   * @param {string[]} baseSigs
   * @param {string[]} currentSigs
   * @returns {{added:string[], removed:string[], modified:string[]}}
   */
  function diffSignatures(baseSigs, currentSigs) {
    const base = new Set(baseSigs || []);
    const curr = new Set(currentSigs || []);

    const added = [...curr].filter((s) => !base.has(s));
    const removed = [...base].filter((s) => !curr.has(s));

    const byName = (arr) => {
      const m = new Map();
      for (const s of arr) {
        const n = extractName(s);
        if (!n) continue;
        if (!m.has(n)) m.set(n, []);
        m.get(n).push(s);
      }
      return m;
    };

    const aBy = byName(added);
    const rBy = byName(removed);
    const modified = [];

    for (const [name] of aBy) {
      if (rBy.has(name)) modified.push(name);
    }

    return { added, removed, modified };
  }

  function extractName(sig) {
    if (!sig) return '';
    const t = sig.trim();
    const m = t.match(/(?:def|function|func|class|interface|trait|struct|enum|record)?\s*([A-Za-z_][A-Za-z0-9_]*)\s*(?:\(|$)/);
    return m ? m[1] : '';
  }

  module.exports = { diffSignatures, extractName };

};

// ── ./src/extractors/r ──
__factories["./src/extractors/r"] = function(module, exports) {

  'use strict';

  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const sigs = [];
    const docHints = collectRoxygenHints(src);
    const stripped = src.replace(/#.*$/gm, '');
    const consumedRanges = [];

    const r6Re = /([\w.]+)\s*(?:<<-|<-|=)\s*(?:R6::)?R6Class\s*\(/g;
    let m;
    while ((m = r6Re.exec(stripped)) !== null && sigs.length < 30) {
      const name = m[1];
      if (name.startsWith('.')) continue;
      const openIdx = r6Re.lastIndex - 1;
      const body = readBalancedParens(stripped, openIdx);
      if (body === null) continue;
      const closeIdx = openIdx + body.length + 1;
      const classNameLit = readFirstStringArg(body) || name;
      sigs.push(`${name} <- R6Class("${classNameLit}")` + applyHint(docHints, name));
      for (const memberSig of extractListMethods(body, 8)) {
        sigs.push('  ' + memberSig);
        if (sigs.length >= 30) break;
      }
      consumedRanges.push([m.index, closeIdx]);
      r6Re.lastIndex = closeIdx;
    }

    const s7Classes = new Set();
    const s7Re = /([\w.]+)\s*(?:<<-|<-|=)\s*(?:S7::)?new_class\s*\(/g;
    while ((m = s7Re.exec(stripped)) !== null && sigs.length < 30) {
      const name = m[1];
      if (name.startsWith('.')) continue;
      const openIdx = s7Re.lastIndex - 1;
      const body = readBalancedParens(stripped, openIdx);
      if (body === null) continue;
      const closeIdx = openIdx + body.length + 1;
      const classNameLit = readFirstStringArg(body) || name;
      s7Classes.add(classNameLit);
      s7Classes.add(name);
      sigs.push(`${name} <- new_class("${classNameLit}")` + applyHint(docHints, name));
      consumedRanges.push([m.index, closeIdx]);
      s7Re.lastIndex = closeIdx;
    }

    const s7MethodRe = /^[ \t]*method\s*\(\s*([\w.]+)\s*,\s*([\w.]+)\s*\)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
    while ((m = s7MethodRe.exec(stripped)) !== null && sigs.length < 30) {
      if (!s7Classes.has(m[2])) continue;
      const argsStart = s7MethodRe.lastIndex - 1;
      const args = readBalancedParens(stripped, argsStart);
      if (args === null) continue;
      sigs.push(`  method(${m[1]}, ${m[2]}) <- function(${normalizeParams(args)})`);
    }

    const funcRe = /^(?:[ \t]*)([\w.]+)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
    while ((m = funcRe.exec(stripped)) !== null && sigs.length < 30) {
      const name = m[1];
      if (name.startsWith('.')) continue;
      if (inAnyRange(m.index, consumedRanges)) continue;
      const argsStart = funcRe.lastIndex - 1;
      const args = readBalancedParens(stripped, argsStart);
      if (args === null) continue;
      sigs.push(`${name} <- function(${normalizeParams(args)})` + applyHint(docHints, name));
    }

    for (const sm of stripped.matchAll(/^[ \t]*setGeneric\s*\(\s*["']([\w.]+)["']/gm)) {
      if (sigs.length >= 30) break;
      sigs.push(`setGeneric("${sm[1]}")`);
    }
    for (const sm of stripped.matchAll(/^[ \t]*setMethod\s*\(\s*["']([\w.]+)["']\s*,\s*["']([\w.]+)["']/gm)) {
      if (sigs.length >= 30) break;
      sigs.push(`setMethod("${sm[1]}", "${sm[2]}")`);
    }
    for (const sm of stripped.matchAll(/^[ \t]*setClass\s*\(\s*["']([\w.]+)["']/gm)) {
      if (sigs.length >= 30) break;
      sigs.push(`setClass("${sm[1]}")`);
    }

    return sigs.slice(0, 30);
  }

  function collectRoxygenHints(src) {
    const hints = new Map();
    const lines = src.split('\n');
    let block = [];
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];
      if (/^\s*#'/.test(line)) {
        block.push(line.replace(/^\s*#'\s?/, ''));
        continue;
      }
      if (block.length > 0) {
        const mm = line.match(/^[ \t]*([\w.]+)\s*(?:<<-|<-|=)\s*(?:R6::)?R6Class\b/)
                || line.match(/^[ \t]*([\w.]+)\s*(?:<<-|<-|=)\s*(?:S7::)?new_class\b/)
                || line.match(/^[ \t]*([\w.]+)\s*(?:<<-|<-|=)\s*function\b/);
        if (mm) {
          const name = mm[1];
          let hint = pickRoxygenLine(block, '@title')
                  || pickRoxygenLine(block, '@description')
                  || pickRoxygenLine(block, null);
          if (hint) {
            hint = hint.replace(/\s+/g, ' ').trim().slice(0, 60).replace(/[.,;:!?]+$/, '').trim();
            if (hint) hints.set(name, hint);
          }
        }
        block = [];
      }
    }
    return hints;
  }

  function pickRoxygenLine(block, tag) {
    for (const raw of block) {
      const b = raw.trim();
      if (!b) continue;
      if (tag) {
        if (b.startsWith(tag)) {
          const rest = b.slice(tag.length).trim();
          if (rest) return rest;
        }
      } else if (!b.startsWith('@')) {
        return b;
      }
    }
    return null;
  }

  function applyHint(hints, name) {
    const h = hints.get(name);
    return h ? `  # ${h}` : '';
  }

  function extractListMethods(body, cap) {
    const out = [];
    const re = /(?:^|[\n,])\s*([\w.]+)\s*=\s*function\s*\(/g;
    let m;
    while ((m = re.exec(body)) !== null && out.length < cap) {
      const name = m[1];
      if (name.startsWith('.')) continue;
      const argsStart = re.lastIndex - 1;
      const args = readBalancedParens(body, argsStart);
      if (args === null) continue;
      out.push(`${name} <- function(${normalizeParams(args)})`);
    }
    return out;
  }

  function inAnyRange(pos, ranges) {
    for (const [s, e] of ranges) {
      if (pos >= s && pos < e) return true;
    }
    return false;
  }

  function readFirstStringArg(body) {
    const m = body.match(/^\s*["']([\w.]+)["']/);
    return m ? m[1] : null;
  }

  function readBalancedParens(src, openIdx, cap = 16384) {
    if (src[openIdx] !== '(') return null;
    let depth = 1;
    let i = openIdx + 1;
    const end = Math.min(src.length, openIdx + cap);
    let inString = null;
    while (i < end) {
      const ch = src[i];
      if (inString) {
        if (ch === '\\') { i += 2; continue; }
        if (ch === inString) inString = null;
        i++;
        continue;
      }
      if (ch === '"' || ch === "'") { inString = ch; i++; continue; }
      if (ch === '(') depth++;
      else if (ch === ')') {
        depth--;
        if (depth === 0) return src.slice(openIdx + 1, i);
      }
      i++;
    }
    return null;
  }

  function normalizeParams(raw) {
    let out = '';
    let inString = null;
    for (let i = 0; i < raw.length; i++) {
      const ch = raw[i];
      if (inString) {
        out += ch;
        if (ch === '\\' && i + 1 < raw.length) { out += raw[i + 1]; i++; continue; }
        if (ch === inString) inString = null;
        continue;
      }
      if (ch === '"' || ch === "'") { inString = ch; out += ch; continue; }
      if (/\s/.test(ch)) {
        if (out.length && !/\s$/.test(out)) out += ' ';
        continue;
      }
      if (ch === ',') {
        out = out.replace(/\s+$/, '') + ', ';
        continue;
      }
      if (ch === '=') {
        out = out.replace(/\s+$/, '') + ' = ';
        continue;
      }
      out += ch;
    }
    return out.trim();
  }

  module.exports = { extract };

};

// ── ./src/format/cache ──
__factories["./src/format/cache"] = function(module, exports) {
  
  /**
   * Format context output for Anthropic prompt cache API.
   *
   * Usage:
   *   const { formatCache } = require('./src/format/cache');
   *   const json = formatCache(markdownContent);
   *   // json is a ready-to-use Anthropic system block with cache_control
   *
   * Writes: .github/copilot-instructions.cache.json
   */
  
  /**
   * Wrap markdown context in an Anthropic cache-control system block.
   * @param {string} content - Markdown content from formatOutput()
   * @returns {string} - JSON string: a single Anthropic system content block
   */
  function formatCache(content) {
    if (!content || typeof content !== 'string') content = '';
    const block = {
      type: 'text',
      text: content,
      cache_control: { type: 'ephemeral' },
    };
    return JSON.stringify(block, null, 2);
  }
  
  /**
   * Wrap markdown context in a full Anthropic messages API payload.
   * Includes the system array with cache_control so it can be copy-pasted
   * directly into an API call.
   * @param {string} content - Markdown content from formatOutput()
   * @param {string} [model] - Anthropic model ID (default: claude-opus-4-5)
   * @returns {string} - JSON string: { model, system: [...] }
   */
  function formatCachePayload(content, model) {
    if (!content || typeof content !== 'string') content = '';
    const payload = {
      model: model || 'claude-opus-4-5',
      system: [
        {
          type: 'text',
          text: content,
          cache_control: { type: 'ephemeral' },
        },
      ],
      messages: [],
    };
    return JSON.stringify(payload, null, 2);
  }
  
  module.exports = { formatCache, formatCachePayload };

};

// ── ./src/format/benchmark-report ──
__factories["./src/format/benchmark-report"] = function(module, exports) {
  'use strict';

  const fs = require('fs');
  const path = require('path');

  function escapeHtml(value) {
    return String(value == null ? '' : value)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
  }

  function formatInt(value) {
    const n = Number(value);
    if (!Number.isFinite(n)) return 'n/a';
    return Math.round(n).toLocaleString('en-US');
  }

  function formatCompact(value) {
    const n = Number(value);
    if (!Number.isFinite(n)) return 'n/a';
    if (Math.abs(n) >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
    if (Math.abs(n) >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
    return String(Math.round(n));
  }

  function formatPct(value, digits = 1) {
    const n = Number(value);
    if (!Number.isFinite(n)) return 'n/a';
    return `${n.toFixed(digits)}%`;
  }

  function formatMaybePct(value, digits = 1) {
    const n = Number(value);
    if (!Number.isFinite(n)) return 'n/a';
    return `${n.toFixed(digits)}%`;
  }

  function formatRatio(value, digits = 1) {
    const n = Number(value);
    if (!Number.isFinite(n)) return 'n/a';
    return `${n.toFixed(digits)}x`;
  }

  function formatMoney(value) {
    const n = Number(value);
    if (!Number.isFinite(n)) return 'n/a';
    return `$${n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
  }

  function durationLabel(ms) {
    const n = Number(ms);
    if (!Number.isFinite(n)) return 'n/a';
    const sec = n / 1000;
    if (sec < 60) return `${sec.toFixed(1)}s`;
    const min = Math.floor(sec / 60);
    const rem = sec - (min * 60);
    return `${min}m ${rem.toFixed(1)}s`;
  }

  function maxOrZero(values) {
    if (!Array.isArray(values) || values.length === 0) return 0;
    return Math.max(...values.map((v) => (Number.isFinite(v) ? v : 0)));
  }

  function readJson(filePath) {
    try {
      if (!fs.existsSync(filePath)) return null;
      return JSON.parse(fs.readFileSync(filePath, 'utf8'));
    } catch (_) {
      return null;
    }
  }

  function loadBenchmarkReports(cwd) {
    const reportsDir = path.join(cwd, 'benchmarks', 'reports');
    return {
      reportsDir,
      token: readJson(path.join(reportsDir, 'token-reduction.json')),
      retrieval: readJson(path.join(reportsDir, 'retrieval.json')),
      quality: readJson(path.join(reportsDir, 'quality.json')),
      task: readJson(path.join(reportsDir, 'task-benchmark.json')),
      matrix: readJson(path.join(reportsDir, 'benchmark-matrix.json')),
    };
  }

  function buildRetrievalSummary(retrieval) {
    if (!retrieval || !Array.isArray(retrieval.repos) || retrieval.repos.length === 0) return null;
    let totalTasks = 0;
    let weightedHit = 0;
    let weightedRand = 0;
    let correct = 0;
    let partial = 0;
    let wrong = 0;
    let repoCount = 0;

    for (const repo of retrieval.repos) {
      const tasks = Number(repo.tasks) || 0;
      repoCount++;
      totalTasks += tasks;
      weightedHit += (Number(repo.hitAt5) || 0) * tasks;
      weightedRand += (Number(repo.randomBaseline) || 0) * tasks;
      correct += Number(repo.tiers && repo.tiers.correct) || 0;
      partial += Number(repo.tiers && repo.tiers.partial) || 0;
      wrong += Number(repo.tiers && repo.tiers.wrong) || 0;
    }

    const hitAt5 = totalTasks > 0 ? (weightedHit / totalTasks) * 100 : null;
    const randomBaseline = totalTasks > 0 ? (weightedRand / totalTasks) * 100 : null;
    const lift = hitAt5 && randomBaseline ? hitAt5 / randomBaseline : null;

    return { repoCount, totalTasks, hitAt5, randomBaseline, lift, correct, partial, wrong };
  }

  function buildBenchmarkSummary(reports, matrixSummary) {
    const missing = [];
    if (!reports.token) missing.push('token-reduction.json');
    if (!reports.retrieval) missing.push('retrieval.json');
    if (!reports.quality) missing.push('quality.json');
    if (!reports.task) missing.push('task-benchmark.json');

    const retrievalSummary = buildRetrievalSummary(reports.retrieval);
    const qualitySummary = reports.quality && reports.quality.summary ? reports.quality.summary : null;
    const tokenSummary = reports.token && reports.token.summary ? reports.token.summary : null;
    const taskSummary = reports.task && reports.task.summary ? reports.task.summary : null;
    const matrix = matrixSummary || reports.matrix || null;

    const generatedCandidates = [
      matrix && matrix.generated,
      reports.task && reports.task.generated,
      reports.retrieval && reports.retrieval.generated,
      reports.quality && reports.quality.timestamp,
      reports.token && reports.token.timestamp,
    ].filter(Boolean);
    const generatedAt = generatedCandidates
      .map((value) => ({ value, time: Date.parse(value) }))
      .filter((item) => Number.isFinite(item.time))
      .sort((a, b) => b.time - a.time)[0];

    return {
      generatedAt: (generatedAt && generatedAt.value) || generatedCandidates[0] || new Date().toISOString(),
      missing,
      tokenSummary,
      retrievalSummary,
      qualitySummary,
      taskSummary,
      matrix,
    };
  }

  function renderCard(label, value, hint, tone) {
    const toneClass = tone ? ` ${tone}` : '';
    return [
      `<article class="card${toneClass}">`,
      `<div class="label">${escapeHtml(label)}</div>`,
      `<div class="value">${escapeHtml(value)}</div>`,
      `<div class="hint">${escapeHtml(hint || '')}</div>`,
      '</article>',
    ].join('');
  }

  function renderProgress(label, value, max, suffix) {
    const safeValue = Number.isFinite(value) ? value : 0;
    const safeMax = Math.max(1, Number.isFinite(max) ? max : 1);
    const width = Math.max(2, Math.min(100, (safeValue / safeMax) * 100));
    return [
      '<div class="progress-row">',
      `<div class="progress-label">${escapeHtml(label)}</div>`,
      '<div class="progress-bar"><span style="width:',
      String(width.toFixed(1)),
      '%"></span></div>',
      `<div class="progress-value">${escapeHtml(`${safeValue}${suffix || ''}`)}</div>`,
      '</div>',
    ].join('');
  }

  function renderMatrixSection(matrix) {
    if (!matrix || !Array.isArray(matrix.steps) || matrix.steps.length === 0) return '';
    const rows = matrix.steps.map((step) => {
      const status = step.ok ? 'ok' : 'fail';
      return [
        '<tr>',
        `<td>${escapeHtml(step.name)}</td>`,
        `<td><span class="badge ${status}">${escapeHtml(step.ok ? 'ok' : `exit ${step.status}`)}</span></td>`,
        `<td>${escapeHtml(durationLabel(step.durationMs))}</td>`,
        `<td><code>${escapeHtml(['node', step.script].concat(step.args || []).join(' '))}</code></td>`,
        '</tr>',
      ].join('');
    }).join('');

    return [
      '<section>',
      '<h2>Run matrix</h2>',
      '<p class="section-copy">This shows which benchmark jobs ran, whether they succeeded, and how long each step took.</p>',
      '<table>',
      '<thead><tr><th>Step</th><th>Status</th><th>Duration</th><th>Command</th></tr></thead>',
      `<tbody>${rows}</tbody>`,
      '</table>',
      '</section>',
    ].join('');
  }

  function renderTokenSection(token) {
    if (!token || !Array.isArray(token.repos) || token.repos.length === 0) return '';
    const rows = token.repos
      .slice()
      .sort((a, b) => (b.reductionPct || 0) - (a.reductionPct || 0))
      .map((repo) => [
        '<tr>',
        `<td>${escapeHtml(repo.repo)}</td>`,
        `<td>${escapeHtml(repo.language || 'n/a')}</td>`,
        `<td>${escapeHtml(formatCompact(repo.rawTokens))}</td>`,
        `<td>${escapeHtml(formatCompact(repo.finalTokens))}</td>`,
        `<td>${escapeHtml(formatMaybePct(repo.reductionPct, 1))}</td>`,
        '</tr>',
      ].join(''))
      .join('');

    return [
      '<section>',
      '<h2>Token reduction</h2>',
      '<p class="section-copy">Raw repository tokens versus SigMap output size across the benchmark repos.</p>',
      '<table>',
      '<thead><tr><th>Repo</th><th>Language</th><th>Raw tokens</th><th>Final tokens</th><th>Reduction</th></tr></thead>',
      `<tbody>${rows}</tbody>`,
      '</table>',
      '</section>',
    ].join('');
  }

  function renderRetrievalSection(retrieval) {
    if (!retrieval || !Array.isArray(retrieval.repos) || retrieval.repos.length === 0) return '';
    const rows = retrieval.repos.map((repo) => {
      const lift = repo.randomBaseline > 0 ? (repo.hitAt5 / repo.randomBaseline) : null;
      return [
        '<tr>',
        `<td>${escapeHtml(repo.repo)}</td>`,
        `<td>${escapeHtml(formatMaybePct((repo.randomBaseline || 0) * 100, 1))}</td>`,
        `<td>${escapeHtml(formatMaybePct((repo.hitAt5 || 0) * 100, 1))}</td>`,
        `<td>${escapeHtml(formatRatio(lift, 1))}</td>`,
        `<td>${escapeHtml(String((repo.tiers && repo.tiers.correct) || 0))}</td>`,
        `<td>${escapeHtml(String((repo.tiers && repo.tiers.partial) || 0))}</td>`,
        `<td>${escapeHtml(String((repo.tiers && repo.tiers.wrong) || 0))}</td>`,
        '</tr>',
      ].join('');
    }).join('');

    return [
      '<section>',
      '<h2>Retrieval quality</h2>',
      '<p class="section-copy">Hit@5 performance against the random baseline, plus the quality-tier mix that drives the task benchmark.</p>',
      '<table>',
      '<thead><tr><th>Repo</th><th>Random hit@5</th><th>SigMap hit@5</th><th>Lift</th><th>Correct</th><th>Partial</th><th>Wrong</th></tr></thead>',
      `<tbody>${rows}</tbody>`,
      '</table>',
      '</section>',
    ].join('');
  }

  function renderQualitySection(quality) {
    if (!quality || !Array.isArray(quality.repos) || quality.repos.length === 0) return '';
    const rows = quality.repos.map((repo) => {
      const overflow = (repo.rawTokens || 0) > 128000 ? 'overflow' : 'fits';
      return [
        '<tr>',
        `<td>${escapeHtml(repo.repo)}</td>`,
        `<td>${escapeHtml(formatInt(repo.groundedSymbols))}</td>`,
        `<td>${escapeHtml(formatInt(repo.darkSymbols))}</td>`,
        `<td>${escapeHtml(formatMaybePct(repo.groundingPct, 0))}</td>`,
        `<td>${escapeHtml(String(repo.filesHiddenRaw || 0))}</td>`,
        `<td><span class="badge ${overflow === 'overflow' ? 'warn' : 'ok'}">${escapeHtml(overflow)}</span></td>`,
        '</tr>',
      ].join('');
    }).join('');

    return [
      '<section>',
      '<h2>Quality and hallucination surface</h2>',
      '<p class="section-copy">How much code stays visible to the model, plus the overflow and dark-symbol risk by repo.</p>',
      '<table>',
      '<thead><tr><th>Repo</th><th>Grounded symbols</th><th>Dark symbols</th><th>Grounding</th><th>Hidden files (raw)</th><th>GPT-4o 128K</th></tr></thead>',
      `<tbody>${rows}</tbody>`,
      '</table>',
      '</section>',
    ].join('');
  }

  function renderTaskSection(task) {
    if (!task || !Array.isArray(task.repos) || task.repos.length === 0 || !task.summary) return '';
    const summary = task.summary;
    const maxReduction = maxOrZero(task.repos.map((repo) => Number(repo.reductionPct) || 0));
    const repoBars = task.repos
      .slice()
      .sort((a, b) => (b.reductionPct || 0) - (a.reductionPct || 0))
      .slice(0, 10)
      .map((repo) => renderProgress(repo.repo, Number(repo.reductionPct) || 0, maxReduction, '%'))
      .join('');

    return [
      '<section>',
      '<h2>Task benchmark</h2>',
      '<p class="section-copy">A prompt-reduction proxy derived from retrieval quality tiers. Lower prompts means the right file surfaces sooner.</p>',
      '<div class="split">',
      '<div class="panel">',
      '<h3>Answer quality tiers</h3>',
      renderProgress('Correct', Number(summary.correctPct) || 0, 100, '%'),
      renderProgress('Partial', Number(summary.partialPct) || 0, 100, '%'),
      renderProgress('Wrong', Number(summary.wrongPct) || 0, 100, '%'),
      '</div>',
      '<div class="panel">',
      '<h3>Best prompt reduction by repo</h3>',
      repoBars,
      '</div>',
      '</div>',
      '</section>',
    ].join('');
  }

  function generateBenchmarkReportHtml(reports, opts = {}) {
    const summary = buildBenchmarkSummary(reports, opts.matrixSummary);
    const cards = [];
    cards.push(renderCard(
      'Token reduction',
      summary.tokenSummary ? formatPct(summary.tokenSummary.overallReductionPct, 1) : 'n/a',
      summary.tokenSummary ? `${formatInt(summary.tokenSummary.repoCount)} repos • ${formatCompact(summary.tokenSummary.totalRawTokens)} raw -> ${formatCompact(summary.tokenSummary.totalFinalTokens)} final` : 'token-reduction.json missing',
      'cool'
    ));
    cards.push(renderCard(
      'Retrieval hit@5',
      summary.retrievalSummary ? formatPct(summary.retrievalSummary.hitAt5, 1) : 'n/a',
      summary.retrievalSummary ? `${formatPct(summary.retrievalSummary.randomBaseline, 1)} random baseline • ${formatRatio(summary.retrievalSummary.lift, 1)} lift` : 'retrieval.json missing',
      'warm'
    ));
    cards.push(renderCard(
      'Prompt reduction',
      summary.taskSummary ? formatPct(summary.taskSummary.avgReductionPct, 0) : 'n/a',
      summary.taskSummary ? `${summary.taskSummary.avgPromptsWithout} -> ${summary.taskSummary.avgPromptsWith} prompts • ${formatInt(summary.taskSummary.totalTasks)} tasks` : 'task-benchmark.json missing',
      'neutral'
    ));
    cards.push(renderCard(
      'Overflow risk',
      summary.qualitySummary ? `${formatInt(summary.qualitySummary.overflowGPT4oCount)} repos` : 'n/a',
      summary.qualitySummary ? `${formatInt(summary.qualitySummary.totalHiddenFiles)} hidden raw files • ${formatMoney(summary.qualitySummary.gpt4oSavedPerMonth)}/month saved` : 'quality.json missing',
      summary.qualitySummary && summary.qualitySummary.overflowGPT4oCount > 0 ? 'warn' : 'ok'
    ));

    const missingHtml = summary.missing.length > 0
      ? `<div class="notice">Missing source reports: ${escapeHtml(summary.missing.join(', '))}. The page still renders whatever data is available.</div>`
      : '';

    return [
      '<!doctype html>',
      '<html lang="en">',
      '<head>',
      '<meta charset="utf-8" />',
      '<meta name="viewport" content="width=device-width, initial-scale=1" />',
      '<title>SigMap Benchmark Report</title>',
      '<style>',
      ':root { color-scheme: light; --bg:#f5f1e8; --panel:#fffaf2; --ink:#1f1b16; --muted:#6a6258; --line:#dccfbf; --gold:#c87f2a; --green:#2f6f52; --blue:#2f5f8f; --red:#9f4f43; --shadow:0 18px 40px rgba(54,38,14,.10);} ',
      '*{box-sizing:border-box} body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:linear-gradient(180deg,#f3ecdf 0%,#f7f3ed 100%);color:var(--ink)}',
      '.page{max-width:1240px;margin:0 auto;padding:28px 20px 56px}',
      'header{display:flex;justify-content:space-between;gap:24px;align-items:flex-end;margin-bottom:24px}',
      'h1{margin:0;font-size:clamp(2rem,4vw,3.6rem);line-height:1.02;letter-spacing:-.04em}',
      '.lede{max-width:760px;color:var(--muted);font-size:1rem;line-height:1.6;margin-top:10px}',
      '.stamp{font-size:.92rem;color:var(--muted);text-align:right}',
      '.grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;margin:20px 0 24px}',
      '.card,.panel,.notice,section{background:var(--panel);border:1px solid var(--line);box-shadow:var(--shadow);border-radius:18px}',
      '.card{padding:18px 18px 16px}.card.cool{background:#f7f5ff}.card.warm{background:#fff4eb}.card.warn{background:#fff1eb}.card.ok{background:#eff8f1}',
      '.label{font-size:.84rem;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}',
      '.value{font-size:2rem;font-weight:700;letter-spacing:-.04em;margin-top:8px}',
      '.hint{font-size:.95rem;color:var(--muted);margin-top:8px;line-height:1.5}',
      '.notice{padding:14px 16px;margin-bottom:20px;color:var(--muted)}',
      'section{padding:20px;margin-top:18px}',
      'h2{margin:0 0 6px;font-size:1.4rem;letter-spacing:-.03em}',
      'h3{margin:0 0 14px;font-size:1rem}',
      '.section-copy{margin:0 0 16px;color:var(--muted);line-height:1.6}',
      'table{width:100%;border-collapse:collapse;font-size:.95rem}',
      'th,td{padding:10px 12px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}',
      'th{font-size:.82rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}',
      'tbody tr:hover{background:rgba(200,127,42,.06)}',
      '.badge{display:inline-flex;align-items:center;padding:4px 8px;border-radius:999px;font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em}',
      '.badge.ok{background:#e6f4ea;color:#21573f}.badge.warn{background:#fff0de;color:#8a4a17}.badge.fail{background:#fde8e5;color:#8a2e23}',
      '.split{display:grid;grid-template-columns:1fr 1fr;gap:16px}',
      '.panel{padding:16px}',
      '.progress-row{display:grid;grid-template-columns:140px 1fr 60px;gap:12px;align-items:center;margin:10px 0}',
      '.progress-label,.progress-value{font-size:.92rem}',
      '.progress-bar{height:10px;border-radius:999px;background:#efe4d5;overflow:hidden}',
      '.progress-bar span{display:block;height:100%;border-radius:999px;background:linear-gradient(90deg,var(--gold),#ebbb61)}',
      'code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.85rem}',
      '@media (max-width: 1020px){.grid{grid-template-columns:repeat(2,minmax(0,1fr))}.split{grid-template-columns:1fr}header{flex-direction:column;align-items:flex-start}.stamp{text-align:left}}',
      '@media (max-width: 640px){.grid{grid-template-columns:1fr}.progress-row{grid-template-columns:110px 1fr 52px}th:nth-child(n+5),td:nth-child(n+5){display:none}}',
      '</style>',
      '</head>',
      '<body>',
      '<div class="page">',
      '<header>',
      '<div>',
      '<h1>SigMap Benchmark Report</h1>',
      '<p class="lede">A self-contained view of token reduction, retrieval quality, hallucination surface, and task-level prompt reduction. This page reads the saved JSON benchmark artifacts so it stays easy to regenerate locally.</p>',
      '</div>',
      `<div class="stamp">Generated: ${escapeHtml(summary.generatedAt)}<br />Source directory: <code>benchmarks/reports</code></div>`,
      '</header>',
      missingHtml,
      `<div class="grid">${cards.join('')}</div>`,
      renderMatrixSection(summary.matrix),
      renderTokenSection(reports.token),
      renderRetrievalSection(reports.retrieval),
      renderQualitySection(reports.quality),
      renderTaskSection(reports.task),
      '</div>',
      '</body>',
      '</html>',
    ].join('');
  }

  function writeBenchmarkReport(cwd, opts = {}) {
    const reports = loadBenchmarkReports(cwd);
    const html = generateBenchmarkReportHtml(reports, opts);
    const filePath = path.join(reports.reportsDir, opts.fileName || 'benchmark-report.html');
    fs.mkdirSync(path.dirname(filePath), { recursive: true });
    fs.writeFileSync(filePath, html, 'utf8');
    return {
      file: filePath,
      summary: buildBenchmarkSummary(reports, opts.matrixSummary),
    };
  }

  module.exports = {
    loadBenchmarkReports,
    buildBenchmarkSummary,
    generateBenchmarkReportHtml,
    writeBenchmarkReport,
  };
};

  // ── ./src/format/dashboard ──
  __factories["./src/format/dashboard"] = function(module, exports) {
    const fs = require('fs');
    const path = require('path');
    const { readLog } = __require('./src/tracking/logger');

    const LANGUAGE_KEYS = [
      'typescript', 'javascript', 'python', 'java', 'kotlin', 'go', 'rust',
      'csharp', 'cpp', 'ruby', 'php', 'swift', 'dart', 'scala', 'vue',
      'svelte', 'html', 'css', 'yaml', 'shell', 'dockerfile',
    ];

    function toNumber(v) {
      const n = Number(v);
      return Number.isFinite(n) ? n : null;
    }

    function percentile(values, p) {
      if (!Array.isArray(values) || values.length === 0) return 0;
      const sorted = values.filter(Number.isFinite).slice().sort((a, b) => a - b);
      if (sorted.length === 0) return 0;
      if (p <= 0) return sorted[0];
      if (p >= 100) return sorted[sorted.length - 1];
      const idx = (p / 100) * (sorted.length - 1);
      const lo = Math.floor(idx);
      const hi = Math.ceil(idx);
      if (lo === hi) return sorted[lo];
      const frac = idx - lo;
      return sorted[lo] + (sorted[hi] - sorted[lo]) * frac;
    }

    function overBudgetStreak(entries) {
      if (!Array.isArray(entries) || entries.length === 0) return 0;
      let streak = 0;
      for (let i = entries.length - 1; i >= 0; i--) {
        if (entries[i] && entries[i].overBudget) streak++;
        else break;
      }
      return streak;
    }

    function walkFiles(dir, maxDepth, depth, out, excludeSet) {
      if (depth > maxDepth) return;
      let entries = [];
      try {
        entries = fs.readdirSync(dir, { withFileTypes: true });
      } catch (_) {
        return;
      }
      for (const entry of entries) {
        const abs = path.join(dir, entry.name);
        const rel = abs.replace(/\\/g, '/');
        if (excludeSet.has(entry.name) || rel.includes('/node_modules/') || rel.includes('/.git/')) continue;
        if (entry.isDirectory()) walkFiles(abs, maxDepth, depth + 1, out, excludeSet);
        else if (entry.isFile()) out.push(abs);
      }
    }

    function detectLanguage(filePath) {
      const base = path.basename(filePath);
      const ext = path.extname(filePath).toLowerCase();
      if (base === 'Dockerfile' || /^Dockerfile\./.test(base)) return 'dockerfile';
      if (ext === '.ts' || ext === '.tsx') return 'typescript';
      if (ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') return 'javascript';
      if (ext === '.py' || ext === '.pyw') return 'python';
      if (ext === '.java') return 'java';
      if (ext === '.kt' || ext === '.kts') return 'kotlin';
      if (ext === '.go') return 'go';
      if (ext === '.rs') return 'rust';
      if (ext === '.cs') return 'csharp';
      if (ext === '.cpp' || ext === '.c' || ext === '.h' || ext === '.hpp' || ext === '.cc') return 'cpp';
      if (ext === '.rb' || ext === '.rake') return 'ruby';
      if (ext === '.php') return 'php';
      if (ext === '.swift') return 'swift';
      if (ext === '.dart') return 'dart';
      if (ext === '.scala' || ext === '.sc') return 'scala';
      if (ext === '.vue') return 'vue';
      if (ext === '.svelte') return 'svelte';
      if (ext === '.html' || ext === '.htm') return 'html';
      if (ext === '.css' || ext === '.scss' || ext === '.sass' || ext === '.less') return 'css';
      if (ext === '.yml' || ext === '.yaml') return 'yaml';
      if (ext === '.sh' || ext === '.bash' || ext === '.zsh' || ext === '.fish') return 'shell';
      return null;
    }

    function computeExtractorCoverage(cwd) {
      let cfg = {};
      try {
        const cfgPath = path.join(cwd, 'gen-context.config.json');
        if (fs.existsSync(cfgPath)) cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
      } catch (_) {}
      const srcDirs = Array.isArray(cfg.srcDirs) && cfg.srcDirs.length > 0
        ? cfg.srcDirs
        : ['src', 'app', 'lib', 'packages', 'services', 'api'];
      const exclude = new Set(['node_modules', '.git', 'dist', 'build', 'out', '__pycache__', '.next', 'coverage', 'target', 'vendor', '.context']);
      if (Array.isArray(cfg.exclude)) for (const x of cfg.exclude) exclude.add(String(x));
      const counts = {};
      for (const k of LANGUAGE_KEYS) counts[k] = 0;
      const files = [];
      for (const relDir of srcDirs) {
        const absDir = path.join(cwd, relDir);
        if (!fs.existsSync(absDir)) continue;
        walkFiles(absDir, 8, 0, files, exclude);
      }
      for (const f of files) {
        const lang = detectLanguage(f);
        if (lang) counts[lang]++;
      }
      const covered = LANGUAGE_KEYS.filter((k) => counts[k] > 0).length;
      const supported = LANGUAGE_KEYS.length;
      const pct = supported > 0 ? parseFloat(((covered / supported) * 100).toFixed(1)) : 0;
      return { supported, covered, pct, perLanguage: counts };
    }

    function readBenchmarkTrend(cwd) {
      // Prefer per-user history file written by benchmark scripts
      const histPath = path.join(cwd, '.context', 'benchmark-history.ndjson');
      if (fs.existsSync(histPath)) {
        const histValues = [];
        try {
          for (const line of fs.readFileSync(histPath, 'utf8').trim().split('\n').filter(Boolean)) {
            try {
              const obj = JSON.parse(line);
              if (obj.type === 'retrieval') {
                const v = toNumber(obj.hitAt5Pct);
                if (v !== null) histValues.push(v);
              }
            } catch (_) {}
          }
        } catch (_) {}
        if (histValues.length > 0) return histValues.slice(-30);
      }

      // Fallback: legacy benchmarks/results directory (CI artifacts)
      const resultDir = path.join(cwd, 'benchmarks', 'results');
      if (!fs.existsSync(resultDir)) return [];
      const files = [];
      walkFiles(resultDir, 6, 0, files, new Set());
      const values = [];
      for (const filePath of files) {
        const base = path.basename(filePath).toLowerCase();
        if (!base.endsWith('.json') && !base.endsWith('.jsonl') && !base.endsWith('.ndjson')) continue;
        let raw = '';
        try { raw = fs.readFileSync(filePath, 'utf8'); } catch (_) { continue; }
        if (base.endsWith('.json')) {
          try {
            const obj = JSON.parse(raw);
            const v = toNumber(obj && obj.hitAt5);
            const m = toNumber(obj && obj.metrics && obj.metrics.hitAt5);
            if (v !== null) values.push(v);
            else if (m !== null) values.push(m);
          } catch (_) {}
          continue;
        }
        for (const line of raw.split('\n').filter(Boolean)) {
          try {
            const obj = JSON.parse(line);
            const v = toNumber(obj && obj.hitAt5);
            const m = toNumber(obj && obj.metrics && obj.metrics.hitAt5);
            if (v !== null) values.push(v);
            else if (m !== null) values.push(m);
          } catch (_) {}
        }
      }
      return values.slice(-30);
    }

    function lineChartSvg(values, title, ySuffix) {
      const width = 760;
      const height = 210;
      const left = 38;
      const right = 18;
      const top = 22;
      const bottom = 30;
      const innerW = width - left - right;
      const innerH = height - top - bottom;
      const clean = values.filter((n) => Number.isFinite(n));
      if (clean.length === 0) {
        return `<svg viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="${title}"><rect x="0" y="0" width="100%" height="100%" fill="#0f1320" rx="12"/><text x="20" y="36" fill="#d7defa" font-size="14" font-family="monospace">${title}</text><text x="20" y="96" fill="#8ea0d9" font-size="13" font-family="monospace">No data yet. Run with --track and --benchmark.</text></svg>`;
      }
      const min = Math.min(...clean);
      const max = Math.max(...clean);
      const span = max - min || 1;
      const points = clean.map((v, i) => {
        const x = left + ((clean.length === 1 ? 0 : i / (clean.length - 1)) * innerW);
        const y = top + (1 - ((v - min) / span)) * innerH;
        return `${x.toFixed(1)},${y.toFixed(1)}`;
      }).join(' ');
      const latest = clean[clean.length - 1];
      const grid = [];
      for (let i = 0; i <= 4; i++) {
        const gy = top + (i / 4) * innerH;
        grid.push(`<line x1="${left}" y1="${gy.toFixed(1)}" x2="${left + innerW}" y2="${gy.toFixed(1)}" stroke="#223056" stroke-width="1"/>`);
      }
      return `<svg viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="${title}"><rect x="0" y="0" width="100%" height="100%" fill="#0f1320" rx="12"/><text x="20" y="26" fill="#d7defa" font-size="13" font-family="monospace">${title}</text>${grid.join('')}<polyline fill="none" stroke="#42d392" stroke-width="2.5" points="${points}"/><text x="20" y="${height - 8}" fill="#8ea0d9" font-size="12" font-family="monospace">latest: ${latest.toFixed(2)}${ySuffix || ''}</text></svg>`;
    }

    function barChartSvg(perLanguage) {
      const width = 760;
      const height = 260;
      const left = 20;
      const top = 34;
      const usableW = width - left * 2;
      const max = Math.max(1, ...LANGUAGE_KEYS.map((k) => perLanguage[k] || 0));
      const barW = usableW / LANGUAGE_KEYS.length;
      const labels = ['ts', 'js', 'py', 'java', 'kt', 'go', 'rs', 'cs', 'cpp', 'rb', 'php', 'swift', 'dart', 'scala', 'vue', 'sv', 'html', 'css', 'yaml', 'sh', 'df'];
      const bars = [];
      for (let i = 0; i < LANGUAGE_KEYS.length; i++) {
        const key = LANGUAGE_KEYS[i];
        const v = perLanguage[key] || 0;
        const h = (v / max) * 160;
        const x = left + i * barW + 2;
        const y = top + 160 - h;
        bars.push(`<rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${Math.max(2, barW - 4).toFixed(1)}" height="${h.toFixed(1)}" fill="#7aa2ff" rx="2"/>`);
      }
      const xLabels = labels.map((lbl, i) => {
        const x = left + i * barW + barW / 2;
        return `<text x="${x.toFixed(1)}" y="222" fill="#8ea0d9" font-size="9" font-family="monospace" text-anchor="middle">${lbl}</text>`;
      });
      return `<svg viewBox="0 0 760 260" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Extractor coverage by language"><rect x="0" y="0" width="100%" height="100%" fill="#0f1320" rx="12"/><text x="20" y="24" fill="#d7defa" font-size="13" font-family="monospace">Per-language extractor coverage (file counts)</text><line x1="20" y1="194" x2="740" y2="194" stroke="#223056" stroke-width="1"/>${bars.join('')}${xLabels.join('')}</svg>`;
    }

    function sparkline(values) {
      const clean = values.filter((n) => Number.isFinite(n));
      if (clean.length === 0) return 'n/a';
      const ticks = '▁▂▃▄▅▆▇█';
      const min = Math.min(...clean);
      const max = Math.max(...clean);
      const span = max - min || 1;
      return clean.map((v) => {
        const idx = Math.max(0, Math.min(ticks.length - 1, Math.round(((v - min) / span) * (ticks.length - 1))));
        return ticks[idx];
      }).join('');
    }

    function buildDashboardData(cwd, health) {
      const entries = readLog(cwd);
      const recent = entries.slice(-30);
      const tokenReductionTrend = recent.map((e) => toNumber(e.reductionPct)).filter((n) => n !== null);
      const hitAt5Trend = readBenchmarkTrend(cwd);
      const coverage = computeExtractorCoverage(cwd);
      const finals = entries.map((e) => toNumber(e.finalTokens)).filter((n) => n !== null);
      const summary = {
        grade: health.grade,
        score: health.score,
        daysSinceRegen: health.daysSinceRegen,
        totalRuns: entries.length,
        overBudgetRate: entries.length > 0 ? parseFloat(((entries.filter((e) => e.overBudget).length / entries.length) * 100).toFixed(1)) : 0,
        p50TokenCount: Math.round(percentile(finals, 50)),
        p95TokenCount: Math.round(percentile(finals, 95)),
        overBudgetStreak: overBudgetStreak(entries),
        extractorCoverage: coverage.pct,
      };
      return {
        summary,
        tokenReductionTrend,
        hitAt5Trend,
        coverage,
        charts: {
          tokenReductionSvg: lineChartSvg(tokenReductionTrend, 'Token reduction trend (last 30 tracked runs)', '%'),
          hitAt5Svg: lineChartSvg(hitAt5Trend, 'hit@5 trend (last 30 benchmark runs)', ''),
          coverageSvg: barChartSvg(coverage.perLanguage),
        },
      };
    }

    function generateDashboardHtml(cwd, health) {
      const data = buildDashboardData(cwd, health);
      const cards = [
        { label: 'Current grade', value: `${data.summary.grade} (${data.summary.score}/100)` },
        { label: 'Days since regen', value: data.summary.daysSinceRegen === null ? 'n/a' : String(data.summary.daysSinceRegen) },
        { label: 'Total tracked runs', value: String(data.summary.totalRuns) },
        { label: 'Over-budget %', value: `${data.summary.overBudgetRate}%` },
        { label: 'p50 token count', value: String(data.summary.p50TokenCount) },
        { label: 'p95 token count', value: String(data.summary.p95TokenCount) },
        { label: 'Over-budget streak', value: String(data.summary.overBudgetStreak) },
        { label: 'Extractor coverage', value: `${data.summary.extractorCoverage}%` },
      ];
      const cardHtml = cards.map((c) => `<div class="card"><div class="label">${c.label}</div><div class="value">${c.value}</div></div>`).join('');
      const html = [
        '<!doctype html>', '<html lang="en">', '<head>', '<meta charset="utf-8"/>',
        '<meta name="viewport" content="width=device-width,initial-scale=1"/>', '<title>SigMap Dashboard</title>',
        '<style>',
        'body{margin:0;background:#0a0f1e;color:#e6ecff;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}',
        '.wrap{max-width:980px;margin:0 auto;padding:24px}',
        'h1{font-size:22px;margin:0 0 6px 0}', '.sub{color:#8ea0d9;font-size:12px;margin-bottom:20px}',
        '.grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:16px}',
        '.card{background:#111a33;border:1px solid #223056;border-radius:10px;padding:10px}',
        '.label{font-size:11px;color:#8ea0d9;margin-bottom:6px}', '.value{font-size:16px;color:#f5f7ff}',
        '.panel{background:#111a33;border:1px solid #223056;border-radius:12px;padding:10px;margin-top:12px}',
        '@media (max-width:900px){.grid{grid-template-columns:repeat(2,minmax(0,1fr));}}',
        '</style>', '</head>', '<body>', '<div class="wrap">',
        '<h1>SigMap v2.10 dashboard</h1>',
        '<div class="sub">Self-contained report. No external scripts, styles, or network calls.</div>',
        `<div class="grid">${cardHtml}</div>`,
        `<div class="panel">${data.charts.tokenReductionSvg}</div>`,
        `<div class="panel">${data.charts.hitAt5Svg}</div>`,
        `<div class="panel">${data.charts.coverageSvg}</div>`,
        '</div>', '</body>', '</html>',
      ].join('');
      return { html, data };
    }

    function renderHistoryCharts(cwd, health) {
      const data = buildDashboardData(cwd, health);
      const lines = [
        '[sigmap] history charts:',
        `  token reduction trend : ${sparkline(data.tokenReductionTrend)}`,
        `  hit@5 trend           : ${sparkline(data.hitAt5Trend)}`,
        `  extractor coverage    : ${data.coverage.covered}/${data.coverage.supported} (${data.coverage.pct}%)`,
        '',
        '[sigmap] inline svg: token reduction', data.charts.tokenReductionSvg,
        '',
        '[sigmap] inline svg: hit@5', data.charts.hitAt5Svg,
        '',
        '[sigmap] inline svg: coverage', data.charts.coverageSvg,
      ];
      return {
        text: lines.join('\n'),
        tokenReductionSparkline: sparkline(data.tokenReductionTrend),
        hitAt5Sparkline: sparkline(data.hitAt5Trend),
        summary: data.summary,
        charts: data.charts,
      };
    }

    module.exports = { generateDashboardHtml, renderHistoryCharts, computeExtractorCoverage, percentile, overBudgetStreak };
  };

// ── ./src/health/scorer ──
// ── ./src/analysis/coverage-score ──
__factories["./src/analysis/coverage-score"] = function(module, exports) {

'use strict';

function coverageScore(cwd, fileEntries, config) {
  const fs   = require('fs');
  const path = require('path');

  const srcDirs = (config && Array.isArray(config.srcDirs) && config.srcDirs.length > 0)
    ? config.srcDirs
    : ['src', 'app', 'lib'];

  const excludeSet = new Set([
    'node_modules', '.git', 'dist', 'build', 'out', '__pycache__',
    '.next', 'coverage', 'target', 'vendor', '.context',
  ]);
  if (config && Array.isArray(config.exclude)) {
    for (const x of config.exclude) excludeSet.add(String(x));
  }

  const includedSet = new Set((fileEntries || []).map(f => f.filePath));

  const allSource = [];
  for (const relDir of srcDirs) {
    const absDir = path.resolve(cwd, relDir);
    if (fs.existsSync(absDir)) _walkCov(absDir, excludeSet, allSource);
  }

  const total    = allSource.length;
  const included = allSource.filter(f => includedSet.has(f)).length;
  const dropped  = total - included;
  const pct      = total > 0 ? Math.round((included / total) * 100) : 100;

  const grade      = pct >= 90 ? 'A' : pct >= 75 ? 'B' : pct >= 50 ? 'C' : 'D';
  const confidence = pct >= 90 ? 'HIGH' : pct >= 70 ? 'MEDIUM' : 'LOW';

  const perModule = new Map();
  for (const relDir of srcDirs) {
    const absDir   = path.resolve(cwd, relDir);
    const modFiles = allSource.filter(f => f.startsWith(absDir + path.sep) || f === absDir);
    const modIncl  = modFiles.filter(f => includedSet.has(f)).length;
    const modPct   = modFiles.length > 0 ? Math.round((modIncl / modFiles.length) * 100) : 100;
    perModule.set(relDir, { total: modFiles.length, included: modIncl, pct: modPct });
  }

  return { score: pct, grade, total, included, dropped, confidence, perModule };
}

function _walkCov(dir, excludeSet, out) {
  const fs   = require('fs');
  const path = require('path');
  let entries;
  try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
  for (const e of entries) {
    if (excludeSet.has(e.name)) continue;
    const full = path.join(dir, e.name);
    if (e.isDirectory()) { _walkCov(full, excludeSet, out); }
    else if (e.isFile())  { out.push(full); }
  }
}

module.exports = { coverageScore };

};

__factories["./src/analysis/diagnostics"] = function(module, exports) {

'use strict';

const path = require('path');

function estimateTokens(text) {
  return Math.ceil(text.length / 4);
}

function formatFileDecision(entry, decision, reason, score = null) {
  const rel = path.relative(process.cwd(), entry.filePath);
  const tokens = estimateTokens(entry.sigs.join('\n'));
  let line = `${decision === 'included' ? '✓' : '✗'} ${rel}`;
  line += ` [${tokens} tokens]`;
  if (score !== null) line += ` [score: ${score.toFixed(2)}]`;
  if (reason) line += ` — ${reason}`;
  return line;
}

function computeFileMetrics(entry) {
  const loc = entry.content ? entry.content.split('\n').length : 1;
  const sigCount = entry.sigs ? entry.sigs.length : 0;
  const signalQuality = loc > 0 ? sigCount / loc : 0;
  const tokens = estimateTokens(entry.sigs.join('\n'));

  return {
    lineOfCode: loc,
    sigCount: sigCount,
    signalQuality: signalQuality.toFixed(3),
    tokens: tokens,
    relevance: (sigCount / Math.max(loc, 1)).toFixed(3),
  };
}

function explainInclusion(fileEntries, budgetLimit) {
  const lines = [];

  lines.push('## File Inclusion Diagnostics\n');
  lines.push(`Budget: ${budgetLimit} tokens`);
  lines.push(`Files: ${fileEntries.length} scanned\n`);

  let totalTokens = 0;
  const withMetrics = fileEntries.map((e) => {
    const metrics = computeFileMetrics(e);
    totalTokens += metrics.tokens;
    return { entry: e, metrics };
  });

  lines.push(`Total token requirement: ${totalTokens} tokens`);
  lines.push(`Budget headroom: ${budgetLimit * 0.9} tokens (90% of ${budgetLimit})\n`);

  if (totalTokens > budgetLimit * 0.9) {
    lines.push('⚠ Over budget — files will be dropped\n');
    lines.push('### Per-file metrics:');
    for (const { entry, metrics } of withMetrics) {
      const rel = path.relative(process.cwd(), entry.filePath);
      lines.push(`- ${rel}`);
      lines.push(`  - Size: ${metrics.tokens} tokens, ${metrics.lineOfCode} lines`);
      lines.push(`  - Sigs: ${metrics.sigCount}, quality: ${metrics.signalQuality}`);
    }
  } else {
    lines.push('✓ All files fit within budget\n');
  }

  return lines.join('\n');
}

function explainExclusion(dropped, reason) {
  return `Excluded ${dropped.length} files: ${reason}`;
}

module.exports = {
  formatFileDecision,
  computeFileMetrics,
  explainInclusion,
  explainExclusion,
  estimateTokens,
};

};

__factories["./src/health/scorer"] = function(module, exports) {
  
  /**
   * SigMap health scorer.
   *
   * Computes a composite 0-100 health score for the current project by combining:
   *   1. Days since context file was last regenerated  (staleness penalty ≤ 30 pts)
   *   2. Average token reduction percentage             (low-reduction penalty 20 pts)
   *   3. Over-budget run rate                           (budget penalty 20 pts)
   *
   * Strategy-aware: thresholds adjust based on the active strategy so that
   * hot-cold (90% reduction intentional) is not penalized as 'low reduction'.
   *
   * Grade scale:  A ≥ 90  |  B ≥ 75  |  C ≥ 60  |  D < 60
   *
   * Never throws — returns graceful result with nulls for unavailable metrics.
   *
   * @param {string} cwd - Working directory (root of the project)
   * @returns {{
   *   score: number,
   *   grade: 'A'|'B'|'C'|'D',
   *   strategy: string,
   *   tokenReductionPct: number|null,
   *   daysSinceRegen: number|null,
   *   strategyFreshnessDays: number|null,
   *   totalRuns: number,
   *   overBudgetRuns: number,
   * }}
   */
  function score(cwd) {
    const fs = require('fs');
    const path = require('path');

    function toNumber(v) {
      const n = Number(v);
      return Number.isFinite(n) ? n : null;
    }

    function percentile(values, p) {
      if (!Array.isArray(values) || values.length === 0) return 0;
      const sorted = values.filter(Number.isFinite).slice().sort((a, b) => a - b);
      if (sorted.length === 0) return 0;
      if (p <= 0) return sorted[0];
      if (p >= 100) return sorted[sorted.length - 1];
      const idx = (p / 100) * (sorted.length - 1);
      const lo = Math.floor(idx);
      const hi = Math.ceil(idx);
      if (lo === hi) return sorted[lo];
      const frac = idx - lo;
      return sorted[lo] + (sorted[hi] - sorted[lo]) * frac;
    }

    function overBudgetStreak(entries) {
      if (!Array.isArray(entries) || entries.length === 0) return 0;
      let streak = 0;
      for (let i = entries.length - 1; i >= 0; i--) {
        if (entries[i] && entries[i].overBudget) streak++;
        else break;
      }
      return streak;
    }

    function walkFiles(dir, maxDepth, depth, out, excludeSet) {
      if (depth > maxDepth) return;
      let entries = [];
      try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
      for (const entry of entries) {
        const abs = path.join(dir, entry.name);
        const rel = abs.replace(/\\/g, '/');
        if (excludeSet.has(entry.name) || rel.includes('/node_modules/') || rel.includes('/.git/')) continue;
        if (entry.isDirectory()) walkFiles(abs, maxDepth, depth + 1, out, excludeSet);
        else if (entry.isFile()) out.push(abs);
      }
    }

    function detectLanguage(filePath) {
      const base = path.basename(filePath);
      const ext = path.extname(filePath).toLowerCase();
      if (base === 'Dockerfile' || /^Dockerfile\./.test(base)) return 'dockerfile';
      if (ext === '.ts' || ext === '.tsx') return 'typescript';
      if (ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') return 'javascript';
      if (ext === '.py' || ext === '.pyw') return 'python';
      if (ext === '.java') return 'java';
      if (ext === '.kt' || ext === '.kts') return 'kotlin';
      if (ext === '.go') return 'go';
      if (ext === '.rs') return 'rust';
      if (ext === '.cs') return 'csharp';
      if (ext === '.cpp' || ext === '.c' || ext === '.h' || ext === '.hpp' || ext === '.cc') return 'cpp';
      if (ext === '.rb' || ext === '.rake') return 'ruby';
      if (ext === '.php') return 'php';
      if (ext === '.swift') return 'swift';
      if (ext === '.dart') return 'dart';
      if (ext === '.scala' || ext === '.sc') return 'scala';
      if (ext === '.vue') return 'vue';
      if (ext === '.svelte') return 'svelte';
      if (ext === '.html' || ext === '.htm') return 'html';
      if (ext === '.css' || ext === '.scss' || ext === '.sass' || ext === '.less') return 'css';
      if (ext === '.yml' || ext === '.yaml') return 'yaml';
      if (ext === '.sh' || ext === '.bash' || ext === '.zsh' || ext === '.fish') return 'shell';
      return null;
    }

    function computeExtractorCoverage(cwd, cfg) {
      const LANGUAGE_KEYS = [
        'typescript', 'javascript', 'python', 'java', 'kotlin', 'go', 'rust',
        'csharp', 'cpp', 'ruby', 'php', 'swift', 'dart', 'scala', 'vue',
        'svelte', 'html', 'css', 'yaml', 'shell', 'dockerfile',
      ];
      const srcDirs = Array.isArray(cfg && cfg.srcDirs) && cfg.srcDirs.length > 0
        ? cfg.srcDirs
        : ['src', 'app', 'lib', 'packages', 'services', 'api'];
      const exclude = new Set(['node_modules', '.git', 'dist', 'build', 'out', '__pycache__', '.next', 'coverage', 'target', 'vendor', '.context']);
      if (cfg && Array.isArray(cfg.exclude)) for (const x of cfg.exclude) exclude.add(String(x));
      const counts = {};
      for (const k of LANGUAGE_KEYS) counts[k] = 0;
      const files = [];
      for (const relDir of srcDirs) {
        const absDir = path.join(cwd, relDir);
        if (!fs.existsSync(absDir)) continue;
        walkFiles(absDir, 8, 0, files, exclude);
      }
      for (const f of files) {
        const lang = detectLanguage(f);
        if (lang) counts[lang]++;
      }
      const covered = LANGUAGE_KEYS.filter((k) => counts[k] > 0).length;
      return LANGUAGE_KEYS.length > 0 ? parseFloat(((covered / LANGUAGE_KEYS.length) * 100).toFixed(1)) : 0;
    }
  
    let tokenReductionPct = null;
    let daysSinceRegen = null;
    let strategyFreshnessDays = null;
    let overBudgetRuns = 0;
    let totalRuns = 0;
    let p50TokenCount = 0;
    let p95TokenCount = 0;
    let overBudgetStreakCount = 0;
    let extractorCoverage = 0;
    let cfgObj = null;
    let entries = [];
  
    // ── Detect active strategy ──────────────────────────────────────────────
    let strategy = 'full';
    try {
      const cfgPath = path.join(cwd, 'gen-context.config.json');
      if (fs.existsSync(cfgPath)) {
        cfgObj = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
        strategy = cfgObj.strategy || 'full';
      }
    } catch (_) {}
  
    // ── Read usage log via tracking logger ──────────────────────────────────
    try {
      const { readLog, summarize } = __require('./src/tracking/logger');
      entries = readLog(cwd);
      const s = summarize(entries);
      // Only set tokenReductionPct when there is actual history; a brand-new/
      // untracked project should not be penalised for "0% reduction".
      if (s.totalRuns > 0) tokenReductionPct = s.avgReductionPct;
      overBudgetRuns = s.overBudgetRuns;
      totalRuns = s.totalRuns;
      const finals = entries.map((e) => toNumber(e.finalTokens)).filter((n) => n !== null);
      p50TokenCount = Math.round(percentile(finals, 50));
      p95TokenCount = Math.round(percentile(finals, 95));
      overBudgetStreakCount = overBudgetStreak(entries);
    } catch (_) {
      // No usage log yet — proceed with nulls
    }

    try {
      extractorCoverage = computeExtractorCoverage(cwd, cfgObj || {});
    } catch (_) {
      extractorCoverage = 0;
    }
  
    // ── Days since primary context file was last regenerated ─────────────────
    try {
      const ctxFile = path.join(cwd, '.github', 'copilot-instructions.md');
      if (fs.existsSync(ctxFile)) {
        const mtime = fs.statSync(ctxFile).mtimeMs;
        daysSinceRegen = parseFloat(((Date.now() - mtime) / (1000 * 60 * 60 * 24)).toFixed(1));
      }
    } catch (_) {}
  
    // ── Strategy freshness: context-cold.md age (hot-cold only) ─────────────
    if (strategy === 'hot-cold') {
      try {
        const coldFile = path.join(cwd, '.github', 'context-cold.md');
        if (fs.existsSync(coldFile)) {
          const mtime = fs.statSync(coldFile).mtimeMs;
          strategyFreshnessDays = parseFloat(((Date.now() - mtime) / (1000 * 60 * 60 * 24)).toFixed(1));
        }
      } catch (_) {}
    }
  
    // ── Compute composite score ───────────────────────────────────────────────
    let points = 100;
  
    // Staleness penalty: -4 pts per day over the 7-day freshness window (max -30)
    if (daysSinceRegen !== null && daysSinceRegen > 7) {
      points -= Math.min(30, Math.floor((daysSinceRegen - 7) * 4));
    }
  
    // Low-reduction penalty — threshold depends on strategy:
    // - hot-cold: primary output tiny by design; use cold freshness instead
    // - per-module: per-file budgets; global < 60% is expected, no penalty
    // - full: standard 60% threshold
    const reductionThreshold = (strategy === 'full') ? 60 : 0;
    if (tokenReductionPct !== null && tokenReductionPct < reductionThreshold) {
      points -= 20;
    }
  
    // hot-cold strategy freshness penalty: context-cold.md older than 1 day (-10 pts)
    if (strategy === 'hot-cold' && strategyFreshnessDays !== null && strategyFreshnessDays > 1) {
      points -= Math.min(10, Math.floor(strategyFreshnessDays - 1) * 3);
    }
  
    // Over-budget penalty: more than 20% of runs exceeded the token budget (-20)
    if (overBudgetRuns > 0 && totalRuns > 0) {
      const overBudgetRate = (overBudgetRuns / totalRuns) * 100;
      if (overBudgetRate > 20) points -= 20;
    }
  
    points = Math.max(0, Math.min(100, Math.round(points)));
  
    let grade;
    if (points >= 90) grade = 'A';
    else if (points >= 75) grade = 'B';
    else if (points >= 60) grade = 'C';
    else grade = 'D';
  
    return {
      score: points,
      grade,
      strategy,
      tokenReductionPct,
      daysSinceRegen,
      strategyFreshnessDays,
      totalRuns,
      overBudgetRuns,
      p50TokenCount,
      p95TokenCount,
      overBudgetStreak: overBudgetStreakCount,
      extractorCoverage,
    };
  }
  
  module.exports = { score };
  
};

// ── ./src/map/class-hierarchy ──
__factories["./src/map/class-hierarchy"] = function(module, exports) {
  
  /**
   * Class hierarchy analyzer.
   * Extracts class declarations with extends/implements across
   * TypeScript, JavaScript, Python, Java, Kotlin, C# files.
   *
   * @param {string[]} files — absolute file paths to analyze
   * @param {string}   cwd   — project root for relative path display
   * @returns {string} formatted section content (empty string if nothing found)
   */
  
  const fs = require('fs');
  const path = require('path');
  
  function analyze(files, cwd) {
    const entries = [];
  
    for (const filePath of files) {
      const ext = path.extname(filePath).toLowerCase();
      const rel = path.relative(cwd, filePath).replace(/\\/g, '/');
      let content;
      try { content = fs.readFileSync(filePath, 'utf8'); } catch (_) { continue; }
  
      // TS / JS
      if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
        const re = /^\s*(?:export\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+([\w<>.]+?))?(?:\s+implements\s+([\w<>.,\s]+?))?\s*\{/gm;
        let m;
        while ((m = re.exec(content)) !== null) {
          const parent = m[2] ? m[2].split('<')[0].trim() : null;
          const ifaces = m[3]
            ? m[3].split(',').map((s) => s.split('<')[0].trim()).filter(Boolean)
            : [];
          entries.push({ name: m[1], parent, interfaces: ifaces, file: rel });
        }
      }
  
      // Python
      if (['.py', '.pyw'].includes(ext)) {
        const re = /^\s*class\s+(\w+)\s*\(([^)]*)\)\s*:/gm;
        let m;
        while ((m = re.exec(content)) !== null) {
          const parents = m[2]
            .split(',')
            .map((s) => s.trim())
            .filter((s) => s && s !== 'object');
          entries.push({
            name: m[1],
            parent: parents[0] || null,
            interfaces: parents.slice(1),
            file: rel,
          });
        }
      }
  
      // Java
      if (ext === '.java') {
        const re = /^\s*(?:(?:public|protected|private|static|abstract|final)\s+)*class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w,\s<>]+?))?\s*\{/gm;
        let m;
        while ((m = re.exec(content)) !== null) {
          const ifaces = m[3]
            ? m[3].split(',').map((s) => s.split('<')[0].trim()).filter(Boolean)
            : [];
          entries.push({ name: m[1], parent: m[2] || null, interfaces: ifaces, file: rel });
        }
      }
  
      // Kotlin
      if (['.kt', '.kts'].includes(ext)) {
        const re = /^\s*(?:(?:data|sealed|abstract|open|inner)\s+)?class\s+(\w+)(?:\s*[^:\r\n]*)?\s*:\s*([\w<>(),.\s]+?)(?:\s*\{|$)/gm;
        let m;
        while ((m = re.exec(content)) !== null) {
          const parents = m[2]
            .split(',')
            .map((s) => s.replace(/\(.*?\)/, '').split('<')[0].trim())
            .filter(Boolean);
          entries.push({
            name: m[1],
            parent: parents[0] || null,
            interfaces: parents.slice(1),
            file: rel,
          });
        }
      }
  
      // C#
      if (ext === '.cs') {
        const re = /^\s*(?:(?:public|internal|protected|private|static|abstract|sealed|partial)\s+)*class\s+(\w+)(?:\s*:\s*([\w<>.,\s]+?))?\s*\{/gm;
        let m;
        while ((m = re.exec(content)) !== null) {
          const parents = m[2]
            ? m[2].split(',').map((s) => s.split('<')[0].trim()).filter(Boolean)
            : [];
          entries.push({
            name: m[1],
            parent: parents[0] || null,
            interfaces: parents.slice(1),
            file: rel,
          });
        }
      }
    }
  
    if (entries.length === 0) return '';
  
    return entries
      .map((e) => {
        let line = e.name;
        if (e.parent) line += ` extends ${e.parent}`;
        if (e.interfaces.length > 0) line += ` implements ${e.interfaces.join(', ')}`;
        line += ` (${e.file})`;
        return line;
      })
      .join('\n');
  }
  
  module.exports = { analyze, extractImports };
  
};

// ── ./src/map/import-graph ──
__factories["./src/map/import-graph"] = function(module, exports) {
  
  /**
   * Import graph analyzer.
   * Extracts relative import relationships from JS/TS/Python files
   * and detects circular dependencies.
   *
   * @param {string[]} files — absolute file paths to analyze
   * @param {string}   cwd   — project root for relative path display
   * @returns {string} formatted section content (empty string if nothing found)
   */
  
  const fs = require('fs');
  const path = require('path');
  
  const JS_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);
  const PY_EXTS = new Set(['.py', '.pyw']);
  
  // ---------------------------------------------------------------------------
  // Import extraction per language
  // ---------------------------------------------------------------------------
  function extractImports(filePath, content, fileSet) {
    const ext = path.extname(filePath).toLowerCase();
    const dir = path.dirname(filePath);
    const found = [];
  
    if (JS_EXTS.has(ext)) {
      // ES: import ... from './foo'  or  import './side-effect'
      const re1 = /(?:^|[\r\n])\s*import\s+(?:[^'";\r\n]*?\s+from\s+)?['"](\.[^'"]+)['"]/g;
      let m;
      while ((m = re1.exec(content)) !== null) {
        const resolved = resolveJsPath(dir, m[1], fileSet);
        if (resolved) found.push(resolved);
      }
      // CommonJS: require('./foo')
      const re2 = /\brequire\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
      while ((m = re2.exec(content)) !== null) {
        const resolved = resolveJsPath(dir, m[1], fileSet);
        if (resolved) found.push(resolved);
      }
    }
  
    if (PY_EXTS.has(ext)) {
      // Relative imports: from .module import ...  /  from ..pkg import ...
      const reRel = /^[ \t]*from\s+(\.+[\w.]*)\s+import/gm;
      let m;
      while ((m = reRel.exec(content)) !== null) {
        const dotCount = (m[1].match(/^\.+/) || [''])[0].length;
        const modPart = m[1].slice(dotCount).replace(/\./g, '/');
        let base = dir;
        for (let i = 1; i < dotCount; i++) base = path.dirname(base);
        const candidate = modPart ? path.join(base, modPart + '.py') : null;
        if (candidate && fileSet.has(candidate)) found.push(candidate);
      }

      // Absolute imports: from package.module import ... (infer from project structure)
      const reAbs = /^[ \t]*from\s+([\w.]+)\s+import/gm;
      while ((m = reAbs.exec(content)) !== null) {
        const modulePath = m[1].replace(/\./g, '/');
        const candidates = [
          path.join(dir, modulePath + '.py'),
          path.join(dir, modulePath, '__init__.py'),
          path.resolve(dir, '..', modulePath + '.py'),
          path.resolve(dir, '..', modulePath, '__init__.py'),
        ];
        for (const c of candidates) {
          if (fileSet.has(c)) {
            found.push(c);
            break;
          }
        }
      }
    }
  
    return [...new Set(found)];
  }
  
  function resolveJsPath(dir, importStr, fileSet) {
    const base = path.resolve(dir, importStr);
    const candidates = [
      base,
      base + '.ts', base + '.tsx',
      base + '.js', base + '.jsx',
      base + '/index.ts', base + '/index.js',
    ];
    for (const c of candidates) {
      if (fileSet.has(c)) return c;
    }
    return null;
  }
  
  // ---------------------------------------------------------------------------
  // Cycle detection (DFS with path tracking)
  // ---------------------------------------------------------------------------
  function detectCycles(graph) {
    const cycles = [];
    const visited = new Set();
    const onStack = new Set();
    const stackArr = [];
  
    function dfs(node) {
      if (onStack.has(node)) {
        const start = stackArr.indexOf(node);
        if (start !== -1) cycles.push([...stackArr.slice(start), node]);
        return;
      }
      if (visited.has(node)) return;
  
      onStack.add(node);
      stackArr.push(node);
      for (const dep of (graph.get(node) || [])) dfs(dep);
      stackArr.pop();
      onStack.delete(node);
      visited.add(node);
    }
  
    for (const node of graph.keys()) {
      if (!visited.has(node)) dfs(node);
    }
    return cycles;
  }
  
  // ---------------------------------------------------------------------------
  // Public API
  // ---------------------------------------------------------------------------
  function analyze(files, cwd) {
    const fileSet = new Set(files.map((f) => path.resolve(f)));
    const graph = new Map();
  
    for (const filePath of files) {
      let content;
      try { content = fs.readFileSync(filePath, 'utf8'); } catch (_) { continue; }
      const deps = extractImports(path.resolve(filePath), content, fileSet);
      if (deps.length > 0) graph.set(path.resolve(filePath), deps);
    }
  
    if (graph.size === 0) return '';
  
    const cycles = detectCycles(graph);
    const cycleNodeSet = new Set(cycles.flatMap((c) => c));
  
    const lines = [];
    const sorted = [...graph.entries()].sort((a, b) => a[0].localeCompare(b[0]));
  
    for (const [fp, deps] of sorted) {
      const rel = path.relative(cwd, fp).replace(/\\/g, '/');
      const depList = deps.map((d) => {
        const drel = path.relative(cwd, d).replace(/\\/g, '/');
        return cycleNodeSet.has(d) ? `${drel} ⚠` : drel;
      });
      lines.push(`${rel} → ${depList.join(', ')}`);
    }
  
    if (cycles.length > 0) {
      lines.push('');
      lines.push('Circular dependencies detected:');
      for (const cycle of cycles) {
        const relPath = cycle.map((n) => path.relative(cwd, n).replace(/\\/g, '/')).join(' → ');
        lines.push(`  ⚠ ${relPath}`);
      }
    }
  
    return lines.join('\n');
  }

  module.exports = { analyze, extractImports };

};

// ── ./src/map/route-table ──
__factories["./src/map/route-table"] = function(module, exports) {
  
  /**
   * HTTP route table extractor.
   * Detects routes in Express, Fastify, NestJS, Flask, FastAPI, Gin, Spring.
   *
   * @param {string[]} files — absolute file paths to analyze
   * @param {string}   cwd   — project root for relative path display
   * @returns {string} formatted markdown table (empty string if no routes found)
   */
  
  const fs = require('fs');
  const path = require('path');
  
  const JS_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);
  const PY_EXTS = new Set(['.py', '.pyw']);
  
  function analyze(files, cwd) {
    const routes = [];
  
    for (const filePath of files) {
      const ext = path.extname(filePath).toLowerCase();
      const rel = path.relative(cwd, filePath).replace(/\\/g, '/');
      let content;
      try { content = fs.readFileSync(filePath, 'utf8'); } catch (_) { continue; }
  
      // -----------------------------------------------------------------------
      // Express / Fastify / Koa (JS/TS)
      // -----------------------------------------------------------------------
      if (JS_EXTS.has(ext)) {
        // app.get('/path', ...) / router.post('/path') / fastify.put('/path')
        const re1 = /\b(?:app|router|fastify|server|koa|instance)\.(get|post|put|patch|delete|head|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
        let m;
        while ((m = re1.exec(content)) !== null) {
          routes.push({ method: m[1].toUpperCase(), path: m[2], file: rel });
        }
  
        // NestJS decorators: @Get('/path') @Post('/path')
        const re2 = /@(Get|Post|Put|Patch|Delete|Head|Options|All)\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
        while ((m = re2.exec(content)) !== null) {
          routes.push({ method: m[1].toUpperCase(), path: m[2], file: rel });
        }
  
        // NestJS: @Get() with no path
        const re3 = /@(Get|Post|Put|Patch|Delete)\s*\(\s*\)/g;
        while ((m = re3.exec(content)) !== null) {
          routes.push({ method: m[1].toUpperCase(), path: '/', file: rel });
        }
      }
  
      // -----------------------------------------------------------------------
      // Flask / FastAPI (Python)
      // -----------------------------------------------------------------------
      if (PY_EXTS.has(ext)) {
        // @app.route('/path', methods=['GET', 'POST'])
        const re1 = /@[\w.]+\.route\s*\(\s*['"]([^'"]+)['"]([\s\S]{0,150}?)\)/g;
        let m;
        while ((m = re1.exec(content)) !== null) {
          const routePath = m[1];
          const methodsMatch = m[2].match(/methods\s*=\s*\[([^\]]+)\]/);
          if (methodsMatch) {
            const methods = methodsMatch[1].match(/['"]([A-Z]+)['"]/g) || [];
            for (const meth of methods) {
              routes.push({ method: meth.replace(/['"]/g, ''), path: routePath, file: rel });
            }
          } else {
            routes.push({ method: 'GET', path: routePath, file: rel });
          }
        }
  
        // @app.get('/path')  @router.post('/path')  FastAPI style
        const re2 = /@[\w.]+\.(get|post|put|patch|delete|head|options)\s*\(\s*['"]([^'"]+)['"]/g;
        while ((m = re2.exec(content)) !== null) {
          routes.push({ method: m[1].toUpperCase(), path: m[2], file: rel });
        }
      }
  
      // -----------------------------------------------------------------------
      // Go — Gin / Echo / chi / net/http
      // -----------------------------------------------------------------------
      if (ext === '.go') {
        // r.GET("/path", handler)
        const re1 = /\b\w+\.(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s*\(\s*["']([^"']+)["']/g;
        let m;
        while ((m = re1.exec(content)) !== null) {
          routes.push({ method: m[1], path: m[2], file: rel });
        }
        // http.HandleFunc("/path", handler)
        const re2 = /http\.HandleFunc\s*\(\s*["']([^"']+)["']/g;
        while ((m = re2.exec(content)) !== null) {
          routes.push({ method: 'ANY', path: m[1], file: rel });
        }
      }
  
      // -----------------------------------------------------------------------
      // Spring (Java)
      // -----------------------------------------------------------------------
      if (ext === '.java') {
        // @GetMapping("/path") @PostMapping @RequestMapping
        const re1 = /@(Get|Post|Put|Patch|Delete|Request)Mapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/g;
        let m;
        while ((m = re1.exec(content)) !== null) {
          const method = m[1] === 'Request' ? 'ANY' : m[1].toUpperCase();
          routes.push({ method, path: m[2], file: rel });
        }
      }
    }
  
    if (routes.length === 0) return '';
  
    const lines = [
      '| Method | Path | File |',
      '|--------|------|------|',
    ];
    for (const r of routes) {
      lines.push(`| ${r.method} | ${r.path} | ${r.file} |`);
    }
    return lines.join('\n');
  }
  
  module.exports = { analyze };
  
};

// ── ./src/cache/sig-cache ──
__factories["./src/cache/sig-cache"] = function(module, exports) {
  'use strict';

  const fs   = require('fs');
  const path = require('path');

  const CACHE_FILE = '.sigmap-cache.json';

  function cachePath(cwd) {
    return path.join(cwd, CACHE_FILE);
  }

  function loadCache(cwd, currentVersion) {
    try {
      const raw = fs.readFileSync(cachePath(cwd), 'utf8');
      const data = JSON.parse(raw);
      if (data.sigmapVersion !== currentVersion) return new Map();
      return new Map(Object.entries(data.entries || {}));
    } catch (_) {
      return new Map();
    }
  }

  function saveCache(cwd, currentVersion, cache) {
    try {
      const data = {
        sigmapVersion: currentVersion,
        entries: Object.fromEntries(cache),
      };
      fs.writeFileSync(cachePath(cwd), JSON.stringify(data), 'utf8');
    } catch (_) {}
  }

  function getChangedFiles(files, cache) {
    const changed = [];
    const unchanged = [];
    for (const f of files) {
      try {
        const mtime = fs.statSync(f).mtimeMs;
        const cached = cache.get(f);
        if (!cached || cached.mtime !== mtime) {
          changed.push(f);
        } else {
          unchanged.push(f);
        }
      } catch (_) {
        changed.push(f);
      }
    }
    return { changed, unchanged };
  }

  function updateCacheEntries(cache, extracted) {
    for (const { file, sigs } of extracted) {
      try {
        const mtime = fs.statSync(file).mtimeMs;
        cache.set(file, { mtime, sigs });
      } catch (_) {}
    }
  }

  module.exports = { loadCache, saveCache, getChangedFiles, updateCacheEntries };
};

// ── ./src/graph/builder ──
__factories["./src/graph/builder"] = function(module, exports) {
  'use strict';
  const fs   = require('fs');
  const path = require('path');
  const JS_EXTS  = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);
  const PY_EXTS  = new Set(['.py', '.pyw']);
  const GO_EXTS  = new Set(['.go']);
  const RS_EXTS  = new Set(['.rs']);
  const JVM_EXTS = new Set(['.java', '.kt', '.kts', '.scala', '.sc']);
  const RB_EXTS  = new Set(['.rb', '.rake']);
  const R_EXTS   = new Set(['.r', '.R']);
  function resolveJsPath(dir, importStr, fileSet) {
    const base = path.resolve(dir, importStr);
    const candidates = [base, base+'.ts', base+'.tsx', base+'.js', base+'.jsx', base+'.mjs', base+'.cjs', path.join(base,'index.ts'), path.join(base,'index.js')];
    for (const c of candidates) { if (fileSet.has(c)) return c; }
    return null;
  }
  function normalizePath(p) {
    return path.normalize(p).toLowerCase();
  }
  function resolveRPath(dir, importStr, fileSet, cwd) {
    const tried = new Set();
    const bases = [path.resolve(dir, importStr)];
    if (cwd) bases.push(path.resolve(cwd, importStr));
    for (const base of bases) {
      for (const c of [base, base + '.R', base + '.r']) {
        const normC = normalizePath(c);
        if (tried.has(normC)) continue;
        tried.add(normC);
        if (fileSet.has(c)) return c;
        if (fileSet.has(normC)) return normC;
      }
    }
    return null;
  }
  function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
  function extractFileDeps(filePath, content, fileSet, cwd, ctx) {
    const ext = path.extname(filePath).toLowerCase();
    const dir = path.dirname(filePath);
    const found = [];
    if (JS_EXTS.has(ext)) {
      const stripped = content.replace(/\/\/.*$/gm,'').replace(/\/\*[\s\S]*?\*\//g,'');
      const reEs = /(?:^|[\r\n])\s*import\s+(?:[^'";\r\n]*?\s+from\s+)?['"](\.[^'"]+)['"]/g;
      let m;
      while ((m = reEs.exec(stripped)) !== null) { const r = resolveJsPath(dir, m[1], fileSet); if (r) found.push(r); }
      const reCjs = /\brequire\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
      while ((m = reCjs.exec(stripped)) !== null) { const r = resolveJsPath(dir, m[1], fileSet); if (r) found.push(r); }
    }
    if (PY_EXTS.has(ext)) {
      const re = /^[ \t]*from\s+(\.+[\w.]*)\s+import/gm; let m;
      while ((m = re.exec(content)) !== null) {
        const dotCount = (m[1].match(/^\.+/)||[''])[0].length;
        const modPart = m[1].slice(dotCount).replace(/\./g,'/');
        let base = dir; for (let i=1;i<dotCount;i++) base=path.dirname(base);
        const candidate = modPart ? path.join(base,modPart+'.py') : null;
        if (candidate) { const normC = normalizePath(candidate); if (fileSet.has(normC)) found.push(normC); }
      }
      const reAbs = /^[ \t]*from\s+([\w.]+)\s+import/gm;
      while ((m = reAbs.exec(content)) !== null) {
        const modulePath = m[1].replace(/\./g,'/');
        const candidates = [path.join(dir,modulePath+'.py'),path.join(dir,modulePath,'__init__.py'),path.resolve(dir,'..',modulePath+'.py'),path.resolve(dir,'..',modulePath,'__init__.py')];
        for (const c of candidates) { const normC = normalizePath(c); if (fileSet.has(normC)) { found.push(normC); break; } }
      }
    }
    if (GO_EXTS.has(ext)) {
      const imports = []; let m;
      const re = /import\s*\(\s*([\s\S]*?)\s*\)/g;
      const reInline = /import\s+"([^"]+)"/g;
      while ((m = re.exec(content)) !== null) { for (const imp of m[1].matchAll(/"([^"]+)"/g)) imports.push(imp[1]); }
      while ((m = reInline.exec(content)) !== null) imports.push(m[1]);
      for (const imp of imports) {
        const suffix = imp.split('/').pop();
        for (const f of fileSet) { const normF = normalizePath(f); if (normF.endsWith(path.sep+suffix+'.go')||normF.includes(path.sep+suffix+path.sep)) { found.push(normF); break; } }
      }
    }
    if (RS_EXTS.has(ext)) {
      const reMod = /^\s*(?:pub\s+)?mod\s+(\w+)\s*;/gm; let m;
      while ((m = reMod.exec(content)) !== null) {
        const c1 = path.join(dir,m[1]+'.rs'); const normC1 = normalizePath(c1); if (fileSet.has(normC1)) found.push(normC1);
        const c2 = path.join(dir,m[1],'mod.rs'); const normC2 = normalizePath(c2); if (fileSet.has(normC2)) found.push(normC2);
      }
    }
    if (JVM_EXTS.has(ext)) {
      const re = /^\s*import\s+([\w.]+)\s*;?/gm; let m;
      while ((m = re.exec(content)) !== null) {
        const asPath = m[1].replace(/\./g,path.sep);
        for (const jvmExt of ['.java','.kt','.kts','.scala','.sc']) {
          for (const f of fileSet) { const normF = normalizePath(f); if (normF.endsWith(normalizePath(asPath+jvmExt))) { found.push(normF); break; } }
        }
      }
    }
    if (RB_EXTS.has(ext)) {
      const re = /^\s*require_relative\s+['"]([^'"]+)['"]/gm; let m;
      while ((m = re.exec(content)) !== null) {
        const base = path.resolve(dir,m[1]);
        const candidate = base.endsWith('.rb') ? base : base+'.rb';
        const normC = normalizePath(candidate);
        if (fileSet.has(normC)) found.push(normC);
      }
    }
    if (R_EXTS.has(ext)) {
      const stripped = content.replace(/#.*$/gm, '');
      const reSrc = /(?:^|[^\w.])source\s*\(\s*["']([^"']+)["']/g; let m;
      while ((m = reSrc.exec(stripped)) !== null) {
        const r = resolveRPath(dir, m[1], fileSet, cwd); if (r) found.push(r);
      }
      if (ctx && ctx.rPackage && ctx.rLocalDefs && ctx.rLocalDefs.size > 0) {
        const reNs = new RegExp('\\b' + escapeRegex(ctx.rPackage) + ':::?([A-Za-z][\\w.]*)', 'g');
        while ((m = reNs.exec(stripped)) !== null) {
          const target = ctx.rLocalDefs.get(m[1]);
          if (!target) continue;
          const normTarget = normalizePath(target);
          const normFilePath = normalizePath(filePath);
          if (normTarget === normFilePath) continue;
          if (fileSet.has(target)) {
            found.push(target);
          } else if (fileSet.has(normTarget)) {
            found.push(normTarget);
          }
        }
      }
    }
    return [...new Set(found)];
  }
  function build(files, cwd, ctx) {
    const fileSet = new Set(files.map((f) => path.resolve(f)));
    const fileSetNormalized = new Set([...fileSet].map(normalizePath));
    const forward = new Map();
    const reverse = new Map();
    for (const f of fileSet) {
      const normF = normalizePath(f);
      if (!forward.has(normF)) forward.set(normF, []);
      if (!reverse.has(normF)) reverse.set(normF, []);
    }
    for (const filePath of fileSet) {
      let content;
      try { content = fs.readFileSync(filePath,'utf8'); } catch(_) { continue; }
      const normFilePath = normalizePath(filePath);
      const deps = extractFileDeps(filePath, content, fileSetNormalized, cwd, ctx);
      if (deps.length > 0) {
        forward.set(normFilePath, deps);
        for (const dep of deps) {
          if (!reverse.has(dep)) reverse.set(dep, []);
          reverse.get(dep).push(normFilePath);
        }
      }
    }
    return { forward, reverse };
  }
  function _readDescriptionPackage(cwd) {
    try {
      const raw = fs.readFileSync(path.join(cwd, 'DESCRIPTION'), 'utf8');
      const m = raw.match(/^Package\s*:\s*(\S+)/m);
      return m ? m[1] : null;
    } catch (_) { return null; }
  }
  function _collectRLocalDefs(rFiles) {
    const defs = new Map();
    const reAssign = /^(?:[ \t]*)([\w.]+)\s*(?:<<-|<-|=)\s*(?:(?:R6::)?R6Class|(?:S7::)?new_class|function)\b/gm;
    const reS4Generic = /^[ \t]*setGeneric\s*\(\s*["']([\w.]+)["']/gm;
    const reS4Class   = /^[ \t]*setClass\s*\(\s*["']([\w.]+)["']/gm;
    for (const filePath of rFiles) {
      let content; try { content = fs.readFileSync(filePath, 'utf8'); } catch (_) { continue; }
      const stripped = content.replace(/#.*$/gm, '');
      let m;
      while ((m = reAssign.exec(stripped)) !== null) {
        if (m[1].startsWith('.')) continue;
        if (!defs.has(m[1])) defs.set(m[1], filePath);
      }
      while ((m = reS4Generic.exec(stripped)) !== null) {
        if (!defs.has(m[1])) defs.set(m[1], filePath);
      }
      while ((m = reS4Class.exec(stripped)) !== null) {
        if (!defs.has(m[1])) defs.set(m[1], filePath);
      }
    }
    return defs;
  }
  function buildFromCwd(cwd, opts) {
    const { srcDirs=['src','app','lib','R','inst'], exclude=['node_modules','.git','dist','build'] } = opts||{};
    const excludeSet = new Set(exclude);
    function walkDir(dir,depth) {
      if (depth>8) return [];
      let entries; try { entries = fs.readdirSync(dir,{withFileTypes:true}); } catch(_) { return []; }
      const out=[];
      for (const e of entries) {
        if (excludeSet.has(e.name)||e.name.startsWith('.')) continue;
        const full = path.join(dir,e.name);
        if (e.isDirectory()) out.push(...walkDir(full,depth+1));
        else if (e.isFile()) {
          const ext = path.extname(e.name).toLowerCase();
          if (JS_EXTS.has(ext)||PY_EXTS.has(ext)||GO_EXTS.has(ext)||RS_EXTS.has(ext)||JVM_EXTS.has(ext)||RB_EXTS.has(ext)||R_EXTS.has(ext)) out.push(full);
        }
      }
      return out;
    }
    const files=[];
    for (const sd of srcDirs) { const absDir=path.resolve(cwd,sd); if (fs.existsSync(absDir)) files.push(...walkDir(absDir,0)); }
    for (const rootFile of ['gen-context.js','index.js','main.js','app.js','app.R','server.R','ui.R','global.R']) {
      const abs=path.resolve(cwd,rootFile); if (fs.existsSync(abs)) files.push(abs);
    }
    let ctx;
    const rPackage = _readDescriptionPackage(cwd);
    if (rPackage) {
      const rFiles = files.filter((f) => R_EXTS.has(path.extname(f).toLowerCase()));
      if (rFiles.length > 0) ctx = { rPackage, rLocalDefs: _collectRLocalDefs(rFiles) };
    }
    return build(files, cwd, ctx);
  }
  module.exports = { build, buildFromCwd, extractFileDeps, normalizePath };
};

// ── ./src/graph/impact ──
__factories["./src/graph/impact"] = function(module, exports) {
  'use strict';
  const path = require('path');
  const { buildFromCwd } = __require('./src/graph/builder');
  const TEST_PATTERNS = [/[./\\](test|tests|spec|__tests__)[./\\]/,/\.(test|spec)\.[jt]sx?$/,/_test\.[jt]sx?$/,/_test\.py$/,/test_[^/\\]+\.py$/];
  const ROUTE_PATTERNS = [/router?\.[jt]sx?$/i,/routes?\.[jt]sx?$/i,/controller\.[jt]sx?$/i,/views?\.[jt]sx?$/i,/handlers?\.[jt]sx?$/i];
  function isTestFile(f) { return TEST_PATTERNS.some((re) => re.test(f.replace(/\\/g,'/'))); }
  function isRouteFile(f) { return ROUTE_PATTERNS.some((re) => re.test(f.replace(/\\/g,'/'))); }
  function normalizePath(p) { return path.normalize(p).toLowerCase(); }
  function bfs(startFile, reverseGraph, maxDepth) {
    const direct=new Set(); const transitive=new Set(); const visited=new Set([startFile]);
    const firstLevel = reverseGraph.get(startFile)||[];
    for (const f of firstLevel) { if (!visited.has(f)) { direct.add(f); visited.add(f); } }
    if (maxDepth===1) return {direct,transitive};
    let frontier=[...direct]; let depth=1;
    while (frontier.length>0 && (maxDepth===0||depth<maxDepth)) {
      const nextFrontier=[];
      for (const node of frontier) {
        const importers=reverseGraph.get(node)||[];
        for (const imp of importers) { if (!visited.has(imp)) { transitive.add(imp); visited.add(imp); nextFrontier.push(imp); } }
      }
      frontier=nextFrontier; depth++;
    }
    return {direct,transitive};
  }
  function getImpact(changedFile, graph, opts) {
    const {depth=0,cwd=process.cwd()} = opts||{};
    const absChanged = normalizePath(path.resolve(cwd,changedFile));
    if (!graph||!graph.reverse) return {changed:changedFile,direct:[],transitive:[],tests:[],routes:[],totalImpact:0};
    const {direct,transitive} = bfs(absChanged,graph.reverse,depth);
    const allImpacted=[...direct,...transitive];
    const tests=allImpacted.filter(isTestFile); const routes=allImpacted.filter(isRouteFile);
    const toRel=(f)=>path.relative(cwd,f).replace(/\\/g,'/');
    return {changed:toRel(absChanged),direct:[...direct].map(toRel),transitive:[...transitive].map(toRel),tests:tests.map(toRel),routes:routes.map(toRel),totalImpact:direct.size+transitive.size};
  }
  function analyzeImpact(changedFiles, cwd, opts) {
    const {depth=3}=opts||{};
    const files=Array.isArray(changedFiles)?changedFiles:[changedFiles];
    let graph;
    try { graph=buildFromCwd(cwd,opts); } catch(_) { graph={forward:new Map(),reverse:new Map()}; }
    return files.map((f)=>({file:f,impact:getImpact(f,graph,{depth,cwd})}));
  }
  function formatImpact(result) {
    const lines=[`## Impact: \`${result.changed}\``,``];
    if (result.direct.length===0&&result.transitive.length===0) { lines.push('_No files import this file — zero blast radius._'); return lines.join('\n'); }
    lines.push(`**Total impacted files:** ${result.totalImpact}`,``);
    if (result.direct.length>0) { lines.push('### Direct importers'); for (const f of result.direct) lines.push(`- \`${f}\``); lines.push(''); }
    if (result.transitive.length>0) { lines.push('### Transitive importers'); for (const f of result.transitive) lines.push(`- \`${f}\``); lines.push(''); }
    if (result.tests.length>0) { lines.push('### Affected tests'); for (const f of result.tests) lines.push(`- \`${f}\``); lines.push(''); }
    if (result.routes.length>0) { lines.push('### Affected routes / controllers'); for (const f of result.routes) lines.push(`- \`${f}\``); lines.push(''); }
    return lines.join('\n');
  }
  function formatImpactJSON(result) {
    return {changed:result.changed,direct:result.direct,transitive:result.transitive,tests:result.tests,routes:result.routes,totalImpact:result.totalImpact};
  }
  module.exports = { getImpact, analyzeImpact, formatImpact, formatImpactJSON };
};

// ── ./src/mcp/handlers ──
__factories["./src/mcp/handlers"] = function(module, exports) {
  
  const fs   = require('fs');
  const path = require('path');
  const { execSync } = require('child_process');
  
  const CONTEXT_FILE = path.join('.github', 'copilot-instructions.md');
  
  // Section header keywords in PROJECT_MAP.md
  const MAP_SECTIONS = {
    imports: '### Import graph',
    classes: '### Class hierarchy',
    routes: '### Route table',
  };
  
  /**
   * read_context({ module? }) → string
   *
   * Returns the full context file, or just the sections whose file paths
   * contain the given module substring.
   */
  function readContext(args, cwd) {
    const contextPath = path.join(cwd, CONTEXT_FILE);
    if (!fs.existsSync(contextPath)) {
      return 'No context file found. Run: node gen-context.js';
    }
  
    const content = fs.readFileSync(contextPath, 'utf8');
  
    if (!args || !args.module) return content;
  
    const mod = args.module.replace(/\\/g, '/').replace(/\/$/, '');
    const lines = content.split('\n');
    const result = [];
    let capturing = false;
  
    for (const line of lines) {
      if (line.startsWith('### ')) {
        const filePath = line.slice(4).trim().replace(/\\/g, '/');
        // Match if file path starts with mod or contains /mod/ or /mod
        capturing =
          filePath === mod ||
          filePath.startsWith(mod + '/') ||
          filePath.includes('/' + mod + '/') ||
          filePath.includes('/' + mod);
        if (capturing) result.push(line);
        continue;
      }
      if (capturing) result.push(line);
    }
  
    if (result.length === 0) return `No signatures found for module: ${mod}`;
    return result.join('\n');
  }
  
  /**
   * search_signatures({ query }) → string
   *
   * Case-insensitive search through all signature lines.
   * Returns matching lines grouped by file path.
   */
  function searchSignatures(args, cwd) {
    if (!args || !args.query) return 'Missing required argument: query';
  
    const contextPath = path.join(cwd, CONTEXT_FILE);
    if (!fs.existsSync(contextPath)) {
      return 'No context file found. Run: node gen-context.js';
    }
  
    const content = fs.readFileSync(contextPath, 'utf8');
    const query = args.query.toLowerCase();
    const lines = content.split('\n');
  
    const result = [];
    let currentFile = '';
    let fileHeaderAdded = false;
  
    for (const line of lines) {
      if (line.startsWith('### ')) {
        currentFile = line.slice(4).trim();
        fileHeaderAdded = false;
        continue;
      }
      // Skip markdown fences and top-level headers
      if (line.startsWith('```') || line.startsWith('## ') || line.startsWith('# ') || line.startsWith('<!--')) {
        continue;
      }
      if (line.toLowerCase().includes(query)) {
        if (currentFile && !fileHeaderAdded) {
          if (result.length > 0) result.push('');
          result.push(`### ${currentFile}`);
          fileHeaderAdded = true;
        }
        result.push(line);
      }
    }
  
    if (result.length === 0) return `No signatures found matching: ${args.query}`;
    return result.join('\n');
  }
  
  /**
   * get_map({ type }) → string
   *
   * Returns a section from PROJECT_MAP.md.
   * type: 'imports' | 'classes' | 'routes'
   */
  function getMap(args, cwd) {
    if (!args || !args.type) return 'Missing required argument: type';
  
    const header = MAP_SECTIONS[args.type];
    if (!header) {
      return `Unknown map type: "${args.type}". Use: imports, classes, routes`;
    }
  
    const mapPath = path.join(cwd, 'PROJECT_MAP.md');
    if (!fs.existsSync(mapPath)) {
      return 'PROJECT_MAP.md not found. Run: node gen-project-map.js';
    }
  
    const content = fs.readFileSync(mapPath, 'utf8');
    const idx = content.indexOf(header);
    if (idx === -1) {
      return `Section "${header}" not found in PROJECT_MAP.md`;
    }
  
    // Extract from this header to the next ### header
    const after = content.slice(idx);
    const nextMatch = after.slice(header.length).search(/\n###\s/);
    return nextMatch === -1 ? after : after.slice(0, header.length + nextMatch);
  }
  
  /**
   * create_checkpoint({ note? }) → string
   *
   * Returns a markdown checkpoint summarising current project state:
   * - Timestamp and optional user note
   * - Active git branch + last 5 commit messages
   * - Token count of current context file
   * - List of modules present in the context
   * - Route count (if PROJECT_MAP.md exists)
   */
  function createCheckpoint(args, cwd) {
    const note = (args && args.note) ? args.note.trim() : '';
    const now = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
    const lines = [
      '# SigMap Checkpoint',
      `**Created:** ${now}`,
    ];
  
    if (note) lines.push(`**Note:** ${note}`);
    lines.push('');
  
    // ── Git info ────────────────────────────────────────────────────────────
    lines.push('## Git state');
    try {
      const branch = execSync('git rev-parse --abbrev-ref HEAD', {
        cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
      }).trim();
      lines.push(`**Branch:** ${branch}`);
    } catch (_) {
      lines.push('**Branch:** (not a git repo)');
    }
  
    try {
      const log = execSync(
        'git log --oneline -5 --no-decorate 2>/dev/null',
        { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
      ).trim();
      if (log) {
        lines.push('');
        lines.push('**Recent commits:**');
        for (const l of log.split('\n')) lines.push(`- ${l}`);
      }
    } catch (_) {} // ignore — not every project uses git
    lines.push('');
  
    // ── Context stats ────────────────────────────────────────────────────────
    lines.push('## Context snapshot');
    const contextPath = path.join(cwd, CONTEXT_FILE);
    if (fs.existsSync(contextPath)) {
      const content = fs.readFileSync(contextPath, 'utf8');
      const tokens = Math.ceil(content.length / 4);
  
      // Count modules (### headers are file paths)
      const modules = content.split('\n').filter((l) => l.startsWith('### ')).map((l) => l.slice(4).trim());
      lines.push(`**Token count:** ~${tokens}`);
      lines.push(`**Modules in context:** ${modules.length}`);
  
      if (modules.length > 0) {
        lines.push('');
        lines.push('**Modules:**');
        for (const m of modules.slice(0, 20)) lines.push(`- ${m}`);
        if (modules.length > 20) lines.push(`- … and ${modules.length - 20} more`);
      }
    } else {
      lines.push('_No context file found. Run: node gen-context.js_');
    }
    lines.push('');
  
    // ── Route summary ────────────────────────────────────────────────────────
    const mapPath = path.join(cwd, 'PROJECT_MAP.md');
    if (fs.existsSync(mapPath)) {
      const mapContent = fs.readFileSync(mapPath, 'utf8');
      const routeLines = mapContent.split('\n').filter((l) => l.startsWith('| ') && !l.startsWith('| Method') && !l.startsWith('|---'));
      if (routeLines.length > 0) {
        lines.push('## Routes');
        lines.push(`**Total routes detected:** ${routeLines.length}`);
        lines.push('');
        for (const r of routeLines.slice(0, 10)) lines.push(r);
        if (routeLines.length > 10) lines.push(`| … | +${routeLines.length - 10} more | |`);
        lines.push('');
      }
    }
  
    lines.push('---');
    lines.push('_Generated by SigMap `create_checkpoint`_');
  
    return lines.join('\n');
  }
  
  /**
   * get_routing({}) → string
   *
   * Reads the current context file, classifies all indexed files by complexity,
   * and returns a formatted markdown routing guide showing which files belong
   * to the fast/balanced/powerful model tier.
   */
  function getRouting(args, cwd) {
    const contextPath = path.join(cwd, CONTEXT_FILE);
    if (!fs.existsSync(contextPath)) {
      return (
        '_No context file found. Run `node gen-context.js --routing` first._\n\n' +
        'This generates routing hints that map each file to a model tier:\n' +
        '- **fast** (haiku/gpt-4o-mini) — config, markup, trivial utilities\n' +
        '- **balanced** (sonnet/gpt-4o) — standard application code\n' +
        '- **powerful** (opus/gpt-4-turbo) — complex, security-critical, or large modules'
      );
    }
  
    // Parse file list from context (### headings are file paths)
    const content = fs.readFileSync(contextPath, 'utf8');
    const fileRels = content.split('\n')
      .filter((l) => l.startsWith('### '))
      .map((l) => l.slice(4).trim());
  
    // Build synthetic fileEntries for the classifier
    // We don't have live sig arrays here, so rebuild from the context blocks
    const entries = [];
    const blocks = content.split(/^### /m).slice(1); // slice past the header
    for (const block of blocks) {
      const firstLine = block.split('\n')[0].trim();
      const codeBlock = block.match(/```\n([\s\S]*?)```/);
      const sigs = codeBlock ? codeBlock[1].trim().split('\n').filter(Boolean) : [];
      entries.push({ filePath: path.join(cwd, firstLine), sigs });
    }
  
    try {
      const { classifyAll } = __require('./src/routing/classifier');
      const { formatRoutingSection } = __require('./src/routing/hints');
      const groups = classifyAll(entries, cwd);
      return formatRoutingSection(groups);
    } catch (err) {
      return `_Routing classification failed: ${err.message}_`;
    }
  }
  
  function explainFile(args, cwd) {
    if (!args || !args.path) return 'Missing required argument: path';
  
    const targetRel = args.path.replace(/\\/g, '/').replace(/^\//, '');
    const targetAbs = path.resolve(cwd, targetRel);
    const contextPath = path.join(cwd, CONTEXT_FILE);
  
    const lines = ['# explain_file: ' + targetRel, ''];
  
    lines.push('## Signatures');
    let indexedFiles = [];
  
    if (fs.existsSync(contextPath)) {
      const ctxContent = fs.readFileSync(contextPath, 'utf8');
      const ctxLines = ctxContent.split('\n');
      let capturing = false;
      const sigLines = [];
  
      for (const line of ctxLines) {
        if (line.startsWith('### ')) {
          if (capturing) break;
          const rel = line.slice(4).trim().replace(/\\/g, '/');
          capturing = rel === targetRel || rel.endsWith('/' + targetRel) || targetRel.endsWith('/' + rel);
          if (capturing) continue;
        } else if (capturing) {
          sigLines.push(line);
        }
      }
  
      const sigs = sigLines.filter((l) => l !== '```' && l.trim() !== '');
      if (sigs.length > 0) {
        lines.push(...sigs);
      } else {
        lines.push('_No signatures indexed for this file. Run: node gen-context.js_');
      }
  
      indexedFiles = ctxContent
        .split('\n')
        .filter((l) => l.startsWith('### '))
        .map((l) => path.resolve(cwd, l.slice(4).trim()));
    } else {
      lines.push('_No context file found. Run: node gen-context.js_');
    }
  
    if (!fs.existsSync(targetAbs)) {
      lines.push('');
      lines.push('> File not found on disk: ' + targetRel);
      return lines.join('\n');
    }
  
    lines.push('');
  
    lines.push('## Imports (direct dependencies)');
    try {
      const { extractImports } = __require('./src/map/import-graph');
      const fileContent = fs.readFileSync(targetAbs, 'utf8');
      const fileSet = new Set(indexedFiles);
      fileSet.add(targetAbs);
      const imports = extractImports(targetAbs, fileContent, fileSet);
      if (imports.length > 0) {
        for (const imp of imports) lines.push('- ' + path.relative(cwd, imp).replace(/\\/g, '/'));
      } else {
        lines.push('_No resolvable relative imports found._');
      }
    } catch (err) {
      lines.push('_Could not analyze imports: ' + err.message + '_');
    }
  
    lines.push('');
  
    lines.push('## Callers (files that import this file)');
    try {
      const { extractImports } = __require('./src/map/import-graph');
      const fileSet = new Set(indexedFiles);
      fileSet.add(targetAbs);
      const callers = [];
      for (const f of indexedFiles) {
        if (f === targetAbs || !fs.existsSync(f)) continue;
        try {
          const fc = fs.readFileSync(f, 'utf8');
          const imps = extractImports(f, fc, fileSet);
          if (imps.includes(targetAbs)) callers.push(path.relative(cwd, f).replace(/\\/g, '/'));
        } catch (_) {}
      }
      if (callers.length > 0) {
        for (const c of callers) lines.push('- ' + c);
      } else {
        lines.push('_No indexed files import this file._');
      }
    } catch (err) {
      lines.push('_Could not analyze callers: ' + err.message + '_');
    }
  
    return lines.join('\n');
  }
  
  function listModules(args, cwd) {
    const contextPath = path.join(cwd, CONTEXT_FILE);
    if (!fs.existsSync(contextPath)) {
      return 'No context file found. Run: node gen-context.js';
    }
  
    const content = fs.readFileSync(contextPath, 'utf8');
    const ctxLines = content.split('\n');
    const groups = {};
    let currentGroup = null;
    let blockBuf = [];
  
    function flushBlock() {
      if (currentGroup === null || blockBuf.length === 0) return;
      if (!groups[currentGroup]) groups[currentGroup] = { fileCount: 0, tokenCount: 0 };
      groups[currentGroup].fileCount++;
      groups[currentGroup].tokenCount += Math.ceil(blockBuf.join('\n').length / 4);
      blockBuf = [];
    }
  
    for (const line of ctxLines) {
      if (line.startsWith('### ')) {
        flushBlock();
        const rel = line.slice(4).trim().replace(/\\/g, '/');
        const parts = rel.split('/');
        currentGroup = parts.length > 1 ? parts[0] : '.';
      } else if (currentGroup !== null) {
        blockBuf.push(line);
      }
    }
    flushBlock();
  
    const sorted = Object.entries(groups)
      .map(([mod, data]) => ({ module: mod, fileCount: data.fileCount, tokenCount: data.tokenCount }))
      .sort((a, b) => b.tokenCount - a.tokenCount);
  
    if (sorted.length === 0) return 'No modules found in context file.';
  
    const total = sorted.reduce((s, m) => s + m.tokenCount, 0);
  
    return [
      '# Modules',
      '',
      '| Module | Files | Tokens |',
      '|--------|-------|--------|',
      ...sorted.map((m) => `| ${m.module} | ${m.fileCount} | ~${m.tokenCount} |`),
      '',
      `**Total context tokens: ~${total}**`,
      '',
      '_Use `read_context({ module: "name" })` to get signatures for a specific module._',
    ].join('\n');
  }
  
  function queryContext(args, cwd) {
    if (!args || !args.query) return 'Missing required argument: query';
    const contextPath = path.join(cwd, CONTEXT_FILE);
    if (!fs.existsSync(contextPath)) return 'No context file found. Run: node gen-context.js';
    try {
      const { rank, buildSigIndex, formatRankTable } = __require('./src/retrieval/ranker');
      const index = buildSigIndex(cwd);
      if (index.size === 0) return 'No signatures indexed. Run: node gen-context.js';
      const topK = Math.min(Math.max(1, parseInt(args.topK, 10) || 10), 25);
      const results = rank(args.query, index, { topK, cwd });
      return formatRankTable(results, args.query);
    } catch (err) {
      return `_query_context failed: ${err.message}_`;
    }
  }
  
  function getImpact(args, cwd) {
    if (!args || !args.file) return 'Missing required argument: file';
    try {
      const { analyzeImpact, formatImpact } = __require('./src/graph/impact');
      const depth = Math.max(0, parseInt(args.depth, 10) || 3);
      const results = analyzeImpact(args.file, cwd, { depth });
      return results.map((r) => formatImpact(r.impact)).join('\n\n---\n\n');
    } catch (err) {
      return `_get_impact failed: ${err.message}_`;
    }
  }

  module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact };
};

// ── ./src/learning/weights ──
__factories["./src/learning/weights"] = function(module, exports) {
  'use strict';

  const fs = require('fs');
  const path = require('path');

  const DECAY = 0.95;
  const MAX_MULT = 3.0;
  const MIN_MULT = 0.30;
  const BASELINE = 1.0;

  function weightsPath(cwd) {
    return path.join(cwd, '.context', 'weights.json');
  }

  function clampMultiplier(value) {
    if (!Number.isFinite(value)) return BASELINE;
    if (value > MAX_MULT) return MAX_MULT;
    if (value < MIN_MULT) return MIN_MULT;
    return parseFloat(value.toFixed(6));
  }

  function normalizeFile(cwd, filePath) {
    if (!cwd || !filePath || typeof filePath !== 'string') return null;
    const cleaned = filePath.trim().replace(/\\/g, '/');
    if (!cleaned) return null;
    const abs = path.resolve(cwd, cleaned);
    const rel = path.relative(cwd, abs);
    if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return null;
    return rel.split(path.sep).join('/');
  }

  function sanitizeWeights(cwd, weights) {
    const out = {};
    const entries = weights && typeof weights === 'object' ? Object.entries(weights) : [];
    for (const [filePath, raw] of entries) {
      const normalized = normalizeFile(cwd, filePath);
      if (!normalized) continue;
      const mult = clampMultiplier(Number(raw));
      if (Math.abs(mult - BASELINE) < 1e-9) continue;
      out[normalized] = mult;
    }
    return out;
  }

  function loadWeights(cwd) {
    try {
      const parsed = JSON.parse(fs.readFileSync(weightsPath(cwd), 'utf8'));
      return sanitizeWeights(cwd, parsed);
    } catch (_) {
      return {};
    }
  }

  function saveWeights(cwd, weights) {
    const cleaned = sanitizeWeights(cwd, weights);
    const outPath = weightsPath(cwd);
    if (Object.keys(cleaned).length === 0) {
      try {
        if (fs.existsSync(outPath)) fs.unlinkSync(outPath);
      } catch (_) {}
      return;
    }
    fs.mkdirSync(path.dirname(outPath), { recursive: true });
    const sorted = Object.keys(cleaned).sort().reduce((acc, key) => {
      acc[key] = cleaned[key];
      return acc;
    }, {});
    fs.writeFileSync(outPath, JSON.stringify(sorted, null, 2) + '\n', 'utf8');
  }

  function updateWeights(cwd, opts) {
    opts = opts || {};
    const goodAmount = Number.isFinite(opts.goodAmount) ? opts.goodAmount : 0.15;
    const badAmount = Number.isFinite(opts.badAmount) ? opts.badAmount : 0.10;
    const goodFiles = Array.isArray(opts.goodFiles) ? opts.goodFiles : [];
    const badFiles = Array.isArray(opts.badFiles) ? opts.badFiles : [];
    const weights = loadWeights(cwd);

    for (const key of Object.keys(weights)) {
      weights[key] = clampMultiplier(weights[key] * DECAY);
    }

    const good = [];
    const bad = [];

    for (const filePath of goodFiles) {
      const normalized = normalizeFile(cwd, filePath);
      if (!normalized) continue;
      weights[normalized] = clampMultiplier((weights[normalized] || BASELINE) + goodAmount);
      good.push(normalized);
    }

    for (const filePath of badFiles) {
      const normalized = normalizeFile(cwd, filePath);
      if (!normalized) continue;
      weights[normalized] = clampMultiplier((weights[normalized] || BASELINE) - badAmount);
      bad.push(normalized);
    }

    saveWeights(cwd, weights);
    return { good, bad, weights: loadWeights(cwd) };
  }

  function boostFiles(cwd, files, amount) {
    return updateWeights(cwd, { goodFiles: files, goodAmount: amount });
  }

  function penalizeFiles(cwd, files, amount) {
    return updateWeights(cwd, { badFiles: files, badAmount: amount });
  }

  function resetWeights(cwd) {
    const outPath = weightsPath(cwd);
    if (fs.existsSync(outPath)) fs.unlinkSync(outPath);
  }

  function exportWeights(cwd, outputPath) {
    const weights = loadWeights(cwd);
    const json = JSON.stringify(weights, null, 2) + '\n';
    if (outputPath) {
      fs.mkdirSync(path.dirname(path.resolve(outputPath)), { recursive: true });
      fs.writeFileSync(outputPath, json, 'utf8');
    } else {
      process.stdout.write(json);
    }
    return weights;
  }

  function importWeights(cwd, importPath, replace) {
    let incoming;
    try {
      incoming = JSON.parse(fs.readFileSync(importPath, 'utf8'));
    } catch (err) {
      throw new Error('Cannot read weights file: ' + err.message);
    }
    const sanitized = sanitizeWeights(cwd, incoming);
    if (replace) {
      saveWeights(cwd, sanitized);
      return sanitized;
    }
    const existing = loadWeights(cwd);
    const merged = Object.assign({}, existing, sanitized);
    saveWeights(cwd, merged);
    return merged;
  }

  module.exports = {
    BASELINE, DECAY, MAX_MULT, MIN_MULT,
    weightsPath, clampMultiplier, normalizeFile,
    loadWeights, saveWeights, updateWeights,
    boostFiles, penalizeFiles, resetWeights,
    exportWeights, importWeights,
  };
};

// ── ./src/mcp/server ──
__factories["./src/mcp/server"] = function(module, exports) {
  
  /**
   * SigMap MCP server — zero npm dependencies.
   *
   * Wire protocol: JSON-RPC 2.0 over stdio.
   * One JSON object per line on both stdin and stdout.
   *
   * Supported methods:
   *   initialize        → serverInfo + capabilities
   *   tools/list        → 3 tool definitions
   *   tools/call        → dispatch to handler, return result
   */
  
  const readline = require('readline');
  const { TOOLS } = __require('./src/mcp/tools');
  const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact } = __require('./src/mcp/handlers');
  
  const SERVER_INFO = {
    name: 'sigmap',
  version: '6.10.10',
    description: 'SigMap MCP server — code signatures on demand',
  };
  
  // ---------------------------------------------------------------------------
  // JSON-RPC helpers
  // ---------------------------------------------------------------------------
  function respond(id, result) {
    process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id, result }) + '\n');
  }
  
  function respondError(id, code, message) {
    process.stdout.write(
      JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } }) + '\n'
    );
  }
  
  // ---------------------------------------------------------------------------
  // Method dispatcher
  // ---------------------------------------------------------------------------
  function dispatch(msg, cwd) {
    const { method, id, params } = msg;
  
    // Notifications (no id) need no response
    if (method === 'notifications/initialized' || method === 'notifications/cancelled') {
      return;
    }
  
    if (method === 'initialize') {
      respond(id, {
        protocolVersion: (params && params.protocolVersion) || '2024-11-05',
        serverInfo: SERVER_INFO,
        capabilities: { tools: {} },
      });
      return;
    }
  
    if (method === 'tools/list') {
      respond(id, { tools: TOOLS });
      return;
    }
  
    if (method === 'tools/call') {
      const name = params && params.name;
      const args = (params && params.arguments) || {};
  
      let text;
      try {
        if (name === 'read_context') text = readContext(args, cwd);
        else if (name === 'search_signatures') text = searchSignatures(args, cwd);
        else if (name === 'get_map') text = getMap(args, cwd);
        else if (name === 'create_checkpoint') text = createCheckpoint(args, cwd);
        else if (name === 'get_routing') text = getRouting(args, cwd);
        else if (name === 'explain_file') text = explainFile(args, cwd);
        else if (name === 'list_modules') text = listModules(args, cwd);
        else if (name === 'query_context') text = queryContext(args, cwd);
        else if (name === 'get_impact') text = getImpact(args, cwd);
        else {
          respondError(id, -32601, `Unknown tool: ${name}`);
          return;
        }
      } catch (err) {
        respondError(id, -32603, `Tool error: ${err.message}`);
        return;
      }
  
      respond(id, {
        content: [{ type: 'text', text: String(text) }],
      });
      return;
    }
  
    // Unknown method
    if (id !== undefined && id !== null) {
      respondError(id, -32601, `Method not found: ${method}`);
    }
  }
  
  // ---------------------------------------------------------------------------
  // Server entry point
  // ---------------------------------------------------------------------------
  function start(cwd) {
    const rl = readline.createInterface({ input: process.stdin, terminal: false });
  
    rl.on('line', (line) => {
      const trimmed = line.trim();
      if (!trimmed) return;
  
      let msg;
      try {
        msg = JSON.parse(trimmed);
      } catch (_) {
        // Cannot respond without a valid id — ignore malformed input
        return;
      }
  
      try {
        dispatch(msg, cwd);
      } catch (err) {
        const id = (msg && msg.id) != null ? msg.id : null;
        respondError(id, -32603, `Internal error: ${err.message}`);
      }
    });
  
    rl.on('close', () => {
      process.exit(0);
    });
  }
  
  module.exports = { start };
  
};

// ── ./src/mcp/tools ──
__factories["./src/mcp/tools"] = function(module, exports) {
  
  /**
   * MCP tool definitions for SigMap.
   * Three tools: read_context, search_signatures, get_map.
   */
  
  const TOOLS = [
    {
      name: 'read_context',
      description:
        'Read extracted code signatures for the project or a specific module path. ' +
        'Returns the full copilot-instructions.md content (~500–4K tokens) or a ' +
        'filtered subset when a module path is provided (~50–500 tokens).',
      inputSchema: {
        type: 'object',
        properties: {
          module: {
            type: 'string',
            description:
              'Optional subdirectory path to scope results (e.g. "src/services"). ' +
              'Omit to get the full codebase context.',
          },
        },
        required: [],
      },
    },
    {
      name: 'search_signatures',
      description:
        'Search extracted code signatures for a keyword, function name, or class name. ' +
        'Returns matching signature lines with their file paths.',
      inputSchema: {
        type: 'object',
        properties: {
          query: {
            type: 'string',
            description: 'Keyword to search for in signatures (case-insensitive).',
          },
        },
        required: ['query'],
      },
    },
    {
      name: 'get_map',
      description:
        'Read a section from PROJECT_MAP.md — import graph, class hierarchy, or route table. ' +
        'Requires gen-project-map.js to have been run first.',
      inputSchema: {
        type: 'object',
        properties: {
          type: {
            type: 'string',
            enum: ['imports', 'classes', 'routes'],
            description: 'Which section to retrieve: imports, classes, or routes.',
          },
        },
        required: ['type'],
      },
    },
    {
      name: 'create_checkpoint',
      description:
        'Create a session checkpoint summarising current project state. ' +
        'Returns recent git commits, active branch, token count, and a ' +
        'compact snapshot of the codebase context — ideal for session handoffs ' +
        'or periodic saves during long coding sessions.',
      inputSchema: {
        type: 'object',
        properties: {
          note: {
            type: 'string',
            description: 'Optional free-text note to include in the checkpoint (e.g. what you were working on).',
          },
        },
        required: [],
      },
    },
    {
      name: 'get_routing',
      description:
        'Get model routing hints for this project — which files belong to which complexity ' +
        'tier (fast/balanced/powerful) and which AI model to use for each type of task. ' +
        'Helps reduce API costs by 40–80% by routing simple tasks to cheaper models.',
      inputSchema: {
        type: 'object',
        properties: {},
        required: [],
      },
    },
    {
      name: 'explain_file',
      description:
        'Explain a specific file: returns its extracted signatures, direct imports ' +
        '(files it depends on), and callers (files that import it). ' +
        'Ideal for understanding a file in isolation without reading raw source. ' +
        'Requires the context file to have been generated first.',
      inputSchema: {
        type: 'object',
        properties: {
          path: {
            type: 'string',
            description:
              'Relative path from the project root (e.g. "src/services/auth.ts"). ' +
              'Use the paths shown in read_context output.',
          },
        },
        required: ['path'],
      },
    },
    {
      name: 'list_modules',
      description:
        'List all top-level modules (srcDirs) present in the context file, ' +
        'sorted by token count descending. Use this to decide which module to ' +
        'pass to read_context before querying a specific area of the codebase.',
      inputSchema: {
        type: 'object',
        properties: {},
        required: [],
      },
    },
    {
      name: 'query_context',
      description:
        'Rank and return the most relevant files for a specific task or question. ' +
        'Uses keyword + symbol + path scoring to surface only the top-K files relevant ' +
        'to the query — much cheaper than reading all context. ' +
        'Returns ranked file list with signatures and relevance scores.',
      inputSchema: {
        type: 'object',
        properties: {
          query: {
            type: 'string',
            description:
              'Natural language task description or keyword(s) to rank files against. ' +
              'E.g. "add a new language extractor", "fix secret scanning", "auth module".',
          },
          topK: {
            type: 'number',
            description: 'Maximum number of files to return (default: 10, max: 25).',
          },
        },
        required: ['query'],
      },
    },
    {
      name: 'get_impact',
      description:
        'Show every file that is impacted when a given file changes — direct importers, ' +
        'transitive importers, affected tests, and affected routes/controllers. ' +
        'Gives agents instant blast-radius awareness before making a change. ' +
        'Handles circular dependencies safely (no infinite loops).',
      inputSchema: {
        type: 'object',
        properties: {
          file: {
            type: 'string',
            description:
              'Relative path from the project root of the file that changed ' +
              '(e.g. "src/extractors/python.js"). Use forward slashes.',
          },
          depth: {
            type: 'number',
            description: 'BFS traversal depth limit (default: 3). Use 0 for unlimited.',
          },
        },
        required: ['file'],
      },
    },
  ];
  
  module.exports = { TOOLS };
  
};

// ── ./src/routing/classifier ──
__factories["./src/routing/classifier"] = function(module, exports) {
  
  /**
   * Classify files by complexity tier for model routing hints.
   *
   * Tiers:
   *   'fast'     — simple files: config, markup, templates, trivial utilities
   *   'balanced' — standard application code: moderate functions and classes
   *   'powerful' — complex files: large classes, many exports, security-critical paths
   *
   * @param {string} filePath - absolute path to the file
   * @param {string[]} sigs   - extracted signatures for the file
   * @returns {'fast'|'balanced'|'powerful'}
   */
  function classify(filePath, sigs) {
    const lower = filePath.toLowerCase();
    const sigCount = sigs.length;
  
    // ── Fast tier heuristics ────────────────────────────────────────────────
    // Configuration, markup, templates, and trivial utilities are small tasks
    if (
      lower.endsWith('.json') ||
      lower.endsWith('.yml') ||
      lower.endsWith('.yaml') ||
      lower.endsWith('.toml') ||
      lower.endsWith('.env') ||
      lower.endsWith('.html') ||
      lower.endsWith('.htm') ||
      lower.endsWith('.css') ||
      lower.endsWith('.scss') ||
      lower.endsWith('.sass') ||
      lower.endsWith('.less') ||
      /dockerfile/i.test(lower) ||
      lower.endsWith('.sh') ||
      lower.endsWith('.bash') ||
      lower.endsWith('.zsh') ||
      lower.endsWith('.fish')
    ) {
      return 'fast';
    }
  
    // Config-like directories
    if (
      lower.includes('/config/') ||
      lower.includes('/configs/') ||
      lower.includes('/fixtures/') ||
      lower.includes('/migrations/') ||
      lower.includes('/seeds/')
    ) {
      return sigCount > 4 ? 'balanced' : 'fast';
    }
  
    // Test files — usually standard complexity
    if (/\.(test|spec)\.[a-z]+$/.test(lower) || /_test\.[a-z]+$/.test(lower)) {
      return 'balanced';
    }
  
    // ── Powerful tier heuristics — high-signal keywords in the path ─────────
    if (
      lower.includes('/security/') ||
      lower.includes('/auth/') ||
      lower.includes('/crypto/') ||
      lower.includes('/core/') ||
      lower.includes('/engine/') ||
      lower.includes('/compiler/') ||
      lower.includes('/parser/') ||
      lower.includes('/scheduler/') ||
      lower.includes('/orchestrat')
    ) {
      return 'powerful';
    }
  
    // Many exports → complex file
    if (sigCount >= 12) return 'powerful';
  
    // Large number of class-level methods (indented 2-space sigs)
    const methodCount = sigs.filter((s) => s.startsWith('  ')).length;
    if (methodCount >= 8) return 'powerful';
  
    // ── Balanced covers everything else ────────────────────────────────────
    if (sigCount <= 2) return 'fast';
    return 'balanced';
  }
  
  /**
   * Classify all file entries and group them by tier.
   *
   * @param {Array<{filePath: string, sigs: string[]}>} fileEntries
   * @returns {{ fast: string[], balanced: string[], powerful: string[] }}
   *   Each array contains relative paths (relative to cwd).
   */
  function classifyAll(fileEntries, cwd) {
    const path = require('path');
    const result = { fast: [], balanced: [], powerful: [] };
    for (const { filePath, sigs } of fileEntries) {
      const tier = classify(filePath, sigs);
      result[tier].push(path.relative(cwd, filePath));
    }
    return result;
  }
  
  module.exports = { classify, classifyAll };
  
};

// ── ./src/routing/hints ──
__factories["./src/routing/hints"] = function(module, exports) {
  
  /**
   * Model routing hint definitions for SigMap.
   *
   * Maps complexity tiers to:
   *   - example model names (vendor-agnostic labels used as hints)
   *   - task types that belong to each tier
   *   - estimated per-1K-token API cost (USD, illustrative — update as needed)
   *
   * These are embedded in the generated context file when `routing: true`
   * so that AI agents and developers know which model tier to invoke.
   */
  
  const TIERS = {
    fast: {
      label: 'Fast (low-cost)',
      examples: 'claude-haiku-4-5, gpt-5-1-codex-mini, gemini-3-flash',
      tasks: [
        'Autocomplete and inline suggestions',
        'Edit config or markup files',
        'Fix typos and rename symbols',
        'Format, lint, and trivial style changes',
        'Explain a short utility function',
        'Generate simple shell scripts or Dockerfiles',
      ],
      costHint: '~$0.0008 / 1K tokens',
    },
  
    balanced: {
      label: 'Balanced (mid-tier)',
      examples: 'claude-sonnet-4-6, gpt-5-2, gemini-3-1-pro',
      tasks: [
        'Write unit or integration tests',
        'Implement a well-scoped feature function',
        'Debug a runtime error with stack trace',
        'Refactor a module (< 200 lines)',
        'Generate a PR description',
        'Explain a multi-function module',
      ],
      costHint: '~$0.003 / 1K tokens',
    },
  
    powerful: {
      label: 'Powerful (high-cost)',
      examples: 'claude-opus-4-6, gpt-5-4, gemini-2-5-pro',
      tasks: [
        'Cross-cutting architecture decisions',
        'Multi-file refactor spanning 5+ files',
        'Security audit (OWASP Top 10)',
        'Complex debugging across async boundaries',
        'Migration plan for a library/framework upgrade',
        'Designing a new module from requirements',
      ],
      costHint: '~$0.015 / 1K tokens',
    },
  };
  
  /**
   * Format the routing section as markdown to append to the context file.
   *
   * @param {{ fast: string[], balanced: string[], powerful: string[] }} groups
   *   Relative file paths grouped by tier (from classifier.classifyAll).
   * @returns {string} Markdown block to embed in the context output.
   */
  function formatRoutingSection(groups) {
    const lines = [
      '',
      '---',
      '',
      '## Model routing hints',
      '<!-- Generated by SigMap routing module — update gen-context.config.json to disable -->',
      '',
      'Select the model tier based on the task complexity and the files involved.',
      '',
    ];
  
    for (const [tier, info] of Object.entries(TIERS)) {
      const files = groups[tier] || [];
      lines.push(`### ${info.label}`);
      lines.push(`**Examples:** ${info.examples}  `);
      lines.push(`**Cost:** ${info.costHint}`);
      lines.push('');
      lines.push('**Use for tasks like:**');
      for (const task of info.tasks) lines.push(`- ${task}`);
      lines.push('');
      if (files.length > 0) {
        lines.push('**Files in this tier:**');
        for (const f of files.slice(0, 15)) lines.push(`- \`${f}\``);
        if (files.length > 15) lines.push(`- … and ${files.length - 15} more`);
      } else {
        lines.push('**Files in this tier:** _(none detected)_');
      }
      lines.push('');
    }
  
    lines.push('> **Tip:** Run `node gen-context.js --routing` to regenerate routing hints.');
    lines.push('> See `docs/MODEL_ROUTING.md` for full routing guide and cost optimisation tips.');
  
    return lines.join('\n');
  }
  
  module.exports = { TIERS, formatRoutingSection };
  
};

// ── ./src/security/patterns ──
__factories["./src/security/patterns"] = function(module, exports) {
  
  /**
   * Secret detection patterns for SigMap scanner.
   * Each pattern has a name and a regex tested against signature strings.
   */
  const PATTERNS = [
    {
      name: 'AWS Access Key',
      regex: /AKIA[0-9A-Z]{16}/,
    },
    {
      name: 'AWS Secret Key',
      // 40-char base64-like string following common AWS secret key assignment patterns
      regex: /(?:aws_secret|secret_access_key|SecretAccessKey)\s*[:=]\s*['"]?[0-9a-zA-Z/+]{40}/i,
    },
    {
      name: 'GCP API Key',
      regex: /AIza[0-9A-Za-z\-_]{35}/,
    },
    {
      name: 'GitHub Token',
      regex: /gh[pousr]_[A-Za-z0-9_]{36,}/,
    },
    {
      name: 'JWT Token',
      regex: /eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/,
    },
    {
      name: 'DB Connection String',
      regex: /(mongodb|postgres|postgresql|mysql|redis):\/\/[^:]+:[^@]+@/i,
    },
    {
      name: 'SSH Private Key',
      regex: /-----BEGIN (RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/,
    },
    {
      name: 'Stripe Key',
      regex: /sk_(live|test)_[0-9a-zA-Z]{24,}/,
    },
    {
      name: 'Twilio Key',
      regex: /SK[0-9a-fA-F]{32}/,
    },
    {
      name: 'Generic Secret',
      regex: /(secret|password|passwd|api_key|apikey|auth_token|access_token)\s*[:=]\s*['"][^'"]{8,}['"]/i,
    },
  ];
  
  module.exports = { PATTERNS };
  
};

// ── ./src/security/scanner ──
__factories["./src/security/scanner"] = function(module, exports) {
  
  const { PATTERNS } = __require('./src/security/patterns');
  
  /**
   * Scan an array of signature strings for secrets.
   *
   * @param {string[]} signatures - Array of extracted signature strings
   * @param {string} filePath - Source file path (used in redaction message)
   * @returns {{ safe: string[], redacted: boolean }}
   *   safe      — signatures with any secret-containing entries replaced
   *   redacted  — true if at least one signature was redacted
   */
  function scan(signatures, filePath) {
    if (!Array.isArray(signatures)) return { safe: [], redacted: false };
  
    try {
      let redacted = false;
      const safe = signatures.map((sig) => {
        if (typeof sig !== 'string') return sig;
        for (const pattern of PATTERNS) {
          if (pattern.regex.test(sig)) {
            redacted = true;
            return `[REDACTED — ${pattern.name} detected in ${filePath}]`;
          }
        }
        return sig;
      });
      return { safe, redacted };
    } catch (_) {
      // Never throw — return original signatures on any error
      return { safe: signatures, redacted: false };
    }
  }
  
  module.exports = { scan };
  
};

// ── ./src/judge/judge-engine ──
__factories["./src/judge/judge-engine"] = function(module, exports) {
  'use strict';

  const fs = require('fs');
  const path = require('path');
  const { boostFiles, normalizeFile, penalizeFiles } = __require('./src/learning/weights');

  const STOP = new Set([
    'the','a','an','in','on','at','to','of','for','and','or','but',
    'is','are','was','were','be','been','being','have','has','had',
    'do','does','did','will','would','could','should','may','might',
    'shall','can','not','with','from','by','as','this','that','it',
  ]);

  function tokenize(text) {
    return (text || '').toLowerCase().match(/\b[a-z][a-z0-9_]{2,}\b/g) || [];
  }

  function groundedness(response, context) {
    if (!response || !context) return 0;
    const ctxTokens = new Set(tokenize(context).filter((t) => !STOP.has(t)));
    if (ctxTokens.size === 0) return 0;
    const respTokens = tokenize(response).filter((t) => !STOP.has(t));
    if (respTokens.length === 0) return 0;
    const matched = respTokens.filter((t) => ctxTokens.has(t));
    return parseFloat((matched.length / respTokens.length).toFixed(3));
  }

  const GENERIC_MARKERS = [
    'however, based on my knowledge',
    'generally speaking',
    'in general',
    'typically,',
    'usually,',
    'as a general rule',
  ];

  function extractContextFiles(context, cwd) {
    if (!context || !cwd) return [];
    const seen = new Set();
    const files = [];
    const lines = context.split('\n');
    for (const line of lines) {
      const match = line.match(/^#{2,3}\s+(.+?)\s*$/);
      if (!match) continue;
      const normalized = normalizeFile(cwd, match[1]);
      if (!normalized) continue;
      const abs = path.join(cwd, normalized);
      if (!fs.existsSync(abs) || seen.has(normalized)) continue;
      seen.add(normalized);
      files.push(normalized);
    }
    return files;
  }

  function judge(response, context, opts) {
    opts = opts || {};
    const score = groundedness(response, context);
    const threshold = opts.threshold !== undefined ? opts.threshold : 0.25;
    const reasons = [];
    if (score < threshold) {
      reasons.push(`score ${score} is below threshold ${threshold} — response may not be grounded in context`);
    }
    if (response) {
      const lower = response.toLowerCase();
      for (const m of GENERIC_MARKERS) {
        if (lower.includes(m)) reasons.push(`response contains generic phrase: "${m}"`);
      }
    }
    const verdict = score >= threshold && reasons.length === 0 ? 'pass' : 'fail';
    const result = { score, verdict, reasons };

    if (opts.learn) {
      const learning = { applied: false, action: 'none', files: [] };
      if (!opts.cwd) {
        learning.reason = 'cwd is required for learning';
        result.learning = learning;
        return result;
      }
      const contextFiles = extractContextFiles(context, opts.cwd);
      learning.files = contextFiles;
      if (contextFiles.length === 0) {
        learning.reason = 'no context files found in context headings';
        result.learning = learning;
        return result;
      }
      if (score > 0.75) {
        boostFiles(opts.cwd, contextFiles, 0.05);
        learning.applied = true;
        learning.action = 'boost';
      } else if (score < 0.40) {
        penalizeFiles(opts.cwd, contextFiles, 0.03);
        learning.applied = true;
        learning.action = 'penalize';
      } else {
        learning.reason = 'groundedness in no-op band (0.40-0.75)';
      }
      result.learning = learning;
    }

    return result;
  }

  module.exports = { groundedness, judge };
};

// ── ./src/tracking/logger ──
__factories["./src/tracking/logger"] = function(module, exports) {
  
  /**
   * SigMap usage logger (v0.9)
   *
   * Writes an append-only newline-delimited JSON (NDJSON) log at
   *   .context/usage.ndjson
   *
   * Each line is one JSON object describing a gen-context run.
   * Zero npm dependencies — pure Node.js fs.
   *
   * Enabled by:
   *   config.tracking: true   (gen-context.config.json)
   *   --track CLI flag
   */
  
  const fs = require('fs');
  const path = require('path');
  
  const LOG_FILE = path.join('.context', 'usage.ndjson');
  
  /**
   * Append one run entry to the usage log.
   * @param {object} entry - Run metrics from runGenerate()
   * @param {string} cwd   - Project root (absolute path)
   */
  function logRun(entry, cwd) {
    try {
      const logPath = path.join(cwd, LOG_FILE);
      const dir = path.dirname(logPath);
      if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
  
      const record = {
        ts: new Date().toISOString(),
        version: entry.version || '0.9.0',
        fileCount: entry.fileCount || 0,
        droppedCount: entry.droppedCount || 0,
        rawTokens: entry.rawTokens || 0,
        finalTokens: entry.finalTokens || 0,
        reductionPct: entry.rawTokens > 0
          ? parseFloat((100 - (entry.finalTokens / entry.rawTokens) * 100).toFixed(1))
          : 0,
        overBudget: entry.overBudget || false,
        budgetLimit: entry.budgetLimit || 6000,
      };
  
      fs.appendFileSync(logPath, JSON.stringify(record) + '\n', 'utf8');
    } catch (err) {
      // Never crash the main process — tracking is optional
      process.stderr.write(`[sigmap] tracking: could not write log: ${err.message}\n`);
    }
  }
  
  /**
   * Read and parse all usage log entries.
   * @param {string} cwd - Project root (absolute path)
   * @returns {object[]} Array of parsed log records (oldest first)
   */
  function readLog(cwd) {
    try {
      const logPath = path.join(cwd, LOG_FILE);
      if (!fs.existsSync(logPath)) return [];
      const raw = fs.readFileSync(logPath, 'utf8');
      return raw
        .split('\n')
        .filter(Boolean)
        .map((line) => {
          try { return JSON.parse(line); } catch (_) { return null; }
        })
        .filter(Boolean);
    } catch (_) {
      return [];
    }
  }
  
  /**
   * Compute summary statistics from an array of log records.
   * @param {object[]} entries
   * @returns {object} Summary stats
   */
  function summarize(entries) {
    if (!entries || entries.length === 0) {
      return {
        totalRuns: 0,
        avgReductionPct: 0,
        avgFinalTokens: 0,
        avgRawTokens: 0,
        minFinalTokens: 0,
        maxFinalTokens: 0,
        firstRun: null,
        lastRun: null,
        overBudgetRuns: 0,
      };
    }
  
    const reductions = entries.map((e) => e.reductionPct || 0);
    const finals = entries.map((e) => e.finalTokens || 0);
    const raws = entries.map((e) => e.rawTokens || 0);
  
    const avg = (arr) => arr.reduce((a, b) => a + b, 0) / arr.length;
  
    return {
      totalRuns: entries.length,
      avgReductionPct: parseFloat(avg(reductions).toFixed(1)),
      avgFinalTokens: Math.round(avg(finals)),
      avgRawTokens: Math.round(avg(raws)),
      minFinalTokens: Math.min(...finals),
      maxFinalTokens: Math.max(...finals),
      firstRun: entries[0].ts || null,
      lastRun: entries[entries.length - 1].ts || null,
      overBudgetRuns: entries.filter((e) => e.overBudget).length,
    };
  }
  
  module.exports = { logRun, readLog, summarize };
  
};

// ── ./src/session/memory ──
__factories["./src/session/memory"] = function(module, exports) {
  'use strict';

  const fs = require('fs');
  const path = require('path');

  const SESSION_TTL_MS = 4 * 60 * 60 * 1000;   // 4 hours — one coding session

  function sessionPath(cwd) {
    return path.join(cwd, '.context', 'session.json');
  }

  function loadSession(cwd) {
    const p = sessionPath(cwd);
    if (!fs.existsSync(p)) return null;
    try {
      const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
      if (Date.now() - raw.ts > SESSION_TTL_MS) return null;  // expired
      return raw;
    } catch {
      return null;
    }
  }

  function saveSession(cwd, { intent, topFiles, query }) {
    const p = sessionPath(cwd);
    fs.mkdirSync(path.dirname(p), { recursive: true });
    fs.writeFileSync(p, JSON.stringify({
      ts: Date.now(),
      intent,
      topFiles,   // [{ file, score }] — top 5 from last ask
      lastQuery: query,
    }));
  }

  function mergeSessionContext(scores, session, currentIntent) {
    if (!session) return scores;

    const boostAmount = session.intent === currentIntent ? 0.20 : 0.10;
    const sessionBoost = new Map(session.topFiles.map(f => [f.file, boostAmount]));

    return scores.map(r => ({
      ...r,
      score: r.score + (sessionBoost.get(r.file) || 0),
    }));
  }

  function clearSession(cwd) {
    const p = sessionPath(cwd);
    if (fs.existsSync(p)) fs.unlinkSync(p);
  }

  module.exports = { loadSession, saveSession, mergeSessionContext, clearSession };
};

// ── ./src/plan/planner ──
__factories["./src/plan/planner"] = function(module, exports) {
  'use strict';

  const { buildFromCwd } = __require('./src/graph/builder');
  const { getImpact } = __require('./src/graph/impact');
  const { buildSigIndex, rank, detectIntent } = __require('./src/retrieval/ranker');
  const { buildTestIndex, isTested } = __require('./src/extractors/coverage');

  function createPlan(goal, cwd, config) {
    const intent = detectIntent(goal);
    const sigIndex = buildSigIndex(cwd);
    if (sigIndex.size === 0) {
      return { error: 'no context found' };
    }

    const ranked = rank(goal, sigIndex, { topK: 15, cwd });
    const highConf = ranked.filter(r => r.confidence === 'high').slice(0, 5);
    const medConf = ranked.filter(r => r.confidence === 'medium').slice(0, 5);

    let impact = null;
    if (highConf.length > 0) {
      const entryFile = highConf[0].file;
      try {
        const graph = buildFromCwd(cwd);
        impact = getImpact(entryFile, graph, { maxDepth: 3, cwd });
      } catch (_) {
        // Graph build failed, continue without impact
      }
    }

    let testedFiles = [];
    try {
      const testIndex = buildTestIndex(cwd, config.testDirs || ['test', 'tests', '__tests__', 'spec']);
      testedFiles = highConf.filter(r => {
        const sigs = r.sigs || [];
        const fnNames = sigs.map(s => {
          const m = s.match(/(?:function|def|fn)\s+(\w+)/);
          return m ? m[1] : null;
        }).filter(Boolean);
        return fnNames.some(fn => isTested(fn, testIndex));
      });
    } catch (_) {
      // Coverage index failed, continue without test info
    }

    return {
      goal,
      intent,
      inspectFirst: highConf.map(r => r.file),
      likelyToChange: medConf.map(r => r.file),
      impactRadius: impact ? {
        direct: [...(impact.direct || [])],
        transitive: [...(impact.transitive || [])],
      } : null,
      testsAffected: testedFiles.map(r => r.file),
    };
  }

  module.exports = { createPlan };
};

// ── ./src/retrieval/tokenizer ──
__factories["./src/retrieval/tokenizer"] = function(module, exports) {
  'use strict';
  const STOP_WORDS = new Set([
    'the', 'a', 'an', 'in', 'of', 'to', 'for', 'and', 'or', 'is', 'are',
    'that', 'this', 'it', 'with', 'from', 'by', 'be', 'as', 'on', 'at',
    'do', 'not', 'use', 'get', 'set', 'up', 'if', 'no', 'so', 'we',
  ]);
  function tokenize(text, opts) {
    if (!text || typeof text !== 'string') return [];
    const removeStop = opts && opts.removeStopWords === false ? false : true;
    const minLen = (opts && opts.minLength) || 2;
    const tokens = text
      .replace(/\.\w{1,6}(?=\s|\/|$)/g, ' ')
      .replace(/([a-z])([A-Z])/g, '$1 $2')
      .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
      .replace(/[_\-\.\/]/g, ' ')
      .replace(/[^\w\s]/g, ' ')
      .toLowerCase()
      .split(/\s+/)
      .filter((t) => t.length >= minLen);
    if (!removeStop) return [...new Set(tokens)];
    return [...new Set(tokens.filter((t) => !STOP_WORDS.has(t)))];
  }
  module.exports = { tokenize, STOP_WORDS };
};

// ── ./src/retrieval/ranker ──
__factories["./src/retrieval/ranker"] = function(module, exports) {
  'use strict';
  const { loadWeights } = __require('./src/learning/weights');
  const { tokenize, STOP_WORDS } = __require('./src/retrieval/tokenizer');
  const DEFAULT_WEIGHTS = {
    exactToken: 1.0, symbolMatch: 0.5, prefixMatch: 0.3, pathMatch: 0.8, recencyBoost: 1.5,
  };
  function scoreFile(filePath, sigs, queryTokens, weights) {
    if (!sigs || sigs.length === 0) return 0;
    const w = weights || DEFAULT_WEIGHTS;
    const sigTokenSet = new Set(tokenize(sigs.join(' ')));
    const pathTokenSet = new Set(tokenize(filePath));
    let score = 0;
    for (const qt of queryTokens) {
      if (STOP_WORDS.has(qt)) continue;
      if (sigTokenSet.has(qt)) {
        score += w.exactToken;
        if (sigs.some((sig) => tokenize(sig.replace(/[^a-zA-Z0-9_\s]/g, ' ')).includes(qt))) score += w.symbolMatch;
      }
      if (qt.length >= 4) {
        for (const st of sigTokenSet) {
          if (st !== qt && st.startsWith(qt)) { score += w.prefixMatch; break; }
        }
      }
      if (pathTokenSet.has(qt)) score += w.pathMatch;
    }
    return score;
  }
  function rank(query, sigIndex, opts) {
    if (!query || typeof query !== 'string') return [];
    if (!sigIndex || !(sigIndex instanceof Map) || sigIndex.size === 0) return [];
    const topK = (opts && opts.topK) || 10;
    const recencyMultiplier = (opts && opts.recencyBoost) || DEFAULT_WEIGHTS.recencyBoost;
    const recencySet = (opts && opts.recencySet) || null;
    const weights = (opts && opts.weights) ? Object.assign({}, DEFAULT_WEIGHTS, opts.weights) : DEFAULT_WEIGHTS;
    const learnedWeights = opts && opts.cwd ? loadWeights(opts.cwd) : null;
    const queryTokens = tokenize(query);
    if (queryTokens.length === 0) {
      const all = [];
      for (const [file, sigs] of sigIndex.entries()) all.push({ file, score: sigs.length, sigs, tokens: Math.ceil(sigs.join('\n').length / 4) });
      all.sort((a, b) => b.score - a.score || a.file.localeCompare(b.file));
      return all.slice(0, topK);
    }
    const scored = [];
    for (const [file, sigs] of sigIndex.entries()) {
      let score = scoreFile(file, sigs, queryTokens, weights);
      if (recencySet && recencySet.has(file) && score > 0) score *= recencyMultiplier;
      if (learnedWeights && score > 0) score *= learnedWeights[file] || 1.0;
      scored.push({ file, score, sigs, tokens: Math.ceil(sigs.join('\n').length / 4) });
    }
    // Compute confidence levels based on score distribution
    if (scored.length > 0) {
      const scores = scored.map(s => s.score);
      const maxScore = Math.max(...scores);
      const minScore = Math.min(...scores);
      const scoreRange = maxScore - minScore || 1;
      for (const entry of scored) {
        if (entry.score <= 0) {
          entry.confidence = 'low';
        } else {
          const normalized = (entry.score - minScore) / scoreRange;
          entry.confidence = normalized > 0.66 ? 'high' : normalized > 0.33 ? 'medium' : 'low';
        }
      }
    }
    scored.sort((a, b) => b.score - a.score || a.file.localeCompare(b.file));
    return scored.slice(0, topK);
  }
  const ADAPTER_OUTPUT_PATHS = [
    ['.github', 'copilot-instructions.md'],
    ['CLAUDE.md'],
    ['AGENTS.md'],
    ['.cursorrules'],
    ['.windsurfrules'],
    ['.github', 'openai-context.md'],
    ['.github', 'gemini-context.md'],
    ['llm-full.txt'],
    ['llm.txt'],
  ];
  function _parseContextFile(contextPath) {
    const fs = require('fs');
    const index = new Map();
    if (!fs.existsSync(contextPath)) return index;
    let content = fs.readFileSync(contextPath, 'utf8');
    const markerIdx = content.indexOf('## Auto-generated signatures');
    if (markerIdx !== -1) content = content.slice(markerIdx);
    const lines = content.split('\n');
    let currentFile = null; let inBlock = false; let sigs = [];
    for (const line of lines) {
      const hm = line.match(/^###\s+(\S+)\s*$/);
      if (hm) { if (currentFile !== null) index.set(currentFile, sigs); currentFile = hm[1]; sigs = []; inBlock = false; continue; }
      if (line.startsWith('```')) { inBlock = !inBlock; continue; }
      if (inBlock && currentFile && line.trim()) sigs.push(line.trim());
    }
    if (currentFile !== null) index.set(currentFile, sigs);
    return index;
  }
  function buildSigIndex(cwd, opts) {
    const fs = require('fs'); const path = require('path');
    if (opts && opts.contextPath) return _parseContextFile(opts.contextPath);
    // Check gen-context.config.json for a persisted customOutput path.
    try {
      const cfgPath = path.join(cwd, 'gen-context.config.json');
      if (fs.existsSync(cfgPath)) {
        const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
        if (cfg.customOutput) {
          const idx = _parseContextFile(path.resolve(cwd, cfg.customOutput));
          if (idx.size > 0) return idx;
        }
      }
    } catch (_) {}
    for (const parts of ADAPTER_OUTPUT_PATHS) {
      const contextPath = path.join(cwd, ...parts);
      const index = _parseContextFile(contextPath);
      if (index.size > 0) return index;
    }
    return new Map();
  }
  function formatRankTable(results, query) {
    if (!results || results.length === 0) return `No matching files found for query: "${query}"\n`;
    const lines = [`## Query: ${query}`, '', '| Rank | File | Score | Sigs | Tokens |', '|------|------|-------|------|--------|',
      ...results.map((r, i) => `| ${i + 1} | ${r.file} | ${r.score.toFixed(2)} | ${r.sigs.length} | ${r.tokens} |`), ''];
    for (const r of results.slice(0, 3)) {
      if (r.sigs.length > 0) {
        lines.push(`### ${r.file}`, '```', ...r.sigs.slice(0, 10));
        if (r.sigs.length > 10) lines.push(`... (${r.sigs.length - 10} more)`);
        lines.push('```', '');
      }
    }
    return lines.join('\n');
  }
  function formatRankJSON(results, query) {
    return { query, results: (results || []).map((r, i) => ({ rank: i + 1, file: r.file, score: r.score, sigs: r.sigs, tokens: r.tokens })), totalResults: (results || []).length };
  }
  const INTENT_PATTERNS = {
    debug:    /\b(bug|fix|error|crash|exception|broken|failing|issue|problem|regression)\b/i,
    explain:  /\b(explain|how does|what is|understand|overview|architecture|describe|walk me)\b/i,
    refactor: /\b(refactor|restructure|redesign|clean up|extract|move|rename|simplify)\b/i,
    review:   /\b(review|check|audit|security|pr|pull request|assess)\b/i,
  };
  function detectIntent(query) {
    if (!query || typeof query !== 'string') return 'search';
    for (const [intent, re] of Object.entries(INTENT_PATTERNS)) {
      if (re.test(query)) return intent;
    }
    return 'search';
  }
  module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, detectIntent };
};

// ── ./src/eval/scorer ──
__factories["./src/eval/scorer"] = function(module, exports) {
  'use strict';
  
  function firstRank(ranked, expected) {
    if (!Array.isArray(ranked) || !Array.isArray(expected)) return Infinity;
    const expSet = new Set(expected.map((f) => normalizePath(f)));
    for (let i = 0; i < ranked.length; i++) {
      if (expSet.has(normalizePath(ranked[i]))) return i + 1;
    }
    return Infinity;
  }
  
  function normalizePath(p) {
    return String(p).replace(/^\.\//, '').replace(/\\/g, '/');
  }
  
  function hitAtK(ranked, expected, k = 5) {
    return firstRank(ranked, expected) <= k ? 1 : 0;
  }
  
  function reciprocalRank(ranked, expected) {
    const rank = firstRank(ranked, expected);
    return rank === Infinity ? 0 : 1 / rank;
  }
  
  function precisionAtK(ranked, expected, k = 5) {
    if (!ranked || ranked.length === 0) return 0;
    const expSet = new Set(expected.map((f) => normalizePath(f)));
    const topK = ranked.slice(0, k);
    const hits = topK.filter((f) => expSet.has(normalizePath(f))).length;
    return hits / topK.length;
  }
  
  function aggregate(results, k = 5) {
    if (!Array.isArray(results) || results.length === 0) {
      return { hitAt5: 0, mrr: 0, precisionAt5: 0, avgTokens: 0, tasks: 0 };
    }
    let totalHit = 0, totalRR = 0, totalPrec = 0, totalTokens = 0;
    for (const r of results) {
      const ranked = r.ranked || [];
      const expected = r.expected || [];
      totalHit += hitAtK(ranked, expected, k);
      totalRR += reciprocalRank(ranked, expected);
      totalPrec += precisionAtK(ranked, expected, k);
      totalTokens += (typeof r.tokens === 'number' ? r.tokens : 0);
    }
    const n = results.length;
    return {
      hitAt5: Math.round(totalHit / n * 1000) / 1000,
      mrr: Math.round(totalRR / n * 1000) / 1000,
      precisionAt5: Math.round(totalPrec / n * 1000) / 1000,
      avgTokens: Math.round(totalTokens / n),
      tasks: n,
    };
  }
  
  module.exports = { hitAtK, reciprocalRank, precisionAtK, aggregate, firstRank };
};

// ── ./src/eval/analyzer ──
__factories["./src/eval/analyzer"] = function(module, exports) {
  'use strict';

  const fs   = require('fs');
  const path = require('path');

  const EXT_MAP = {
    '.ts': 'typescript', '.tsx': 'typescript_react',
    '.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
    '.py': 'python',     '.pyw': 'python',
    '.java': 'java',
    '.kt': 'kotlin',     '.kts': 'kotlin',
    '.go': 'go',
    '.rs': 'rust',
    '.cs': 'csharp',
    '.cpp': 'cpp', '.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.cc': 'cpp',
    '.rb': 'ruby',       '.rake': 'ruby',
    '.php': 'php',
    '.swift': 'swift',
    '.dart': 'dart',
    '.scala': 'scala',   '.sc': 'scala',
    '.r': 'r',           '.R': 'r',
    '.vue': 'vue_sfc',
    '.svelte': 'svelte',
    '.html': 'html',     '.htm': 'html',
    '.css': 'css',       '.scss': 'css', '.sass': 'css', '.less': 'css',
    '.yml': 'yaml',      '.yaml': 'yaml',
    '.sh': 'shell',      '.bash': 'shell', '.zsh': 'shell', '.fish': 'shell',
    // P1 languages
    '.sql': 'sql',
    '.graphql': 'graphql', '.gql': 'graphql',
    '.tf': 'terraform', '.tfvars': 'terraform',
    '.proto': 'protobuf',
    // Phase A formats
    '.toml': 'toml',
    '.properties': 'properties',
    '.xml': 'xml',
    '.md': 'markdown',
  };

  function isDockerfile(name) { return name === 'Dockerfile' || name.startsWith('Dockerfile.'); }

  function getExtractorName(filePath) {
    const base = path.basename(filePath);
    const ext  = path.extname(base).toLowerCase();
    if (EXT_MAP[ext]) return EXT_MAP[ext];
    if (isDockerfile(base)) return 'dockerfile';
    return null;
  }

  function tokenCount(sigs) {
    return Math.ceil(sigs.reduce((sum, s) => sum + s.length, 0) / 4);
  }

  function hasCoverage(filePath, cwd) {
    const base = path.basename(filePath, path.extname(filePath));
    const testDirs = ['test', 'tests', '__tests__', 'spec'];
    for (const d of testDirs) {
      const abs = path.join(cwd, d);
      if (!fs.existsSync(abs)) continue;
      let entries;
      try { entries = fs.readdirSync(abs, { withFileTypes: true }); } catch (_) { continue; }
      for (const e of entries) { if (e.name.includes(base)) return true; }
    }
    return false;
  }

  function analyzeFiles(files, cwd, opts) {
    const slow    = (opts && opts.slow)    || false;
    const slowMs  = (opts && opts.slowMs)  || 50;
    const maxSigs = (opts && opts.maxSigs) || 25;
    const stats   = [];
    const cache   = {};

    for (const filePath of files) {
      const extractorName = getExtractorName(filePath);
      if (!extractorName) continue;
      if (!cache[extractorName]) {
        try { cache[extractorName] = __require(`./src/extractors/${extractorName}`); } catch (_) { cache[extractorName] = null; }
      }
      const extractor = cache[extractorName];
      if (!extractor || typeof extractor.extract !== 'function') continue;

      let content;
      try { content = fs.readFileSync(filePath, 'utf8'); } catch (_) { continue; }

      let sigs; let elapsedMs = 0;
      if (slow) {
        const t0 = Date.now();
        try { sigs = extractor.extract(content); } catch (_) { sigs = []; }
        elapsedMs = Date.now() - t0;
      } else {
        try { sigs = extractor.extract(content); } catch (_) { sigs = []; }
      }
      sigs = (Array.isArray(sigs) ? sigs : []).slice(0, maxSigs);

      stats.push({
        file:      path.relative(cwd, filePath),
        extractor: extractorName,
        sigs:      sigs.length,
        tokens:    tokenCount(sigs),
        covered:   hasCoverage(filePath, cwd),
        elapsedMs: slow ? elapsedMs : undefined,
        slow:      slow ? (elapsedMs > slowMs) : undefined,
      });
    }
    return stats;
  }

  function formatAnalysisTable(stats, showSlow) {
    if (!stats || stats.length === 0) return '_(no files analyzed)_\n';
    const maxFile = Math.max(4, ...stats.map((s) => s.file.length));
    const header  = showSlow
      ? `| ${'File'.padEnd(maxFile)} | Sigs | Tokens | Extractor   | Coverage   | Elapsed  |`
      : `| ${'File'.padEnd(maxFile)} | Sigs | Tokens | Extractor   | Coverage   |`;
    const sep = showSlow
      ? `|${'-'.repeat(maxFile + 2)}|------|--------|-------------|------------|----------|`
      : `|${'-'.repeat(maxFile + 2)}|------|--------|-------------|------------|`;
    const rows = stats.map((s) => {
      const cov  = s.covered ? '✓ tested  ' : '✗ untested';
      const file = s.file.padEnd(maxFile);
      const ext  = (s.extractor || '').padEnd(11);
      const base = `| ${file} | ${String(s.sigs).padStart(4)} | ${String(s.tokens).padStart(6)} | ${ext} | ${cov} |`;
      if (showSlow) {
        const ms   = s.elapsedMs !== undefined ? `${s.elapsedMs}ms` : '';
        return `${base} ${ms.padStart(6)}${s.slow ? ' ⚠️' : ''} |`;
      }
      return base;
    });
    const totalSigs   = stats.reduce((n, s) => n + s.sigs,   0);
    const totalTokens = stats.reduce((n, s) => n + s.tokens, 0);
    const slotFile    = ''.padEnd(maxFile);
    const baseFoot    = `| ${slotFile} | ${String(totalSigs).padStart(4)} | ${String(totalTokens).padStart(6)} | **Total**   |            |`;
    const footer      = showSlow ? `${baseFoot} ${' '.padStart(8)} |` : baseFoot;
    return [header, sep, ...rows, sep, footer].join('\n') + '\n';
  }

  function formatAnalysisJSON(stats) {
    const totalSigs   = stats.reduce((n, s) => n + s.sigs,   0);
    const totalTokens = stats.reduce((n, s) => n + s.tokens, 0);
    const slowFiles   = stats.filter((s) => s.slow).map((s) => ({ file: s.file, elapsedMs: s.elapsedMs }));
    return { files: stats, totalSigs, totalTokens, slowFiles, fileCount: stats.length };
  }

  module.exports = { analyzeFiles, formatAnalysisTable, formatAnalysisJSON };
};

// ── ./src/eval/runner ──
__factories["./src/eval/runner"] = function(module, exports) {
  'use strict';
  
  const fs = require('fs');
  const path = require('path');
  const { hitAtK, reciprocalRank, precisionAtK, aggregate } = __require('./src/eval/scorer');
  
  function buildSigIndex(cwd) {
    const contextPath = path.join(cwd, '.github', 'copilot-instructions.md');
    const index = new Map();
    if (!fs.existsSync(contextPath)) return index;
    const content = fs.readFileSync(contextPath, 'utf8');
    const lines = content.split('\n');
    let currentFile = null;
    let inBlock = false;
    let sigs = [];
    for (const line of lines) {
      const headerMatch = line.match(/^###\s+(\S+\.\w+)\s*$/);
      if (headerMatch) {
        if (currentFile !== null) index.set(currentFile, sigs);
        currentFile = headerMatch[1];
        sigs = [];
        inBlock = false;
        continue;
      }
      if (line.startsWith('```')) { inBlock = !inBlock; continue; }
      if (inBlock && currentFile && line.trim()) sigs.push(line.trim());
    }
    if (currentFile !== null) index.set(currentFile, sigs);
    return index;
  }
  
  function tokenize(text) {
    if (!text) return [];
    return text
      .replace(/([a-z])([A-Z])/g, '$1 $2')
      .replace(/[_\-]/g, ' ')
      .replace(/[^\w\s]/g, ' ')
      .toLowerCase()
      .split(/\s+/)
      .filter((t) => t.length > 1);
  }
  
  const STOP_WORDS = new Set([
    'the', 'a', 'an', 'in', 'of', 'to', 'for', 'and', 'or', 'is', 'are',
    'that', 'this', 'it', 'with', 'from', 'by', 'be', 'as', 'on', 'at',
  ]);
  
  function scoreFile(sigs, queryTokens) {
    if (!sigs || sigs.length === 0) return 0;
    const sigText = sigs.join(' ');
    const sigTokens = new Set(tokenize(sigText));
    let score = 0;
    for (const qt of queryTokens) {
      if (STOP_WORDS.has(qt)) continue;
      if (sigTokens.has(qt)) score += 1;
      for (const st of sigTokens) {
        if (st !== qt && st.startsWith(qt) && qt.length >= 4) score += 0.3;
      }
    }
    return score;
  }
  
  function rank(query, index, topK = 10) {
    const queryTokens = tokenize(query);
    const scored = [];
    for (const [file, sigs] of index.entries()) {
      scored.push({ file, score: scoreFile(sigs, queryTokens), sigs });
    }
    scored.sort((a, b) => b.score - a.score || a.file.localeCompare(b.file));
    return scored.slice(0, topK);
  }
  
  function estimateTokens(sigs) {
    return Math.ceil((sigs || []).join('\n').length / 4);
  }
  
  function loadTasks(tasksFile) {
    if (!fs.existsSync(tasksFile)) return [];
    const lines = fs.readFileSync(tasksFile, 'utf8').split('\n');
    const tasks = [];
    for (const line of lines) {
      const trimmed = line.trim();
      if (!trimmed) continue;
      try {
        const obj = JSON.parse(trimmed);
        if (obj.query && Array.isArray(obj.expected_files)) {
          tasks.push({
            id: obj.id || String(tasks.length + 1),
            query: obj.query,
            expected: obj.expected_files,
            repo: obj.repo || '.',
          });
        }
      } catch (_) {}
    }
    return tasks;
  }
  
  function run(tasksFile, cwd, opts = {}) {
    const topK = opts.topK || 10;
    const resolvedTasksFile = path.isAbsolute(tasksFile)
      ? tasksFile
      : path.resolve(cwd, tasksFile);
    const tasks = loadTasks(resolvedTasksFile);
    if (tasks.length === 0) {
      return { tasks: [], metrics: { hitAt5: 0, mrr: 0, precisionAt5: 0, avgTokens: 0, tasks: 0 } };
    }
    const index = buildSigIndex(cwd);
    const taskResults = [];
    for (const task of tasks) {
      const ranked = rank(task.query, index, topK).map((r) => r.file);
      const topResults = rank(task.query, index, topK);
      const tokens = topResults.reduce((sum, r) => sum + estimateTokens(r.sigs), 0);
      taskResults.push({
        id: task.id,
        query: task.query,
        expected: task.expected,
        ranked,
        hit5: hitAtK(ranked, task.expected, 5),
        rr: reciprocalRank(ranked, task.expected),
        precAt5: precisionAtK(ranked, task.expected, 5),
        tokens,
      });
    }
    const metrics = aggregate(
      taskResults.map((r) => ({ ranked: r.ranked, expected: r.expected, tokens: r.tokens })),
    );
    return { tasks: taskResults, metrics };
  }
  
  function formatTable(taskResults) {
    const header = '| Task | Query | hit@5 | RR | Tokens |';
    const divider = '|---|---|:---:|:---:|---:|';
    const rows = taskResults.map((r) => {
      const q = r.query.length > 40 ? r.query.slice(0, 37) + '...' : r.query;
      return `| ${r.id} | ${q} | ${r.hit5 ? '✓' : '✗'} | ${r.rr.toFixed(2)} | ${r.tokens} |`;
    });
    return [header, divider, ...rows].join('\n');
  }
  
  function formatMetrics(metrics) {
    return [
      `[sigmap] benchmark results:`,
      `  tasks       : ${metrics.tasks}`,
      `  hit@5       : ${(metrics.hitAt5 * 100).toFixed(1)}%`,
      `  MRR         : ${metrics.mrr.toFixed(3)}`,
      `  precision@5 : ${(metrics.precisionAt5 * 100).toFixed(1)}%`,
      `  avg tokens  : ${metrics.avgTokens}`,
    ].join('\n');
  }
  
  module.exports = { run, rank, loadTasks, buildSigIndex, formatTable, formatMetrics, tokenize };
};

// ── ./src/eval/usefulness-scorer ──
__factories["./src/eval/usefulness-scorer"] = function(module, exports) {
  'use strict';

  /**
   * Score answer usefulness based on:
   * 1. Whether right file was retrieved (retrieval hit)
   * 2. Whether retrieved context covered the answer (coverage)
   * 3. Confidence in answer quality (from ranking score)
   */
  function scoreUsefulness(taskResult, rankingScore) {
    const { hitRank } = taskResult;

    // Tier 1: File not retrieved — context cannot be useful
    if (hitRank === -1 || hitRank > 5) {
      return {
        tier: 'not-useful',
        score: 0.0,
        reason: 'expected file not in top 5'
      };
    }

    // Tier 2: File retrieved but not top ranking — partially useful
    if (hitRank > 1) {
      return {
        tier: 'partially-useful',
        score: rankingScore * 0.5,  // Partial usefulness
        reason: `file ranked #${hitRank}`
      };
    }

    // Tier 3: File at top of ranking — fully useful
    return {
      tier: 'fully-useful',
      score: rankingScore,  // Full usefulness
      reason: 'file ranked first'
    };
  }

  function computeUsefulnessStats(taskResults) {
    const tiers = {
      'fully-useful': 0,
      'partially-useful': 0,
      'not-useful': 0
    };

    let totalScore = 0;
    let count = 0;

    taskResults.forEach(result => {
      const usefulness = scoreUsefulness(result, result.rankingScore || 1.0);
      tiers[usefulness.tier]++;
      totalScore += usefulness.score;
      count++;
    });

    return {
      fully_useful: tiers['fully-useful'],
      partially_useful: tiers['partially-useful'],
      not_useful: tiers['not-useful'],
      fully_useful_pct: count > 0 ? (tiers['fully-useful'] / count * 100).toFixed(1) : 0,
      partially_useful_pct: count > 0 ? (tiers['partially-useful'] / count * 100).toFixed(1) : 0,
      not_useful_pct: count > 0 ? (tiers['not-useful'] / count * 100).toFixed(1) : 0,
      average_usefulness_score: count > 0 ? (totalScore / count).toFixed(3) : 0
    };
  }

  module.exports = { scoreUsefulness, computeUsefulnessStats };
};


// ── ./packages/adapters/copilot (bundled) ──
__factories["./packages/adapters/copilot"] = function(module, exports) {
  const path = require('path');
  const fs = require('fs');
  const name = 'copilot';
  const COPILOT_MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
  function format(context, opts = {}) {
    if (!context || typeof context !== 'string') return '';
    const version = (opts && opts.version) || 'unknown';
    const timestamp = new Date().toISOString();
    return [
      `<!-- Generated by SigMap gen-context.js v${version} -->`,
      `<!-- Updated: ${timestamp} -->`,
      `<!-- Do not edit below — regenerate with: node gen-context.js -->`,
      '',
      '## SigMap commands',
      '',
      '| When | Command |',
      '|------|---------|',
      '| Before answering a question about code | `sigmap ask "<your question>"` |',
      '| After changing config or source dirs | `sigmap validate` |',
      '| To verify an AI answer is grounded | `sigmap judge --response <file>` |',
      '',
      '# Code signatures',
      '',
      context,
    ].join('\n');
  }
  function outputPath(cwd) { return path.join(cwd, '.github', 'copilot-instructions.md'); }
  function write(context, cwd, opts = {}) {
    const filePath = outputPath(cwd);
    let existing = '';
    if (fs.existsSync(filePath)) existing = fs.readFileSync(filePath, 'utf8');
    const formatted = format(context, opts);
    const markerIdx = existing.indexOf('## Auto-generated signatures');
    const isLegacyGenerated = existing.includes('<!-- Generated by SigMap gen-context.js') || existing.includes('# Code signatures');
    const newContent = markerIdx !== -1
      ? existing.slice(0, markerIdx) + COPILOT_MARKER.trimStart() + formatted
      : (isLegacyGenerated ? COPILOT_MARKER.trimStart() + formatted : existing + COPILOT_MARKER + formatted);
    fs.mkdirSync(path.dirname(filePath), { recursive: true });
    fs.writeFileSync(filePath, newContent, 'utf8');
  }
  module.exports = { name, format, outputPath, write };
};

// ── ./packages/adapters/claude (bundled) ──
__factories["./packages/adapters/claude"] = function(module, exports) {
  const path = require('path');
  const fs = require('fs');
  const name = 'claude';
  const CLAUDE_MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
  const CLAUDE_ALLOWLIST_MARKER = '<!-- sigmap-bash-allowlist -->';
  const CLAUDE_ALLOWLIST_BLOCK = [
    '## Bash allowlist',
    '',
    CLAUDE_ALLOWLIST_MARKER,
    '',
    'The following sigmap commands are pre-approved — Claude Code will not prompt for confirmation:',
    '',
    '```json',
    JSON.stringify({
      permissions: {
        allow: [
          'Bash(sigmap ask*)',
          'Bash(sigmap validate*)',
          'Bash(sigmap judge*)',
          'Bash(sigmap weights*)',
          'Bash(sigmap history*)',
          'Bash(sigmap --query*)',
          'Bash(sigmap --diff*)',
          'Bash(sigmap --health*)',
          'Bash(sigmap --coverage*)',
          'Bash(node gen-context.js*)',
        ],
      },
    }, null, 2),
    '```',
    '',
    'Add the `permissions.allow` array above to `.claude/settings.json` to activate.',
    '',
  ].join('\n');
  function format(context, opts = {}) {
    if (!context || typeof context !== 'string') return '';
    const version = (opts && opts.version) || 'unknown';
    const timestamp = new Date().toISOString();
    return [
      '## SigMap',
      '- Before searching for files, run `sigmap ask "<query>"` — ranks by TF-IDF relevance',
      '- After config changes, run `sigmap validate` to confirm coverage',
      '- To verify answer groundedness, run `sigmap judge --response <file> --context CLAUDE.md`',
      '- For PR-scoped context, run `sigmap --diff`',
      '',
      `<!-- Generated by SigMap v${version} — ${timestamp} -->`,
      '',
      context,
    ].join('\n');
  }
  function outputPath(cwd) { return path.join(cwd, 'CLAUDE.md'); }
  function write(context, cwd, opts = {}) {
    const filePath = outputPath(cwd);
    let existing = '';
    if (fs.existsSync(filePath)) existing = fs.readFileSync(filePath, 'utf8');
    const formatted = format(context, opts);
    const markerIdx = existing.indexOf('## Auto-generated signatures');
    let newContent = markerIdx !== -1
      ? existing.slice(0, markerIdx) + CLAUDE_MARKER.trimStart() + formatted
      : existing + CLAUDE_MARKER + formatted;
    if (!newContent.includes(CLAUDE_ALLOWLIST_MARKER)) {
      const sigPos = newContent.indexOf('## Auto-generated signatures');
      if (sigPos !== -1) {
        newContent = newContent.slice(0, sigPos) + CLAUDE_ALLOWLIST_BLOCK + '\n' + newContent.slice(sigPos);
      } else {
        newContent = CLAUDE_ALLOWLIST_BLOCK + '\n' + newContent;
      }
    }
    fs.writeFileSync(filePath, newContent, 'utf8');
  }
  module.exports = { name, format, outputPath, write };
};

// ── ./packages/adapters/cursor (bundled) ──
__factories["./packages/adapters/cursor"] = function(module, exports) {
  const path = require('path');
  const name = 'cursor';
  function format(context, opts = {}) {
    if (!context || typeof context !== 'string') return '';
    const version = (opts && opts.version) || 'unknown';
    const timestamp = new Date().toISOString();
    return [
      `# Code signatures — generated by SigMap v${version}`,
      `# Updated: ${timestamp}`,
      `# Regenerate: node gen-context.js`,
      '#',
      '# SigMap: before answering, run: sigmap ask "<query>" — finds relevant files',
      '# SigMap: after config changes, run: sigmap validate',
      '# SigMap: to verify answers, run: sigmap judge --response <file>',
      '',
      context,
    ].join('\n');
  }
  function outputPath(cwd) { return path.join(cwd, '.cursorrules'); }
  module.exports = { name, format, outputPath };
};

// ── ./packages/adapters/windsurf (bundled) ──
__factories["./packages/adapters/windsurf"] = function(module, exports) {
  const path = require('path');
  const name = 'windsurf';
  function format(context, opts = {}) {
    if (!context || typeof context !== 'string') return '';
    const version = (opts && opts.version) || 'unknown';
    const timestamp = new Date().toISOString();
    return [
      `# Code signatures — generated by SigMap v${version}`,
      `# Updated: ${timestamp}`,
      `# Regenerate: node gen-context.js`,
      '#',
      '# SigMap: before answering, run: sigmap ask "<query>" — finds relevant files',
      '# SigMap: after config changes, run: sigmap validate',
      '# SigMap: to verify answers, run: sigmap judge --response <file>',
      '',
      context,
    ].join('\n');
  }
  function outputPath(cwd) { return path.join(cwd, '.windsurfrules'); }
  module.exports = { name, format, outputPath };
};

// ── ./packages/adapters/openai (bundled) ──
__factories["./packages/adapters/openai"] = function(module, exports) {
  const path = require('path');
  const name = 'openai';
  function format(context, opts = {}) {
    if (!context || typeof context !== 'string') return '';
    const version = (opts && opts.version) || 'unknown';
    const timestamp = new Date().toISOString();
    const projectLine = (opts && opts.projectName) ? `Project: ${opts.projectName}\n` : '';
    return [
      `You are a coding assistant with full knowledge of this codebase.`,
      `Below are the code signatures extracted by SigMap v${version} on ${timestamp}.`,
      projectLine,
      `Use these signatures to answer questions about the code accurately.`,
      `When the user asks about a specific file or function, refer to the signatures below.`,
      `Before answering questions about specific code areas, prefer running \`sigmap ask "<query>"\` to get the most relevant files. After config changes, \`sigmap validate\` confirms coverage.`,
      ``,
      `## Code Signatures`,
      ``,
      context,
    ].join('\n');
  }
  function outputPath(cwd) { return path.join(cwd, '.github', 'openai-context.md'); }
  module.exports = { name, format, outputPath };
};

// ── ./packages/adapters/gemini (bundled) ──
__factories["./packages/adapters/gemini"] = function(module, exports) {
  const path = require('path');
  const fs = require('fs');
  const name = 'gemini';
  const GEMINI_MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
  function format(context, opts = {}) {
    if (!context || typeof context !== 'string') return '';
    const version = (opts && opts.version) || 'unknown';
    const timestamp = new Date().toISOString();
    const projectLine = (opts && opts.projectName) ? `Project: ${opts.projectName}\n` : '';
    return [
      `You are a coding assistant with complete knowledge of this codebase.`,
      `The following code signatures were extracted by SigMap v${version} on ${timestamp}.`,
      projectLine,
      `These signatures represent every public function, class, and type in the project.`,
      `Refer to them when answering questions about code structure, APIs, and implementation.`,
      `Before answering questions about specific code areas, suggest running \`sigmap ask "<query>"\` to get the most relevant files. After config changes, \`sigmap validate\` confirms coverage.`,
      ``,
      `## Code Signatures`,
      ``,
      context,
    ].join('\n');
  }
  function outputPath(cwd) { return path.join(cwd, '.github', 'gemini-context.md'); }
  function write(context, cwd, opts = {}) {
    const filePath = outputPath(cwd);
    let existing = '';
    if (fs.existsSync(filePath)) existing = fs.readFileSync(filePath, 'utf8');
    const formatted = format(context, opts);
    const markerIdx = existing.indexOf('## Auto-generated signatures');
    const isLegacyGenerated = existing.includes('<!-- Generated by SigMap gen-context.js') || existing.includes('## Code Signatures');
    const newContent = markerIdx !== -1
      ? existing.slice(0, markerIdx) + GEMINI_MARKER.trimStart() + formatted
      : (isLegacyGenerated ? GEMINI_MARKER.trimStart() + formatted : existing + GEMINI_MARKER + formatted);
    fs.mkdirSync(path.dirname(filePath), { recursive: true });
    fs.writeFileSync(filePath, newContent, 'utf8');
  }
  module.exports = { name, format, outputPath, write };
};

// ── ./packages/adapters/codex (bundled) ──
__factories["./packages/adapters/codex"] = function(module, exports) {
  const path = require('path');
  const fs = require('fs');
  const name = 'codex';
  const CODEX_MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
  const CODEX_TOOLS_MARKER = '<!-- sigmap-tools -->';
  const CODEX_TOOLS_BLOCK = [
    '## Tools',
    '',
    CODEX_TOOLS_MARKER,
    '',
    '```json',
    JSON.stringify([
      { name: 'sigmap_ask', description: 'Rank source files by relevance to a natural-language query. Run before exploring the codebase.', command: 'sigmap ask "$QUERY"' },
      { name: 'sigmap_validate', description: 'Validate SigMap config and measure context coverage. Run after changing config or source dirs.', command: 'sigmap validate' },
      { name: 'sigmap_judge', description: 'Score an LLM response for groundedness against source context. Use to verify answer quality.', command: 'sigmap judge --response "$RESPONSE" --context "$CONTEXT"' },
      { name: 'sigmap_query', description: 'Rank all files by relevance using TF-IDF and write a focused mini-context.', command: 'sigmap --query "$QUERY" --context' },
      { name: 'sigmap_weights', description: 'Show learned file-ranking multipliers accumulated from past sessions.', command: 'sigmap weights' },
    ], null, 2),
    '```',
    '',
  ].join('\n');
  function format(context, opts = {}) {
    if (!context || typeof context !== 'string' || !context.trim()) return '';
    const toolBlock = [
      '## SigMap commands',
      '',
      '| When | Command |',
      '|------|---------|',
      '| Before answering a question | `sigmap ask "<your question>"` |',
      '| After code changes | `sigmap validate` |',
      '| To query by topic | `sigmap --query "<topic>"` |',
      '',
      'Always run `sigmap ask` or `sigmap --query` before searching for files relevant to a task.',
      '',
    ].join('\n');
    return '# Code signatures\n\n' + toolBlock + context;
  }
  function outputPath(cwd) { return path.join(cwd, 'AGENTS.md'); }
  function write(context, cwd, opts = {}) {
    const filePath = outputPath(cwd);
    let existing = '';
    if (fs.existsSync(filePath)) existing = fs.readFileSync(filePath, 'utf8');
    const formatted = format(context, opts);
    const markerIdx = existing.indexOf('## Auto-generated signatures');
    const isLegacyGenerated = existing.includes('<!-- Generated by SigMap gen-context.js') || existing.includes('## Code Signatures') || existing.includes('# Code signatures');
    let newContent = markerIdx !== -1
      ? existing.slice(0, markerIdx) + CODEX_MARKER.trimStart() + formatted
      : (isLegacyGenerated ? CODEX_MARKER.trimStart() + formatted : existing + CODEX_MARKER + formatted);
    if (!newContent.includes(CODEX_TOOLS_MARKER)) {
      const sigPos = newContent.indexOf('## Auto-generated signatures');
      if (sigPos !== -1) {
        newContent = newContent.slice(0, sigPos) + CODEX_TOOLS_BLOCK + '\n' + newContent.slice(sigPos);
      } else {
        newContent = CODEX_TOOLS_BLOCK + '\n' + newContent;
      }
    }
    fs.writeFileSync(filePath, newContent, 'utf8');
  }
  module.exports = { name, format, outputPath, write };
};

// ── ./packages/adapters/index (bundled) ──
__factories["./packages/adapters/index"] = function(module, exports) {
  const ADAPTER_NAMES = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex'];
  const _adapterCache = {};
  function getAdapter(name) {
    if (!name || typeof name !== 'string') return null;
    const key = name.toLowerCase();
    if (!ADAPTER_NAMES.includes(key)) return null;
    if (!_adapterCache[key]) {
      try { _adapterCache[key] = __require('./packages/adapters/' + key); } catch (_) { return null; }
    }
    return _adapterCache[key];
  }
  function listAdapters() { return ADAPTER_NAMES.slice(); }
  function adapt(context, adapterName, opts = {}) {
    if (!context || typeof context !== 'string') return '';
    const adapter = getAdapter(adapterName);
    if (!adapter || typeof adapter.format !== 'function') return '';
    try { return adapter.format(context, opts); } catch (_) { return ''; }
  }
  function outputsToAdapters(outputs) {
    if (!Array.isArray(outputs)) return ['copilot'];
    return outputs.map((o) => ADAPTER_NAMES.includes(o) ? o : o);
  }
  module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters };
};

// ── ./src/extractors/generic ──
__factories["./src/extractors/generic"] = function(module, exports) {
  'use strict';
  const PATTERNS = [
    /^(?:pub\s+)?(?:async\s+)?function\s+\w+\s*\(/,
    /^(?:pub\s+)?(?:async\s+)?fn\s+\w+[\s(<]/,
    /^def\s+\w+[\s(|:]/,
    /^(?:pub\s+)?func\s+\w+\s*\(/,
    /^(?:let|let\s+rec)\s+\w+\s*[=(]/,
    /^class\s+\w+/,
    /^(?:proc|sub|method)\s+\w+\s*\(/,
  ];
  function extract(src) {
    if (!src || typeof src !== 'string') return [];
    const results = [];
    for (const raw of src.split('\n')) {
      const line = raw.trim();
      if (!line || /^[#\-]/.test(line) || /^\/\//.test(line) || line.includes('\0')) continue;
      for (const pat of PATTERNS) {
        if (pat.test(line)) { results.push(line.slice(0, 120)); break; }
      }
      if (results.length >= 15) break;
    }
    return results;
  }
  module.exports = { extract };
};

// ── ./src/format/llm-txt ──
__factories["./src/format/llm-txt"] = function(module, exports) {
  'use strict';
  const path = require('path');
  function outputPath(cwd) { return path.join(cwd, 'llm.txt'); }
  function format(context, cwd, version) {
    const name  = context.projectName || path.basename(cwd);
    const langs = [...new Set((context.fileEntries || []).map(f => f.language).filter(Boolean))];
    const mods  = context.srcDirs || [];
    return [
      `# Project: ${name}`,
      `Languages: ${langs.join(', ') || 'unknown'}`,
      `Root: ${mods[0] || 'src/'}`,
      '',
      '## Modules',
      ...mods.map(m => `- ${m}/`),
      '',
      '## Key flows',
      '- <!-- describe your main user flows here -->',
      '',
      '## Rules',
      '- <!-- describe your team conventions here -->',
      '',
      `Generated: ${new Date().toISOString()} | SigMap v${version}`,
    ].join('\n');
  }
  module.exports = { format, outputPath };
};

// ── ./src/format/llms-txt ──
__factories["./src/format/llms-txt"] = function(module, exports) {
  'use strict';
  const path = require('path');
  const fs   = require('fs');
  const { execSync } = require('child_process');
  function outputPath(cwd) { return path.join(cwd, 'llms.txt'); }
  function getShortCommit(cwd) {
    try { return execSync('git rev-parse --short HEAD', { cwd, timeout: 2000 }).toString().trim(); }
    catch (_) { return ''; }
  }
  function detectVersion(cwd) {
    try {
      const pkg = path.join(cwd, 'package.json');
      if (fs.existsSync(pkg)) return JSON.parse(fs.readFileSync(pkg, 'utf8')).version || '';
    } catch (_) {}
    return '';
  }
  function format(context, cwd, writtenFiles, sigmapVersion) {
    writtenFiles  = writtenFiles  || [];
    sigmapVersion = sigmapVersion || '';
    const name   = context.projectName || path.basename(cwd);
    const ver    = detectVersion(cwd);
    const commit = getShortCommit(cwd);
    const langs  = [...new Set((context.fileEntries || []).map(f => f.language).filter(Boolean))];
    const lines = [
      '# SigMap Context Index',
      `> Generated by SigMap v${sigmapVersion} — zero-dependency AI context engine`,
      '',
      '## Project',
      `- Name: ${name}`,
    ];
    if (ver)          lines.push(`- Version: ${ver}`);
    if (langs.length) lines.push(`- Language: ${langs.join(', ')}`);
    if (commit)       lines.push(`- Commit: ${commit}`);
    if (writtenFiles.length) {
      lines.push('', '## Context Files');
      for (const f of writtenFiles) {
        const rel   = path.relative(cwd, f.path);
        const extra = f.tokens ? `: ${f.tokens} tokens` : '';
        lines.push(`- [${f.label || rel}](${rel})${extra}`);
      }
    }
    const mods = context.srcDirs || [];
    if (mods.length) {
      lines.push('', '## Source Modules');
      for (const m of mods) lines.push(`- [${m}/](${m}/)`);
    }
    const top5 = (context.fileEntries || [])
      .sort((a, b) => (b.sigs || []).length - (a.sigs || []).length)
      .slice(0, 5);
    if (top5.length) {
      lines.push('', '## Key Files');
      for (const f of top5) {
        const rel     = path.relative(cwd, f.filePath);
        const preview = (f.sigs || []).slice(0, 3).join(', ');
        lines.push(`- ${rel}: ${preview}`);
      }
    }
    return lines.join('\n');
  }
  module.exports = { format, outputPath };
};

// ── ./packages/adapters/llm-full ──
__factories["./packages/adapters/llm-full"] = function(module, exports) {
  'use strict';
  const path = require('path');
  const fs   = require('fs');
  function outputPath(cwd) { return path.join(cwd, 'llm-full.txt'); }
  function format(context, opts) {
    opts = opts || {};
    const lines = [
      `# ${context.projectName || 'Project'} — SigMap Context`,
      `Generated: ${new Date().toISOString()} | SigMap v${opts.version || ''}`,
      '',
    ];
    for (const entry of (context.fileEntries || [])) {
      const rel = path.relative(opts.cwd || '', entry.filePath);
      lines.push(`## ${rel}`, '```', ...(entry.sigs || []), '```', '');
    }
    return lines.join('\n');
  }
  function write(context, cwd, opts) {
    opts = opts || {};
    fs.writeFileSync(outputPath(cwd), format(context, { ...opts, cwd }));
  }
  module.exports = { name: 'llm-full', format, outputPath, write };
};

// ── ./src/discovery/source-root-registry ──
__factories["./src/discovery/source-root-registry"] = function(module, exports) {
  'use strict';
  const REGISTRY = {
    javascript: {
      manifestFiles: ['package.json'],
      frameworks: {
        nextjs:    { detectionFiles: ['next.config.js','next.config.ts','next.config.mjs'], detectionDeps: ['next'],               srcDirs: ['app','src/app','pages','src/pages','src','components','lib','hooks','utils'], entrypoints: ['app/page.tsx','pages/index.tsx'] },
        nestjs:    { detectionFiles: ['nest-cli.json'],                                    detectionDeps: ['@nestjs/core'],        srcDirs: ['src'], entrypoints: ['src/main.ts','src/app.module.ts'] },
        express:   { detectionFiles: [],                                                    detectionDeps: ['express'],             srcDirs: ['src','routes','middleware','controllers','services'], entrypoints: ['src/index.js','server.js','app.js'] },
        fastify:   { detectionFiles: [],                                                    detectionDeps: ['fastify'],             srcDirs: ['src','routes','plugins'], entrypoints: ['src/index.js'] },
        react:     { detectionFiles: [],                                                    detectionDeps: ['react'],               srcDirs: ['src','components','hooks','context','pages','app','lib','utils'] },
        vue:       { detectionFiles: ['vue.config.js','vue.config.ts'],                    detectionDeps: ['vue'],                 srcDirs: ['src','components','composables','pages','views'] },
        nuxt:      { detectionFiles: ['nuxt.config.js','nuxt.config.ts'],                  detectionDeps: ['nuxt'],                srcDirs: ['pages','components','composables','server','middleware','plugins'] },
        svelte:    { detectionFiles: ['svelte.config.js'],                                 detectionDeps: ['svelte','@sveltejs/kit'], srcDirs: ['src','src/routes','src/lib'] },
        angular:   { detectionFiles: ['angular.json'],                                     detectionDeps: ['@angular/core'],       srcDirs: ['src','src/app','projects','apps','libs'] },
        gatsby:    { detectionFiles: ['gatsby-config.js','gatsby-config.ts'],              detectionDeps: ['gatsby'],              srcDirs: ['src','gatsby'] },
        vite:      { detectionFiles: ['vite.config.js','vite.config.ts'],                  detectionDeps: ['vite'],                srcDirs: ['src'] },
        remix:     { detectionFiles: ['remix.config.js'],                                  detectionDeps: ['@remix-run/react'],    srcDirs: ['app'] },
        trpc:      { detectionFiles: [],                                                    detectionDeps: ['@trpc/server'],        srcDirs: ['src','server','routers'] },
      },
      srcDirs:  ['src','lib','index.js','server.js','app.js'],
      penalties: ['dist','build','.next','.nuxt','coverage','storybook-static'],
    },
    typescript: {
      manifestFiles: ['package.json','tsconfig.json'],
      frameworks: {
        nextjs:  { detectionFiles: ['next.config.ts','next.config.mjs'], detectionDeps: ['next'],            srcDirs: ['app','src/app','pages','src','components','lib','hooks','utils'] },
        nestjs:  { detectionFiles: ['nest-cli.json'],                     detectionDeps: ['@nestjs/core'],   srcDirs: ['src'], entrypoints: ['src/main.ts'] },
        angular: { detectionFiles: ['angular.json'],                      detectionDeps: ['@angular/core'],  srcDirs: ['src','src/app','projects','apps','libs'] },
      },
      srcDirs:  ['src','lib','packages'],
      penalties: ['dist','build','.next'],
    },
    python: {
      manifestFiles: ['requirements.txt','pyproject.toml','setup.py','Pipfile'],
      frameworks: {
        django:  { detectionFiles: ['manage.py'],           detectionDeps: ['Django'],  srcDirs: [],         specialRule: 'django-app-dirs', entrypoints: ['manage.py'] },
        fastapi: { detectionFiles: [],                       detectionDeps: ['fastapi'], srcDirs: ['app','src','routers','api'], entrypoints: ['main.py','app/main.py'] },
        flask:   { detectionFiles: ['wsgi.py','app.py'],    detectionDeps: ['Flask'],   srcDirs: ['app','src'], entrypoints: ['app.py','wsgi.py'] },
        celery:  { detectionFiles: [],                       detectionDeps: ['celery'],  srcDirs: ['tasks','workers','app'] },
      },
      srcDirs:  ['.'],
      penalties: ['venv','.venv','__pycache__','.pytest_cache','htmlcov'],
    },
    go: {
      manifestFiles: ['go.mod'],
      frameworks: {
        gin:   { detectionFiles: [], detectionDeps: ['github.com/gin-gonic/gin'],      srcDirs: ['internal','cmd','pkg','api','handler','middleware'] },
        echo:  { detectionFiles: [], detectionDeps: ['github.com/labstack/echo'],      srcDirs: ['internal','cmd','handler','middleware'] },
        fiber: { detectionFiles: [], detectionDeps: ['github.com/gofiber/fiber'],      srcDirs: ['internal','cmd','handler','routes'] },
        grpc:  { detectionFiles: [], detectionDeps: ['google.golang.org/grpc'],        srcDirs: ['internal','proto','server','client'] },
        chi:   { detectionFiles: [], detectionDeps: ['github.com/go-chi/chi'],         srcDirs: ['internal','cmd','handler'] },
      },
      srcDirs:  ['internal','cmd','pkg','api'],
      penalties: ['vendor'],
    },
    rust: {
      manifestFiles: ['Cargo.toml'],
      frameworks: {
        actix: { detectionFiles: [], detectionDeps: ['actix-web'], srcDirs: ['src'] },
        axum:  { detectionFiles: [], detectionDeps: ['axum'],      srcDirs: ['src'] },
        tauri: { detectionFiles: ['src-tauri/tauri.conf.json'], detectionDeps: ['tauri'], srcDirs: ['src','src-tauri/src'] },
      },
      srcDirs:  ['src'],
      penalties: ['target'],
    },
    java: {
      manifestFiles: ['pom.xml','build.gradle'],
      frameworks: {
        spring:   { detectionFiles: [], detectionDeps: ['spring-boot'], srcDirs: ['src/main/java','src/main/kotlin','src/main/resources'] },
        quarkus:  { detectionFiles: [], detectionDeps: ['io.quarkus'],  srcDirs: ['src/main/java'] },
        android:  { detectionFiles: ['AndroidManifest.xml'],            srcDirs: ['app/src/main/java','app/src/main','src'] },
        micronaut:{ detectionFiles: [], detectionDeps: ['io.micronaut'],srcDirs: ['src/main/java'] },
      },
      srcDirs:  ['src/main/java','src'],
      penalties: ['target','build'],
    },
    kotlin: {
      manifestFiles: ['build.gradle.kts'],
      frameworks: {
        spring:  { detectionFiles: [], detectionDeps: ['spring-boot'],    srcDirs: ['src/main/kotlin'] },
        android: { detectionFiles: ['AndroidManifest.xml'],               srcDirs: ['app/src/main/kotlin','app/src/main/java'] },
        ktor:    { detectionFiles: [], detectionDeps: ['io.ktor'],         srcDirs: ['src'] },
        compose: { detectionFiles: [], detectionDeps: ['compose-runtime'], srcDirs: ['app/src/main/kotlin','src'] },
      },
      srcDirs:  ['src/main/kotlin','src'],
      penalties: ['build','.gradle'],
    },
    csharp: {
      manifestFiles: ['.csproj','.sln'],
      frameworks: {
        aspnet:  { detectionFiles: ['appsettings.json'], detectionDeps: ['Microsoft.AspNetCore'], srcDirs: ['Controllers','Services','Models','Middleware','Pages'] },
        blazor:  { detectionFiles: [], detectionDeps: ['Microsoft.AspNetCore.Components'],        srcDirs: ['Components','Pages','Services'] },
        unity:   { detectionFiles: ['ProjectSettings/ProjectSettings.asset'],                     srcDirs: ['Assets/Scripts','Assets'] },
        maui:    { detectionFiles: [], detectionDeps: ['Microsoft.Maui'],                          srcDirs: ['src','Pages','ViewModels'] },
      },
      srcDirs:  ['src','Controllers','Services','Models'],
      penalties: ['bin','obj','.vs'],
    },
    php: {
      manifestFiles: ['composer.json'],
      frameworks: {
        laravel:   { detectionFiles: ['artisan'],           srcDirs: ['app','routes','config','database','resources','tests'], entrypoints: ['artisan'] },
        symfony:   { detectionFiles: ['symfony.lock'],      srcDirs: ['src','config','templates'], specialRule: 'symfony-bundle-dirs' },
        wordpress: { detectionFiles: ['wp-config.php'],     srcDirs: ['wp-content/themes','wp-content/plugins','wp-content/mu-plugins'] },
        slim:      { detectionFiles: [],                    detectionDeps: ['slim/slim'], srcDirs: ['src','app','routes'] },
      },
      srcDirs:  ['src','app'],
      penalties: ['vendor'],
    },
    ruby: {
      manifestFiles: ['Gemfile'],
      frameworks: {
        rails:   { detectionFiles: ['config/routes.rb'],   srcDirs: ['app','lib','config','db','spec','test'], entrypoints: ['config/routes.rb'] },
        sinatra: { detectionFiles: ['config.ru','app.rb'], srcDirs: ['.','lib'],            entrypoints: ['app.rb','config.ru'] },
        hanami:  { detectionFiles: [],                     detectionDeps: ['hanami'],       srcDirs: ['apps','lib','slices'] },
      },
      srcDirs:  ['app','lib'],
      penalties: ['vendor','coverage','.bundle'],
    },
    swift: {
      manifestFiles: ['Package.swift'],
      frameworks: {
        vapor:   { detectionFiles: [],              detectionDeps: ['vapor/vapor'], srcDirs: ['Sources','App'] },
        swiftui: { detectionFiles: ['.xcodeproj'],  srcDirs: [],                    specialRule: 'swift-project-dir' },
        swiftpm: { detectionFiles: ['Package.swift'],srcDirs: ['Sources'] },
      },
      srcDirs:  ['Sources','Source'],
      penalties: ['.build','DerivedData','Pods','Carthage'],
    },
    dart: {
      manifestFiles: ['pubspec.yaml'],
      frameworks: {
        flutter:    { detectionFiles: [],                   detectionDeps: ['flutter'],    srcDirs: ['lib','lib/src'], entrypoints: ['lib/main.dart'] },
        serverpod:  { detectionFiles: [],                   detectionDeps: ['serverpod'],  srcDirs: ['lib','endpoints','models'] },
        'dart-frog':{ detectionFiles: ['dart_frog.yaml'],   srcDirs: ['routes','lib'] },
      },
      srcDirs:  ['lib','lib/src'],
      penalties: ['.dart_tool','build'],
    },
    scala: {
      manifestFiles: ['build.sbt'],
      frameworks: {
        akka:  { detectionFiles: [], detectionDeps: ['akka'], srcDirs: ['src/main/scala','src'] },
        play:  { detectionFiles: [], detectionDeps: ['play'], srcDirs: ['app','conf'] },
        spark: { detectionFiles: [], detectionDeps: ['spark'],srcDirs: ['src/main/scala'] },
        zio:   { detectionFiles: [], detectionDeps: ['zio'],  srcDirs: ['src/main/scala'] },
      },
      srcDirs:  ['src/main/scala','src'],
      penalties: ['target'],
    },
  };
  module.exports = { REGISTRY };
};

// ── ./src/discovery/sigmapignore ──
__factories["./src/discovery/sigmapignore"] = function(module, exports) {
  'use strict';
  const fs   = require('fs');
  const path = require('path');
  function loadIgnorePatterns(cwd) {
    for (const fname of ['.sigmapignore', '.contextignore']) {
      const p = path.join(cwd, fname);
      if (fs.existsSync(p)) {
        return fs.readFileSync(p, 'utf8')
          .split('\n')
          .map(l => l.trim())
          .filter(l => l && !l.startsWith('#'));
      }
    }
    return [];
  }
  function matchesIgnorePattern(dirName, patterns) {
    for (const pat of patterns) {
      const clean = pat.replace(/\/$/, '');
      if (clean === dirName) return true;
      if (clean.endsWith('/**') && dirName.startsWith(clean.slice(0, -3))) return true;
      if (clean.endsWith('/*') && dirName.startsWith(clean.slice(0, -2))) return true;
    }
    return false;
  }
  module.exports = { loadIgnorePatterns, matchesIgnorePattern };
};

// ── ./src/discovery/language-detector ──
__factories["./src/discovery/language-detector"] = function(module, exports) {
  'use strict';
  const fs   = require('fs');
  const path = require('path');
  const { REGISTRY } = __require('./src/discovery/source-root-registry');
  function detectLanguages(cwd) {
    const weights = {};
    for (const [lang, reg] of Object.entries(REGISTRY)) {
      for (const mf of (reg.manifestFiles || [])) {
        if (fs.existsSync(path.join(cwd, mf))) {
          weights[lang] = (weights[lang] || 0) + 3;
        }
      }
    }
    try {
      const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
      const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
      if (allDeps.typescript) { weights.typescript = (weights.typescript || 0) + 2; }
    } catch (_) {}
    const extCount = {};
    (function _walkDepth(dir, depth, extCount) {
      if (depth <= 0) return;
      let entries;
      try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
      for (const e of entries) {
        const SKIP_DIRS = new Set(['node_modules','dist','build','.git','.next','.nuxt','vendor','DerivedData','Pods','target','coverage','__pycache__','.venv','venv','.build','Carthage','storybook-static']);
        if (SKIP_DIRS.has(e.name)) continue;
        if (e.isDirectory()) {
          _walkDepth(path.join(dir, e.name), depth - 1, extCount);
        } else if (e.isFile()) {
          const EXT_TO_LANG = {'.js': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript', '.ts': 'typescript', '.tsx': 'typescript', '.jsx': 'javascript', '.py': 'python', '.rb': 'ruby', '.go': 'go', '.rs': 'rust', '.java': 'java', '.kt': 'kotlin', '.cs': 'csharp', '.cpp': 'cpp', '.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.swift': 'swift', '.dart': 'dart', '.scala': 'scala', '.php': 'php', '.r': 'r', '.R': 'r'};
          const ext = path.extname(e.name).toLowerCase();
          if (EXT_TO_LANG[ext]) extCount[ext] = (extCount[ext] || 0) + 1;
        }
      }
    })(cwd, 3, extCount);
    const maxCount = Math.max(1, ...Object.values(extCount));
    const EXT_TO_LANG = {'.js': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript', '.ts': 'typescript', '.tsx': 'typescript', '.jsx': 'javascript', '.py': 'python', '.rb': 'ruby', '.go': 'go', '.rs': 'rust', '.java': 'java', '.kt': 'kotlin', '.cs': 'csharp', '.cpp': 'cpp', '.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.swift': 'swift', '.dart': 'dart', '.scala': 'scala', '.php': 'php'};
    for (const [ext, count] of Object.entries(extCount)) {
      const lang = EXT_TO_LANG[ext];
      if (lang) {
        weights[lang] = (weights[lang] || 0) + Math.min(5, (count / maxCount) * 5);
      }
    }
    const maxW = Math.max(1, ...Object.values(weights));
    return Object.entries(weights)
      .map(([name, w]) => ({ name, weight: Math.round(w / maxW * 100) / 100 }))
      .sort((a, b) => b.weight - a.weight);
  }
  module.exports = { detectLanguages };
};

// ── ./src/discovery/source-root-scorer ──
__factories["./src/discovery/source-root-scorer"] = function(module, exports) {
  'use strict';
  const fs   = require('fs');
  const path = require('path');
  const { execSync } = require('child_process');
  const CODE_EXTS = new Set(['.js','.mjs','.cjs','.ts','.tsx','.jsx','.py','.rb','.go','.rs','.java','.kt','.cs','.cpp','.c','.h','.swift','.dart','.scala','.php']);
  const AUTO_SKIP = new Set(['node_modules','dist','build','.git','.next','.nuxt','vendor','DerivedData','Pods','target','coverage','__pycache__','.venv','venv','.build','Carthage','storybook-static','.gradle','bin','obj','.vs']);
  const PENALTY_DIRS = new Set(['test','tests','spec','__tests__','e2e','docs','doc','docs-vp','examples','example','fixtures','mocks','__mocks__','demo','samples','migrations','benchmarks','scripts']);
  const ROOT_ENTRYPOINTS = { go: ['main.go'], python: ['app.py','main.py','wsgi.py','asgi.py'], javascript: ['index.js','server.js','app.js'], typescript: ['index.ts','main.ts'], rust: [], php: ['index.php'] };
  function getRecentlyChangedDirs(cwd) {
    try {
      const out = execSync('git log --name-only --format="" HEAD~10 2>/dev/null', { cwd, timeout: 3000 }).toString();
      return new Set(out.split('\n').filter(Boolean).map(f => f.split('/')[0]));
    } catch { return new Set(); }
  }
  function scoreCandidate(dirName, fullPath, context) {
    const { frameworks, languages, recentDirs, frameworkSrcDirs, entrypoints, frameworkPenalties } = context;
    if (AUTO_SKIP.has(dirName)) return -99;
    if (!fs.existsSync(fullPath)) return -99;
    let score = 0;
    if (frameworkSrcDirs.has(dirName)) score += 3.0;
    const sourceFileCount = (function _countSourceFiles(dir, depth) {
      if (depth <= 0) return 0;
      let count = 0;
      try {
        for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
          if (e.isFile() && CODE_EXTS.has(path.extname(e.name).toLowerCase())) count++;
          else if (e.isDirectory() && depth > 1) count += _countSourceFiles(path.join(dir, e.name), depth - 1);
        }
      } catch (_) {}
      return count;
    })(fullPath, 2);
    const density = Math.min(1.0, sourceFileCount / 10);
    score += density * 2.5;
    if (sourceFileCount >= 3) score += 2.0;
    if ((entrypoints || []).some(ep => ep.startsWith(dirName + '/'))) score += 1.5;
    if (fs.existsSync(path.join(fullPath, 'package.json')) ||
        fs.existsSync(path.join(fullPath, 'go.mod')) ||
        fs.existsSync(path.join(fullPath, 'Cargo.toml')) ||
        fs.existsSync(path.join(fullPath, 'pom.xml'))) {
      score += 1.0;
    }
    if (recentDirs.has(dirName)) score += 2.0;
    if (PENALTY_DIRS.has(dirName.toLowerCase()) && !frameworkSrcDirs.has(dirName)) score -= 3.0;
    if ((frameworkPenalties || []).includes(dirName)) score -= 3.0;
    return Math.round(score * 100) / 100;
  }
  module.exports = { scoreCandidate, getRecentlyChangedDirs, ROOT_ENTRYPOINTS };
};

// ── ./src/discovery/framework-detector ──
__factories["./src/discovery/framework-detector"] = function(module, exports) {
  'use strict';
  const fs   = require('fs');
  const path = require('path');
  const { REGISTRY } = __require('./src/discovery/source-root-registry');
  function detectFrameworks(cwd) {
    const detected = [];
    for (const [lang, reg] of Object.entries(REGISTRY)) {
      if (!reg.frameworks) continue;
      for (const [name, fw] of Object.entries(reg.frameworks)) {
        let confidence = 0;
        for (const f of (fw.detectionFiles || [])) {
          if ((function _existsAnywhere(cwd, filename, maxDepth) {
            const parts = filename.split('/');
            if (parts.length > 1) return fs.existsSync(path.join(cwd, filename));
            return (function _walkFind(dir, name, depth) {
              if (depth <= 0) return false;
              try {
                const entries = fs.readdirSync(dir, { withFileTypes: true });
                for (const e of entries) {
                  if (e.name === name) return true;
                  if (e.isDirectory() && depth > 1) {
                    if (_walkFind(path.join(dir, e.name), name, depth - 1)) return true;
                  }
                }
              } catch (_) {}
              return false;
            })(cwd, parts[0], maxDepth);
          })(cwd, f, 3)) { confidence = Math.max(confidence, 0.93); }
        }
        if (fw.detectionDeps?.length) {
          const deps = (function _readDeps(cwd) {
            try {
              const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
              return new Set([...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.devDependencies || {})]);
            } catch { return new Set(); }
          })(cwd);
          for (const dep of fw.detectionDeps) {
            if (deps.has(dep)) { confidence = Math.max(confidence, 0.90); }
          }
        }
        if (lang === 'go' && fw.detectionDeps?.length) {
          const goMod = (function _readFile(p) {
            try { return fs.readFileSync(p, 'utf8'); } catch { return ''; }
          })(path.join(cwd, 'go.mod'));
          for (const dep of fw.detectionDeps) {
            if (goMod.includes(dep)) { confidence = Math.max(confidence, 0.90); }
          }
        }
        if (lang === 'rust' && fw.detectionDeps?.length) {
          const cargoToml = (function _readFile(p) {
            try { return fs.readFileSync(p, 'utf8'); } catch { return ''; }
          })(path.join(cwd, 'Cargo.toml'));
          for (const dep of fw.detectionDeps) {
            if (cargoToml.includes(dep)) { confidence = Math.max(confidence, 0.88); }
          }
        }
        if (fw.specialRule === 'django-app-dirs' && fs.existsSync(path.join(cwd, 'manage.py'))) {
          confidence = Math.max(confidence, 0.95);
        }
        if (fw.specialRule === 'swift-project-dir' && (function _existsAnywhere(cwd, filename, maxDepth) {
          const parts = filename.split('/');
          if (parts.length > 1) return fs.existsSync(path.join(cwd, filename));
          return (function _walkFind(dir, name, depth) {
            if (depth <= 0) return false;
            try {
              const entries = fs.readdirSync(dir, { withFileTypes: true });
              for (const e of entries) {
                if (e.name === name) return true;
                if (e.isDirectory() && depth > 1) {
                  if (_walkFind(path.join(dir, e.name), name, depth - 1)) return true;
                }
              }
            } catch (_) {}
            return false;
          })(cwd, parts[0], maxDepth);
        })(cwd, '.xcodeproj', 2)) {
          confidence = Math.max(confidence, 0.90);
        }
        if (confidence > 0) detected.push({ name, language: lang, confidence });
      }
    }
    return detected.sort((a, b) => b.confidence - a.confidence);
  }
  module.exports = { detectFrameworks };
};

// ── ./src/discovery/source-root-resolver ──
__factories["./src/discovery/source-root-resolver"] = function(module, exports) {
  'use strict';
  const fs   = require('fs');
  const path = require('path');
  const { REGISTRY }              = __require('./src/discovery/source-root-registry');
  const { detectLanguages }       = __require('./src/discovery/language-detector');
  const { detectFrameworks }      = __require('./src/discovery/framework-detector');
  const { scoreCandidate, getRecentlyChangedDirs, ROOT_ENTRYPOINTS } = __require('./src/discovery/source-root-scorer');
  const { loadIgnorePatterns, matchesIgnorePattern } = __require('./src/discovery/sigmapignore');
  function resolveSourceRoots(cwd, opts = {}) {
    const ignorePatterns = loadIgnorePatterns(cwd);
    const languages      = detectLanguages(cwd);
    const frameworks     = detectFrameworks(cwd);
    const recentDirs     = getRecentlyChangedDirs(cwd);
    const isMonorepo     = (function _detectMonorepo(cwd) {
      const MONOREPO_MARKERS = ['pnpm-workspace.yaml','turbo.json','nx.json','lerna.json'];
      for (const m of MONOREPO_MARKERS) {
        if (fs.existsSync(path.join(cwd, m))) return true;
      }
      try {
        const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
        if (pkg.workspaces) return true;
      } catch (_) {}
      return false;
    })(cwd);
    const primaryLang   = languages[0]?.name;
    const primaryFw     = frameworks[0];
    const registry      = primaryLang ? REGISTRY[primaryLang] : null;
    const fwEntry        = primaryFw && registry?.frameworks?.[primaryFw.name];
    const frameworkSrcDirs   = new Set(fwEntry?.srcDirs || registry?.srcDirs || []);
    const entrypoints        = fwEntry?.entrypoints || [];
    const frameworkPenalties = registry?.penalties || [];
    const context = { frameworks, languages, recentDirs, frameworkSrcDirs, entrypoints, frameworkPenalties };
    const candidates = (function _enumerateCandidates(cwd, isMonorepo, ignorePatterns, excludeList) {
      const candidates = [];
      const excSet     = new Set(excludeList);
      try {
        for (const e of fs.readdirSync(cwd, { withFileTypes: true })) {
          if (!e.isDirectory()) continue;
          if (excSet.has(e.name)) continue;
          if (matchesIgnorePattern(e.name, ignorePatterns)) continue;
          candidates.push({ name: e.name, full: path.join(cwd, e.name) });
        }
      } catch (_) {}
      if (isMonorepo) {
        for (const top of ['packages','apps','services','modules']) {
          const topFull = path.join(cwd, top);
          if (!fs.existsSync(topFull)) continue;
          try {
            for (const pkg of fs.readdirSync(topFull, { withFileTypes: true })) {
              if (!pkg.isDirectory()) continue;
              const srcFull = path.join(topFull, pkg.name, 'src');
              if (fs.existsSync(srcFull)) {
                candidates.push({ name: `${top}/${pkg.name}/src`, full: srcFull });
              }
              candidates.push({ name: `${top}/${pkg.name}`, full: path.join(topFull, pkg.name) });
            }
          } catch (_) {}
        }
      }
      const DEEP_PATHS = ['src/main/java','src/main/kotlin','src/main/scala','src-tauri/src','Sources/App','app/src/main/java','app/src/main/kotlin'];
      for (const dp of DEEP_PATHS) {
        const full = path.join(cwd, dp);
        if (fs.existsSync(full)) candidates.push({ name: dp, full });
      }
      return candidates;
    })(cwd, isMonorepo, ignorePatterns, opts.exclude || []);
    const scored = candidates
      .map(({ name, full }) => ({
        dir:   name,
        full,
        score: scoreCandidate(name, full, context),
      }))
      .filter(c => c.score > 0)
      .sort((a, b) => b.score - a.score);
    let roots = (function _applySpecialRules(scored, cwd, primaryFw, fwEntry, frameworks) {
      let roots = [...scored];
      if (primaryFw?.name === 'django' || frameworks.some(f => f.name === 'django')) {
        try {
          for (const e of fs.readdirSync(cwd, { withFileTypes: true })) {
            if (!e.isDirectory()) continue;
            const d = path.join(cwd, e.name);
            if (fs.existsSync(path.join(d, 'models.py')) || fs.existsSync(path.join(d, 'views.py'))) {
              if (!roots.find(r => r.dir === e.name)) {
                roots.push({ dir: e.name, full: d, score: 5.0 });
              }
            }
          }
        } catch (_) {}
        roots.sort((a, b) => b.score - a.score);
      }
      if (frameworks.some(f => f.name === 'swiftui')) {
        try {
          for (const e of fs.readdirSync(cwd, { withFileTypes: true })) {
            if (!e.isDirectory()) continue;
            const d = path.join(cwd, e.name);
            const swiftCount = (fs.readdirSync(d).filter(f => f.endsWith('.swift'))).length;
            if (swiftCount >= 3 && !roots.find(r => r.dir === e.name)) {
              roots.push({ dir: e.name, full: d, score: 4.0 });
            }
          }
        } catch (_) {}
        roots.sort((a, b) => b.score - a.score);
      }
      return roots;
    })(scored, cwd, primaryFw, fwEntry, frameworks);
    roots = (function _dedupeNested(scored) {
      const result = [];
      for (const c of scored) {
        const isNested = result.some(r => c.dir.startsWith(r.dir + '/'));
        if (!isNested) result.push(c);
      }
      return result;
    })(roots);
    const MAX_ROOTS = 6;
    roots = roots.slice(0, MAX_ROOTS).map(r => r.dir);
    const confidence = (function _computeConfidence(frameworks, languages, scoredCount) {
      if (frameworks.length > 0 && frameworks[0].confidence >= 0.90) return 'high';
      if (languages.length > 0 && scoredCount > 0) return 'medium';
      return 'low';
    })(frameworks, languages, scored.length);
    return {
      roots,
      languages,
      frameworks,
      confidence,
      explanation: scored.slice(0, 8).map(c => ({
        dir:   c.dir,
        score: c.score,
        reason: `score: ${c.score}`,
      })),
      isMonorepo,
    };
  }
  module.exports = { resolveSourceRoots };
};

// ── ./src/workspace/detector ──
__factories["./src/workspace/detector"] = function(module, exports) {
  'use strict';
  const fs   = require('fs');
  const path = require('path');
  module.exports = { detectWorkspaces, inferPackage, scopeToPackage };

  function detectWorkspaces(cwd) {
    const pkgPath = path.join(cwd, 'package.json');
    if (!fs.existsSync(pkgPath)) return [];

    let pkg;
    try {
      pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
    } catch {
      return [];
    }

    const patterns = pkg.workspaces || [];
    const dirs = [];

    // Handle both flat array and object with packages field (Yarn v2 format)
    const patternArray = Array.isArray(patterns) ? patterns : (patterns.packages || []);

    for (const p of patternArray) {
      const base = p.replace(/\/\*\*?$/, '');
      const resolved = path.join(cwd, base);
      if (fs.existsSync(resolved)) {
        try {
          for (const entry of fs.readdirSync(resolved, { withFileTypes: true })) {
            if (entry.isDirectory()) dirs.push(path.join(resolved, entry.name));
          }
        } catch (_) {}
      }
    }

    return dirs;
  }

  // Infer package from query tokens: "add rate limiting to payments" → "packages/payments"
  function inferPackage(query, workspaceDirs, cwd) {
    const tokens = query.toLowerCase().split(/\W+/).filter(t => t.length > 2);

    // Find longest matching package name
    let bestMatch = null;
    let bestLen = 0;
    let bestMatchLen = 0;

    for (const dir of workspaceDirs) {
      const name = path.basename(dir).toLowerCase();
      for (const token of tokens) {
        const matchLen = _getMatchLength(name, token);
        // Only consider matches; use longest match, and break ties by longest package name
        if (matchLen > 0 && (matchLen > bestLen || (matchLen === bestLen && name.length > bestMatchLen))) {
          bestMatch = dir;
          bestLen = matchLen;
          bestMatchLen = name.length;
        }
      }
    }

    return bestMatch;
  }

  function _getMatchLength(name, token) {
    if (name === token) return 1000 + name.length;  // Exact match is best
    if (name.startsWith(token) && token.length >= 3) return 100 + token.length;
    if (token.startsWith(name) && name.length >= 3) return name.length;
    return 0;
  }

  // Return boost multiplier for files inside the inferred package
  function scopeToPackage(filePath, packageDir) {
    const normalized = filePath.replace(/\\/g, '/');
    const normalizedPkg = packageDir.replace(/\\/g, '/');

    // Ensure we match the directory boundary, not just a prefix
    // e.g., packages/payment should not match packages/payment-old
    if (normalized.startsWith(normalizedPkg)) {
      const afterPrefix = normalized.slice(normalizedPkg.length);
      // Check if next char is / or if it's the exact match
      if (afterPrefix === '' || afterPrefix[0] === '/') {
        return 0.30;
      }
    }
    return 0;
  }
};

/**
 * SigMap — gen-context.js v1.2.0
 * Zero-dependency AI context engine.
 * Runs with: node gen-context.js
 * No npm install required. Node 18+ built-ins only.
 */

const fs = require('fs');
const path = require('path');
const os = require('os');
const { execSync } = require('child_process');

const VERSION = '6.10.10';
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';

function requireSourceOrBundled(key) {
  try {
    const rel = key.replace(/^\.\//, '') + '.js';
    const abs = path.join(__dirname, rel);
    if (fs.existsSync(abs)) return require(abs);
  } catch (_) {}
  return __require(key);
}

// ---------------------------------------------------------------------------
// Config — delegate to src/config/loader.js
// ---------------------------------------------------------------------------
const { loadConfig } = requireSourceOrBundled('./src/config/loader');
const { DEFAULTS } = requireSourceOrBundled('./src/config/defaults');

// ---------------------------------------------------------------------------
// Language → extractor mapping (by file extension)
// ---------------------------------------------------------------------------
const EXT_MAP = {
  '.ts': 'typescript', '.tsx': 'typescript_react',
  '.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
  '.py': 'python', '.pyw': 'python',
  '.java': 'java',
  '.kt': 'kotlin', '.kts': 'kotlin',
  '.go': 'go',
  '.rs': 'rust',
  '.cs': 'csharp',
  '.cpp': 'cpp', '.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.cc': 'cpp',
  '.rb': 'ruby', '.rake': 'ruby',
  '.php': 'php',
  '.swift': 'swift',
  '.dart': 'dart',
  '.scala': 'scala', '.sc': 'scala',
  '.r': 'r', '.R': 'r',
  '.vue': 'vue_sfc',
  '.svelte': 'svelte',
  '.html': 'html', '.htm': 'html',
  '.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
  '.yml': 'yaml', '.yaml': 'yaml',
  '.sh': 'shell', '.bash': 'shell', '.zsh': 'shell', '.fish': 'shell',
  // P1 languages
  '.sql': 'sql',
  '.graphql': 'graphql', '.gql': 'graphql',
  '.tf': 'terraform', '.tfvars': 'terraform',
  '.proto': 'protobuf',
  // Phase A formats
  '.toml': 'toml',
  '.properties': 'properties',
  '.xml': 'xml',
  '.md': 'markdown',
};

// Dockerfile handled separately (no extension)
function isDockerfile(filename) {
  return filename === 'Dockerfile' || filename.startsWith('Dockerfile.');
}

// ---------------------------------------------------------------------------
// .contextignore parser (gitignore-style subset)
// ---------------------------------------------------------------------------
function loadIgnorePatterns(cwd) {
  const patterns = [];
  for (const name of ['.contextignore', '.repomixignore']) {
    const p = path.join(cwd, name);
    if (fs.existsSync(p)) {
      const lines = fs.readFileSync(p, 'utf8').split('\n');
      for (const line of lines) {
        const trimmed = line.trim();
        if (trimmed && !trimmed.startsWith('#')) patterns.push(trimmed);
      }
    }
  }
  return patterns;
}

function matchesIgnore(relPath, patterns) {
  for (const pat of patterns) {
    const normalized = pat.replace(/\\/g, '/');
    // Strip trailing slash (gitignore style — directory patterns)
    const patternToUse = normalized.endsWith('/')
      ? normalized.slice(0, -1)
      : normalized;
    // Escape regex special chars but NOT brackets (keep them for character classes)
    const regexStr = patternToUse
      .replace(/[.+^${}()|\\]/g, '\\$&')
      .replace(/\*\*/g, '___DOUBLE___')
      .replace(/\*/g, '[^/]*')
      .replace(/___DOUBLE___/g, '.*');
    try {
      const regex = new RegExp(`(^|/)${regexStr}($|/)`);
      if (regex.test(relPath)) return true;
    } catch (_) {
      // Malformed bracket syntax or invalid regex — skip this pattern
    }
  }
  return false;
}

// ---------------------------------------------------------------------------
// File walker
// ---------------------------------------------------------------------------
function walkDir(dir, exclude, maxDepth, depth = 0) {
  if (depth > maxDepth) return [];
  let results = [];
  let entries;
  try {
    entries = fs.readdirSync(dir, { withFileTypes: true });
  } catch (_) {
    return [];
  }
  for (const entry of entries) {
    if (exclude.includes(entry.name)) continue;
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      results = results.concat(walkDir(full, exclude, maxDepth, depth + 1));
    } else if (entry.isFile()) {
      results.push(full);
    }
  }
  return results;
}

function buildFileList(cwd, config) {
  const files = [];
  for (const srcDir of config.srcDirs) {
    const abs = path.join(cwd, srcDir);
    if (!fs.existsSync(abs)) continue;
    const found = walkDir(abs, config.exclude, config.maxDepth);
    files.push(...found);
  }
  // Deduplicate
  return [...new Set(files)];
}

// ---------------------------------------------------------------------------
// Extractor loader (lazy, cached)
// ---------------------------------------------------------------------------
const _extractorCache = {};
function getExtractor(name) {
  if (_extractorCache[name]) return _extractorCache[name];
  try {
    const mod = requireSourceOrBundled(`./src/extractors/${name}`);
    _extractorCache[name] = mod;
    return mod;
  } catch (err) {
    console.warn(`[sigmap] failed to load extractor ${name}: ${err.message}`);
    return null;
  }
}

function detectAndExtract(filePath, content, maxSigsPerFile) {
  const base = path.basename(filePath);
  const ext = path.extname(base).toLowerCase();
  let extractorName = EXT_MAP[ext] || null;
  if (!extractorName && isDockerfile(base)) extractorName = 'dockerfile';
  // Feature 7: generic fallback — catches Nix, Elixir, Gleam, Zig, Go templates, etc.
  if (!extractorName) extractorName = 'generic';

  const extractor = getExtractor(extractorName);
  if (!extractor) return [];

  try {
    const sigs = extractor.extract(content);
    return Array.isArray(sigs) ? sigs.slice(0, maxSigsPerFile) : [];
  } catch (err) {
    console.warn(`[sigmap] extractor failed for ${filePath}: ${err.message}`);
    return [];
  }
}

function extractFileDeps(filePath, content, config) {
  if (config && config.depMap === false) return [];
  try {
    const { extractPythonDeps, extractTSDeps, extractRDeps } = requireSourceOrBundled('./src/extractors/deps');
    const ext = path.extname(filePath).toLowerCase();
    if (ext === '.py' || ext === '.pyw') return extractPythonDeps(content);
    if (['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(ext)) return extractTSDeps(content);
    if (ext === '.r') return extractRDeps ? extractRDeps(content) : [];
  } catch (_) {}
  return [];
}

function extractSignatureName(sig) {
  const s = (sig || '').trim();
  const m = s.match(/(?:def|function|func|constructor|async\s+def|async\s+function)?\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
  return m ? m[1] : '';
}

function annotateCoverage(sigs, testIndex, enabled) {
  if (!enabled || !testIndex || testIndex.size === 0) return sigs;
  const { isTested } = requireSourceOrBundled('./src/extractors/coverage');
  return sigs.map((sig) => {
    const name = extractSignatureName(sig);
    if (!name) return sig;
    // Skip non-callable/type declarations.
    if (/^(class|interface|enum|type|module|trait|struct|record)\b/.test(sig.trim())) return sig;
    return `${sig}  ${isTested(name, testIndex) ? '✓' : '✗'}`;
  });
}

// ---------------------------------------------------------------------------
// Token budget enforcement
// ---------------------------------------------------------------------------
function estimateTokens(str) {
  return Math.ceil(str.length / 4);
}

function isTestFile(filePath) {
  return /\.(test|spec)\.[a-z]+$/.test(filePath) || /_test\.[a-z]+$/.test(filePath);
}

function isConfigFile(filePath) {
  return /\.(config|conf)\.[a-z]+$/.test(filePath) ||
    path.extname(filePath) === '.json';
}

function isGeneratedFile(filePath) {
  return /(\.generated\.|\.pb\.|_pb\.)/.test(filePath);
}

function isMockFile(filePath) {
  const p = filePath.replace(/\\/g, '/');
  return /\/(mock|mocks|stub|stubs|fake|fakes|demo|demos|__mocks__|fixtures)\//i.test(p) ||
    /\.(mock|stub|fake)\.[jt]sx?$/.test(p) ||
    /mock\.(ts|js|tsx|jsx)$/.test(p);
}

/**
 * Compute the effective token budget based on repo size and config.
 *
 * Formula:
 *   totalSigTokens  = sum of estimated tokens for all extracted sig blocks
 *   needed          = ceil(totalSigTokens * coverageTarget)   // tokens for target% coverage
 *   hardCap         = floor(modelContextLimit * maxTokensHeadroom)
 *   effective       = clamp(needed, 4000, hardCap)
 *
 * When autoMaxTokens is false the configured maxTokens is returned unchanged.
 *
 * @param {Array}  fileEntries - All file entries BEFORE budget enforcement
 * @param {object} config
 * @returns {number} effective token budget
 */
function computeEffectiveMaxTokens(fileEntries, config) {
  if (config.autoMaxTokens === false) return config.maxTokens;

  const coverageTarget    = (config.coverageTarget    != null) ? config.coverageTarget    : 0.80;
  const modelContextLimit = (config.modelContextLimit != null) ? config.modelContextLimit : 128000;
  const maxTokensHeadroom = (config.maxTokensHeadroom != null) ? config.maxTokensHeadroom : 0.20;

  const totalSigTokens = fileEntries.reduce(
    (s, e) => s + estimateTokens((e.sigs || []).join('\n')), 0
  );
  if (totalSigTokens === 0) return config.maxTokens;

  const hardCap  = Math.floor(modelContextLimit * maxTokensHeadroom);
  const needed   = Math.ceil(totalSigTokens * coverageTarget);
  const MIN      = 4000;
  const effective = Math.min(Math.max(MIN, needed), hardCap);

  // Warn when repo is so large the hard cap prevents hitting the coverage target
  if (needed > hardCap) {
    const estimatedCovPct = Math.round((hardCap / totalSigTokens) * 100);
    const targetPct       = Math.round(coverageTarget * 100);
    if (estimatedCovPct < targetPct - 10) {
      console.warn(
        `[sigmap] auto-budget: ${fileEntries.length} files need ~${Math.round(needed / 1000)}K tokens ` +
        `for ${targetPct}% coverage`
      );
      console.warn(
        `[sigmap] auto-budget: capped at ${hardCap} ` +
        `(${Math.round(maxTokensHeadroom * 100)}% of ${Math.round(modelContextLimit / 1000)}K model limit) ` +
        `→ est. ${estimatedCovPct}% coverage`
      );
      console.warn(
        `[sigmap] auto-budget: tip — set strategy:"per-module" for full coverage on large repos`
      );
    }
  }

  return effective;
}

function applyTokenBudget(fileEntries, maxTokens) {
  // fileEntries: [{ filePath, sigs, mtime }]
  // Reserve ~10% for formatting overhead (section headers, code fences, top-level header)
  const effectiveBudget = Math.floor(maxTokens * 0.90);
  let total = fileEntries.reduce((s, e) => s + estimateTokens(e.sigs.join('\n')), 0);
  if (total <= effectiveBudget) return fileEntries;

  // Sort by drop priority (drop first = index 0)
  const withPriority = fileEntries.map((e) => {
    let priority = 0;
    let dropReason = 'budget: low recency';
    if (isGeneratedFile(e.filePath)) { priority = 10; dropReason = 'budget: generated file'; }
    else if (isMockFile(e.filePath)) { priority = 9;  dropReason = 'budget: mock file'; }
    else if (isTestFile(e.filePath)) { priority = 8;  dropReason = 'budget: test file'; }
    else if (isConfigFile(e.filePath)) { priority = 6; dropReason = 'budget: config file'; }
    else priority = 4;
    // v4.0: signal quality = sigs per line-of-code (higher = more informative)
    const loc = e.content ? e.content.split('\n').length : 1;
    const signalQuality = loc > 0 ? (e.sigs ? e.sigs.length : 0) / loc : 0;
    return { ...e, priority, dropReason, signalQuality };
  });

  // Within same priority: sort by mtime ascending (oldest first), then signalQuality ascending (least informative first)
  withPriority.sort((a, b) => {
    if (b.priority !== a.priority) return b.priority - a.priority;
    if ((a.mtime || 0) !== (b.mtime || 0)) return (a.mtime || 0) - (b.mtime || 0);
    return (a.signalQuality || 0) - (b.signalQuality || 0);
  });

  const kept = [];
  const verboseDropped = [];
  // Iterate forward: highest drop-priority files (generated=10, mock=9, test=8) are at index 0
  // Drop those first until we're under budget, then keep everything else
  for (const entry of withPriority) {
    const entryTokens = estimateTokens(entry.sigs.join('\n'));
    if (total > effectiveBudget) {
      total -= entryTokens;
      verboseDropped.push({ filePath: entry.filePath, reason: entry.dropReason });
    } else {
      kept.push(entry);
    }
  }
  if (verboseDropped.length > 0) {
    console.warn(`[sigmap] budget: dropped ${verboseDropped.length} files to stay under ${maxTokens} tokens`);
    // Feature 7: --verbose — print per-file drop reason
    if (process.argv.includes('--verbose')) {
      for (const { filePath, reason } of verboseDropped) {
        console.warn(`[sigmap] dropped: ${path.relative(process.cwd(), filePath)} — ${reason}`);
      }
      console.warn(`[sigmap] included: ${kept.length} files, dropped: ${verboseDropped.length}`);
    }
  }
  return kept;
}

// ---------------------------------------------------------------------------
// Recently committed files (git, optional)
// ---------------------------------------------------------------------------
function getRecentlyCommittedFiles(cwd, count) {
  const n = count || 10;
  try {
    const out = execSync(`git log --name-only --format="" -n ${n}`, {
      cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
    });
    return new Set(out.split('\n').map((f) => f.trim()).filter(Boolean).map((f) => path.resolve(cwd, f)));
  } catch (_) {
    return new Set();
  }
}

// ---------------------------------------------------------------------------
// Diff mode: files changed in working tree or staging area
// ---------------------------------------------------------------------------
function getDiffFiles(cwd, stagedOnly) {
  try {
    const cmd = stagedOnly ? 'git diff --cached --name-only' : 'git diff HEAD --name-only';
    const out = execSync(cmd, { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
    return new Set(
      out.split('\n').map((f) => f.trim()).filter(Boolean).map((f) => path.resolve(cwd, f))
    );
  } catch (_) {
    return new Set();
  }
}

function getFilesChangedSinceBase(cwd, baseRef) {
  if (!/^[A-Za-z0-9._/\-~^]+$/.test(baseRef)) {
    console.warn(`[sigmap] --diff: invalid base ref '${baseRef}'`);
    return new Set();
  }
  try {
    const { execFileSync } = require('child_process');
    const out = execFileSync('git', ['diff', `${baseRef}..HEAD`, '--name-only'], {
      cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
    });
    return new Set(out.split('\n').map((f) => f.trim()).filter(Boolean).map((f) => path.resolve(cwd, f)));
  } catch (_) {
    return new Set();
  }
}

function buildDiffSectionFromBase(cwd, baseRef, currentEntries, config) {
  const { diffSignatures, extractName } = requireSourceOrBundled('./src/extractors/prdiff');
  const lines = ['## diff (vs ' + baseRef + ')', '```'];

  for (const entry of currentEntries) {
    const rel = path.relative(cwd, entry.filePath);
    let baseSrc = '';
    try {
      const { execFileSync } = require('child_process');
      baseSrc = execFileSync('git', ['show', `${baseRef}:${rel}`], {
        cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
      });
    } catch (_) {
      baseSrc = '';
    }

    const baseSigs = baseSrc ? detectAndExtract(entry.filePath, baseSrc, config.maxSigsPerFile) : [];
    const d = diffSignatures(baseSigs, entry.sigs || []);

    const markers = [];
    for (const a of d.added.slice(0, 4)) {
      const n = extractName(a);
      if (n) markers.push(`+${n}`);
    }
    for (const r of d.removed.slice(0, 4)) {
      const n = extractName(r);
      if (n) markers.push(`-${n}`);
    }
    for (const m of d.modified.slice(0, 4)) markers.push(`~${m}`);
    if (markers.length === 0) continue;
    lines.push(`${rel}`);
    lines.push(`  ${[...new Set(markers)].slice(0, 6).join('  ')}`);
  }

  lines.push('```', '');
  return lines.length > 3 ? lines : [];
}

// ---------------------------------------------------------------------------
// Output formatter
// ---------------------------------------------------------------------------
function buildDepMap(fileEntries, cwd) {
  const lines = [];
  for (const entry of fileEntries) {
    const deps = entry.deps;
    if (!deps || deps.length === 0) continue;
    const rel = path.relative(cwd, entry.filePath);
    lines.push(`${rel} \u2190 ${deps.join(', ')}`);
  }
  return lines;
}

function buildTodoSection(fileEntries, cwd, config) {
  if (config && config.todos === false) return [];
  const { extractTodos } = requireSourceOrBundled('./src/extractors/todos');
  const out = [];
  for (const entry of fileEntries) {
    const todos = extractTodos(entry.content || '');
    if (!todos || todos.length === 0) continue;
    const rel = path.relative(cwd, entry.filePath);
    for (const t of todos) {
      out.push(`${rel}:${t.line}  # ${t.tag}: ${t.text}`);
    }
  }
  return out.slice(0, 20);
}

function buildChangesSection(cwd, config, fileEntries) {
  if (config && config.changes === false) return [];
  const n = (config && config.changesCommits) || 5;
  try {
    let range = `HEAD~${n}..HEAD`;
    try {
      execSync(`git rev-parse --verify HEAD~${n} 2>/dev/null`, {
        cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
      });
    } catch (_) {
      // HEAD~n doesn't exist (shallow repo) — clamp to deepest valid ancestor
      let best = 1;
      for (let k = n - 1; k >= 2; k--) {
        try {
          execSync(`git rev-parse --verify HEAD~${k} 2>/dev/null`, {
            cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
          });
          best = k;
          break;
        } catch (_) {}
      }
      range = `HEAD~${best}..HEAD`;
    }

    const namesOnly = execSync(`git diff ${range} --name-only 2>/dev/null`, {
      cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
    });
    const changed = new Set(namesOnly.split('\n').map((l) => l.trim()).filter(Boolean).map((f) => path.resolve(cwd, f)));
    if (changed.size === 0) return [];

    const timeAgo = execSync('git log -1 --format="%cr" 2>/dev/null', {
      cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
    }).trim();

    const lines = [`## changes (last ${n} commits — ${timeAgo})`, '```'];
    let hasDelta = false;
    for (const entry of fileEntries) {
      if (!changed.has(entry.filePath)) continue;
      const rel = path.relative(cwd, entry.filePath);
      let diff = '';
      try {
        diff = execSync(`git diff ${range} -- "${rel}" 2>/dev/null`, {
          cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
        });
      } catch (_) {
        continue;
      }
      const plus = [...diff.matchAll(/^\+.*(?:def\s+|function\s+|class\s+|func\s+)(\w+)/gm)].map((m) => `+${m[1]}`);
      const modDef = [...diff.matchAll(/^-.*(?:def\s+|function\s+|class\s+|func\s+)(\w+)/gm)].map((m) => `~${m[1]}`);
      // Hunk headers (Python/Go/Java: git shows enclosing func in @@ ... @@ context)
      const modCtx = [...diff.matchAll(/^@@[^@]*@@\s+(?:(?:async|export|public|private|static)\s+)*(?:def|function|class|func)\s+(\w+)/gm)].map((m) => `~${m[1]}`);
      // Context lines (TS/JS: git doesn't detect funcname, so scan the unchanged lines adjacent to hunks)
      const ctxFn = [...diff.matchAll(/^ +(?:export\s+)?(?:async\s+)?(?:default\s+)?(?:function\s+(\w+)|class\s+(\w+))/gm)].map((m) => `~${m[1] || m[2]}`);
      const delta = [...new Set([...plus, ...modDef, ...modCtx, ...ctxFn])].slice(0, 4).join('  ');
      if (delta) {
        hasDelta = true;
        lines.push(`${rel.padEnd(45)} ${delta}`);
      }
    }
    lines.push('```', '');
    return hasDelta ? lines : [];
  } catch (_) {
    return [];
  }
}

function normalizeRelNoExt(rel) {
  return rel.replace(/\.[^.]+$/, '').replace(/\\/g, '/');
}

function resolveImpactRadius(fileEntries, cwd, config) {
  const map = new Map();
  if (!config || !config.impactRadius) return map;
  const rels = fileEntries.map((e) => path.relative(cwd, e.filePath));
  const keys = rels.map((r) => ({ rel: r, norm: normalizeRelNoExt(r), base: path.basename(normalizeRelNoExt(r)) }));

  for (const entry of fileEntries) {
    const importer = path.relative(cwd, entry.filePath);
    const importerTop = importer.split('/')[0];
    for (const dep of (entry.deps || [])) {
      const depNorm = dep.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\.[^.]+$/, '');
      const matches = keys.filter((k) => {
        if (k.norm === depNorm) return true;
        // Path-segment or base-only match: both require same top-level directory
        // to prevent cross-subsystem matches (e.g. 'app' dep in server/ matching desktop/gui/app.py)
        const matchTop = k.rel.split('/')[0];
        if (k.norm.endsWith('/' + depNorm) || k.base === depNorm) return matchTop === importerTop;
        return false;
      });
      for (const m of matches) {
        if (!map.has(m.rel)) map.set(m.rel, []);
        map.get(m.rel).push(importer);
      }
    }
  }

  for (const [k, v] of map.entries()) {
    map.set(k, [...new Set(v)].slice(0, 5));
  }
  return map;
}

function formatOutput(fileEntries, cwd, routingEnabled, config, extras) {
  const lines = [
    '<!-- Generated by SigMap gen-context.js v' + VERSION + ' -->',
    '<!-- DO NOT EDIT below the marker line — run gen-context.js to regenerate -->',
    '',
    '# Code signatures',
    '',
  ];

  // Compact dependency map section (shows import relationships, ~50-100 tokens)
  const depLines = buildDepMap(fileEntries, cwd);
  if ((!config || config.depMap !== false) && depLines.length) {
    lines.push('## deps');
    lines.push('```');
    lines.push(...depLines);
    lines.push('```');
    lines.push('');
  }

  const todoLines = buildTodoSection(fileEntries, cwd, config || {});
  if (todoLines.length) {
    lines.push('## todos');
    lines.push('```');
    lines.push(...todoLines);
    lines.push('```');
    lines.push('');
  }

  const changesLines = buildChangesSection(cwd, config || {}, fileEntries);
  if (changesLines.length) lines.push(...changesLines);

  if (extras && Array.isArray(extras.diffSection) && extras.diffSection.length) {
    lines.push(...extras.diffSection);
  }

  const impactMap = resolveImpactRadius(fileEntries, cwd, config || {});

  // Group by top-level src dir
  const groups = {};
  for (const entry of fileEntries) {
    const rel = path.relative(cwd, entry.filePath);
    const parts = rel.split(path.sep);
    const group = parts.length > 1 ? parts[0] : '.';
    if (!groups[group]) groups[group] = [];
    groups[group].push({ rel, sigs: entry.sigs });
  }

  for (const [group, entries] of Object.entries(groups).sort()) {
    lines.push(`## ${group}`);
    lines.push('');
    for (const { rel, sigs } of entries) {
      if (sigs.length === 0) continue;
      const usedBy = impactMap.get(rel);
      const usedByStr = usedBy && usedBy.length ? ` (used by: ${usedBy.join(', ')})` : '';
      lines.push(`### ${rel}${usedByStr}`);
      lines.push('```');
      lines.push(...sigs);
      lines.push('```');
      lines.push('');
    }
  }

  if (routingEnabled) {
    try {
      const { classifyAll } = requireSourceOrBundled('./src/routing/classifier');
      const { formatRoutingSection } = requireSourceOrBundled('./src/routing/hints');
      const groups = classifyAll(fileEntries, cwd);
      lines.push(formatRoutingSection(groups));
    } catch (err) {
      console.warn(`[sigmap] routing hints skipped: ${err.message}`);
    }
  }

  return lines.join('\n');
}

// ---------------------------------------------------------------------------
// Output writers
// ---------------------------------------------------------------------------
function ensureDir(filePath) {
  const dir = path.dirname(filePath);
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}

// ---------------------------------------------------------------------------
// Resolve output path for a given adapter
// ---------------------------------------------------------------------------
// Takes config.output into account for the copilot adapter.
// Returns the full path where output should be written.
function resolveAdapterPath(adapter, cwd, config) {
  config = config || {};
  
  // Copilot uses config.output if available, otherwise default
  if (adapter === 'copilot') {
    return config.output 
      ? path.join(cwd, config.output)
      : path.join(cwd, '.github', 'copilot-instructions.md');
  }
  
  // Fixed paths for other adapters
  if (adapter === 'claude') return path.join(cwd, 'CLAUDE.md');
  if (adapter === 'cursor') return path.join(cwd, '.cursorrules');
  if (adapter === 'windsurf') return path.join(cwd, '.windsurfrules');
  if (adapter === 'codex') return path.join(cwd, 'AGENTS.md');
  
  return null;
}

// ---------------------------------------------------------------------------
// Cache output writer (v0.8)
// ---------------------------------------------------------------------------
function writeCacheOutput(content, cwd) {
  try {
    const { formatCache } = __require('./src/format/cache');
    const cachePath = path.join(cwd, '.github', 'copilot-instructions.cache.json');
    ensureDir(cachePath);
    fs.writeFileSync(cachePath, formatCache(content), 'utf8');
    console.warn(`[sigmap] cache: wrote ${path.relative(cwd, cachePath)}`);
  } catch (err) {
    console.warn(`[sigmap] cache: failed to write cache output: ${err.message}`);
  }
}

function writeOutputs(content, targets, cwd, config) {
  config = config || {};
  // v3.0+: adapter-aware output targets
  const ADAPTER_TARGETS = new Set(['copilot', 'openai', 'gemini', 'codex']);

  const targetMap = {
    copilot: resolveAdapterPath('copilot', cwd, config),
    cursor: resolveAdapterPath('cursor', cwd, config),
    windsurf: resolveAdapterPath('windsurf', cwd, config),
  };

  for (const target of targets) {
    if (target === 'claude') {
      writeClaude(content, cwd);
      continue;
    }
    // v3.0+ adapter targets (copilot, openai, gemini, codex) — use bundled adapter to format + write
    if (ADAPTER_TARGETS.has(target)) {
      try {
        const adapterMod = __require('./packages/adapters/' + target);
        // Adapters add their own header via format() — strip the formatOutput() header
        // so the content body (## src, ## deps, etc.) is passed without a pre-existing
        // header, preventing duplicate "# Code signatures" blocks on every run.
        const adapterContent = stripFormatHeader(content);
        // copilot: honour config.output custom path (redirects away from default .github/copilot-instructions.md)
        if (target === 'copilot') {
          const outPath = resolveAdapterPath('copilot', cwd, config);
          const defaultPath = path.join(cwd, '.github', 'copilot-instructions.md');
          if (outPath !== defaultPath) {
            // custom path: format and write directly (no append logic)
            const formatted = adapterMod.format(adapterContent, { version: VERSION });
            ensureDir(outPath);
            fs.writeFileSync(outPath, formatted, 'utf8');
            console.warn(`[sigmap] wrote ${path.relative(cwd, outPath)}`);
            continue;
          }
        }
        if (typeof adapterMod.write === 'function') {
          adapterMod.write(adapterContent, cwd, { version: VERSION });
          const outPath = adapterMod.outputPath(cwd);
          console.warn(`[sigmap] wrote ${path.relative(cwd, outPath)} (appended signatures)`);
        } else {
          const formatted = adapterMod.format(adapterContent, { version: VERSION });
          const outPath = adapterMod.outputPath(cwd);
          ensureDir(outPath);
          fs.writeFileSync(outPath, formatted, 'utf8');
          console.warn(`[sigmap] wrote ${path.relative(cwd, outPath)}`);
        }
      } catch (err) {
        console.warn(`[sigmap] adapter "${target}" failed: ${err.message}`);
      }
      continue;
    }
    const outPath = targetMap[target];
    if (!outPath) {
      console.warn(`[sigmap] unknown output target: ${target}`);
      continue;
    }
    ensureDir(outPath);
    fs.writeFileSync(outPath, content, 'utf8');
    console.warn(`[sigmap] wrote ${path.relative(cwd, outPath)}`);
  }
}

// Strip the formatOutput() header block before passing content to adapters.
// formatOutput() prepends "<!-- Generated... -->\n\n# Code signatures\n\n" which
// is the raw-file header. Adapters add their own header via format(), so passing
// pre-headered content produces duplicate headers on every run.
function stripFormatHeader(content) {
  if (!content || typeof content !== 'string') return content;
  // Header ends after "# Code signatures\n\n" (with blank line)
  const marker = '\n# Code signatures\n';
  const idx = content.indexOf(marker);
  if (idx === -1) return content;
  // Skip past the marker line and one blank line
  const afterMarker = content.slice(idx + marker.length);
  return afterMarker.startsWith('\n') ? afterMarker.slice(1) : afterMarker;
}

function writeClaude(content, cwd) {
  const claudePath = path.join(cwd, 'CLAUDE.md');
  let existing = '';
  if (fs.existsSync(claudePath)) {
    existing = fs.readFileSync(claudePath, 'utf8');
  }
  // Strip the formatOutput() header — CLAUDE.md gets its own header via the MARKER
  const body = stripFormatHeader(content);
  const sigHeader = '# Code signatures\n\n';
  const markerIdx = existing.indexOf('## Auto-generated signatures');
  let newContent;
  if (markerIdx !== -1) {
    newContent = existing.slice(0, markerIdx) + MARKER.trimStart() + sigHeader + body;
  } else {
    newContent = existing + MARKER + sigHeader + body;
  }
  fs.writeFileSync(claudePath, newContent, 'utf8');
  console.warn(`[sigmap] wrote CLAUDE.md (appended signatures)`);
}

// ---------------------------------------------------------------------------
// Report
// ---------------------------------------------------------------------------
function _coverageBar(pct, width) {
  width = width || 16;
  const filled = Math.round(pct / 100 * width);
  return '\u2588'.repeat(filled) + '\u2591'.repeat(width - filled);
}

function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson, budgetLimit, coverageResult, isAutoBudget, configuredMaxTokens) {
  const reduction = inputTokens > 0 ? (100 - (finalTokens / inputTokens) * 100).toFixed(1) : 0;
  const overBudget = finalTokens > (budgetLimit || 6000);
  if (asJson) {
    const payload = {
      version: VERSION,
      timestamp: new Date().toISOString(),
      rawTokens: inputTokens,
      inputTokens,
      finalTokens,
      fileCount,
      droppedCount,
      reductionPct: parseFloat(reduction),
      overBudget,
      budgetLimit: budgetLimit || 6000,
      autoBudget: !!isAutoBudget,
    };
    if (coverageResult) {
      payload.coverage = {
        score: coverageResult.score,
        grade: coverageResult.grade,
        confidence: coverageResult.confidence,
        totalFiles: coverageResult.total,
        includedFiles: coverageResult.included,
        droppedFiles: coverageResult.dropped,
        perModule: Object.fromEntries(
          Array.from(coverageResult.perModule.entries())
            .map(([k, v]) => [k, v])
        ),
      };
    }
    process.stdout.write(JSON.stringify(payload) + '\n');
    // Exit 1 in CI if over budget — lets pipelines fail fast
    if (overBudget) process.exitCode = 1;
  } else {
    const budgetLabel = isAutoBudget
      ? `${budgetLimit || 6000} (auto-scaled)`
      : `${budgetLimit || 6000} (fixed)`;
    if (isAutoBudget && configuredMaxTokens && configuredMaxTokens !== budgetLimit) {
      console.warn(`[sigmap] note: autoMaxTokens is active — your maxTokens:${configuredMaxTokens} config was overridden by auto-scaled budget (${budgetLimit})`);
      console.warn(`  to use your value, set "autoMaxTokens": false in gen-context.config.json`);
    }
    console.log(`[sigmap] report:`);
    console.log(`  version         : ${VERSION}`);
    console.log(`  files processed : ${fileCount}`);
    console.log(`  files dropped   : ${droppedCount}`);
    console.log(`  input tokens    : ~${inputTokens}`);
    console.log(`  output tokens   : ~${finalTokens}`);
    console.log(`  budget limit    : ${budgetLabel}`);
    console.log(`  reduction       : ${reduction}%`);
    if (coverageResult) {
      const skipNote = coverageResult.nonCodeSkipped > 0
        ? `  (${coverageResult.nonCodeSkipped} non-code files skipped — json, md, config)`
        : '';
      console.log(`  coverage        : ${coverageResult.grade} (${coverageResult.score}%)  — ${coverageResult.included} of ${coverageResult.total} code files included`);
      if (skipNote) console.log(`                  ${skipNote}`);
      console.log(`  confidence      : ${coverageResult.confidence}`);
      if (coverageResult.perModule && coverageResult.perModule.size > 0) {
        console.log('');
        console.log('  Module Coverage:');
        for (const [dir, mod] of coverageResult.perModule) {
          if (mod.total === 0) continue;
          const bar = _coverageBar(mod.pct);
          const attention = mod.pct < 50 ? '  \u2190 attention needed' : '';
          console.log(`    ${dir.padEnd(18)} ${bar} ${String(mod.pct).padStart(3)}% (${mod.included}/${mod.total} files)${attention}`);
        }
        const lowModules = [...coverageResult.perModule.entries()].filter(([, m]) => m.pct < 50 && m.total > 0);
        if (lowModules.length > 0) {
          console.log('');
          console.log('  tip: modules marked "\u2190 attention needed" have <50% code-file coverage.');
          console.log('       Common causes:');
          console.log('         \u2022 token budget dropped files \u2014 raise maxTokens or use strategy:"per-module"');
          console.log('         \u2022 srcDir path includes non-code files \u2014 check .contextignore or exclude config');
          console.log('         \u2022 srcDir path is wrong \u2014 verify srcDirs in gen-context.config.json');
        }
      }
    }
    if (overBudget) console.warn(`[sigmap] WARNING: output (${finalTokens} tokens) exceeds budget (${budgetLimit || 6000})`);
  }
}

// ---------------------------------------------------------------------------
// Watch mode
// ---------------------------------------------------------------------------
function watchMode(cwd, config) {
  console.warn('[sigmap] watching for changes (Ctrl+C to stop)…');
  let debounce = null;
  for (const srcDir of config.srcDirs) {
    const abs = path.join(cwd, srcDir);
    if (!fs.existsSync(abs)) continue;
    fs.watch(abs, { recursive: true }, () => {
      if (debounce) clearTimeout(debounce);
      debounce = setTimeout(() => {
        console.warn('[sigmap] change detected, regenerating…');
        runGenerate(cwd, config, false);
      }, config.watchDebounce || 300);
    });
  }
}

// ---------------------------------------------------------------------------
// Git hook installer
// ---------------------------------------------------------------------------
function installHook(cwd, scriptPath) {
  const hookDir = path.join(cwd, '.git', 'hooks');
  if (!fs.existsSync(hookDir)) {
    console.warn('[sigmap] .git/hooks not found — skipping hook install');
    return;
  }
  const hookPath = path.join(hookDir, 'post-commit');
  const resolvedScript = path.resolve(scriptPath);
  const hookLine = `\nnode ${JSON.stringify(resolvedScript)} --generate 2>/dev/null || true\n`;

  if (fs.existsSync(hookPath)) {
    const existing = fs.readFileSync(hookPath, 'utf8');
    const existingHookLines = existing.split('\n').filter((line) => line.includes('gen-context.js'));
    if (existing.includes(hookLine.trim()) && existingHookLines.length === 1) {
      console.warn('[sigmap] post-commit hook already installed');
      return;
    }
    if (existing.includes('gen-context.js')) {
      const updated = existing
        .split('\n')
        .filter((line) => !line.includes('gen-context.js'))
        .join('\n')
        .replace(/\n+$/g, '\n');
      fs.writeFileSync(hookPath, updated.replace(/\n?$/g, '') + hookLine);
      console.warn('[sigmap] updated post-commit hook');
      return;
    }
    fs.appendFileSync(hookPath, hookLine);
  } else {
    fs.writeFileSync(hookPath, `#!/bin/sh${hookLine}`);
    fs.chmodSync(hookPath, '755');
  }
  console.warn('[sigmap] installed post-commit hook');
}

// ---------------------------------------------------------------------------
// Example config writer
// ---------------------------------------------------------------------------
function writeInitConfig(cwd) {
  const dest = path.join(cwd, 'gen-context.config.json');
  if (fs.existsSync(dest)) {
    console.warn('[sigmap] gen-context.config.json already exists — skipping');
  } else {
    const example = path.join(__dirname, 'gen-context.config.json.example');
    if (fs.existsSync(example)) {
      fs.copyFileSync(example, dest);
    } else {
      fs.writeFileSync(dest, JSON.stringify(DEFAULTS, null, 2) + '\n');
    }
    console.warn('[sigmap] wrote gen-context.config.json');
  }

  // Also scaffold .contextignore if it does not already exist
  const ignoreDest = path.join(cwd, '.contextignore');
  if (fs.existsSync(ignoreDest)) {
    console.warn('[sigmap] .contextignore already exists — skipping');
  } else {
    const ignoreContent = [
      '# SigMap ignore file — gitignore syntax',
      '# Files and directories listed here are excluded from signature extraction.',
      '# This file is union-merged with .repomixignore if present.',
      '',
      'node_modules/',
      'dist/',
      'build/',
      'out/',
      '.next/',
      'coverage/',
      'target/',
      'vendor/',
      '*.generated.*',
      '*.pb.*',
      '*_pb.*',
      '*.min.js',
      '*.min.css',
    ].join('\n') + '\n';
    fs.writeFileSync(ignoreDest, ignoreContent);
    console.warn('[sigmap] wrote .contextignore');
  }
}

// ---------------------------------------------------------------------------
// Core generate pipeline
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Strategy: per-module — one output file per top-level srcDir
// ---------------------------------------------------------------------------
function runPerModuleStrategy(cwd, config, fileEntries, inputTokenTotal) {
  // Group entries by their top-level srcDir
  const modules = {};
  for (const entry of fileEntries) {
    const rel = path.relative(cwd, entry.filePath);
    const topDir = rel.split(path.sep)[0];
    if (!modules[topDir]) modules[topDir] = [];
    modules[topDir].push(entry);
  }

  const moduleNames = Object.keys(modules).sort();
  console.warn(`[sigmap] per-module: ${moduleNames.length} modules — ${moduleNames.join(', ')}`);

  // Thin overview → primary output (always-on, tiny)
  const overviewLines = [
    '<!-- Generated by SigMap gen-context.js v' + VERSION + ' — per-module overview -->',
    '<!-- Load the full module context file for detailed signatures -->',
    '',
    '# Codebase overview',
    '',
    '| Module | Full context file |',
    '|--------|-------------------|',
  ];

  let totalOut = 0;
  for (const mod of moduleNames) {
    const outName = `context-${mod}.md`;
    const outPath = path.join(cwd, '.github', outName);
    const modEntries = modules[mod];

    // Per-module budget: each module gets its own full effective budget
    // (per-module strategy is the recommended path for large repos — no sharing needed)
    const modBudget = Math.max(1000, config.maxTokens);
    const budgeted = applyTokenBudget(modEntries, modBudget);

    const content = formatOutput(budgeted, cwd, false, config, null);
    ensureDir(outPath);
    fs.writeFileSync(outPath, content, 'utf8');
    const modTokens = estimateTokens(content);
    totalOut += modTokens;
    console.warn(`[sigmap] per-module: wrote .github/${outName} (~${modTokens} tokens, ${budgeted.length} files)`);

    overviewLines.push(`| \`${mod}\` | \`.github/${outName}\` |`);
  }

  overviewLines.push('');
  overviewLines.push('> Inject the relevant module file into your IDE context window.');
  overviewLines.push('> For cross-module questions load both files.');
  const overviewContent = overviewLines.join('\n') + '\n';
  const primaryTargets = config.outputs || ['copilot'];
  writeOutputs(overviewContent, primaryTargets, cwd, config);

  const overviewTokens = estimateTokens(overviewContent);
  console.warn(`[sigmap] per-module: overview ~${overviewTokens} tokens (always-on), modules total ~${totalOut} tokens (on-demand)`);
  return { inputTokenTotal, finalTokens: overviewTokens, fileCount: fileEntries.length, droppedCount: 0 };
}

// ---------------------------------------------------------------------------
// Strategy: hot/cold — hot injected always, cold available via MCP only
// ---------------------------------------------------------------------------
function runHotColdStrategy(cwd, config, fileEntries, recentFiles, inputTokenTotal) {
  const hotEntries = fileEntries.filter((e) => recentFiles.has(e.filePath));
  const coldEntries = fileEntries.filter((e) => !recentFiles.has(e.filePath));

  // Hot → primary output (auto-injected by IDE)
  const hotContent = hotEntries.length > 0
    ? formatOutput(hotEntries, cwd, false, config, null)
    : '<!-- Generated by SigMap — no recently changed files -->\n';
  const primaryTargets = config.outputs || ['copilot'];
  writeOutputs(hotContent, primaryTargets, cwd, config);
  const hotTokens = estimateTokens(hotContent);

  // Cold → .github/context-cold.md (MCP reads this on demand)
  const coldPath = path.join(cwd, '.github', 'context-cold.md');
  const coldHeader = [
    '<!-- Generated by SigMap gen-context.js v' + VERSION + ' — cold context (MCP only) -->',
    '<!-- NOT auto-injected. Retrieve via MCP: read_context({ module: "..." }) -->',
    '',
  ].join('\n');
  const coldContent = coldHeader + formatOutput(coldEntries, cwd, false, config, null);
  ensureDir(coldPath);
  fs.writeFileSync(coldPath, coldContent, 'utf8');
  const coldTokens = estimateTokens(coldContent);

  console.warn('[sigmap] hot-cold:');
  console.warn(`  hot  (auto-injected) : ${hotEntries.length} files ~${hotTokens} tokens → primary output`);
  console.warn(`  cold (MCP on-demand) : ${coldEntries.length} files ~${coldTokens} tokens → .github/context-cold.md`);
  if (coldEntries.length > 0) {
    console.warn('  retrieve cold: read_context({ module: "<dir>" }) via MCP');
  }

  return { inputTokenTotal, finalTokens: hotTokens, fileCount: fileEntries.length, droppedCount: 0 };
}

// ---------------------------------------------------------------------------
// Diff-mode pipeline — context for changed files only
// ---------------------------------------------------------------------------
function runDiff(cwd, config, stagedOnly, baseRef) {
  const diffFiles = baseRef ? getFilesChangedSinceBase(cwd, baseRef) : getDiffFiles(cwd, stagedOnly);

  if (diffFiles.size === 0) {
    const scope = baseRef ? `base ${baseRef}` : (stagedOnly ? 'staged' : 'working tree');
    console.warn(`[sigmap] --diff: no changed files found in ${scope} — running full generate`);
    runGenerate(cwd, config, false);
    return;
  }

  const ignorePatterns = loadIgnorePatterns(cwd);
  let allFiles = buildFileList(cwd, config);
  allFiles = allFiles.filter((f) => {
    const rel = path.relative(cwd, f).replace(/\\/g, '/');
    return !matchesIgnore(rel, ignorePatterns);
  });

  // Restrict to diff files, keeping only those already in srcDirs
  const diffFiltered = allFiles.filter((f) => diffFiles.has(f));

  if (diffFiltered.length === 0) {
    console.warn('[sigmap] --diff: changed files are outside tracked srcDirs — running full generate');
    runGenerate(cwd, config, false);
    return;
  }

  let inputTokenTotal = 0;
  let fileEntries = [];
  let testIndex = null;
  if (config.testCoverage) {
    try {
      const { buildTestIndex } = requireSourceOrBundled('./src/extractors/coverage');
      testIndex = buildTestIndex(cwd, config.testDirs);
    } catch (err) {
      console.warn(`[sigmap] coverage index failed: ${err.message}`);
    }
  }

  for (const filePath of diffFiltered) {
    let content = '';
    try {
      content = fs.readFileSync(filePath, 'utf8');
    } catch (_) {
      continue;
    }

    let sigs = detectAndExtract(filePath, content, config.maxSigsPerFile);
    if (sigs.length === 0) continue;

    inputTokenTotal += estimateTokens(content);

    if (config.secretScan) {
      const { scan } = requireSourceOrBundled('./src/security/scanner');
      const result = scan(sigs, filePath);
      if (result.redacted) {
        console.warn(`[sigmap] secrets redacted in ${path.relative(cwd, filePath)}`);
      }
      sigs = result.safe;
    }

    sigs = annotateCoverage(sigs, testIndex, !!config.testCoverage);

    fileEntries.push({ filePath, sigs, deps: extractFileDeps(filePath, content, config), content, mtime: Date.now() });
  }

  if (fileEntries.length === 0) {
    console.warn('[sigmap] --diff: no extractable signatures in changed files — running full generate');
    runGenerate(cwd, config, false);
    return;
  }

  const routingEnabled = !!(config.routing || process.argv.includes('--routing'));
  const diffSection = baseRef ? buildDiffSectionFromBase(cwd, baseRef, fileEntries, config) : [];
  const content = formatOutput(fileEntries, cwd, routingEnabled, config, { diffSection });
  const finalTokens = estimateTokens(content);
  writeOutputs(content, config.outputs, cwd, config);

  const scope = baseRef ? `diff-vs-${baseRef}` : (stagedOnly ? 'staged' : 'diff');
  console.warn(`[sigmap] ${scope} files: ${fileEntries.length}, diff tokens: ~${finalTokens}`);

  // v4.0: risk score per changed file
  try {
    const { buildFromCwd } = requireSourceOrBundled('./src/graph/builder');
    const graph = buildFromCwd(cwd, { silent: true });
    const reverseGraph = graph.reverse || new Map();

    function _isRouteFile(f) {
      return /\/(routes?|pages?|controllers?|handlers?|api)\//i.test(f)
        || /\.(route|page|controller|handler)\.\w+$/.test(f);
    }
    function _riskScore(filePath, sigs, revGraph) {
      let s = 0;
      if (sigs.some(sig => sig.includes('export') || sig.includes('module.exports'))) s += 2;
      const deps = revGraph.get(filePath) || new Set();
      if (deps.size > 3) s += 2;
      if (_isRouteFile(filePath)) s += 1;
      if (/\.(config|env|settings)\.\w+$/.test(filePath)) s += 1;
      return s >= 4 ? 'HIGH' : s >= 2 ? 'MEDIUM' : 'LOW';
    }

    console.warn(`[sigmap] Risk: Changed files (${fileEntries.length}):`);
    for (const fe of fileEntries) {
      const level = _riskScore(fe.filePath, fe.sigs, reverseGraph);
      const rel   = path.relative(cwd, fe.filePath).replace(/\\/g, '/');
      const deps  = reverseGraph.get(fe.filePath) || new Set();
      const reasons = [];
      if (fe.sigs.some(s => s.includes('export') || s.includes('module.exports'))) reasons.push('exports public API');
      if (deps.size > 0) reasons.push(`${deps.size} downstream dependent${deps.size === 1 ? '' : 's'}`);
      if (_isRouteFile(fe.filePath)) reasons.push('route file');
      if (/\.(config|env|settings)\.\w+$/.test(fe.filePath)) reasons.push('config file');
      const label = level === 'HIGH' ? '[HIGH]  ' : level === 'MEDIUM' ? '[MEDIUM]' : '[LOW]   ';
      console.warn(`  ${rel.padEnd(40)} ${label}${reasons.length ? '  — ' + reasons.join(', ') : ''}`);
    }
  } catch (_) {}

  if (process.argv.includes('--report')) {
    // Also show what the full run would cost for comparison
    const fullResult = runGenerate(cwd, config, true);
    console.log(`[sigmap] diff report:`);
    console.log(`  mode            : ${scope}`);
    console.log(`  diff files      : ${fileEntries.length}`);
    console.log(`  diff tokens     : ~${finalTokens}`);
    console.log(`  full tokens     : ~${fullResult.finalTokens}`);
    console.log(`  savings         : ~${fullResult.finalTokens - finalTokens} tokens`);
  }
}

// ---------------------------------------------------------------------------
// Core generate pipeline
// ---------------------------------------------------------------------------
function runGenerate(cwd, config, reportMode, reportJson = false) {
  const ignorePatterns = loadIgnorePatterns(cwd);
  let allFiles = buildFileList(cwd, config);

  // Apply .contextignore
  allFiles = allFiles.filter((f) => {
    const rel = path.relative(cwd, f).replace(/\\/g, '/');
    return !matchesIgnore(rel, ignorePatterns);
  });

  // Gather mtime and git-committed info
  const hotCommits = config.hotCommits || 10;
  const recentFiles = config.diffPriority ? getRecentlyCommittedFiles(cwd, hotCommits) : new Set();

  // v6.7: Load signature cache if enabled
  let cache = null;
  const { loadCache, saveCache, getChangedFiles, updateCacheEntries } = requireSourceOrBundled('./src/cache/sig-cache');
  if (config.sigCache) {
    cache = loadCache(cwd, VERSION);
  }

  let inputTokenTotal = 0;
  let fileEntries = [];
  let testIndex = null;
  if (config.testCoverage) {
    try {
      const { buildTestIndex } = requireSourceOrBundled('./src/extractors/coverage');
      testIndex = buildTestIndex(cwd, config.testDirs);
    } catch (err) {
      console.warn(`[sigmap] coverage index failed: ${err.message}`);
    }
  }

  for (const filePath of allFiles) {
    let content = '';
    try {
      content = fs.readFileSync(filePath, 'utf8');
    } catch (_) {
      continue;
    }

    // v6.7: Check cache before extracting
    let sigs = [];
    if (cache) {
      const cached = cache.get(filePath);
      let mtime = 0;
      try { mtime = fs.statSync(filePath).mtimeMs; } catch (_) {}
      if (cached && cached.mtime === mtime) {
        sigs = cached.sigs;
      } else {
        sigs = detectAndExtract(filePath, content, config.maxSigsPerFile);
        if (sigs.length > 0) {
          cache.set(filePath, { mtime, sigs });
        }
      }
    } else {
      sigs = detectAndExtract(filePath, content, config.maxSigsPerFile);
    }
    if (sigs.length === 0) continue;

    // Baseline = estimated tokens of original source content for intuitive reduction stats.
    inputTokenTotal += estimateTokens(content);

    if (config.secretScan) {
      const { scan } = requireSourceOrBundled('./src/security/scanner');
      const result = scan(sigs, filePath);
      if (result.redacted) {
        console.warn(`[sigmap] secrets redacted in ${path.relative(cwd, filePath)}`);
      }
      sigs = result.safe;
    }

    sigs = annotateCoverage(sigs, testIndex, !!config.testCoverage);

    let mtime = 0;
    try {
      mtime = fs.statSync(filePath).mtimeMs;
    } catch (_) {}

    // Boost recently committed files (give them max mtime so they aren't dropped first)
    if (recentFiles.has(filePath)) mtime = Date.now();

    fileEntries.push({ filePath, sigs, deps: extractFileDeps(filePath, content, config), content, mtime });
  }

  // v6.5: Coverage feedback loop — warn if symbol density is suspiciously low
  const symbolsFound = fileEntries.reduce((n, f) => n + (f.sigs?.length || 0), 0);
  const filesFound   = fileEntries.length;
  const coverageRatio = filesFound > 0 ? symbolsFound / filesFound : 1;
  if (coverageRatio < 0.25 && !config.srcDirs?.length) {
    process.stderr.write('[sigmap] ⚠ low symbol coverage — source roots may be wrong\n');
    process.stderr.write('[sigmap]   run: sigmap roots --explain\n');
  }

  const strategy = config.strategy || 'full';
  const beforeCount = fileEntries.length;

  // Sort for output ordering: recently committed files appear first
  if (config.diffPriority && recentFiles.size > 0) {
    fileEntries.sort((a, b) => {
      const aRecent = recentFiles.has(a.filePath) ? 0 : 1;
      const bRecent = recentFiles.has(b.filePath) ? 0 : 1;
      return aRecent - bRecent;
    });
  }

  // v4.1: compute effective budget once; used by all strategies
  const effectiveMaxTokens = computeEffectiveMaxTokens(fileEntries, config);
  // Propagate to config so per-module / hot-cold strategies pick it up
  const configWithBudget = effectiveMaxTokens !== config.maxTokens
    ? Object.assign({}, config, { maxTokens: effectiveMaxTokens, _autoMaxTokens: effectiveMaxTokens })
    : config;

  let result;
  if (!reportMode) {
    if (strategy === 'per-module') {
      result = runPerModuleStrategy(cwd, configWithBudget, fileEntries, inputTokenTotal);
    } else if (strategy === 'hot-cold') {
      result = runHotColdStrategy(cwd, configWithBudget, fileEntries, recentFiles, inputTokenTotal);
    } else {
      // 'full' — original behaviour
      fileEntries = applyTokenBudget(fileEntries, effectiveMaxTokens);
      const droppedCount = beforeCount - fileEntries.length;
      const routingEnabled = !!(config.routing || process.argv.includes('--routing'));
      const content = formatOutput(fileEntries, cwd, routingEnabled, config, null);
      const finalTokens = estimateTokens(content);
      const formatIdx = process.argv.indexOf('--format');
      const formatValue = formatIdx >= 0 ? process.argv[formatIdx + 1] : (config.format || 'default');
      // Feature 2: --mode write matrix
      const activeMode = process.argv.indexOf('--mode') >= 0
        ? process.argv[process.argv.indexOf('--mode') + 1]
        : (config.mode || 'default');
      if (activeMode === 'fast') {
        const llmTxtMod = requireSourceOrBundled('./src/format/llm-txt');
        const syncContext = { projectName: path.basename(cwd), fileEntries, srcDirs: config.srcDirs || [] };
        fs.writeFileSync(llmTxtMod.outputPath(cwd), llmTxtMod.format(syncContext, cwd, VERSION));
        console.warn(`[sigmap] --mode fast → llm.txt`);
      } else if (activeMode === 'full') {
        const llmFullMod = requireSourceOrBundled('./packages/adapters/llm-full');
        const syncContext = { projectName: path.basename(cwd), fileEntries, srcDirs: config.srcDirs || [] };
        llmFullMod.write(syncContext, cwd, { version: VERSION });
        console.warn(`[sigmap] --mode full → llm-full.txt`);
      } else if (activeMode === 'both') {
        const llmTxtMod  = requireSourceOrBundled('./src/format/llm-txt');
        const llmFullMod = requireSourceOrBundled('./packages/adapters/llm-full');
        const llmsMod    = requireSourceOrBundled('./src/format/llms-txt');
        const syncContext = { projectName: path.basename(cwd), fileEntries, srcDirs: config.srcDirs || [] };
        fs.writeFileSync(llmTxtMod.outputPath(cwd),  llmTxtMod.format(syncContext, cwd, VERSION));
        llmFullMod.write(syncContext, cwd, { version: VERSION });
        writeOutputs(content, config.outputs, cwd, config);
        const llmsContent = llmsMod.format(syncContext, cwd, [
          { path: llmTxtMod.outputPath(cwd),  label: 'llm.txt' },
          { path: llmFullMod.outputPath(cwd), label: 'llm-full.txt' },
        ], VERSION);
        fs.writeFileSync(llmsMod.outputPath(cwd), llmsContent);
        console.warn(`[sigmap] --mode both → copilot-instructions.md + llm.txt + llm-full.txt + llms.txt`);
      } else {
        writeOutputs(content, config.outputs, cwd, config);
      }
      if (formatValue === 'cache') writeCacheOutput(content, cwd);

      // --output <file>: write a copy of the formatted context to the custom path.
      if (config.customOutput) {
        try {
          const absCustom = path.resolve(cwd, config.customOutput);
          const SIGMAP_HEADER = [
            `<!-- Generated by SigMap v${VERSION} -->`,
            `<!-- Updated: ${new Date().toISOString()} -->`,
            `<!-- Regenerate: node gen-context.js --output ${config.customOutput} -->`,
            '',
          ].join('\n');
          fs.mkdirSync(path.dirname(absCustom), { recursive: true });
          fs.writeFileSync(absCustom, SIGMAP_HEADER + content, 'utf8');
          console.warn(`[sigmap] wrote ${path.relative(cwd, absCustom)} (custom output)`);
        } catch (err) {
          console.warn(`[sigmap] --output write failed: ${err.message}`);
        }
      }

      result = { inputTokenTotal, finalTokens, fileCount: beforeCount, droppedCount };
    }
  } else {
    // report mode: always run full pipeline for accurate stats
    const budgeted = applyTokenBudget([...fileEntries], effectiveMaxTokens);
    const droppedCount = beforeCount - budgeted.length;
    const content = formatOutput(budgeted, cwd, false, configWithBudget, null);
    const finalTokens = estimateTokens(content);
    // v4.0: compute coverage score for --report heatmap
    let coverageResult = null;
    try {
      const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
      coverageResult = coverageScore(cwd, budgeted, configWithBudget);
    } catch (_) {}
    result = { inputTokenTotal, finalTokens, fileCount: beforeCount, droppedCount, coverageResult };
  }

  if (reportMode || process.argv.includes('--report')) {
    printReport(result.inputTokenTotal, result.finalTokens, result.fileCount, result.droppedCount, reportJson, effectiveMaxTokens, result.coverageResult, config.autoMaxTokens !== false && effectiveMaxTokens !== config.maxTokens, config.maxTokens);
  }

  // Usage tracking (v0.9) — optional append-only NDJSON log
  const trackingEnabled = !!(config.tracking || process.argv.includes('--track'));
  if (trackingEnabled && !reportMode) {
    try {
      const { logRun } = requireSourceOrBundled('./src/tracking/logger');
      logRun({
        version: VERSION,
        fileCount: result.fileCount,
        droppedCount: result.droppedCount,
        rawTokens: result.inputTokenTotal,
        finalTokens: result.finalTokens,
        overBudget: result.finalTokens > effectiveMaxTokens,
        budgetLimit: effectiveMaxTokens,
        autoBudget: config.autoMaxTokens !== false && effectiveMaxTokens !== config.maxTokens,
      }, cwd);
    } catch (err) {
      console.warn(`[sigmap] tracking: ${err.message}`);
    }
  }

  // Feature 8: post-run summary — 6-line stdout after every normal run
  if (!reportMode
    && !process.argv.includes('--json')
    && !process.argv.includes('--report')
    && !process.argv.includes('--quiet')) {
    const bar  = '\u2500'.repeat(43);
    const syms = fileEntries.reduce((n, f) => n + (f.sigs ? f.sigs.length : 0), 0);
    const pct  = result.inputTokenTotal > 0
      ? Math.round((1 - result.finalTokens / result.inputTokenTotal) * 100)
      : 0;
    // v4.0: coverage score in post-run summary
    let coverageLine = '';
    try {
      const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
      const cov = coverageScore(cwd, fileEntries, configWithBudget);
      const autoBudgetNote = (config.autoMaxTokens !== false && effectiveMaxTokens !== config.maxTokens)
        ? `  [budget: ${effectiveMaxTokens} auto-scaled]`
        : '';
      coverageLine = ` Coverage       : ${cov.grade} (${cov.score}%)  \u2014 ${cov.included} of ${cov.total} source files included${autoBudgetNote}`;
      // Extra warning line when coverage is still poor despite auto-scaling
      if (cov.score < 40 && config.strategy !== 'per-module' && config.strategy !== 'hot-cold') {
        coverageLine += '\n [sigmap] tip: large repo — consider strategy:"per-module" for full coverage';
      }
    } catch (_) {}
    const lines = [
      bar,
      ` SigMap v${VERSION}`,
      ` Files scanned  : ${result.fileCount}`,
      ` Symbols found  : ${syms.toLocaleString()}`,
      ` Token reduction: ${pct}%  (${result.inputTokenTotal.toLocaleString()} \u2192 ${result.finalTokens.toLocaleString()})`,
    ];
    if (coverageLine) lines.push(coverageLine);
    lines.push(` Output         : .github/copilot-instructions.md`);
    lines.push(bar);
    lines.push(` Try: "explain the architecture" \u00b7 "find the auth module"`);
    lines.push(bar, '');
    process.stderr.write(lines.join('\n'));
  }

  // v6.7: Save cache if enabled
  if (config.sigCache && cache) {
    try {
      saveCache(cwd, VERSION, cache);
    } catch (err) {
      console.warn(`[sigmap] cache save failed: ${err.message}`);
    }
  }

  return result;
}

// ---------------------------------------------------------------------------
// Monorepo support
// ---------------------------------------------------------------------------
const MONO_ROOTS  = ['packages', 'apps', 'services', 'libs'];
const PKG_MANIFESTS = ['package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod', 'build.gradle', 'pom.xml', 'requirements.txt'];

function detectMonorepoPackages(cwd, config) {
  const packages = [];
  const seen = new Set();

  const addIfPackage = (pkgPath) => {
    if (seen.has(pkgPath)) return;
    seen.add(pkgPath);
    for (const manifest of PKG_MANIFESTS) {
      if (fs.existsSync(path.join(pkgPath, manifest))) {
        packages.push(pkgPath);
        return;
      }
    }
  };

  // Strategy 1: classic JS monorepo roots (packages/, apps/, services/, libs/)
  for (const monoDir of MONO_ROOTS) {
    const abs = path.join(cwd, monoDir);
    if (!fs.existsSync(abs)) continue;
    let entries;
    try { entries = fs.readdirSync(abs, { withFileTypes: true }); } catch (_) { continue; }
    for (const entry of entries) {
      if (!entry.isDirectory()) continue;
      addIfPackage(path.join(abs, entry.name));
    }
  }

  // Strategy 2: treat each srcDir that has a manifest as its own package
  // (covers server/web/desktop-style layouts)
  if (packages.length === 0 && config && config.srcDirs) {
    for (const srcDir of config.srcDirs) {
      const abs = path.join(cwd, srcDir);
      if (!fs.existsSync(abs)) continue;
      addIfPackage(abs);
    }
  }

  return packages;
}

function runMonorepo(cwd, config) {
  const packages = detectMonorepoPackages(cwd, config);
  if (packages.length === 0) {
    console.warn('[sigmap] no monorepo packages found — checked packages/, apps/, services/, libs/');
    return;
  }
  console.warn(`[sigmap] monorepo: found ${packages.length} packages`);

  for (const pkgPath of packages) {
    const pkgName = path.relative(cwd, pkgPath);
    console.warn(`[sigmap] monorepo: processing ${pkgName}`);
    // Per-package config: scan src/ and package root, inherit outputs from parent config
    const pkgConfig = {
      ...config,
      srcDirs: ['src', 'lib', 'app', '.'],
    };
    try {
      runGenerate(pkgPath, pkgConfig, false);
    } catch (err) {
      console.warn(`[sigmap] monorepo: failed for ${pkgName}: ${err.message}`);
    }
  }
  console.warn(`[sigmap] monorepo: done — wrote context files for ${packages.length} packages`);
}

// ---------------------------------------------------------------------------
// Multi-repo support  (--each)
// Run sigmap independently for every immediate subdirectory that looks like
// an independent git repo or project root.  Each sub-repo gets its own
// context files written inside it, using its own gen-context.config.json
// when present.
// ---------------------------------------------------------------------------
function detectRepoDirs(cwd) {
  let entries;
  try {
    entries = fs.readdirSync(cwd, { withFileTypes: true });
  } catch (_) {
    return [];
  }
  const repos = [];
  for (const entry of entries) {
    if (!entry.isDirectory()) continue;
    if (entry.name.startsWith('.')) continue;
    const abs = path.join(cwd, entry.name);
    // A directory qualifies if it has its own .git folder
    // OR contains a recognised project manifest (fallback for un-initialised repos)
    const hasGit = fs.existsSync(path.join(abs, '.git'));
    const hasManifest = PKG_MANIFESTS.some((m) => fs.existsSync(path.join(abs, m)));
    if (hasGit || hasManifest) repos.push(abs);
  }
  return repos;
}

function runEach(cwd, baseConfig, adapterOverride) {
  const repos = detectRepoDirs(cwd);
  if (repos.length === 0) {
    console.warn('[sigmap] --each: no project subdirectories found');
    console.warn('[sigmap]   A subdirectory qualifies when it contains .git or a package manifest');
    console.warn(`[sigmap]   (package.json / pyproject.toml / Cargo.toml / go.mod / etc.)`);
    return;
  }
  console.warn(`[sigmap] --each: found ${repos.length} repos — ${repos.map((r) => path.basename(r)).join(', ')}`);
  let ok = 0, failed = 0;
  for (const repoDir of repos) {
    const name = path.basename(repoDir);
    // Load each repo's own config independently, fall back to base config
    let repoConfig;
    try {
      repoConfig = loadConfig(repoDir);
    } catch (_) {
      repoConfig = { ...baseConfig };
    }
    // --adapter override: apply on top of per-repo config
    if (adapterOverride) {
      repoConfig = Object.assign({}, repoConfig, {
        outputs: adapterOverride === 'claude' ? ['claude'] : [adapterOverride],
        adapters: [adapterOverride],
      });
    }
    console.warn(`[sigmap] --each: processing ${name} …`);
    try {
      runGenerate(repoDir, repoConfig, false);
      ok++;
    } catch (err) {
      console.warn(`[sigmap] --each: FAILED for ${name}: ${err.message}`);
      failed++;
    }
  }
  console.warn(`[sigmap] --each: done — ${ok} succeeded${failed > 0 ? `, ${failed} failed` : ''}`);
}

// ---------------------------------------------------------------------------
// CLI entry point
// ---------------------------------------------------------------------------
/**
 * Classify a task description and return the recommended model tier.
 *
 * Rules (ordered):
 *  1. Match any 'powerful' keyword → 'powerful'
 *  2. Match any 'fast' keyword     → 'fast'
 *  3. Default                      → 'balanced'
 *
 * @param {string} description - Natural-language task description
 * @returns {{ tier: string, label: string, models: string, costHint: string }}
 */
function suggestTool(description) {
  const lower = description.toLowerCase();
  const { TIERS } = __require('./src/routing/hints');

  const powerfulKeywords = [
    'architecture', 'cross-cutting', 'multi-file', 'security audit', 'owasp',
    'migration plan', 'framework upgrade', 'complex debug', 'async boundar',
    'multi-service', 'distributed system', 'performance audit', 'new module from',
    'redesign', 'from requirements',
  ];
  const fastKeywords = [
    'typo', 'rename symbol', 'format', 'lint', 'style change', 'trivial',
    'quick fix', 'inline suggest', 'autocomplete', 'config file',
    'dockerfile', 'shell script', '.yaml', '.yml', '.json', '.css',
    'html template', 'markup',
  ];

  let tier = 'balanced';
  if (powerfulKeywords.some((kw) => lower.includes(kw))) tier = 'powerful';
  else if (fastKeywords.some((kw) => lower.includes(kw))) tier = 'fast';

  const info = TIERS[tier];
  return { tier, label: info.label, models: info.examples, costHint: info.costHint };
}

function resolveProjectRoot(startDir) {
  try {
    const gitRoot = execSync('git rev-parse --show-toplevel', {
      cwd: startDir,
      encoding: 'utf8',
      stdio: ['ignore', 'pipe', 'ignore'],
    }).trim();
    if (gitRoot) return gitRoot;
  } catch (_) {}
  return startDir;
}

function detectInvokedAs() {
  const argv1 = process.argv[1] || '';
  const base = path.basename(argv1);
  const baseNoExt = base.endsWith('.js') ? base.slice(0, -3) : base;
  // npx: the script path goes through an _npx cache directory
  if (argv1.includes('/_npx/') || argv1.includes('\\_npx\\') ||
      argv1.includes('/.npm/_') || argv1.includes('\\.npm\\_')) {
    return 'npx sigmap';
  }
  // globally-installed bin aliases (no .js extension)
  if (baseNoExt === 'sigmap') return 'sigmap';
  if (baseNoExt === 'gen-context') return 'gen-context';
  // default: local file invocation
  return 'node gen-context.js';
}

function printHelp(cmd) {
  cmd = cmd || 'node gen-context.js';
  const header = cmd === 'node gen-context.js'
    ? `SigMap — gen-context.js v${VERSION}`
    : `SigMap v${VERSION}  (${cmd})`;
  console.log(`
${header}
Zero-dependency AI context engine

Usage:
  ${cmd}                                   Generate context once and exit
  ${cmd} --monorepo                        Generate per-package context (monorepo)
  ${cmd} --each                            Run for every repo in the current directory
  ${cmd} --routing                         Include model routing hints in output
  ${cmd} --format cache                    Also write Anthropic prompt-cache JSON
  ${cmd} --track                           Append run metrics to .context/usage.ndjson
  ${cmd} --watch                           Generate + watch for file changes
  ${cmd} --setup                           Generate + install git hook + watch
  ${cmd} --mcp                             Start MCP server on stdio
  ${cmd} --report                          Token reduction stats to stdout
  ${cmd} --report --json                   Token report as JSON (for CI; exits 1 if over budget)
  ${cmd} --report --history                Print usage log summary from .context/usage.ndjson
  ${cmd} --report --history --chart        Include inline SVG charts + Unicode sparklines
  ${cmd} --dashboard                       Write benchmarks/reports/dashboard.html
  ${cmd} --suggest-tool "<task>"           Recommend model tier for a task description
  ${cmd} --suggest-tool "<task>" --json    Machine-readable tier recommendation
  ${cmd} --health                          Print composite health score
  ${cmd} --health --json                   Machine-readable health score
  ${cmd} --diff                            Generate context for git-changed files only
  ${cmd} --diff <base-ref>                 Generate context + structural diff vs base ref (e.g. main)
  ${cmd} --diff --staged                   Generate context for staged files only
  ${cmd} --benchmark                       Run retrieval benchmark (benchmarks/tasks/retrieval.jsonl)
  ${cmd} --adapter <name>                  Generate for a specific adapter only (v3.0+)
  ${cmd} --adapter <name> --json           Show adapter output path as JSON
  ${cmd} --benchmark --json                Benchmark results as JSON
  ${cmd} --eval                            Alias for --benchmark
  ${cmd} --analyze                         Per-file breakdown: sigs, tokens, extractor, coverage
  ${cmd} --analyze --json                  Breakdown as JSON
  ${cmd} --analyze --slow                  Re-time each extractor; flag files >50ms
  ${cmd} --diagnose-extractors             Run all 21 extractors vs fixtures; show pass/fail + diff
  ${cmd} --query "<text>"                  Rank files by relevance to a query
  ${cmd} --query "<text>" --json           Ranked results as JSON
  ${cmd} --query "<text>" --top <n>        Limit results to top N files (default 10)
  ${cmd} learn --good <files...>           Boost files in .context/weights.json
  ${cmd} learn --bad <files...>            Penalize files in .context/weights.json
  ${cmd} learn --reset                     Delete learned file weights
  ${cmd} weights                           Show learned file multipliers
  ${cmd} weights --json                    Learned weights as JSON
  ${cmd} --impact <file>                   Show every file impacted by changing <file>
  ${cmd} --impact <file> --json            Impact as JSON {changed, direct, transitive, tests, routes}
  ${cmd} --impact <file> --depth <n>       BFS depth limit (default 3, 0=unlimited)
  ${cmd} --init                            Write example config + .contextignore scaffold
  ${cmd} --help                            Show this message
  ${cmd} --version                         Show version

Strategies (set via config "strategy" key):
  "full"        Single file, all signatures. Works everywhere. (default)
  "per-module"  One .github/context-<module>.md per srcDir + thin overview.
                ~70% fewer tokens per question. No MCP needed.
  "hot-cold"    Hot (recently changed) auto-injected; cold in .github/context-cold.md
                ~90% fewer tokens. Best with MCP (Claude Code, Cursor).
                Set "hotCommits": N to control how many commits count as hot (default 10).

Adapters (v3.0+): copilot | claude | cursor | windsurf | openai | gemini | codex
  Set "adapters": ["copilot","openai","codex"] in config to write multiple adapter outputs.
  Old "outputs" config key is still accepted (maps to adapters automatically).

Config: gen-context.config.json
Ignore: .contextignore, .repomixignore
Output: .github/copilot-instructions.md (default)
`);
}

// ---------------------------------------------------------------------------
// MCP auto-registration
// ---------------------------------------------------------------------------
function _displayPath(p, cwd) {
  return p.startsWith(os.homedir()) ? '~' + p.slice(os.homedir().length) : path.relative(cwd, p);
}

function _registerMcpJson(filePath, serverEntry, cwd) {
  if (!fs.existsSync(filePath)) return;
  try {
    const raw = fs.readFileSync(filePath, 'utf8');
    const settings = JSON.parse(raw);
    if (!settings.mcpServers) settings.mcpServers = {};
    if (settings.mcpServers['sigmap']) return;
    settings.mcpServers['sigmap'] = serverEntry;
    fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + '\n');
    console.warn(`[sigmap] registered MCP server in ${_displayPath(filePath, cwd)}`);
  } catch (err) {
    console.warn(`[sigmap] could not update ${_displayPath(filePath, cwd)}: ${err.message}`);
  }
}

function _registerCodexYaml(filePath, scriptPath) {
  if (!fs.existsSync(filePath)) return;
  try {
    const raw = fs.readFileSync(filePath, 'utf8');
    if (raw.includes('sigmap')) return; // already registered
    const yamlBlock = [
      '',
      'mcpServers:',
      '  sigmap:',
      `    command: node`,
      `    args:`,
      `      - ${path.resolve(scriptPath)}`,
      `      - --mcp`,
    ].join('\n');
    fs.writeFileSync(filePath, raw.trimEnd() + yamlBlock + '\n');
    console.warn('[sigmap] registered MCP server in ~/.codex/config.yaml');
  } catch (err) {
    console.warn(`[sigmap] could not update ~/.codex/config.yaml: ${err.message}`);
  }
}

function registerMcp(cwd, scriptPath) {
  const serverEntry = {
    command: 'node',
    args: [path.resolve(scriptPath), '--mcp'],
  };

  // JSON mcpServers targets: Claude, Cursor, Windsurf project, Windsurf global,
  // VS Code (GitHub Copilot 1.99+), OpenCode project, OpenCode global, Gemini CLI
  const jsonTargets = [
    path.join(cwd, '.claude', 'settings.json'),
    path.join(cwd, '.cursor', 'mcp.json'),
    path.join(cwd, '.windsurf', 'mcp.json'),
    path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json'),
    path.join(cwd, '.vscode', 'mcp.json'),
    path.join(cwd, 'opencode.json'),
    path.join(os.homedir(), '.config', 'opencode', 'config.json'),
    path.join(os.homedir(), '.gemini', 'settings.json'),
  ];

  for (const target of jsonTargets) {
    _registerMcpJson(target, serverEntry, cwd);
  }

  // Zed uses context_servers (different shape from mcpServers)
  const zedSettingsPath = path.join(os.homedir(), '.config', 'zed', 'settings.json');
  if (fs.existsSync(zedSettingsPath)) {
    try {
      const raw      = fs.readFileSync(zedSettingsPath, 'utf8');
      const settings = JSON.parse(raw);
      if (!settings.context_servers) settings.context_servers = {};
      if (!settings.context_servers['sigmap']) {
        settings.context_servers['sigmap'] = {
          command: { path: 'node', args: [path.resolve(scriptPath), '--mcp'] },
        };
        fs.writeFileSync(zedSettingsPath, JSON.stringify(settings, null, 2) + '\n');
        console.warn('[sigmap] registered context server in ~/.config/zed/settings.json');
      }
    } catch (err) {
      console.warn(`[sigmap] could not update ~/.config/zed/settings.json: ${err.message}`);
    }
  }

  // Codex CLI uses YAML (~/.codex/config.yaml)
  _registerCodexYaml(path.join(os.homedir(), '.codex', 'config.yaml'), scriptPath);

  // Print manual snippets for all targets
  console.warn('[sigmap] MCP / context server config snippets:');
  console.warn('  Claude / Cursor / Windsurf / VS Code / OpenCode / Gemini CLI:');
  console.warn(JSON.stringify({ mcpServers: { sigmap: serverEntry } }, null, 2));
  console.warn('  Zed (~/.config/zed/settings.json):');
  console.warn(JSON.stringify({ context_servers: { sigmap: { command: { path: 'node', args: [path.resolve(scriptPath), '--mcp'] } } } }, null, 2));
  console.warn('  Codex CLI (~/.codex/config.yaml):');
  console.warn([
    'mcpServers:',
    '  sigmap:',
    `    command: node`,
    `    args:`,
    `      - ${path.resolve(scriptPath)}`,
    `      - --mcp`,
  ].join('\n'));
}

// ---------------------------------------------------------------------------
// v4.2 helpers
// ---------------------------------------------------------------------------
const MODEL_COSTS = {
  'gpt-4':             0.030,
  'gpt-4o':            0.005,
  'gpt-4o-mini':       0.000150,
  'claude-3-5-sonnet': 0.003,
  'claude-3-haiku':    0.00025,
  'claude-opus-4':     0.015,
  'gemini-1.5-pro':    0.00125,
};

function buildMiniContext(ranked, cwd) {
  const lines = ['# SigMap Query Context', `Generated: ${new Date().toISOString()}`, ''];
  for (const { file, sigs } of ranked) {
    lines.push(`## ${file}`, '```', ...sigs.slice(0, 20), '```', '');
  }
  return lines.join('\n');
}

function computeCurrentRisk(cwd) {
  try {
    const { execSync } = require('child_process');
    const out = execSync('git diff --name-only HEAD', { cwd, timeout: 3000, encoding: 'utf8' });
    const count = out.trim().split('\n').filter(Boolean).length;
    if (count === 0) return 'NONE';
    if (count <= 3)  return 'LOW';
    if (count <= 10) return 'MEDIUM';
    return 'HIGH';
  } catch (_) { return 'UNKNOWN'; }
}

function getRawTokenCount(cwd, config) {
  let total = 0;
  const files = buildFileList(cwd, config);
  for (const fp of files) {
    try { total += estimateTokens(fs.readFileSync(fp, 'utf8')); } catch (_) {}
  }
  return total;
}

function getIntentWeights(intent) {
  const { DEFAULT_WEIGHTS } = requireSourceOrBundled('./src/retrieval/ranker');
  const base = Object.assign({}, DEFAULT_WEIGHTS);
  if (intent === 'debug')    return Object.assign({}, base, { recencyBoost: base.recencyBoost * 1.5 });
  if (intent === 'explain')  return Object.assign({}, base, { symbolMatch:  base.symbolMatch  * 1.5 });
  if (intent === 'refactor') return Object.assign({}, base, { pathMatch:    base.pathMatch    * 1.5 });
  if (intent === 'review')   return Object.assign({}, base, { exactToken:   base.exactToken   * 1.3 });
  return base;
}

function extractQuerySymbols(query) {
  return (query.match(/\b[A-Z][a-zA-Z]+|[a-z]+(?:[A-Z][a-z]+)+\b/g) || []);
}

function collectLearnFiles(args, flag, cwd) {
  const idx = args.indexOf(flag);
  if (idx < 0) return { rawCount: 0, files: [], warnings: [] };

  const { normalizeFile } = requireSourceOrBundled('./src/learning/weights');
  const seen = new Set();
  const files = [];
  const warnings = [];
  let rawCount = 0;

  for (let i = idx + 1; i < args.length; i++) {
    const value = args[i];
    if (!value || value.startsWith('--')) break;
    rawCount++;
    const normalized = normalizeFile(cwd, value);
    if (!normalized) {
      warnings.push(`${value} is outside the repo`);
      continue;
    }
    if (!fs.existsSync(path.join(cwd, normalized))) {
      warnings.push(`${normalized} does not exist`);
      continue;
    }
    if (!seen.has(normalized)) {
      seen.add(normalized);
      files.push(normalized);
    }
  }

  return { rawCount, files, warnings };
}

function main() {
  const args = process.argv.slice(2);

  // Feature 1: `sigmap run` alias — strip positional 'run' so existing logic handles it
  if (args[0] === 'run' && !args[0].startsWith('--')) {
    args.shift();
  }

  const invokedFrom = process.cwd();

  // --cwd <dir>: restrict scanning to that directory instead of the project root
  const cwdFlagIdx = args.indexOf('--cwd');
  const cwdFlag = cwdFlagIdx !== -1 ? (args[cwdFlagIdx + 1] || '').trim() : null;
  if (cwdFlag !== null && (!cwdFlag || cwdFlag.startsWith('--'))) {
    console.error('[sigmap] --cwd requires a directory path');
    process.exit(1);
  }
  const cwd = cwdFlag
    ? path.resolve(invokedFrom, cwdFlag)
    : resolveProjectRoot(invokedFrom);
  const scriptPath = process.argv[1] || path.join(invokedFrom, 'gen-context.js');

  if (cwdFlag) {
    if (!fs.existsSync(cwd)) {
      console.error(`[sigmap] --cwd directory does not exist: ${cwd}`);
      process.exit(1);
    }
    if (!fs.statSync(cwd).isDirectory()) {
      console.error(`[sigmap] --cwd must be a directory, not a file: ${cwd}`);
      process.exit(1);
    }
    console.warn(`[sigmap] --cwd: restricting scan to ${cwd}`);
  } else if (cwd !== invokedFrom) {
    console.warn(`[sigmap] using project root: ${cwd}`);
  }

  if (args.includes('--help') || args.includes('-h')) {
    printHelp(detectInvokedAs());
    process.exit(0);
  }

  if (args.includes('--version') || args.includes('-v')) {
    console.log(VERSION);
    process.exit(0);
  }

  // MCP server — start before loading config (reads files on demand)
  if (args.includes('--mcp')) {
    const { start } = __require('./src/mcp/server');
    start(cwd);
    return; // keep process alive — server drives lifecycle via stdin
  }

  const config = loadConfig(cwd);

  // --cwd restricts scanning: override srcDirs so only the given directory is scanned
  if (cwdFlag) {
    config.srcDirs = ['.'];
  }

  // --coverage: enable test coverage annotation without editing config
  if (args.includes('--coverage')) {
    config.testCoverage = true;
  }

  // ── --output <file> — parse early so every subsequent block can use it ─────
  // Resolves the custom output path and merges it into config.customOutput.
  // Also persists the resolved relative path to gen-context.config.json so
  // future --query calls (without --output) find the file automatically.
  (function resolveOutputFlag() {
    const outIdx = args.indexOf('--output');
    if (outIdx < 0) return;
    const raw = (args[outIdx + 1] || '').trim();
    if (!raw || raw.startsWith('--')) {
      console.error('[sigmap] --output requires a file path');
      console.error('  Example: node gen-context.js --output .context/ai-context.md');
      process.exit(1);
    }
    const abs  = path.resolve(cwd, raw);
    const rel  = path.relative(cwd, abs);
    config.customOutput = rel; // consumed by runGenerate

    // Persist to gen-context.config.json for future --query calls
    const cfgPath = path.join(cwd, 'gen-context.config.json');
    try {
      let savedCfg = {};
      if (fs.existsSync(cfgPath)) {
        try { savedCfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8')); } catch (_) {}
      }
      if (savedCfg.customOutput !== rel) {
        savedCfg.customOutput = rel;
        fs.writeFileSync(cfgPath, JSON.stringify(savedCfg, null, 2) + '\n', 'utf8');
      }
    } catch (err) {
      console.warn(`[sigmap] could not persist customOutput to config: ${err.message}`);
    }
  })();

  // Feature 2: `--mode fast|full|both`
  const modeIdx = args.indexOf('--mode');
  const mode = modeIdx !== -1
    ? args[modeIdx + 1]
    : (config.mode || 'default');
  const VALID_MODES = ['default', 'fast', 'full', 'both'];
  if (mode && !VALID_MODES.includes(mode)) {
    console.error(`[sigmap] unknown --mode "${mode}". Valid: ${VALID_MODES.join(', ')}`);
    process.exit(1);
  }

  // v4.2: `sigmap ask "<query>"` — unified pipeline
  if (args[0] === 'ask') {
    // v6.8: Handle --followup flag which may appear before or after query
    let query = args[1];
    if (query === '--followup' && args[2]) {
      query = args[2];
    } else if (query && query.startsWith('--') && query !== '--json' && query !== '--model') {
      // Allow --json and --model but not other flags as query
      console.error('[sigmap] Usage: sigmap ask "<query>" [--followup] [--json] [--model <name>]');
      console.error('  Example: sigmap ask "fix the login bug" --followup');
      process.exit(1);
    }
    if (!query || query.startsWith('--')) {
      console.error('[sigmap] Usage: sigmap ask "<query>" [--followup] [--json] [--model <name>]');
      console.error('  Example: sigmap ask "fix the login bug" --followup');
      process.exit(1);
    }

    const { detectIntent, buildSigIndex, rank } = requireSourceOrBundled('./src/retrieval/ranker');
    const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
    const { loadSession, saveSession, mergeSessionContext } = requireSourceOrBundled('./src/session/memory');
    const { detectWorkspaces, inferPackage, scopeToPackage } = requireSourceOrBundled('./src/workspace/detector');

    const intent = detectIntent(query);
    const intentWeights = getIntentWeights(intent);

    const sigIndex = buildSigIndex(cwd);
    if (sigIndex.size === 0) {
      console.error('[sigmap] no context file found. Run: sigmap  (to generate first)');
      process.exit(1);
    }

    let ranked = rank(query, sigIndex, { topK: 5, weights: intentWeights, cwd });

    // v6.10: Workspace scoping — infer package from query and apply boost
    const workspaces = detectWorkspaces(cwd);
    const packageFlag = args[args.indexOf('--package') + 1];
    const globalFlag = args.includes('--global');

    let inferredPackage = null;
    if (!globalFlag && workspaces.length > 0) {
      if (packageFlag) {
        inferredPackage = workspaces.find(d => path.basename(d) === packageFlag) || null;
        if (!packageFlag || !inferredPackage) {
          process.stderr.write(`[sigmap] ⚠  package "${packageFlag}" not found — searching entire repo\n`);
        }
      } else {
        inferredPackage = inferPackage(query, workspaces, cwd);
      }
    }

    if (inferredPackage) {
      ranked = ranked.map(r => ({
        ...r,
        score: r.score + scopeToPackage(r.file, inferredPackage),
      })).sort((a, b) => b.score - a.score);

      if (!args.includes('--json') && !globalFlag) {
        process.stderr.write(`[sigmap] 📦 package scope: ${path.relative(cwd, inferredPackage)}\n`);
      }
    }

    // v6.8: --followup support — carry session context forward
    const isFollowup = args.includes('--followup');
    const session = isFollowup ? loadSession(cwd) : null;
    if (session) {
      ranked = mergeSessionContext(ranked, session, intent);
      ranked.sort((a, b) => b.score - a.score);
      if (!args.includes('--json')) {
        process.stderr.write(`[sigmap] ♻  followup — carrying context from: "${session.lastQuery.slice(0, 60)}"\n`);
      }
    }

    // v6.8: Save session for future --followup calls
    saveSession(cwd, { intent, topFiles: ranked.slice(0, 5).map(r => ({ file: r.file, score: r.score })), query });
    const miniCtx = buildMiniContext(ranked, cwd);
    const outPath = path.join(cwd, '.context', 'query-context.md');
    fs.mkdirSync(path.dirname(outPath), { recursive: true });
    fs.writeFileSync(outPath, miniCtx, 'utf8');
    const ctxTok = estimateTokens(miniCtx);

    const allFiles = buildFileList(cwd, config);
    const fakeEntries = allFiles.map((f) => ({ filePath: f }));
    let coveragePct = 0;
    try { coveragePct = coverageScore(cwd, fakeEntries, config).score; } catch (_) {}

    const rawTok = getRawTokenCount(cwd, config);
    const savings = rawTok > 0 ? Math.round((1 - ctxTok / rawTok) * 100) : 0;
    const model = args[args.indexOf('--model') + 1] || 'gpt-4o';
    const rateK = MODEL_COSTS[model] || MODEL_COSTS['gpt-4o'];
    const costRaw = ((rawTok / 1000) * rateK).toFixed(4);
    const costCtx = ((ctxTok / 1000) * rateK).toFixed(4);

    const riskLevel = computeCurrentRisk(cwd);

    if (args.includes('--json')) {
      process.stdout.write(JSON.stringify({
        intent, coverage: coveragePct, contextTokens: ctxTok,
        costBefore: costRaw, costAfter: costCtx, savingsPct: savings,
        riskLevel, contextPath: path.relative(cwd, outPath),
      }) + '\n');
    } else {
      if (coveragePct < 70) {
        process.stderr.write(`[sigmap] ⚠  coverage ${coveragePct}% — consider running: sigmap validate\n`);
      }
      const bar = '─'.repeat(44);
      console.log([
        bar,
        ` sigmap ask  "${query}"`,
        ` Intent    : ${intent}`,
        ` Context   : ${ctxTok.toLocaleString()} tokens  →  ${path.relative(cwd, outPath)}`,
        ` Coverage  : ${coveragePct}%`,
        ` Risk      : ${riskLevel}`,
        ` Cost      : $${costCtx}/query  (was $${costRaw} · saved ${savings}%)`,
        bar,
      ].join('\n'));
    }
    process.exit(0);
  }

  // v4.2: `sigmap suggest-profile` — auto-detect task type from git state
  if (args[0] === 'suggest-profile') {
    const short = args.includes('--short');
    let msg = '', diff = '';
    try {
      const { execSync } = require('child_process');
      msg  = execSync('git log -1 --format=%s',        { cwd, timeout: 3000, encoding: 'utf8' }).trim();
      diff = execSync('git diff --cached --name-only',  { cwd, timeout: 3000, encoding: 'utf8' });
    } catch (_) {}

    let profile = 'default';
    let reason  = 'no strong signal in git state';
    if      (/fix|bug|error|crash|exception/i.test(msg))         { profile = 'debug';        reason = `commit: "${msg.slice(0, 60)}"`;  }
    else if (/refactor|architect|redesign|module/i.test(msg))    { profile = 'architecture'; reason = `commit: "${msg.slice(0, 60)}"`;  }
    else if (/review|pr|pull.request|check/i.test(msg))          { profile = 'review';       reason = `commit: "${msg.slice(0, 60)}"`;  }
    else if (diff.includes('.spec.') || diff.includes('.test.')) { profile = 'debug';        reason = 'staged test files detected'; }

    if (short) {
      console.log(profile);
    } else {
      console.log(`[sigmap] suggested profile: --profile ${profile}`);
      console.log(`  Reason: ${reason}`);
    }
    process.exit(0);
  }

  // v4.2: `sigmap compare` — human-readable benchmark CLI
  if (args[0] === 'compare') {
    const { execSync } = require('child_process');
    console.log('[sigmap] Running comparison benchmark (this may take ~30s)...\n');

    let raw = '';
    try {
      raw = execSync(
        `node ${JSON.stringify(path.join(__dirname, 'scripts', 'run-retrieval-benchmark.mjs'))} --compare`,
        { cwd, timeout: 90_000, encoding: 'utf8' }
      );
    } catch (e) { raw = (e && e.stdout) ? e.stdout : ''; }

    let results = null;
    try { results = JSON.parse(raw); } catch (_) {}

    if (!results) {
      console.error('[sigmap] Could not parse benchmark output.');
      console.error('  Run manually: node scripts/run-retrieval-benchmark.mjs --skip-run --json');
      process.exit(1);
    }

    if (args.includes('--json')) {
      process.stdout.write(JSON.stringify(results, null, 2) + '\n');
    } else {
      const pct  = (v) => `${(v * 100).toFixed(1)}%`;
      const lift = (a, b) => (b > 0 ? (a / b).toFixed(1) : '∞');
      const bar  = '─'.repeat(44);
      console.log([
        bar,
        ' SigMap vs Baseline',
        bar,
        ` hit@5         ${pct(results.sigmap.hitAt5)} vs ${pct(results.baseline.hitAt5)}   (${lift(results.sigmap.hitAt5, results.baseline.hitAt5)}× lift)`,
        ` Avg tokens    ${results.sigmap.tokens.toLocaleString()} vs ${results.baseline.tokens.toLocaleString()}`,
        bar,
      ].join('\n'));
    }
    process.exit(0);
  }

  // v4.2: `sigmap share` — shareable one-liner with live benchmark numbers
  if (args[0] === 'share') {
    const histPath = path.join(cwd, '.context', 'benchmark-history.ndjson');
    let reduction = 97, hitAt5 = 88;

    if (fs.existsSync(histPath)) {
      try {
        const entries = fs.readFileSync(histPath, 'utf8').trim().split('\n')
          .map((l) => { try { return JSON.parse(l); } catch (_) { return null; } }).filter(Boolean);
        const tok = [...entries].reverse().find((e) => e.type === 'token-reduction');
        const ret = [...entries].reverse().find((e) => e.type === 'retrieval');
        if (tok && tok.reduction) reduction = tok.reduction;
        if (ret && ret.hitAt5)   hitAt5    = Math.round(ret.hitAt5 * 100);
      } catch (_) {}
    }

    const shareText = [
      'Generated with SigMap — zero-dependency AI context engine',
      `${reduction}% fewer tokens · ${hitAt5}% retrieval accuracy · 6× better results`,
      'https://sigmap.dev',
    ].join('\n');

    console.log(shareText);

    try {
      const { execSync } = require('child_process');
      const clipCmd = process.platform === 'darwin' ? 'pbcopy' : 'xclip -selection clipboard';
      execSync(`printf '%s' ${JSON.stringify(shareText)} | ${clipCmd}`, { timeout: 2000 });
      console.log('\n[sigmap] Copied to clipboard.');
    } catch (_) {}

    process.exit(0);
  }

  // v6.8: `sigmap plan "<goal>"` — analyze what files need changes and impact radius
  if (args[0] === 'plan') {
    const goal = args[1];
    if (!goal || goal.startsWith('--')) {
      console.error('[sigmap] Usage: sigmap plan "<goal>"');
      console.error('  Example: sigmap plan "add rate limiting to the API"');
      process.exit(1);
    }

    const { detectIntent, buildSigIndex, rank } = requireSourceOrBundled('./src/retrieval/ranker');

    const intent = detectIntent(goal);
    const intentWeights = getIntentWeights(intent);

    const sigIndex = buildSigIndex(cwd);
    if (sigIndex.size === 0) {
      console.error('[sigmap] no context file found. Run: sigmap (to generate first)');
      process.exit(1);
    }

    const ranked = rank(goal, sigIndex, { topK: 15, weights: intentWeights, cwd });

    // Separate into confidence levels
    const highConf = ranked.filter(r => r.confidence === 'high').slice(0, 5);
    const medConf = ranked.filter(r => r.confidence === 'medium').slice(0, 5);

    // Compute impact radius (simplified — no graph for now)
    let impact = null;

    // Identify likely-affected tests (simplified — checks for .test. or .spec. in filename)
    const testedFiles = highConf.filter(r => /\.(test|spec)\.(js|ts|py)$|_test\.(js|ts|py)$/.test(r.file));

    if (args.includes('--json')) {
      process.stdout.write(JSON.stringify({
        goal, intent,
        inspectFirst: highConf.map(r => r.file),
        likelyToChange: medConf.map(r => r.file),
        impactRadius: impact,
        testsAffected: testedFiles.map(r => r.file),
      }, null, 2) + '\n');
    } else {
      const bar = '─'.repeat(50);
      console.log(bar);
      console.log(` sigmap plan  "${goal}"`);
      console.log(` Intent     : ${intent}`);
      console.log(bar);
      console.log('');
      console.log(' Inspect first (highest relevance):');
      if (highConf.length === 0) {
        console.log('   (no files found)');
      } else {
        highConf.forEach((r, i) => console.log(`   ${i + 1}. ${path.relative(cwd, r.file)}`));
      }
      console.log('');
      console.log(' Likely to change:');
      if (medConf.length === 0) {
        console.log('   (no files found)');
      } else {
        medConf.forEach((r, i) => console.log(`   ${i + 1}. ${path.relative(cwd, r.file)}`));
      }
      if (testedFiles.length > 0) {
        console.log('');
        console.log(' Tests to run after change:');
        testedFiles.forEach(r => console.log(`   • ${path.relative(cwd, r.file)}`));
      }
      console.log('');
      console.log(bar);
    }
    process.exit(0);
  }

  // v5.9: `sigmap bench --submit` — format local benchmark history as a shareable community block
  if (args[0] === 'bench' && args.includes('--submit')) {
    const versionMeta = (() => {
      try { return JSON.parse(fs.readFileSync(path.join(__dirname, 'version.json'), 'utf8')); }
      catch (_) { return {}; }
    })();

    const histPath = path.join(cwd, '.context', 'benchmark-history.ndjson');
    let localMetrics = null;
    if (fs.existsSync(histPath)) {
      try {
        const entries = fs.readFileSync(histPath, 'utf8').trim().split('\n')
          .map((l) => { try { return JSON.parse(l); } catch (_) { return null; } }).filter(Boolean);
        const ret = [...entries].reverse().find((e) => e.type === 'retrieval');
        const tok = [...entries].reverse().find((e) => e.type === 'token-reduction');
        if (ret || tok) {
          localMetrics = {
            hitAt5Pct:       ret ? (ret.hitAt5Pct || Math.round((ret.hitAt5 || 0) * 100)) : null,
            reductionPct:    tok ? (tok.reduction || tok.avgReductionPct || null) : null,
            runDate:         (ret || tok).ts ? new Date((ret || tok).ts).toISOString().slice(0, 10) : null,
          };
        }
      } catch (_) {}
    }

    const canonical = versionMeta.metrics || {};
    const submission = {
      sigmapVersion:     versionMeta.version || 'unknown',
      benchmarkId:       versionMeta.benchmark_id || 'unknown',
      canonicalHitAt5:   canonical.hit_at_5 != null ? Math.round(canonical.hit_at_5 * 1000) / 10 : null,
      canonicalReduction: canonical.overall_token_reduction_pct || null,
      local:             localMetrics,
      submittedAt:       new Date().toISOString().slice(0, 10),
    };

    if (args.includes('--json')) {
      process.stdout.write(JSON.stringify(submission, null, 2) + '\n');
    } else {
      const bar = '─'.repeat(56);
      const lines = [
        bar,
        ' SigMap Community Benchmark Submission',
        bar,
        ` SigMap version : ${submission.sigmapVersion}`,
        ` Benchmark ID   : ${submission.benchmarkId}`,
        ` Submitted      : ${submission.submittedAt}`,
        bar,
        ' Canonical metrics (official release):',
        ` hit@5          : ${submission.canonicalHitAt5 != null ? submission.canonicalHitAt5 + '%' : 'n/a'}`,
        ` token reduction: ${submission.canonicalReduction != null ? submission.canonicalReduction + '%' : 'n/a'}`,
      ];
      if (localMetrics) {
        lines.push(bar, ' Local run metrics (this repo):');
        if (localMetrics.hitAt5Pct != null)    lines.push(` hit@5          : ${localMetrics.hitAt5Pct}%`);
        if (localMetrics.reductionPct != null)  lines.push(` token reduction: ${localMetrics.reductionPct}%`);
        if (localMetrics.runDate)               lines.push(` run date       : ${localMetrics.runDate}`);
      } else {
        lines.push(bar, ' Local run metrics: none yet — run node scripts/run-retrieval-benchmark.mjs');
      }
      lines.push(bar);
      lines.push(' Paste the block above into a GitHub Discussion to share your results.');
      lines.push(' https://github.com/manojmallick/sigmap/discussions');
      lines.push(bar);
      console.log(lines.join('\n'));
    }
    process.exit(0);
  }

  // v5.2: `sigmap learn` — manual learning controls for ranking
  if (args[0] === 'learn') {
    const doReset = args.includes('--reset');
    const good = collectLearnFiles(args, '--good', cwd);
    const bad = collectLearnFiles(args, '--bad', cwd);

    if (doReset && (good.rawCount > 0 || bad.rawCount > 0)) {
      console.error('[sigmap] --reset cannot be combined with --good or --bad');
      process.exit(1);
    }

    if (doReset) {
      const { resetWeights } = requireSourceOrBundled('./src/learning/weights');
      resetWeights(cwd);
      console.log('[sigmap] weights reset — all files back to baseline');
      process.exit(0);
    }

    if (good.rawCount === 0 && bad.rawCount === 0) {
      console.error('[sigmap] Usage: sigmap learn --good <files...> [--bad <files...>] | sigmap learn --reset');
      process.exit(1);
    }

    for (const warning of [...good.warnings, ...bad.warnings]) {
      console.warn(`[sigmap] warning: ${warning}`);
    }

    if (good.files.length === 0 && bad.files.length === 0) {
      console.error('[sigmap] No valid files to learn from.');
      process.exit(1);
    }

    const { updateWeights } = requireSourceOrBundled('./src/learning/weights');
    const result = updateWeights(cwd, { goodFiles: good.files, badFiles: bad.files });
    const parts = [];
    if (result.good.length) parts.push(`boosted ${result.good.length} file(s)`);
    if (result.bad.length) parts.push(`penalized ${result.bad.length} file(s)`);
    console.log(`[sigmap] learned: ${parts.join(', ')}`);
    process.exit(0);
  }

  // v5.2: `sigmap weights` — explain learned ranking multipliers
  if (args[0] === 'weights') {
    const { loadWeights, exportWeights, importWeights } = requireSourceOrBundled('./src/learning/weights');

    if (args.includes('--export')) {
      const exportIdx = args.indexOf('--export');
      const maybeFile = args[exportIdx + 1];
      const exportFile = (maybeFile && !maybeFile.startsWith('--')) ? maybeFile : null;
      exportWeights(cwd, exportFile);
      if (exportFile) console.warn(`[sigmap] weights exported to ${exportFile}`);
      process.exit(0);
    }

    if (args.includes('--import')) {
      const importIdx = args.indexOf('--import');
      const importFile = args[importIdx + 1];
      if (!importFile || importFile.startsWith('--')) {
        console.error('[sigmap] --import requires a file path');
        process.exit(1);
      }
      const replace = args.includes('--replace');
      try {
        const result = importWeights(cwd, importFile, replace);
        const count = Object.keys(result).length;
        console.warn(`[sigmap] weights ${replace ? 'replaced' : 'merged'} from ${importFile} — ${count} file(s) with non-baseline weights`);
      } catch (err) {
        console.error(`[sigmap] weights import failed: ${err.message}`);
        process.exit(1);
      }
      process.exit(0);
    }

    const weights = loadWeights(cwd);
    const entries = Object.entries(weights).sort(([, a], [, b]) => b - a || 0);

    if (args.includes('--json')) {
      process.stdout.write(JSON.stringify(weights, null, 2) + '\n');
      process.exit(0);
    }

    if (entries.length === 0) {
      console.log('[sigmap] No learned weights yet. Run: sigmap learn --good <file>');
      process.exit(0);
    }

    console.log('[sigmap] Learned file weights (xmultiplier vs baseline):');
    for (const [file, mult] of entries) {
      const bar = mult >= 1
        ? `+${'█'.repeat(Math.max(1, Math.round((mult - 1) * 10)))}`
        : `-${'░'.repeat(Math.max(1, Math.round((1 - mult) * 10)))}`;
      console.log(`  ${file.padEnd(50)} x${mult.toFixed(2)}  ${bar}`);
    }
    console.log(`\n  Total files with learned weights: ${entries.length}`);
    console.log('  To reset: sigmap learn --reset');
    process.exit(0);
  }

  // v4.3: `sigmap validate` — config + coverage + optional query symbol check
  if (args[0] === 'validate') {
    const issues   = [];
    const warnings = [];

    // Config checks
    for (const d of (config.srcDirs || [])) {
      if (!fs.existsSync(path.join(cwd, d)))
        issues.push(`srcDir '${d}' does not exist`);
    }
    if ((config.exclude || []).some((p) => p === 'src/**'))
      issues.push(`exclude pattern 'src/**' will exclude all source files`);
    if ((config.maxTokens || 0) < 1000)
      warnings.push(`maxTokens ${config.maxTokens} is very low — consider ≥ 4000`);
    if ((config.maxTokens || 0) > 50000)
      warnings.push(`maxTokens ${config.maxTokens} is very high — may exceed LLM context windows`);

    // Coverage check: files actually in context vs total source files
    const { buildSigIndex: valBuildSigIndex } = requireSourceOrBundled('./src/retrieval/ranker');
    const valSigIndex  = valBuildSigIndex(cwd);
    const valTotal     = buildFileList(cwd, config).length;
    const coveragePct  = valTotal > 0 ? Math.round((valSigIndex.size / valTotal) * 100) : 0;
    if (coveragePct < 70)
      warnings.push(`coverage ${coveragePct}% is below recommended 70% — increase maxTokens or expand srcDirs`);

    // Optional query symbol check
    const valQueryIdx = args.indexOf('--query');
    if (valQueryIdx !== -1) {
      const q = (args[valQueryIdx + 1] || '').trim();
      if (q && !q.startsWith('--')) {
        try {
          const { rank, buildSigIndex } = requireSourceOrBundled('./src/retrieval/ranker');
          const ranked = rank(q, buildSigIndex(cwd), { topK: 5, cwd });
          const symbols = extractQuerySymbols(q);
          const missing = symbols.filter((sym) =>
            !ranked.some((r) => r.sigs && r.sigs.some((s) => s.toLowerCase().includes(sym.toLowerCase())))
          );
          if (missing.length > 0)
            warnings.push(`query "${q}" references symbols not in top-5 context: ${missing.join(', ')}`);
          else if (symbols.length > 0)
            console.log(`[sigmap] ✓ query coverage OK — all ${symbols.length} symbols found`);
        } catch (_) {}
      }
    }

    if (args.includes('--json')) {
      process.stdout.write(JSON.stringify({ valid: issues.length === 0, issues, warnings, coverage: coveragePct }) + '\n');
    } else {
      for (const w of warnings) console.warn(`[sigmap] ⚠  ${w}`);
      if (issues.length === 0) {
        console.log(`[sigmap] ✓ config valid  coverage: ${coveragePct}%`);
      } else {
        for (const iss of issues) console.error(`[sigmap] ✗ ${iss}`);
        process.exit(1);
      }
    }
    process.exit(0);
  }

  // v5.0: `sigmap judge --response <file> --context <file>` — groundedness scoring
  if (args[0] === 'judge') {
    const respIdx = args.indexOf('--response');
    const ctxIdx  = args.indexOf('--context');

    if (respIdx < 0 || ctxIdx < 0) {
      console.error('[sigmap] Usage: sigmap judge --response <file> --context <file> [--json] [--threshold 0.25] [--learn]');
      process.exit(1);
    }

    const respFile = (args[respIdx + 1] || '').trim();
    const ctxFile  = (args[ctxIdx + 1]  || '').trim();

    if (!respFile || respFile.startsWith('--') || !ctxFile || ctxFile.startsWith('--')) {
      console.error('[sigmap] --response and --context require file paths');
      process.exit(1);
    }

    let responseText = '', contextText = '';
    try { responseText = fs.readFileSync(path.resolve(cwd, respFile), 'utf8'); }
    catch (e) { console.error(`[sigmap] cannot read --response file: ${e.message}`); process.exit(1); }
    try { contextText = fs.readFileSync(path.resolve(cwd, ctxFile), 'utf8'); }
    catch (e) { console.error(`[sigmap] cannot read --context file: ${e.message}`); process.exit(1); }

    const thrIdx = args.indexOf('--threshold');
    const judgeOpts = thrIdx >= 0 ? { threshold: parseFloat(args[thrIdx + 1]) || 0.25 } : {};
    if (args.includes('--learn')) {
      judgeOpts.learn = true;
      judgeOpts.cwd = cwd;
    }

    const { judge: runJudge } = requireSourceOrBundled('./src/judge/judge-engine');
    const result = runJudge(responseText, contextText, judgeOpts);

    if (args.includes('--json')) {
      process.stdout.write(JSON.stringify(result) + '\n');
    } else {
      const bar = '─'.repeat(44);
      console.log([
        bar,
        ` sigmap judge`,
        ` Score     : ${result.score}`,
        ` Verdict   : ${result.verdict}`,
        result.reasons.length ? ` Reasons   :\n   ${result.reasons.join('\n   ')}` : ` Reasons   : none`,
        result.learning
          ? ` Learning  : ${result.learning.applied ? result.learning.action : 'skipped'}${result.learning.files.length ? ` (${result.learning.files.join(', ')})` : ''}${result.learning.reason ? ` — ${result.learning.reason}` : ''}`
          : null,
        bar,
      ].filter(Boolean).join('\n'));
    }
    process.exit(result.verdict === 'pass' ? 0 : 1);
  }

  // v5.0: `sigmap history` — show last N usage log entries with sparkline
  if (args[0] === 'history') {
    const { readLog } = requireSourceOrBundled('./src/tracking/logger');
    const entries = readLog(cwd);

    const nIdx = args.indexOf('--last');
    const n    = nIdx >= 0 ? (parseInt(args[nIdx + 1], 10) || 10) : 10;
    const last = entries.slice(-n);

    if (args.includes('--json')) {
      process.stdout.write(JSON.stringify(last) + '\n');
      process.exit(0);
    }

    const SPARK_CHARS = '▁▂▃▄▅▆▇█';
    function sparkline(values) {
      if (values.length === 0) return '';
      const min = Math.min(...values);
      const max = Math.max(...values);
      const range = max - min || 1;
      return values.map((v) => {
        const idx = Math.round(((v - min) / range) * (SPARK_CHARS.length - 1));
        return SPARK_CHARS[idx];
      }).join('');
    }

    const bar = '─'.repeat(62);
    console.log(bar);
    console.log(` sigmap history  (last ${Math.max(last.length, 1)} runs)`);
    console.log(bar);

    if (last.length === 0) {
      console.log(' No usage log entries. Enable tracking: true in config to start recording runs.');
    } else {
      console.log(` ${'Date'.padEnd(24)} ${'Files'.padStart(5)} ${'Tokens'.padStart(7)} ${'Reduction'.padStart(9)} ${'Budget?'.padStart(7)}`);
      console.log(` ${'─'.repeat(24)} ${'─'.repeat(5)} ${'─'.repeat(7)} ${'─'.repeat(9)} ${'─'.repeat(7)}`);
      for (const e of last) {
        const date = (e.ts || '').slice(0, 19).replace('T', ' ');
        const files = String(e.fileCount || 0).padStart(5);
        const tok   = String(e.finalTokens || 0).padStart(7);
        const red   = `${e.reductionPct || 0}%`.padStart(9);
        const over  = (e.overBudget ? '  ⚠ yes' : '     no').padStart(7);
        console.log(` ${date.padEnd(24)} ${files} ${tok} ${red} ${over}`);
      }
      console.log(bar);
      const tokens = last.map((e) => e.finalTokens || 0);
      console.log(` Token trend: ${sparkline(tokens)}`);
    }

    // Show benchmark trend row if .context/benchmark-history.ndjson exists
    const benchHistPath = path.join(cwd, '.context', 'benchmark-history.ndjson');
    if (fs.existsSync(benchHistPath)) {
      try {
        const benchEntries = fs.readFileSync(benchHistPath, 'utf8').trim().split('\n')
          .map((l) => { try { return JSON.parse(l); } catch (_) { return null; } }).filter(Boolean);
        const retrieval = benchEntries.filter((e) => e.type === 'retrieval').slice(-n);
        if (retrieval.length > 0) {
          const hits = retrieval.map((e) => e.hitAt5Pct || 0);
          console.log(` hit@5 trend: ${sparkline(hits)}  ${hits.at(-1)}% (latest)`);
        }
        const tokenBench = benchEntries.filter((e) => e.type === 'token-reduction').slice(-n);
        if (tokenBench.length > 0) {
          const reds = tokenBench.map((e) => e.reduction || e.avgReductionPct || 0);
          console.log(` tok reduce : ${sparkline(reds)}  ${reds.at(-1)}% (latest)`);
        }
      } catch (_) {}
    }

    console.log(bar);
    process.exit(0);
  }

  // Feature 6: `sigmap sync` — write all outputs + llms.txt + print compact diff
  if (args[0] === 'sync') {
    try {
      // Generate copilot-instructions.md first (existing output)
      runGenerate(cwd, config, false);
      const llmTxt = requireSourceOrBundled('./src/format/llm-txt');
      const llmsGen = requireSourceOrBundled('./src/format/llms-txt');
      const llmFullMod = requireSourceOrBundled('./packages/adapters/llm-full');

      // Gather file entries for llm.txt / llm-full.txt
      const ignorePatterns = loadIgnorePatterns(cwd);
      let syncFiles = buildFileList(cwd, config).filter((f) => {
        const rel = path.relative(cwd, f).replace(/\\/g, '/');
        return !matchesIgnore(rel, ignorePatterns);
      });
      const syncEntries = [];
      for (const fp of syncFiles) {
        let content = '';
        try { content = fs.readFileSync(fp, 'utf8'); } catch (_) { continue; }
        const sigs = detectAndExtract(fp, content, config.maxSigsPerFile || 25);
        if (sigs.length === 0) continue;
        syncEntries.push({ filePath: fp, sigs, language: null });
      }
      const syncContext = {
        projectName: path.basename(cwd),
        fileEntries: syncEntries,
        srcDirs: config.srcDirs || [],
      };

      const llmTxtPath   = llmTxt.outputPath(cwd);
      const llmFullPath  = llmFullMod.outputPath(cwd);
      const llmsTxtPath  = llmsGen.outputPath(cwd);

      fs.writeFileSync(llmTxtPath,  llmTxt.format(syncContext, cwd, VERSION));
      llmFullMod.write(syncContext, cwd, { version: VERSION });

      const prev  = fs.existsSync(llmsTxtPath) ? fs.readFileSync(llmsTxtPath, 'utf8') : '';
      const next  = llmsGen.format(syncContext, cwd, [
        { path: llmTxtPath,  label: 'llm.txt' },
        { path: llmFullPath, label: 'llm-full.txt' },
      ], VERSION);
      const changed = prev !== next ? 'updated' : 'no change';
      fs.writeFileSync(llmsTxtPath, next);

      console.log('[sigmap] sync complete');
      console.log(`  .github/copilot-instructions.md  updated`);
      console.log(`  llm.txt                          updated`);
      console.log(`  llm-full.txt                     updated`);
      console.log(`  llms.txt                         ${changed}`);
    } catch (err) {
      console.error(`[sigmap] sync error: ${err.message}`);
      process.exit(1);
    }
    process.exit(0);
  }

  // v6.5: `sigmap roots` — detect source roots
  if (args[0] === 'roots') {
    const { resolveSourceRoots } = requireSourceOrBundled('./src/discovery/source-root-resolver');
    const result = resolveSourceRoots(cwd);

    if (args.includes('--json')) {
      console.log(JSON.stringify(result, null, 2));
      process.exit(0);
    }

    if (args.includes('--fix')) {
      console.log('[sigmap] Current detected roots:', result.roots.join(', '));
      const readline = require('readline');
      const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
      rl.question('Enter correct srcDirs (comma-separated): ', answer => {
        const dirs = answer.split(',').map(d => d.trim()).filter(Boolean);
        const cfgPath = path.join(cwd, 'gen-context.config.json');
        const cfg = fs.existsSync(cfgPath) ? JSON.parse(fs.readFileSync(cfgPath, 'utf8')) : {};
        cfg.srcDirs = dirs;
        fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
        console.log(`[sigmap] Saved srcDirs: ${dirs.join(', ')} → gen-context.config.json`);
        rl.close();
        process.exit(0);
      });
      return;
    }

    // --explain (default)
    console.log('\nDetected languages:');
    for (const l of result.languages.slice(0, 4)) {
      console.log(`  ${l.name.padEnd(16)} ${l.weight.toFixed(2)}`);
    }
    console.log('\nDetected frameworks:');
    if (result.frameworks.length === 0) console.log('  (none)');
    for (const f of result.frameworks.slice(0, 3)) {
      console.log(`  ${f.name.padEnd(16)} ${f.confidence.toFixed(2)}`);
    }
    console.log('\nChosen source roots:');
    if (result.roots.length === 0) console.log('  (none — legacy fallback used)');
    result.roots.forEach((r, i) => {
      const exp = result.explanation?.find(e => e.dir === r);
      console.log(`  ${i + 1}. ${r.padEnd(20)} ${exp ? 'score ' + exp.score : ''}`);
    });
    console.log('\nMonorepo:', result.isMonorepo ? 'yes' : 'no');
    console.log('Confidence:', result.confidence);
    process.exit(0);
  }

  // Feature 1: `sigmap explain <file>` — why a file is included or excluded
  if (args[0] === 'explain' || args.includes('--explain')) {
    const target = args[0] === 'explain'
      ? args[1]
      : args[args.indexOf('--explain') + 1];

    if (!target || target.startsWith('--')) {
      console.error('[sigmap] Usage: sigmap explain <file>');
      process.exit(1);
    }

    const absPath = path.resolve(cwd, target);
    const rel     = path.relative(cwd, absPath);
    const jsonOut = args.includes('--json');

    // 1. Check .contextignore
    const ignorePatterns = loadIgnorePatterns(cwd);
    const ignored = matchesIgnore(rel.replace(/\\/g, '/'), ignorePatterns);
    if (ignored) {
      if (jsonOut) {
        process.stdout.write(JSON.stringify({ status: 'excluded', reason: '.contextignore', fix: `remove the pattern or add '!${rel}' as an exception` }) + '\n');
      } else {
        console.log(`[sigmap] ${rel} — EXCLUDED`);
        console.log(`  Reason: matched .contextignore`);
        console.log(`  Fix:    remove the pattern or add '!${rel}' as an exception`);
      }
      process.exit(0);
    }

    // 2. Check srcDirs membership
    const inSrcDirs = (config.srcDirs || []).some(d => absPath.startsWith(path.resolve(cwd, d)));
    if (!inSrcDirs) {
      if (jsonOut) {
        process.stdout.write(JSON.stringify({ status: 'excluded', reason: 'not in srcDirs', srcDirs: config.srcDirs, fix: 'add the containing directory to srcDirs in gen-context.config.json' }) + '\n');
      } else {
        console.log(`[sigmap] ${rel} — EXCLUDED`);
        console.log(`  Reason: not under any srcDir (${(config.srcDirs || []).join(', ')})`);
        console.log(`  Fix:    add the containing directory to srcDirs in gen-context.config.json`);
      }
      process.exit(0);
    }

    // 3. Detect extractor and extract sigs
    const base    = path.basename(absPath);
    const ext     = path.extname(base).toLowerCase();
    let extractorName = EXT_MAP[ext] || null;
    if (!extractorName && isDockerfile(base)) extractorName = 'dockerfile';
    if (!extractorName) extractorName = 'generic';

    let sigs = [];
    if (fs.existsSync(absPath)) {
      try {
        const content = fs.readFileSync(absPath, 'utf8');
        const extractor = getExtractor(extractorName);
        if (extractor) sigs = extractor.extract(content).slice(0, config.maxSigsPerFile || 25);
      } catch (_) {}
    }

    if (!sigs.length) {
      if (jsonOut) {
        process.stdout.write(JSON.stringify({ status: 'excluded', reason: 'no signatures', extractor: extractorName, fix: 'check that the file contains function/class definitions' }) + '\n');
      } else {
        console.log(`[sigmap] ${rel} — EXCLUDED`);
        console.log(`  Reason: no extractable signatures (extractor: ${extractorName})`);
        console.log(`  Fix:    check that the file contains function/class definitions`);
      }
      process.exit(0);
    }

    // 4. Included
    if (jsonOut) {
      process.stdout.write(JSON.stringify({ status: 'included', extractor: extractorName, signatures: sigs.length, preview: sigs.slice(0, 3), sigs }) + '\n');
    } else {
      console.log(`[sigmap] ${rel} — INCLUDED`);
      console.log(`  Extractor : ${extractorName}`);
      console.log(`  Signatures: ${sigs.length}`);
      console.log(`  Preview   : ${sigs.slice(0, 3).join(' · ')}`);
    }
    process.exit(0);
  }

  if (args.includes('--init')) {
    writeInitConfig(cwd);
    process.exit(0);
  }

  if (args.includes('--health')) {
    const { score } = __require('./src/health/scorer');
    const result = score(cwd);
    // v4.0: compute live coverage score to include in health output
    let coverageResult = null;
    try {
      const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
      const { loadConfig: lc } = requireSourceOrBundled('./src/config/loader');
      const cfg = lc(cwd);
      // Use all files from srcDirs as proxies for "included" (no budget applied in health mode)
      const allFiles = buildFileList(cwd, cfg);
      const fakeEntries = allFiles.map(f => ({ filePath: f }));
      coverageResult = coverageScore(cwd, fakeEntries, cfg);
    } catch (_) {}
    // v6.7: Collect cache stats if enabled
    let cacheStats = null;
    try {
      const cachePath = path.join(cwd, '.sigmap-cache.json');
      if (fs.existsSync(cachePath)) {
        const raw = fs.readFileSync(cachePath, 'utf8');
        const data = JSON.parse(raw);
        const sizeKb = Math.round(Buffer.byteLength(raw) / 1024);
        const entries = Object.keys(data.entries || {}).length;
        const mtime = fs.statSync(cachePath).mtimeMs;
        cacheStats = { sizeKb, entries, mtimeMs: mtime };
      }
    } catch (_) {}
    if (args.includes('--json')) {
      // Feature 3 (VS Code) + Feature 5 (JetBrains): emit tokens + reduction for plugins
      const ctxPath = path.join(cwd, '.github', 'copilot-instructions.md');
      let tokens = 0;
      let reduction = result.tokenReductionPct || 0;
      if (fs.existsSync(ctxPath)) {
        try { tokens = estimateTokens(fs.readFileSync(ctxPath, 'utf8')); } catch (_) {}
      }
      const payload = { ...result, tokens, reduction };
      if (coverageResult) {
        payload.coverage = coverageResult.score;
        payload.coverageGrade = coverageResult.grade;
        payload.coverageConfidence = coverageResult.confidence;
        payload.coverageTotalFiles = coverageResult.total;
        payload.coverageIncludedFiles = coverageResult.included;
      }
      if (cacheStats) {
        payload.cacheStats = cacheStats;
      }
      process.stdout.write(JSON.stringify(payload) + '\n');
    } else {
      console.log('[sigmap] health:');
      console.log(`  score           : ${result.score}/100 (grade ${result.grade})`);
      if (coverageResult) {
        console.log(`  file access     : ${coverageResult.grade} (${coverageResult.score}%)  — ${coverageResult.included} of ${coverageResult.total} files accessible in srcDirs`);
      }
      console.log(`  strategy        : ${result.strategy}`);
      console.log(`  token reduction : ${result.tokenReductionPct !== null ? result.tokenReductionPct + '%' : 'no history'}`);
      console.log(`  days since regen: ${result.daysSinceRegen !== null ? result.daysSinceRegen : 'context file not found'}`);
      if (result.strategyFreshnessDays !== null) {
        console.log(`  cold freshness  : ${result.strategyFreshnessDays} day(s)`);
      }
      if (cacheStats) {
        console.log(`  sig-cache       : ${cacheStats.entries} files cached  ${cacheStats.sizeKb}KB on disk`);
      }
      console.log(`  total runs      : ${result.totalRuns}`);
      console.log(`  over-budget runs: ${result.overBudgetRuns}`);
      console.log(`  p50 token count : ${result.p50TokenCount}`);
      console.log(`  p95 token count : ${result.p95TokenCount}`);
      console.log(`  overbudget streak: ${result.overBudgetStreak}`);
      console.log(`  extractor cover.: ${result.extractorCoverage}%`);
    }
    process.exit(0);
  }

  if (args.includes('--dashboard')) {
    try {
      const { score } = __require('./src/health/scorer');
      const health = score(cwd);
      const { generateDashboardHtml } = requireSourceOrBundled('./src/format/dashboard');
      const out = generateDashboardHtml(cwd, health);
      const outPath = path.join(cwd, 'benchmarks', 'reports', 'dashboard.html');
      fs.mkdirSync(path.dirname(outPath), { recursive: true });
      fs.writeFileSync(outPath, out.html, 'utf8');
      if (args.includes('--json')) {
        process.stdout.write(JSON.stringify({ ok: true, file: path.relative(cwd, outPath), summary: out.data.summary }) + '\n');
      } else {
        console.log(`[sigmap] dashboard written: ${path.relative(cwd, outPath)}`);
      }
    } catch (err) {
      console.error(`[sigmap] dashboard error: ${err.message}`);
      process.exit(1);
    }
    process.exit(0);
  }

  if (args.includes('--suggest-tool')) {
    const idx = args.indexOf('--suggest-tool');
    const taskDesc = (args[idx + 1] || '').trim();
    if (!taskDesc || taskDesc.startsWith('--')) {
      console.error('[sigmap] --suggest-tool requires a task description');
      console.error('  Example: node gen-context.js --suggest-tool "refactor the auth module"');
      process.exit(1);
    }
    const result = suggestTool(taskDesc);
    if (args.includes('--json')) {
      process.stdout.write(JSON.stringify(result) + '\n');
    } else {
      console.log('[sigmap] suggest-tool:');
      console.log(`  tier   : ${result.tier}`);
      console.log(`  label  : ${result.label}`);
      console.log(`  models : ${result.models}`);
      console.log(`  cost   : ${result.costHint}`);
    }
    process.exit(0);
  }

  if (args.includes('--benchmark') || args.includes('--eval')) {
    try {
      const { run, formatTable, formatMetrics } = __require('./src/eval/runner');
      const tasksFile = path.join(cwd, 'benchmarks', 'tasks', 'retrieval.jsonl');
      const result = run(tasksFile, cwd, { topK: 10 });
      if (args.includes('--json')) {
        // Compact output: strip ranked arrays to keep payload small
        const compact = {
          metrics: result.metrics,
          tasks: result.tasks.map((t) => ({
            id: t.id,
            query: t.query,
            hit5: t.hit5,
            rr: t.rr,
            precAt5: t.precAt5,
            tokens: t.tokens,
            top1: t.ranked[0] || null,
          })),
        };
        process.stdout.write(JSON.stringify(compact) + '\n');
      } else {
        console.log(formatMetrics(result.metrics));
        if (result.tasks.length > 0) {
          console.log('');
          console.log(formatTable(result.tasks));
        }
      }
    } catch (err) {
      console.error(`[sigmap] benchmark error: ${err.message}`);
      process.exit(1);
    }
    process.exit(0);
  }

  if (args.includes('--analyze')) {
    try {
      const { analyzeFiles, formatAnalysisTable, formatAnalysisJSON } = requireSourceOrBundled('./src/eval/analyzer');
      const cfg    = config || {};
      const srcDirs = cfg.srcDirs || DEFAULTS.srcDirs;
      const exclude = cfg.exclude || DEFAULTS.exclude;
      const slow    = args.includes('--slow');

      // Collect files (reuse existing file-walker if accessible, else inline)
      const allFiles = [];
      function walkForAnalyze(dir, depth) {
        if (depth > (cfg.maxDepth || 6)) return;
        let entries;
        try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
        for (const e of entries) {
          if (exclude.some((x) => e.name === x || e.name.startsWith(x))) continue;
          const full = path.join(dir, e.name);
          if (e.isDirectory()) walkForAnalyze(full, depth + 1);
          else if (e.isFile()) allFiles.push(full);
        }
      }
      for (const sd of srcDirs) {
        const abs = path.join(cwd, sd);
        if (fs.existsSync(abs)) walkForAnalyze(abs, 0);
      }

      const stats = analyzeFiles(allFiles, cwd, { slow, maxSigs: cfg.maxSigsPerFile || 25 });

      if (args.includes('--json')) {
        const out = JSON.stringify(formatAnalysisJSON(stats)) + '\n';
        // Use the write callback to exit only after the OS has accepted all
        // bytes. Calling process.exit(0) synchronously after write() truncates
        // large outputs because the underlying pipe write is asynchronous even
        // when write() returns true.
        process.stdout.write(out, 'utf8', () => process.exit(0));
        return; // exit is handled by the callback above
      } else {
        const table = formatAnalysisTable(stats, slow);
        process.stdout.write(table);
      }
    } catch (err) {
      console.error(`[sigmap] analyze error: ${err.message}`);
      process.exit(1);
    }
    process.exit(0);
  }

  if (args.includes('--diagnose-extractors')) {
    try {
      const fixturesDir = path.join(cwd, 'test', 'fixtures');
      const expectedDir = path.join(cwd, 'test', 'expected');
      if (!fs.existsSync(fixturesDir) || !fs.existsSync(expectedDir)) {
        console.error('[sigmap] test/fixtures or test/expected not found — run from the SigMap repo root');
        process.exit(1);
      }

      const EXT_TO_LANG = {
        '.ts': 'typescript', '.js': 'javascript', '.py': 'python',
        '.java': 'java', '.kt': 'kotlin', '.go': 'go', '.rs': 'rust',
        '.cs': 'csharp', '.cpp': 'cpp', '.rb': 'ruby', '.php': 'php',
        '.swift': 'swift', '.dart': 'dart', '.scala': 'scala',
        '.r': 'r', '.R': 'r',
        '.vue': 'vue', '.svelte': 'svelte', '.html': 'html',
        '.css': 'css', '.yml': 'yaml', '.sh': 'shell',
      };
      const SPECIAL = { 'Dockerfile': 'dockerfile' };

      let passed = 0; let failed = 0;
      const entries = fs.readdirSync(fixturesDir).sort();

      for (const filename of entries) {
        const ext  = path.extname(filename).toLowerCase();
        const lang = EXT_TO_LANG[ext] || SPECIAL[filename];
        if (!lang) continue;

        const fixturePath = path.join(fixturesDir, filename);
        const expectedPath = path.join(expectedDir, `${lang}.txt`);
        if (!fs.existsSync(expectedPath)) {
          console.log(`  SKIP  ${lang.padEnd(12)} (no expected file)`);
          continue;
        }

        const src      = fs.readFileSync(fixturePath, 'utf8');
        const expected = fs.readFileSync(expectedPath, 'utf8').trim();

        let mod;
        try {
          mod = requireSourceOrBundled(`./src/extractors/${lang}`);
        } catch (e) {
          console.log(`  ERROR ${lang.padEnd(12)} loader failed: ${e.message}`);
          failed++;
          continue;
        }

        let actual;
        try {
          const sigs = mod.extract(src);
          actual = sigs.join('\n').trim();
        } catch (e) {
          console.log(`  ERROR ${lang.padEnd(12)} extract() threw: ${e.message}`);
          failed++;
          continue;
        }

        if (actual === expected) {
          console.log(`  PASS  ${lang}`);
          passed++;
        } else {
          console.log(`  FAIL  ${lang}`);
          // Show first diff line
          const aLines = actual.split('\n');
          const eLines = expected.split('\n');
          const maxLen = Math.max(aLines.length, eLines.length);
          for (let i = 0; i < maxLen; i++) {
            if (aLines[i] !== eLines[i]) {
              console.log(`         expected: ${(eLines[i] || '(missing)').slice(0, 100)}`);
              console.log(`         actual  : ${(aLines[i] || '(missing)').slice(0, 100)}`);
              break;
            }
          }
          failed++;
        }
      }

      console.log(`\n${passed} passed, ${failed} failed`);
      process.exit(failed > 0 ? 1 : 0);
    } catch (err) {
      console.error(`[sigmap] diagnose error: ${err.message}`);
      process.exit(1);
    }
  }

  if (args.includes('--query')) {
    try {
      const qIdx = args.indexOf('--query');
      const query = (args[qIdx + 1] || '').trim();
      if (!query || query.startsWith('--')) {
        console.error('[sigmap] --query requires a search string');
        console.error('  Example: node gen-context.js --query "add a new language extractor"');
        process.exit(1);
      }
      const { rank, buildSigIndex, formatRankTable, formatRankJSON } = requireSourceOrBundled('./src/retrieval/ranker');

      // Resolve the context file path to query against.
      // Priority: --output flag > --adapter flag > buildSigIndex probe order
      //   (customOutput from config is handled inside buildSigIndex itself)
      let queryOpts;
      const adpIdx = args.indexOf('--adapter');

      // 1. --output <file> pins to an explicit path
      if (config.customOutput) {
        queryOpts = { contextPath: path.resolve(cwd, config.customOutput) };
      }

      // 2. --adapter <name> pins to that adapter's output path (if --output not given)
      if (!queryOpts && adpIdx >= 0) {
        const adapterName = (args[adpIdx + 1] || '').trim().toLowerCase();
        const VALID_ADAPTERS = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex'];
        if (VALID_ADAPTERS.includes(adapterName)) {
          try {
            const adapterMod = __require('./packages/adapters/' + adapterName);
            queryOpts = { contextPath: adapterMod.outputPath(cwd) };
          } catch (_) {}
        }
      }

      const index = buildSigIndex(cwd, queryOpts);
      if (index.size === 0) {
        console.error('[sigmap] no context file found. Run: node gen-context.js');
        if (adpIdx >= 0) {
          console.error('  (tried the path for --adapter ' + (args[adpIdx + 1] || '') + ')');
        }
        process.exit(1);
      }
      const topIdx = args.indexOf('--top');
      const topK = topIdx >= 0 ? Math.min(Math.max(1, parseInt(args[topIdx + 1], 10) || 10), 25)
                               : ((config && config.retrieval && config.retrieval.topK) || 10);
      const recencyBoost = (config && config.retrieval && config.retrieval.recencyBoost) || 1.5;
      const results = rank(query, index, { topK, recencyBoost, cwd });
      if (args.includes('--context')) {
        const miniCtx  = buildMiniContext(results, cwd);
        const ctxOut   = path.join(cwd, '.context', 'query-context.md');
        fs.mkdirSync(path.dirname(ctxOut), { recursive: true });
        fs.writeFileSync(ctxOut, miniCtx, 'utf8');
        console.log(`[sigmap] query context → ${path.relative(cwd, ctxOut)}  (${estimateTokens(miniCtx)} tokens)`);
      } else if (args.includes('--json')) {
        process.stdout.write(JSON.stringify(formatRankJSON(results, query)) + '\n');
      } else {
        process.stdout.write(formatRankTable(results, query));
      }
    } catch (err) {
      console.error(`[sigmap] query error: ${err.message}`);
      process.exit(1);
    }
    process.exit(0);
  }

  // ── --each [--adapter <name>] ────────────────────────────────────────────
  // Must be checked before --adapter so that --each --adapter <name> is
  // handled here (per-repo) rather than running a single generate on the
  // parent directory.
  if (args.includes('--each')) {
    const VALID_ADAPTERS = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex'];
    const adpIdx = args.indexOf('--adapter');
    const adapterOverride = adpIdx >= 0 ? (args[adpIdx + 1] || '').trim().toLowerCase() : null;
    if (adapterOverride && !VALID_ADAPTERS.includes(adapterOverride)) {
      console.error(`[sigmap] --each: unknown adapter "${adapterOverride}"`);
      console.error(`  Valid adapters: ${VALID_ADAPTERS.join(', ')}`);
      process.exit(1);
    }
    runEach(cwd, config, adapterOverride || null);
    process.exit(0);
  }

  // ── --adapter <name> ───────────────────────────────────────────────────────
  if (args.includes('--adapter')) {
    try {
      const adpIdx = args.indexOf('--adapter');
      const adapterName = (args[adpIdx + 1] || '').trim().toLowerCase();
      const VALID_ADAPTERS = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex'];
      if (!adapterName || adapterName.startsWith('--')) {
        console.error('[sigmap] --adapter requires an adapter name');
        console.error(`  Valid adapters: ${VALID_ADAPTERS.join(', ')}`);
        console.error('  Example: node gen-context.js --adapter copilot');
        process.exit(1);
      }
      if (!VALID_ADAPTERS.includes(adapterName)) {
        console.error(`[sigmap] unknown adapter: "${adapterName}"`);
        console.error(`  Valid adapters: ${VALID_ADAPTERS.join(', ')}`);
        process.exit(1);
      }
      if (args.includes('--json')) {
        const adapterMod = __require('./packages/adapters/' + adapterName);
        const outPath = adapterMod.outputPath(cwd);
        process.stdout.write(JSON.stringify({ adapter: adapterName, outputPath: outPath, version: VERSION }) + '\n');
        process.exit(0);
      }
      // Run generate with this adapter's output targets
      const singleAdapterConfig = Object.assign({}, config, {
        outputs: adapterName === 'claude' ? ['claude'] : [adapterName],
        adapters: [adapterName],
      });
      runGenerate(cwd, singleAdapterConfig, false);
      const adapterMod = __require('./packages/adapters/' + adapterName);
      console.warn(`[sigmap] adapter "${adapterName}" → ${path.relative(cwd, adapterMod.outputPath(cwd))}`);
    } catch (err) {
      console.error(`[sigmap] adapter error: ${err.message}`);
      process.exit(1);
    }
    process.exit(0);
  }

  // ── --impact <file> ────────────────────────────────────────────────────────
  if (args.includes('--impact')) {
    try {
      const impIdx = args.indexOf('--impact');
      const targetFile = (args[impIdx + 1] || '').trim();
      if (!targetFile || targetFile.startsWith('--')) {
        console.error('[sigmap] --impact requires a file path');
        console.error('  Example: node gen-context.js --impact src/extractors/python.js');
        process.exit(1);
      }
      const { analyzeImpact, formatImpact, formatImpactJSON } = requireSourceOrBundled('./src/graph/impact');
      const depthIdx = args.indexOf('--depth');
      const depth = depthIdx >= 0 ? Math.max(0, parseInt(args[depthIdx + 1], 10) || 3) : ((config && config.impact && config.impact.depth) || 3);
      const results = analyzeImpact(targetFile, cwd, { depth });
      if (args.includes('--json')) {
        const out = results.map((r) => formatImpactJSON(r.impact));
        process.stdout.write(JSON.stringify(out.length === 1 ? out[0] : out) + '\n');
      } else {
        for (const r of results) {
          process.stdout.write(formatImpact(r.impact) + '\n');
        }
      }
    } catch (err) {
      console.error(`[sigmap] impact error: ${err.message}`);
      process.exit(1);
    }
    process.exit(0);
  }

  if (args.includes('--report')) {
    if (args.includes('--history')) {
      try {
        const { readLog, summarize } = __require('./src/tracking/logger');
        const entries = readLog(cwd);
        const summary = summarize(entries);
        let chartInfo = null;
        if (args.includes('--chart')) {
          const { score } = __require('./src/health/scorer');
          const { renderHistoryCharts } = requireSourceOrBundled('./src/format/dashboard');
          chartInfo = renderHistoryCharts(cwd, score(cwd));
        }
        if (args.includes('--json')) {
          const payload = chartInfo
            ? {
                ...summary,
                chart: {
                  tokenReductionSparkline: chartInfo.tokenReductionSparkline,
                  hitAt5Sparkline: chartInfo.hitAt5Sparkline,
                  summary: chartInfo.summary,
                  charts: chartInfo.charts,
                },
              }
            : summary;
          process.stdout.write(JSON.stringify(payload) + '\n');
        } else {
          console.log('[sigmap] usage history:');
          console.log(`  total runs      : ${summary.totalRuns}`);
          console.log(`  avg reduction   : ${summary.avgReductionPct}%`);
          console.log(`  avg tokens out  : ~${summary.avgFinalTokens}`);
          console.log(`  over-budget runs: ${summary.overBudgetRuns}`);
          if (summary.firstRun) console.log(`  first run       : ${summary.firstRun}`);
          if (summary.lastRun)  console.log(`  last run        : ${summary.lastRun}`);
          if (chartInfo) {
            console.log('');
            console.log(chartInfo.text);
          }
        }
      } catch (err) {
        console.warn(`[sigmap] tracking: ${err.message}`);
      }
      process.exit(0);
    }
    runGenerate(cwd, config, true, args.includes('--json'));
    process.exit(0);
  }

  if (args.includes('--monorepo') || config.monorepo) {
    runMonorepo(cwd, config);
    process.exit(0);
  }

  if (args.includes('--setup')) {
    runGenerate(cwd, config, false);
    installHook(cwd, scriptPath);
    registerMcp(cwd, scriptPath);
    watchMode(cwd, config);
    return; // keep process alive for watch
  }

  if (args.includes('--watch')) {
    runGenerate(cwd, config, false);
    watchMode(cwd, config);
    return; // keep process alive
  }

  if (args.includes('--diff')) {
    const idx = args.indexOf('--diff');
    const maybeBase = args[idx + 1];
    const baseRef = (maybeBase && !maybeBase.startsWith('--')) ? maybeBase : null;
    runDiff(cwd, config, args.includes('--staged'), baseRef);
    process.exit(0);
  }

  // v4.2: `--cost` — show token/$ cost estimate before and after SigMap
  if (args.includes('--cost')) {
    const rawTok = getRawTokenCount(cwd, config);
    runGenerate(cwd, config, false);

    const model = args[args.indexOf('--model') + 1] || 'gpt-4o';
    const rateK = MODEL_COSTS[model] || MODEL_COSTS['gpt-4o'];

    const ctxPath = config.customOutput
      ? path.resolve(cwd, config.customOutput)
      : path.join(cwd, '.github', 'copilot-instructions.md');
    let outTok = 0;
    try { outTok = estimateTokens(fs.readFileSync(ctxPath, 'utf8')); } catch (_) {}

    const savings = rawTok > 0 ? Math.round((1 - outTok / rawTok) * 100) : 0;
    const costRaw = (rawTok / 1000) * rateK;
    const costCtx = (outTok / 1000) * rateK;

    const out = {
      model,
      rawTokens:     rawTok,
      contextTokens: outTok,
      costRaw:       costRaw.toFixed(4),
      costContext:   costCtx.toFixed(4),
      savingsPct:    savings,
    };

    if (args.includes('--json')) {
      process.stdout.write(JSON.stringify(out) + '\n');
    } else {
      console.log(`\n Cost estimate (${model}):`);
      console.log(` Without SigMap : ${rawTok.toLocaleString()} tok  $${out.costRaw}/query`);
      console.log(` With SigMap    : ${outTok.toLocaleString()} tok  $${out.costContext}/query`);
      console.log(` Savings        : ${savings}%  ($${(costRaw - costCtx).toFixed(4)} saved per query)\n`);
    }
    process.exit(0);
  }

  // v4.3: `--ci [--min-coverage N] [--json]` — GitHub Actions exit gate
  if (args.includes('--ci')) {
    const minCovIdx = args.indexOf('--min-coverage');
    const minCoverage = minCovIdx !== -1 ? Math.max(0, Math.min(100, parseInt(args[minCovIdx + 1], 10) || 80)) : 80;

    // Coverage = files actually in context / total source files
    const { buildSigIndex: ciBuildSigIndex } = requireSourceOrBundled('./src/retrieval/ranker');
    const ciSigIndex  = ciBuildSigIndex(cwd);
    const ciTotal     = buildFileList(cwd, config).length;
    const coveragePct = ciTotal > 0 ? Math.round((ciSigIndex.size / ciTotal) * 100) : 0;

    const pass = coveragePct >= minCoverage;

    if (args.includes('--json')) {
      process.stdout.write(JSON.stringify({ pass, coverage: coveragePct, threshold: minCoverage }) + '\n');
    } else if (pass) {
      console.log(`[sigmap] ✓ CI gate passed — coverage ${coveragePct}% ≥ ${minCoverage}%`);
    } else {
      console.error(`[sigmap] ✗ CI gate FAILED — coverage ${coveragePct}% < ${minCoverage}%`);
      console.error(`  Fix: increase maxTokens or expand srcDirs in gen-context.config.json`);
    }
    process.exit(pass ? 0 : 1);
  }

  // Default: generate once
  runGenerate(cwd, config, false);
}

main();
