migrate-memory-raw-section.js 4.63 KB
#!/usr/bin/env node
/**
 * 一次性迁移:把 `memory.md` 里**自动追加**的那些行,从 `## 关键事实`
 * 搬到 `## 原始记录(未提炼)`。
 *
 * ── 为什么 ────────────────────────────────────────────────────────────
 * 旧的 `appendToMemoryMd` 取任务与结果的第一行、砍到 80/100 字符,写进「关键事实」。
 * 那不是提炼,是搬运 + 截断。实查(2026-08-20)现有内容里**没有一条真事实**:
 *
 *     - [2026-08-18] [SYSTEM HEARTBEAT] → 🔧 [TOOL_CALL] Bash (ID: toolu_01M1t4...)
 *     - [2026-04-09] [TASK from master] → ...ates/community-manager/train/evolve/SKILL_v1.md` |
 *
 * 前者是工具调用 id,后者是**半句**(`...` 开头,主语被截掉了)。
 * 而 `memory.md` 会被注入**每一轮**提示词 —— 也就是说这些东西一直在冒充事实。
 *
 * 写入侧已改(29-DREAM §四 步骤 1),但**存量不会自己搬家**。这个脚本补那一步。
 *
 * ── 判据(刻意窄)────────────────────────────────────────────────────
 * 只搬形如 `- [YYYY-MM-DD] … → …` 的行 —— 那是旧自动追加的**唯一**形状。
 * 人手写的事实不长这样,不动它。判不准就不搬:留在原地最多是「多了一条噪声」,
 * 搬错则是把人写的东西降级成日志。
 *
 * ── 用法 ──────────────────────────────────────────────────────────────
 *   node scripts/migrate-memory-raw-section.js           # dry-run,只打印
 *   node scripts/migrate-memory-raw-section.js --apply   # 真改(先自动备份到 .bak)
 */

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

const ROOT = path.resolve(__dirname, '..');
const AGENTS = path.join(ROOT, 'system', 'agents');
const RAW_SECTION = '## 原始记录(未提炼)';
/** 旧自动追加的唯一形状 */
const AUTO_LINE = /^-\s*\[\d{4}-\d{2}-\d{2}\]\s.*→/;

const apply = process.argv.includes('--apply');

/** 找出所有 memory.md(本体根 + 公司态两层都可能有) */
function findMemoryFiles() {
  const out = [];
  const walk = (dir, depth) => {
    if (depth > 3) return;
    let entries;
    try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
    for (const e of entries) {
      const p = path.join(dir, e.name);
      if (e.isDirectory()) walk(p, depth + 1);
      else if (e.name === 'memory.md' && path.basename(dir) === 'memory') out.push(p);
    }
  };
  walk(AGENTS, 0);
  return out;
}

function migrate(text) {
  const factsIdx = text.indexOf('## 关键事实');
  if (factsIdx < 0) return null;
  const head = text.slice(0, factsIdx);
  const rest = text.slice(factsIdx + '## 关键事实'.length);
  const body = rest.split(/\n## /)[0];
  const tail = rest.slice(body.length);

  const lines = body.split('\n');
  const moved = lines.filter((l) => AUTO_LINE.test(l.trim()));
  if (moved.length === 0) return null;
  const kept = lines.filter((l) => !AUTO_LINE.test(l.trim()));

  // 「关键事实」清空后要留占位,否则下游读到空段会以为文件坏了
  const keptText = kept.join('\n').trim() || '\n_(尚无提炼产物)_\n';
  let out = `${head}## 关键事实\n${keptText.startsWith('\n') ? keptText : `\n${keptText}\n`}${tail}`;

  // 合并进已有的原始记录段;没有就补一个(放最后,提炼产物的位置在前)
  if (out.includes(RAW_SECTION)) {
    out = out.replace(
      new RegExp(`${RAW_SECTION}\\n([\\s\\S]*?)(?=\\n## |$)`),
      (m, existing) => `${RAW_SECTION}\n${moved.join('\n')}\n${existing.trim() ? `${existing.trim()}\n` : ''}`
    );
  } else {
    out = `${out.replace(/\s*$/, '')}\n\n${RAW_SECTION}\n${moved.join('\n')}\n`;
  }
  return { out, movedCount: moved.length };
}

let files = 0; let lines = 0;
for (const f of findMemoryFiles()) {
  const text = fs.readFileSync(f, 'utf-8');
  const r = migrate(text);
  if (!r) continue;
  files += 1; lines += r.movedCount;
  console.log(`${apply ? '改' : '待改'} ${path.relative(ROOT, f)}  搬 ${r.movedCount} 行`);
  if (apply) {
    fs.writeFileSync(`${f}.bak`, text, 'utf-8'); // 这些是 gitignore 的运行态,没有 git 历史兜底
    fs.writeFileSync(f, r.out, 'utf-8');
  }
}
console.log(`\n${apply ? '已改' : 'dry-run'}:${files} 个文件 / ${lines} 行`);
if (!apply && files) console.log('要真改:node scripts/migrate-memory-raw-section.js --apply');