speccrew 0.6.11 → 0.6.13

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,75 +1,76 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * update-progress.js - 通用进度文件更新工具
4
+ * update-progress.js - Universal Progress File Update Tool
5
5
  *
6
- * SpecCrew 所有 Agent 提供统一的进度文件更新工具,替代手动 PowerShell/Python 内联操作。
6
+ * Provides a unified progress file update tool for all SpecCrew Agents,
7
+ * replacing manual PowerShell/Python inline operations.
7
8
  *
8
- * 支持的命令:
9
+ * Supported Commands:
9
10
  *
10
- * 1. init - 初始化 DISPATCH-PROGRESS.json
11
+ * 1. init - Initialize DISPATCH-PROGRESS.json
11
12
  * node update-progress.js init --file <path> --stage <stage_name> --tasks <tasks_json_or_file>
12
- * 选项:
13
- * --file <path> 进度文件路径(必需)
14
- * --stage <name> 阶段名称(必需)
15
- * --tasks <json|file> 任务列表 JSON JSON 文件路径
16
- * --tasks-file <path> 从文件读取任务列表
13
+ * Options:
14
+ * --file <path> Progress file path (required)
15
+ * --stage <name> Stage name (required)
16
+ * --tasks <json|file> Task list JSON or JSON file path
17
+ * --tasks-file <path> Read task list from file
17
18
  *
18
- * 2. read - 读取进度文件
19
+ * 2. read - Read progress file
19
20
  * node update-progress.js read --file <path> [options]
20
- * 选项:
21
- * --file <path> 进度文件路径(必需)
22
- * --task-id <id> 仅输出指定任务
23
- * --status <status> 按状态过滤任务列表(pending/in_progress/partial/completed/failed
24
- * --summary 输出进度摘要(总数/完成/失败/部分/待处理)
25
- * --checkpoints 读取所有 checkpoint 状态
26
- * --overview 读取 workflow 全景(阶段概览)
21
+ * Options:
22
+ * --file <path> Progress file path (required)
23
+ * --task-id <id> Output only the specified task
24
+ * --status <status> Filter tasks by status (pending/in_progress/partial/completed/failed)
25
+ * --summary Output progress summary (total/completed/failed/partial/pending)
26
+ * --checkpoints Read all checkpoint statuses
27
+ * --overview Read workflow overview (stage summary)
27
28
  *
28
- * 3. update-task - 更新单个任务状态
29
+ * 3. update-task - Update a single task status
29
30
  * node update-progress.js update-task --file <path> --task-id <id> --status <status> [options]
30
- * 选项:
31
- * --file <path> 进度文件路径(必需)
32
- * --task-id <id> 任务 ID(必需)
33
- * --status <status> 任务状态:pending/in_progress/partial/completed/failed(必需)
34
- * --output <text> 任务输出(completed 时使用)
35
- * --error <text> 错误信息(failed 时使用)
36
- * --error-category <cat> 错误类别(failed 时使用)
31
+ * Options:
32
+ * --file <path> Progress file path (required)
33
+ * --task-id <id> Task ID (required)
34
+ * --status <status> Task status: pending/in_progress/partial/completed/failed (required)
35
+ * --output <text> Task output (used when completed)
36
+ * --error <text> Error message (used when failed)
37
+ * --error-category <cat> Error category (used when failed)
37
38
  *
38
- * 4. update-counts - 强制重算计数
39
+ * 4. update-counts - Force recalculate counts
39
40
  * node update-progress.js update-counts --file <path>
40
41
  *
41
- * 5. write-checkpoint - 写入/更新 checkpoint
42
+ * 5. write-checkpoint - Write/update checkpoint
42
43
  * node update-progress.js write-checkpoint --file <path> --stage <stage> --checkpoint <name> --passed <true|false> [--description <text>]
43
- * 选项:
44
- * --file <path> 进度文件路径(必需)
45
- * --stage <name> 阶段名称(必需,如文件不存在则创建)
46
- * --checkpoint <name> 检查点名称(必需)
47
- * --passed <true|false> 是否通过(必需)
48
- * --description <text> 描述信息(可选)
44
+ * Options:
45
+ * --file <path> Progress file path (required)
46
+ * --stage <name> Stage name (required, creates file if not exists)
47
+ * --checkpoint <name> Checkpoint name (required)
48
+ * --passed <true|false> Whether passed (required)
49
+ * --description <text> Description (optional)
49
50
  *
50
- * 6. update-workflow - 更新 WORKFLOW-PROGRESS 阶段状态
51
+ * 6. update-workflow - Update WORKFLOW-PROGRESS stage status
51
52
  * node update-progress.js update-workflow --file <path> --stage <name> --status <status> [--output <text>]
52
- * 选项:
53
- * --file <path> 进度文件路径(必需)
54
- * --stage <name> 阶段名称(必需)
55
- * --status <status> 状态:pending/in_progress/completed/confirmed(必需)
56
- * --output <text> 输出信息(可选)
53
+ * Options:
54
+ * --file <path> Progress file path (required)
55
+ * --stage <name> Stage name (required)
56
+ * --status <status> Status: pending/in_progress/completed/confirmed (required)
57
+ * --output <text> Output information (optional)
57
58
  *
58
- * 输出格式:
59
- * 成功:{"success": true, "message": "...", "data": {...}}
60
- * 失败:{"success": false, "error": "..."}(输出到 stderrexit code 1
59
+ * Output Format:
60
+ * Success: {"success": true, "message": "...", "data": {...}}
61
+ * Failure: {"success": false, "error": "..."} (output to stderr, exit code 1)
61
62
  */
62
63
 
63
64
  const fs = require('fs');
64
65
  const path = require('path');
65
66
 
66
67
  // ============================================================================
67
- // 工具函数
68
+ // Utility Functions
68
69
  // ============================================================================
69
70
 
70
71
  /**
71
- * 生成本地时区的 ISO 8601 格式时间戳
72
- * 例如:2026-04-10T20:38:21.978+08:00
72
+ * Generate ISO 8601 format timestamp in local timezone
73
+ * Example: 2026-04-10T20:38:21.978+08:00
73
74
  */
74
75
  function getLocalISOString() {
75
76
  const now = new Date();
@@ -87,14 +88,14 @@ function getLocalISOString() {
87
88
  }
88
89
 
89
90
  /**
90
- * 生成 ISO 8601 格式时间戳(本地时区)
91
+ * Generate ISO 8601 format timestamp (local timezone)
91
92
  */
92
93
  function getTimestamp() {
93
94
  return getLocalISOString();
94
95
  }
95
96
 
96
97
  /**
97
- * 输出成功结果到 stdout
98
+ * Output success result to stdout
98
99
  */
99
100
  function outputSuccess(message, data = null) {
100
101
  const result = { success: true, message };
@@ -105,7 +106,7 @@ function outputSuccess(message, data = null) {
105
106
  }
106
107
 
107
108
  /**
108
- * 输出错误结果到 stderr 并退出
109
+ * Output error result to stderr and exit
109
110
  */
110
111
  function outputError(error) {
111
112
  console.error(JSON.stringify({ success: false, error }, null, 2));
@@ -113,9 +114,9 @@ function outputError(error) {
113
114
  }
114
115
 
115
116
  /**
116
- * 获取文件锁(防止并发冲突)
117
- * @param {string} filePath 目标文件路径
118
- * @returns {string} 锁文件路径
117
+ * Acquire file lock (prevent concurrent conflicts)
118
+ * @param {string} filePath Target file path
119
+ * @returns {string} Lock file path
119
120
  */
120
121
  function acquireLock(filePath) {
121
122
  const lockPath = `${filePath}.lock`;
@@ -124,12 +125,12 @@ function acquireLock(filePath) {
124
125
 
125
126
  while (retryCount < maxRetries) {
126
127
  try {
127
- // 尝试独占创建锁文件
128
+ // Attempt to exclusively create lock file
128
129
  const fd = fs.openSync(lockPath, 'wx');
129
130
  fs.closeSync(fd);
130
131
  return lockPath;
131
132
  } catch (error) {
132
- // 检查是否为锁文件已存在的错误
133
+ // Check if error is lock file already exists
133
134
  if (error.code === 'EEXIST') {
134
135
  try {
135
136
  const lockStat = fs.statSync(lockPath);
@@ -137,18 +138,18 @@ function acquireLock(filePath) {
137
138
  if (ageSeconds > 60) {
138
139
  console.error(`Warning: Stale lock file detected (age: ${Math.round(ageSeconds)}s), removing: ${lockPath}`);
139
140
  fs.unlinkSync(lockPath);
140
- // 不消耗重试次数,继续下一次循环尝试获取锁
141
+ // Do not consume retry count, continue to next loop attempt to acquire lock
141
142
  continue;
142
143
  }
143
144
  } catch (statErr) {
144
- // 锁文件在 stat 时已被删除,继续重试即可
145
+ // Lock file was deleted during stat, continue retrying
145
146
  }
146
147
  }
147
148
  retryCount++;
148
149
  if (retryCount >= maxRetries) {
149
150
  throw new Error(`Failed to acquire file lock for '${filePath}' after ${maxRetries} attempts`);
150
151
  }
151
- // 等待 1 秒后重试
152
+ // Wait 1 second before retry
152
153
  const start = Date.now();
153
154
  while (Date.now() - start < 1000) {
154
155
  // Busy wait
@@ -158,8 +159,8 @@ function acquireLock(filePath) {
158
159
  }
159
160
 
160
161
  /**
161
- * 释放文件锁
162
- * @param {string} lockPath 锁文件路径
162
+ * Release file lock
163
+ * @param {string} lockPath Lock file path
163
164
  */
164
165
  function releaseLock(lockPath) {
165
166
  try {
@@ -167,14 +168,14 @@ function releaseLock(lockPath) {
167
168
  fs.unlinkSync(lockPath);
168
169
  }
169
170
  } catch (e) {
170
- // 忽略清理错误
171
+ // Ignore cleanup errors
171
172
  }
172
173
  }
173
174
 
174
175
  /**
175
- * 原子写入 JSON 文件
176
- * @param {string} filePath 目标文件路径
177
- * @param {object} data 要写入的数据
176
+ * Atomically write JSON file
177
+ * @param {string} filePath Target file path
178
+ * @param {object} data Data to write
178
179
  */
179
180
  function atomicWriteJson(filePath, data) {
180
181
  const tempFile = `${filePath}.tmp`;
@@ -183,9 +184,9 @@ function atomicWriteJson(filePath, data) {
183
184
  }
184
185
 
185
186
  /**
186
- * 读取 JSON 文件
187
- * @param {string} filePath 文件路径
188
- * @returns {object} 解析后的 JSON 对象
187
+ * Read JSON file
188
+ * @param {string} filePath File path
189
+ * @returns {object} Parsed JSON object
189
190
  */
190
191
  function readJsonFile(filePath) {
191
192
  if (!fs.existsSync(filePath)) {
@@ -200,9 +201,9 @@ function readJsonFile(filePath) {
200
201
  }
201
202
 
202
203
  /**
203
- * 计算任务计数
204
- * @param {Array} tasks 任务列表
205
- * @returns {object} 计数结果
204
+ * Calculate task counts
205
+ * @param {Array} tasks Task list
206
+ * @returns {object} Count results
206
207
  */
207
208
  function calculateCounts(tasks) {
208
209
  const total = tasks.length;
@@ -215,12 +216,12 @@ function calculateCounts(tasks) {
215
216
  }
216
217
 
217
218
  // ============================================================================
218
- // 参数解析
219
+ // Argument Parsing
219
220
  // ============================================================================
220
221
 
221
222
  /**
222
- * 解析命令行参数
223
- * 支持 --flag value -Flag value 两种格式
223
+ * Parse command line arguments
224
+ * Supports both --flag value and -Flag value formats
224
225
  */
225
226
  function parseArgs() {
226
227
  const args = process.argv.slice(2);
@@ -246,7 +247,7 @@ function parseArgs() {
246
247
  force: false
247
248
  };
248
249
 
249
- // 第一个参数是命令
250
+ // First argument is the command
250
251
  if (args.length > 0 && !args[0].startsWith('-')) {
251
252
  result.command = args[0];
252
253
  }
@@ -254,7 +255,7 @@ function parseArgs() {
254
255
  for (let i = 0; i < args.length; i++) {
255
256
  const arg = args[i];
256
257
 
257
- // 跳过命令本身
258
+ // Skip the command itself
258
259
  if (i === 0 && !arg.startsWith('-')) {
259
260
  continue;
260
261
  }
@@ -351,11 +352,11 @@ function parseArgs() {
351
352
  }
352
353
 
353
354
  // ============================================================================
354
- // 命令实现
355
+ // Command Implementations
355
356
  // ============================================================================
356
357
 
357
358
  /**
358
- * 命令:init - 初始化进度文件
359
+ * Command: init - Initialize progress file
359
360
  */
360
361
  function cmdInit(args) {
361
362
  if (!args.file || !args.stage) {
@@ -365,9 +366,9 @@ function cmdInit(args) {
365
366
  const filePath = path.resolve(args.file);
366
367
  let tasks = [];
367
368
 
368
- // 从参数或文件读取任务列表
369
+ // Read task list from argument or file
369
370
  if (args.tasksFile) {
370
- // 从文件读取
371
+ // Read from file
371
372
  const tasksContent = fs.readFileSync(path.resolve(args.tasksFile), 'utf8');
372
373
  try {
373
374
  tasks = JSON.parse(tasksContent);
@@ -375,7 +376,7 @@ function cmdInit(args) {
375
376
  outputError(`Failed to parse tasks file: ${e.message}`);
376
377
  }
377
378
  } else if (args.tasks) {
378
- // 直接解析 JSON
379
+ // Parse JSON directly
379
380
  try {
380
381
  tasks = JSON.parse(args.tasks);
381
382
  } catch (e) {
@@ -383,12 +384,12 @@ function cmdInit(args) {
383
384
  }
384
385
  }
385
386
 
386
- // 验证 tasks 是数组
387
+ // Validate tasks is an array
387
388
  if (!Array.isArray(tasks)) {
388
389
  outputError('Tasks must be an array');
389
390
  }
390
391
 
391
- // 确保每个任务有必要的字段
392
+ // Ensure each task has required fields
392
393
  tasks = tasks.map((task, index) => ({
393
394
  id: task.id || `task-${index + 1}`,
394
395
  name: task.name || task.id || `Task ${index + 1}`,
@@ -397,7 +398,7 @@ function cmdInit(args) {
397
398
  ...task
398
399
  }));
399
400
 
400
- // 创建进度文件结构
401
+ // Create progress file structure
401
402
  const progressData = {
402
403
  stage: args.stage,
403
404
  created_at: getTimestamp(),
@@ -407,13 +408,13 @@ function cmdInit(args) {
407
408
  checkpoints: {}
408
409
  };
409
410
 
410
- // 确保目录存在
411
+ // Ensure directory exists
411
412
  const dir = path.dirname(filePath);
412
413
  if (!fs.existsSync(dir)) {
413
414
  fs.mkdirSync(dir, { recursive: true });
414
415
  }
415
416
 
416
- // 获取锁并写入
417
+ // Acquire lock and write
417
418
  let lockPath = null;
418
419
  try {
419
420
  lockPath = acquireLock(filePath);
@@ -429,14 +430,14 @@ function cmdInit(args) {
429
430
  }
430
431
 
431
432
  /**
432
- * 命令:read - 读取进度文件(增强版)
433
- * 支持多种查询模式:
434
- * - 默认:读取整个文件
435
- * - --task-id:查询单个任务
436
- * - --status:按状态过滤任务
437
- * - --summary:输出进度摘要
438
- * - --checkpoints:读取 checkpoint 状态
439
- * - --overview:读取 workflow 全景
433
+ * Command: read - Read progress file (enhanced version)
434
+ * Supports multiple query modes:
435
+ * - Default: read entire file
436
+ * - --task-id: query single task
437
+ * - --status: filter tasks by status
438
+ * - --summary: output progress summary
439
+ * - --checkpoints: read checkpoint status
440
+ * - --overview: read workflow overview
440
441
  */
441
442
  function cmdRead(args) {
442
443
  if (!args.file) {
@@ -450,7 +451,7 @@ function cmdRead(args) {
450
451
  lockPath = acquireLock(filePath);
451
452
  const data = readJsonFile(filePath);
452
453
 
453
- // 1. --summary 模式:输出进度摘要
454
+ // 1. --summary mode: output progress summary
454
455
  if (args.summary) {
455
456
  const counts = data.counts || calculateCounts(data.tasks || []);
456
457
  const summary = {
@@ -467,7 +468,7 @@ function cmdRead(args) {
467
468
  return;
468
469
  }
469
470
 
470
- // 2. --checkpoints 模式:读取 checkpoint 状态
471
+ // 2. --checkpoints mode: read checkpoint status
471
472
  if (args.checkpoints) {
472
473
  const checkpoints = data.checkpoints || {};
473
474
  const checkpointList = Object.entries(checkpoints).map(([name, cp]) => ({
@@ -486,7 +487,7 @@ function cmdRead(args) {
486
487
  return;
487
488
  }
488
489
 
489
- // 3. --overview 模式:读取 workflow 全景
490
+ // 3. --overview mode: read workflow overview
490
491
  if (args.overview) {
491
492
  const stages = data.stages || {};
492
493
  const stageList = Object.entries(stages).map(([name, stage]) => ({
@@ -515,7 +516,7 @@ function cmdRead(args) {
515
516
  return;
516
517
  }
517
518
 
518
- // 4. --task-id 模式:查询单个任务
519
+ // 4. --task-id mode: query single task
519
520
  if (args.taskId) {
520
521
  const task = data.tasks?.find(t => t.id === args.taskId);
521
522
  if (!task) {
@@ -525,7 +526,7 @@ function cmdRead(args) {
525
526
  return;
526
527
  }
527
528
 
528
- // 5. --status 模式:按状态过滤任务
529
+ // 5. --status mode: filter tasks by status
529
530
  if (args.status) {
530
531
  const validStatuses = ['pending', 'in_progress', 'partial', 'completed', 'failed'];
531
532
  if (!validStatuses.includes(args.status)) {
@@ -540,7 +541,7 @@ function cmdRead(args) {
540
541
  return;
541
542
  }
542
543
 
543
- // 6. 默认模式:输出整个文件
544
+ // 6. Default mode: output entire file
544
545
  outputSuccess(`Progress file: ${filePath}`, data);
545
546
  } finally {
546
547
  if (lockPath) releaseLock(lockPath);
@@ -548,7 +549,7 @@ function cmdRead(args) {
548
549
  }
549
550
 
550
551
  /**
551
- * 命令:update-task - 更新单个任务状态
552
+ * Command: update-task - Update a single task status
552
553
  */
553
554
  function cmdUpdateTask(args) {
554
555
  if (!args.file || !args.taskId || !args.status) {
@@ -567,7 +568,7 @@ function cmdUpdateTask(args) {
567
568
  lockPath = acquireLock(filePath);
568
569
  const data = readJsonFile(filePath);
569
570
 
570
- // 查找任务
571
+ // Find task
571
572
  const taskIndex = data.tasks?.findIndex(t => t.id === args.taskId);
572
573
  if (taskIndex === -1 || taskIndex === undefined) {
573
574
  outputError(`Task not found: ${args.taskId}`);
@@ -576,15 +577,15 @@ function cmdUpdateTask(args) {
576
577
  const task = data.tasks[taskIndex];
577
578
  const now = getTimestamp();
578
579
 
579
- // 更新状态
580
+ // Update status
580
581
  task.status = args.status;
581
582
  task.updated_at = now;
582
583
 
583
- // 根据状态自动设置时间戳(始终使用脚本生成的真实时间,不接受外部参数)
584
+ // Set timestamps based on status (always use real timestamp generated by script, external parameters not accepted)
584
585
  if (args.status === 'in_progress') {
585
586
  task.started_at = now;
586
587
  } else if (args.status === 'partial') {
587
- // partial 状态:部分完成,可能已有 started_at,可选设置 completed_at
588
+ // partial status: partially completed, may already have started_at, optionally set completed_at
588
589
  if (!task.started_at) {
589
590
  task.started_at = now;
590
591
  }
@@ -606,14 +607,14 @@ function cmdUpdateTask(args) {
606
607
  }
607
608
  }
608
609
 
609
- // 更新任务
610
+ // Update task
610
611
  data.tasks[taskIndex] = task;
611
612
  data.updated_at = now;
612
613
 
613
- // 重算计数
614
+ // Recalculate counts
614
615
  data.counts = calculateCounts(data.tasks);
615
616
 
616
- // 原子写入
617
+ // Atomic write
617
618
  atomicWriteJson(filePath, data);
618
619
 
619
620
  outputSuccess(`Task updated: ${args.taskId}`, {
@@ -627,7 +628,7 @@ function cmdUpdateTask(args) {
627
628
  }
628
629
 
629
630
  /**
630
- * 命令:update-counts - 强制重算计数
631
+ * Command: update-counts - Force recalculate counts
631
632
  */
632
633
  function cmdUpdateCounts(args) {
633
634
  if (!args.file) {
@@ -645,11 +646,11 @@ function cmdUpdateCounts(args) {
645
646
  outputError('No tasks array found in progress file');
646
647
  }
647
648
 
648
- // 重算计数
649
+ // Recalculate counts
649
650
  data.counts = calculateCounts(data.tasks);
650
651
  data.updated_at = getTimestamp();
651
652
 
652
- // 原子写入
653
+ // Atomic write
653
654
  atomicWriteJson(filePath, data);
654
655
 
655
656
  outputSuccess('Counts updated', { counts: data.counts });
@@ -659,7 +660,7 @@ function cmdUpdateCounts(args) {
659
660
  }
660
661
 
661
662
  /**
662
- * 命令:write-checkpoint - 写入/更新 checkpoint
663
+ * Command: write-checkpoint - Write/update checkpoint
663
664
  */
664
665
  function cmdWriteCheckpoint(args) {
665
666
  if (!args.file || !args.stage || !args.checkpoint || args.passed === null) {
@@ -678,7 +679,7 @@ function cmdWriteCheckpoint(args) {
678
679
  if (fs.existsSync(filePath)) {
679
680
  data = readJsonFile(filePath);
680
681
  } else {
681
- // 创建新文件
682
+ // Create new file
682
683
  data = {
683
684
  stage: args.stage,
684
685
  created_at: now,
@@ -688,12 +689,12 @@ function cmdWriteCheckpoint(args) {
688
689
  };
689
690
  }
690
691
 
691
- // 确保 checkpoints 对象存在
692
+ // Ensure checkpoints object exists
692
693
  if (!data.checkpoints) {
693
694
  data.checkpoints = {};
694
695
  }
695
696
 
696
- // 更新或创建 checkpoint
697
+ // Update or create checkpoint
697
698
  data.checkpoints[args.checkpoint] = {
698
699
  passed: passed,
699
700
  checked_at: now,
@@ -703,13 +704,13 @@ function cmdWriteCheckpoint(args) {
703
704
 
704
705
  data.updated_at = now;
705
706
 
706
- // 确保目录存在
707
+ // Ensure directory exists
707
708
  const dir = path.dirname(filePath);
708
709
  if (!fs.existsSync(dir)) {
709
710
  fs.mkdirSync(dir, { recursive: true });
710
711
  }
711
712
 
712
- // 原子写入
713
+ // Atomic write
713
714
  atomicWriteJson(filePath, data);
714
715
 
715
716
  outputSuccess(`Checkpoint updated: ${args.checkpoint}`, {
@@ -723,7 +724,7 @@ function cmdWriteCheckpoint(args) {
723
724
  }
724
725
 
725
726
  /**
726
- * 命令:update-workflow - 更新 WORKFLOW-PROGRESS 阶段状态
727
+ * Command: update-workflow - Update WORKFLOW-PROGRESS stage status
727
728
  */
728
729
  function cmdUpdateWorkflow(args) {
729
730
  if (!args.file || !args.stage || !args.status) {
@@ -746,7 +747,7 @@ function cmdUpdateWorkflow(args) {
746
747
  if (fs.existsSync(filePath)) {
747
748
  data = readJsonFile(filePath);
748
749
  } else {
749
- // 创建新文件
750
+ // Create new file
750
751
  data = {
751
752
  created_at: now,
752
753
  stages: {},
@@ -754,12 +755,12 @@ function cmdUpdateWorkflow(args) {
754
755
  };
755
756
  }
756
757
 
757
- // 确保 stages 对象存在
758
+ // Ensure stages object exists
758
759
  if (!data.stages) {
759
760
  data.stages = {};
760
761
  }
761
762
 
762
- // 获取或创建阶段
763
+ // Get or create stage
763
764
  if (!data.stages[args.stage]) {
764
765
  data.stages[args.stage] = {
765
766
  status: 'pending',
@@ -772,12 +773,12 @@ function cmdUpdateWorkflow(args) {
772
773
 
773
774
  const stage = data.stages[args.stage];
774
775
 
775
- // 更新状态
776
+ // Update status
776
777
  stage.status = args.status;
777
778
 
778
- // 根据状态自动设置时间戳(始终使用脚本生成的真实时间,不接受外部参数)
779
+ // Set timestamps based on status (always use real timestamp generated by script, external parameters not accepted)
779
780
  if (args.status === 'in_progress') {
780
- // started_at 已有值则不覆盖
781
+ // Do not overwrite if started_at already has a value
781
782
  if (!stage.started_at) {
782
783
  stage.started_at = now;
783
784
  }
@@ -787,22 +788,22 @@ function cmdUpdateWorkflow(args) {
787
788
  stage.confirmed_at = now;
788
789
  }
789
790
 
790
- // 更新输出
791
+ // Update output
791
792
  if (args.output) {
792
793
  stage.output = args.output;
793
794
  }
794
795
 
795
- // 更新当前阶段
796
+ // Update current stage
796
797
  data.current_stage = args.stage;
797
798
  data.updated_at = now;
798
799
 
799
- // 确保目录存在
800
+ // Ensure directory exists
800
801
  const dir = path.dirname(filePath);
801
802
  if (!fs.existsSync(dir)) {
802
803
  fs.mkdirSync(dir, { recursive: true });
803
804
  }
804
805
 
805
- // 原子写入
806
+ // Atomic write
806
807
  atomicWriteJson(filePath, data);
807
808
 
808
809
  outputSuccess(`Workflow stage updated: ${args.stage}`, {
@@ -816,10 +817,10 @@ function cmdUpdateWorkflow(args) {
816
817
  }
817
818
 
818
819
  /**
819
- * 命令:init-tasks - 扫描 feature-design 目录生成任务列表
820
+ * Command: init-tasks - Scan feature-design directory to generate task list
820
821
  */
821
822
  function cmdInitTasks(args) {
822
- // 参数验证
823
+ // Argument validation
823
824
  if (!args.file || !args.stage || !args.featuresDir || !args.platforms) {
824
825
  outputError('Usage: init-tasks --file <path> --stage <stage_name> --features-dir <dir> --platforms <comma-separated> [--force]');
825
826
  }
@@ -828,17 +829,17 @@ function cmdInitTasks(args) {
828
829
  const featuresDir = path.resolve(args.featuresDir);
829
830
  const platforms = args.platforms.split(',').map(p => p.trim()).filter(p => p);
830
831
 
831
- // 验证 platforms 非空
832
+ // Validate platforms is not empty
832
833
  if (platforms.length === 0) {
833
834
  outputError('Platforms list cannot be empty');
834
835
  }
835
836
 
836
- // 验证 features-dir 存在
837
+ // Validate features-dir exists
837
838
  if (!fs.existsSync(featuresDir)) {
838
839
  outputError(`Features directory not found: ${featuresDir}`);
839
840
  }
840
841
 
841
- // 扫描 .feature-spec.md 文件
842
+ // Scan .feature-spec.md files
842
843
  const featureFiles = [];
843
844
  const files = fs.readdirSync(featuresDir);
844
845
  for (const file of files) {
@@ -851,8 +852,8 @@ function cmdInitTasks(args) {
851
852
  outputError(`No .feature-spec.md files found in: ${featuresDir}`);
852
853
  }
853
854
 
854
- // 从文件名提取 feature 信息
855
- // 格式: F-{MODULE}-{NNN}-{feature-name}.feature-spec.md
855
+ // Extract feature info from filenames
856
+ // Format: F-{MODULE}-{NNN}-{feature-name}.feature-spec.md
856
857
  const featurePattern = /^(F-([A-Z]+)-\d+)-(.+)\.feature-spec\.md$/;
857
858
  const features = [];
858
859
 
@@ -862,7 +863,7 @@ function cmdInitTasks(args) {
862
863
  features.push({
863
864
  feature_id: match[1], // F-APPT-001
864
865
  module: match[2], // APPT
865
- name: match[3], // 预约信息CRUD
866
+ name: match[3], // appointment-crud
866
867
  file: file
867
868
  });
868
869
  }
@@ -872,10 +873,10 @@ function cmdInitTasks(args) {
872
873
  outputError('No valid feature files found. Expected format: F-{MODULE}-{NNN}-{feature-name}.feature-spec.md');
873
874
  }
874
875
 
875
- // feature ID 排序
876
+ // Sort by feature ID
876
877
  features.sort((a, b) => a.feature_id.localeCompare(b.feature_id));
877
878
 
878
- // 检查目标文件是否已有 tasks
879
+ // Check if target file already has tasks
879
880
  if (fs.existsSync(filePath)) {
880
881
  const existingData = readJsonFile(filePath);
881
882
  if (existingData.tasks && existingData.tasks.length > 0 && !args.force) {
@@ -883,18 +884,18 @@ function cmdInitTasks(args) {
883
884
  }
884
885
  }
885
886
 
886
- // 生成任务列表
887
+ // Generate task list
887
888
  const tasks = [];
888
889
  const now = getTimestamp();
889
890
 
890
- // Module 排序顺序
891
+ // Module sort order
891
892
  const moduleOrder = ['APPT', 'BASE', 'CUST', 'EMP', 'ITEM', 'KNW', 'REPORT', 'REV', 'SERV'];
892
893
  const getModuleIndex = (module) => {
893
894
  const idx = moduleOrder.indexOf(module);
894
895
  return idx === -1 ? 999 : idx;
895
896
  };
896
897
 
897
- // module 分组
898
+ // Group by module
898
899
  const featuresByModule = {};
899
900
  for (const feature of features) {
900
901
  if (!featuresByModule[feature.module]) {
@@ -903,12 +904,12 @@ function cmdInitTasks(args) {
903
904
  featuresByModule[feature.module].push(feature);
904
905
  }
905
906
 
906
- // 每个 module 内按 feature ID 排序
907
+ // Sort by feature ID within each module
907
908
  for (const module of Object.keys(featuresByModule)) {
908
909
  featuresByModule[module].sort((a, b) => a.feature_id.localeCompare(b.feature_id));
909
910
  }
910
911
 
911
- // module 顺序生成任务
912
+ // Generate tasks in module order
912
913
  const sortedModules = Object.keys(featuresByModule).sort((a, b) => getModuleIndex(a) - getModuleIndex(b));
913
914
 
914
915
  for (const module of sortedModules) {
@@ -927,7 +928,7 @@ function cmdInitTasks(args) {
927
928
  }
928
929
  }
929
930
 
930
- // 创建进度文件结构
931
+ // Create progress file structure
931
932
  const progressData = {
932
933
  stage: args.stage,
933
934
  created_at: now,
@@ -937,13 +938,13 @@ function cmdInitTasks(args) {
937
938
  checkpoints: {}
938
939
  };
939
940
 
940
- // 确保目录存在
941
+ // Ensure directory exists
941
942
  const dir = path.dirname(filePath);
942
943
  if (!fs.existsSync(dir)) {
943
944
  fs.mkdirSync(dir, { recursive: true });
944
945
  }
945
946
 
946
- // 获取锁并写入
947
+ // Acquire lock and write
947
948
  let lockPath = null;
948
949
  try {
949
950
  lockPath = acquireLock(filePath);
@@ -964,13 +965,13 @@ function cmdInitTasks(args) {
964
965
  }
965
966
 
966
967
  // ============================================================================
967
- // 主入口
968
+ // Main Entry
968
969
  // ============================================================================
969
970
 
970
971
  function main() {
971
972
  const args = parseArgs();
972
973
 
973
- // 无命令时显示帮助
974
+ // Show help when no command
974
975
  if (!args.command) {
975
976
  console.error('Usage: node update-progress.js <command> [options]');
976
977
  console.error('');
@@ -981,13 +982,13 @@ function main() {
981
982
  console.error(' update-counts Recalculate task counts');
982
983
  console.error(' write-checkpoint Write or update a checkpoint');
983
984
  console.error(' update-workflow Update a workflow stage status');
984
- console.error(' init-tasks Generate tasks from feature-spec files');
985
+ console.error(' init-tasks Generate tasks from feature-spec files');
985
986
  console.error('');
986
987
  console.error('Run "node update-progress.js <command> --help" for more information.');
987
988
  process.exit(1);
988
989
  }
989
990
 
990
- // 分发命令
991
+ // Dispatch command
991
992
  try {
992
993
  switch (args.command) {
993
994
  case 'init':