@snipcodeit/mgw 0.2.2 → 0.3.0
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 +55 -5
- package/commands/board/configure.md +205 -0
- package/commands/board/create.md +496 -0
- package/commands/board/show.md +221 -0
- package/commands/board/sync.md +417 -0
- package/commands/board/views.md +230 -0
- package/commands/board.md +23 -1543
- package/commands/milestone.md +5 -38
- package/commands/review.md +222 -42
- package/commands/run/execute.md +675 -0
- package/commands/run/pr-create.md +282 -0
- package/commands/run/triage.md +510 -0
- package/commands/run/worktree.md +54 -0
- package/commands/run.md +23 -1547
- package/commands/workflows/gsd.md +1 -13
- package/dist/bin/mgw.cjs +95 -6
- package/dist/{index-BiwU0uWA.cjs → index-s7v-ifd0.cjs} +669 -48
- package/dist/lib/index.cjs +185 -142
- package/package.json +5 -2
|
@@ -84,17 +84,21 @@ function requireState () {
|
|
|
84
84
|
return existing;
|
|
85
85
|
}
|
|
86
86
|
function migrateProjectState() {
|
|
87
|
+
const warnings = [];
|
|
87
88
|
const existing = loadProjectState();
|
|
88
|
-
if (!existing) return null;
|
|
89
|
+
if (!existing) return { state: null, warnings: [] };
|
|
89
90
|
let changed = false;
|
|
90
91
|
if (!existing.hasOwnProperty("active_gsd_milestone")) {
|
|
91
92
|
existing.active_gsd_milestone = null;
|
|
92
93
|
changed = true;
|
|
94
|
+
warnings.push("migration: added active_gsd_milestone field");
|
|
93
95
|
}
|
|
94
96
|
for (const m of existing.milestones || []) {
|
|
97
|
+
const mLabel = m.title || m.gsd_milestone_id || "unnamed";
|
|
95
98
|
if (!m.hasOwnProperty("gsd_milestone_id")) {
|
|
96
99
|
m.gsd_milestone_id = null;
|
|
97
100
|
changed = true;
|
|
101
|
+
warnings.push(`migration: added gsd_milestone_id to milestone "${mLabel}"`);
|
|
98
102
|
}
|
|
99
103
|
if (!m.hasOwnProperty("gsd_state")) {
|
|
100
104
|
m.gsd_state = null;
|
|
@@ -113,8 +117,9 @@ function requireState () {
|
|
|
113
117
|
let entries;
|
|
114
118
|
try {
|
|
115
119
|
entries = fs.readdirSync(activeDir);
|
|
116
|
-
} catch {
|
|
120
|
+
} catch (err) {
|
|
117
121
|
entries = [];
|
|
122
|
+
warnings.push(`migration: could not read active dir: ${err.message}`);
|
|
118
123
|
}
|
|
119
124
|
for (const file of entries) {
|
|
120
125
|
if (!file.endsWith(".json")) continue;
|
|
@@ -122,7 +127,8 @@ function requireState () {
|
|
|
122
127
|
let issueState;
|
|
123
128
|
try {
|
|
124
129
|
issueState = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
125
|
-
} catch {
|
|
130
|
+
} catch (err) {
|
|
131
|
+
warnings.push(`migration: skipping unreadable ${file}: ${err.message}`);
|
|
126
132
|
continue;
|
|
127
133
|
}
|
|
128
134
|
let issueChanged = false;
|
|
@@ -137,12 +143,13 @@ function requireState () {
|
|
|
137
143
|
if (issueChanged) {
|
|
138
144
|
try {
|
|
139
145
|
fs.writeFileSync(filePath, JSON.stringify(issueState, null, 2), "utf-8");
|
|
140
|
-
} catch {
|
|
146
|
+
} catch (err) {
|
|
147
|
+
warnings.push(`migration: failed to write ${file}: ${err.message}`);
|
|
141
148
|
}
|
|
142
149
|
}
|
|
143
150
|
}
|
|
144
151
|
}
|
|
145
|
-
return existing;
|
|
152
|
+
return { state: existing, warnings };
|
|
146
153
|
}
|
|
147
154
|
function resolveActiveMilestoneIndex(state) {
|
|
148
155
|
if (!state) return -1;
|
|
@@ -157,6 +164,159 @@ function requireState () {
|
|
|
157
164
|
}
|
|
158
165
|
return -1;
|
|
159
166
|
}
|
|
167
|
+
const VALID_LINK_TYPES = /* @__PURE__ */ new Set(["related", "implements", "tracks", "maps-to", "blocked-by"]);
|
|
168
|
+
function loadCrossRefs() {
|
|
169
|
+
const filePath = path.join(getMgwDir(), "cross-refs.json");
|
|
170
|
+
const warnings = [];
|
|
171
|
+
if (!fs.existsSync(filePath)) {
|
|
172
|
+
return { links: [], warnings: [] };
|
|
173
|
+
}
|
|
174
|
+
let raw;
|
|
175
|
+
try {
|
|
176
|
+
raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
177
|
+
} catch (err) {
|
|
178
|
+
return { links: [], warnings: [`cross-refs.json parse error: ${err.message}`] };
|
|
179
|
+
}
|
|
180
|
+
if (!raw || !Array.isArray(raw.links)) {
|
|
181
|
+
return { links: [], warnings: ["cross-refs.json missing links array"] };
|
|
182
|
+
}
|
|
183
|
+
const validLinks = [];
|
|
184
|
+
for (let i = 0; i < raw.links.length; i++) {
|
|
185
|
+
const link = raw.links[i];
|
|
186
|
+
const issues = [];
|
|
187
|
+
if (!link || typeof link !== "object") {
|
|
188
|
+
warnings.push(`cross-refs link[${i}]: not an object, skipping`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (typeof link.a !== "string" || !link.a) {
|
|
192
|
+
issues.push('missing "a"');
|
|
193
|
+
}
|
|
194
|
+
if (typeof link.b !== "string" || !link.b) {
|
|
195
|
+
issues.push('missing "b"');
|
|
196
|
+
}
|
|
197
|
+
if (link.type && !VALID_LINK_TYPES.has(link.type)) {
|
|
198
|
+
issues.push(`unknown type "${link.type}"`);
|
|
199
|
+
}
|
|
200
|
+
if (issues.length > 0) {
|
|
201
|
+
warnings.push(`cross-refs link[${i}]: ${issues.join(", ")}, skipping`);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
validLinks.push(link);
|
|
205
|
+
}
|
|
206
|
+
return { links: validLinks, warnings };
|
|
207
|
+
}
|
|
208
|
+
function parseDependencies(body) {
|
|
209
|
+
if (!body || typeof body !== "string") return [];
|
|
210
|
+
const deps = /* @__PURE__ */ new Set();
|
|
211
|
+
const linePattern = /(?:depends[\s-]*on|blocked[\s-]*by)[:\s]+([^\n]+)/gi;
|
|
212
|
+
let match;
|
|
213
|
+
while ((match = linePattern.exec(body)) !== null) {
|
|
214
|
+
const refs = match[1];
|
|
215
|
+
const numPattern = /#(\d+)/g;
|
|
216
|
+
let numMatch;
|
|
217
|
+
while ((numMatch = numPattern.exec(refs)) !== null) {
|
|
218
|
+
deps.add(parseInt(numMatch[1], 10));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return Array.from(deps).sort((a, b) => a - b);
|
|
222
|
+
}
|
|
223
|
+
function storeDependencies(issueNumber, dependsOn) {
|
|
224
|
+
if (!dependsOn || dependsOn.length === 0) return { added: 0, existing: 0 };
|
|
225
|
+
const crossRefsPath = path.join(getMgwDir(), "cross-refs.json");
|
|
226
|
+
let crossRefs = { links: [] };
|
|
227
|
+
if (fs.existsSync(crossRefsPath)) {
|
|
228
|
+
try {
|
|
229
|
+
crossRefs = JSON.parse(fs.readFileSync(crossRefsPath, "utf-8"));
|
|
230
|
+
if (!Array.isArray(crossRefs.links)) crossRefs.links = [];
|
|
231
|
+
} catch {
|
|
232
|
+
crossRefs = { links: [] };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
let added = 0;
|
|
236
|
+
let existing = 0;
|
|
237
|
+
const issueRef = `#${issueNumber}`;
|
|
238
|
+
for (const dep of dependsOn) {
|
|
239
|
+
const depRef = `#${dep}`;
|
|
240
|
+
const alreadyExists = crossRefs.links.some(
|
|
241
|
+
(l) => l.a === issueRef && l.b === depRef && l.type === "blocked-by"
|
|
242
|
+
);
|
|
243
|
+
if (alreadyExists) {
|
|
244
|
+
existing++;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
crossRefs.links.push({
|
|
248
|
+
a: issueRef,
|
|
249
|
+
b: depRef,
|
|
250
|
+
type: "blocked-by",
|
|
251
|
+
created: (/* @__PURE__ */ new Date()).toISOString()
|
|
252
|
+
});
|
|
253
|
+
added++;
|
|
254
|
+
}
|
|
255
|
+
if (added > 0) {
|
|
256
|
+
const mgwDir = getMgwDir();
|
|
257
|
+
if (!fs.existsSync(mgwDir)) {
|
|
258
|
+
fs.mkdirSync(mgwDir, { recursive: true });
|
|
259
|
+
}
|
|
260
|
+
fs.writeFileSync(crossRefsPath, JSON.stringify(crossRefs, null, 2), "utf-8");
|
|
261
|
+
}
|
|
262
|
+
return { added, existing };
|
|
263
|
+
}
|
|
264
|
+
function topologicalSort(issues, links) {
|
|
265
|
+
if (!issues || issues.length === 0) return [];
|
|
266
|
+
if (!links || links.length === 0) return [...issues];
|
|
267
|
+
const deps = /* @__PURE__ */ new Map();
|
|
268
|
+
const issueSet = new Set(issues.map((i) => i.number));
|
|
269
|
+
for (const issue of issues) {
|
|
270
|
+
deps.set(issue.number, /* @__PURE__ */ new Set());
|
|
271
|
+
}
|
|
272
|
+
for (const link of links) {
|
|
273
|
+
if (link.type !== "blocked-by") continue;
|
|
274
|
+
const aMatch = String(link.a).match(/#(\d+)/);
|
|
275
|
+
const bMatch = String(link.b).match(/#(\d+)/);
|
|
276
|
+
if (!aMatch || !bMatch) continue;
|
|
277
|
+
const a = parseInt(aMatch[1], 10);
|
|
278
|
+
const b = parseInt(bMatch[1], 10);
|
|
279
|
+
if (issueSet.has(a) && issueSet.has(b) && deps.has(a)) {
|
|
280
|
+
deps.get(a).add(b);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
284
|
+
for (const issue of issues) {
|
|
285
|
+
inDegree.set(issue.number, deps.get(issue.number).size);
|
|
286
|
+
}
|
|
287
|
+
const queue = [];
|
|
288
|
+
for (const [num, degree] of inDegree) {
|
|
289
|
+
if (degree === 0) queue.push(num);
|
|
290
|
+
}
|
|
291
|
+
const issueMap = new Map(issues.map((i) => [i.number, i]));
|
|
292
|
+
const sorted = [];
|
|
293
|
+
while (queue.length > 0) {
|
|
294
|
+
queue.sort((a, b) => {
|
|
295
|
+
const idxA = issues.findIndex((i) => i.number === a);
|
|
296
|
+
const idxB = issues.findIndex((i) => i.number === b);
|
|
297
|
+
return idxA - idxB;
|
|
298
|
+
});
|
|
299
|
+
const num = queue.shift();
|
|
300
|
+
sorted.push(issueMap.get(num));
|
|
301
|
+
for (const [dependent, depSet] of deps) {
|
|
302
|
+
if (depSet.has(num)) {
|
|
303
|
+
depSet.delete(num);
|
|
304
|
+
inDegree.set(dependent, inDegree.get(dependent) - 1);
|
|
305
|
+
if (inDegree.get(dependent) === 0) {
|
|
306
|
+
queue.push(dependent);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (sorted.length < issues.length) {
|
|
312
|
+
for (const issue of issues) {
|
|
313
|
+
if (!sorted.includes(issue)) {
|
|
314
|
+
sorted.push(issue);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return sorted;
|
|
319
|
+
}
|
|
160
320
|
state = {
|
|
161
321
|
getMgwDir,
|
|
162
322
|
getActiveDir,
|
|
@@ -166,11 +326,260 @@ function requireState () {
|
|
|
166
326
|
loadActiveIssue,
|
|
167
327
|
mergeProjectState,
|
|
168
328
|
migrateProjectState,
|
|
169
|
-
resolveActiveMilestoneIndex
|
|
329
|
+
resolveActiveMilestoneIndex,
|
|
330
|
+
loadCrossRefs,
|
|
331
|
+
VALID_LINK_TYPES,
|
|
332
|
+
parseDependencies,
|
|
333
|
+
storeDependencies,
|
|
334
|
+
topologicalSort
|
|
170
335
|
};
|
|
171
336
|
return state;
|
|
172
337
|
}
|
|
173
338
|
|
|
339
|
+
var errors;
|
|
340
|
+
var hasRequiredErrors;
|
|
341
|
+
|
|
342
|
+
function requireErrors () {
|
|
343
|
+
if (hasRequiredErrors) return errors;
|
|
344
|
+
hasRequiredErrors = 1;
|
|
345
|
+
class MgwError extends Error {
|
|
346
|
+
/**
|
|
347
|
+
* @param {string} message
|
|
348
|
+
* @param {object} [opts]
|
|
349
|
+
* @param {string} [opts.code] - Machine-readable error code
|
|
350
|
+
* @param {string} [opts.stage] - Pipeline stage where error occurred
|
|
351
|
+
* @param {number} [opts.issueNumber] - Related GitHub issue number
|
|
352
|
+
* @param {Error} [opts.cause] - Original error
|
|
353
|
+
*/
|
|
354
|
+
constructor(message, opts) {
|
|
355
|
+
super(message);
|
|
356
|
+
this.name = "MgwError";
|
|
357
|
+
const o = opts || {};
|
|
358
|
+
this.code = o.code || "MGW_ERROR";
|
|
359
|
+
this.stage = o.stage || null;
|
|
360
|
+
this.issueNumber = o.issueNumber || null;
|
|
361
|
+
if (o.cause) this.cause = o.cause;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
class GitHubApiError extends MgwError {
|
|
365
|
+
/**
|
|
366
|
+
* @param {string} message
|
|
367
|
+
* @param {object} [opts]
|
|
368
|
+
* @param {number} [opts.status] - HTTP status code
|
|
369
|
+
* @param {string} [opts.endpoint] - API endpoint or gh subcommand
|
|
370
|
+
*/
|
|
371
|
+
constructor(message, opts) {
|
|
372
|
+
const o = opts || {};
|
|
373
|
+
super(message, { code: "GITHUB_API_ERROR", ...o });
|
|
374
|
+
this.name = "GitHubApiError";
|
|
375
|
+
this.status = o.status || null;
|
|
376
|
+
this.endpoint = o.endpoint || null;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
class GsdToolError extends MgwError {
|
|
380
|
+
/**
|
|
381
|
+
* @param {string} message
|
|
382
|
+
* @param {object} [opts]
|
|
383
|
+
* @param {string} [opts.command] - GSD subcommand that failed
|
|
384
|
+
*/
|
|
385
|
+
constructor(message, opts) {
|
|
386
|
+
const o = opts || {};
|
|
387
|
+
super(message, { code: "GSD_TOOL_ERROR", ...o });
|
|
388
|
+
this.name = "GsdToolError";
|
|
389
|
+
this.command = o.command || null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
class StateError extends MgwError {
|
|
393
|
+
/**
|
|
394
|
+
* @param {string} message
|
|
395
|
+
* @param {object} [opts]
|
|
396
|
+
* @param {string} [opts.filePath] - State file path
|
|
397
|
+
*/
|
|
398
|
+
constructor(message, opts) {
|
|
399
|
+
const o = opts || {};
|
|
400
|
+
super(message, { code: "STATE_ERROR", ...o });
|
|
401
|
+
this.name = "StateError";
|
|
402
|
+
this.filePath = o.filePath || null;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
class TimeoutError extends MgwError {
|
|
406
|
+
/**
|
|
407
|
+
* @param {string} message
|
|
408
|
+
* @param {object} [opts]
|
|
409
|
+
* @param {number} [opts.timeoutMs] - Timeout duration in milliseconds
|
|
410
|
+
* @param {string} [opts.operation] - Description of the timed-out operation
|
|
411
|
+
*/
|
|
412
|
+
constructor(message, opts) {
|
|
413
|
+
const o = opts || {};
|
|
414
|
+
super(message, { code: "TIMEOUT_ERROR", ...o });
|
|
415
|
+
this.name = "TimeoutError";
|
|
416
|
+
this.timeoutMs = o.timeoutMs || null;
|
|
417
|
+
this.operation = o.operation || null;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
class ClaudeNotAvailableError extends MgwError {
|
|
421
|
+
/**
|
|
422
|
+
* @param {string} message
|
|
423
|
+
* @param {object} [opts]
|
|
424
|
+
* @param {'not-installed'|'not-authenticated'|'check-failed'} [opts.reason]
|
|
425
|
+
*/
|
|
426
|
+
constructor(message, opts) {
|
|
427
|
+
const o = opts || {};
|
|
428
|
+
super(message, { code: "CLAUDE_NOT_AVAILABLE", ...o });
|
|
429
|
+
this.name = "ClaudeNotAvailableError";
|
|
430
|
+
this.reason = o.reason || null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
errors = {
|
|
434
|
+
MgwError,
|
|
435
|
+
GitHubApiError,
|
|
436
|
+
GsdToolError,
|
|
437
|
+
StateError,
|
|
438
|
+
TimeoutError,
|
|
439
|
+
ClaudeNotAvailableError
|
|
440
|
+
};
|
|
441
|
+
return errors;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
var retry;
|
|
445
|
+
var hasRequiredRetry;
|
|
446
|
+
|
|
447
|
+
function requireRetry () {
|
|
448
|
+
if (hasRequiredRetry) return retry;
|
|
449
|
+
hasRequiredRetry = 1;
|
|
450
|
+
const MAX_RETRIES = 3;
|
|
451
|
+
const BACKOFF_BASE_MS = 5e3;
|
|
452
|
+
const BACKOFF_MAX_MS = 3e5;
|
|
453
|
+
const TRANSIENT_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
454
|
+
const TRANSIENT_MESSAGE_PATTERNS = [
|
|
455
|
+
"network timeout",
|
|
456
|
+
"econnreset",
|
|
457
|
+
"econnrefused",
|
|
458
|
+
"etimedout",
|
|
459
|
+
"socket hang up",
|
|
460
|
+
"worktree lock",
|
|
461
|
+
"model overload",
|
|
462
|
+
"rate limit",
|
|
463
|
+
"too many requests",
|
|
464
|
+
"service unavailable",
|
|
465
|
+
"bad gateway",
|
|
466
|
+
"gateway timeout"
|
|
467
|
+
];
|
|
468
|
+
const NEEDS_INFO_MESSAGE_PATTERNS = [
|
|
469
|
+
"ambiguous",
|
|
470
|
+
"missing required field",
|
|
471
|
+
"contradictory requirements",
|
|
472
|
+
"issue body"
|
|
473
|
+
];
|
|
474
|
+
function classifyFailure(error) {
|
|
475
|
+
if (!error || typeof error !== "object") {
|
|
476
|
+
return { class: "permanent", reason: "no error object provided" };
|
|
477
|
+
}
|
|
478
|
+
const status = error.status;
|
|
479
|
+
const message = (error.message || "").toLowerCase();
|
|
480
|
+
const code = (error.code || "").toLowerCase();
|
|
481
|
+
if (typeof status === "number") {
|
|
482
|
+
if (status === 429) {
|
|
483
|
+
return { class: "transient", reason: "rate limit (HTTP 429)" };
|
|
484
|
+
}
|
|
485
|
+
if (TRANSIENT_STATUS_CODES.has(status)) {
|
|
486
|
+
return { class: "transient", reason: `server error (HTTP ${status})` };
|
|
487
|
+
}
|
|
488
|
+
if (status === 403) {
|
|
489
|
+
return { class: "permanent", reason: "forbidden (HTTP 403 \u2014 non-rate-limit)" };
|
|
490
|
+
}
|
|
491
|
+
if (status >= 400 && status < 500) {
|
|
492
|
+
return { class: "permanent", reason: `client error (HTTP ${status})` };
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (code) {
|
|
496
|
+
const networkCodes = /* @__PURE__ */ new Set([
|
|
497
|
+
"econnreset",
|
|
498
|
+
"econnrefused",
|
|
499
|
+
"etimedout",
|
|
500
|
+
"enotfound",
|
|
501
|
+
"epipe"
|
|
502
|
+
]);
|
|
503
|
+
if (networkCodes.has(code)) {
|
|
504
|
+
return { class: "transient", reason: `network error (${code.toUpperCase()})` };
|
|
505
|
+
}
|
|
506
|
+
if (code === "enoent") {
|
|
507
|
+
return { class: "permanent", reason: "file not found (ENOENT) \u2014 GSD tools may be missing" };
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
for (const pattern of TRANSIENT_MESSAGE_PATTERNS) {
|
|
511
|
+
if (message.includes(pattern)) {
|
|
512
|
+
return { class: "transient", reason: `transient condition detected: "${pattern}"` };
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
for (const pattern of NEEDS_INFO_MESSAGE_PATTERNS) {
|
|
516
|
+
if (message.includes(pattern)) {
|
|
517
|
+
return { class: "needs-info", reason: `issue requires clarification: "${pattern}"` };
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
class: "permanent",
|
|
522
|
+
reason: "unknown error \u2014 classified as permanent to prevent runaway retries"
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function canRetry(issueState) {
|
|
526
|
+
if (!issueState || typeof issueState !== "object") return false;
|
|
527
|
+
if (issueState.dead_letter === true) return false;
|
|
528
|
+
const count = typeof issueState.retry_count === "number" ? issueState.retry_count : 0;
|
|
529
|
+
return count < MAX_RETRIES;
|
|
530
|
+
}
|
|
531
|
+
function incrementRetry(issueState) {
|
|
532
|
+
const current = typeof issueState.retry_count === "number" ? issueState.retry_count : 0;
|
|
533
|
+
return Object.assign({}, issueState, { retry_count: current + 1 });
|
|
534
|
+
}
|
|
535
|
+
function resetRetryState(issueState) {
|
|
536
|
+
return Object.assign({}, issueState, {
|
|
537
|
+
retry_count: 0,
|
|
538
|
+
last_failure_class: null,
|
|
539
|
+
dead_letter: false
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
function getBackoffMs(retryCount) {
|
|
543
|
+
const count = Math.max(0, Math.floor(retryCount));
|
|
544
|
+
const base = Math.min(BACKOFF_MAX_MS, BACKOFF_BASE_MS * Math.pow(2, count));
|
|
545
|
+
return Math.floor(Math.random() * (base + 1));
|
|
546
|
+
}
|
|
547
|
+
async function withRetry(fn, opts) {
|
|
548
|
+
const o = opts || {};
|
|
549
|
+
const maxAttempts = (typeof o.maxRetries === "number" ? o.maxRetries : MAX_RETRIES) + 1;
|
|
550
|
+
let lastError;
|
|
551
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
552
|
+
try {
|
|
553
|
+
return await fn();
|
|
554
|
+
} catch (err) {
|
|
555
|
+
lastError = err;
|
|
556
|
+
if (attempt >= maxAttempts - 1) break;
|
|
557
|
+
const classification = classifyFailure(err);
|
|
558
|
+
if (classification.class !== "transient") break;
|
|
559
|
+
const delayMs = getBackoffMs(attempt);
|
|
560
|
+
if (delayMs > 0) {
|
|
561
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
throw lastError;
|
|
566
|
+
}
|
|
567
|
+
retry = {
|
|
568
|
+
// Constants
|
|
569
|
+
MAX_RETRIES,
|
|
570
|
+
BACKOFF_BASE_MS,
|
|
571
|
+
BACKOFF_MAX_MS,
|
|
572
|
+
// Core functions
|
|
573
|
+
classifyFailure,
|
|
574
|
+
canRetry,
|
|
575
|
+
incrementRetry,
|
|
576
|
+
resetRetryState,
|
|
577
|
+
getBackoffMs,
|
|
578
|
+
withRetry
|
|
579
|
+
};
|
|
580
|
+
return retry;
|
|
581
|
+
}
|
|
582
|
+
|
|
174
583
|
var github;
|
|
175
584
|
var hasRequiredGithub;
|
|
176
585
|
|
|
@@ -178,22 +587,51 @@ function requireGithub () {
|
|
|
178
587
|
if (hasRequiredGithub) return github;
|
|
179
588
|
hasRequiredGithub = 1;
|
|
180
589
|
const { execSync } = require$$0$1;
|
|
590
|
+
const { TimeoutError, GitHubApiError } = requireErrors();
|
|
591
|
+
const { withRetry } = requireRetry();
|
|
592
|
+
const GH_TIMEOUT_MS = 3e4;
|
|
593
|
+
function parseHttpStatus(stderr) {
|
|
594
|
+
if (!stderr) return null;
|
|
595
|
+
const match = stderr.match(/HTTP (\d{3})/);
|
|
596
|
+
return match ? parseInt(match[1], 10) : null;
|
|
597
|
+
}
|
|
181
598
|
function run(cmd) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
599
|
+
try {
|
|
600
|
+
return execSync(cmd, {
|
|
601
|
+
encoding: "utf-8",
|
|
602
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
603
|
+
timeout: GH_TIMEOUT_MS
|
|
604
|
+
}).trim();
|
|
605
|
+
} catch (err) {
|
|
606
|
+
if (err.killed) {
|
|
607
|
+
throw new TimeoutError(
|
|
608
|
+
`gh command timed out after ${GH_TIMEOUT_MS / 1e3}s: ${cmd.slice(0, 80)}`,
|
|
609
|
+
{ timeoutMs: GH_TIMEOUT_MS, operation: cmd.slice(0, 120) }
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
const stderr = (err.stderr || "").trim();
|
|
613
|
+
const httpStatus = parseHttpStatus(stderr);
|
|
614
|
+
const ghErr = new GitHubApiError(stderr || err.message, {
|
|
615
|
+
cause: err,
|
|
616
|
+
status: httpStatus
|
|
617
|
+
});
|
|
618
|
+
if (err.code) ghErr.code = err.code;
|
|
619
|
+
throw ghErr;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
async function runWithRetry(cmd) {
|
|
623
|
+
return withRetry(async () => run(cmd));
|
|
186
624
|
}
|
|
187
|
-
function getRepo() {
|
|
188
|
-
return
|
|
625
|
+
async function getRepo() {
|
|
626
|
+
return runWithRetry("gh repo view --json nameWithOwner -q .nameWithOwner");
|
|
189
627
|
}
|
|
190
|
-
function getIssue(number) {
|
|
191
|
-
const raw =
|
|
628
|
+
async function getIssue(number) {
|
|
629
|
+
const raw = await runWithRetry(
|
|
192
630
|
`gh issue view ${number} --json number,title,state,labels,milestone,assignees,body`
|
|
193
631
|
);
|
|
194
632
|
return JSON.parse(raw);
|
|
195
633
|
}
|
|
196
|
-
function listIssues(filters) {
|
|
634
|
+
async function listIssues(filters) {
|
|
197
635
|
const f = filters || {};
|
|
198
636
|
let cmd = "gh issue list --json number,title,state,labels,milestone,assignees,createdAt,url,body,comments";
|
|
199
637
|
if (f.label) cmd += ` --label ${JSON.stringify(f.label)}`;
|
|
@@ -201,16 +639,16 @@ function requireGithub () {
|
|
|
201
639
|
if (f.assignee && f.assignee !== "all") cmd += ` --assignee ${JSON.stringify(f.assignee)}`;
|
|
202
640
|
if (f.state) cmd += ` --state ${f.state}`;
|
|
203
641
|
if (f.limit) cmd += ` --limit ${parseInt(f.limit, 10)}`;
|
|
204
|
-
const raw =
|
|
642
|
+
const raw = await runWithRetry(cmd);
|
|
205
643
|
return JSON.parse(raw);
|
|
206
644
|
}
|
|
207
|
-
function getMilestone(number) {
|
|
208
|
-
const repo = getRepo();
|
|
209
|
-
const raw =
|
|
645
|
+
async function getMilestone(number) {
|
|
646
|
+
const repo = await getRepo();
|
|
647
|
+
const raw = await runWithRetry(`gh api repos/${repo}/milestones/${number}`);
|
|
210
648
|
return JSON.parse(raw);
|
|
211
649
|
}
|
|
212
|
-
function getRateLimit() {
|
|
213
|
-
const raw =
|
|
650
|
+
async function getRateLimit() {
|
|
651
|
+
const raw = await runWithRetry("gh api rate_limit");
|
|
214
652
|
const data = JSON.parse(raw);
|
|
215
653
|
const core = data.resources.core;
|
|
216
654
|
return {
|
|
@@ -219,21 +657,21 @@ function requireGithub () {
|
|
|
219
657
|
reset: core.reset
|
|
220
658
|
};
|
|
221
659
|
}
|
|
222
|
-
function closeMilestone(repo, number) {
|
|
223
|
-
const raw =
|
|
660
|
+
async function closeMilestone(repo, number) {
|
|
661
|
+
const raw = await runWithRetry(
|
|
224
662
|
`gh api repos/${repo}/milestones/${number} --method PATCH -f state=closed`
|
|
225
663
|
);
|
|
226
664
|
return JSON.parse(raw);
|
|
227
665
|
}
|
|
228
|
-
function createRelease(repo, tag, title, opts) {
|
|
666
|
+
async function createRelease(repo, tag, title, opts) {
|
|
229
667
|
const o = opts || {};
|
|
230
668
|
let cmd = `gh release create ${JSON.stringify(tag)} --repo ${JSON.stringify(repo)} --title ${JSON.stringify(title)}`;
|
|
231
669
|
if (o.notes) cmd += ` --notes ${JSON.stringify(o.notes)}`;
|
|
232
670
|
if (o.draft) cmd += " --draft";
|
|
233
671
|
if (o.prerelease) cmd += " --prerelease";
|
|
234
|
-
return
|
|
672
|
+
return runWithRetry(cmd);
|
|
235
673
|
}
|
|
236
|
-
function getProjectNodeId(owner, projectNumber) {
|
|
674
|
+
async function getProjectNodeId(owner, projectNumber) {
|
|
237
675
|
const userQuery = `'query($login: String!, $number: Int!) { user(login: $login) { projectV2(number: $number) { id } } }'`;
|
|
238
676
|
const orgQuery = `'query($login: String!, $number: Int!) { organization(login: $login) { projectV2(number: $number) { id } } }'`;
|
|
239
677
|
try {
|
|
@@ -252,7 +690,7 @@ function requireGithub () {
|
|
|
252
690
|
}
|
|
253
691
|
return null;
|
|
254
692
|
}
|
|
255
|
-
function findExistingBoard(owner, titlePattern) {
|
|
693
|
+
async function findExistingBoard(owner, titlePattern) {
|
|
256
694
|
const pattern = titlePattern.toLowerCase();
|
|
257
695
|
try {
|
|
258
696
|
const raw = run(
|
|
@@ -274,7 +712,7 @@ function requireGithub () {
|
|
|
274
712
|
}
|
|
275
713
|
return null;
|
|
276
714
|
}
|
|
277
|
-
function getProjectFields(owner, projectNumber) {
|
|
715
|
+
async function getProjectFields(owner, projectNumber) {
|
|
278
716
|
const query = `'query($login: String!, $number: Int!) { user(login: $login) { projectV2(number: $number) { fields(first: 20) { nodes { ... on ProjectV2SingleSelectField { id name options { id name } } ... on ProjectV2Field { id name dataType } } } } } }'`;
|
|
279
717
|
const orgQuery = `'query($login: String!, $number: Int!) { organization(login: $login) { projectV2(number: $number) { fields(first: 20) { nodes { ... on ProjectV2SingleSelectField { id name options { id name } } ... on ProjectV2Field { id name dataType } } } } } }'`;
|
|
280
718
|
let raw;
|
|
@@ -352,19 +790,19 @@ function requireGithub () {
|
|
|
352
790
|
}
|
|
353
791
|
return Object.keys(fields).length > 0 ? fields : null;
|
|
354
792
|
}
|
|
355
|
-
function createProject(owner, title) {
|
|
356
|
-
const raw =
|
|
793
|
+
async function createProject(owner, title) {
|
|
794
|
+
const raw = await runWithRetry(
|
|
357
795
|
`gh project create --owner ${JSON.stringify(owner)} --title ${JSON.stringify(title)} --format json`
|
|
358
796
|
);
|
|
359
797
|
const data = JSON.parse(raw);
|
|
360
798
|
return { number: data.number, url: data.url };
|
|
361
799
|
}
|
|
362
|
-
function addItemToProject(owner, projectNumber, issueUrl) {
|
|
363
|
-
return
|
|
800
|
+
async function addItemToProject(owner, projectNumber, issueUrl) {
|
|
801
|
+
return runWithRetry(
|
|
364
802
|
`gh project item-add ${projectNumber} --owner ${JSON.stringify(owner)} --url ${JSON.stringify(issueUrl)}`
|
|
365
803
|
);
|
|
366
804
|
}
|
|
367
|
-
function postMilestoneStartAnnouncement(opts) {
|
|
805
|
+
async function postMilestoneStartAnnouncement(opts) {
|
|
368
806
|
const {
|
|
369
807
|
repo,
|
|
370
808
|
milestoneName,
|
|
@@ -435,6 +873,8 @@ function requireGithub () {
|
|
|
435
873
|
return { posted: false, method: "none", url: null };
|
|
436
874
|
}
|
|
437
875
|
github = {
|
|
876
|
+
GH_TIMEOUT_MS,
|
|
877
|
+
run,
|
|
438
878
|
getRepo,
|
|
439
879
|
getIssue,
|
|
440
880
|
listIssues,
|
|
@@ -526,6 +966,7 @@ function requireClaude () {
|
|
|
526
966
|
const { execSync, spawn } = require$$0$1;
|
|
527
967
|
const path = require$$1;
|
|
528
968
|
const fs = require$$0;
|
|
969
|
+
const { ClaudeNotAvailableError, TimeoutError } = requireErrors();
|
|
529
970
|
function getCommandsDir() {
|
|
530
971
|
const dir = path.join(__dirname, "..", "commands");
|
|
531
972
|
if (!fs.existsSync(dir)) {
|
|
@@ -540,30 +981,44 @@ This may indicate a corrupted installation. Try reinstalling mgw.`
|
|
|
540
981
|
try {
|
|
541
982
|
execSync("claude --version", {
|
|
542
983
|
encoding: "utf-8",
|
|
543
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
984
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
985
|
+
timeout: 1e4
|
|
544
986
|
});
|
|
545
987
|
} catch (err) {
|
|
546
|
-
if (err.
|
|
547
|
-
|
|
548
|
-
"
|
|
988
|
+
if (err.killed) {
|
|
989
|
+
throw new TimeoutError(
|
|
990
|
+
"claude --version timed out after 10s",
|
|
991
|
+
{ timeoutMs: 1e4, operation: "claude --version" }
|
|
549
992
|
);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
|
|
993
|
+
}
|
|
994
|
+
if (err.code === "ENOENT") {
|
|
995
|
+
throw new ClaudeNotAvailableError(
|
|
996
|
+
"claude CLI is not installed.\n\nInstall it with:\n npm install -g @anthropic-ai/claude-code\n\nThen run:\n claude login",
|
|
997
|
+
{ reason: "not-installed" }
|
|
553
998
|
);
|
|
554
999
|
}
|
|
555
|
-
|
|
1000
|
+
throw new ClaudeNotAvailableError(
|
|
1001
|
+
"claude CLI check failed.\nEnsure claude is installed and on your PATH.",
|
|
1002
|
+
{ reason: "check-failed", cause: err }
|
|
1003
|
+
);
|
|
556
1004
|
}
|
|
557
1005
|
try {
|
|
558
1006
|
execSync("claude auth status", {
|
|
559
1007
|
encoding: "utf-8",
|
|
560
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1008
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1009
|
+
timeout: 1e4
|
|
561
1010
|
});
|
|
562
|
-
} catch {
|
|
563
|
-
|
|
564
|
-
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
if (err.killed) {
|
|
1013
|
+
throw new TimeoutError(
|
|
1014
|
+
"claude auth status timed out after 10s",
|
|
1015
|
+
{ timeoutMs: 1e4, operation: "claude auth status" }
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
throw new ClaudeNotAvailableError(
|
|
1019
|
+
"claude CLI is not authenticated.\n\nRun:\n claude login\n\nThen retry your command.",
|
|
1020
|
+
{ reason: "not-authenticated" }
|
|
565
1021
|
);
|
|
566
|
-
process.exit(1);
|
|
567
1022
|
}
|
|
568
1023
|
}
|
|
569
1024
|
function invokeClaude(commandFile, userPrompt, opts) {
|
|
@@ -587,6 +1042,10 @@ This may indicate a corrupted installation. Try reinstalling mgw.`
|
|
|
587
1042
|
const stdio = o.quiet ? ["pipe", "pipe", "pipe"] : ["inherit", "inherit", "inherit"];
|
|
588
1043
|
const child = spawn("claude", args, { stdio });
|
|
589
1044
|
let output = "";
|
|
1045
|
+
const sigintHandler = () => {
|
|
1046
|
+
child.kill("SIGINT");
|
|
1047
|
+
};
|
|
1048
|
+
process.on("SIGINT", sigintHandler);
|
|
590
1049
|
if (o.quiet) {
|
|
591
1050
|
child.stdout.on("data", (chunk) => {
|
|
592
1051
|
output += chunk.toString();
|
|
@@ -596,13 +1055,18 @@ This may indicate a corrupted installation. Try reinstalling mgw.`
|
|
|
596
1055
|
});
|
|
597
1056
|
}
|
|
598
1057
|
child.on("error", (err) => {
|
|
1058
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
599
1059
|
if (err.code === "ENOENT") {
|
|
600
|
-
reject(new
|
|
1060
|
+
reject(new ClaudeNotAvailableError(
|
|
1061
|
+
"claude CLI not found. Install with: npm install -g @anthropic-ai/claude-code",
|
|
1062
|
+
{ reason: "not-installed" }
|
|
1063
|
+
));
|
|
601
1064
|
} else {
|
|
602
1065
|
reject(err);
|
|
603
1066
|
}
|
|
604
1067
|
});
|
|
605
1068
|
child.on("close", (code) => {
|
|
1069
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
606
1070
|
resolve({ exitCode: code || 0, output });
|
|
607
1071
|
});
|
|
608
1072
|
});
|
|
@@ -716,6 +1180,160 @@ function requireSpinner () {
|
|
|
716
1180
|
return spinner;
|
|
717
1181
|
}
|
|
718
1182
|
|
|
1183
|
+
var logger;
|
|
1184
|
+
var hasRequiredLogger;
|
|
1185
|
+
|
|
1186
|
+
function requireLogger () {
|
|
1187
|
+
if (hasRequiredLogger) return logger;
|
|
1188
|
+
hasRequiredLogger = 1;
|
|
1189
|
+
const path = require$$1;
|
|
1190
|
+
const fs = require$$0;
|
|
1191
|
+
function getLogDir(repoRoot) {
|
|
1192
|
+
const root = repoRoot || process.cwd();
|
|
1193
|
+
const logDir = path.join(root, ".mgw", "logs");
|
|
1194
|
+
if (!fs.existsSync(logDir)) {
|
|
1195
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
1196
|
+
}
|
|
1197
|
+
return logDir;
|
|
1198
|
+
}
|
|
1199
|
+
function getLogFile(repoRoot) {
|
|
1200
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1201
|
+
return path.join(getLogDir(repoRoot), `${date}.jsonl`);
|
|
1202
|
+
}
|
|
1203
|
+
function writeLog(entry) {
|
|
1204
|
+
const { repoRoot, ...rest } = entry;
|
|
1205
|
+
const record = {
|
|
1206
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1207
|
+
...rest
|
|
1208
|
+
};
|
|
1209
|
+
try {
|
|
1210
|
+
const logFile = getLogFile(repoRoot);
|
|
1211
|
+
fs.appendFileSync(logFile, JSON.stringify(record) + "\n");
|
|
1212
|
+
} catch {
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
function startTimer(entry) {
|
|
1216
|
+
const start = Date.now();
|
|
1217
|
+
return {
|
|
1218
|
+
finish(status, errorMsg) {
|
|
1219
|
+
writeLog({
|
|
1220
|
+
...entry,
|
|
1221
|
+
duration_ms: Date.now() - start,
|
|
1222
|
+
status,
|
|
1223
|
+
error: errorMsg || void 0
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
function readLogs(opts) {
|
|
1229
|
+
const o = opts || {};
|
|
1230
|
+
const logDir = getLogDir(o.repoRoot);
|
|
1231
|
+
if (!fs.existsSync(logDir)) return [];
|
|
1232
|
+
let sinceDate = null;
|
|
1233
|
+
if (o.since) {
|
|
1234
|
+
const relativeMatch = o.since.match(/^(\d+)d$/);
|
|
1235
|
+
if (relativeMatch) {
|
|
1236
|
+
sinceDate = /* @__PURE__ */ new Date();
|
|
1237
|
+
sinceDate.setDate(sinceDate.getDate() - parseInt(relativeMatch[1], 10));
|
|
1238
|
+
} else {
|
|
1239
|
+
sinceDate = new Date(o.since);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
let files;
|
|
1243
|
+
try {
|
|
1244
|
+
files = fs.readdirSync(logDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1245
|
+
} catch {
|
|
1246
|
+
return [];
|
|
1247
|
+
}
|
|
1248
|
+
if (sinceDate) {
|
|
1249
|
+
const sinceStr = sinceDate.toISOString().slice(0, 10);
|
|
1250
|
+
files = files.filter((f) => f.replace(".jsonl", "") >= sinceStr);
|
|
1251
|
+
}
|
|
1252
|
+
const entries = [];
|
|
1253
|
+
for (const file of files) {
|
|
1254
|
+
const filePath = path.join(logDir, file);
|
|
1255
|
+
let content;
|
|
1256
|
+
try {
|
|
1257
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
1258
|
+
} catch {
|
|
1259
|
+
continue;
|
|
1260
|
+
}
|
|
1261
|
+
for (const line of content.split("\n")) {
|
|
1262
|
+
if (!line.trim()) continue;
|
|
1263
|
+
let entry;
|
|
1264
|
+
try {
|
|
1265
|
+
entry = JSON.parse(line);
|
|
1266
|
+
} catch {
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
if (o.issue && entry.issue !== o.issue) continue;
|
|
1270
|
+
if (o.command && entry.command !== o.command) continue;
|
|
1271
|
+
if (o.stage && entry.stage !== o.stage) continue;
|
|
1272
|
+
entries.push(entry);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
entries.reverse();
|
|
1276
|
+
if (o.limit && entries.length > o.limit) {
|
|
1277
|
+
return entries.slice(0, o.limit);
|
|
1278
|
+
}
|
|
1279
|
+
return entries;
|
|
1280
|
+
}
|
|
1281
|
+
function aggregateMetrics(entries) {
|
|
1282
|
+
if (!entries || entries.length === 0) {
|
|
1283
|
+
return {
|
|
1284
|
+
total: 0,
|
|
1285
|
+
byStatus: {},
|
|
1286
|
+
byCommand: {},
|
|
1287
|
+
avgDuration: 0,
|
|
1288
|
+
failureRate: 0
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
const byStatus = {};
|
|
1292
|
+
const byCommand = {};
|
|
1293
|
+
let totalDuration = 0;
|
|
1294
|
+
let durationCount = 0;
|
|
1295
|
+
let failures = 0;
|
|
1296
|
+
for (const e of entries) {
|
|
1297
|
+
byStatus[e.status] = (byStatus[e.status] || 0) + 1;
|
|
1298
|
+
if (e.status === "error") failures++;
|
|
1299
|
+
if (e.command) {
|
|
1300
|
+
if (!byCommand[e.command]) {
|
|
1301
|
+
byCommand[e.command] = { count: 0, errors: 0, totalDuration: 0 };
|
|
1302
|
+
}
|
|
1303
|
+
byCommand[e.command].count++;
|
|
1304
|
+
if (e.status === "error") byCommand[e.command].errors++;
|
|
1305
|
+
if (typeof e.duration_ms === "number") {
|
|
1306
|
+
byCommand[e.command].totalDuration += e.duration_ms;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
if (typeof e.duration_ms === "number") {
|
|
1310
|
+
totalDuration += e.duration_ms;
|
|
1311
|
+
durationCount++;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
for (const cmd of Object.keys(byCommand)) {
|
|
1315
|
+
const c = byCommand[cmd];
|
|
1316
|
+
c.avgDuration = c.count > 0 ? Math.round(c.totalDuration / c.count) : 0;
|
|
1317
|
+
}
|
|
1318
|
+
return {
|
|
1319
|
+
total: entries.length,
|
|
1320
|
+
byStatus,
|
|
1321
|
+
byCommand,
|
|
1322
|
+
avgDuration: durationCount > 0 ? Math.round(totalDuration / durationCount) : 0,
|
|
1323
|
+
failureRate: entries.length > 0 ? Math.round(failures / entries.length * 100) : 0
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
logger = {
|
|
1327
|
+
getLogDir,
|
|
1328
|
+
getLogFile,
|
|
1329
|
+
writeLog,
|
|
1330
|
+
startTimer,
|
|
1331
|
+
readLogs,
|
|
1332
|
+
aggregateMetrics
|
|
1333
|
+
};
|
|
1334
|
+
return logger;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
719
1337
|
var renderer;
|
|
720
1338
|
var hasRequiredRenderer;
|
|
721
1339
|
|
|
@@ -1545,7 +2163,7 @@ function requireGraceful () {
|
|
|
1545
2163
|
let ms;
|
|
1546
2164
|
try {
|
|
1547
2165
|
ms = Date.now() - new Date(dateStr).getTime();
|
|
1548
|
-
} catch (
|
|
2166
|
+
} catch (_e) {
|
|
1549
2167
|
return "-";
|
|
1550
2168
|
}
|
|
1551
2169
|
if (ms < 0) return "now";
|
|
@@ -1809,8 +2427,11 @@ function requireTui () {
|
|
|
1809
2427
|
|
|
1810
2428
|
exports.getDefaultExportFromCjs = getDefaultExportFromCjs;
|
|
1811
2429
|
exports.requireClaude = requireClaude;
|
|
2430
|
+
exports.requireErrors = requireErrors;
|
|
1812
2431
|
exports.requireGithub = requireGithub;
|
|
2432
|
+
exports.requireLogger = requireLogger;
|
|
1813
2433
|
exports.requireOutput = requireOutput;
|
|
2434
|
+
exports.requireRetry = requireRetry;
|
|
1814
2435
|
exports.requireSpinner = requireSpinner;
|
|
1815
2436
|
exports.requireState = requireState;
|
|
1816
2437
|
exports.requireTui = requireTui;
|