claude-coder 1.8.3 → 1.9.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.
Files changed (64) hide show
  1. package/README.md +59 -12
  2. package/bin/cli.js +20 -37
  3. package/package.json +4 -1
  4. package/recipes/_shared/roles/developer.md +11 -0
  5. package/recipes/_shared/roles/product.md +12 -0
  6. package/recipes/_shared/roles/tester.md +12 -0
  7. package/recipes/_shared/test/report-format.md +86 -0
  8. package/recipes/backend/base.md +27 -0
  9. package/recipes/backend/components/auth.md +18 -0
  10. package/recipes/backend/components/crud-api.md +18 -0
  11. package/recipes/backend/components/file-service.md +15 -0
  12. package/recipes/backend/manifest.json +20 -0
  13. package/recipes/backend/test/api-test.md +25 -0
  14. package/recipes/console/base.md +37 -0
  15. package/recipes/console/components/modal-form.md +20 -0
  16. package/recipes/console/components/pagination.md +17 -0
  17. package/recipes/console/components/search.md +17 -0
  18. package/recipes/console/components/table-list.md +18 -0
  19. package/recipes/console/components/tabs.md +14 -0
  20. package/recipes/console/components/tree.md +15 -0
  21. package/recipes/console/components/upload.md +15 -0
  22. package/recipes/console/manifest.json +24 -0
  23. package/recipes/console/test/crud-e2e.md +47 -0
  24. package/recipes/h5/base.md +26 -0
  25. package/recipes/h5/components/animation.md +11 -0
  26. package/recipes/h5/components/countdown.md +11 -0
  27. package/recipes/h5/components/share.md +11 -0
  28. package/recipes/h5/components/swiper.md +11 -0
  29. package/recipes/h5/manifest.json +21 -0
  30. package/recipes/h5/test/h5-e2e.md +20 -0
  31. package/src/commands/auth.js +87 -15
  32. package/src/commands/setup-modules/helpers.js +4 -3
  33. package/src/commands/setup-modules/mcp.js +44 -24
  34. package/src/commands/setup-modules/safety.js +1 -15
  35. package/src/commands/setup.js +8 -8
  36. package/src/common/assets.js +10 -1
  37. package/src/common/config.js +2 -2
  38. package/src/common/indicator.js +158 -120
  39. package/src/common/utils.js +60 -8
  40. package/src/core/coding.js +16 -38
  41. package/src/core/go.js +31 -77
  42. package/src/core/hooks.js +56 -89
  43. package/src/core/init.js +94 -100
  44. package/src/core/plan.js +85 -223
  45. package/src/core/prompts.js +36 -16
  46. package/src/core/repair.js +7 -17
  47. package/src/core/runner.js +306 -43
  48. package/src/core/scan.js +38 -34
  49. package/src/core/session.js +253 -39
  50. package/src/core/simplify.js +45 -24
  51. package/src/core/state.js +105 -0
  52. package/src/index.js +76 -0
  53. package/templates/codingSystem.md +2 -2
  54. package/templates/codingUser.md +1 -1
  55. package/templates/guidance.json +22 -3
  56. package/templates/planSystem.md +2 -2
  57. package/templates/scanSystem.md +3 -3
  58. package/templates/scanUser.md +1 -1
  59. package/templates/web-testing.md +17 -0
  60. package/types/index.d.ts +217 -0
  61. package/src/core/context.js +0 -117
  62. package/src/core/harness.js +0 -484
  63. package/src/core/query.js +0 -50
  64. package/templates/playwright.md +0 -17
@@ -5,7 +5,7 @@ const path = require('path');
5
5
  const { loadConfig } = require('../common/config');
6
6
  const { assets } = require('../common/assets');
7
7
  const { loadTasks, getStats } = require('../common/tasks');
8
- const { loadState, selectNextTask } = require('./harness');
8
+ const { loadState, selectNextTask } = require('./state');
9
9
 
10
10
  // --------------- System Prompt ---------------
11
11
 
@@ -36,8 +36,11 @@ function needsWebTools(task) {
36
36
  // --------------- Hint Builders ---------------
37
37
 
38
38
  function buildMcpHint(config, task) {
39
- if (!config.mcpPlaywright) return '';
39
+ if (!config.webTestTool) return '';
40
40
  if (!needsWebTools(task)) return '';
41
+ if (config.webTestTool === 'chrome-devtools') {
42
+ return '前端/全栈任务可用 Chrome DevTools MCP(navigate、click、type_text、screenshot 等)做端到端测试和调试。';
43
+ }
41
44
  return '前端/全栈任务可用 Playwright MCP(browser_navigate、browser_snapshot、browser_click 等)做端到端测试。';
42
45
  }
43
46
 
@@ -77,21 +80,33 @@ function buildTaskContext(projectRoot, taskId) {
77
80
 
78
81
  if (!task) return '无待处理任务。';
79
82
 
80
- const steps = (task.steps || [])
83
+ const { id, description, status, category, priority, depends_on, steps, ...rest } = task;
84
+
85
+ const stepLines = (steps || [])
81
86
  .map((s, i) => ` ${i + 1}. ${s}`)
82
87
  .join('\n');
83
88
 
84
- const deps = (task.depends_on || []).length > 0
85
- ? `depends_on: [${task.depends_on.join(', ')}]`
89
+ const deps = (depends_on || []).length > 0
90
+ ? `depends_on: [${depends_on.join(', ')}]`
86
91
  : '';
87
92
 
88
- return [
89
- `**${task.id}**: "${task.description}"`,
90
- `状态: ${task.status}, category: ${task.category}, priority: ${task.priority || 'N/A'} ${deps}`,
91
- `步骤:\n${steps}`,
93
+ const lines = [
94
+ `**${id}**: "${description}"`,
95
+ `状态: ${status}, category: ${category}, priority: ${priority || 'N/A'} ${deps}`,
96
+ `步骤:\n${stepLines}`,
92
97
  `进度: ${stats.done}/${stats.total} done, ${stats.failed} failed`,
93
98
  `项目路径: ${projectRoot}`,
94
- ].join('\n');
99
+ ];
100
+
101
+ const extras = Object.entries(rest).filter(([, v]) => v != null && v !== '');
102
+ if (extras.length > 0) {
103
+ lines.push('补充信息:');
104
+ for (const [k, v] of extras) {
105
+ lines.push(` ${k}: ${typeof v === 'object' ? JSON.stringify(v) : v}`);
106
+ }
107
+ }
108
+
109
+ return lines.join('\n');
95
110
  } catch {
96
111
  return '任务上下文加载失败,请读取 .claude-coder/tasks.json 自行确认。';
97
112
  }
@@ -104,10 +119,15 @@ function buildTestEnvHint(projectRoot) {
104
119
  return '';
105
120
  }
106
121
 
107
- function buildPlaywrightAuthHint(config, task) {
108
- if (!config.mcpPlaywright) return '';
122
+ function buildWebTestHint(config, task) {
123
+ if (!config.webTestTool) return '';
109
124
  if (!needsWebTools(task)) return '';
110
- const mode = config.playwrightMode;
125
+
126
+ if (config.webTestTool === 'chrome-devtools') {
127
+ return 'Chrome DevTools MCP 已启用,通过 autoConnect 连接已打开的 Chrome 浏览器,直接复用已有登录态。';
128
+ }
129
+
130
+ const mode = config.webTestMode;
111
131
  switch (mode) {
112
132
  case 'persistent':
113
133
  return 'Playwright MCP 使用 persistent 模式,浏览器登录状态持久保存,无需额外登录操作。';
@@ -127,7 +147,7 @@ function buildMemoryHint() {
127
147
  if (!sr?.session_result) return '';
128
148
  const base = `上次会话 ${sr.session_result}(${sr.status_before || '?'} → ${sr.status_after || '?'})。`;
129
149
  if (!sr.notes || !sr.notes.trim()) return base;
130
- return `${base}遗留: ${sr.notes.slice(0, 200)}`;
150
+ return `${base}遗留: ${sr.notes}`;
131
151
  }
132
152
 
133
153
  function buildServiceHint(maxSessions) {
@@ -164,7 +184,7 @@ function buildCodingContext(sessionNum, opts = {}) {
164
184
  envHint: buildEnvHint(consecutiveFailures, sessionNum),
165
185
  docsHint: buildDocsHint(),
166
186
  testEnvHint: buildTestEnvHint(projectRoot),
167
- playwrightAuthHint: buildPlaywrightAuthHint(config, task),
187
+ webTestHint: buildWebTestHint(config, task),
168
188
  memoryHint: buildMemoryHint(),
169
189
  serviceHint: buildServiceHint(opts.maxSessions || 50),
170
190
  });
@@ -203,7 +223,7 @@ function buildPlanPrompt(planPath) {
203
223
 
204
224
  let testRuleHint = '';
205
225
  if (assets.exists('testRule') && assets.exists('mcpConfig')) {
206
- testRuleHint = '【Playwright 测试规则】项目已配置 Playwright MCP(.mcp.json),' +
226
+ testRuleHint = '【浏览器测试规则】项目已配置浏览器测试工具(.mcp.json),' +
207
227
  '`.claude-coder/assets/test_rule.md` 包含测试规范(Smart Snapshot、等待策略、步骤模板等)。' +
208
228
  '前端页面 test 类任务 steps 首步加入 `【规则】阅读 .claude-coder/assets/test_rule.md`。';
209
229
  }
@@ -2,16 +2,10 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const { runSession } = require('./session');
6
- const { buildQueryOptions } = require('./query');
7
5
  const { log } = require('../common/config');
6
+ const { Session } = require('./session');
8
7
 
9
- /**
10
- * 使用 AI 修复损坏的 JSON 文件
11
- * @param {string} filePath - 文件绝对路径
12
- * @param {object} [opts] - 透传给 runSession 的选项
13
- */
14
- async function repairJsonFile(filePath, opts = {}) {
8
+ async function executeRepair(config, filePath, opts = {}) {
15
9
  if (!fs.existsSync(filePath)) return;
16
10
 
17
11
  const rawContent = fs.readFileSync(filePath, 'utf8');
@@ -23,17 +17,13 @@ async function repairJsonFile(filePath, opts = {}) {
23
17
  const prompt = `文件 ${filePath} 的 JSON 格式已损坏,请修复并用 Write 工具写入原路径。\n\n当前损坏内容:\n${rawContent}`;
24
18
 
25
19
  try {
26
- await runSession('repair', {
27
- opts,
28
- sessionNum: 0,
20
+ await Session.run('repair', config, {
29
21
  logFileName: `repair_${fileName.replace('.json', '')}.log`,
30
22
  label: `repair:${fileName}`,
31
23
 
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);
24
+ async execute(session) {
25
+ const queryOpts = session.buildQueryOptions(opts);
26
+ await session.runQuery(prompt, queryOpts);
37
27
  log('ok', `AI 修复 ${fileName} 完成`);
38
28
  return {};
39
29
  },
@@ -43,4 +33,4 @@ async function repairJsonFile(filePath, opts = {}) {
43
33
  }
44
34
  }
45
35
 
46
- module.exports = { repairJsonFile };
36
+ module.exports = { executeRepair };
@@ -1,15 +1,20 @@
1
1
  'use strict';
2
2
 
3
+ const { execSync } = require('child_process');
3
4
  const readline = require('readline');
4
- const { log, loadConfig } = require('../common/config');
5
+ const { log } = require('../common/config');
5
6
  const { assets } = require('../common/assets');
6
- const { loadTasks, getFeatures, getStats, printStats } = require('../common/tasks');
7
- const { runCodingSession } = require('./coding');
8
- const { Harness, selectNextTask } = require('./harness');
9
- const { simplify } = require('./simplify');
10
- const { repairJsonFile } = require('./repair');
7
+ const { loadTasks, saveTasks, getFeatures, getStats, printStats } = require('../common/tasks');
8
+ const { getGitHead, sleep, tryPush, killServices } = require('../common/utils');
9
+ const { RETRY } = require('../common/constants');
10
+ const {
11
+ loadState, saveState, selectNextTask, isAllDone,
12
+ appendProgress, incrementSession, markSimplifyDone,
13
+ } = require('./state');
11
14
 
12
- // ─── Display Helpers ──────────────────────────────────────────
15
+ const MAX_RETRY = RETRY.MAX_ATTEMPTS;
16
+
17
+ // ─── Display Helpers ──────────────────────────────────────
13
18
 
14
19
  function printBanner(dryRun) {
15
20
  console.log('');
@@ -69,42 +74,296 @@ async function promptContinue() {
69
74
  });
70
75
  }
71
76
 
72
- // ─── Simplify Helper ─────────────────────────────────────────
77
+ // ─── Lifecycle: Snapshot ──────────────────────────────────
78
+
79
+ function snapshot(projectRoot, taskData) {
80
+ const nextTask = selectNextTask(taskData);
81
+ const taskId = nextTask?.id || 'unknown';
82
+
83
+ const state = loadState();
84
+ state.current_task_id = taskId;
85
+ saveState(state);
86
+
87
+ return {
88
+ headBefore: getGitHead(projectRoot),
89
+ taskId,
90
+ };
91
+ }
92
+
93
+ // ─── Lifecycle: Validation ────────────────────────────────
94
+
95
+ function _validateSessionResult() {
96
+ if (!assets.exists('sessionResult')) {
97
+ log('error', 'Agent 未生成 session_result.json');
98
+ return { valid: false, reason: 'session_result.json 不存在' };
99
+ }
100
+
101
+ const raw = assets.readJson('sessionResult', null);
102
+ if (raw === null) {
103
+ log('warn', 'session_result.json 解析失败');
104
+ return { valid: false, reason: 'JSON 解析失败', rawContent: assets.read('sessionResult') };
105
+ }
106
+
107
+ const data = raw.current && typeof raw.current === 'object' ? raw.current : raw;
108
+ const { TASK_STATUSES } = require('../common/constants');
109
+
110
+ const required = ['session_result', 'status_after'];
111
+ const missing = required.filter(k => !(k in data));
112
+ if (missing.length > 0) {
113
+ log('warn', `session_result.json 缺少字段: ${missing.join(', ')}`);
114
+ return { valid: false, reason: `缺少字段: ${missing.join(', ')}`, data };
115
+ }
116
+
117
+ if (!['success', 'failed'].includes(data.session_result)) {
118
+ return { valid: false, reason: `无效 session_result: ${data.session_result}`, data };
119
+ }
120
+
121
+ if (!TASK_STATUSES.includes(data.status_after)) {
122
+ return { valid: false, reason: `无效 status_after: ${data.status_after}`, data };
123
+ }
124
+
125
+ const level = data.session_result === 'success' ? 'ok' : 'warn';
126
+ log(level, `session_result.json 合法 (${data.session_result})`);
127
+ return { valid: true, data };
128
+ }
129
+
130
+ function _checkGitProgress(headBefore, projectRoot) {
131
+ if (!headBefore) {
132
+ log('info', '未提供 head_before,跳过 git 检查');
133
+ return { hasCommit: false, warning: false };
134
+ }
135
+
136
+ const headAfter = getGitHead(projectRoot);
137
+
138
+ if (headBefore === headAfter) {
139
+ log('warn', '本次会话没有新的 git 提交');
140
+ return { hasCommit: false, warning: true };
141
+ }
142
+
143
+ try {
144
+ const msg = execSync('git log --oneline -1', { cwd: projectRoot, encoding: 'utf8' }).trim();
145
+ log('ok', `检测到新提交: ${msg}`);
146
+ } catch { /* ignore */ }
147
+
148
+ return { hasCommit: true, warning: false };
149
+ }
150
+
151
+ function _inferFromTasks(taskId) {
152
+ if (!taskId) return null;
153
+ const data = loadTasks();
154
+ if (!data) return null;
155
+ const task = getFeatures(data).find(f => f.id === taskId);
156
+ return task ? task.status : null;
157
+ }
158
+
159
+ async function validate(config, headBefore, taskId) {
160
+ const projectRoot = assets.projectRoot;
161
+ log('info', '========== 开始校验 ==========');
162
+
163
+ let srResult = _validateSessionResult();
164
+ const gitResult = _checkGitProgress(headBefore, projectRoot);
165
+
166
+ if (!srResult.valid && srResult.rawContent) {
167
+ const srPath = assets.path('sessionResult');
168
+ if (srPath) {
169
+ const { executeRepair } = require('./repair');
170
+ await executeRepair(config, srPath);
171
+ srResult = _validateSessionResult();
172
+ }
173
+ }
174
+
175
+ let fatal = false;
176
+ let hasWarnings = false;
177
+
178
+ if (srResult.valid) {
179
+ hasWarnings = gitResult.warning;
180
+ } else {
181
+ if (gitResult.hasCommit) {
182
+ const taskStatus = _inferFromTasks(taskId);
183
+ if (taskStatus === 'done' || taskStatus === 'testing') {
184
+ log('warn', `session_result.json 异常,但 tasks.json 显示 ${taskId} 已 ${taskStatus},且有新提交,降级为警告`);
185
+ } else {
186
+ log('warn', 'session_result.json 异常,但有新提交,降级为警告(不回滚代码)');
187
+ }
188
+ hasWarnings = true;
189
+ } else {
190
+ log('error', '无新提交且 session_result.json 异常,视为致命');
191
+ fatal = true;
192
+ }
193
+ }
194
+
195
+ if (fatal) {
196
+ log('error', '========== 校验失败 (致命) ==========');
197
+ } else if (hasWarnings) {
198
+ log('warn', '========== 校验通过 (有警告) ==========');
199
+ } else {
200
+ log('ok', '========== 校验全部通过 ==========');
201
+ }
202
+
203
+ const reason = fatal ? (srResult.reason || '无新提交且 session_result.json 异常') : '';
204
+ return { fatal, hasWarnings, sessionData: srResult.data, reason };
205
+ }
206
+
207
+ // ─── Lifecycle: Rollback ──────────────────────────────────
208
+
209
+ async function rollback(headBefore, reason) {
210
+ if (!headBefore || headBefore === 'none') return;
211
+
212
+ const projectRoot = assets.projectRoot;
213
+ killServices(projectRoot);
214
+ if (process.platform === 'win32') await sleep(1500);
215
+
216
+ const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
217
+
218
+ log('warn', `回滚到 ${headBefore} ...`);
219
+
220
+ let success = false;
221
+ for (let attempt = 1; attempt <= 2; attempt++) {
222
+ try {
223
+ execSync(`git reset --hard ${headBefore}`, { cwd: projectRoot, stdio: 'pipe', env: gitEnv });
224
+ execSync('git clean -fd', { cwd: projectRoot, stdio: 'pipe', env: gitEnv });
225
+ log('ok', '回滚完成');
226
+ success = true;
227
+ break;
228
+ } catch (err) {
229
+ if (attempt === 1) {
230
+ log('warn', `回滚首次失败,等待后重试: ${err.message}`);
231
+ await sleep(2000);
232
+ } else {
233
+ log('error', `回滚失败: ${err.message}`);
234
+ }
235
+ }
236
+ }
237
+
238
+ appendProgress({
239
+ type: 'rollback',
240
+ timestamp: _timestamp(),
241
+ reason: reason || 'harness 校验失败',
242
+ rollbackTo: headBefore,
243
+ success,
244
+ });
245
+ }
246
+
247
+ // ─── Lifecycle: Retry / Skip ──────────────────────────────
248
+
249
+ function _markTaskFailed(taskId) {
250
+ if (!taskId) return;
251
+ const data = loadTasks();
252
+ if (!data) return;
253
+ const features = getFeatures(data);
254
+ const task = features.find(f => f.id === taskId);
255
+ if (task && task.status !== 'done') {
256
+ task.status = 'failed';
257
+ saveTasks(data);
258
+ log('warn', `已将任务 ${taskId} 强制标记为 failed`);
259
+ }
260
+ }
261
+
262
+ async function _handleRetryOrSkip(session, {
263
+ headBefore, taskId, sessionResult, consecutiveFailures, result, reason, lastFailMsg,
264
+ }) {
265
+ const newFailures = consecutiveFailures + 1;
266
+ const exceeded = newFailures >= MAX_RETRY;
267
+
268
+ await rollback(headBefore, reason);
269
+
270
+ if (exceeded) {
271
+ log('error', `连续失败 ${MAX_RETRY} 次,跳过当前任务`);
272
+ _markTaskFailed(taskId);
273
+ }
274
+
275
+ const entry = { session, timestamp: _timestamp(), result, cost: sessionResult.cost, taskId };
276
+ if (result === 'fatal') entry.reason = reason;
277
+ appendProgress(entry);
278
+
279
+ if (exceeded) return { consecutiveFailures: 0, lastFailReason: '' };
280
+ return { consecutiveFailures: newFailures, lastFailReason: lastFailMsg };
281
+ }
282
+
283
+ // ─── Lifecycle: Session Outcome ───────────────────────────
284
+
285
+ async function onSuccess(session, { taskId, sessionResult, validateResult }) {
286
+ incrementSession();
287
+
288
+ appendProgress({
289
+ session,
290
+ timestamp: _timestamp(),
291
+ result: 'success',
292
+ cost: sessionResult.cost,
293
+ taskId,
294
+ statusAfter: validateResult.sessionData?.status_after || null,
295
+ notes: validateResult.sessionData?.notes || null,
296
+ });
297
+
298
+ return { consecutiveFailures: 0, lastFailReason: '' };
299
+ }
300
+
301
+ async function onFailure(session, { headBefore, taskId, sessionResult, validateResult, consecutiveFailures }) {
302
+ const reason = validateResult.reason || '校验失败';
303
+ log('error', `Session ${session} 校验失败 (连续失败: ${consecutiveFailures + 1}/${MAX_RETRY})`);
304
+ return _handleRetryOrSkip(session, {
305
+ headBefore, taskId, sessionResult, consecutiveFailures,
306
+ result: 'fatal', reason,
307
+ lastFailMsg: `上次校验失败: ${reason},代码已回滚`,
308
+ });
309
+ }
310
+
311
+ async function onStall(session, { headBefore, taskId, sessionResult, consecutiveFailures }) {
312
+ log('warn', `Session ${session} 因停顿超时中断,跳过校验直接重试`);
313
+ return _handleRetryOrSkip(session, {
314
+ headBefore, taskId, sessionResult, consecutiveFailures,
315
+ result: 'stalled', reason: '停顿超时',
316
+ lastFailMsg: '上次会话停顿超时,已回滚',
317
+ });
318
+ }
319
+
320
+ // ─── Lifecycle: Simplify Scheduling ───────────────────────
73
321
 
74
- async function tryRunSimplify(harness, config, msg, commitMsg) {
322
+ function shouldSimplify(config) {
323
+ const { simplifyInterval } = config;
324
+ if (simplifyInterval <= 0) return false;
325
+ const state = loadState();
326
+ return state.session_count % simplifyInterval === 0;
327
+ }
328
+
329
+ function needsFinalSimplify(config) {
330
+ const { simplifyInterval } = config;
331
+ if (simplifyInterval <= 0) return false;
332
+ const state = loadState();
333
+ return state.last_simplify_session < state.session_count;
334
+ }
335
+
336
+ async function tryRunSimplify(config, msg) {
75
337
  log('info', msg || `每 ${config.simplifyInterval} 个成功 session 运行代码审查...`);
76
338
  try {
77
- await simplify(null, { n: config.simplifyCommits });
78
- harness.afterSimplify(commitMsg);
339
+ const { executeSimplify } = require('./simplify');
340
+ await executeSimplify(config, null, { n: config.simplifyCommits });
341
+ markSimplifyDone();
79
342
  } catch (err) {
80
343
  log('warn', `代码审查失败,跳过: ${err.message}`);
81
344
  }
82
345
  }
83
346
 
84
- // ─── Main Orchestration Loop ──────────────────────────────────
347
+ // ─── Utilities ────────────────────────────────────────────
85
348
 
86
- async function run(opts = {}) {
87
- const config = loadConfig();
88
- const harness = new Harness(config);
349
+ function _timestamp() {
350
+ return new Date().toISOString().replace(/[-:T]/g, '').slice(0, 12);
351
+ }
352
+
353
+ // ─── Main Orchestration Loop ──────────────────────────────
89
354
 
90
- harness.ensureEnvironment();
355
+ async function executeRun(config, opts = {}) {
356
+ if (!assets.exists('tasks')) {
357
+ throw new Error('tasks.json 不存在,请先运行 claude-coder plan 生成任务');
358
+ }
91
359
 
360
+ const projectRoot = assets.projectRoot;
92
361
  const dryRun = opts.dryRun || false;
93
362
  const maxSessions = opts.max || 50;
94
363
  const pauseEvery = opts.pause ?? 0;
95
364
 
96
365
  printBanner(dryRun);
97
366
 
98
- if (config.provider !== 'claude' && config.baseUrl) {
99
- log('ok', `模型配置已加载: ${config.provider}${config.model ? ` (${config.model})` : ''}`);
100
- }
101
-
102
- const prereq = harness.checkPrerequisites();
103
- if (!prereq.ok) {
104
- log('error', prereq.msg);
105
- process.exit(1);
106
- }
107
-
108
367
  printStats();
109
368
 
110
369
  log('info', `开始编码循环 (最多 ${maxSessions} 个会话) ...`);
@@ -118,7 +377,10 @@ async function run(opts = {}) {
118
377
  let taskData = loadTasks();
119
378
  if (!taskData) {
120
379
  const tasksPath = assets.path('tasks');
121
- if (tasksPath) await repairJsonFile(tasksPath);
380
+ if (tasksPath) {
381
+ const { executeRepair } = require('./repair');
382
+ await executeRepair(config, tasksPath);
383
+ }
122
384
  taskData = loadTasks();
123
385
  if (!taskData) {
124
386
  log('error', 'tasks.json 无法读取且修复失败,终止循环');
@@ -126,12 +388,12 @@ async function run(opts = {}) {
126
388
  }
127
389
  }
128
390
 
129
- if (harness.isAllDone(taskData)) {
391
+ if (isAllDone(taskData)) {
130
392
  if (!dryRun) {
131
- if (harness.needsFinalSimplify()) {
132
- await tryRunSimplify(harness, config, '所有任务完成,运行最终代码审查...', 'style: final simplify');
393
+ if (needsFinalSimplify(config)) {
394
+ await tryRunSimplify(config, '所有任务完成,运行最终代码审查...');
133
395
  }
134
- harness.tryPush();
396
+ tryPush(projectRoot);
135
397
  }
136
398
  console.log('');
137
399
  log('ok', '所有任务已完成!');
@@ -146,10 +408,11 @@ async function run(opts = {}) {
146
408
  break;
147
409
  }
148
410
 
149
- const { headBefore, taskId } = harness.snapshot(taskData);
411
+ const { headBefore, taskId } = snapshot(projectRoot, taskData);
150
412
 
151
- const sessionResult = await runCodingSession(session, {
152
- projectRoot: harness.projectRoot,
413
+ const { executeCoding } = require('./coding');
414
+ const sessionResult = await executeCoding(config, session, {
415
+ projectRoot,
153
416
  taskId,
154
417
  consecutiveFailures: state.consecutiveFailures,
155
418
  maxSessions,
@@ -157,24 +420,24 @@ async function run(opts = {}) {
157
420
  });
158
421
 
159
422
  if (sessionResult.stalled) {
160
- state = await harness.onStall(session, { headBefore, taskId, sessionResult, ...state });
423
+ state = await onStall(session, { headBefore, taskId, sessionResult, ...state });
161
424
  continue;
162
425
  }
163
426
 
164
427
  log('info', '开始 harness 校验 ...');
165
- const validateResult = await harness.validate(headBefore, taskId);
428
+ const validateResult = await validate(config, headBefore, taskId);
166
429
 
167
430
  if (!validateResult.fatal) {
168
431
  const level = validateResult.hasWarnings ? 'warn' : 'ok';
169
432
  log(level, `Session ${session} 校验通过${validateResult.hasWarnings ? ' (有警告)' : ''}`);
170
- state = await harness.onSuccess(session, { taskId, sessionResult, validateResult });
433
+ state = await onSuccess(session, { taskId, sessionResult, validateResult });
171
434
 
172
- if (harness.shouldSimplify()) {
173
- await tryRunSimplify(harness, config);
435
+ if (shouldSimplify(config)) {
436
+ await tryRunSimplify(config);
174
437
  }
175
- harness.tryPush();
438
+ tryPush(projectRoot);
176
439
  } else {
177
- state = await harness.onFailure(session, { headBefore, taskId, sessionResult, validateResult, ...state });
440
+ state = await onFailure(session, { headBefore, taskId, sessionResult, validateResult, ...state });
178
441
  }
179
442
 
180
443
  if (pauseEvery > 0 && session % pauseEvery === 0) {
@@ -187,9 +450,9 @@ async function run(opts = {}) {
187
450
  }
188
451
  }
189
452
 
190
- harness.cleanup();
453
+ killServices(projectRoot);
191
454
  printEndBanner();
192
455
  printStats();
193
456
  }
194
457
 
195
- module.exports = { run };
458
+ module.exports = { executeRun };