speccrew 0.7.37 → 0.7.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speccrew",
3
- "version": "0.7.37",
3
+ "version": "0.7.38",
4
4
  "description": "Spec-Driven Development toolkit for AI-powered IDEs",
5
5
  "author": "charlesmu99",
6
6
  "repository": {
@@ -137,7 +137,7 @@ function outputError(error) {
137
137
  */
138
138
  function acquireLock(filePath) {
139
139
  const lockPath = `${filePath}.lock`;
140
- const maxRetries = 30;
140
+ const maxRetries = 50;
141
141
  let retryCount = 0;
142
142
 
143
143
  while (retryCount < maxRetries) {
@@ -152,7 +152,7 @@ function acquireLock(filePath) {
152
152
  try {
153
153
  const lockStat = fs.statSync(lockPath);
154
154
  const ageSeconds = (Date.now() - lockStat.mtimeMs) / 1000;
155
- if (ageSeconds > 60) {
155
+ if (ageSeconds > 120) {
156
156
  console.error(`Warning: Stale lock file detected (age: ${Math.round(ageSeconds)}s), removing: ${lockPath}`);
157
157
  fs.unlinkSync(lockPath);
158
158
  // Do not consume retry count, continue to next loop attempt to acquire lock
@@ -166,9 +166,10 @@ function acquireLock(filePath) {
166
166
  if (retryCount >= maxRetries) {
167
167
  throw new Error(`Failed to acquire file lock for '${filePath}' after ${maxRetries} attempts`);
168
168
  }
169
- // Wait 1 second before retry
169
+ // Retry with jitter to avoid thundering herd
170
+ const delay = 200 + Math.floor(Math.random() * 300);
170
171
  const start = Date.now();
171
- while (Date.now() - start < 1000) {
172
+ while (Date.now() - start < delay) {
172
173
  // Busy wait
173
174
  }
174
175
  }
@@ -209,7 +210,13 @@ function readJsonFile(filePath) {
209
210
  if (!fs.existsSync(filePath)) {
210
211
  throw new Error(`File not found: ${filePath}`);
211
212
  }
212
- const content = fs.readFileSync(filePath, 'utf8');
213
+ let content = fs.readFileSync(filePath, 'utf8');
214
+
215
+ // Remove UTF-8 BOM if present
216
+ if (content.charCodeAt(0) === 0xFEFF) {
217
+ content = content.slice(1);
218
+ }
219
+
213
220
  try {
214
221
  return JSON.parse(content);
215
222
  } catch (e) {
@@ -569,7 +576,7 @@ function cmdRead(args) {
569
576
 
570
577
  // 5. --status mode: filter tasks by status
571
578
  if (args.status) {
572
- const validStatuses = ['pending', 'in_progress', 'partial', 'completed', 'failed'];
579
+ const validStatuses = ['pending', 'in_progress', 'partial', 'completed', 'failed', 'confirmed'];
573
580
  if (!validStatuses.includes(args.status)) {
574
581
  outputError(`Invalid status filter: ${args.status}. Must be one of: ${validStatuses.join(', ')}`);
575
582
  }
@@ -597,7 +604,7 @@ function cmdUpdateTask(args) {
597
604
  outputError('Usage: update-task --file <path> --task-id <id> --status <status> [options]');
598
605
  }
599
606
 
600
- const validStatuses = ['pending', 'in_progress', 'partial', 'completed', 'failed'];
607
+ const validStatuses = ['pending', 'in_progress', 'partial', 'completed', 'failed', 'confirmed'];
601
608
  if (!validStatuses.includes(args.status)) {
602
609
  outputError(`Invalid status: ${args.status}. Must be one of: ${validStatuses.join(', ')}`);
603
610
  }
@@ -638,6 +645,8 @@ function cmdUpdateTask(args) {
638
645
  if (args.output) {
639
646
  task.output = args.output;
640
647
  }
648
+ } else if (args.status === 'confirmed') {
649
+ task.confirmed_at = now;
641
650
  } else if (args.status === 'failed') {
642
651
  task.completed_at = now;
643
652
  if (args.error) {