#!/usr/bin/env node /** * scripts/extract-agent-prompts.js * * 从 docs/prd/newfunction/agents-{role}.html 抽取 §2.2 "完整 System Prompt" * 区块,写到 system/agents/devloop-{role}/prompt.md。 * * role ∈ pm | architect | coder | tester | integrator | triager * * 抽取规则(本脚本只做规则正向匹配,不依赖 cheerio 来保留原始换行): * 1. 定位
...的内层 * 3. 去除全部 ... 包装,保留文本 * 4. 反转义 HTML entity (< > & ") * * 规格: docs/prd/newfunction/plan-migration.md · P0.B */ const fs = require('fs'); const path = require('path'); const ROLES = ['pm', 'architect', 'coder', 'tester', 'integrator', 'triager']; const SRC_DIR = path.resolve(__dirname, '../docs/prd/newfunction'); const OUT_DIR = path.resolve(__dirname, '../system/agents'); function decodeHtmlEntities(s) { return s .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, "'") .replace(/'/g, "'") .replace(/&/g, '&'); } function stripSpans(s) { // 去除 ...,保留内部文本(可能嵌套也用同一规则处理) let prev; let cur = s; do { prev = cur; cur = cur.replace(/]*>([\s\S]*?)<\/span>/g, '$1'); } while (cur !== prev); // 去除残留的开闭标签 (容错) cur = cur.replace(/<\/?span[^>]*>/g, ''); return cur; } function extractPrompt(html, role) { const anchorRe = /
]*>([\s\S]*?)<\/pre>/;
const pm = after.match(preRe);
if (!pm) throw new Error(` after §2.2 not found in agents-${role}.html`);
const inner = pm[1];
const stripped = stripSpans(inner);
const decoded = decodeHtmlEntities(stripped);
return decoded.trim();
}
function run() {
fs.mkdirSync(OUT_DIR, { recursive: true });
const results = [];
for (const role of ROLES) {
const src = path.join(SRC_DIR, `agents-${role}.html`);
if (!fs.existsSync(src)) {
results.push({ role, ok: false, reason: 'source missing: ' + src });
continue;
}
try {
const html = fs.readFileSync(src, 'utf8');
const prompt = extractPrompt(html, role);
const outDir = path.join(OUT_DIR, `devloop-${role}`);
fs.mkdirSync(outDir, { recursive: true });
const outPath = path.join(outDir, 'prompt.md');
const header = `\n\n`;
fs.writeFileSync(outPath, header + prompt + '\n');
results.push({ role, ok: true, bytes: prompt.length, path: outPath });
} catch (e) {
results.push({ role, ok: false, reason: e.message });
}
}
for (const r of results) {
if (r.ok) console.log(`✓ devloop-${r.role}.md (${r.bytes} bytes)`);
else console.error(`✗ ${r.role}: ${r.reason}`);
}
const allOk = results.every(r => r.ok);
if (!allOk) process.exit(1);
}
if (require.main === module) run();
module.exports = { extractPrompt, stripSpans, decodeHtmlEntities };