claude-coder 1.7.0 → 1.8.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 +177 -125
- package/bin/cli.js +159 -161
- package/package.json +52 -47
- package/src/commands/auth.js +294 -0
- package/src/commands/setup-modules/helpers.js +105 -0
- package/src/commands/setup-modules/index.js +26 -0
- package/src/commands/setup-modules/mcp.js +95 -0
- package/src/commands/setup-modules/provider.js +261 -0
- package/src/commands/setup-modules/safety.js +62 -0
- package/src/commands/setup-modules/simplify.js +53 -0
- package/src/commands/setup.js +172 -0
- package/src/common/assets.js +192 -0
- package/src/{config.js → common/config.js} +138 -201
- package/src/common/constants.js +57 -0
- package/src/{indicator.js → common/indicator.js} +222 -217
- package/src/common/interaction.js +170 -0
- package/src/common/logging.js +77 -0
- package/src/common/sdk.js +51 -0
- package/src/{tasks.js → common/tasks.js} +157 -172
- package/src/common/utils.js +147 -0
- package/src/core/base.js +54 -0
- package/src/core/coding.js +55 -0
- package/src/core/context.js +132 -0
- package/src/core/hooks.js +529 -0
- package/src/{init.js → core/init.js} +163 -144
- package/src/core/plan.js +318 -0
- package/src/core/prompts.js +253 -0
- package/src/core/query.js +48 -0
- package/src/core/repair.js +58 -0
- package/src/{runner.js → core/runner.js} +352 -420
- package/src/core/scan.js +89 -0
- package/src/core/simplify.js +59 -0
- package/src/core/validator.js +138 -0
- package/{prompts/ADD_GUIDE.md → templates/addGuide.md} +98 -98
- package/templates/addUser.md +26 -0
- package/{prompts/CLAUDE.md → templates/agentProtocol.md} +195 -199
- package/templates/bash-process.md +5 -0
- package/{prompts/coding_user.md → templates/codingUser.md} +31 -23
- package/templates/guidance.json +35 -0
- package/templates/playwright.md +17 -0
- package/templates/requirements.example.md +56 -56
- package/{prompts/SCAN_PROTOCOL.md → templates/scanProtocol.md} +118 -118
- package/{prompts/scan_user.md → templates/scanUser.md} +17 -17
- package/templates/test_rule.md +194 -194
- package/prompts/add_user.md +0 -24
- package/src/auth.js +0 -245
- package/src/hooks.js +0 -160
- package/src/prompts.js +0 -295
- package/src/scanner.js +0 -62
- package/src/session.js +0 -352
- package/src/setup.js +0 -579
- package/src/validator.js +0 -181
|
@@ -1,420 +1,352 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const {
|
|
7
|
-
const {
|
|
8
|
-
const {
|
|
9
|
-
const {
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
markTaskFailed();
|
|
354
|
-
consecutiveFailures = 0;
|
|
355
|
-
log('warn', '已将任务标记为 failed,继续下一个任务');
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (pauseEvery > 0 && session % pauseEvery === 0) {
|
|
360
|
-
console.log('');
|
|
361
|
-
printStats();
|
|
362
|
-
const shouldContinue = await promptContinue();
|
|
363
|
-
if (!shouldContinue) {
|
|
364
|
-
log('info', '手动停止');
|
|
365
|
-
break;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
killServicesByProfile();
|
|
371
|
-
|
|
372
|
-
console.log('');
|
|
373
|
-
console.log('============================================');
|
|
374
|
-
console.log(' 运行结束');
|
|
375
|
-
console.log('============================================');
|
|
376
|
-
console.log('');
|
|
377
|
-
printStats();
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
async function add(instruction, opts = {}) {
|
|
381
|
-
await loadSDK();
|
|
382
|
-
const p = paths();
|
|
383
|
-
const projectRoot = getProjectRoot();
|
|
384
|
-
ensureLoopDir();
|
|
385
|
-
|
|
386
|
-
const config = loadConfig();
|
|
387
|
-
|
|
388
|
-
if (!opts.model) {
|
|
389
|
-
if (config.defaultOpus) {
|
|
390
|
-
opts.model = config.defaultOpus;
|
|
391
|
-
} else if (config.model) {
|
|
392
|
-
opts.model = config.model;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const displayModel = opts.model || config.model || '(default)';
|
|
397
|
-
log('ok', `模型配置已加载: ${config.provider || 'claude'} (add 使用: ${displayModel})`);
|
|
398
|
-
|
|
399
|
-
if (!fs.existsSync(p.profile)) {
|
|
400
|
-
log('error', 'add 需要先完成项目扫描(至少运行一次 claude-coder run)');
|
|
401
|
-
process.exit(1);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
deployTestRule(p);
|
|
405
|
-
|
|
406
|
-
await runAddSession(instruction, { projectRoot, ...opts });
|
|
407
|
-
printStats();
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function deployTestRule(p) {
|
|
411
|
-
const dest = path.join(p.loopDir, 'test_rule.md');
|
|
412
|
-
if (fs.existsSync(dest)) return;
|
|
413
|
-
if (!fs.existsSync(p.testRuleTemplate)) return;
|
|
414
|
-
try {
|
|
415
|
-
fs.copyFileSync(p.testRuleTemplate, dest);
|
|
416
|
-
log('ok', '已部署测试指导规则 → .claude-coder/test_rule.md');
|
|
417
|
-
} catch { /* ignore */ }
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
module.exports = { run, add };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
const { log, loadConfig } = require('../common/config');
|
|
7
|
+
const { assets } = require('../common/assets');
|
|
8
|
+
const { getGitHead, isGitRepo, sleep } = require('../common/utils');
|
|
9
|
+
const { RETRY } = require('../common/constants');
|
|
10
|
+
const { loadTasks, getFeatures, getStats, findNextTask, forceStatus, printStats } = require('../common/tasks');
|
|
11
|
+
const { validate } = require('./validator');
|
|
12
|
+
const { runCodingSession } = require('./coding');
|
|
13
|
+
const { simplify } = require('./simplify');
|
|
14
|
+
const { repairFile } = require('./repair');
|
|
15
|
+
const { buildArchivePrompt } = require('./prompts');
|
|
16
|
+
const { loadSDK } = require('../common/sdk');
|
|
17
|
+
|
|
18
|
+
const MAX_RETRY = RETRY.MAX_ATTEMPTS;
|
|
19
|
+
|
|
20
|
+
function getHead() {
|
|
21
|
+
return getGitHead(assets.projectRoot);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function killServicesByProfile() {
|
|
25
|
+
const profile = assets.readJson('profile', null);
|
|
26
|
+
if (!profile) return;
|
|
27
|
+
try {
|
|
28
|
+
const services = profile.services || [];
|
|
29
|
+
const ports = services.map(s => s.port).filter(Boolean);
|
|
30
|
+
if (ports.length === 0) return;
|
|
31
|
+
|
|
32
|
+
const isWin = process.platform === 'win32';
|
|
33
|
+
for (const port of ports) {
|
|
34
|
+
try {
|
|
35
|
+
if (isWin) {
|
|
36
|
+
const out = execSync(`netstat -ano | findstr :${port} | findstr LISTENING`, { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
37
|
+
const pids = [...new Set(out.split('\n').map(l => l.trim().split(/\s+/).pop()).filter(Boolean))];
|
|
38
|
+
for (const pid of pids) { try { execSync(`taskkill /F /PID ${pid}`, { stdio: 'pipe' }); } catch { /* ignore */ } }
|
|
39
|
+
} else {
|
|
40
|
+
execSync(`lsof -ti :${port} | xargs kill -9 2>/dev/null`, { stdio: 'pipe' });
|
|
41
|
+
}
|
|
42
|
+
} catch { /* no process on port */ }
|
|
43
|
+
}
|
|
44
|
+
log('info', `已停止端口 ${ports.join(', ')} 上的服务`);
|
|
45
|
+
} catch { /* ignore profile read errors */ }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function rollback(headBefore, reason) {
|
|
49
|
+
if (!headBefore || headBefore === 'none') return;
|
|
50
|
+
|
|
51
|
+
killServicesByProfile();
|
|
52
|
+
|
|
53
|
+
if (process.platform === 'win32') await sleep(1500);
|
|
54
|
+
|
|
55
|
+
const cwd = assets.projectRoot;
|
|
56
|
+
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
57
|
+
|
|
58
|
+
log('warn', `回滚到 ${headBefore} ...`);
|
|
59
|
+
|
|
60
|
+
let success = false;
|
|
61
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
62
|
+
try {
|
|
63
|
+
execSync(`git reset --hard ${headBefore}`, { cwd, stdio: 'pipe', env: gitEnv });
|
|
64
|
+
log('ok', '回滚完成');
|
|
65
|
+
success = true;
|
|
66
|
+
break;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (attempt === 1) {
|
|
69
|
+
log('warn', `回滚首次失败,等待后重试: ${err.message}`);
|
|
70
|
+
await sleep(2000);
|
|
71
|
+
} else {
|
|
72
|
+
log('error', `回滚失败: ${err.message}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
appendProgress({
|
|
78
|
+
type: 'rollback',
|
|
79
|
+
timestamp: new Date().toISOString(),
|
|
80
|
+
reason: reason || 'harness 校验失败',
|
|
81
|
+
rollbackTo: headBefore,
|
|
82
|
+
success,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function markTaskFailed() {
|
|
87
|
+
const data = loadTasks();
|
|
88
|
+
if (!data) return;
|
|
89
|
+
const result = forceStatus(data, 'failed');
|
|
90
|
+
if (result) {
|
|
91
|
+
log('warn', `已将任务 ${result.id} 强制标记为 failed`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function tryPush() {
|
|
96
|
+
try {
|
|
97
|
+
const cwd = assets.projectRoot;
|
|
98
|
+
const remotes = execSync('git remote', { cwd, encoding: 'utf8' }).trim();
|
|
99
|
+
if (!remotes) return;
|
|
100
|
+
log('info', '正在推送代码...');
|
|
101
|
+
execSync('git push', { cwd, stdio: 'inherit' });
|
|
102
|
+
log('ok', '推送成功');
|
|
103
|
+
} catch {
|
|
104
|
+
log('warn', '推送失败 (请检查网络或权限),继续执行...');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function appendProgress(entry) {
|
|
109
|
+
let progress = assets.readJson('progress', { sessions: [] });
|
|
110
|
+
if (!Array.isArray(progress.sessions)) progress.sessions = [];
|
|
111
|
+
progress.sessions.push(entry);
|
|
112
|
+
assets.writeJson('progress', progress);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function archiveDoneTasks() {
|
|
116
|
+
const data = loadTasks();
|
|
117
|
+
if (!data) return;
|
|
118
|
+
const features = data.features || [];
|
|
119
|
+
const done = features.filter(f => f.status === 'done');
|
|
120
|
+
if (done.length < 3) return;
|
|
121
|
+
|
|
122
|
+
const tasksPath = assets.path('tasks');
|
|
123
|
+
const prompt = buildArchivePrompt(done, data.completed_milestones, tasksPath);
|
|
124
|
+
|
|
125
|
+
log('info', `归档 ${done.length} 个完成任务...`);
|
|
126
|
+
await repairFile(tasksPath, { prompt });
|
|
127
|
+
log('ok', '任务归档完成');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function promptContinue() {
|
|
131
|
+
if (!process.stdin.isTTY) return true;
|
|
132
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
133
|
+
return new Promise(resolve => {
|
|
134
|
+
rl.question('是否继续?(y/n) ', answer => {
|
|
135
|
+
rl.close();
|
|
136
|
+
resolve(/^[Yy]/.test(answer.trim()));
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function run(opts = {}) {
|
|
142
|
+
assets.ensureDirs();
|
|
143
|
+
const projectRoot = assets.projectRoot;
|
|
144
|
+
|
|
145
|
+
const maxSessions = opts.max || 50;
|
|
146
|
+
const pauseEvery = opts.pause ?? 0;
|
|
147
|
+
const dryRun = opts.dryRun || false;
|
|
148
|
+
|
|
149
|
+
console.log('');
|
|
150
|
+
console.log('============================================');
|
|
151
|
+
console.log(` Claude Coder${dryRun ? ' (预览模式)' : ''}`);
|
|
152
|
+
console.log('============================================');
|
|
153
|
+
console.log('');
|
|
154
|
+
|
|
155
|
+
const config = loadConfig();
|
|
156
|
+
if (config.provider !== 'claude' && config.baseUrl) {
|
|
157
|
+
log('ok', `模型配置已加载: ${config.provider}${config.model ? ` (${config.model})` : ''}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!isGitRepo(projectRoot)) {
|
|
161
|
+
log('info', '初始化 git 仓库...');
|
|
162
|
+
execSync('git init', { cwd: projectRoot, stdio: 'inherit' });
|
|
163
|
+
execSync('git add -A && git commit -m "init: 项目初始化" --allow-empty', {
|
|
164
|
+
cwd: projectRoot,
|
|
165
|
+
stdio: 'inherit',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!assets.exists('profile')) {
|
|
170
|
+
log('error', 'profile 不存在,请先运行 claude-coder init 初始化项目');
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!assets.exists('tasks')) {
|
|
175
|
+
log('error', 'tasks.json 不存在,请先运行 claude-coder plan 生成任务');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
printStats();
|
|
180
|
+
|
|
181
|
+
if (!dryRun) {
|
|
182
|
+
await loadSDK();
|
|
183
|
+
}
|
|
184
|
+
log('info', `开始编码循环 (最多 ${maxSessions} 个会话) ...`);
|
|
185
|
+
console.log('');
|
|
186
|
+
|
|
187
|
+
let consecutiveFailures = 0;
|
|
188
|
+
|
|
189
|
+
for (let session = 1; session <= maxSessions; session++) {
|
|
190
|
+
console.log('');
|
|
191
|
+
console.log('--------------------------------------------');
|
|
192
|
+
log('info', `Session ${session} / ${maxSessions}`);
|
|
193
|
+
console.log('--------------------------------------------');
|
|
194
|
+
|
|
195
|
+
let taskData = loadTasks();
|
|
196
|
+
if (!taskData) {
|
|
197
|
+
log('warn', 'tasks.json 读取异常,尝试 AI 修复...');
|
|
198
|
+
await repairFile(assets.path('tasks'));
|
|
199
|
+
taskData = loadTasks();
|
|
200
|
+
if (!taskData) {
|
|
201
|
+
log('error', 'tasks.json 无法修复,终止循环');
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
log('ok', 'tasks.json AI 修复成功');
|
|
205
|
+
await archiveDoneTasks();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const features = getFeatures(taskData);
|
|
209
|
+
if (features.length > 0 && features.every(f => f.status === 'done')) {
|
|
210
|
+
console.log('');
|
|
211
|
+
log('ok', '所有任务已完成!');
|
|
212
|
+
printStats();
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const stats = getStats(taskData);
|
|
217
|
+
log('info', `进度: ${stats.done}/${stats.total} done, ${stats.in_progress} in_progress, ${stats.testing} testing, ${stats.failed} failed, ${stats.pending} pending`);
|
|
218
|
+
|
|
219
|
+
if (dryRun) {
|
|
220
|
+
const next = findNextTask(taskData);
|
|
221
|
+
log('info', `[DRY-RUN] 下一个任务: ${next ? `${next.id} - ${next.description}` : '无待处理任务'}`);
|
|
222
|
+
if (!next) {
|
|
223
|
+
log('ok', '[DRY-RUN] 无可执行任务,预览结束');
|
|
224
|
+
} else {
|
|
225
|
+
console.log('');
|
|
226
|
+
log('info', '[DRY-RUN] 任务队列:');
|
|
227
|
+
const allFeatures = getFeatures(taskData);
|
|
228
|
+
for (const f of allFeatures) {
|
|
229
|
+
const st = f.status || 'unknown';
|
|
230
|
+
const statusTag = { done: '✓', in_progress: '▸', pending: '○', failed: '✗', testing: '◇' }[st] || '?';
|
|
231
|
+
log('info', ` ${statusTag} [${st.padEnd(11)}] ${f.id} - ${f.description || ''}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const headBefore = getHead();
|
|
238
|
+
const nextTask = findNextTask(taskData);
|
|
239
|
+
const taskId = nextTask?.id || 'unknown';
|
|
240
|
+
|
|
241
|
+
const sessionResult = await runCodingSession(session, {
|
|
242
|
+
projectRoot,
|
|
243
|
+
taskId,
|
|
244
|
+
consecutiveFailures,
|
|
245
|
+
maxSessions,
|
|
246
|
+
lastValidateLog: consecutiveFailures > 0 ? '上次校验失败' : '',
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (sessionResult.stalled) {
|
|
250
|
+
log('warn', `Session ${session} 因停顿超时中断,跳过校验直接重试`);
|
|
251
|
+
consecutiveFailures++;
|
|
252
|
+
await rollback(headBefore, '停顿超时');
|
|
253
|
+
if (consecutiveFailures >= MAX_RETRY) {
|
|
254
|
+
log('error', `连续失败 ${MAX_RETRY} 次,跳过当前任务`);
|
|
255
|
+
markTaskFailed();
|
|
256
|
+
consecutiveFailures = 0;
|
|
257
|
+
}
|
|
258
|
+
appendProgress({
|
|
259
|
+
session,
|
|
260
|
+
timestamp: new Date().toISOString(),
|
|
261
|
+
result: 'stalled',
|
|
262
|
+
cost: sessionResult.cost,
|
|
263
|
+
taskId,
|
|
264
|
+
});
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
log('info', '开始 harness 校验 ...');
|
|
269
|
+
const validateResult = await validate(headBefore, taskId);
|
|
270
|
+
|
|
271
|
+
if (!validateResult.fatal) {
|
|
272
|
+
if (validateResult.hasWarnings) {
|
|
273
|
+
log('warn', `Session ${session} 校验通过 (有自动修复或警告)`);
|
|
274
|
+
} else {
|
|
275
|
+
log('ok', `Session ${session} 校验通过`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 定期归档 + 代码审查
|
|
279
|
+
const simplifyInterval = config.simplifyInterval;
|
|
280
|
+
if (simplifyInterval > 0 && session % simplifyInterval === 0) {
|
|
281
|
+
await archiveDoneTasks();
|
|
282
|
+
log('info', `每 ${simplifyInterval} 个 session 运行代码审查...`);
|
|
283
|
+
await simplify(null, { n: config.simplifyCommits });
|
|
284
|
+
|
|
285
|
+
// 检查是否有代码变更
|
|
286
|
+
try {
|
|
287
|
+
execSync('git diff --quiet HEAD', { cwd: projectRoot, stdio: 'pipe' });
|
|
288
|
+
} catch {
|
|
289
|
+
// 有变更,自动提交
|
|
290
|
+
execSync('git add -A && git commit -m "style: simplify optimization"', { cwd: projectRoot, stdio: 'pipe' });
|
|
291
|
+
log('ok', '代码优化已提交');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
tryPush();
|
|
296
|
+
consecutiveFailures = 0;
|
|
297
|
+
|
|
298
|
+
appendProgress({
|
|
299
|
+
session,
|
|
300
|
+
timestamp: new Date().toISOString(),
|
|
301
|
+
result: 'success',
|
|
302
|
+
cost: sessionResult.cost,
|
|
303
|
+
taskId,
|
|
304
|
+
statusAfter: validateResult.sessionData?.status_after || null,
|
|
305
|
+
notes: validateResult.sessionData?.notes || null,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
} else {
|
|
309
|
+
consecutiveFailures++;
|
|
310
|
+
log('error', `Session ${session} 校验失败 (连续失败: ${consecutiveFailures}/${MAX_RETRY})`);
|
|
311
|
+
|
|
312
|
+
appendProgress({
|
|
313
|
+
session,
|
|
314
|
+
timestamp: new Date().toISOString(),
|
|
315
|
+
result: 'fatal',
|
|
316
|
+
cost: sessionResult.cost,
|
|
317
|
+
taskId,
|
|
318
|
+
reason: validateResult.sessionData?.reason || '校验失败',
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
await rollback(headBefore, '校验失败');
|
|
322
|
+
|
|
323
|
+
if (consecutiveFailures >= MAX_RETRY) {
|
|
324
|
+
log('error', `连续失败 ${MAX_RETRY} 次,跳过当前任务`);
|
|
325
|
+
markTaskFailed();
|
|
326
|
+
consecutiveFailures = 0;
|
|
327
|
+
log('warn', '已将任务标记为 failed,继续下一个任务');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (pauseEvery > 0 && session % pauseEvery === 0) {
|
|
332
|
+
console.log('');
|
|
333
|
+
printStats();
|
|
334
|
+
const shouldContinue = await promptContinue();
|
|
335
|
+
if (!shouldContinue) {
|
|
336
|
+
log('info', '手动停止');
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
killServicesByProfile();
|
|
343
|
+
|
|
344
|
+
console.log('');
|
|
345
|
+
console.log('============================================');
|
|
346
|
+
console.log(' 运行结束');
|
|
347
|
+
console.log('============================================');
|
|
348
|
+
console.log('');
|
|
349
|
+
printStats();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
module.exports = { run };
|