claude-coder 1.6.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/prompts.js CHANGED
@@ -1,339 +1,295 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { paths, loadConfig, getProjectRoot } = require('./config');
6
- const { loadTasks, findNextTask, getStats } = require('./tasks');
7
-
8
- /**
9
- * Build system prompt by combining template files.
10
- * @param {boolean} includeScanProtocol - Whether to append SCAN_PROTOCOL.md
11
- */
12
- function buildSystemPrompt(includeScanProtocol = false) {
13
- const p = paths();
14
- let prompt = fs.readFileSync(p.claudeMd, 'utf8');
15
- if (includeScanProtocol && fs.existsSync(p.scanProtocol)) {
16
- prompt += '\n\n' + fs.readFileSync(p.scanProtocol, 'utf8');
17
- }
18
- return prompt;
19
- }
20
-
21
- /**
22
- * Build user prompt for coding sessions.
23
- * Includes conditional hints based on session state.
24
- */
25
- function buildCodingPrompt(sessionNum, opts = {}) {
26
- const p = paths();
27
- const config = loadConfig();
28
- const consecutiveFailures = opts.consecutiveFailures || 0;
29
-
30
- // Hint 1: Playwright MCP availability
31
- const mcpHint = config.mcpPlaywright
32
- ? '前端/全栈任务可用 Playwright MCP(browser_navigate、browser_snapshot、browser_click 等)做端到端测试。'
33
- : '';
34
-
35
- // Hint 2: Retry context from previous failures
36
- let retryContext = '';
37
- if (consecutiveFailures > 0 && opts.lastValidateLog) {
38
- retryContext = `\n注意:上次会话校验失败,原因:${opts.lastValidateLog}。请避免同样的问题。`;
39
- }
40
-
41
- // Hint 3: Environment readiness
42
- let envHint = '';
43
- if (consecutiveFailures === 0 && sessionNum > 1) {
44
- envHint = '环境已就绪,第二步可跳过 claude-coder init,仅确认服务存活。涉及新依赖时仍需运行 claude-coder init。';
45
- }
46
-
47
- // Hint 4: Existing test records
48
- let testHint = '';
49
- if (fs.existsSync(p.testsFile)) {
50
- try {
51
- const count = (JSON.parse(fs.readFileSync(p.testsFile, 'utf8')).test_cases || []).length;
52
- if (count > 0) testHint = `tests.json 已有 ${count} 条验证记录,Step 5 时先查已有记录避免重复验证。`;
53
- } catch { /* ignore */ }
54
- }
55
-
56
- // Hint 5: Project documentation awareness + profile quality check
57
- let docsHint = '';
58
- if (fs.existsSync(p.profile)) {
59
- try {
60
- const profile = JSON.parse(fs.readFileSync(p.profile, 'utf8'));
61
- const docs = profile.existing_docs || [];
62
- if (docs.length > 0) {
63
- docsHint = `项目文档: ${docs.join(', ')}。Step 4 编码前先读与任务相关的文档,了解接口约定和编码规范。完成后若新增了模块或 API,更新对应文档。`;
64
- }
65
- if (profile.tech_stack?.backend?.framework &&
66
- (!profile.services || profile.services.length === 0)) {
67
- docsHint += ' 注意:project_profile.json 的 services 为空,请在本次 session 末尾补全 services 数组(command, port, health_check)。';
68
- }
69
- if (!docs.length) {
70
- docsHint += ' 注意:project_profile.json 的 existing_docs 为空,请在 Step 6 收尾时补全文档列表。';
71
- }
72
- } catch { /* ignore */ }
73
- }
74
-
75
- // Hint 6: Task context (harness pre-read, saves Agent 2-3 Read calls)
76
- const projectRoot = getProjectRoot();
77
- let taskHint = '';
78
- try {
79
- const taskData = loadTasks();
80
- if (taskData) {
81
- const next = findNextTask(taskData);
82
- const stats = getStats(taskData);
83
- if (next) {
84
- taskHint = `任务上下文: ${next.id} "${next.description}" (${next.status}), ` +
85
- `category=${next.category}, steps=${next.steps.length}步。` +
86
- `进度: ${stats.done}/${stats.total} done, ${stats.failed} failed。` +
87
- `项目绝对路径: ${projectRoot}。运行时目录: ${projectRoot}/.claude-coder/(隐藏目录)。` +
88
- `第一步无需读取 tasks.json(已注入),直接确认任务后进入 Step 2。`;
89
- }
90
- }
91
- } catch { /* ignore */ }
92
-
93
- // Hint 6b: Test environment variables (readable + writable by Agent)
94
- let testEnvHint = '';
95
- if (p.testEnvFile && fs.existsSync(p.testEnvFile)) {
96
- testEnvHint = `测试凭证文件: ${projectRoot}/.claude-coder/test.env(含 API Key、测试账号等),测试前用 source ${projectRoot}/.claude-coder/test.env 加载。发现新凭证需求时可追加写入(KEY=value 格式)。`;
97
- } else {
98
- testEnvHint = `如需持久化测试凭证(API Key、测试账号密码等),写入 ${projectRoot}/.claude-coder/test.env(KEY=value 格式,每行一个)。后续 session 会自动感知。`;
99
- }
100
-
101
- // Hint 6c: Playwright mode awareness
102
- let playwrightAuthHint = '';
103
- if (config.mcpPlaywright) {
104
- const mode = config.playwrightMode;
105
- switch (mode) {
106
- case 'persistent':
107
- playwrightAuthHint = 'Playwright MCP 使用 persistent 模式(user-data-dir),浏览器登录状态持久保存在本地配置中,无需额外登录操作。';
108
- break;
109
- case 'isolated':
110
- playwrightAuthHint = fs.existsSync(p.playwrightAuth)
111
- ? `Playwright MCP 使用 isolated 模式,已检测到登录状态文件(playwright-auth.json),每次会话自动加载 cookies 和 localStorage。`
112
- : 'Playwright MCP 使用 isolated 模式,但未检测到登录状态文件。如目标页面需要登录,请先运行 claude-coder auth <URL>。';
113
- break;
114
- case 'extension':
115
- playwrightAuthHint = 'Playwright MCP 使用 extension 模式,已连接用户真实浏览器,直接复用浏览器已有的登录态和扩展。注意:操作会影响用户正在使用的浏览器。';
116
- break;
117
- }
118
- }
119
-
120
- // Hint 7: Session memory (read flat session_result.json)
121
- let memoryHint = '';
122
- if (fs.existsSync(p.sessionResult)) {
123
- try {
124
- const sr = JSON.parse(fs.readFileSync(p.sessionResult, 'utf8'));
125
- if (sr?.session_result) {
126
- memoryHint = `上次会话: ${sr.session_result}(${sr.status_before || '?'} ${sr.status_after || '?'})` +
127
- (sr.notes ? `, 要点: ${sr.notes.slice(0, 150)}` : '') + '。';
128
- }
129
- } catch { /* ignore */ }
130
- }
131
-
132
- // Hint 8: Service management (continuous vs single-shot mode)
133
- const maxSessions = opts.maxSessions || 50;
134
- const serviceHint = maxSessions === 1
135
- ? '单次模式:收尾时停止所有后台服务。'
136
- : '连续模式:收尾时不要停止后台服务,保持服务运行以便下个 session 继续使用。';
137
-
138
- // Hint 9: Tool usage guidance (critical for non-Claude models)
139
- const toolGuidance = [
140
- '可用工具与使用规范(严格遵守):',
141
- '- 搜索文件名: Glob(如 **/*.ts),禁止 bash find',
142
- '- 搜索文件内容: Grep(正则,基于 ripgrep),禁止 bash grep',
143
- '- 读文件: Read(支持批量多文件同时读取),禁止 bash cat/head/tail',
144
- '- 列目录: LS,禁止 bash ls',
145
- '- 编辑文件: 同一文件多处修改用 MultiEdit(一次原子调用),单处用 Edit',
146
- '- 复杂搜索: Task(启动子 Agent 并行搜索,不消耗主 context),适合开放式探索',
147
- '- 查文档/API: WebSearch + WebFetch',
148
- '- 效率: 多个 Read/Glob/Grep 尽量合并为一次批量调用,减少工具轮次',
149
- ].join('\n');
150
-
151
- return [
152
- `Session ${sessionNum}。执行 6 步流程。`,
153
- '效率要求:先规划后编码,完成全部编码后再统一测试,禁止编码-测试反复跳转。后端任务用 curl 验证,不启动浏览器。',
154
- mcpHint,
155
- testHint,
156
- docsHint,
157
- envHint,
158
- taskHint,
159
- testEnvHint,
160
- playwrightAuthHint,
161
- memoryHint,
162
- serviceHint,
163
- toolGuidance,
164
- `完成后写入 session_result.json。${retryContext}`,
165
- ].filter(Boolean).join('\n');
166
- }
167
-
168
- /**
169
- * Build task decomposition guide for scan and add sessions.
170
- * Placed in user prompt (recency zone) for maximum attention.
171
- */
172
- function buildTaskGuide(projectType) {
173
- const lines = [
174
- '任务分解指导(严格遵守):',
175
- '1. 粒度:每个任务是独立可测试的功能单元,1-3 session 可完成,不超 500 行新增',
176
- '2. steps 具体可验证:最后一步必须是 curl/grep 等验证命令',
177
- '3. depends_on 形成 DAG(有向无环图),不得循环依赖',
178
- '4. 单任务 steps 不超过 5 步,超过则拆分为多个任务',
179
- '5. category 准确:backend | frontend | fullstack | infra',
180
- '6. 第一个任务从第一个有业务逻辑的功能开始,不重复脚手架内容',
181
- '',
182
- '验证命令模板:',
183
- ' API: curl -s -o /dev/null -w "%{http_code}" http://localhost:PORT/path → 200',
184
- ' 文件: grep -q "关键内容" path/to/file && echo "pass"',
185
- ' 构建: npm run build 2>&1 | tail -1 → 无 error',
186
- '',
187
- '反面案例(禁止出现):',
188
- ' X "实现用户功能" → 太模糊,应拆为具体接口',
189
- ' X "编写测试" 无具体内容,测试应内嵌在 steps 末尾',
190
- ' X steps 只有 "实现xxx" 没有验证步骤',
191
- ];
192
-
193
- if (projectType === 'new') {
194
- lines.push('', '新项目注意:infra 任务合并为尽量少的条目,不拆碎');
195
- }
196
- return lines.join('\n');
197
- }
198
-
199
- /**
200
- * Build user prompt for scan sessions.
201
- */
202
- function buildScanPrompt(projectType, requirement) {
203
- const taskGuide = buildTaskGuide(projectType);
204
- return [
205
- '你是项目初始化 Agent,同时也是资深的需求分析师。',
206
- '',
207
- `项目类型: ${projectType}`,
208
- `用户需求: ${requirement || '(无指定需求)'}`,
209
- '',
210
- '步骤 1-2:按「项目扫描协议」扫描项目、生成 project_profile.json。',
211
- '',
212
- 'profile 质量要求(必须遵守,harness 会校验):',
213
- '- services 数组必须包含所有可启动服务(command、port、health_check),不得为空',
214
- '- existing_docs 必须列出所有实际存在的文档路径',
215
- '- 检查 .claude/CLAUDE.md 是否存在,若无则生成(WHAT/WHY/HOW 格式:技术栈、关键决策、开发命令、关键路径、编码规则),并加入 existing_docs',
216
- '- scan_files_checked 必须列出所有实际扫描过的文件',
217
- '',
218
- '步骤 3:根据以下指导分解任务到 tasks.json(格式见 CLAUDE.md):',
219
- '',
220
- taskGuide,
221
- '',
222
- '步骤 4:写入 session_result.json git commit。',
223
- ].join('\n');
224
- }
225
-
226
- /**
227
- * Build lightweight system prompt for add sessions.
228
- * Add sessions only decompose requirements — no coding workflow needed.
229
- * CLAUDE.md is NOT injected to avoid role conflict and save ~2000 tokens.
230
- */
231
- function buildAddSystemPrompt() {
232
- return '你是一个任务分解专家,擅长将模糊需求拆解为结构化、可执行的原子任务。你只分析需求和分解任务,不实现任何代码。';
233
- }
234
-
235
- /**
236
- * Build user prompt for add sessions.
237
- * Structure: Role (primacy) Context → CoT → TaskGuide → Instruction (recency)
238
- */
239
- function buildAddPrompt(instruction) {
240
- const p = paths();
241
- const projectRoot = getProjectRoot();
242
- const taskGuide = buildTaskGuide();
243
-
244
- // --- Context injection: pre-read project state ---
245
- let profileContext = '';
246
- if (fs.existsSync(p.profile)) {
247
- try {
248
- const profile = JSON.parse(fs.readFileSync(p.profile, 'utf8'));
249
- const stack = profile.tech_stack || {};
250
- const parts = [];
251
- if (stack.backend?.framework) parts.push(`后端: ${stack.backend.framework}`);
252
- if (stack.frontend?.framework) parts.push(`前端: ${stack.frontend.framework}`);
253
- if (stack.backend?.language) parts.push(`语言: ${stack.backend.language}`);
254
- if (parts.length) profileContext = `项目技术栈: ${parts.join(', ')}`;
255
- } catch { /* ignore */ }
256
- }
257
-
258
- let taskContext = '';
259
- let recentExamples = '';
260
- try {
261
- const taskData = loadTasks();
262
- if (taskData) {
263
- const stats = getStats(taskData);
264
- const features = taskData.features || [];
265
- const maxId = features.length ? features[features.length - 1].id : 'feat-000';
266
- const maxPriority = features.length ? Math.max(...features.map(f => f.priority || 0)) : 0;
267
- const categories = [...new Set(features.map(f => f.category))].join(', ');
268
-
269
- taskContext = `已有 ${stats.total} 个任务(${stats.done} done, ${stats.pending} pending, ${stats.failed} failed)。` +
270
- `最大 id: ${maxId}, 最大 priority: ${maxPriority}。已有 category: ${categories}。`;
271
-
272
- const recent = features.slice(-3);
273
- if (recent.length) {
274
- recentExamples = '已有任务格式参考(保持一致性):\n' +
275
- recent.map(f => ` ${f.id}: "${f.description}" (category=${f.category}, steps=${f.steps.length}步, depends_on=[${f.depends_on.join(',')}])`).join('\n');
276
- }
277
- }
278
- } catch { /* ignore */ }
279
-
280
- // --- Conditional: Playwright test rule hint ---
281
- let testRuleHint = '';
282
- const testRulePath = path.join(p.loopDir, 'test_rule.md');
283
- const hasMcp = fs.existsSync(p.mcpConfig);
284
- if (fs.existsSync(testRulePath) && hasMcp) {
285
- testRuleHint = [
286
- '【Playwright 测试规则】项目已配置 Playwright MCP(.mcp.json),' +
287
- '`.claude-coder/test_rule.md` 中包含通用测试指导规则(Smart Snapshot、Token 预算控制、三步测试方法论、等待策略等)。',
288
- '当任务涉及端到端测试时:',
289
- ' - 在 steps 中第一步加入「阅读 .claude-coder/test_rule.md 了解测试规范和成本控制」',
290
- ' - 测试步骤按 test_rule.md 中的 tasks.json 模板格式编写(含环境检查、优先级标注、预算控制)',
291
- ' - 设定合理的 test_tier(unit/smoke/regression/full_e2e)',
292
- ].join('\n');
293
- }
294
-
295
- return [
296
- // --- Primacy zone: role + identity ---
297
- '你是资深需求分析师,擅长将模糊需求分解为可执行的原子任务。',
298
- '这是任务追加 session,不是编码 session。你只分解任务,不实现代码。',
299
- '',
300
-
301
- // --- Context layer ---
302
- profileContext,
303
- taskContext,
304
- recentExamples,
305
- `项目绝对路径: ${projectRoot}`,
306
- '',
307
-
308
- // --- CoT: explicit thinking steps ---
309
- '执行步骤(按顺序,不可跳过):',
310
- '1. 读取 .claude-coder/tasks.json 和 .claude-coder/project_profile.json,全面了解项目现状',
311
- '2. 分析用户指令:识别核心功能点,判断是单任务还是需要拆分为多任务',
312
- '3. 检查重复:对比已有任务,避免功能重叠',
313
- '4. 确定依赖:新任务的 depends_on 引用已有或新增任务的 id,形成 DAG',
314
- '5. 分解任务:每个任务对应一个独立可测试的功能单元,description 简明(40字内),steps 具体可操作',
315
- '6. 追加到 tasks.json,id 和 priority 从已有最大值递增,status: pending',
316
- '7. git add -A && git commit -m "chore: add new tasks"',
317
- '8. 写入 session_result.json(格式:{ "session_result": "success", "status_before": "N/A", "status_after": "N/A", "notes": "追加了 N 个任务:简述" })',
318
- '',
319
-
320
- // --- Quality constraints ---
321
- taskGuide,
322
- '',
323
- testRuleHint,
324
- '不修改已有任务,不实现代码。',
325
- '',
326
-
327
- // --- Recency zone: user instruction (highest attention) ---
328
- `用户指令:${instruction}`,
329
- ].filter(Boolean).join('\n');
330
- }
331
-
332
- module.exports = {
333
- buildSystemPrompt,
334
- buildCodingPrompt,
335
- buildTaskGuide,
336
- buildScanPrompt,
337
- buildAddSystemPrompt,
338
- buildAddPrompt,
339
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { paths, loadConfig, getProjectRoot } = require('./config');
6
+ const { loadTasks, findNextTask, getStats } = require('./tasks');
7
+
8
+ // --------------- Template Engine ---------------
9
+
10
+ /**
11
+ * Replace {{key}} placeholders with values from vars object.
12
+ * - Uses Object.prototype.hasOwnProperty.call to prevent prototype pollution
13
+ * - Coerces values to string via String() for type safety
14
+ * - Collapses 3+ consecutive newlines (from empty variables) into double newline
15
+ * - Only matches \w+ inside {{ }}, so JSON braces and %{...} are safe
16
+ */
17
+ function renderTemplate(template, vars = {}) {
18
+ return template
19
+ .replace(/\{\{(\w+)\}\}/g, (_, key) =>
20
+ Object.prototype.hasOwnProperty.call(vars, key) ? String(vars[key]) : ''
21
+ )
22
+ .replace(/^\s+$/gm, '') // remove lines that became whitespace-only after replacement
23
+ .replace(/\n{3,}/g, '\n\n') // collapse 3+ consecutive newlines into double
24
+ .trim();
25
+ }
26
+
27
+ /**
28
+ * Read a prompt template file and render it with variables.
29
+ * Falls back to empty string if file doesn't exist.
30
+ */
31
+ function loadAndRender(filepath, vars = {}) {
32
+ if (!fs.existsSync(filepath)) return '';
33
+ const template = fs.readFileSync(filepath, 'utf8');
34
+ return renderTemplate(template, vars);
35
+ }
36
+
37
+ // --------------- System Prompt ---------------
38
+
39
+ /**
40
+ * Build system prompt by combining prompt files.
41
+ * CLAUDE.md and SCAN_PROTOCOL.md are read as-is (no variable injection).
42
+ */
43
+ function buildSystemPrompt(includeScanProtocol = false) {
44
+ const p = paths();
45
+ let prompt = fs.readFileSync(p.claudeMd, 'utf8');
46
+ if (includeScanProtocol && fs.existsSync(p.scanProtocol)) {
47
+ prompt += '\n\n' + fs.readFileSync(p.scanProtocol, 'utf8');
48
+ }
49
+ return prompt;
50
+ }
51
+
52
+ // --------------- Coding Session ---------------
53
+
54
+ /**
55
+ * Build user prompt for coding sessions.
56
+ * Computes conditional hints, then injects them into coding_user.md template.
57
+ */
58
+ function buildCodingPrompt(sessionNum, opts = {}) {
59
+ const p = paths();
60
+ const config = loadConfig();
61
+ const consecutiveFailures = opts.consecutiveFailures || 0;
62
+ const projectRoot = getProjectRoot();
63
+
64
+ // Hint 1: Playwright MCP availability
65
+ const mcpHint = config.mcpPlaywright
66
+ ? '前端/全栈任务可用 Playwright MCP(browser_navigate、browser_snapshot、browser_click 等)做端到端测试。'
67
+ : '';
68
+
69
+ // Hint 2: Retry context from previous failures
70
+ let retryContext = '';
71
+ if (consecutiveFailures > 0 && opts.lastValidateLog) {
72
+ retryContext = `\n注意:上次会话校验失败,原因:${opts.lastValidateLog}。请避免同样的问题。`;
73
+ }
74
+
75
+ // Hint 3: Environment readiness
76
+ let envHint = '';
77
+ if (consecutiveFailures === 0 && sessionNum > 1) {
78
+ envHint = '环境已就绪,第二步可跳过 claude-coder init,仅确认服务存活。涉及新依赖时仍需运行 claude-coder init。';
79
+ }
80
+
81
+ // Hint 4: Existing test records
82
+ let testHint = '';
83
+ if (fs.existsSync(p.testsFile)) {
84
+ try {
85
+ const count = (JSON.parse(fs.readFileSync(p.testsFile, 'utf8')).test_cases || []).length;
86
+ if (count > 0) testHint = `tests.json 已有 ${count} 条验证记录,Step 5 时先查已有记录避免重复验证。`;
87
+ } catch { /* ignore */ }
88
+ }
89
+
90
+ // Hint 5: Project documentation awareness + profile quality check
91
+ let docsHint = '';
92
+ if (fs.existsSync(p.profile)) {
93
+ try {
94
+ const profile = JSON.parse(fs.readFileSync(p.profile, 'utf8'));
95
+ const docs = profile.existing_docs || [];
96
+ if (docs.length > 0) {
97
+ docsHint = `项目文档: ${docs.join(', ')}。Step 4 编码前先读与任务相关的文档,了解接口约定和编码规范。完成后若新增了模块或 API,更新对应文档。`;
98
+ }
99
+ if (profile.tech_stack?.backend?.framework &&
100
+ (!profile.services || profile.services.length === 0)) {
101
+ docsHint += ' 注意:project_profile.json services 为空,请在本次 session 末尾补全 services 数组(command, port, health_check)。';
102
+ }
103
+ if (!docs.length) {
104
+ docsHint += ' 注意:project_profile.json 的 existing_docs 为空,请在 Step 6 收尾时补全文档列表。';
105
+ }
106
+ } catch { /* ignore */ }
107
+ }
108
+
109
+ // Hint 6: Task context (harness pre-read, saves Agent 2-3 Read calls)
110
+ let taskHint = '';
111
+ try {
112
+ const taskData = loadTasks();
113
+ if (taskData) {
114
+ const next = findNextTask(taskData);
115
+ const stats = getStats(taskData);
116
+ if (next) {
117
+ taskHint = `任务上下文: ${next.id} "${next.description}" (${next.status}), ` +
118
+ `category=${next.category}, steps=${next.steps.length}步。` +
119
+ `进度: ${stats.done}/${stats.total} done, ${stats.failed} failed。` +
120
+ `项目绝对路径: ${projectRoot}。运行时目录: ${projectRoot}/.claude-coder/(隐藏目录)。` +
121
+ `第一步无需读取 tasks.json(已注入),直接确认任务后进入 Step 2。`;
122
+ }
123
+ }
124
+ } catch { /* ignore */ }
125
+
126
+ // Hint 6b: Test environment variables (readable + writable by Agent)
127
+ let testEnvHint = '';
128
+ if (p.testEnvFile && fs.existsSync(p.testEnvFile)) {
129
+ testEnvHint = `测试凭证文件: ${projectRoot}/.claude-coder/test.env(含 API Key、测试账号等),测试前用 source ${projectRoot}/.claude-coder/test.env 加载。发现新凭证需求时可追加写入(KEY=value 格式)。`;
130
+ } else {
131
+ testEnvHint = `如需持久化测试凭证(API Key、测试账号密码等),写入 ${projectRoot}/.claude-coder/test.env(KEY=value 格式,每行一个)。后续 session 会自动感知。`;
132
+ }
133
+
134
+ // Hint 6c: Playwright mode awareness
135
+ let playwrightAuthHint = '';
136
+ if (config.mcpPlaywright) {
137
+ const mode = config.playwrightMode;
138
+ switch (mode) {
139
+ case 'persistent':
140
+ playwrightAuthHint = 'Playwright MCP 使用 persistent 模式(user-data-dir),浏览器登录状态持久保存在本地配置中,无需额外登录操作。';
141
+ break;
142
+ case 'isolated':
143
+ playwrightAuthHint = fs.existsSync(p.playwrightAuth)
144
+ ? `Playwright MCP 使用 isolated 模式,已检测到登录状态文件(playwright-auth.json),每次会话自动加载 cookies localStorage。`
145
+ : 'Playwright MCP 使用 isolated 模式,但未检测到登录状态文件。如目标页面需要登录,请先运行 claude-coder auth <URL>。';
146
+ break;
147
+ case 'extension':
148
+ playwrightAuthHint = 'Playwright MCP 使用 extension 模式,已连接用户真实浏览器,直接复用浏览器已有的登录态和扩展。注意:操作会影响用户正在使用的浏览器。';
149
+ break;
150
+ }
151
+ }
152
+
153
+ // Hint 7: Session memory (read flat session_result.json)
154
+ let memoryHint = '';
155
+ if (fs.existsSync(p.sessionResult)) {
156
+ try {
157
+ const sr = JSON.parse(fs.readFileSync(p.sessionResult, 'utf8'));
158
+ if (sr?.session_result) {
159
+ memoryHint = `上次会话: ${sr.session_result}(${sr.status_before || '?'} → ${sr.status_after || '?'})` +
160
+ (sr.notes ? `, 要点: ${sr.notes.slice(0, 150)}` : '') + '。';
161
+ }
162
+ } catch { /* ignore */ }
163
+ }
164
+
165
+ // Hint 8: Service management (continuous vs single-shot mode)
166
+ const maxSessions = opts.maxSessions || 50;
167
+ const serviceHint = maxSessions === 1
168
+ ? '单次模式:收尾时停止所有后台服务。'
169
+ : '连续模式:收尾时不要停止后台服务,保持服务运行以便下个 session 继续使用。';
170
+
171
+ return loadAndRender(p.codingUser, {
172
+ sessionNum,
173
+ mcpHint,
174
+ retryContext,
175
+ envHint,
176
+ testHint,
177
+ docsHint,
178
+ taskHint,
179
+ testEnvHint,
180
+ playwrightAuthHint,
181
+ memoryHint,
182
+ serviceHint,
183
+ });
184
+ }
185
+
186
+ // --------------- Scan Session ---------------
187
+
188
+ /**
189
+ * Build user prompt for scan sessions.
190
+ * Scan only generates profile — task decomposition is handled by add session.
191
+ */
192
+ function buildScanPrompt(projectType, requirement) {
193
+ const p = paths();
194
+ const requirementLine = requirement
195
+ ? `用户需求概述: ${requirement.slice(0, 500)}`
196
+ : '';
197
+
198
+ return loadAndRender(p.scanUser, {
199
+ projectType,
200
+ requirement: requirementLine,
201
+ });
202
+ }
203
+
204
+ // --------------- Add Session ---------------
205
+
206
+ /**
207
+ * Build lightweight system prompt for add sessions.
208
+ * CLAUDE.md is NOT injected to avoid role conflict and save ~2000 tokens.
209
+ */
210
+ function buildAddSystemPrompt() {
211
+ return '你是一个任务分解专家,擅长将模糊需求拆解为结构化、可执行的原子任务。你只分析需求和分解任务,不实现任何代码。';
212
+ }
213
+
214
+ /**
215
+ * Build user prompt for add sessions.
216
+ * Structure: Role (primacy) → Dynamic context → ADD_GUIDE.md (reference) → Instruction (recency)
217
+ */
218
+ function buildAddPrompt(instruction) {
219
+ const p = paths();
220
+ const projectRoot = getProjectRoot();
221
+
222
+ // --- Load ADD_GUIDE.md reference document ---
223
+ let addGuide = '';
224
+ if (fs.existsSync(p.addGuide)) {
225
+ addGuide = fs.readFileSync(p.addGuide, 'utf8');
226
+ }
227
+
228
+ // --- Context injection: project tech stack ---
229
+ let profileContext = '';
230
+ if (fs.existsSync(p.profile)) {
231
+ try {
232
+ const profile = JSON.parse(fs.readFileSync(p.profile, 'utf8'));
233
+ const stack = profile.tech_stack || {};
234
+ const parts = [];
235
+ if (stack.backend?.framework) parts.push(`后端: ${stack.backend.framework}`);
236
+ if (stack.frontend?.framework) parts.push(`前端: ${stack.frontend.framework}`);
237
+ if (stack.backend?.language) parts.push(`语言: ${stack.backend.language}`);
238
+ if (parts.length) profileContext = `项目技术栈: ${parts.join(', ')}`;
239
+ } catch { /* ignore */ }
240
+ }
241
+
242
+ // --- Context injection: existing tasks summary ---
243
+ let taskContext = '';
244
+ let recentExamples = '';
245
+ try {
246
+ const taskData = loadTasks();
247
+ if (taskData) {
248
+ const stats = getStats(taskData);
249
+ const features = taskData.features || [];
250
+ const maxId = features.length ? features[features.length - 1].id : 'feat-000';
251
+ const maxPriority = features.length ? Math.max(...features.map(f => f.priority || 0)) : 0;
252
+ const categories = [...new Set(features.map(f => f.category))].join(', ');
253
+
254
+ taskContext = `已有 ${stats.total} 个任务(${stats.done} done, ${stats.pending} pending, ${stats.failed} failed)。` +
255
+ `最大 id: ${maxId}, 最大 priority: ${maxPriority}。已有 category: ${categories}。`;
256
+
257
+ const recent = features.slice(-3);
258
+ if (recent.length) {
259
+ recentExamples = '已有任务格式参考(保持一致性):\n' +
260
+ recent.map(f => ` ${f.id}: "${f.description}" (category=${f.category}, steps=${f.steps.length}步, depends_on=[${f.depends_on.join(',')}])`).join('\n');
261
+ }
262
+ }
263
+ } catch { /* ignore */ }
264
+
265
+ // --- Conditional: Playwright test rule hint ---
266
+ let testRuleHint = '';
267
+ const testRulePath = path.join(p.loopDir, 'test_rule.md');
268
+ const hasMcp = fs.existsSync(p.mcpConfig);
269
+ if (fs.existsSync(testRulePath) && hasMcp) {
270
+ testRuleHint = '【Playwright 测试规则】项目已配置 Playwright MCP(.mcp.json),' +
271
+ '`.claude-coder/test_rule.md` 包含测试规范(Smart Snapshot、等待策略、步骤模板等)。端到端测试任务请参考 test_rule.md。';
272
+ }
273
+
274
+ return loadAndRender(p.addUser, {
275
+ profileContext,
276
+ taskContext,
277
+ recentExamples,
278
+ projectRoot,
279
+ addGuide,
280
+ testRuleHint,
281
+ instruction,
282
+ });
283
+ }
284
+
285
+ // --------------- Exports ---------------
286
+
287
+ module.exports = {
288
+ renderTemplate,
289
+ loadAndRender,
290
+ buildSystemPrompt,
291
+ buildCodingPrompt,
292
+ buildScanPrompt,
293
+ buildAddSystemPrompt,
294
+ buildAddPrompt,
295
+ };