graph-seed.js 4.42 KB
#!/usr/bin/env node

/**
 * graph-seed.js — seeds data/graph.sqlite from the on-disk agent/template/company sources.
 *
 * 依次跑 reconcileDanglingRows()(P3B W1)→ seedGlobalAgents()(T1)→ seedTemplates()(T2)
 * → seedCompanyAgents()(T3)→ verifyCounts()(T4,独立重新扫描目录/文件做行数交叉核对)。
 * 打印按 source_kind 分组的汇总报告;核对不一致时以非零状态码退出。
 *
 * 前四步里的前三步(reconcile + global + templates)服务启动时会自动跑一遍
 * (graph-seed-service.runStartupSeedSafely,见 server/index.js);本脚本比启动多跑
 * seedCompanyAgents()——那一步会从 company.json 反推 org_graph_nodes,属于手动补种,
 * 不放进启动流程(理由见 runStartupSeed 的注释)。
 *
 * 用法:
 *   node scripts/graph-seed.js
 */

const {
  reconcileDanglingRows, seedGlobalAgents, seedTemplates, seedCompanyAgents, verifyCounts,
} = require('../server/services/graph-seed-service');

function main() {
  console.log('[graph-seed] 开始种子导入\n');

  const reconciled = reconcileDanglingRows();
  console.log(`[reconcile] 清理悬空 company_hired 行: ${reconciled.agentsRemoved.length} 条${reconciled.agentsRemoved.length ? ` (${reconciled.agentsRemoved.join(', ')})` : ''}`);
  console.log(`  已删公司的 org 节点/边: ${reconciled.orgNodesRemoved}/${reconciled.orgEdgesRemoved} 行${reconciled.ghostCompanyIds.length ? `,涉及公司 ${reconciled.ghostCompanyIds.join(', ')}` : ''}`);
  if (reconciled.retained.length > 0) {
    console.log(`  ⚠️  被外键引用、暂时保留 (${reconciled.retained.length}): ${reconciled.retained.map(r => `${r.id} — ${r.reason}`).join('; ')}`);
  }

  const globalAgents = seedGlobalAgents();
  console.log(`[global_agent] 扫描 system/agents/* 目录: ${globalAgents.scanned} 个,含 agent.md 且已导入: ${globalAgents.seeded} 个`);
  console.log(`  ids: ${globalAgents.ids.join(', ')}`);

  const templates = seedTemplates();
  console.log(`\n[template] 扫描 system/templates/* 目录: ${templates.scanned} 个,已导入: ${templates.seeded} 个`);
  if (templates.reclaimed.length > 0) {
    console.log(`  ♻️  从悬空 company_hired 占位接管回 template 的 ids (${templates.reclaimed.length}): ${templates.reclaimed.join(', ')}`);
  }
  console.log(`  registry 匹配到 benchmark_score 的 ids (${templates.registryMatched.length}): ${templates.registryMatched.join(', ')}`);
  if (templates.skipped.length > 0) {
    console.log(`  ⚠️  id 与其他 source_kind 冲突,已跳过写入 (${templates.skipped.length}): ${templates.skipped.map(s => `${s.id}(已属于 ${s.existingSourceKind})`).join(', ')}`);
  }

  const companyAgents = seedCompanyAgents();
  console.log(`\n[company_hired] 扫描到 ${companyAgents.companies.length} 家有 company.json 的公司: ${companyAgents.companies.join(', ')}`);
  console.log(`  org_graph_nodes 本次新建: ${companyAgents.nodesCreated} 行,已存在跳过: ${companyAgents.nodesExisting} 行`);
  console.log(`  引用已有 system_agents 行 (${companyAgents.reusedSystemAgents.length}): ${companyAgents.reusedSystemAgents.join(', ')}`);
  console.log(`  新建 scope='company' 行 (${companyAgents.newSystemAgents.length}): ${companyAgents.newSystemAgents.join(', ')}`);

  console.log('\n[verify] 行数对账(独立重新扫描目录/文件,不复用上面的计数器)');
  const v = verifyCounts();
  console.log(`  system_agents[global_agent]: 期望 ${v.expected.global_agent},实际 ${v.actual.global_agent}`);
  console.log(`  system_agents[template]:     期望 ${v.expected.template},实际 ${v.actual.template}`);
  console.log(`  system_agents[company_hired]:期望 ${v.expected.company_hired},实际 ${v.actual.company_hired}`);
  const companyIds = Object.keys({ ...v.expected.nodesPerCompany, ...v.actual.nodesPerCompany });
  for (const companyId of companyIds) {
    const expected = v.expected.nodesPerCompany[companyId] || 0;
    const actual = v.actual.nodesPerCompany[companyId] || 0;
    console.log(`  org_graph_nodes[${companyId}]: 期望 ${expected},实际 ${actual}`);
  }

  if (!v.ok) {
    console.error('\n❌ 行数对账失败,发现以下不一致:');
    for (const m of v.mismatches) {
      console.error(`  - ${m.check}: 期望 ${m.expected},实际 ${m.actual}`);
    }
    process.exitCode = 1;
    return;
  }

  console.log('\n✅ 种子导入完成,行数对账通过');
}

main();