extract-agent-prompts.js
3.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/bin/env node
/**
* scripts/extract-agent-prompts.js
*
* 从 docs/prd/newfunction/agents-{role}.html 抽取 §2.2 "完整 System Prompt"
* 区块,写到 system/templates/devloop-{role}/prompt.md。
*
* role ∈ pm | architect | coder | tester | integrator | triager
*
* 抽取规则(本脚本只做规则正向匹配,不依赖 cheerio 来保留原始换行):
* 1. 定位 <h3>2.2 完整 System Prompt</h3>
* 2. 取其后<b>第一个</b> <pre class="code">...</pre> 的内层
* 3. 去除全部 <span class="...">...</span> 包装,保留文本
* 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/templates');
function decodeHtmlEntities(s) {
return s
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/'/g, "'")
.replace(/&/g, '&');
}
function stripSpans(s) {
// 去除 <span class="...">...</span>,保留内部文本(可能嵌套也用同一规则处理)
let prev;
let cur = s;
do {
prev = cur;
cur = cur.replace(/<span\b[^>]*>([\s\S]*?)<\/span>/g, '$1');
} while (cur !== prev);
// 去除残留的开闭标签 (容错)
cur = cur.replace(/<\/?span[^>]*>/g, '');
return cur;
}
function extractPrompt(html, role) {
const anchorRe = /<h3[^>]*>\s*2\.2\s+完整\s*System Prompt[^<]*<\/h3>/;
const am = html.match(anchorRe);
if (!am) throw new Error(`§2.2 header not found in agents-${role}.html`);
const after = html.slice(am.index + am[0].length);
const preRe = /<pre\s+class="code"[^>]*>([\s\S]*?)<\/pre>/;
const pm = after.match(preRe);
if (!pm) throw new Error(`<pre class="code"> 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 = `<!-- AUTO-GENERATED by scripts/extract-agent-prompts.js. Edit agents-${role}.html §2.2 instead. -->\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 };