@howlil/ez-agents 3.1.0 → 3.4.2

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 (110) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +288 -718
  3. package/bin/install.js +438 -71
  4. package/commands/ez/auth.md +87 -0
  5. package/commands/ez/join-discord.md +18 -18
  6. package/ez-agents/bin/ez-tools.cjs +120 -2
  7. package/ez-agents/bin/lib/assistant-adapter.cjs +264 -205
  8. package/ez-agents/bin/lib/audit-exec.cjs +26 -9
  9. package/ez-agents/bin/lib/auth.cjs +2 -1
  10. package/ez-agents/bin/lib/circuit-breaker.cjs +118 -118
  11. package/ez-agents/bin/lib/commands.cjs +42 -23
  12. package/ez-agents/bin/lib/config.cjs +190 -183
  13. package/ez-agents/bin/lib/core.cjs +42 -25
  14. package/ez-agents/bin/lib/file-lock.cjs +236 -236
  15. package/ez-agents/bin/lib/frontmatter.cjs +299 -299
  16. package/ez-agents/bin/lib/fs-utils.cjs +153 -153
  17. package/ez-agents/bin/lib/git-utils.cjs +203 -203
  18. package/ez-agents/bin/lib/health-check.cjs +2 -3
  19. package/ez-agents/bin/lib/index.cjs +113 -113
  20. package/ez-agents/bin/lib/init.cjs +757 -710
  21. package/ez-agents/bin/lib/logger.cjs +52 -15
  22. package/ez-agents/bin/lib/milestone.cjs +241 -241
  23. package/ez-agents/bin/lib/model-provider.cjs +241 -146
  24. package/ez-agents/bin/lib/phase.cjs +925 -908
  25. package/ez-agents/bin/lib/planning-write.cjs +107 -0
  26. package/ez-agents/bin/lib/retry.cjs +119 -119
  27. package/ez-agents/bin/lib/roadmap.cjs +306 -305
  28. package/ez-agents/bin/lib/safe-exec.cjs +91 -5
  29. package/ez-agents/bin/lib/safe-path.cjs +130 -130
  30. package/ez-agents/bin/lib/state.cjs +736 -721
  31. package/ez-agents/bin/lib/temp-file.cjs +239 -239
  32. package/ez-agents/bin/lib/template.cjs +223 -222
  33. package/ez-agents/bin/lib/test-file-lock.cjs +112 -112
  34. package/ez-agents/bin/lib/test-graceful.cjs +93 -93
  35. package/ez-agents/bin/lib/test-logger.cjs +60 -60
  36. package/ez-agents/bin/lib/test-safe-exec.cjs +38 -38
  37. package/ez-agents/bin/lib/test-safe-path.cjs +33 -33
  38. package/ez-agents/bin/lib/test-temp-file.cjs +125 -125
  39. package/ez-agents/bin/lib/timeout-exec.cjs +63 -62
  40. package/ez-agents/bin/lib/verify.cjs +69 -26
  41. package/ez-agents/references/checkpoints.md +776 -776
  42. package/ez-agents/references/continuation-format.md +249 -249
  43. package/ez-agents/references/questioning.md +162 -162
  44. package/ez-agents/references/tdd.md +263 -263
  45. package/ez-agents/templates/codebase/concerns.md +310 -310
  46. package/ez-agents/templates/codebase/conventions.md +307 -307
  47. package/ez-agents/templates/codebase/integrations.md +280 -280
  48. package/ez-agents/templates/codebase/stack.md +186 -186
  49. package/ez-agents/templates/codebase/testing.md +480 -480
  50. package/ez-agents/templates/config.json +37 -37
  51. package/ez-agents/templates/continue-here.md +78 -78
  52. package/ez-agents/templates/milestone-archive.md +123 -123
  53. package/ez-agents/templates/milestone.md +115 -115
  54. package/ez-agents/templates/requirements.md +231 -231
  55. package/ez-agents/templates/research-project/ARCHITECTURE.md +204 -204
  56. package/ez-agents/templates/research-project/FEATURES.md +147 -147
  57. package/ez-agents/templates/research-project/PITFALLS.md +200 -200
  58. package/ez-agents/templates/research-project/STACK.md +120 -120
  59. package/ez-agents/templates/research-project/SUMMARY.md +170 -170
  60. package/ez-agents/templates/retrospective.md +54 -54
  61. package/ez-agents/templates/roadmap.md +202 -202
  62. package/ez-agents/templates/summary-minimal.md +41 -41
  63. package/ez-agents/templates/summary-standard.md +48 -48
  64. package/ez-agents/templates/summary.md +248 -248
  65. package/ez-agents/templates/user-setup.md +311 -311
  66. package/ez-agents/templates/verification-report.md +322 -322
  67. package/ez-agents/workflows/add-phase.md +112 -112
  68. package/ez-agents/workflows/add-tests.md +351 -351
  69. package/ez-agents/workflows/add-todo.md +158 -158
  70. package/ez-agents/workflows/audit-milestone.md +332 -332
  71. package/ez-agents/workflows/autonomous.md +743 -743
  72. package/ez-agents/workflows/check-todos.md +177 -177
  73. package/ez-agents/workflows/cleanup.md +152 -152
  74. package/ez-agents/workflows/complete-milestone.md +766 -766
  75. package/ez-agents/workflows/diagnose-issues.md +219 -219
  76. package/ez-agents/workflows/discovery-phase.md +289 -289
  77. package/ez-agents/workflows/discuss-phase.md +762 -762
  78. package/ez-agents/workflows/execute-phase.md +468 -468
  79. package/ez-agents/workflows/execute-plan.md +483 -483
  80. package/ez-agents/workflows/health.md +159 -159
  81. package/ez-agents/workflows/help.md +492 -492
  82. package/ez-agents/workflows/insert-phase.md +130 -130
  83. package/ez-agents/workflows/list-phase-assumptions.md +178 -178
  84. package/ez-agents/workflows/map-codebase.md +316 -316
  85. package/ez-agents/workflows/new-milestone.md +384 -384
  86. package/ez-agents/workflows/new-project.md +1113 -1111
  87. package/ez-agents/workflows/node-repair.md +92 -92
  88. package/ez-agents/workflows/pause-work.md +122 -122
  89. package/ez-agents/workflows/plan-milestone-gaps.md +274 -274
  90. package/ez-agents/workflows/plan-phase.md +651 -651
  91. package/ez-agents/workflows/progress.md +382 -382
  92. package/ez-agents/workflows/quick.md +610 -610
  93. package/ez-agents/workflows/remove-phase.md +155 -155
  94. package/ez-agents/workflows/research-phase.md +74 -74
  95. package/ez-agents/workflows/resume-project.md +307 -307
  96. package/ez-agents/workflows/set-profile.md +81 -81
  97. package/ez-agents/workflows/settings.md +242 -242
  98. package/ez-agents/workflows/stats.md +57 -57
  99. package/ez-agents/workflows/transition.md +544 -544
  100. package/ez-agents/workflows/ui-phase.md +290 -290
  101. package/ez-agents/workflows/ui-review.md +157 -157
  102. package/ez-agents/workflows/update.md +320 -320
  103. package/ez-agents/workflows/validate-phase.md +167 -167
  104. package/ez-agents/workflows/verify-phase.md +243 -243
  105. package/ez-agents/workflows/verify-work.md +584 -584
  106. package/package.json +2 -3
  107. package/scripts/build-hooks.js +43 -43
  108. package/scripts/fix-qwen-installation.js +144 -0
  109. package/scripts/run-tests.cjs +29 -29
  110. package/README.zh-CN.md +0 -702
@@ -1,118 +1,118 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * GSD Circuit Breaker — Prevent cascading failures
5
- *
6
- * Implements circuit breaker pattern:
7
- * - CLOSED: Normal operation
8
- * - OPEN: Failing, reject requests
9
- * - HALF_OPEN: Testing if service recovered
10
- *
11
- * Usage:
12
- * const CircuitBreaker = require('./circuit-breaker.cjs');
13
- * const breaker = new CircuitBreaker({ failureThreshold: 5 });
14
- * const result = await breaker.execute(() => riskyOperation());
15
- */
16
-
17
- const Logger = require('./logger.cjs');
18
- const logger = new Logger();
19
-
20
- class CircuitBreaker {
21
- /**
22
- * Create circuit breaker
23
- * @param {Object} options - Configuration
24
- */
25
- constructor(options = {}) {
26
- this.failureThreshold = options.failureThreshold || 5;
27
- this.resetTimeout = options.resetTimeout || 60000;
28
- this.state = 'CLOSED';
29
- this.failures = 0;
30
- this.lastFailureTime = null;
31
- this.successes = 0;
32
- }
33
-
34
- /**
35
- * Execute operation with circuit breaker protection
36
- * @param {Function} operation - Async function to execute
37
- * @returns {Promise<any>} - Result of operation
38
- */
39
- async execute(operation) {
40
- // Check if circuit is OPEN
41
- if (this.state === 'OPEN') {
42
- const timeSinceFailure = Date.now() - this.lastFailureTime;
43
-
44
- if (timeSinceFailure > this.resetTimeout) {
45
- // Try to recover
46
- this.state = 'HALF_OPEN';
47
- this.failures = 0;
48
- logger.info('Circuit breaker HALF_OPEN - testing recovery');
49
- } else {
50
- const waitTime = Math.round((this.resetTimeout - timeSinceFailure) / 1000);
51
- logger.warn('Circuit breaker OPEN - rejecting request', { waitTime });
52
- throw new Error(`Circuit breaker is OPEN. Try again in ${waitTime}s`);
53
- }
54
- }
55
-
56
- try {
57
- const result = await operation();
58
-
59
- // Success - reset counters if in HALF_OPEN
60
- if (this.state === 'HALF_OPEN') {
61
- this.state = 'CLOSED';
62
- this.failures = 0;
63
- logger.info('Circuit breaker CLOSED - service recovered');
64
- }
65
-
66
- this.successes++;
67
- return result;
68
- } catch (err) {
69
- this.failures++;
70
- this.lastFailureTime = Date.now();
71
-
72
- // Open circuit if threshold reached
73
- if (this.failures >= this.failureThreshold) {
74
- this.state = 'OPEN';
75
- logger.error('Circuit breaker OPEN - failure threshold reached', {
76
- failures: this.failures
77
- });
78
- }
79
-
80
- throw err;
81
- }
82
- }
83
-
84
- /**
85
- * Get current state
86
- * @returns {string} - CLOSED, OPEN, or HALF_OPEN
87
- */
88
- getState() {
89
- return this.state;
90
- }
91
-
92
- /**
93
- * Get stats
94
- * @returns {Object} - Statistics
95
- */
96
- getStats() {
97
- return {
98
- state: this.state,
99
- failures: this.failures,
100
- successes: this.successes,
101
- failureThreshold: this.failureThreshold,
102
- lastFailureTime: this.lastFailureTime
103
- };
104
- }
105
-
106
- /**
107
- * Reset circuit breaker
108
- */
109
- reset() {
110
- this.state = 'CLOSED';
111
- this.failures = 0;
112
- this.successes = 0;
113
- this.lastFailureTime = null;
114
- logger.info('Circuit breaker reset');
115
- }
116
- }
117
-
118
- module.exports = CircuitBreaker;
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * EZ Circuit Breaker — Prevent cascading failures
5
+ *
6
+ * Implements circuit breaker pattern:
7
+ * - CLOSED: Normal operation
8
+ * - OPEN: Failing, reject requests
9
+ * - HALF_OPEN: Testing if service recovered
10
+ *
11
+ * Usage:
12
+ * const CircuitBreaker = require('./circuit-breaker.cjs');
13
+ * const breaker = new CircuitBreaker({ failureThreshold: 5 });
14
+ * const result = await breaker.execute(() => riskyOperation());
15
+ */
16
+
17
+ const Logger = require('./logger.cjs');
18
+ const logger = new Logger();
19
+
20
+ class CircuitBreaker {
21
+ /**
22
+ * Create circuit breaker
23
+ * @param {Object} options - Configuration
24
+ */
25
+ constructor(options = {}) {
26
+ this.failureThreshold = options.failureThreshold || 5;
27
+ this.resetTimeout = options.resetTimeout || 60000;
28
+ this.state = 'CLOSED';
29
+ this.failures = 0;
30
+ this.lastFailureTime = null;
31
+ this.successes = 0;
32
+ }
33
+
34
+ /**
35
+ * Execute operation with circuit breaker protection
36
+ * @param {Function} operation - Async function to execute
37
+ * @returns {Promise<any>} - Result of operation
38
+ */
39
+ async execute(operation) {
40
+ // Check if circuit is OPEN
41
+ if (this.state === 'OPEN') {
42
+ const timeSinceFailure = Date.now() - this.lastFailureTime;
43
+
44
+ if (timeSinceFailure > this.resetTimeout) {
45
+ // Try to recover
46
+ this.state = 'HALF_OPEN';
47
+ this.failures = 0;
48
+ logger.info('Circuit breaker HALF_OPEN - testing recovery');
49
+ } else {
50
+ const waitTime = Math.round((this.resetTimeout - timeSinceFailure) / 1000);
51
+ logger.warn('Circuit breaker OPEN - rejecting request', { waitTime });
52
+ throw new Error(`Circuit breaker is OPEN. Try again in ${waitTime}s`);
53
+ }
54
+ }
55
+
56
+ try {
57
+ const result = await operation();
58
+
59
+ // Success - reset counters if in HALF_OPEN
60
+ if (this.state === 'HALF_OPEN') {
61
+ this.state = 'CLOSED';
62
+ this.failures = 0;
63
+ logger.info('Circuit breaker CLOSED - service recovered');
64
+ }
65
+
66
+ this.successes++;
67
+ return result;
68
+ } catch (err) {
69
+ this.failures++;
70
+ this.lastFailureTime = Date.now();
71
+
72
+ // Open circuit if threshold reached
73
+ if (this.failures >= this.failureThreshold) {
74
+ this.state = 'OPEN';
75
+ logger.error('Circuit breaker OPEN - failure threshold reached', {
76
+ failures: this.failures
77
+ });
78
+ }
79
+
80
+ throw err;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Get current state
86
+ * @returns {string} - CLOSED, OPEN, or HALF_OPEN
87
+ */
88
+ getState() {
89
+ return this.state;
90
+ }
91
+
92
+ /**
93
+ * Get stats
94
+ * @returns {Object} - Statistics
95
+ */
96
+ getStats() {
97
+ return {
98
+ state: this.state,
99
+ failures: this.failures,
100
+ successes: this.successes,
101
+ failureThreshold: this.failureThreshold,
102
+ lastFailureTime: this.lastFailureTime
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Reset circuit breaker
108
+ */
109
+ reset() {
110
+ this.state = 'CLOSED';
111
+ this.failures = 0;
112
+ this.successes = 0;
113
+ this.lastFailureTime = null;
114
+ logger.info('Circuit breaker reset');
115
+ }
116
+ }
117
+
118
+ module.exports = CircuitBreaker;
@@ -3,9 +3,9 @@
3
3
  */
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
- const { execSync } = require('child_process');
7
6
  const { safeReadFile, loadConfig, isGitIgnored, execGit, normalizePhaseName, comparePhaseNum, getArchivedPhaseDirs, generateSlugInternal, getMilestoneInfo, resolveModelInternal, MODEL_PROFILES, toPosixPath, output, error, findPhaseInternal } = require('./core.cjs');
8
7
  const { extractFrontmatter } = require('./frontmatter.cjs');
8
+ const { defaultLogger: logger } = require('./logger.cjs');
9
9
 
10
10
  function cmdGenerateSlug(text, raw) {
11
11
  if (!text) {
@@ -70,9 +70,13 @@ function cmdListTodos(cwd, area, raw) {
70
70
  area: todoArea,
71
71
  path: toPosixPath(path.join('.planning', 'todos', 'pending', file)),
72
72
  });
73
- } catch {}
73
+ } catch (err) {
74
+ logger.warn('Failed to parse todo file in cmdListTodos', { file, error: err.message });
75
+ }
74
76
  }
75
- } catch {}
77
+ } catch (err) {
78
+ logger.warn('Failed to list pending todos in cmdListTodos', { pendingDir, error: err.message });
79
+ }
76
80
 
77
81
  const result = { count, todos };
78
82
  output(result, raw, count.toString());
@@ -90,7 +94,8 @@ function cmdVerifyPathExists(cwd, targetPath, raw) {
90
94
  const type = stats.isDirectory() ? 'directory' : stats.isFile() ? 'file' : 'other';
91
95
  const result = { exists: true, type };
92
96
  output(result, raw, 'true');
93
- } catch {
97
+ } catch (err) {
98
+ logger.warn('Path verification failed in cmdVerifyPathExists', { fullPath, error: err.message });
94
99
  const result = { exists: false, type: null };
95
100
  output(result, raw, 'false');
96
101
  }
@@ -119,7 +124,9 @@ function cmdHistoryDigest(cwd, raw) {
119
124
  for (const dir of currentDirs) {
120
125
  allPhaseDirs.push({ name: dir, fullPath: path.join(phasesDir, dir), milestone: null });
121
126
  }
122
- } catch {}
127
+ } catch (err) {
128
+ logger.warn('Failed to enumerate current phase directories in cmdHistoryDigest', { phasesDir, error: err.message });
129
+ }
123
130
  }
124
131
 
125
132
  if (allPhaseDirs.length === 0) {
@@ -177,8 +184,8 @@ function cmdHistoryDigest(cwd, raw) {
177
184
  fm['tech-stack'].added.forEach(t => digest.tech_stack.add(typeof t === 'string' ? t : t.name));
178
185
  }
179
186
 
180
- } catch (e) {
181
- // Skip malformed summaries
187
+ } catch (err) {
188
+ logger.warn('Skipping malformed summary in cmdHistoryDigest', { summary, dirPath, error: err.message });
182
189
  }
183
190
  }
184
191
  }
@@ -192,8 +199,9 @@ function cmdHistoryDigest(cwd, raw) {
192
199
  digest.tech_stack = [...digest.tech_stack];
193
200
 
194
201
  output(digest, raw);
195
- } catch (e) {
196
- error('Failed to generate history digest: ' + e.message);
202
+ } catch (err) {
203
+ logger.error('Failed to generate history digest', { error: err.message });
204
+ error('Failed to generate history digest: ' + err.message);
197
205
  }
198
206
  }
199
207
 
@@ -213,7 +221,7 @@ function cmdResolveModel(cwd, agentType, raw) {
213
221
  output(result, raw, model);
214
222
  }
215
223
 
216
- function cmdCommit(cwd, message, files, raw, amend) {
224
+ async function cmdCommit(cwd, message, files, raw, amend) {
217
225
  if (!message && !amend) {
218
226
  error('commit message required');
219
227
  }
@@ -228,7 +236,7 @@ function cmdCommit(cwd, message, files, raw, amend) {
228
236
  }
229
237
 
230
238
  // Check if .planning is gitignored
231
- if (isGitIgnored(cwd, '.planning')) {
239
+ if (await isGitIgnored(cwd, '.planning')) {
232
240
  const result = { committed: false, hash: null, reason: 'skipped_gitignored' };
233
241
  output(result, raw, 'skipped');
234
242
  return;
@@ -237,12 +245,12 @@ function cmdCommit(cwd, message, files, raw, amend) {
237
245
  // Stage files
238
246
  const filesToStage = files && files.length > 0 ? files : ['.planning/'];
239
247
  for (const file of filesToStage) {
240
- execGit(cwd, ['add', file]);
248
+ await execGit(cwd, ['add', file]);
241
249
  }
242
250
 
243
251
  // Commit
244
252
  const commitArgs = amend ? ['commit', '--amend', '--no-edit'] : ['commit', '-m', message];
245
- const commitResult = execGit(cwd, commitArgs);
253
+ const commitResult = await execGit(cwd, commitArgs);
246
254
  if (commitResult.exitCode !== 0) {
247
255
  if (commitResult.stdout.includes('nothing to commit') || commitResult.stderr.includes('nothing to commit')) {
248
256
  const result = { committed: false, hash: null, reason: 'nothing_to_commit' };
@@ -255,7 +263,7 @@ function cmdCommit(cwd, message, files, raw, amend) {
255
263
  }
256
264
 
257
265
  // Get short hash
258
- const hashResult = execGit(cwd, ['rev-parse', '--short', 'HEAD']);
266
+ const hashResult = await execGit(cwd, ['rev-parse', '--short', 'HEAD']);
259
267
  const hash = hashResult.exitCode === 0 ? hashResult.stdout : null;
260
268
  const result = { committed: true, hash, reason: 'committed' };
261
269
  output(result, raw, hash || 'committed');
@@ -375,6 +383,7 @@ async function cmdWebsearch(query, options, raw) {
375
383
  results
376
384
  }, raw, results.map(r => `${r.title}\n${r.url}\n${r.description}`).join('\n\n'));
377
385
  } catch (err) {
386
+ logger.warn('Websearch request failed in cmdWebsearch', { query, error: err.message });
378
387
  output({ available: false, error: err.message }, raw, '');
379
388
  }
380
389
  }
@@ -411,7 +420,9 @@ function cmdProgressRender(cwd, format, raw) {
411
420
 
412
421
  phases.push({ number: phaseNum, name: phaseName, plans, summaries, status });
413
422
  }
414
- } catch {}
423
+ } catch (err) {
424
+ logger.warn('Failed to enumerate phase directories in cmdProgressRender', { phasesDir, error: err.message });
425
+ }
415
426
 
416
427
  const percent = totalPlans > 0 ? Math.min(100, Math.round((totalSummaries / totalPlans) * 100)) : 0;
417
428
 
@@ -492,7 +503,7 @@ function cmdScaffold(cwd, type, options, raw) {
492
503
  switch (type) {
493
504
  case 'context': {
494
505
  filePath = path.join(phaseDir, `${padded}-CONTEXT.md`);
495
- content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.phase_name || 'Unnamed'}"\ncreated: ${today}\n---\n\n# Phase ${phase}: ${name || phaseInfo?.phase_name || 'Unnamed'} — Context\n\n## Decisions\n\n_Decisions will be captured during /gsd:discuss-phase ${phase}_\n\n## Discretion Areas\n\n_Areas where the executor can use judgment_\n\n## Deferred Ideas\n\n_Ideas to consider later_\n`;
506
+ content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.phase_name || 'Unnamed'}"\ncreated: ${today}\n---\n\n# Phase ${phase}: ${name || phaseInfo?.phase_name || 'Unnamed'} — Context\n\n## Decisions\n\n_Decisions will be captured during /ez-discuss-phase ${phase}_\n\n## Discretion Areas\n\n_Areas where the executor can use judgment_\n\n## Deferred Ideas\n\n_Ideas to consider later_\n`;
496
507
  break;
497
508
  }
498
509
  case 'uat': {
@@ -532,7 +543,7 @@ function cmdScaffold(cwd, type, options, raw) {
532
543
  output({ created: true, path: relPath }, raw, relPath);
533
544
  }
534
545
 
535
- function cmdStats(cwd, format, raw) {
546
+ async function cmdStats(cwd, format, raw) {
536
547
  const phasesDir = path.join(cwd, '.planning', 'phases');
537
548
  const reqPath = path.join(cwd, '.planning', 'REQUIREMENTS.md');
538
549
  const statePath = path.join(cwd, '.planning', 'STATE.md');
@@ -566,7 +577,9 @@ function cmdStats(cwd, format, raw) {
566
577
 
567
578
  phases.push({ number: phaseNum, name: phaseName, plans, summaries, status });
568
579
  }
569
- } catch {}
580
+ } catch (err) {
581
+ logger.warn('Failed to enumerate phase directories in cmdStats', { phasesDir, error: err.message });
582
+ }
570
583
 
571
584
  const percent = totalPlans > 0 ? Math.min(100, Math.round((totalSummaries / totalPlans) * 100)) : 0;
572
585
 
@@ -581,7 +594,9 @@ function cmdStats(cwd, format, raw) {
581
594
  requirementsComplete = checked ? checked.length : 0;
582
595
  requirementsTotal = requirementsComplete + (unchecked ? unchecked.length : 0);
583
596
  }
584
- } catch {}
597
+ } catch (err) {
598
+ logger.warn('Failed to parse REQUIREMENTS.md in cmdStats', { reqPath, error: err.message });
599
+ }
585
600
 
586
601
  // Last activity from STATE.md
587
602
  let lastActivity = null;
@@ -591,17 +606,21 @@ function cmdStats(cwd, format, raw) {
591
606
  const activityMatch = stateContent.match(/\*\*Last Activity:\*\*\s*(.+)/);
592
607
  if (activityMatch) lastActivity = activityMatch[1].trim();
593
608
  }
594
- } catch {}
609
+ } catch (err) {
610
+ logger.warn('Failed to read STATE.md in cmdStats', { statePath, error: err.message });
611
+ }
595
612
 
596
613
  // Git stats
597
614
  let gitCommits = 0;
598
615
  let gitFirstCommitDate = null;
599
616
  try {
600
- const commitCount = execGit(cwd, ['rev-list', '--count', 'HEAD']);
617
+ const commitCount = await execGit(cwd, ['rev-list', '--count', 'HEAD']);
601
618
  gitCommits = parseInt(commitCount.trim(), 10) || 0;
602
- const firstDate = execGit(cwd, ['log', '--reverse', '--format=%as', '--max-count=1']);
619
+ const firstDate = await execGit(cwd, ['log', '--reverse', '--format=%as', '--max-count=1']);
603
620
  gitFirstCommitDate = firstDate.trim() || null;
604
- } catch {}
621
+ } catch (err) {
622
+ logger.warn('Failed to compute git stats in cmdStats', { cwd, error: err.message });
623
+ }
605
624
 
606
625
  const completedPhases = phases.filter(p => p.status === 'Complete').length;
607
626