@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.
- package/README.md +295 -714
- package/bin/install.js +387 -62
- package/commands/ez/auth.md +87 -0
- package/commands/ez/join-discord.md +1 -1
- package/ez-agents/bin/ez-tools.cjs +120 -2
- package/ez-agents/bin/lib/assistant-adapter.cjs +62 -3
- package/ez-agents/bin/lib/audit-exec.cjs +20 -8
- package/ez-agents/bin/lib/auth.cjs +2 -1
- package/ez-agents/bin/lib/circuit-breaker.cjs +1 -1
- package/ez-agents/bin/lib/commands.cjs +42 -23
- package/ez-agents/bin/lib/config.cjs +18 -11
- package/ez-agents/bin/lib/core.cjs +42 -25
- package/ez-agents/bin/lib/file-lock.cjs +3 -3
- package/ez-agents/bin/lib/fs-utils.cjs +1 -1
- package/ez-agents/bin/lib/git-utils.cjs +1 -1
- package/ez-agents/bin/lib/health-check.cjs +2 -3
- package/ez-agents/bin/lib/index.cjs +1 -1
- package/ez-agents/bin/lib/init.cjs +70 -23
- package/ez-agents/bin/lib/logger.cjs +11 -4
- package/ez-agents/bin/lib/model-provider.cjs +124 -29
- package/ez-agents/bin/lib/phase.cjs +39 -22
- package/ez-agents/bin/lib/planning-write.cjs +107 -0
- package/ez-agents/bin/lib/retry.cjs +1 -1
- package/ez-agents/bin/lib/roadmap.cjs +3 -2
- package/ez-agents/bin/lib/safe-exec.cjs +1 -1
- package/ez-agents/bin/lib/safe-path.cjs +1 -1
- package/ez-agents/bin/lib/state.cjs +24 -9
- package/ez-agents/bin/lib/temp-file.cjs +1 -1
- package/ez-agents/bin/lib/template.cjs +2 -1
- package/ez-agents/bin/lib/test-file-lock.cjs +1 -1
- package/ez-agents/bin/lib/test-graceful.cjs +2 -2
- package/ez-agents/bin/lib/test-logger.cjs +2 -2
- package/ez-agents/bin/lib/test-temp-file.cjs +1 -1
- package/ez-agents/bin/lib/timeout-exec.cjs +4 -3
- package/ez-agents/bin/lib/verify.cjs +54 -25
- package/ez-agents/references/continuation-format.md +1 -1
- package/ez-agents/workflows/add-tests.md +2 -2
- package/ez-agents/workflows/add-todo.md +1 -1
- package/ez-agents/workflows/autonomous.md +15 -15
- package/ez-agents/workflows/diagnose-issues.md +1 -1
- package/ez-agents/workflows/discuss-phase.md +3 -3
- package/ez-agents/workflows/execute-phase.md +2 -2
- package/ez-agents/workflows/health.md +1 -1
- package/ez-agents/workflows/help.md +2 -2
- package/ez-agents/workflows/map-codebase.md +1 -1
- package/ez-agents/workflows/new-milestone.md +5 -5
- package/ez-agents/workflows/new-project.md +12 -10
- package/ez-agents/workflows/plan-phase.md +8 -8
- package/ez-agents/workflows/progress.md +1 -1
- package/ez-agents/workflows/set-profile.md +1 -1
- package/ez-agents/workflows/settings.md +9 -9
- package/ez-agents/workflows/stats.md +1 -1
- package/ez-agents/workflows/ui-phase.md +3 -3
- package/ez-agents/workflows/ui-review.md +2 -2
- package/ez-agents/workflows/update.md +1 -1
- package/ez-agents/workflows/validate-phase.md +3 -3
- package/ez-agents/workflows/verify-work.md +3 -3
- package/package.json +1 -1
- package/scripts/build-hooks.js +1 -1
- package/scripts/fix-qwen-installation.js +144 -0
- 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 {
|
|
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: '
|
|
79
|
-
milestone_branch_template: '
|
|
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 {
|
|
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
|
-
|
|
150
|
+
const safePath = targetPath.replace(/[^a-zA-Z0-9._\-/]/g, '');
|
|
151
|
+
await auditExec('git', ['check-ignore', '-q', '--no-index', '--', safePath], {
|
|
144
152
|
cwd,
|
|
145
|
-
|
|
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
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
*
|
|
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,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* EZ Health Check — Health monitoring for EZ workflow
|
|
5
5
|
*
|
|
6
|
-
* Validates
|
|
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
|
/**
|
|
@@ -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
|
|
168
|
-
|
|
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
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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 =
|
|
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:
|
|
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
|
-
*
|
|
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(`[
|
|
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(`[
|
|
79
|
+
console.error(`[EZ ${level}] ${message} (file write failed: ${err.message})`);
|
|
73
80
|
}
|
|
74
81
|
}
|
|
75
82
|
|