@jojonax/codex-copilot 1.0.2 → 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';
@@ -31,17 +31,17 @@ export async function run(projectDir) {
31
31
  tasks = readJSON(tasksPath);
32
32
  state = readJSON(statePath);
33
33
  } catch (err) {
34
- log.error(`读取任务/状态文件失败: ${err.message}`);
35
- log.warn('文件可能已损坏,请运行 codex-copilot reset');
34
+ log.error(`Failed to read task/state files: ${err.message}`);
35
+ log.warn('Files may be corrupted. Run: codex-copilot reset');
36
36
  closePrompt();
37
37
  process.exit(1);
38
38
  }
39
39
  const config = existsSync(configPath) ? readJSON(configPath) : {};
40
40
 
41
- // 校验 tasks.json 结构
41
+ // Validate tasks.json structure
42
42
  if (!tasks.tasks || !Array.isArray(tasks.tasks) || !tasks.total) {
43
- log.error('tasks.json 格式无效: 缺少 tasks 数组或 total 字段');
44
- log.warn('请重新运行 codex-copilot init 并让 CodeX 重新生成 tasks.json');
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
45
  closePrompt();
46
46
  process.exit(1);
47
47
  }
@@ -51,54 +51,54 @@ export async function run(projectDir) {
51
51
  const pollInterval = config.review_poll_interval || 60;
52
52
  const waitTimeout = config.review_wait_timeout || 600;
53
53
 
54
- log.title('🚀 开始自动化开发循环');
54
+ log.title('🚀 Starting automated development loop');
55
55
  log.blank();
56
- log.info(`项目: ${tasks.project}`);
57
- log.info(`总任务数: ${tasks.total}`);
58
- log.info(`已完成: ${state.current_task}`);
59
- 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}`);
60
60
  log.blank();
61
- 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`);
62
62
  log.blank();
63
63
 
64
- // 逐个任务执行
64
+ // Execute tasks one by one
65
65
  for (const task of tasks.tasks) {
66
- if (task.id <= state.current_task) continue; // 跳过已完成的
66
+ if (task.id <= state.current_task) continue; // Skip completed
67
67
  if (task.status === 'completed' || task.status === 'skipped') continue;
68
68
 
69
69
  log.blank();
70
- log.title(`━━━ 任务 ${task.id}/${tasks.total}: ${task.title} ━━━`);
70
+ log.title(`━━━ Task ${task.id}/${tasks.total}: ${task.title} ━━━`);
71
71
  log.blank();
72
72
 
73
- // 检查依赖是否已完成
73
+ // Check dependencies
74
74
  if (task.depends_on && task.depends_on.length > 0) {
75
75
  const unfinished = task.depends_on.filter(dep => {
76
76
  const depTask = tasks.tasks.find(t => t.id === dep);
77
77
  return depTask && depTask.status !== 'completed';
78
78
  });
79
79
  if (unfinished.length > 0) {
80
- log.warn(`依赖任务未完成: ${unfinished.join(', ')},跳过`);
80
+ log.warn(`Unfinished dependencies: ${unfinished.join(', ')} — skipping`);
81
81
  continue;
82
82
  }
83
83
  }
84
84
 
85
- // ===== 阶段 1: 开发 =====
85
+ // ===== Phase 1: Develop =====
86
86
  await developPhase(projectDir, task, baseBranch);
87
87
 
88
- // ===== 阶段 2: 创建 PR =====
88
+ // ===== Phase 2: Create PR =====
89
89
  const prInfo = await prPhase(projectDir, task, baseBranch);
90
90
 
91
- // ===== 阶段 3: Review 循环 =====
91
+ // ===== Phase 3: Review loop =====
92
92
  await reviewLoop(projectDir, task, prInfo, {
93
93
  maxRounds: maxReviewRounds,
94
94
  pollInterval,
95
95
  waitTimeout,
96
96
  });
97
97
 
98
- // ===== 阶段 4: 合并 =====
98
+ // ===== Phase 4: Merge =====
99
99
  await mergePhase(projectDir, task, prInfo, baseBranch);
100
100
 
101
- // 更新任务状态
101
+ // Update task state
102
102
  task.status = 'completed';
103
103
  state.current_task = task.id;
104
104
  state.current_pr = null;
@@ -107,91 +107,94 @@ export async function run(projectDir) {
107
107
  writeJSON(statePath, state);
108
108
 
109
109
  log.blank();
110
- progressBar(task.id, tasks.total, `${task.id}/${tasks.total} 任务完成`);
111
- log.info(`✅ 任务 #${task.id} 完成!`);
110
+ progressBar(task.id, tasks.total, `${task.id}/${tasks.total} tasks done`);
111
+ log.info(`✅ Task #${task.id} complete!`);
112
112
  }
113
113
 
114
114
  log.blank();
115
- log.title('🎉 所有任务已完成!');
115
+ log.title('🎉 All tasks complete!');
116
116
  log.blank();
117
117
  closePrompt();
118
118
  }
119
119
 
120
120
  // ──────────────────────────────────────────────
121
- // 阶段 1: 开发
121
+ // Phase 1: Develop
122
122
  // ──────────────────────────────────────────────
123
123
  async function developPhase(projectDir, task, baseBranch) {
124
- log.step('阶段 1/4: 开发');
124
+ log.step('Phase 1/4: Develop');
125
125
 
126
- // 切换到 feature 分支
126
+ // Switch to feature branch
127
127
  git.checkoutBranch(projectDir, task.branch, baseBranch);
128
- log.info(`已切换到分支: ${task.branch}`);
128
+ log.info(`Switched to branch: ${task.branch}`);
129
129
 
130
- // 构建开发 Prompt
130
+ // Build development prompt
131
131
  const devPrompt = buildDevPrompt(task);
132
132
 
133
- // 检查是否有 CodeX CLI
133
+ // Check if CodeX CLI is available
134
134
  const codexAvailable = isCodexAvailable();
135
135
 
136
136
  if (codexAvailable) {
137
- log.info('检测到 CodeX CLI,自动执行...');
138
- 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?');
139
139
  if (autoRun) {
140
140
  try {
141
141
  const promptPath = resolve(projectDir, '.codex-copilot/_current_prompt.md');
142
142
  writeFileSync(promptPath, devPrompt);
143
- execSync(`codex -q --file .codex-copilot/_current_prompt.md`, {
143
+ execSync(`cat .codex-copilot/_current_prompt.md | codex exec --full-auto -`, {
144
144
  cwd: projectDir,
145
145
  stdio: 'inherit',
146
146
  });
147
- log.info('CodeX 开发完成');
147
+ log.info('CodeX development complete');
148
148
  return;
149
149
  } catch (err) {
150
- log.warn(`CodeX CLI 调用失败: ${err.message}`);
151
- log.warn('回退到手动模式');
150
+ log.warn(`CodeX CLI invocation failed: ${err.message}`);
151
+ log.warn('Falling back to manual mode');
152
152
  }
153
153
  }
154
154
  }
155
155
 
156
- // 手动模式:显示 Prompt,等待用户确认
156
+ // Manual mode: display prompt, wait for user confirmation
157
157
  log.blank();
158
- console.log(' ┌─── 请将以下内容粘贴到 CodeX 桌面版中执行 ───┐');
158
+ console.log(' ┌─── Paste the following into CodeX Desktop to execute ───┐');
159
159
  log.blank();
160
160
  console.log(devPrompt.split('\n').map(l => ` │ ${l}`).join('\n'));
161
161
  log.blank();
162
- console.log(' └──────────────────────────────────────────────┘');
162
+ console.log(' └─────────────────────────────────────────────────────────┘');
163
163
  log.blank();
164
164
 
165
- // 同时将 Prompt 保存到文件,方便复制
165
+ // Also save prompt to file for easy copying
166
166
  const promptPath = resolve(projectDir, '.codex-copilot/_current_prompt.md');
167
167
  writeFileSync(promptPath, devPrompt);
168
- 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)');
169
169
  log.blank();
170
170
 
171
- // 尝试复制到剪贴板(通过 stdin 传入,避免 shell 注入)
171
+ // Try to copy to clipboard (via stdin to avoid shell injection)
172
172
  copyToClipboard(devPrompt);
173
173
 
174
- await ask('CodeX 开发完成后按 Enter 继续...');
174
+ await ask('Press Enter after CodeX development is complete...');
175
175
  }
176
176
 
177
177
  // ──────────────────────────────────────────────
178
- // 阶段 2: 创建 PR
178
+ // Phase 2: Create PR
179
179
  // ──────────────────────────────────────────────
180
180
  async function prPhase(projectDir, task, baseBranch) {
181
- log.step('阶段 2/4: 提交 PR');
181
+ log.step('Phase 2/4: Submit PR');
182
182
 
183
- // 确保变更已提交
183
+ // Ensure changes are committed
184
184
  if (!git.isClean(projectDir)) {
185
- log.info('检测到未提交的变更,自动提交...');
185
+ log.info('Uncommitted changes detected, auto-committing...');
186
186
  git.commitAll(projectDir, `feat(task-${task.id}): ${task.title}`);
187
187
  }
188
188
 
189
- // 推送
189
+ // Push
190
190
  git.pushBranch(projectDir, task.branch);
191
- log.info('代码已推送');
191
+ log.info('Code pushed');
192
192
 
193
- // 创建 PR
194
- 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*`;
195
198
 
196
199
  try {
197
200
  const prInfo = github.createPR(projectDir, {
@@ -200,36 +203,36 @@ async function prPhase(projectDir, task, baseBranch) {
200
203
  base: baseBranch,
201
204
  head: task.branch,
202
205
  });
203
- log.info(`PR 已创建: ${prInfo.url}`);
206
+ log.info(`PR created: ${prInfo.url}`);
204
207
  return prInfo;
205
208
  } catch (err) {
206
- // PR 可能已存在
207
- log.warn(`创建 PR 异常: ${err.message}`);
208
- const prNumber = await ask('请输入已存在的 PR 编号:');
209
+ // PR may already exist
210
+ log.warn(`PR creation error: ${err.message}`);
211
+ const prNumber = await ask('Enter existing PR number:');
209
212
  const parsed = parseInt(prNumber, 10);
210
213
  if (isNaN(parsed) || parsed <= 0) {
211
- log.error('无效的 PR 编号');
212
- throw new Error('无效的 PR 编号');
214
+ log.error('Invalid PR number');
215
+ throw new Error('Invalid PR number');
213
216
  }
214
217
  return { number: parsed, url: '' };
215
218
  }
216
219
  }
217
220
 
218
221
  // ──────────────────────────────────────────────
219
- // 阶段 3: Review 循环
222
+ // Phase 3: Review loop
220
223
  // ──────────────────────────────────────────────
221
224
  async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pollInterval, waitTimeout }) {
222
225
  let maxRounds = _maxRounds;
223
- log.step('阶段 3/4: 等待 Review');
226
+ log.step('Phase 3/4: Waiting for review');
224
227
 
225
228
  for (let round = 1; round <= maxRounds; round++) {
226
- // 等待 Review
227
- log.info(`等待 Review 意见... (超时: ${waitTimeout}s)`);
229
+ // Wait for review
230
+ log.info(`Waiting for review feedback... (timeout: ${waitTimeout}s)`);
228
231
  const gotReview = await waitForReview(projectDir, prInfo.number, pollInterval, waitTimeout);
229
232
 
230
233
  if (!gotReview) {
231
- log.warn('等待 Review 超时');
232
- 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):');
233
236
  if (action === 'wait') {
234
237
  round--;
235
238
  continue;
@@ -237,28 +240,28 @@ async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pol
237
240
  break;
238
241
  }
239
242
 
240
- // 检查 Review 状态
243
+ // Check review status
241
244
  const state = github.getLatestReviewState(projectDir, prInfo.number);
242
- log.info(`Review 状态: ${state}`);
245
+ log.info(`Review status: ${state}`);
243
246
 
244
247
  if (state === 'APPROVED') {
245
- log.info('✅ Review 已通过!');
248
+ log.info('✅ Review approved!');
246
249
  return;
247
250
  }
248
251
 
249
- // 收集 Review 反馈
252
+ // Collect review feedback
250
253
  const feedback = github.collectReviewFeedback(projectDir, prInfo.number);
251
254
  if (!feedback) {
252
- log.info('未发现具体修改意见,继续');
255
+ log.info('No specific change requests found, proceeding');
253
256
  return;
254
257
  }
255
258
 
256
259
  log.blank();
257
- log.warn(`收到 Review 意见 ( ${round}/${maxRounds})`);
260
+ log.warn(`Received review feedback (round ${round}/${maxRounds})`);
258
261
 
259
262
  if (round >= maxRounds) {
260
- log.warn(`已达最大修复轮次 (${maxRounds})`);
261
- 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):');
262
265
  if (choice === 'fix') {
263
266
  maxRounds++;
264
267
  } else if (choice === 'skip') {
@@ -268,17 +271,17 @@ async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pol
268
271
  }
269
272
  }
270
273
 
271
- // CodeX 修复
274
+ // Let CodeX fix
272
275
  await fixPhase(projectDir, task, feedback, round);
273
276
 
274
- // 推送修复
277
+ // Push fix
275
278
  if (!git.isClean(projectDir)) {
276
279
  git.commitAll(projectDir, `fix(task-${task.id}): address review comments (round ${round})`);
277
280
  }
278
281
  git.pushBranch(projectDir, task.branch);
279
- log.info('修复已推送,等待新一轮 Review...');
282
+ log.info('Fix pushed, waiting for next review round...');
280
283
 
281
- // 稍等一下让 Review bot 反应
284
+ // Brief wait for review bot to react
282
285
  await sleep(10000);
283
286
  }
284
287
  }
@@ -294,7 +297,7 @@ async function waitForReview(projectDir, prNumber, pollInterval, timeout) {
294
297
  const currentReviews = github.getReviews(projectDir, prNumber);
295
298
  const currentComments = github.getIssueComments(projectDir, prNumber);
296
299
 
297
- // 检查是否有新的 Review Bot 评论
300
+ // Check for new reviews or bot comments
298
301
  const hasNewReview = currentReviews.length > startReviewCount;
299
302
  const hasBotComment = currentComments.some(c =>
300
303
  (c.user?.type === 'Bot' || c.user?.login?.includes('bot')) &&
@@ -312,95 +315,98 @@ async function waitForReview(projectDir, prNumber, pollInterval, timeout) {
312
315
  }
313
316
 
314
317
  // ──────────────────────────────────────────────
315
- // 修复阶段
318
+ // Fix phase
316
319
  // ──────────────────────────────────────────────
317
320
  async function fixPhase(projectDir, task, feedback, round) {
318
- log.step(`修复 Review 意见 ( ${round})`);
321
+ log.step(`Fixing review comments (round ${round})`);
319
322
 
320
- const fixPrompt = `以下是 PR Review 的意见,请逐一修复:
323
+ const fixPrompt = `The following are PR review comments. Please fix each one:
321
324
 
322
- ## Review 意见
325
+ ## Review Comments
323
326
  ${feedback}
324
327
 
325
- ## 要求
326
- 1. 逐条修复上述问题
327
- 2. 建议性意见可以不修复,但在 commit message 中说明原因
328
- 3. 修复后确保不引入新问题
329
- 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}"
330
333
  `;
331
334
 
332
- // 保存到文件并提示用户
335
+ // Save to file and prompt user
333
336
  const promptPath = resolve(projectDir, '.codex-copilot/_current_prompt.md');
334
337
  writeFileSync(promptPath, fixPrompt);
335
338
 
336
- // 尝试 CodeX CLI
339
+ // Try CodeX CLI
337
340
  const codexAvailable = isCodexAvailable();
338
341
 
339
342
  if (codexAvailable) {
340
- const autoFix = await confirm('自动调用 CodeX 修复?');
343
+ const autoFix = await confirm('Auto-invoke CodeX to fix?');
341
344
  if (autoFix) {
342
345
  try {
343
- execSync(`codex -q --file .codex-copilot/_current_prompt.md`, {
346
+ execSync(`cat .codex-copilot/_current_prompt.md | codex exec --full-auto -`, {
344
347
  cwd: projectDir,
345
348
  stdio: 'inherit',
346
349
  });
347
- log.info('CodeX 修复完成');
350
+ log.info('CodeX fix complete');
348
351
  return;
349
352
  } catch {
350
- log.warn('CodeX CLI 调用失败,回退到手动模式');
353
+ log.warn('CodeX CLI invocation failed, falling back to manual mode');
351
354
  }
352
355
  }
353
356
  }
354
357
 
355
- // 手动模式
358
+ // Manual mode
356
359
  log.blank();
357
- log.dim(`Review 修复 Prompt 已保存到 .codex-copilot/_current_prompt.md`);
358
- 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');
359
362
 
360
363
  copyToClipboard(fixPrompt);
361
364
 
362
- await ask('CodeX 修复完成后按 Enter 继续...');
365
+ await ask('Press Enter after CodeX fix is complete...');
363
366
  }
364
367
 
365
368
  // ──────────────────────────────────────────────
366
- // 阶段 4: 合并
369
+ // Phase 4: Merge
367
370
  // ──────────────────────────────────────────────
368
371
  async function mergePhase(projectDir, task, prInfo, baseBranch) {
369
- log.step('阶段 4/4: 合并 PR');
372
+ log.step('Phase 4/4: Merge PR');
370
373
 
371
- const doMerge = await confirm(`合并 PR #${prInfo.number}?`);
374
+ const doMerge = await confirm(`Merge PR #${prInfo.number}?`);
372
375
  if (!doMerge) {
373
- log.warn('用户跳过合并');
376
+ log.warn('User skipped merge');
374
377
  return;
375
378
  }
376
379
 
377
380
  try {
378
381
  github.mergePR(projectDir, prInfo.number);
379
- log.info(`PR #${prInfo.number} 已合并 ✅`);
382
+ log.info(`PR #${prInfo.number} merged ✅`);
380
383
  } catch (err) {
381
- log.error(`合并失败: ${err.message}`);
382
- log.warn('请手动合并后按 Enter 继续');
383
- await ask('继续...');
384
+ log.error(`Merge failed: ${err.message}`);
385
+ log.warn('Please merge manually, then press Enter to continue');
386
+ await ask('Continue...');
384
387
  }
385
388
 
386
- // 切回主分支
389
+ // Switch back to main branch
387
390
  git.checkoutMain(projectDir, baseBranch);
388
391
  }
389
392
 
390
393
  function buildDevPrompt(task) {
391
- 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:
392
398
 
393
- ## 任务 #${task.id}: ${task.title}
399
+ ## Task #${task.id}: ${task.title}
394
400
 
395
401
  ${task.description}
396
402
 
397
- ## 验收标准
398
- ${task.acceptance.map(a => `- ${a}`).join('\n')}
403
+ ## Acceptance Criteria
404
+ ${acceptanceList}
399
405
 
400
- ## 要求
401
- 1. 严格按照项目现有的代码规范和技术栈
402
- 2. 完成后确保代码可以正常编译/运行
403
- 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:
404
410
  git add -A
405
411
  git commit -m "feat(task-${task.id}): ${task.title}"
406
412
  `;
@@ -423,6 +429,6 @@ function isCodexAvailable() {
423
429
  function copyToClipboard(text) {
424
430
  try {
425
431
  execSync('pbcopy', { input: text, stdio: ['pipe', 'pipe', 'pipe'] });
426
- log.info('📋 已复制到剪贴板');
432
+ log.info('📋 Copied to clipboard');
427
433
  } catch {}
428
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';
@@ -15,38 +15,38 @@ export async function status(projectDir) {
15
15
  tasks = JSON.parse(readFileSync(tasksPath, 'utf-8'));
16
16
  state = JSON.parse(readFileSync(statePath, 'utf-8'));
17
17
  } catch (err) {
18
- log.error(`读取文件失败: ${err.message}`);
19
- log.warn('文件可能已损坏,请运行 codex-copilot reset');
18
+ log.error(`Failed to read files: ${err.message}`);
19
+ log.warn('Files may be corrupted. Run: codex-copilot reset');
20
20
  return;
21
21
  }
22
22
 
23
- log.title(`📊 项目: ${tasks.project}`);
23
+ log.title(`📊 Project: ${tasks.project}`);
24
24
  log.blank();
25
25
 
26
- // 进度条
26
+ // Progress bar
27
27
  const completed = tasks.tasks.filter(t => t.status === 'completed').length;
28
28
  const inProgress = tasks.tasks.filter(t => t.status === 'in_progress' || t.status === 'developed').length;
29
29
  const pending = tasks.tasks.filter(t => t.status === 'pending').length;
30
30
  const skipped = tasks.tasks.filter(t => t.status === 'skipped').length;
31
31
 
32
- progressBar(completed, tasks.total, `${completed}/${tasks.total} 完成`);
32
+ progressBar(completed, tasks.total, `${completed}/${tasks.total} done`);
33
33
  log.blank();
34
34
 
35
- // 统计
36
- log.info(`✅ 已完成: ${completed}`);
37
- if (inProgress > 0) log.info(`🔄 进行中: ${inProgress}`);
38
- log.info(`⏳ 待开发: ${pending}`);
39
- 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}`);
40
40
  log.blank();
41
41
 
42
- // 当前状态
42
+ // Current state
43
43
  if (state.current_pr) {
44
- log.info(`当前 PR: #${state.current_pr} (Review ${state.review_round})`);
44
+ log.info(`Current PR: #${state.current_pr} (review round ${state.review_round})`);
45
45
  }
46
46
  log.blank();
47
47
 
48
- // 任务列表
49
- log.title('任务列表:');
48
+ // Task list
49
+ log.title('Task list:');
50
50
  log.blank();
51
51
  for (const task of tasks.tasks) {
52
52
  const icon = task.status === 'completed' ? '✅' :
@@ -1,13 +1,13 @@
1
1
  /**
2
- * PRD 自动检测模块
3
- * 在项目目录中自动查找 PRD 文档
2
+ * PRD auto-detection module
3
+ * Automatically finds PRD documents in the project directory
4
4
  */
5
5
 
6
6
  import { readdirSync, readFileSync, statSync } from 'fs';
7
7
  import { resolve, extname, relative } from 'path';
8
8
  import { log } from './logger.js';
9
9
 
10
- // PRD 文件名匹配模式(优先级由高到低)
10
+ // PRD filename patterns (ordered by priority, highest first)
11
11
  const PRD_PATTERNS = [
12
12
  /prd/i,
13
13
  /product.?requirement/i,
@@ -18,9 +18,9 @@ const PRD_PATTERNS = [
18
18
  /spec/i,
19
19
  ];
20
20
 
21
- // 搜索目录(按优先级)
21
+ // Directories to search (ordered by priority)
22
22
  const SEARCH_DIRS = [
23
- '.', // 项目根目录
23
+ '.', // Project root
24
24
  'docs',
25
25
  'doc',
26
26
  'PRD',
@@ -30,16 +30,16 @@ const SEARCH_DIRS = [
30
30
  '文档',
31
31
  ];
32
32
 
33
- // 忽略目录
33
+ // Directories to ignore
34
34
  const IGNORE_DIRS = new Set([
35
35
  'node_modules', '.git', '.next', 'dist', 'build', 'vendor',
36
36
  '.codex-copilot', '.vscode', '.idea', '__pycache__', 'coverage',
37
37
  ]);
38
38
 
39
39
  /**
40
- * 自动检测当前项目中的 PRD 文件
41
- * @param {string} projectDir - 项目根目录
42
- * @returns {Array<{path: string, score: number, name: string}>} 候选 PRD 文件列表(按匹配度排序)
40
+ * Auto-detect PRD files in the project
41
+ * @param {string} projectDir - Project root directory
42
+ * @returns {Array<{path: string, score: number, name: string}>} Candidate PRD files sorted by match score
43
43
  */
44
44
  export function detectPRD(projectDir) {
45
45
  const candidates = [];
@@ -55,14 +55,14 @@ export function detectPRD(projectDir) {
55
55
  scanDir(searchPath, projectDir, candidates, 0);
56
56
  }
57
57
 
58
- // 按匹配度排序
58
+ // Sort by match score (descending)
59
59
  candidates.sort((a, b) => b.score - a.score);
60
60
 
61
61
  return candidates;
62
62
  }
63
63
 
64
64
  function scanDir(dir, projectDir, candidates, depth) {
65
- if (depth > 3) return; // 最多搜索 3
65
+ if (depth > 3) return; // Max 3 levels deep
66
66
 
67
67
  let entries;
68
68
  try {
@@ -81,10 +81,10 @@ function scanDir(dir, projectDir, candidates, depth) {
81
81
  continue;
82
82
  }
83
83
 
84
- // 只搜索 markdown 文件
84
+ // Only scan markdown files
85
85
  if (extname(entry.name).toLowerCase() !== '.md') continue;
86
86
 
87
- // 计算匹配分数
87
+ // Calculate match score
88
88
  const score = scorePRDMatch(entry.name, fullPath);
89
89
  if (score > 0) {
90
90
  candidates.push({
@@ -101,21 +101,21 @@ function scorePRDMatch(filename, fullPath) {
101
101
  let score = 0;
102
102
  const lower = filename.toLowerCase();
103
103
 
104
- // 文件名匹配
104
+ // Filename pattern matching
105
105
  for (let i = 0; i < PRD_PATTERNS.length; i++) {
106
106
  if (PRD_PATTERNS[i].test(filename)) {
107
- score += (PRD_PATTERNS.length - i) * 10; // 越靠前的模式分数越高
107
+ score += (PRD_PATTERNS.length - i) * 10; // Higher priority patterns get higher scores
108
108
  }
109
109
  }
110
110
 
111
- // 文件大小加分(PRD 通常内容较多)
111
+ // File size bonus (PRDs are typically longer)
112
112
  try {
113
113
  const stat = statSync(fullPath);
114
114
  if (stat.size > 5000) score += 5; // > 5KB
115
115
  if (stat.size > 20000) score += 5; // > 20KB
116
116
  } catch {}
117
117
 
118
- // 内容采样检测(读前 2000 字符)
118
+ // Content sampling (read first 2000 chars)
119
119
  if (score > 0) {
120
120
  try {
121
121
  const content = readFileSync(fullPath, 'utf-8').slice(0, 2000);
@@ -130,7 +130,7 @@ function scorePRDMatch(filename, fullPath) {
130
130
  }
131
131
 
132
132
  /**
133
- * 读取 PRD 文件内容
133
+ * Read PRD file content
134
134
  */
135
135
  export function readPRD(prdPath) {
136
136
  return readFileSync(prdPath, 'utf-8');