claude-coder 1.9.2 → 1.10.1

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 (40) hide show
  1. package/README.md +166 -141
  2. package/bin/cli.js +19 -4
  3. package/package.json +2 -2
  4. package/src/common/assets.js +66 -52
  5. package/src/common/config.js +22 -0
  6. package/src/common/sdk.js +1 -3
  7. package/src/common/utils.js +3 -1
  8. package/src/core/coding.js +3 -1
  9. package/src/core/design.js +268 -0
  10. package/src/core/go.js +3 -3
  11. package/src/core/hooks.js +30 -16
  12. package/src/core/init.js +9 -0
  13. package/src/core/plan.js +21 -15
  14. package/src/core/prompts.js +47 -2
  15. package/src/core/repair.js +1 -1
  16. package/src/core/runner.js +84 -117
  17. package/src/core/scan.js +4 -3
  18. package/src/core/session.js +30 -16
  19. package/src/core/simplify.js +4 -2
  20. package/src/core/state.js +23 -8
  21. package/src/index.js +4 -0
  22. package/templates/{codingUser.md → coding/user.md} +1 -0
  23. package/templates/design/base.md +103 -0
  24. package/templates/design/fixSystem.md +71 -0
  25. package/templates/design/fixUser.md +3 -0
  26. package/templates/design/init.md +304 -0
  27. package/templates/design/system.md +108 -0
  28. package/templates/design/user.md +11 -0
  29. package/templates/{coreProtocol.md → other/coreProtocol.md} +1 -0
  30. package/templates/{test_rule.md → other/test_rule.md} +0 -2
  31. package/templates/{planUser.md → plan/user.md} +2 -1
  32. /package/templates/{codingSystem.md → coding/system.md} +0 -0
  33. /package/templates/{goSystem.md → go/system.md} +0 -0
  34. /package/templates/{bash-process.md → other/bash-process.md} +0 -0
  35. /package/templates/{guidance.json → other/guidance.json} +0 -0
  36. /package/templates/{requirements.example.md → other/requirements.example.md} +0 -0
  37. /package/templates/{web-testing.md → other/web-testing.md} +0 -0
  38. /package/templates/{planSystem.md → plan/system.md} +0 -0
  39. /package/templates/{scanSystem.md → scan/system.md} +0 -0
  40. /package/templates/{scanUser.md → scan/user.md} +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { execSync } = require('child_process');
4
4
  const readline = require('readline');
5
- const { log } = require('../common/config');
5
+ const { log, COLOR, printModeBanner } = require('../common/config');
6
6
  const { assets } = require('../common/assets');
7
7
  const { loadTasks, saveTasks, getFeatures, getStats, printStats } = require('../common/tasks');
8
8
  const { getGitHead, sleep, tryPush, killServices } = require('../common/utils');
@@ -16,24 +16,29 @@ const MAX_RETRY = RETRY.MAX_ATTEMPTS;
16
16
 
17
17
  // ─── Display Helpers ──────────────────────────────────────
18
18
 
19
- function printBanner(dryRun) {
20
- console.log('');
21
- console.log('============================================');
22
- console.log(` Claude Coder${dryRun ? ' (预览模式)' : ''}`);
23
- console.log('============================================');
24
- console.log('');
19
+ function printBanner(dryRun, config, maxSessions) {
20
+ const mode = dryRun ? '预览模式' : `max: ${maxSessions}`;
21
+ printModeBanner('run', mode, config?.model);
25
22
  }
26
23
 
27
- function printSessionHeader(session, maxSessions) {
28
- console.log('');
29
- console.log('--------------------------------------------');
30
- log('info', `Session ${session} / ${maxSessions}`);
31
- console.log('--------------------------------------------');
24
+ function _progressBar(done, total, width = 24) {
25
+ if (total === 0) return `${COLOR.dim}[${''.repeat(width)}]${COLOR.reset}`;
26
+ const filled = Math.round(done / total * width);
27
+ return `${COLOR.green}[${'█'.repeat(filled)}${COLOR.dim}${''.repeat(width - filled)}${COLOR.reset}${COLOR.green}]${COLOR.reset}`;
32
28
  }
33
29
 
34
- function printProgress(taskData) {
30
+ function printSessionHeader(session, maxSessions, taskData, taskId) {
35
31
  const stats = getStats(taskData);
36
- log('info', `进度: ${stats.done}/${stats.total} done, ${stats.in_progress} in_progress, ${stats.testing} testing, ${stats.failed} failed, ${stats.pending} pending`);
32
+ const task = taskId ? getFeatures(taskData).find(f => f.id === taskId) : null;
33
+ const bar = _progressBar(stats.done, stats.total);
34
+
35
+ console.error('');
36
+ console.error(`${COLOR.cyan}┌─ Session ${session} / ${maxSessions} ${'─'.repeat(32)}┐${COLOR.reset}`);
37
+ if (task) {
38
+ console.error(`${COLOR.cyan}│${COLOR.reset} 任务: ${COLOR.bold}${task.id}${COLOR.reset} ${COLOR.dim}-${COLOR.reset} ${task.description || ''}`);
39
+ }
40
+ console.error(`${COLOR.cyan}│${COLOR.reset} 进度: ${bar} ${stats.done}/${stats.total} ${COLOR.green}✔${stats.done}${COLOR.reset} ${COLOR.yellow}○${stats.pending}${COLOR.reset} ${COLOR.red}✘${stats.failed}${COLOR.reset}`);
41
+ console.error(`${COLOR.cyan}└${'─'.repeat(46)}┘${COLOR.reset}`);
37
42
  }
38
43
 
39
44
  function printDryRun(taskData) {
@@ -51,16 +56,17 @@ function printDryRun(taskData) {
51
56
  for (const f of features) {
52
57
  const st = f.status || 'unknown';
53
58
  const icon = { done: '✓', in_progress: '▸', pending: '○', failed: '✗', testing: '◇' }[st] || '?';
54
- log('info', ` ${icon} [${st.padEnd(11)}] ${f.id} - ${f.description || ''}`);
59
+ const color = { done: COLOR.green, failed: COLOR.red, in_progress: COLOR.blue, testing: COLOR.yellow, pending: COLOR.dim }[st] || '';
60
+ log('info', ` ${color}${icon}${COLOR.reset} [${st.padEnd(11)}] ${f.id} - ${f.description || ''}`);
55
61
  }
56
62
  }
57
63
 
58
64
  function printEndBanner() {
59
- console.log('');
60
- console.log('============================================');
61
- console.log(' 运行结束');
62
- console.log('============================================');
63
- console.log('');
65
+ console.error('');
66
+ console.error(`${COLOR.cyan}╔══════════════════════════════════════════════╗${COLOR.reset}`);
67
+ console.error(`${COLOR.cyan}║${COLOR.reset} ${COLOR.bold}运行结束${COLOR.reset}`);
68
+ console.error(`${COLOR.cyan}╚══════════════════════════════════════════════╝${COLOR.reset}`);
69
+ console.error('');
64
70
  }
65
71
 
66
72
  async function promptContinue() {
@@ -94,74 +100,35 @@ function snapshot(projectRoot, taskData) {
94
100
 
95
101
  function _validateSessionResult() {
96
102
  if (!assets.exists('sessionResult')) {
97
- log('error', 'Agent 未生成 session_result.json');
98
103
  return { valid: false, reason: 'session_result.json 不存在' };
99
104
  }
100
-
101
105
  const raw = assets.readJson('sessionResult', null);
102
- if (raw === null) {
103
- log('warn', 'session_result.json 解析失败');
106
+ if (!raw) {
104
107
  return { valid: false, reason: 'JSON 解析失败', rawContent: assets.read('sessionResult') };
105
108
  }
106
-
107
109
  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
110
  if (!['success', 'failed'].includes(data.session_result)) {
118
111
  return { valid: false, reason: `无效 session_result: ${data.session_result}`, data };
119
112
  }
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})`);
113
+ log(data.session_result === 'success' ? 'ok' : 'warn', `session_result: ${data.session_result}`);
127
114
  return { valid: true, data };
128
115
  }
129
116
 
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
117
  async function validate(config, headBefore, taskId) {
160
118
  const projectRoot = assets.projectRoot;
161
- log('info', '========== 开始校验 ==========');
119
+ log('info', '校验中...');
162
120
 
163
121
  let srResult = _validateSessionResult();
164
- const gitResult = _checkGitProgress(headBefore, projectRoot);
122
+ const hasCommit = headBefore ? getGitHead(projectRoot) !== headBefore : false;
123
+
124
+ if (hasCommit) {
125
+ try {
126
+ const msg = execSync('git log --oneline -1', { cwd: projectRoot, encoding: 'utf8' }).trim();
127
+ log('ok', `检测到新提交: ${msg}`);
128
+ } catch { /* ignore */ }
129
+ } else if (headBefore) {
130
+ log('warn', '本次会话没有新的 git 提交');
131
+ }
165
132
 
166
133
  if (!srResult.valid && srResult.rawContent) {
167
134
  const srPath = assets.path('sessionResult');
@@ -173,35 +140,22 @@ async function validate(config, headBefore, taskId) {
173
140
  }
174
141
 
175
142
  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
- }
143
+ let reason = '';
144
+
145
+ if (srResult.valid && srResult.data.session_result === 'failed') {
146
+ fatal = true;
147
+ reason = 'AI 报告任务失败';
148
+ } else if (!srResult.valid && !hasCommit) {
149
+ fatal = true;
150
+ reason = srResult.reason || 'session_result.json 异常';
151
+ } else if (!srResult.valid && hasCommit) {
152
+ log('warn', 'session_result.json 异常,但有新提交,降级为警告');
193
153
  }
194
154
 
195
- if (fatal) {
196
- log('error', '========== 校验失败 (致命) ==========');
197
- } else if (hasWarnings) {
198
- log('warn', '========== 校验通过 (有警告) ==========');
199
- } else {
200
- log('ok', '========== 校验全部通过 ==========');
201
- }
155
+ log(fatal ? 'error' : (hasCommit ? 'ok' : 'warn'),
156
+ fatal ? `校验失败: ${reason}` : `校验通过${hasCommit ? '' : ' (无新提交)'}`);
202
157
 
203
- const reason = fatal ? (srResult.reason || '无新提交且 session_result.json 异常') : '';
204
- return { fatal, hasWarnings, sessionData: srResult.data, reason };
158
+ return { fatal, hasCommit, sessionData: srResult.data, reason };
205
159
  }
206
160
 
207
161
  // ─── Lifecycle: Rollback ──────────────────────────────────
@@ -260,12 +214,16 @@ function _markTaskFailed(taskId) {
260
214
  }
261
215
 
262
216
  async function _handleRetryOrSkip(session, {
263
- headBefore, taskId, sessionResult, consecutiveFailures, result, reason, lastFailMsg,
217
+ headBefore, taskId, sessionResult, consecutiveFailures, result, reason, lastFailMsg, skipRollback,
264
218
  }) {
265
219
  const newFailures = consecutiveFailures + 1;
266
220
  const exceeded = newFailures >= MAX_RETRY;
267
221
 
268
- await rollback(headBefore, reason);
222
+ if (skipRollback) {
223
+ log('info', '已有提交,跳过回滚');
224
+ } else {
225
+ await rollback(headBefore, reason);
226
+ }
269
227
 
270
228
  if (exceeded) {
271
229
  log('error', `连续失败 ${MAX_RETRY} 次,跳过当前任务`);
@@ -300,11 +258,12 @@ async function onSuccess(session, { taskId, sessionResult, validateResult }) {
300
258
 
301
259
  async function onFailure(session, { headBefore, taskId, sessionResult, validateResult, consecutiveFailures }) {
302
260
  const reason = validateResult.reason || '校验失败';
303
- log('error', `Session ${session} 校验失败 (连续失败: ${consecutiveFailures + 1}/${MAX_RETRY})`);
261
+ log('error', `Session ${session} 失败 (连续: ${consecutiveFailures + 1}/${MAX_RETRY})`);
304
262
  return _handleRetryOrSkip(session, {
305
263
  headBefore, taskId, sessionResult, consecutiveFailures,
306
264
  result: 'fatal', reason,
307
- lastFailMsg: `上次校验失败: ${reason},代码已回滚`,
265
+ lastFailMsg: `上次失败: ${reason}${validateResult.hasCommit ? '' : ',代码已回滚'}`,
266
+ skipRollback: validateResult.hasCommit,
308
267
  });
309
268
  }
310
269
 
@@ -314,7 +273,7 @@ async function onStall(session, { headBefore, taskId, sessionResult, consecutive
314
273
  const validateResult = await validate(config, headBefore, taskId);
315
274
 
316
275
  if (!validateResult.fatal) {
317
- log('ok', `停顿超时但任务已完成,按成功处理${validateResult.hasWarnings ? ' (有警告)' : ''}`);
276
+ log('ok', '停顿超时但任务已完成,按成功处理');
318
277
  return onSuccess(session, { taskId, sessionResult, validateResult });
319
278
  }
320
279
 
@@ -370,19 +329,15 @@ async function executeRun(config, opts = {}) {
370
329
  const dryRun = opts.dryRun || false;
371
330
  const maxSessions = opts.max || 50;
372
331
  const pauseEvery = opts.pause ?? 0;
373
-
374
- printBanner(dryRun);
332
+ printBanner(dryRun, config, maxSessions);
375
333
 
376
334
  printStats();
377
335
 
378
336
  log('info', `开始编码循环 (最多 ${maxSessions} 个会话) ...`);
379
- console.log('');
380
337
 
381
338
  let state = { consecutiveFailures: 0, lastFailReason: '' };
382
339
 
383
340
  for (let session = 1; session <= maxSessions; session++) {
384
- printSessionHeader(session, maxSessions);
385
-
386
341
  let taskData = loadTasks();
387
342
  if (!taskData) {
388
343
  const tasksPath = assets.path('tasks');
@@ -404,21 +359,37 @@ async function executeRun(config, opts = {}) {
404
359
  }
405
360
  tryPush(projectRoot);
406
361
  }
407
- console.log('');
362
+ console.error('');
408
363
  log('ok', '所有任务已完成!');
409
364
  printStats();
410
365
  break;
411
366
  }
412
367
 
413
- printProgress(taskData);
368
+ const { headBefore, taskId } = dryRun ? { headBefore: null, taskId: null } : snapshot(projectRoot, taskData);
369
+
370
+ if (!dryRun && taskId === 'unknown') {
371
+ log('warn', '无可执行任务(剩余任务均已失败或依赖未满足),退出');
372
+ break;
373
+ }
374
+
375
+ const selectedTask = getFeatures(taskData).find(f => f.id === taskId);
376
+ if (!dryRun && selectedTask?.status === 'failed') {
377
+ const hasActionable = getFeatures(taskData).some(f =>
378
+ f.status === 'pending' || f.status === 'in_progress' || f.status === 'testing'
379
+ );
380
+ if (!hasActionable) {
381
+ log('warn', '所有可执行任务已完成或失败,退出(剩余 failed 任务需人工排查)');
382
+ break;
383
+ }
384
+ }
385
+
386
+ printSessionHeader(session, maxSessions, taskData, taskId);
414
387
 
415
388
  if (dryRun) {
416
389
  printDryRun(taskData);
417
390
  break;
418
391
  }
419
392
 
420
- const { headBefore, taskId } = snapshot(projectRoot, taskData);
421
-
422
393
  const { executeCoding } = require('./coding');
423
394
  const sessionResult = await executeCoding(config, session, {
424
395
  projectRoot,
@@ -426,29 +397,25 @@ async function executeRun(config, opts = {}) {
426
397
  consecutiveFailures: state.consecutiveFailures,
427
398
  maxSessions,
428
399
  lastValidateLog: state.lastFailReason,
400
+ continue: true,
429
401
  });
430
402
 
431
403
  if (sessionResult.stalled) {
432
404
  state = await onStall(session, { headBefore, taskId, sessionResult, config, ...state });
433
405
  if (state.consecutiveFailures === 0) {
434
406
  if (shouldSimplify(config)) await tryRunSimplify(config);
435
- tryPush(projectRoot);
436
407
  }
437
408
  continue;
438
409
  }
439
410
 
440
- log('info', '开始 harness 校验 ...');
441
411
  const validateResult = await validate(config, headBefore, taskId);
442
412
 
443
413
  if (!validateResult.fatal) {
444
- const level = validateResult.hasWarnings ? 'warn' : 'ok';
445
- log(level, `Session ${session} 校验通过${validateResult.hasWarnings ? ' (有警告)' : ''}`);
446
414
  state = await onSuccess(session, { taskId, sessionResult, validateResult });
447
415
 
448
416
  if (shouldSimplify(config)) {
449
417
  await tryRunSimplify(config);
450
418
  }
451
- tryPush(projectRoot);
452
419
  } else {
453
420
  state = await onFailure(session, { headBefore, taskId, sessionResult, validateResult, ...state });
454
421
  }
package/src/core/scan.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const { log } = require('../common/config');
5
+ const { log, printModeBanner } = require('../common/config');
6
6
  const { assets } = require('../common/assets');
7
7
  const { buildSystemPrompt, buildScanPrompt } = require('./prompts');
8
8
  const { Session } = require('./session');
@@ -47,10 +47,11 @@ function validateProfile() {
47
47
  async function executeScan(config, opts = {}) {
48
48
  const maxAttempts = RETRY.SCAN_ATTEMPTS;
49
49
 
50
+ const projectType = hasCodeFiles(opts.projectRoot || assets.projectRoot) ? 'existing' : 'new';
51
+ printModeBanner('scan', projectType === 'existing' ? '已有项目' : '全新项目', config?.model);
52
+
50
53
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
51
54
  log('info', `初始化尝试 ${attempt} / ${maxAttempts} ...`);
52
-
53
- const projectType = hasCodeFiles(opts.projectRoot || assets.projectRoot) ? 'existing' : 'new';
54
55
  const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, '');
55
56
 
56
57
  const result = await Session.run('scan', config, {
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const { buildEnvVars, log } = require('../common/config');
5
+ const { buildEnvVars, log, COLOR } = require('../common/config');
6
6
  const { Indicator } = require('../common/indicator');
7
7
  const { logMessage: baseLogMessage, extractResult, writeSessionSeparator } = require('../common/logging');
8
8
  const { createHooks } = require('./hooks');
@@ -12,7 +12,7 @@ const { assets } = require('../common/assets');
12
12
  * @typedef {Object} SessionRunOptions
13
13
  * @property {string} logFileName - 日志文件名
14
14
  * @property {import('fs').WriteStream} [logStream] - 外部日志流(与 logFileName 二选一)
15
- * @property {number} [sessionNum=0] - 会话编号
15
+ * @property {number} [sessionNum=1] - 会话编号
16
16
  * @property {string} [label=''] - 会话标签
17
17
  * @property {(session: Session) => Promise<Object>} execute - 执行回调,接收 session 实例
18
18
  */
@@ -72,7 +72,7 @@ class Session {
72
72
  * @param {SessionRunOptions} options - 运行选项
73
73
  * @returns {Promise<Object>} 包含 exitCode、logFile、stalled 以及 execute 返回值
74
74
  */
75
- static async run(type, config, { logFileName, logStream, sessionNum = 0, label = '', execute }) {
75
+ static async run(type, config, { logFileName, logStream, sessionNum = 1, label = '', execute }) {
76
76
  await Session.ensureSDK(config);
77
77
  const session = new Session(type, config, { logFileName, logStream, sessionNum, label });
78
78
  try {
@@ -99,7 +99,7 @@ class Session {
99
99
  * @param {number} [options.sessionNum=0]
100
100
  * @param {string} [options.label='']
101
101
  */
102
- constructor(type, config, { logFileName, logStream, sessionNum = 0, label = '' }) {
102
+ constructor(type, config, { logFileName, logStream, sessionNum = 1, label = '' }) {
103
103
  this.config = config;
104
104
  this.type = type;
105
105
  this.indicator = new Indicator();
@@ -148,7 +148,7 @@ class Session {
148
148
  * 执行一次 SDK 查询,遍历消息流并收集结果
149
149
  * @param {string} prompt - 用户提示
150
150
  * @param {Object} queryOpts - SDK query 选项(通常来自 buildQueryOptions)
151
- * @param {RunQueryOpts} [opts={}] - 额外选项(onMessage 回调)
151
+ * @param {RunQueryOpts} [opts={}] - 额外选项(onMessage 回调, continue, resume)
152
152
  * @returns {Promise<QueryResult>}
153
153
  */
154
154
  async runQuery(prompt, queryOpts, opts = {}) {
@@ -162,7 +162,10 @@ class Session {
162
162
 
163
163
  const sdk = Session._sdk;
164
164
  const messages = [];
165
- const querySession = sdk.query({ prompt, options: queryOpts });
165
+ const queryPayload = { prompt, options: queryOpts };
166
+ if (opts.continue) queryPayload.options.continue = true;
167
+ if (opts.resume) queryPayload.options.resume = opts.resume;
168
+ const querySession = sdk.query(queryPayload);
166
169
 
167
170
  try {
168
171
  for await (const message of querySession) {
@@ -191,21 +194,32 @@ class Session {
191
194
  const cost = sdkResult?.total_cost_usd || null;
192
195
  const usage = sdkResult?.usage || null;
193
196
  const turns = sdkResult?.num_turns || null;
197
+ const sessionId = sdkResult?.session_id || null;
194
198
 
195
- if (cost != null || turns != null) {
196
- const parts = [];
197
- if (turns != null) parts.push(`turns: ${turns}`);
198
- if (cost != null) parts.push(`cost: $${cost}`);
199
+ if (cost != null || turns != null || sessionId) {
200
+ const fmtTokens = (n) => n >= 10000 ? `${(n / 1000).toFixed(1)}k` : String(n);
201
+ const fmtCost = (c) => c >= 1 ? `$${c.toFixed(2)}` : `$${c.toFixed(4)}`;
202
+
203
+ const cliParts = [];
204
+ if (sessionId) cliParts.push(`${COLOR.dim}sid:${COLOR.reset} ${sessionId.slice(0, 8)}`);
205
+ if (turns != null) cliParts.push(`${COLOR.dim}turns:${COLOR.reset} ${turns}`);
206
+ if (cost != null) cliParts.push(`${COLOR.dim}cost:${COLOR.reset} ${COLOR.yellow}${fmtCost(cost)}${COLOR.reset}`);
199
207
  if (usage) {
200
208
  const inp = usage.input_tokens || 0;
201
209
  const out = usage.output_tokens || 0;
202
- parts.push(`tokens: ${inp}+${out}`);
210
+ cliParts.push(`${COLOR.dim}tokens:${COLOR.reset} ${fmtTokens(inp)}+${fmtTokens(out)}`);
203
211
  }
204
- const summary = parts.join(', ');
205
- console.log('----- SESSION END -----');
206
- log('info', `session 统计: ${summary}`);
212
+ console.error(`${COLOR.dim}─── session end ───${COLOR.reset}`);
213
+ log('info', `session 统计: ${cliParts.join(' ')}`);
214
+
215
+ // 日志文件保留完整精度
216
+ const logParts = [];
217
+ if (sessionId) logParts.push(`sid: ${sessionId}`);
218
+ if (turns != null) logParts.push(`turns: ${turns}`);
219
+ if (cost != null) logParts.push(`cost: $${cost}`);
220
+ if (usage) logParts.push(`tokens: ${usage.input_tokens || 0}+${usage.output_tokens || 0}`);
207
221
  if (this.logStream?.writable) {
208
- this.logStream.write(`[SESSION_INFO] ${summary}\n`);
222
+ this.logStream.write(`[SESSION_INFO] ${logParts.join(', ')}\n`);
209
223
  }
210
224
  }
211
225
 
@@ -213,7 +227,7 @@ class Session {
213
227
  messages,
214
228
  success: sdkResult?.subtype === 'success',
215
229
  subtype: sdkResult?.subtype || null,
216
- cost, usage, turns,
230
+ cost, usage, turns, sessionId,
217
231
  };
218
232
  }
219
233
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { log } = require('../common/config');
3
+ const { log, printModeBanner } = require('../common/config');
4
4
  const { assets } = require('../common/assets');
5
5
  const { Session } = require('./session');
6
6
  const { execSync } = require('child_process');
@@ -33,6 +33,8 @@ async function executeSimplify(config, focus = null, opts = {}) {
33
33
 
34
34
  const { range, label } = getSmartDiffRange(projectRoot, n);
35
35
 
36
+ printModeBanner('simplify', label, config?.model);
37
+
36
38
  let diff = '';
37
39
  try {
38
40
  diff = execSync(`git diff ${range}`, { cwd: projectRoot, encoding: 'utf8', maxBuffer: 50 * 1024 * 1024 });
@@ -59,7 +61,7 @@ async function executeSimplify(config, focus = null, opts = {}) {
59
61
  const queryOpts = session.buildQueryOptions(opts);
60
62
  queryOpts.disallowedTools = ['askUserQuestion'];
61
63
 
62
- await session.runQuery(prompt, queryOpts);
64
+ await session.runQuery(prompt, queryOpts, { continue: true });
63
65
  log('ok', '代码审查完成');
64
66
 
65
67
  return {};
package/src/core/state.js CHANGED
@@ -44,10 +44,7 @@ function syncAfterPlan() {
44
44
 
45
45
  function selectNextTask(taskData) {
46
46
  const features = getFeatures(taskData);
47
-
48
- const failed = features.filter(f => f.status === 'failed')
49
- .sort((a, b) => (a.priority || 999) - (b.priority || 999));
50
- if (failed.length > 0) return failed[0];
47
+ const byPriority = (a, b) => (a.priority || 999) - (b.priority || 999);
51
48
 
52
49
  const pending = features.filter(f => f.status === 'pending')
53
50
  .filter(f => {
@@ -57,12 +54,15 @@ function selectNextTask(taskData) {
57
54
  return dep && dep.status === 'done';
58
55
  });
59
56
  })
60
- .sort((a, b) => (a.priority || 999) - (b.priority || 999));
57
+ .sort(byPriority);
61
58
  if (pending.length > 0) return pending[0];
62
59
 
63
- const inProgress = features.filter(f => f.status === 'in_progress')
64
- .sort((a, b) => (a.priority || 999) - (b.priority || 999));
65
- return inProgress[0] || null;
60
+ const active = features.filter(f => f.status === 'in_progress' || f.status === 'testing')
61
+ .sort(byPriority);
62
+ if (active.length > 0) return active[0];
63
+
64
+ const failed = features.filter(f => f.status === 'failed').sort(byPriority);
65
+ return failed[0] || null;
66
66
  }
67
67
 
68
68
  function isAllDone(taskData) {
@@ -91,6 +91,19 @@ function markSimplifyDone() {
91
91
  saveState(state);
92
92
  }
93
93
 
94
+ // ─── Design State Helpers ─────────────────────────────────
95
+
96
+ function loadDesignState() {
97
+ const state = loadState();
98
+ return state.design || {};
99
+ }
100
+
101
+ function saveDesignState(designData) {
102
+ const state = loadState();
103
+ state.design = { ...state.design, ...designData };
104
+ saveState(state);
105
+ }
106
+
94
107
  module.exports = {
95
108
  DEFAULT_STATE,
96
109
  loadState,
@@ -101,5 +114,7 @@ module.exports = {
101
114
  appendProgress,
102
115
  incrementSession,
103
116
  markSimplifyDone,
117
+ loadDesignState,
118
+ saveDesignState,
104
119
  TASK_STATUSES,
105
120
  };
package/src/index.js CHANGED
@@ -68,6 +68,10 @@ async function main(command, input, opts = {}) {
68
68
  const { executeGo } = require('./core/go');
69
69
  return executeGo(config, input, opts);
70
70
  }
71
+ case 'design': {
72
+ const { executeDesign } = require('./core/design');
73
+ return executeDesign(config, input, opts);
74
+ }
71
75
  default:
72
76
  throw new Error(`未知命令: ${command}`);
73
77
  }
@@ -10,6 +10,7 @@ Session {{sessionNum}}。任务已分配,按 3 步流程执行。
10
10
  {{testEnvHint}}
11
11
  {{mcpHint}}
12
12
  {{webTestHint}}
13
+ {{designHint}}
13
14
  {{retryContext}}
14
15
 
15
16
  ## 收尾