claude-coder 1.8.2 → 1.8.4
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 +167 -167
- package/bin/cli.js +172 -172
- package/package.json +53 -52
- package/recipes/_shared/roles/developer.md +11 -0
- package/recipes/_shared/roles/product.md +12 -0
- package/recipes/_shared/roles/tester.md +12 -0
- package/recipes/_shared/test/report-format.md +86 -0
- package/recipes/backend/base.md +27 -0
- package/recipes/backend/components/auth.md +18 -0
- package/recipes/backend/components/crud-api.md +18 -0
- package/recipes/backend/components/file-service.md +15 -0
- package/recipes/backend/manifest.json +20 -0
- package/recipes/backend/test/api-test.md +25 -0
- package/recipes/console/base.md +37 -0
- package/recipes/console/components/modal-form.md +20 -0
- package/recipes/console/components/pagination.md +17 -0
- package/recipes/console/components/search.md +17 -0
- package/recipes/console/components/table-list.md +18 -0
- package/recipes/console/components/tabs.md +14 -0
- package/recipes/console/components/tree.md +15 -0
- package/recipes/console/components/upload.md +15 -0
- package/recipes/console/manifest.json +24 -0
- package/recipes/console/test/crud-e2e.md +47 -0
- package/recipes/h5/base.md +26 -0
- package/recipes/h5/components/animation.md +11 -0
- package/recipes/h5/components/countdown.md +11 -0
- package/recipes/h5/components/share.md +11 -0
- package/recipes/h5/components/swiper.md +11 -0
- package/recipes/h5/manifest.json +21 -0
- package/recipes/h5/test/h5-e2e.md +20 -0
- package/src/commands/auth.js +290 -240
- package/src/commands/setup-modules/helpers.js +99 -99
- package/src/commands/setup-modules/index.js +25 -25
- package/src/commands/setup-modules/mcp.js +94 -94
- package/src/commands/setup-modules/provider.js +260 -260
- package/src/commands/setup-modules/safety.js +61 -61
- package/src/commands/setup-modules/simplify.js +52 -52
- package/src/commands/setup.js +172 -172
- package/src/common/assets.js +236 -236
- package/src/common/config.js +125 -125
- package/src/common/constants.js +55 -55
- package/src/common/indicator.js +222 -222
- package/src/common/interaction.js +170 -170
- package/src/common/logging.js +77 -77
- package/src/common/sdk.js +50 -50
- package/src/common/tasks.js +88 -88
- package/src/common/utils.js +161 -161
- package/src/core/coding.js +55 -55
- package/src/core/context.js +117 -117
- package/src/core/go.js +310 -310
- package/src/core/harness.js +484 -484
- package/src/core/hooks.js +533 -533
- package/src/core/init.js +171 -171
- package/src/core/plan.js +325 -325
- package/src/core/prompts.js +227 -227
- package/src/core/query.js +49 -49
- package/src/core/repair.js +46 -46
- package/src/core/runner.js +195 -195
- package/src/core/scan.js +89 -89
- package/src/core/session.js +56 -56
- package/src/core/simplify.js +53 -52
- package/templates/bash-process.md +12 -12
- package/templates/codingSystem.md +65 -65
- package/templates/codingUser.md +17 -17
- package/templates/coreProtocol.md +29 -29
- package/templates/goSystem.md +130 -130
- package/templates/guidance.json +52 -52
- package/templates/planSystem.md +78 -78
- package/templates/planUser.md +8 -8
- package/templates/playwright.md +16 -16
- package/templates/requirements.example.md +57 -57
- package/templates/scanSystem.md +120 -120
- package/templates/scanUser.md +10 -10
- package/templates/test_rule.md +194 -194
package/src/core/prompts.js
CHANGED
|
@@ -1,227 +1,227 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { loadConfig } = require('../common/config');
|
|
6
|
-
const { assets } = require('../common/assets');
|
|
7
|
-
const { loadTasks, getStats } = require('../common/tasks');
|
|
8
|
-
const { loadState, selectNextTask } = require('./harness');
|
|
9
|
-
|
|
10
|
-
// --------------- System Prompt ---------------
|
|
11
|
-
|
|
12
|
-
function buildSystemPrompt(type) {
|
|
13
|
-
const core = assets.read('coreProtocol') || '';
|
|
14
|
-
let specific = '';
|
|
15
|
-
switch (type) {
|
|
16
|
-
case 'scan': specific = assets.read('scanSystem') || ''; break;
|
|
17
|
-
case 'coding': specific = assets.read('codingSystem') || ''; break;
|
|
18
|
-
case 'plan': specific = assets.read('planSystem') || ''; break;
|
|
19
|
-
case 'go': specific = assets.read('goSystem') || ''; break;
|
|
20
|
-
}
|
|
21
|
-
return specific ? `${specific}\n\n${core}` : core;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// --------------- Task Type Detection ---------------
|
|
25
|
-
|
|
26
|
-
const WEB_CATEGORIES = new Set(['frontend', 'fullstack', 'test', 'e2e']);
|
|
27
|
-
const WEB_KEYWORDS = /playwright|browser|页面|前端|UI|端到端|e2e/i;
|
|
28
|
-
|
|
29
|
-
function needsWebTools(task) {
|
|
30
|
-
if (!task) return true;
|
|
31
|
-
if (WEB_CATEGORIES.has(task.category)) return true;
|
|
32
|
-
const text = [task.description || '', ...(task.steps || [])].join(' ');
|
|
33
|
-
return WEB_KEYWORDS.test(text);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// --------------- Hint Builders ---------------
|
|
37
|
-
|
|
38
|
-
function buildMcpHint(config, task) {
|
|
39
|
-
if (!config.mcpPlaywright) return '';
|
|
40
|
-
if (!needsWebTools(task)) return '';
|
|
41
|
-
return '前端/全栈任务可用 Playwright MCP(browser_navigate、browser_snapshot、browser_click 等)做端到端测试。';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function buildRetryHint(consecutiveFailures, lastValidateLog) {
|
|
45
|
-
if (consecutiveFailures > 0 && lastValidateLog) {
|
|
46
|
-
return `注意:上次会话校验失败,原因:${lastValidateLog}。请避免同样的问题。`;
|
|
47
|
-
}
|
|
48
|
-
return '';
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function buildEnvHint(consecutiveFailures, sessionNum) {
|
|
52
|
-
if (sessionNum <= 1) return '首次会话,需要时执行 claude-coder init 初始化环境。';
|
|
53
|
-
if (consecutiveFailures > 0) return '上次失败,建议先确认环境状态。';
|
|
54
|
-
return '';
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function buildDocsHint() {
|
|
58
|
-
const profile = assets.readJson('profile', null);
|
|
59
|
-
if (!profile) return '';
|
|
60
|
-
const docs = profile.existing_docs || [];
|
|
61
|
-
if (docs.length > 0) {
|
|
62
|
-
return `项目文档: ${docs.join(', ')}。编码前先读与任务相关的文档,了解接口约定和编码规范。`;
|
|
63
|
-
}
|
|
64
|
-
return '';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function buildTaskContext(projectRoot, taskId) {
|
|
68
|
-
try {
|
|
69
|
-
const taskData = loadTasks();
|
|
70
|
-
if (!taskData) return '无法读取 tasks.json,请手动检查。';
|
|
71
|
-
const features = taskData.features || [];
|
|
72
|
-
const stats = getStats(taskData);
|
|
73
|
-
|
|
74
|
-
const task = taskId
|
|
75
|
-
? features.find(f => f.id === taskId)
|
|
76
|
-
: selectNextTask(taskData);
|
|
77
|
-
|
|
78
|
-
if (!task) return '无待处理任务。';
|
|
79
|
-
|
|
80
|
-
const steps = (task.steps || [])
|
|
81
|
-
.map((s, i) => ` ${i + 1}. ${s}`)
|
|
82
|
-
.join('\n');
|
|
83
|
-
|
|
84
|
-
const deps = (task.depends_on || []).length > 0
|
|
85
|
-
? `depends_on: [${task.depends_on.join(', ')}]`
|
|
86
|
-
: '';
|
|
87
|
-
|
|
88
|
-
return [
|
|
89
|
-
`**${task.id}**: "${task.description}"`,
|
|
90
|
-
`状态: ${task.status}, category: ${task.category}, priority: ${task.priority || 'N/A'} ${deps}`,
|
|
91
|
-
`步骤:\n${steps}`,
|
|
92
|
-
`进度: ${stats.done}/${stats.total} done, ${stats.failed} failed`,
|
|
93
|
-
`项目路径: ${projectRoot}`,
|
|
94
|
-
].join('\n');
|
|
95
|
-
} catch {
|
|
96
|
-
return '任务上下文加载失败,请读取 .claude-coder/tasks.json 自行确认。';
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function buildTestEnvHint(projectRoot) {
|
|
101
|
-
if (assets.exists('testEnv')) {
|
|
102
|
-
return `测试凭证文件: ${projectRoot}/.claude-coder/test.env(含 API Key、测试账号等),测试前用 source 加载。`;
|
|
103
|
-
}
|
|
104
|
-
return '';
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function buildPlaywrightAuthHint(config, task) {
|
|
108
|
-
if (!config.mcpPlaywright) return '';
|
|
109
|
-
if (!needsWebTools(task)) return '';
|
|
110
|
-
const mode = config.playwrightMode;
|
|
111
|
-
switch (mode) {
|
|
112
|
-
case 'persistent':
|
|
113
|
-
return 'Playwright MCP 使用 persistent 模式,浏览器登录状态持久保存,无需额外登录操作。';
|
|
114
|
-
case 'isolated':
|
|
115
|
-
return assets.exists('playwrightAuth')
|
|
116
|
-
? 'Playwright MCP 使用 isolated 模式,已检测到登录状态文件,每次会话自动加载。'
|
|
117
|
-
: 'Playwright MCP 使用 isolated 模式,未检测到登录状态文件。如需登录,请先运行 claude-coder auth <URL>。';
|
|
118
|
-
case 'extension':
|
|
119
|
-
return 'Playwright MCP 使用 extension 模式,已连接用户真实浏览器,直接复用已有登录态。';
|
|
120
|
-
default:
|
|
121
|
-
return '';
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function buildMemoryHint() {
|
|
126
|
-
const sr = assets.readJson('sessionResult', null);
|
|
127
|
-
if (!sr?.session_result) return '';
|
|
128
|
-
const base = `上次会话 ${sr.session_result}(${sr.status_before || '?'} → ${sr.status_after || '?'})。`;
|
|
129
|
-
if (!sr.notes || !sr.notes.trim()) return base;
|
|
130
|
-
return `${base}遗留: ${sr.notes.slice(0, 200)}`;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function buildServiceHint(maxSessions) {
|
|
134
|
-
return maxSessions === 1
|
|
135
|
-
? '单次模式:收尾时停止所有后台服务。'
|
|
136
|
-
: '连续模式:收尾时不要停止后台服务,保持服务运行以便下个 session 继续使用。';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// --------------- Context Builders ---------------
|
|
140
|
-
|
|
141
|
-
function _resolveTask(taskId) {
|
|
142
|
-
try {
|
|
143
|
-
const taskData = loadTasks();
|
|
144
|
-
if (!taskData) return null;
|
|
145
|
-
const features = taskData.features || [];
|
|
146
|
-
return taskId ? features.find(f => f.id === taskId) : selectNextTask(taskData);
|
|
147
|
-
} catch { return null; }
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* 构建 coding session 的完整上下文(user prompt)
|
|
152
|
-
*/
|
|
153
|
-
function buildCodingContext(sessionNum, opts = {}) {
|
|
154
|
-
const config = loadConfig();
|
|
155
|
-
const consecutiveFailures = opts.consecutiveFailures || 0;
|
|
156
|
-
const projectRoot = assets.projectRoot;
|
|
157
|
-
const task = _resolveTask(opts.taskId);
|
|
158
|
-
|
|
159
|
-
return assets.render('codingUser', {
|
|
160
|
-
sessionNum,
|
|
161
|
-
taskContext: buildTaskContext(projectRoot, opts.taskId),
|
|
162
|
-
mcpHint: buildMcpHint(config, task),
|
|
163
|
-
retryContext: buildRetryHint(consecutiveFailures, opts.lastValidateLog),
|
|
164
|
-
envHint: buildEnvHint(consecutiveFailures, sessionNum),
|
|
165
|
-
docsHint: buildDocsHint(),
|
|
166
|
-
testEnvHint: buildTestEnvHint(projectRoot),
|
|
167
|
-
playwrightAuthHint: buildPlaywrightAuthHint(config, task),
|
|
168
|
-
memoryHint: buildMemoryHint(),
|
|
169
|
-
serviceHint: buildServiceHint(opts.maxSessions || 50),
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// --------------- Scan Session ---------------
|
|
174
|
-
|
|
175
|
-
function buildScanPrompt(projectType) {
|
|
176
|
-
return assets.render('scanUser', { projectType });
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// --------------- Plan Session ---------------
|
|
180
|
-
|
|
181
|
-
function buildPlanPrompt(planPath) {
|
|
182
|
-
const projectRoot = assets.projectRoot;
|
|
183
|
-
|
|
184
|
-
let taskContext = '';
|
|
185
|
-
let recentExamples = '';
|
|
186
|
-
try {
|
|
187
|
-
const taskData = loadTasks();
|
|
188
|
-
if (taskData) {
|
|
189
|
-
const features = taskData.features || [];
|
|
190
|
-
const state = loadState();
|
|
191
|
-
const nextId = `feat-${String(state.next_task_id).padStart(3, '0')}`;
|
|
192
|
-
const categories = [...new Set(features.map(f => f.category))].join(', ');
|
|
193
|
-
|
|
194
|
-
taskContext = `新任务 ID 从 ${nextId} 开始,priority 从 ${state.next_priority} 开始。已有 category: ${categories || '无'}。`;
|
|
195
|
-
|
|
196
|
-
const recent = features.slice(-3);
|
|
197
|
-
if (recent.length) {
|
|
198
|
-
recentExamples = '已有任务格式参考(保持一致性):\n' +
|
|
199
|
-
recent.map(f => ` ${f.id}: "${f.description}" (category=${f.category}, steps=${(f.steps || []).length}步, depends_on=[${(f.depends_on || []).join(',')}])`).join('\n');
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
} catch { /* ignore */ }
|
|
203
|
-
|
|
204
|
-
let testRuleHint = '';
|
|
205
|
-
if (assets.exists('testRule') && assets.exists('mcpConfig')) {
|
|
206
|
-
testRuleHint = '【Playwright 测试规则】项目已配置 Playwright MCP(.mcp.json),' +
|
|
207
|
-
'`.claude-coder/assets/test_rule.md` 包含测试规范(Smart Snapshot、等待策略、步骤模板等)。' +
|
|
208
|
-
'前端页面 test 类任务 steps 首步加入 `【规则】阅读 .claude-coder/assets/test_rule.md`。';
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return assets.render('planUser', {
|
|
212
|
-
taskContext,
|
|
213
|
-
recentExamples,
|
|
214
|
-
projectRoot,
|
|
215
|
-
planPath,
|
|
216
|
-
testRuleHint,
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// --------------- Exports ---------------
|
|
221
|
-
|
|
222
|
-
module.exports = {
|
|
223
|
-
buildSystemPrompt,
|
|
224
|
-
buildCodingContext,
|
|
225
|
-
buildScanPrompt,
|
|
226
|
-
buildPlanPrompt,
|
|
227
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { loadConfig } = require('../common/config');
|
|
6
|
+
const { assets } = require('../common/assets');
|
|
7
|
+
const { loadTasks, getStats } = require('../common/tasks');
|
|
8
|
+
const { loadState, selectNextTask } = require('./harness');
|
|
9
|
+
|
|
10
|
+
// --------------- System Prompt ---------------
|
|
11
|
+
|
|
12
|
+
function buildSystemPrompt(type) {
|
|
13
|
+
const core = assets.read('coreProtocol') || '';
|
|
14
|
+
let specific = '';
|
|
15
|
+
switch (type) {
|
|
16
|
+
case 'scan': specific = assets.read('scanSystem') || ''; break;
|
|
17
|
+
case 'coding': specific = assets.read('codingSystem') || ''; break;
|
|
18
|
+
case 'plan': specific = assets.read('planSystem') || ''; break;
|
|
19
|
+
case 'go': specific = assets.read('goSystem') || ''; break;
|
|
20
|
+
}
|
|
21
|
+
return specific ? `${specific}\n\n${core}` : core;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// --------------- Task Type Detection ---------------
|
|
25
|
+
|
|
26
|
+
const WEB_CATEGORIES = new Set(['frontend', 'fullstack', 'test', 'e2e']);
|
|
27
|
+
const WEB_KEYWORDS = /playwright|browser|页面|前端|UI|端到端|e2e/i;
|
|
28
|
+
|
|
29
|
+
function needsWebTools(task) {
|
|
30
|
+
if (!task) return true;
|
|
31
|
+
if (WEB_CATEGORIES.has(task.category)) return true;
|
|
32
|
+
const text = [task.description || '', ...(task.steps || [])].join(' ');
|
|
33
|
+
return WEB_KEYWORDS.test(text);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// --------------- Hint Builders ---------------
|
|
37
|
+
|
|
38
|
+
function buildMcpHint(config, task) {
|
|
39
|
+
if (!config.mcpPlaywright) return '';
|
|
40
|
+
if (!needsWebTools(task)) return '';
|
|
41
|
+
return '前端/全栈任务可用 Playwright MCP(browser_navigate、browser_snapshot、browser_click 等)做端到端测试。';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildRetryHint(consecutiveFailures, lastValidateLog) {
|
|
45
|
+
if (consecutiveFailures > 0 && lastValidateLog) {
|
|
46
|
+
return `注意:上次会话校验失败,原因:${lastValidateLog}。请避免同样的问题。`;
|
|
47
|
+
}
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildEnvHint(consecutiveFailures, sessionNum) {
|
|
52
|
+
if (sessionNum <= 1) return '首次会话,需要时执行 claude-coder init 初始化环境。';
|
|
53
|
+
if (consecutiveFailures > 0) return '上次失败,建议先确认环境状态。';
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function buildDocsHint() {
|
|
58
|
+
const profile = assets.readJson('profile', null);
|
|
59
|
+
if (!profile) return '';
|
|
60
|
+
const docs = profile.existing_docs || [];
|
|
61
|
+
if (docs.length > 0) {
|
|
62
|
+
return `项目文档: ${docs.join(', ')}。编码前先读与任务相关的文档,了解接口约定和编码规范。`;
|
|
63
|
+
}
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildTaskContext(projectRoot, taskId) {
|
|
68
|
+
try {
|
|
69
|
+
const taskData = loadTasks();
|
|
70
|
+
if (!taskData) return '无法读取 tasks.json,请手动检查。';
|
|
71
|
+
const features = taskData.features || [];
|
|
72
|
+
const stats = getStats(taskData);
|
|
73
|
+
|
|
74
|
+
const task = taskId
|
|
75
|
+
? features.find(f => f.id === taskId)
|
|
76
|
+
: selectNextTask(taskData);
|
|
77
|
+
|
|
78
|
+
if (!task) return '无待处理任务。';
|
|
79
|
+
|
|
80
|
+
const steps = (task.steps || [])
|
|
81
|
+
.map((s, i) => ` ${i + 1}. ${s}`)
|
|
82
|
+
.join('\n');
|
|
83
|
+
|
|
84
|
+
const deps = (task.depends_on || []).length > 0
|
|
85
|
+
? `depends_on: [${task.depends_on.join(', ')}]`
|
|
86
|
+
: '';
|
|
87
|
+
|
|
88
|
+
return [
|
|
89
|
+
`**${task.id}**: "${task.description}"`,
|
|
90
|
+
`状态: ${task.status}, category: ${task.category}, priority: ${task.priority || 'N/A'} ${deps}`,
|
|
91
|
+
`步骤:\n${steps}`,
|
|
92
|
+
`进度: ${stats.done}/${stats.total} done, ${stats.failed} failed`,
|
|
93
|
+
`项目路径: ${projectRoot}`,
|
|
94
|
+
].join('\n');
|
|
95
|
+
} catch {
|
|
96
|
+
return '任务上下文加载失败,请读取 .claude-coder/tasks.json 自行确认。';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildTestEnvHint(projectRoot) {
|
|
101
|
+
if (assets.exists('testEnv')) {
|
|
102
|
+
return `测试凭证文件: ${projectRoot}/.claude-coder/test.env(含 API Key、测试账号等),测试前用 source 加载。`;
|
|
103
|
+
}
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildPlaywrightAuthHint(config, task) {
|
|
108
|
+
if (!config.mcpPlaywright) return '';
|
|
109
|
+
if (!needsWebTools(task)) return '';
|
|
110
|
+
const mode = config.playwrightMode;
|
|
111
|
+
switch (mode) {
|
|
112
|
+
case 'persistent':
|
|
113
|
+
return 'Playwright MCP 使用 persistent 模式,浏览器登录状态持久保存,无需额外登录操作。';
|
|
114
|
+
case 'isolated':
|
|
115
|
+
return assets.exists('playwrightAuth')
|
|
116
|
+
? 'Playwright MCP 使用 isolated 模式,已检测到登录状态文件,每次会话自动加载。'
|
|
117
|
+
: 'Playwright MCP 使用 isolated 模式,未检测到登录状态文件。如需登录,请先运行 claude-coder auth <URL>。';
|
|
118
|
+
case 'extension':
|
|
119
|
+
return 'Playwright MCP 使用 extension 模式,已连接用户真实浏览器,直接复用已有登录态。';
|
|
120
|
+
default:
|
|
121
|
+
return '';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildMemoryHint() {
|
|
126
|
+
const sr = assets.readJson('sessionResult', null);
|
|
127
|
+
if (!sr?.session_result) return '';
|
|
128
|
+
const base = `上次会话 ${sr.session_result}(${sr.status_before || '?'} → ${sr.status_after || '?'})。`;
|
|
129
|
+
if (!sr.notes || !sr.notes.trim()) return base;
|
|
130
|
+
return `${base}遗留: ${sr.notes.slice(0, 200)}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function buildServiceHint(maxSessions) {
|
|
134
|
+
return maxSessions === 1
|
|
135
|
+
? '单次模式:收尾时停止所有后台服务。'
|
|
136
|
+
: '连续模式:收尾时不要停止后台服务,保持服务运行以便下个 session 继续使用。';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// --------------- Context Builders ---------------
|
|
140
|
+
|
|
141
|
+
function _resolveTask(taskId) {
|
|
142
|
+
try {
|
|
143
|
+
const taskData = loadTasks();
|
|
144
|
+
if (!taskData) return null;
|
|
145
|
+
const features = taskData.features || [];
|
|
146
|
+
return taskId ? features.find(f => f.id === taskId) : selectNextTask(taskData);
|
|
147
|
+
} catch { return null; }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 构建 coding session 的完整上下文(user prompt)
|
|
152
|
+
*/
|
|
153
|
+
function buildCodingContext(sessionNum, opts = {}) {
|
|
154
|
+
const config = loadConfig();
|
|
155
|
+
const consecutiveFailures = opts.consecutiveFailures || 0;
|
|
156
|
+
const projectRoot = assets.projectRoot;
|
|
157
|
+
const task = _resolveTask(opts.taskId);
|
|
158
|
+
|
|
159
|
+
return assets.render('codingUser', {
|
|
160
|
+
sessionNum,
|
|
161
|
+
taskContext: buildTaskContext(projectRoot, opts.taskId),
|
|
162
|
+
mcpHint: buildMcpHint(config, task),
|
|
163
|
+
retryContext: buildRetryHint(consecutiveFailures, opts.lastValidateLog),
|
|
164
|
+
envHint: buildEnvHint(consecutiveFailures, sessionNum),
|
|
165
|
+
docsHint: buildDocsHint(),
|
|
166
|
+
testEnvHint: buildTestEnvHint(projectRoot),
|
|
167
|
+
playwrightAuthHint: buildPlaywrightAuthHint(config, task),
|
|
168
|
+
memoryHint: buildMemoryHint(),
|
|
169
|
+
serviceHint: buildServiceHint(opts.maxSessions || 50),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// --------------- Scan Session ---------------
|
|
174
|
+
|
|
175
|
+
function buildScanPrompt(projectType) {
|
|
176
|
+
return assets.render('scanUser', { projectType });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// --------------- Plan Session ---------------
|
|
180
|
+
|
|
181
|
+
function buildPlanPrompt(planPath) {
|
|
182
|
+
const projectRoot = assets.projectRoot;
|
|
183
|
+
|
|
184
|
+
let taskContext = '';
|
|
185
|
+
let recentExamples = '';
|
|
186
|
+
try {
|
|
187
|
+
const taskData = loadTasks();
|
|
188
|
+
if (taskData) {
|
|
189
|
+
const features = taskData.features || [];
|
|
190
|
+
const state = loadState();
|
|
191
|
+
const nextId = `feat-${String(state.next_task_id).padStart(3, '0')}`;
|
|
192
|
+
const categories = [...new Set(features.map(f => f.category))].join(', ');
|
|
193
|
+
|
|
194
|
+
taskContext = `新任务 ID 从 ${nextId} 开始,priority 从 ${state.next_priority} 开始。已有 category: ${categories || '无'}。`;
|
|
195
|
+
|
|
196
|
+
const recent = features.slice(-3);
|
|
197
|
+
if (recent.length) {
|
|
198
|
+
recentExamples = '已有任务格式参考(保持一致性):\n' +
|
|
199
|
+
recent.map(f => ` ${f.id}: "${f.description}" (category=${f.category}, steps=${(f.steps || []).length}步, depends_on=[${(f.depends_on || []).join(',')}])`).join('\n');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} catch { /* ignore */ }
|
|
203
|
+
|
|
204
|
+
let testRuleHint = '';
|
|
205
|
+
if (assets.exists('testRule') && assets.exists('mcpConfig')) {
|
|
206
|
+
testRuleHint = '【Playwright 测试规则】项目已配置 Playwright MCP(.mcp.json),' +
|
|
207
|
+
'`.claude-coder/assets/test_rule.md` 包含测试规范(Smart Snapshot、等待策略、步骤模板等)。' +
|
|
208
|
+
'前端页面 test 类任务 steps 首步加入 `【规则】阅读 .claude-coder/assets/test_rule.md`。';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return assets.render('planUser', {
|
|
212
|
+
taskContext,
|
|
213
|
+
recentExamples,
|
|
214
|
+
projectRoot,
|
|
215
|
+
planPath,
|
|
216
|
+
testRuleHint,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// --------------- Exports ---------------
|
|
221
|
+
|
|
222
|
+
module.exports = {
|
|
223
|
+
buildSystemPrompt,
|
|
224
|
+
buildCodingContext,
|
|
225
|
+
buildScanPrompt,
|
|
226
|
+
buildPlanPrompt,
|
|
227
|
+
};
|
package/src/core/query.js
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { buildEnvVars } = require('../common/config');
|
|
6
|
-
const { assets } = require('../common/assets');
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 检查项目是否包含代码文件
|
|
10
|
-
*/
|
|
11
|
-
function hasCodeFiles(projectRoot) {
|
|
12
|
-
const markers = [
|
|
13
|
-
'package.json', 'pyproject.toml', 'requirements.txt', 'setup.py',
|
|
14
|
-
'Cargo.toml', 'go.mod', 'pom.xml', 'build.gradle',
|
|
15
|
-
'Makefile', 'Dockerfile', 'docker-compose.yml',
|
|
16
|
-
'README.md', 'main.py', 'app.py', 'index.js', 'index.ts',
|
|
17
|
-
];
|
|
18
|
-
for (const m of markers) {
|
|
19
|
-
if (fs.existsSync(path.join(projectRoot, m))) return true;
|
|
20
|
-
}
|
|
21
|
-
for (const d of ['src', 'lib', 'app', 'backend', 'frontend', 'web', 'server', 'client']) {
|
|
22
|
-
if (fs.existsSync(path.join(projectRoot, d)) && fs.statSync(path.join(projectRoot, d)).isDirectory()) return true;
|
|
23
|
-
}
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 构建 SDK query 选项
|
|
29
|
-
*/
|
|
30
|
-
function buildQueryOptions(config, opts = {}) {
|
|
31
|
-
const mode = opts.permissionMode || 'bypassPermissions';
|
|
32
|
-
const base = {
|
|
33
|
-
permissionMode: mode,
|
|
34
|
-
cwd: opts.projectRoot || assets.projectRoot,
|
|
35
|
-
env: buildEnvVars(config),
|
|
36
|
-
settingSources: ['project'],
|
|
37
|
-
};
|
|
38
|
-
if (mode === 'bypassPermissions') {
|
|
39
|
-
base.allowDangerouslySkipPermissions = true;
|
|
40
|
-
}
|
|
41
|
-
if (config.maxTurns > 0) base.maxTurns = config.maxTurns;
|
|
42
|
-
if (opts.model) base.model = opts.model;
|
|
43
|
-
else if (config.model) base.model = config.model;
|
|
44
|
-
return base;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
module.exports = {
|
|
48
|
-
hasCodeFiles,
|
|
49
|
-
buildQueryOptions,
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { buildEnvVars } = require('../common/config');
|
|
6
|
+
const { assets } = require('../common/assets');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 检查项目是否包含代码文件
|
|
10
|
+
*/
|
|
11
|
+
function hasCodeFiles(projectRoot) {
|
|
12
|
+
const markers = [
|
|
13
|
+
'package.json', 'pyproject.toml', 'requirements.txt', 'setup.py',
|
|
14
|
+
'Cargo.toml', 'go.mod', 'pom.xml', 'build.gradle',
|
|
15
|
+
'Makefile', 'Dockerfile', 'docker-compose.yml',
|
|
16
|
+
'README.md', 'main.py', 'app.py', 'index.js', 'index.ts',
|
|
17
|
+
];
|
|
18
|
+
for (const m of markers) {
|
|
19
|
+
if (fs.existsSync(path.join(projectRoot, m))) return true;
|
|
20
|
+
}
|
|
21
|
+
for (const d of ['src', 'lib', 'app', 'backend', 'frontend', 'web', 'server', 'client']) {
|
|
22
|
+
if (fs.existsSync(path.join(projectRoot, d)) && fs.statSync(path.join(projectRoot, d)).isDirectory()) return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 构建 SDK query 选项
|
|
29
|
+
*/
|
|
30
|
+
function buildQueryOptions(config, opts = {}) {
|
|
31
|
+
const mode = opts.permissionMode || 'bypassPermissions';
|
|
32
|
+
const base = {
|
|
33
|
+
permissionMode: mode,
|
|
34
|
+
cwd: opts.projectRoot || assets.projectRoot,
|
|
35
|
+
env: buildEnvVars(config),
|
|
36
|
+
settingSources: ['project'],
|
|
37
|
+
};
|
|
38
|
+
if (mode === 'bypassPermissions') {
|
|
39
|
+
base.allowDangerouslySkipPermissions = true;
|
|
40
|
+
}
|
|
41
|
+
if (config.maxTurns > 0) base.maxTurns = config.maxTurns;
|
|
42
|
+
if (opts.model) base.model = opts.model;
|
|
43
|
+
else if (config.model) base.model = config.model;
|
|
44
|
+
return base;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
hasCodeFiles,
|
|
49
|
+
buildQueryOptions,
|
|
50
50
|
};
|
package/src/core/repair.js
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { runSession } = require('./session');
|
|
6
|
-
const { buildQueryOptions } = require('./query');
|
|
7
|
-
const { log } = require('../common/config');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 使用 AI 修复损坏的 JSON 文件
|
|
11
|
-
* @param {string} filePath - 文件绝对路径
|
|
12
|
-
* @param {object} [opts] - 透传给 runSession 的选项
|
|
13
|
-
*/
|
|
14
|
-
async function repairJsonFile(filePath, opts = {}) {
|
|
15
|
-
if (!fs.existsSync(filePath)) return;
|
|
16
|
-
|
|
17
|
-
const rawContent = fs.readFileSync(filePath, 'utf8');
|
|
18
|
-
if (!rawContent || !rawContent.trim()) return;
|
|
19
|
-
|
|
20
|
-
const fileName = path.basename(filePath);
|
|
21
|
-
log('info', `正在使用 AI 修复 ${fileName}...`);
|
|
22
|
-
|
|
23
|
-
const prompt = `文件 ${filePath} 的 JSON 格式已损坏,请修复并用 Write 工具写入原路径。\n\n当前损坏内容:\n${rawContent}`;
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
await runSession('repair', {
|
|
27
|
-
opts,
|
|
28
|
-
sessionNum: 0,
|
|
29
|
-
logFileName: `repair_${fileName.replace('.json', '')}.log`,
|
|
30
|
-
label: `repair:${fileName}`,
|
|
31
|
-
|
|
32
|
-
async execute(sdk, ctx) {
|
|
33
|
-
const queryOpts = buildQueryOptions(ctx.config, opts);
|
|
34
|
-
queryOpts.hooks = ctx.hooks;
|
|
35
|
-
queryOpts.abortController = ctx.abortController;
|
|
36
|
-
await ctx.runQuery(sdk, prompt, queryOpts);
|
|
37
|
-
log('ok', `AI 修复 ${fileName} 完成`);
|
|
38
|
-
return {};
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
} catch (err) {
|
|
42
|
-
log('warn', `AI 修复 ${fileName} 失败: ${err.message}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
module.exports = { repairJsonFile };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { runSession } = require('./session');
|
|
6
|
+
const { buildQueryOptions } = require('./query');
|
|
7
|
+
const { log } = require('../common/config');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 使用 AI 修复损坏的 JSON 文件
|
|
11
|
+
* @param {string} filePath - 文件绝对路径
|
|
12
|
+
* @param {object} [opts] - 透传给 runSession 的选项
|
|
13
|
+
*/
|
|
14
|
+
async function repairJsonFile(filePath, opts = {}) {
|
|
15
|
+
if (!fs.existsSync(filePath)) return;
|
|
16
|
+
|
|
17
|
+
const rawContent = fs.readFileSync(filePath, 'utf8');
|
|
18
|
+
if (!rawContent || !rawContent.trim()) return;
|
|
19
|
+
|
|
20
|
+
const fileName = path.basename(filePath);
|
|
21
|
+
log('info', `正在使用 AI 修复 ${fileName}...`);
|
|
22
|
+
|
|
23
|
+
const prompt = `文件 ${filePath} 的 JSON 格式已损坏,请修复并用 Write 工具写入原路径。\n\n当前损坏内容:\n${rawContent}`;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
await runSession('repair', {
|
|
27
|
+
opts,
|
|
28
|
+
sessionNum: 0,
|
|
29
|
+
logFileName: `repair_${fileName.replace('.json', '')}.log`,
|
|
30
|
+
label: `repair:${fileName}`,
|
|
31
|
+
|
|
32
|
+
async execute(sdk, ctx) {
|
|
33
|
+
const queryOpts = buildQueryOptions(ctx.config, opts);
|
|
34
|
+
queryOpts.hooks = ctx.hooks;
|
|
35
|
+
queryOpts.abortController = ctx.abortController;
|
|
36
|
+
await ctx.runQuery(sdk, prompt, queryOpts);
|
|
37
|
+
log('ok', `AI 修复 ${fileName} 完成`);
|
|
38
|
+
return {};
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
} catch (err) {
|
|
42
|
+
log('warn', `AI 修复 ${fileName} 失败: ${err.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { repairJsonFile };
|