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/README.md +125 -127
- package/bin/cli.js +161 -197
- package/package.json +47 -44
- package/prompts/ADD_GUIDE.md +98 -0
- package/{templates → prompts}/CLAUDE.md +199 -238
- package/{templates → prompts}/SCAN_PROTOCOL.md +118 -123
- package/prompts/add_user.md +24 -0
- package/prompts/coding_user.md +23 -0
- package/prompts/scan_user.md +17 -0
- package/src/auth.js +245 -245
- package/src/config.js +201 -223
- package/src/hooks.js +160 -96
- package/src/indicator.js +217 -160
- package/src/init.js +144 -144
- package/src/prompts.js +295 -339
- package/src/runner.js +420 -394
- package/src/scanner.js +62 -62
- package/src/session.js +352 -320
- package/src/setup.js +579 -397
- package/src/tasks.js +172 -172
- package/src/validator.js +181 -170
- package/templates/requirements.example.md +56 -56
- package/templates/test_rule.md +194 -157
- package/docs/ARCHITECTURE.md +0 -516
- package/docs/PLAYWRIGHT_CREDENTIALS.md +0 -178
- package/docs/README.en.md +0 -103
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return
|
|
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
|
-
// Hint
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
+
};
|