seeder.js 8.06 KB
/**
 * Skill Seeder — 从单个 Skill 基因组生成种群变体
 *
 * 解决的问题 (F1/A2):
 *   RFC 要求种群中同一 Skill 有 N 个策略变体同时存在,
 *   通过变异→选择→遗传的竞争来进化。
 *   但此前 forager 只为每个 SKILL.md 创建 1 个基因组 = 种群只有 1 个个体。
 *
 * 生成策略:
 *   给定一个 Gen 0 基因组,通过 LLM 变异产生 N-1 个变体:
 *   - Variant A: 模式库增强(多加 solutionPatterns)
 *   - Variant B: 判断力增强(多加 decisionRules + boundaries)
 *   - Variant C: 伤疤记忆增强(从外部案例中注入 antiPatterns)
 *   - Variant D: 随机混合变异
 *
 * 这样 crossover 就能真正触发(pool.length >= 2)。
 */
const evolutionQueries = require('../queries').evolutionQueries;

function parseDim(raw) {
  if (!raw) return {};
  return typeof raw === 'string' ? JSON.parse(raw) : raw;
}

// ════════════════════════════════════════════════════════
// 维度变异器 (不需 LLM,纯结构变异)
// ════════════════════════════════════════════════════════

/**
 * 策略 A: 增强判断力 — 添加更激进的决策规则
 */
function mutateTowardJudgment(genome) {
  const judgment = parseDim(genome.judgment);
  if (!judgment.decisionRules) judgment.decisionRules = [];
  if (!judgment.boundaries) judgment.boundaries = [];

  // 注入标准变异规则
  const injectedRules = [
    '修改超过 3 个文件时,先用 grep 确认影响面再动手',
    '遇到不熟悉的 API 时,先搜索项目中已有用法而非查文档',
    '如果前 5 步都是信息收集且未发现关键信息,切换搜索策略',
  ];

  const existing = new Set(judgment.decisionRules.map(r => typeof r === 'string' ? r : r.decision || r.rule));
  for (const rule of injectedRules) {
    if (!existing.has(rule)) {
      judgment.decisionRules.push({ situation: 'auto-seeded', decision: rule, confidence: 0.5 });
    }
  }

  if (judgment.boundaries.length === 0) {
    judgment.boundaries.push('不擅长低层优化(内存布局/SIMD)');
    judgment.boundaries.push('不熟悉的框架先做 spike 验证');
  }

  return { judgment: JSON.stringify(judgment), variant_strategy: 'judgment-enhanced' };
}

/**
 * 策略 B: 增强伤疤记忆 — 注入通用 antiPatterns
 */
function mutateTowardScarTissue(genome) {
  const scars = parseDim(genome.scar_tissue);
  if (!scars.antiPatterns) scars.antiPatterns = [];
  if (!scars.antiRationalizations) scars.antiRationalizations = [];

  const genericAntiPatterns = [
    { pattern: '先实现后测试', symptom: '上线后暴雷', correction: 'TDD 或至少先写断言' },
    { pattern: 'try-catch 吞错误', symptom: '问题被掩盖', correction: '至少 log error,不要空 catch' },
    { pattern: '信文档不验证', symptom: 'API 实际行为与文档不符', correction: '先 spike-test' },
  ];

  const existingPatterns = new Set(scars.antiPatterns.map(a => a.pattern));
  for (const ap of genericAntiPatterns) {
    if (!existingPatterns.has(ap.pattern)) scars.antiPatterns.push(ap);
  }

  if (scars.antiRationalizations.length === 0) {
    scars.antiRationalizations.push(
      { excuse: '这个太简单不需要查', rebuttal: '简单任务最容易犯已知错误' },
      { excuse: '没时间写测试', rebuttal: '不写测试花的时间 > 写测试时间的 3 倍' },
    );
  }

  return { scar_tissue: JSON.stringify(scars), variant_strategy: 'scar-enhanced' };
}

/**
 * 策略 C: 增强模式库 — 注入元模式
 */
function mutateTowardPatterns(genome) {
  const patterns = parseDim(genome.pattern_library);
  if (!patterns.solutionPatterns) patterns.solutionPatterns = [];
  if (!patterns.classificationRules) patterns.classificationRules = [];

  const metaPatterns = [
    {
      id: 'explore-then-act',
      applicableWhen: '任何代码修改任务',
      solution: '先 grep/view 理解上下文 → 再 write/replace',
      notApplicableWhen: '只需要回答问题不需要改代码',
    },
    {
      id: 'convention-first',
      applicableWhen: '调用基础设施(HTTP/DB/Auth)',
      solution: '先搜项目中已有用法,复制约定而非用框架默认',
      notApplicableWhen: '项目中没有已有用法',
    },
  ];

  const existingIds = new Set(patterns.solutionPatterns.map(p => p.id));
  for (const mp of metaPatterns) {
    if (!existingIds.has(mp.id)) patterns.solutionPatterns.push(mp);
  }

  return { pattern_library: JSON.stringify(patterns), variant_strategy: 'pattern-enhanced' };
}

/**
 * 策略 D: 混合变异 — 同时微调多个维度
 */
function mutateHybrid(genome) {
  const result = {};
  // 取每个策略的一小部分
  Object.assign(result, mutateTowardJudgment(genome));
  Object.assign(result, mutateTowardScarTissue(genome));
  result.variant_strategy = 'hybrid';
  return result;
}

// 变异策略表
const MUTATION_STRATEGIES = [
  { name: 'judgment-enhanced', mutate: mutateTowardJudgment },
  { name: 'scar-enhanced', mutate: mutateTowardScarTissue },
  { name: 'pattern-enhanced', mutate: mutateTowardPatterns },
  { name: 'hybrid', mutate: mutateHybrid },
];

// ════════════════════════════════════════════════════════
// 公开 API
// ════════════════════════════════════════════════════════

/**
 * 从一个 Gen 0 基因组产生 N 个变体,形成初始种群
 *
 * @param {string} genomeId - 基础基因组 ID
 * @param {string} runId - Evolution Run ID
 * @param {number} populationSize - 目标种群大小(含原始个体)
 * @returns {object[]} 新创建的变体基因组
 */
async function seedPopulation(genomeId, runId, populationSize = 5) {
  const baseGenome = await evolutionQueries.getGenome(genomeId);
  if (!baseGenome) throw new Error(`Base genome ${genomeId} not found`);

  const variantsNeeded = populationSize - 1; // 原始个体已存在
  const variants = [];

  for (let i = 0; i < variantsNeeded && i < MUTATION_STRATEGIES.length; i++) {
    const strategy = MUTATION_STRATEGIES[i];
    const mutations = strategy.mutate(baseGenome);

    // 创建变体基因组
    const variantData = {
      runId,
      skillName: baseGenome.skill_name,
      generation: baseGenome.generation, // 同代
      parentIds: [baseGenome.id],
      mental_models: baseGenome.mental_models,
      pattern_library: mutations.pattern_library || baseGenome.pattern_library,
      judgment: mutations.judgment || baseGenome.judgment,
      tool_fluency: baseGenome.tool_fluency,
      taste: baseGenome.taste,
      scar_tissue: mutations.scar_tissue || baseGenome.scar_tissue,
    };

    const variant = await evolutionQueries.insertGenome(variantData);
    variants.push({ ...variant, strategy: strategy.name });

    console.log(`  🌱 ${baseGenome.skill_name} 变体 ${i + 1}: ${strategy.name} (${variant.id.slice(0, 8)})`);
  }

  return variants;
}

/**
 * 对 run 中所有 Gen 0 基因组进行种群初始化
 */
async function seedRunPopulation(runId, populationSize = 5) {
  const genomes = await evolutionQueries.getGenomesByRun(runId, { generation: 0, aliveOnly: true });
  console.log(`🌱 种群初始化: ${genomes.length} 个 Skill × ${populationSize} 变体\n`);

  let totalVariants = 0;

  for (const genome of genomes) {
    try {
      const variants = await seedPopulation(genome.id, runId, populationSize);
      totalVariants += variants.length;
    } catch (err) {
      console.error(`  ⚠️  ${genome.skill_name}: ${err.message}`);
    }
  }

  console.log(`\n✅ 种群初始化完成: ${totalVariants} 个变体`);
  return totalVariants;
}

module.exports = {
  seedPopulation,
  seedRunPopulation,
  // 暴露策略用于测试
  MUTATION_STRATEGIES,
  mutateTowardJudgment,
  mutateTowardScarTissue,
  mutateTowardPatterns,
  mutateHybrid,
};