@howlil/ez-agents 3.1.0 → 3.4.1

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 (61) hide show
  1. package/README.md +295 -714
  2. package/bin/install.js +387 -62
  3. package/commands/ez/auth.md +87 -0
  4. package/commands/ez/join-discord.md +1 -1
  5. package/ez-agents/bin/ez-tools.cjs +120 -2
  6. package/ez-agents/bin/lib/assistant-adapter.cjs +62 -3
  7. package/ez-agents/bin/lib/audit-exec.cjs +20 -8
  8. package/ez-agents/bin/lib/auth.cjs +2 -1
  9. package/ez-agents/bin/lib/circuit-breaker.cjs +1 -1
  10. package/ez-agents/bin/lib/commands.cjs +42 -23
  11. package/ez-agents/bin/lib/config.cjs +18 -11
  12. package/ez-agents/bin/lib/core.cjs +42 -25
  13. package/ez-agents/bin/lib/file-lock.cjs +3 -3
  14. package/ez-agents/bin/lib/fs-utils.cjs +1 -1
  15. package/ez-agents/bin/lib/git-utils.cjs +1 -1
  16. package/ez-agents/bin/lib/health-check.cjs +2 -3
  17. package/ez-agents/bin/lib/index.cjs +1 -1
  18. package/ez-agents/bin/lib/init.cjs +70 -23
  19. package/ez-agents/bin/lib/logger.cjs +11 -4
  20. package/ez-agents/bin/lib/model-provider.cjs +124 -29
  21. package/ez-agents/bin/lib/phase.cjs +39 -22
  22. package/ez-agents/bin/lib/planning-write.cjs +107 -0
  23. package/ez-agents/bin/lib/retry.cjs +1 -1
  24. package/ez-agents/bin/lib/roadmap.cjs +3 -2
  25. package/ez-agents/bin/lib/safe-exec.cjs +1 -1
  26. package/ez-agents/bin/lib/safe-path.cjs +1 -1
  27. package/ez-agents/bin/lib/state.cjs +24 -9
  28. package/ez-agents/bin/lib/temp-file.cjs +1 -1
  29. package/ez-agents/bin/lib/template.cjs +2 -1
  30. package/ez-agents/bin/lib/test-file-lock.cjs +1 -1
  31. package/ez-agents/bin/lib/test-graceful.cjs +2 -2
  32. package/ez-agents/bin/lib/test-logger.cjs +2 -2
  33. package/ez-agents/bin/lib/test-temp-file.cjs +1 -1
  34. package/ez-agents/bin/lib/timeout-exec.cjs +4 -3
  35. package/ez-agents/bin/lib/verify.cjs +54 -25
  36. package/ez-agents/references/continuation-format.md +1 -1
  37. package/ez-agents/workflows/add-tests.md +2 -2
  38. package/ez-agents/workflows/add-todo.md +1 -1
  39. package/ez-agents/workflows/autonomous.md +15 -15
  40. package/ez-agents/workflows/diagnose-issues.md +1 -1
  41. package/ez-agents/workflows/discuss-phase.md +3 -3
  42. package/ez-agents/workflows/execute-phase.md +2 -2
  43. package/ez-agents/workflows/health.md +1 -1
  44. package/ez-agents/workflows/help.md +2 -2
  45. package/ez-agents/workflows/map-codebase.md +1 -1
  46. package/ez-agents/workflows/new-milestone.md +5 -5
  47. package/ez-agents/workflows/new-project.md +12 -10
  48. package/ez-agents/workflows/plan-phase.md +8 -8
  49. package/ez-agents/workflows/progress.md +1 -1
  50. package/ez-agents/workflows/set-profile.md +1 -1
  51. package/ez-agents/workflows/settings.md +9 -9
  52. package/ez-agents/workflows/stats.md +1 -1
  53. package/ez-agents/workflows/ui-phase.md +3 -3
  54. package/ez-agents/workflows/ui-review.md +2 -2
  55. package/ez-agents/workflows/update.md +1 -1
  56. package/ez-agents/workflows/validate-phase.md +3 -3
  57. package/ez-agents/workflows/verify-work.md +3 -3
  58. package/package.json +1 -1
  59. package/scripts/build-hooks.js +1 -1
  60. package/scripts/fix-qwen-installation.js +144 -0
  61. package/README.zh-CN.md +0 -702
@@ -4,7 +4,8 @@
4
4
 
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
- const { execSync } = require('child_process');
7
+ const { safeExec, auditExec } = require('./safe-exec.cjs');
8
+ const { defaultLogger: logger } = require('./logger.cjs');
8
9
 
9
10
  // ─── Path helpers ────────────────────────────────────────────────────────────
10
11
 
@@ -63,7 +64,8 @@ function error(message) {
63
64
  function safeReadFile(filePath) {
64
65
  try {
65
66
  return fs.readFileSync(filePath, 'utf-8');
66
- } catch {
67
+ } catch (err) {
68
+ logger.warn('safeReadFile failed', { filePath, error: err.message });
67
69
  return null;
68
70
  }
69
71
  }
@@ -75,8 +77,8 @@ function loadConfig(cwd) {
75
77
  commit_docs: true,
76
78
  search_gitignored: false,
77
79
  branching_strategy: 'none',
78
- phase_branch_template: 'gsd/phase-{phase}-{slug}',
79
- milestone_branch_template: 'gsd/{milestone}-{slug}',
80
+ phase_branch_template: 'ez/phase-{phase}-{slug}',
81
+ milestone_branch_template: 'ez/{milestone}-{slug}',
80
82
  research: true,
81
83
  plan_checker: true,
82
84
  verifier: true,
@@ -94,7 +96,11 @@ function loadConfig(cwd) {
94
96
  const depthToGranularity = { quick: 'coarse', standard: 'standard', comprehensive: 'fine' };
95
97
  parsed.granularity = depthToGranularity[parsed.depth] || parsed.depth;
96
98
  delete parsed.depth;
97
- try { fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2), 'utf-8'); } catch {}
99
+ try {
100
+ fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2), 'utf-8');
101
+ } catch (err) {
102
+ logger.warn('Failed to persist migrated config depth->granularity', { configPath, error: err.message });
103
+ }
98
104
  }
99
105
 
100
106
  const get = (key, nested) => {
@@ -127,42 +133,43 @@ function loadConfig(cwd) {
127
133
  brave_search: get('brave_search') ?? defaults.brave_search,
128
134
  model_overrides: parsed.model_overrides || null,
129
135
  };
130
- } catch {
136
+ } catch (err) {
137
+ logger.warn('Failed to load config, using defaults', { configPath, error: err.message });
131
138
  return defaults;
132
139
  }
133
140
  }
134
141
 
135
142
  // ─── Git utilities ────────────────────────────────────────────────────────────
136
143
 
137
- function isGitIgnored(cwd, targetPath) {
144
+ async function isGitIgnored(cwd, targetPath) {
138
145
  try {
139
146
  // --no-index checks .gitignore rules regardless of whether the file is tracked.
140
147
  // Without it, git check-ignore returns "not ignored" for tracked files even when
141
148
  // .gitignore explicitly lists them — a common source of confusion when .planning/
142
149
  // was committed before being added to .gitignore.
143
- execSync('git check-ignore -q --no-index -- ' + targetPath.replace(/[^a-zA-Z0-9._\-/]/g, ''), {
150
+ const safePath = targetPath.replace(/[^a-zA-Z0-9._\-/]/g, '');
151
+ await auditExec('git', ['check-ignore', '-q', '--no-index', '--', safePath], {
144
152
  cwd,
145
- stdio: 'pipe',
153
+ context: 'isGitIgnored',
154
+ timeout: 5000
146
155
  });
147
156
  return true;
148
- } catch {
157
+ } catch (err) {
158
+ logger.warn('git check-ignore failed, assuming not ignored', { targetPath, error: err.message });
149
159
  return false;
150
160
  }
151
161
  }
152
162
 
153
- function execGit(cwd, args) {
163
+ async function execGit(cwd, args) {
154
164
  try {
155
- const escaped = args.map(a => {
156
- if (/^[a-zA-Z0-9._\-/=:@]+$/.test(a)) return a;
157
- return "'" + a.replace(/'/g, "'\\''") + "'";
158
- });
159
- const stdout = execSync('git ' + escaped.join(' '), {
165
+ const stdout = await auditExec('git', args, {
160
166
  cwd,
161
- stdio: 'pipe',
162
- encoding: 'utf-8',
167
+ context: 'execGit',
168
+ timeout: 30000
163
169
  });
164
170
  return { exitCode: 0, stdout: stdout.trim(), stderr: '' };
165
171
  } catch (err) {
172
+ logger.warn('execGit failed', { args, error: err.message });
166
173
  return {
167
174
  exitCode: err.status ?? 1,
168
175
  stdout: (err.stdout ?? '').toString().trim(),
@@ -254,7 +261,8 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
254
261
  has_context: hasContext,
255
262
  has_verification: hasVerification,
256
263
  };
257
- } catch {
264
+ } catch (err) {
265
+ logger.warn('Failed to search phase directory', { baseDir, normalized, error: err.message });
258
266
  return null;
259
267
  }
260
268
  }
@@ -291,7 +299,9 @@ function findPhaseInternal(cwd, phase) {
291
299
  return result;
292
300
  }
293
301
  }
294
- } catch {}
302
+ } catch (err) {
303
+ logger.warn('Failed while searching archived milestone phases', { milestonesDir, error: err.message });
304
+ }
295
305
 
296
306
  return null;
297
307
  }
@@ -326,7 +336,9 @@ function getArchivedPhaseDirs(cwd) {
326
336
  });
327
337
  }
328
338
  }
329
- } catch {}
339
+ } catch (err) {
340
+ logger.warn('Failed to enumerate archived phase directories', { milestonesDir, error: err.message });
341
+ }
330
342
 
331
343
  return results;
332
344
  }
@@ -362,7 +374,8 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
362
374
  goal,
363
375
  section,
364
376
  };
365
- } catch {
377
+ } catch (err) {
378
+ logger.warn('Failed to read roadmap phase metadata', { roadmapPath, phaseNum, error: err.message });
366
379
  return null;
367
380
  }
368
381
  }
@@ -391,7 +404,8 @@ function pathExistsInternal(cwd, targetPath) {
391
404
  try {
392
405
  fs.statSync(fullPath);
393
406
  return true;
394
- } catch {
407
+ } catch (err) {
408
+ logger.warn('Path existence check failed', { fullPath, error: err.message });
395
409
  return false;
396
410
  }
397
411
  }
@@ -431,7 +445,8 @@ function getMilestoneInfo(cwd) {
431
445
  version: versionMatch ? versionMatch[0] : 'v1.0',
432
446
  name: 'milestone',
433
447
  };
434
- } catch {
448
+ } catch (err) {
449
+ logger.warn('Failed to load milestone info, using fallback', { error: err.message });
435
450
  return { version: 'v1.0', name: 'milestone' };
436
451
  }
437
452
  }
@@ -450,7 +465,9 @@ function getMilestonePhaseFilter(cwd) {
450
465
  while ((m = phasePattern.exec(roadmap)) !== null) {
451
466
  milestonePhaseNums.add(m[1]);
452
467
  }
453
- } catch {}
468
+ } catch (err) {
469
+ logger.warn('Failed to parse milestone phases from roadmap', { error: err.message });
470
+ }
454
471
 
455
472
  if (milestonePhaseNums.size === 0) {
456
473
  const passAll = () => true;
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * GSD File Lock — File locking utility for concurrent write protection
4
+ * EZ File Lock — File locking utility for concurrent write protection
5
5
  *
6
6
  * Uses proper-lockfile to prevent concurrent writes to planning files
7
7
  * Includes deadlock detection (30s timeout) and automatic lock cleanup
@@ -82,11 +82,11 @@ async function simpleLock(filePath, options = {}) {
82
82
  }
83
83
  }
84
84
 
85
- // Try to create lock file
85
+ // Try to create lock file (using 'wx' flag to fail if it exists)
86
86
  fs.writeFileSync(lockPath, JSON.stringify({
87
87
  pid: process.pid,
88
88
  timestamp: Date.now()
89
- }), 'utf-8');
89
+ }), { encoding: 'utf-8', flag: 'wx' });
90
90
 
91
91
  lockHolders.set(filePath, lockPath);
92
92
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * GSD FS Utils — Cross-platform file system utilities
4
+ * EZ FS Utils — Cross-platform file system utilities
5
5
  *
6
6
  * Replaces Unix commands (find, grep, head, tail) with
7
7
  * cross-platform JavaScript implementations.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * GSD Git Utils — Safe git operations with atomic commits
4
+ * EZ Git Utils — Safe git operations with atomic commits
5
5
  *
6
6
  * Provides:
7
7
  * - Atomic commits with validation
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * GSD Health Check — Health monitoring for GSD workflow
4
+ * EZ Health Check — Health monitoring for EZ workflow
5
5
  *
6
- * Validates GSD environment and configuration
6
+ * Validates EZ environment and configuration
7
7
  * Used by workflows to detect failures and use fallback functions
8
8
  *
9
9
  * Usage:
@@ -14,7 +14,6 @@
14
14
 
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
- const { execSync } = require('child_process');
18
17
 
19
18
  class HealthCheck {
20
19
  /**
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * GSD Lib Index — Central export for all GSD libraries
4
+ * EZ Lib Index — Central export for all EZ libraries
5
5
  *
6
6
  * Provides single import point for all utility modules.
7
7
  *
@@ -4,8 +4,10 @@
4
4
 
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
- const { execSync } = require('child_process');
8
7
  const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, normalizePhaseName, toPosixPath, output, error } = require('./core.cjs');
8
+ const { defaultLogger: logger } = require('./logger.cjs');
9
+ const { findFiles } = require('./fs-utils.cjs');
10
+ const { execWithTimeout } = require('./timeout-exec.cjs');
9
11
 
10
12
  function cmdInitExecutePhase(cwd, phase, raw) {
11
13
  if (!phase) {
@@ -153,31 +155,44 @@ function cmdInitPlanPhase(cwd, phase, raw) {
153
155
  if (uatFile) {
154
156
  result.uat_path = toPosixPath(path.join(phaseInfo.directory, uatFile));
155
157
  }
156
- } catch {}
158
+ } catch (err) {
159
+ logger.warn('Failed to inspect phase artifacts in cmdInitPlanPhase', { phaseDirFull, error: err.message });
160
+ }
157
161
  }
158
162
 
159
163
  output(result, raw);
160
164
  }
161
165
 
162
- function cmdInitNewProject(cwd, raw) {
166
+ async function cmdInitNewProject(cwd, raw) {
163
167
  const config = loadConfig(cwd);
164
168
 
165
- // Detect Brave Search API key availability
169
+ // Detect Brave Search API key availability (prefer ~/.ez)
166
170
  const homedir = require('os').homedir();
167
- const braveKeyFile = path.join(homedir, '.gsd', 'brave_api_key');
168
- const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile));
171
+ const braveKeyCandidates = [
172
+ path.join(homedir, '.ez', 'brave_api_key'),
173
+ ];
174
+ const hasBraveSearch = !!(process.env.BRAVE_API_KEY || braveKeyCandidates.some(p => fs.existsSync(p)));
169
175
 
170
176
  // Detect existing code
171
177
  let hasCode = false;
172
178
  let hasPackageFile = false;
173
179
  try {
174
- const files = execSync('find . -maxdepth 3 \\( -name "*.ts" -o -name "*.js" -o -name "*.py" -o -name "*.go" -o -name "*.rs" -o -name "*.swift" -o -name "*.java" \\) 2>/dev/null | grep -v node_modules | grep -v .git | head -5', {
175
- cwd,
176
- encoding: 'utf-8',
177
- stdio: ['pipe', 'pipe', 'pipe'],
180
+ const codeFiles = findFiles(cwd, [
181
+ /\.ts$/,
182
+ /\.js$/,
183
+ /\.py$/,
184
+ /\.go$/,
185
+ /\.rs$/,
186
+ /\.swift$/,
187
+ /\.java$/,
188
+ ], {
189
+ maxDepth: 3,
190
+ exclude: ['node_modules', '.git'],
178
191
  });
179
- hasCode = files.trim().length > 0;
180
- } catch {}
192
+ hasCode = codeFiles.length > 0;
193
+ } catch (err) {
194
+ logger.warn('Failed to detect existing source files in cmdInitNewProject', { cwd, error: err.message });
195
+ }
181
196
 
182
197
  hasPackageFile = pathExistsInternal(cwd, 'package.json') ||
183
198
  pathExistsInternal(cwd, 'requirements.txt') ||
@@ -185,6 +200,18 @@ function cmdInitNewProject(cwd, raw) {
185
200
  pathExistsInternal(cwd, 'go.mod') ||
186
201
  pathExistsInternal(cwd, 'Package.swift');
187
202
 
203
+ let hasGit = pathExistsInternal(cwd, '.git');
204
+ try {
205
+ const gitProbe = await execWithTimeout('git', ['rev-parse', '--is-inside-work-tree'], { timeout: 5000, fallback: '' });
206
+ if (gitProbe === '') {
207
+ logger.info('Fallback activated during init new-project git probe', { command: 'git rev-parse --is-inside-work-tree' });
208
+ } else {
209
+ hasGit = gitProbe.trim() === 'true' || hasGit;
210
+ }
211
+ } catch (err) {
212
+ logger.warn('Init new-project git probe failed without fallback', { error: err.message });
213
+ }
214
+
188
215
  const result = {
189
216
  // Models
190
217
  researcher_model: resolveModelInternal(cwd, 'ez-project-researcher'),
@@ -206,7 +233,7 @@ function cmdInitNewProject(cwd, raw) {
206
233
  needs_codebase_map: (hasCode || hasPackageFile) && !pathExistsInternal(cwd, '.planning/codebase'),
207
234
 
208
235
  // Git state
209
- has_git: pathExistsInternal(cwd, '.git'),
236
+ has_git: hasGit,
210
237
 
211
238
  // Enhanced search
212
239
  brave_search_available: hasBraveSearch,
@@ -307,7 +334,9 @@ function cmdInitResume(cwd, raw) {
307
334
  let interruptedAgentId = null;
308
335
  try {
309
336
  interruptedAgentId = fs.readFileSync(path.join(cwd, '.planning', 'current-agent-id.txt'), 'utf-8').trim();
310
- } catch {}
337
+ } catch (err) {
338
+ logger.warn('Failed to read current-agent-id marker in cmdInitResume', { cwd, error: err.message });
339
+ }
311
340
 
312
341
  const result = {
313
342
  // File existence
@@ -436,7 +465,9 @@ function cmdInitPhaseOp(cwd, phase, raw) {
436
465
  if (uatFile) {
437
466
  result.uat_path = toPosixPath(path.join(phaseInfo.directory, uatFile));
438
467
  }
439
- } catch {}
468
+ } catch (err) {
469
+ logger.warn('Failed to inspect phase artifacts in cmdInitPhaseOp', { phaseDirFull, error: err.message });
470
+ }
440
471
  }
441
472
 
442
473
  output(result, raw);
@@ -471,9 +502,13 @@ function cmdInitTodos(cwd, area, raw) {
471
502
  area: todoArea,
472
503
  path: '.planning/todos/pending/' + file,
473
504
  });
474
- } catch {}
505
+ } catch (err) {
506
+ logger.warn('Failed to parse todo file in cmdInitTodos', { file, error: err.message });
507
+ }
475
508
  }
476
- } catch {}
509
+ } catch (err) {
510
+ logger.warn('Failed to list pending todos in cmdInitTodos', { pendingDir, error: err.message });
511
+ }
477
512
 
478
513
  const result = {
479
514
  // Config
@@ -520,9 +555,13 @@ function cmdInitMilestoneOp(cwd, raw) {
520
555
  const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
521
556
  const hasSummary = phaseFiles.some(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
522
557
  if (hasSummary) completedPhases++;
523
- } catch {}
558
+ } catch (err) {
559
+ logger.warn('Failed to inspect phase directory in cmdInitMilestoneOp', { dir, error: err.message });
560
+ }
524
561
  }
525
- } catch {}
562
+ } catch (err) {
563
+ logger.warn('Failed to list phase directories in cmdInitMilestoneOp', { phasesDir, error: err.message });
564
+ }
526
565
 
527
566
  // Check archive
528
567
  const archiveDir = path.join(cwd, '.planning', 'archive');
@@ -531,7 +570,9 @@ function cmdInitMilestoneOp(cwd, raw) {
531
570
  archivedMilestones = fs.readdirSync(archiveDir, { withFileTypes: true })
532
571
  .filter(e => e.isDirectory())
533
572
  .map(e => e.name);
534
- } catch {}
573
+ } catch (err) {
574
+ logger.warn('Failed to list archived milestones in cmdInitMilestoneOp', { archiveDir, error: err.message });
575
+ }
535
576
 
536
577
  const result = {
537
578
  // Config
@@ -570,7 +611,9 @@ function cmdInitMapCodebase(cwd, raw) {
570
611
  let existingMaps = [];
571
612
  try {
572
613
  existingMaps = fs.readdirSync(codebaseDir).filter(f => f.endsWith('.md'));
573
- } catch {}
614
+ } catch (err) {
615
+ logger.warn('Failed to list codebase map files in cmdInitMapCodebase', { codebaseDir, error: err.message });
616
+ }
574
617
 
575
618
  const result = {
576
619
  // Models
@@ -646,7 +689,9 @@ function cmdInitProgress(cwd, raw) {
646
689
  nextPhase = phaseInfo;
647
690
  }
648
691
  }
649
- } catch {}
692
+ } catch (err) {
693
+ logger.warn('Failed to analyze phase progress in cmdInitProgress', { phasesDir, error: err.message });
694
+ }
650
695
 
651
696
  // Check for paused work
652
697
  let pausedAt = null;
@@ -654,7 +699,9 @@ function cmdInitProgress(cwd, raw) {
654
699
  const state = fs.readFileSync(path.join(cwd, '.planning', 'STATE.md'), 'utf-8');
655
700
  const pauseMatch = state.match(/\*\*Paused At:\*\*\s*(.+)/);
656
701
  if (pauseMatch) pausedAt = pauseMatch[1].trim();
657
- } catch {}
702
+ } catch (err) {
703
+ logger.warn('Failed to read paused state in cmdInitProgress', { cwd, error: err.message });
704
+ }
658
705
 
659
706
  const result = {
660
707
  // Models
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * GSD Logger — Centralized logging module for GSD workflow
4
+ * EZ Logger — Centralized logging module for EZ workflow
5
5
  *
6
6
  * Provides structured logging with levels (ERROR, WARN, INFO, DEBUG)
7
7
  * Writes to .planning/logs/ez-{timestamp}.log
@@ -24,7 +24,6 @@ class Logger {
24
24
  constructor(logDir = '.planning/logs') {
25
25
  this.logDir = logDir;
26
26
  this.logFile = null;
27
- this._ensureLogDir();
28
27
  }
29
28
 
30
29
  /**
@@ -42,6 +41,9 @@ class Logger {
42
41
  * @returns {string} - Path to log file
43
42
  */
44
43
  getLogFile() {
44
+ if (!this.logFile) {
45
+ this._ensureLogDir();
46
+ }
45
47
  return this.logFile;
46
48
  }
47
49
 
@@ -52,6 +54,11 @@ class Logger {
52
54
  * @param {Object} context - Additional context data
53
55
  */
54
56
  log(level, message, context = {}) {
57
+ // Ensure log directory exists before first write
58
+ if (!this.logFile) {
59
+ this._ensureLogDir();
60
+ }
61
+
55
62
  const entry = {
56
63
  timestamp: new Date().toISOString(),
57
64
  level,
@@ -65,11 +72,11 @@ class Logger {
65
72
 
66
73
  // Always output ERROR level to console for visibility
67
74
  if (level === 'ERROR') {
68
- console.error(`[GSD ${level}] ${message}`);
75
+ console.error(`[EZ ${level}] ${message}`);
69
76
  }
70
77
  } catch (err) {
71
78
  // Fallback: log to console if file write fails
72
- console.error(`[GSD ${level}] ${message} (file write failed: ${err.message})`);
79
+ console.error(`[EZ ${level}] ${message} (file write failed: ${err.message})`);
73
80
  }
74
81
  }
75
82