@jojonax/codex-copilot 1.0.1 → 1.0.3

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.
@@ -1,7 +1,7 @@
1
1
  /**
2
- * codex-copilot run - 主编排循环
2
+ * codex-copilot run - Main orchestration loop
3
3
  *
4
- * 逐个任务执行: 开发 → PR → Review → 修复合并下一个
4
+ * Executes tasks one by one: Develop → PR → Review → FixMergeNext
5
5
  */
6
6
 
7
7
  import { readFileSync, writeFileSync, existsSync } from 'fs';
@@ -25,63 +25,80 @@ export async function run(projectDir) {
25
25
  const statePath = resolve(projectDir, '.codex-copilot/state.json');
26
26
  const configPath = resolve(projectDir, '.codex-copilot/config.json');
27
27
 
28
- const tasks = readJSON(tasksPath);
29
- let state = readJSON(statePath);
28
+ let tasks;
29
+ let state;
30
+ try {
31
+ tasks = readJSON(tasksPath);
32
+ state = readJSON(statePath);
33
+ } catch (err) {
34
+ log.error(`Failed to read task/state files: ${err.message}`);
35
+ log.warn('Files may be corrupted. Run: codex-copilot reset');
36
+ closePrompt();
37
+ process.exit(1);
38
+ }
30
39
  const config = existsSync(configPath) ? readJSON(configPath) : {};
31
40
 
41
+ // Validate tasks.json structure
42
+ if (!tasks.tasks || !Array.isArray(tasks.tasks) || !tasks.total) {
43
+ log.error('Invalid tasks.json format: missing tasks array or total field');
44
+ log.warn('Please re-run codex-copilot init and let CodeX regenerate tasks.json');
45
+ closePrompt();
46
+ process.exit(1);
47
+ }
48
+
32
49
  const baseBranch = config.base_branch || 'main';
33
50
  const maxReviewRounds = config.max_review_rounds || 2;
34
51
  const pollInterval = config.review_poll_interval || 60;
35
52
  const waitTimeout = config.review_wait_timeout || 600;
36
53
 
37
- log.title('🚀 开始自动化开发循环');
54
+ log.title('🚀 Starting automated development loop');
38
55
  log.blank();
39
- log.info(`项目: ${tasks.project}`);
40
- log.info(`总任务数: ${tasks.total}`);
41
- log.info(`已完成: ${state.current_task}`);
42
- log.info(`基础分支: ${baseBranch}`);
56
+ log.info(`Project: ${tasks.project}`);
57
+ log.info(`Total tasks: ${tasks.total}`);
58
+ log.info(`Completed: ${state.current_task}`);
59
+ log.info(`Base branch: ${baseBranch}`);
43
60
  log.blank();
44
- progressBar(state.current_task, tasks.total, `${state.current_task}/${tasks.total} 任务完成`);
61
+ progressBar(state.current_task, tasks.total, `${state.current_task}/${tasks.total} tasks done`);
45
62
  log.blank();
46
63
 
47
- // 逐个任务执行
64
+ // Execute tasks one by one
48
65
  for (const task of tasks.tasks) {
49
- if (task.id <= state.current_task) continue; // 跳过已完成的
66
+ if (task.id <= state.current_task) continue; // Skip completed
50
67
  if (task.status === 'completed' || task.status === 'skipped') continue;
51
68
 
52
69
  log.blank();
53
- log.title(`━━━ 任务 ${task.id}/${tasks.total}: ${task.title} ━━━`);
70
+ log.title(`━━━ Task ${task.id}/${tasks.total}: ${task.title} ━━━`);
54
71
  log.blank();
55
72
 
56
- // 检查依赖是否已完成
73
+ // Check dependencies
57
74
  if (task.depends_on && task.depends_on.length > 0) {
58
75
  const unfinished = task.depends_on.filter(dep => {
59
76
  const depTask = tasks.tasks.find(t => t.id === dep);
60
77
  return depTask && depTask.status !== 'completed';
61
78
  });
62
79
  if (unfinished.length > 0) {
63
- log.warn(`依赖任务未完成: ${unfinished.join(', ')},跳过`);
80
+ log.warn(`Unfinished dependencies: ${unfinished.join(', ')} — skipping`);
64
81
  continue;
65
82
  }
66
83
  }
67
84
 
68
- // ===== 阶段 1: 开发 =====
85
+ // ===== Phase 1: Develop =====
69
86
  await developPhase(projectDir, task, baseBranch);
70
87
 
71
- // ===== 阶段 2: 创建 PR =====
88
+ // ===== Phase 2: Create PR =====
72
89
  const prInfo = await prPhase(projectDir, task, baseBranch);
73
90
 
74
- // ===== 阶段 3: Review 循环 =====
91
+ // ===== Phase 3: Review loop =====
75
92
  await reviewLoop(projectDir, task, prInfo, {
76
93
  maxRounds: maxReviewRounds,
77
94
  pollInterval,
78
95
  waitTimeout,
79
96
  });
80
97
 
81
- // ===== 阶段 4: 合并 =====
82
- await mergePhase(projectDir, task, prInfo);
98
+ // ===== Phase 4: Merge =====
99
+ await mergePhase(projectDir, task, prInfo, baseBranch);
83
100
 
84
- // 更新任务状态
101
+ // Update task state
85
102
  task.status = 'completed';
86
103
  state.current_task = task.id;
87
104
  state.current_pr = null;
@@ -90,91 +107,94 @@ export async function run(projectDir) {
90
107
  writeJSON(statePath, state);
91
108
 
92
109
  log.blank();
93
- progressBar(task.id, tasks.total, `${task.id}/${tasks.total} 任务完成`);
94
- log.info(`✅ 任务 #${task.id} 完成!`);
110
+ progressBar(task.id, tasks.total, `${task.id}/${tasks.total} tasks done`);
111
+ log.info(`✅ Task #${task.id} complete!`);
95
112
  }
96
113
 
97
114
  log.blank();
98
- log.title('🎉 所有任务已完成!');
115
+ log.title('🎉 All tasks complete!');
99
116
  log.blank();
100
117
  closePrompt();
101
118
  }
102
119
 
103
120
  // ──────────────────────────────────────────────
104
- // 阶段 1: 开发
121
+ // Phase 1: Develop
105
122
  // ──────────────────────────────────────────────
106
123
  async function developPhase(projectDir, task, baseBranch) {
107
- log.step('阶段 1/4: 开发');
124
+ log.step('Phase 1/4: Develop');
108
125
 
109
- // 切换到 feature 分支
126
+ // Switch to feature branch
110
127
  git.checkoutBranch(projectDir, task.branch, baseBranch);
111
- log.info(`已切换到分支: ${task.branch}`);
128
+ log.info(`Switched to branch: ${task.branch}`);
112
129
 
113
- // 构建开发 Prompt
130
+ // Build development prompt
114
131
  const devPrompt = buildDevPrompt(task);
115
132
 
116
- // 检查是否有 CodeX CLI
133
+ // Check if CodeX CLI is available
117
134
  const codexAvailable = isCodexAvailable();
118
135
 
119
136
  if (codexAvailable) {
120
- log.info('检测到 CodeX CLI,自动执行...');
121
- const autoRun = await confirm('自动调用 CodeX 开发?');
137
+ log.info('CodeX CLI detected, ready for auto-execution...');
138
+ const autoRun = await confirm('Auto-invoke CodeX for development?');
122
139
  if (autoRun) {
123
140
  try {
124
141
  const promptPath = resolve(projectDir, '.codex-copilot/_current_prompt.md');
125
142
  writeFileSync(promptPath, devPrompt);
126
- execSync(`codex -q --file .codex-copilot/_current_prompt.md`, {
143
+ execSync(`cat .codex-copilot/_current_prompt.md | codex exec --full-auto -`, {
127
144
  cwd: projectDir,
128
145
  stdio: 'inherit',
129
146
  });
130
- log.info('CodeX 开发完成');
147
+ log.info('CodeX development complete');
131
148
  return;
132
149
  } catch (err) {
133
- log.warn(`CodeX CLI 调用失败: ${err.message}`);
134
- log.warn('回退到手动模式');
150
+ log.warn(`CodeX CLI invocation failed: ${err.message}`);
151
+ log.warn('Falling back to manual mode');
135
152
  }
136
153
  }
137
154
  }
138
155
 
139
- // 手动模式:显示 Prompt,等待用户确认
156
+ // Manual mode: display prompt, wait for user confirmation
140
157
  log.blank();
141
- console.log(' ┌─── 请将以下内容粘贴到 CodeX 桌面版中执行 ───┐');
158
+ console.log(' ┌─── Paste the following into CodeX Desktop to execute ───┐');
142
159
  log.blank();
143
160
  console.log(devPrompt.split('\n').map(l => ` │ ${l}`).join('\n'));
144
161
  log.blank();
145
- console.log(' └──────────────────────────────────────────────┘');
162
+ console.log(' └─────────────────────────────────────────────────────────┘');
146
163
  log.blank();
147
164
 
148
- // 同时将 Prompt 保存到文件,方便复制
165
+ // Also save prompt to file for easy copying
149
166
  const promptPath = resolve(projectDir, '.codex-copilot/_current_prompt.md');
150
167
  writeFileSync(promptPath, devPrompt);
151
- log.dim(`Prompt 已保存到 .codex-copilot/_current_prompt.md (可直接复制文件内容)`);
168
+ log.dim('Prompt saved to .codex-copilot/_current_prompt.md (you can copy the file directly)');
152
169
  log.blank();
153
170
 
154
- // 尝试复制到剪贴板(通过 stdin 传入,避免 shell 注入)
171
+ // Try to copy to clipboard (via stdin to avoid shell injection)
155
172
  copyToClipboard(devPrompt);
156
173
 
157
- await ask('CodeX 开发完成后按 Enter 继续...');
174
+ await ask('Press Enter after CodeX development is complete...');
158
175
  }
159
176
 
160
177
  // ──────────────────────────────────────────────
161
- // 阶段 2: 创建 PR
178
+ // Phase 2: Create PR
162
179
  // ──────────────────────────────────────────────
163
180
  async function prPhase(projectDir, task, baseBranch) {
164
- log.step('阶段 2/4: 提交 PR');
181
+ log.step('Phase 2/4: Submit PR');
165
182
 
166
- // 确保变更已提交
183
+ // Ensure changes are committed
167
184
  if (!git.isClean(projectDir)) {
168
- log.info('检测到未提交的变更,自动提交...');
185
+ log.info('Uncommitted changes detected, auto-committing...');
169
186
  git.commitAll(projectDir, `feat(task-${task.id}): ${task.title}`);
170
187
  }
171
188
 
172
- // 推送
189
+ // Push
173
190
  git.pushBranch(projectDir, task.branch);
174
- log.info('代码已推送');
191
+ log.info('Code pushed');
175
192
 
176
- // 创建 PR
177
- const prBody = `## 任务 #${task.id}: ${task.title}\n\n${task.description}\n\n### 验收标准\n${task.acceptance.map(a => `- [ ] ${a}`).join('\n')}\n\n---\n*由 Codex-Copilot 自动创建*`;
193
+ // Create PR
194
+ const acceptanceList = Array.isArray(task.acceptance) && task.acceptance.length > 0
195
+ ? task.acceptance.map(a => `- [ ] ${a}`).join('\n')
196
+ : '- [ ] Feature works correctly';
197
+ const prBody = `## Task #${task.id}: ${task.title}\n\n${task.description}\n\n### Acceptance Criteria\n${acceptanceList}\n\n---\n*Auto-created by Codex-Copilot*`;
178
198
 
179
199
  try {
180
200
  const prInfo = github.createPR(projectDir, {
@@ -183,31 +203,36 @@ async function prPhase(projectDir, task, baseBranch) {
183
203
  base: baseBranch,
184
204
  head: task.branch,
185
205
  });
186
- log.info(`PR 已创建: ${prInfo.url}`);
206
+ log.info(`PR created: ${prInfo.url}`);
187
207
  return prInfo;
188
208
  } catch (err) {
189
- // PR 可能已存在
190
- log.warn(`创建 PR 异常: ${err.message}`);
191
- const prNumber = await ask('请输入已存在的 PR 编号:');
192
- return { number: parseInt(prNumber), url: '' };
209
+ // PR may already exist
210
+ log.warn(`PR creation error: ${err.message}`);
211
+ const prNumber = await ask('Enter existing PR number:');
212
+ const parsed = parseInt(prNumber, 10);
213
+ if (isNaN(parsed) || parsed <= 0) {
214
+ log.error('Invalid PR number');
215
+ throw new Error('Invalid PR number');
216
+ }
217
+ return { number: parsed, url: '' };
193
218
  }
194
219
  }
195
220
 
196
221
  // ──────────────────────────────────────────────
197
- // 阶段 3: Review 循环
222
+ // Phase 3: Review loop
198
223
  // ──────────────────────────────────────────────
199
224
  async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pollInterval, waitTimeout }) {
200
225
  let maxRounds = _maxRounds;
201
- log.step('阶段 3/4: 等待 Review');
226
+ log.step('Phase 3/4: Waiting for review');
202
227
 
203
228
  for (let round = 1; round <= maxRounds; round++) {
204
- // 等待 Review
205
- log.info(`等待 Review 意见... (超时: ${waitTimeout}s)`);
229
+ // Wait for review
230
+ log.info(`Waiting for review feedback... (timeout: ${waitTimeout}s)`);
206
231
  const gotReview = await waitForReview(projectDir, prInfo.number, pollInterval, waitTimeout);
207
232
 
208
233
  if (!gotReview) {
209
- log.warn('等待 Review 超时');
210
- const action = await ask('输入 skip (跳过Review) wait (继续等待):');
234
+ log.warn('Review wait timed out');
235
+ const action = await ask('Enter "skip" (skip review) or "wait" (keep waiting):');
211
236
  if (action === 'wait') {
212
237
  round--;
213
238
  continue;
@@ -215,28 +240,28 @@ async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pol
215
240
  break;
216
241
  }
217
242
 
218
- // 检查 Review 状态
243
+ // Check review status
219
244
  const state = github.getLatestReviewState(projectDir, prInfo.number);
220
- log.info(`Review 状态: ${state}`);
245
+ log.info(`Review status: ${state}`);
221
246
 
222
247
  if (state === 'APPROVED') {
223
- log.info('✅ Review 已通过!');
248
+ log.info('✅ Review approved!');
224
249
  return;
225
250
  }
226
251
 
227
- // 收集 Review 反馈
252
+ // Collect review feedback
228
253
  const feedback = github.collectReviewFeedback(projectDir, prInfo.number);
229
254
  if (!feedback) {
230
- log.info('未发现具体修改意见,继续');
255
+ log.info('No specific change requests found, proceeding');
231
256
  return;
232
257
  }
233
258
 
234
259
  log.blank();
235
- log.warn(`收到 Review 意见 ( ${round}/${maxRounds})`);
260
+ log.warn(`Received review feedback (round ${round}/${maxRounds})`);
236
261
 
237
262
  if (round >= maxRounds) {
238
- log.warn(`已达最大修复轮次 (${maxRounds})`);
239
- const choice = await ask('输入 merge (强制合并) / fix (再修一轮) / skip (跳过):');
263
+ log.warn(`Max fix rounds reached (${maxRounds})`);
264
+ const choice = await ask('Enter "merge" (force merge) / "fix" (one more round) / "skip" (skip):');
240
265
  if (choice === 'fix') {
241
266
  maxRounds++;
242
267
  } else if (choice === 'skip') {
@@ -246,17 +271,17 @@ async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pol
246
271
  }
247
272
  }
248
273
 
249
- // CodeX 修复
274
+ // Let CodeX fix
250
275
  await fixPhase(projectDir, task, feedback, round);
251
276
 
252
- // 推送修复
277
+ // Push fix
253
278
  if (!git.isClean(projectDir)) {
254
279
  git.commitAll(projectDir, `fix(task-${task.id}): address review comments (round ${round})`);
255
280
  }
256
281
  git.pushBranch(projectDir, task.branch);
257
- log.info('修复已推送,等待新一轮 Review...');
282
+ log.info('Fix pushed, waiting for next review round...');
258
283
 
259
- // 稍等一下让 Review bot 反应
284
+ // Brief wait for review bot to react
260
285
  await sleep(10000);
261
286
  }
262
287
  }
@@ -272,11 +297,11 @@ async function waitForReview(projectDir, prNumber, pollInterval, timeout) {
272
297
  const currentReviews = github.getReviews(projectDir, prNumber);
273
298
  const currentComments = github.getIssueComments(projectDir, prNumber);
274
299
 
275
- // 检查是否有新的 Review Bot 评论
300
+ // Check for new reviews or bot comments
276
301
  const hasNewReview = currentReviews.length > startReviewCount;
277
302
  const hasBotComment = currentComments.some(c =>
278
303
  (c.user?.type === 'Bot' || c.user?.login?.includes('bot')) &&
279
- new Date(c.created_at) > new Date(Date.now() - elapsed * 1000)
304
+ new Date(c.created_at).getTime() > (Date.now() - elapsed * 1000)
280
305
  );
281
306
 
282
307
  if (hasNewReview || hasBotComment) {
@@ -290,97 +315,98 @@ async function waitForReview(projectDir, prNumber, pollInterval, timeout) {
290
315
  }
291
316
 
292
317
  // ──────────────────────────────────────────────
293
- // 修复阶段
318
+ // Fix phase
294
319
  // ──────────────────────────────────────────────
295
320
  async function fixPhase(projectDir, task, feedback, round) {
296
- log.step(`修复 Review 意见 ( ${round})`);
321
+ log.step(`Fixing review comments (round ${round})`);
297
322
 
298
- const fixPrompt = `以下是 PR Review 的意见,请逐一修复:
323
+ const fixPrompt = `The following are PR review comments. Please fix each one:
299
324
 
300
- ## Review 意见
325
+ ## Review Comments
301
326
  ${feedback}
302
327
 
303
- ## 要求
304
- 1. 逐条修复上述问题
305
- 2. 建议性意见可以不修复,但在 commit message 中说明原因
306
- 3. 修复后确保不引入新问题
307
- 4. 完成后执行: git add -A && git commit -m "fix(task-${task.id}): address review round ${round}"
328
+ ## Requirements
329
+ 1. Fix each issue listed above
330
+ 2. Suggestions (non-blocking) can be skipped — explain why in the commit message
331
+ 3. Ensure fixes don't introduce new issues
332
+ 4. When done, run: git add -A && git commit -m "fix(task-${task.id}): address review round ${round}"
308
333
  `;
309
334
 
310
- // 保存到文件并提示用户
335
+ // Save to file and prompt user
311
336
  const promptPath = resolve(projectDir, '.codex-copilot/_current_prompt.md');
312
337
  writeFileSync(promptPath, fixPrompt);
313
338
 
314
- // 尝试 CodeX CLI
339
+ // Try CodeX CLI
315
340
  const codexAvailable = isCodexAvailable();
316
341
 
317
342
  if (codexAvailable) {
318
- const autoFix = await confirm('自动调用 CodeX 修复?');
343
+ const autoFix = await confirm('Auto-invoke CodeX to fix?');
319
344
  if (autoFix) {
320
345
  try {
321
- execSync(`codex -q --file .codex-copilot/_current_prompt.md`, {
346
+ execSync(`cat .codex-copilot/_current_prompt.md | codex exec --full-auto -`, {
322
347
  cwd: projectDir,
323
348
  stdio: 'inherit',
324
349
  });
325
- log.info('CodeX 修复完成');
350
+ log.info('CodeX fix complete');
326
351
  return;
327
352
  } catch {
328
- log.warn('CodeX CLI 调用失败,回退到手动模式');
353
+ log.warn('CodeX CLI invocation failed, falling back to manual mode');
329
354
  }
330
355
  }
331
356
  }
332
357
 
333
- // 手动模式
358
+ // Manual mode
334
359
  log.blank();
335
- log.dim(`Review 修复 Prompt 已保存到 .codex-copilot/_current_prompt.md`);
336
- log.dim('请将文件内容粘贴到 CodeX 执行');
360
+ log.dim('Review fix prompt saved to .codex-copilot/_current_prompt.md');
361
+ log.dim('Paste the file content into CodeX to execute');
337
362
 
338
363
  copyToClipboard(fixPrompt);
339
364
 
340
- await ask('CodeX 修复完成后按 Enter 继续...');
365
+ await ask('Press Enter after CodeX fix is complete...');
341
366
  }
342
367
 
343
368
  // ──────────────────────────────────────────────
344
- // 阶段 4: 合并
369
+ // Phase 4: Merge
345
370
  // ──────────────────────────────────────────────
346
- async function mergePhase(projectDir, task, prInfo) {
347
- log.step('阶段 4/4: 合并 PR');
371
+ async function mergePhase(projectDir, task, prInfo, baseBranch) {
372
+ log.step('Phase 4/4: Merge PR');
348
373
 
349
- const doMerge = await confirm(`合并 PR #${prInfo.number}?`);
374
+ const doMerge = await confirm(`Merge PR #${prInfo.number}?`);
350
375
  if (!doMerge) {
351
- log.warn('用户跳过合并');
376
+ log.warn('User skipped merge');
352
377
  return;
353
378
  }
354
379
 
355
380
  try {
356
381
  github.mergePR(projectDir, prInfo.number);
357
- log.info(`PR #${prInfo.number} 已合并 ✅`);
382
+ log.info(`PR #${prInfo.number} merged ✅`);
358
383
  } catch (err) {
359
- log.error(`合并失败: ${err.message}`);
360
- log.warn('请手动合并后按 Enter 继续');
361
- await ask('继续...');
384
+ log.error(`Merge failed: ${err.message}`);
385
+ log.warn('Please merge manually, then press Enter to continue');
386
+ await ask('Continue...');
362
387
  }
363
388
 
364
- // 切回主分支
365
- const configPath = resolve(projectDir, '.codex-copilot/config.json');
366
- const config = JSON.parse(readFileSync(configPath, 'utf-8'));
367
- git.checkoutMain(projectDir, config.base_branch || 'main');
389
+ // Switch back to main branch
390
+ git.checkoutMain(projectDir, baseBranch);
368
391
  }
369
392
 
370
393
  function buildDevPrompt(task) {
371
- return `请完成以下开发任务:
394
+ const acceptanceList = Array.isArray(task.acceptance) && task.acceptance.length > 0
395
+ ? task.acceptance.map(a => `- ${a}`).join('\n')
396
+ : '- Feature works correctly';
397
+ return `Please complete the following development task:
372
398
 
373
- ## 任务 #${task.id}: ${task.title}
399
+ ## Task #${task.id}: ${task.title}
374
400
 
375
401
  ${task.description}
376
402
 
377
- ## 验收标准
378
- ${task.acceptance.map(a => `- ${a}`).join('\n')}
403
+ ## Acceptance Criteria
404
+ ${acceptanceList}
379
405
 
380
- ## 要求
381
- 1. 严格按照项目现有的代码规范和技术栈
382
- 2. 完成后确保代码可以正常编译/运行
383
- 3. 完成后执行:
406
+ ## Requirements
407
+ 1. Strictly follow the project's existing code style and tech stack
408
+ 2. Ensure the code compiles/runs correctly when done
409
+ 3. When done, run:
384
410
  git add -A
385
411
  git commit -m "feat(task-${task.id}): ${task.title}"
386
412
  `;
@@ -392,7 +418,8 @@ function sleep(ms) {
392
418
 
393
419
  function isCodexAvailable() {
394
420
  try {
395
- execSync('which codex', { stdio: 'pipe' });
421
+ const cmd = process.platform === 'win32' ? 'where codex' : 'which codex';
422
+ execSync(cmd, { stdio: 'pipe' });
396
423
  return true;
397
424
  } catch {
398
425
  return false;
@@ -402,6 +429,6 @@ function isCodexAvailable() {
402
429
  function copyToClipboard(text) {
403
430
  try {
404
431
  execSync('pbcopy', { input: text, stdio: ['pipe', 'pipe', 'pipe'] });
405
- log.info('📋 已复制到剪贴板');
432
+ log.info('📋 Copied to clipboard');
406
433
  } catch {}
407
434
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * codex-copilot status - 显示当前进度
2
+ * codex-copilot status - Show current progress
3
3
  */
4
4
 
5
5
  import { readFileSync } from 'fs';
@@ -10,36 +10,43 @@ export async function status(projectDir) {
10
10
  const tasksPath = resolve(projectDir, '.codex-copilot/tasks.json');
11
11
  const statePath = resolve(projectDir, '.codex-copilot/state.json');
12
12
 
13
- const tasks = JSON.parse(readFileSync(tasksPath, 'utf-8'));
14
- const state = JSON.parse(readFileSync(statePath, 'utf-8'));
13
+ let tasks, state;
14
+ try {
15
+ tasks = JSON.parse(readFileSync(tasksPath, 'utf-8'));
16
+ state = JSON.parse(readFileSync(statePath, 'utf-8'));
17
+ } catch (err) {
18
+ log.error(`Failed to read files: ${err.message}`);
19
+ log.warn('Files may be corrupted. Run: codex-copilot reset');
20
+ return;
21
+ }
15
22
 
16
- log.title(`📊 项目: ${tasks.project}`);
23
+ log.title(`📊 Project: ${tasks.project}`);
17
24
  log.blank();
18
25
 
19
- // 进度条
26
+ // Progress bar
20
27
  const completed = tasks.tasks.filter(t => t.status === 'completed').length;
21
28
  const inProgress = tasks.tasks.filter(t => t.status === 'in_progress' || t.status === 'developed').length;
22
29
  const pending = tasks.tasks.filter(t => t.status === 'pending').length;
23
30
  const skipped = tasks.tasks.filter(t => t.status === 'skipped').length;
24
31
 
25
- progressBar(completed, tasks.total, `${completed}/${tasks.total} 完成`);
32
+ progressBar(completed, tasks.total, `${completed}/${tasks.total} done`);
26
33
  log.blank();
27
34
 
28
- // 统计
29
- log.info(`✅ 已完成: ${completed}`);
30
- if (inProgress > 0) log.info(`🔄 进行中: ${inProgress}`);
31
- log.info(`⏳ 待开发: ${pending}`);
32
- if (skipped > 0) log.warn(`⏭ 已跳过: ${skipped}`);
35
+ // Stats
36
+ log.info(`✅ Completed: ${completed}`);
37
+ if (inProgress > 0) log.info(`🔄 In progress: ${inProgress}`);
38
+ log.info(`⏳ Pending: ${pending}`);
39
+ if (skipped > 0) log.warn(`⏭ Skipped: ${skipped}`);
33
40
  log.blank();
34
41
 
35
- // 当前状态
42
+ // Current state
36
43
  if (state.current_pr) {
37
- log.info(`当前 PR: #${state.current_pr} (Review ${state.review_round})`);
44
+ log.info(`Current PR: #${state.current_pr} (review round ${state.review_round})`);
38
45
  }
39
46
  log.blank();
40
47
 
41
- // 任务列表
42
- log.title('任务列表:');
48
+ // Task list
49
+ log.title('Task list:');
43
50
  log.blank();
44
51
  for (const task of tasks.tasks) {
45
52
  const icon = task.status === 'completed' ? '✅' :