nightralph 0.0.12 → 0.0.13
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/dist/display.js +31 -14
- package/dist/display.js.map +2 -2
- package/dist/index.js +43 -16
- package/dist/index.js.map +2 -2
- package/dist/meta.json +6 -6
- package/dist/orchestrator.js +14 -2
- package/dist/orchestrator.js.map +2 -2
- package/dist/src/display.d.ts +19 -3
- package/dist/src/display.d.ts.map +1 -1
- package/dist/src/orchestrator.d.ts +1 -0
- package/dist/src/orchestrator.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/display.js
CHANGED
|
@@ -42,6 +42,15 @@ const color = {
|
|
|
42
42
|
};
|
|
43
43
|
const FILLED_DOT = "\u25CF";
|
|
44
44
|
const EMPTY_DOT = "\u25CB";
|
|
45
|
+
const SEPARATOR_WIDTH = 50;
|
|
46
|
+
const DEFAULT_COLUMNS = 80;
|
|
47
|
+
const ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
48
|
+
function fitLine(line, width) {
|
|
49
|
+
const visible = line.replace(ANSI_RE, "");
|
|
50
|
+
if (visible.length <= width) return line;
|
|
51
|
+
return visible.slice(0, width);
|
|
52
|
+
}
|
|
53
|
+
__name(fitLine, "fitLine");
|
|
45
54
|
function renderDot(status) {
|
|
46
55
|
switch (status) {
|
|
47
56
|
case "pending":
|
|
@@ -60,6 +69,7 @@ class StatusDisplay {
|
|
|
60
69
|
__name(this, "StatusDisplay");
|
|
61
70
|
}
|
|
62
71
|
wave = null;
|
|
72
|
+
progress = null;
|
|
63
73
|
messages = [];
|
|
64
74
|
renderedLines = 0;
|
|
65
75
|
config;
|
|
@@ -78,7 +88,7 @@ class StatusDisplay {
|
|
|
78
88
|
const available = termHeight - overhead;
|
|
79
89
|
return Math.max(1, available);
|
|
80
90
|
}
|
|
81
|
-
startWave(waveNum, totalWaves, tickets) {
|
|
91
|
+
startWave(waveNum, totalWaves, tickets, progress) {
|
|
82
92
|
this.wave = {
|
|
83
93
|
waveNum,
|
|
84
94
|
totalWaves,
|
|
@@ -87,6 +97,7 @@ class StatusDisplay {
|
|
|
87
97
|
status: "pending"
|
|
88
98
|
}))
|
|
89
99
|
};
|
|
100
|
+
this.progress = progress ?? null;
|
|
90
101
|
this.messages = [];
|
|
91
102
|
this.viewportStart = 0;
|
|
92
103
|
this.config.stream.write(ansi.cursorHide());
|
|
@@ -128,10 +139,7 @@ class StatusDisplay {
|
|
|
128
139
|
}
|
|
129
140
|
this.viewportStart = Math.max(
|
|
130
141
|
0,
|
|
131
|
-
Math.min(
|
|
132
|
-
this.viewportStart,
|
|
133
|
-
totalTickets - 1
|
|
134
|
-
)
|
|
142
|
+
Math.min(this.viewportStart, totalTickets - 1)
|
|
135
143
|
);
|
|
136
144
|
}
|
|
137
145
|
this.render();
|
|
@@ -160,12 +168,17 @@ class StatusDisplay {
|
|
|
160
168
|
if (this.renderedLines > 0) {
|
|
161
169
|
output += ansi.eraseLines(this.renderedLines + 1);
|
|
162
170
|
}
|
|
163
|
-
const
|
|
171
|
+
const doneInWave = this.wave.tickets.filter(
|
|
164
172
|
(t) => t.status === "done"
|
|
165
173
|
).length;
|
|
166
|
-
const
|
|
174
|
+
const completed = doneInWave + (this.progress?.completed ?? 0);
|
|
175
|
+
const total = this.progress?.total ?? this.wave.tickets.length;
|
|
167
176
|
const pct = total === 0 ? 0 : Math.round(completed / total * 100);
|
|
168
|
-
|
|
177
|
+
const width = Math.max(
|
|
178
|
+
1,
|
|
179
|
+
(this.config.stream.columns ?? DEFAULT_COLUMNS) - 1
|
|
180
|
+
);
|
|
181
|
+
output += `Wave ${this.wave.waveNum}/${this.wave.totalWaves} - ${pct}% complete
|
|
169
182
|
`;
|
|
170
183
|
const availableRows = this.getAvailableRows();
|
|
171
184
|
const tickets = this.wave.tickets;
|
|
@@ -173,7 +186,8 @@ class StatusDisplay {
|
|
|
173
186
|
if (tickets.length <= availableRows) {
|
|
174
187
|
for (const ticket of tickets) {
|
|
175
188
|
const dot = renderDot(ticket.status);
|
|
176
|
-
|
|
189
|
+
const name = fitLine(ticket.filename, width - 2);
|
|
190
|
+
output += `${dot} ${name}
|
|
177
191
|
`;
|
|
178
192
|
}
|
|
179
193
|
renderedTicketCount = tickets.length;
|
|
@@ -217,7 +231,8 @@ class StatusDisplay {
|
|
|
217
231
|
);
|
|
218
232
|
for (const ticket of visibleTickets) {
|
|
219
233
|
const dot = renderDot(ticket.status);
|
|
220
|
-
|
|
234
|
+
const name = fitLine(ticket.filename, width - 2);
|
|
235
|
+
output += `${dot} ${name}
|
|
221
236
|
`;
|
|
222
237
|
}
|
|
223
238
|
if (hasMoreBelow) {
|
|
@@ -227,13 +242,13 @@ class StatusDisplay {
|
|
|
227
242
|
}
|
|
228
243
|
renderedTicketCount = (hasMoreAbove ? 1 : 0) + visibleTickets.length + (hasMoreBelow ? 1 : 0);
|
|
229
244
|
}
|
|
230
|
-
output += "-".repeat(
|
|
245
|
+
output += "-".repeat(Math.min(SEPARATOR_WIDTH, width)) + "\n";
|
|
231
246
|
const msgLines = Math.max(
|
|
232
247
|
0,
|
|
233
248
|
this.config.messageLines - this.messages.length
|
|
234
249
|
);
|
|
235
250
|
for (const msg of this.messages) {
|
|
236
|
-
output += `${msg}
|
|
251
|
+
output += `${fitLine(msg, width)}
|
|
237
252
|
`;
|
|
238
253
|
}
|
|
239
254
|
for (let i = 0; i < msgLines; i++) {
|
|
@@ -247,10 +262,11 @@ class PlainDisplay {
|
|
|
247
262
|
static {
|
|
248
263
|
__name(this, "PlainDisplay");
|
|
249
264
|
}
|
|
250
|
-
startWave(waveNum, _totalWaves, tickets) {
|
|
265
|
+
startWave(waveNum, _totalWaves, tickets, progress) {
|
|
266
|
+
const pct = progress && progress.total > 0 ? ` (${progress.completed}/${progress.total} done)` : "";
|
|
251
267
|
console.log(
|
|
252
268
|
`
|
|
253
|
-
Wave ${waveNum}: ` + tickets.map((t) => t.filename).join(", ")
|
|
269
|
+
Wave ${waveNum}${pct}: ` + tickets.map((t) => t.filename).join(", ")
|
|
254
270
|
);
|
|
255
271
|
}
|
|
256
272
|
setTicketStatus(_num, _status) {
|
|
@@ -275,6 +291,7 @@ export {
|
|
|
275
291
|
ansi,
|
|
276
292
|
color,
|
|
277
293
|
createDisplay,
|
|
294
|
+
fitLine,
|
|
278
295
|
renderDot
|
|
279
296
|
};
|
|
280
297
|
//# sourceMappingURL=display.js.map
|
package/dist/display.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/display.ts"],
|
|
4
|
-
"sourcesContent": ["export type TicketStatus =\n | 'pending'\n | 'in-progress'\n | 'done'\n | 'failed'\n\nexport type DisplayTicket = {\n readonly num:number\n readonly filename:string\n status:TicketStatus\n}\n\nexport type WaveState = {\n readonly waveNum:number\n readonly totalWaves:number\n readonly tickets:Array<DisplayTicket>\n}\n\nexport type DisplayConfig = {\n readonly messageLines:number\n readonly stream:NodeJS.WriteStream\n}\n\nexport type Display = {\n startWave(\n waveNum:number,\n totalWaves:number,\n tickets:ReadonlyArray<{\n num:number\n filename:string\n }>\n ):void\n setTicketStatus(num:number, status:TicketStatus):void\n log(message:string):void\n cleanup():void\n}\n\nconst ESC = '\\x1b['\n\nexport const ansi = {\n cursorUp (n:number):string {\n return `${ESC}${n}A`\n },\n cursorTo (col:number):string {\n return `${ESC}${col}G`\n },\n eraseLine ():string {\n return `${ESC}2K`\n },\n eraseLines (n:number):string {\n let out = ''\n for (let i = 0; i < n; i++) {\n out += ansi.eraseLine()\n if (i < n - 1) out += ansi.cursorUp(1)\n }\n return out\n },\n cursorHide ():string {\n return `${ESC}?25l`\n },\n cursorShow ():string {\n return `${ESC}?25h`\n },\n} as const\n\nexport const color = {\n green (text:string):string {\n return `${ESC}32m${text}${ESC}0m`\n },\n yellow (text:string):string {\n return `${ESC}33m${text}${ESC}0m`\n },\n red (text:string):string {\n return `${ESC}31m${text}${ESC}0m`\n },\n dim (text:string):string {\n return `${ESC}2m${text}${ESC}0m`\n },\n} as const\n\nconst FILLED_DOT = '\u25CF'\nconst EMPTY_DOT = '\u25CB'\n\nexport function renderDot (status:TicketStatus):string {\n switch (status) {\n case 'pending':\n return color.dim(EMPTY_DOT)\n case 'in-progress':\n return color.yellow(FILLED_DOT)\n case 'done':\n return color.green(FILLED_DOT)\n case 'failed':\n return color.red(FILLED_DOT)\n }\n}\n\nexport class StatusDisplay implements Display {\n private wave:WaveState|null = null\n private messages:Array<string> = []\n private renderedLines:number = 0\n private config:DisplayConfig\n private viewportStart:number = 0\n private resizeListener:(() => void)|null = null\n private signalHandler:(() => void)|null = null\n\n constructor (config?:Readonly<Partial<DisplayConfig>>) {\n this.config = {\n messageLines:config?.messageLines ?? 10,\n stream:config?.stream ?? process.stdout,\n }\n }\n\n private getAvailableRows ():number {\n const termHeight = this.config.stream.rows ?? 24\n const overhead = 2 + this.config.messageLines\n const available = termHeight - overhead\n return Math.max(1, available)\n }\n\n startWave (\n waveNum:number,\n totalWaves:number,\n tickets:Array<Readonly<{ num:number; filename:string }>>,\n ):void {\n this.wave = {\n waveNum,\n totalWaves,\n tickets:tickets.map(t => ({\n ...t,\n status:'pending' as const,\n })),\n }\n this.messages = []\n this.viewportStart = 0\n this.config.stream.write(ansi.cursorHide())\n\n if (this.config.stream.isTTY) {\n if (this.resizeListener) {\n this.config.stream.removeListener(\n 'resize', this.resizeListener,\n )\n }\n this.resizeListener = () => this.render()\n this.config.stream.on(\n 'resize', this.resizeListener,\n )\n }\n\n // Register signal handler to restore cursor on interruption\n if (this.signalHandler) {\n process.removeListener('SIGINT', this.signalHandler)\n process.removeListener('SIGTERM', this.signalHandler)\n }\n this.signalHandler = () => this.cleanup()\n process.on('SIGINT', this.signalHandler)\n process.on('SIGTERM', this.signalHandler)\n\n this.render()\n }\n\n setTicketStatus (num:number, status:TicketStatus):void {\n if (!this.wave) return\n\n const ticket = this.wave.tickets.find(t => t.num === num)\n if (ticket) {\n ticket.status = status\n\n if (status === 'in-progress') {\n const ticketIndex =\n this.wave.tickets.indexOf(ticket)\n const availableRows =\n this.getAvailableRows()\n const totalTickets =\n this.wave.tickets.length\n\n if (ticketIndex < this.viewportStart) {\n this.viewportStart = ticketIndex\n } else if (\n ticketIndex >=\n this.viewportStart + availableRows\n ) {\n // offset by 3 to keep context visible below\n // the active ticket\n this.viewportStart =\n ticketIndex - availableRows + 3\n }\n this.viewportStart = Math.max(\n 0,\n Math.min(\n this.viewportStart,\n totalTickets - 1,\n ),\n )\n }\n\n this.render()\n }\n }\n\n log (message:string):void {\n this.messages.push(message)\n if (this.messages.length > this.config.messageLines) {\n this.messages.shift()\n }\n this.render()\n }\n\n cleanup ():void {\n // Remove resize listener if it exists\n if (this.resizeListener && this.config.stream.isTTY) {\n this.config.stream.removeListener('resize', this.resizeListener)\n }\n // Remove signal handlers\n if (this.signalHandler) {\n process.removeListener('SIGINT', this.signalHandler)\n process.removeListener('SIGTERM', this.signalHandler)\n }\n this.config.stream.write(ansi.cursorShow())\n }\n\n private render ():void {\n if (!this.wave) return\n\n let output = ''\n\n if (this.renderedLines > 0) {\n output +=\n ansi.eraseLines(this.renderedLines + 1)\n }\n\n const completed = this.wave.tickets.filter(\n t => t.status === 'done',\n ).length\n const total = this.wave.tickets.length\n const pct = total === 0 ? 0 : Math.round((completed / total) * 100)\n\n output += `Wave ${this.wave.waveNum}/` +\n `${this.wave.totalWaves} -- ` +\n `${pct}% complete\\n`\n\n // Pagination logic\n const availableRows = this.getAvailableRows()\n const tickets = this.wave.tickets\n let renderedTicketCount = 0\n\n if (tickets.length <= availableRows) {\n // All tickets fit, no pagination\n for (const ticket of tickets) {\n const dot = renderDot(ticket.status)\n output += `${dot} ${ticket.filename}\\n`\n }\n renderedTicketCount = tickets.length\n } else {\n // Need pagination -- iterative clamp\n this.viewportStart = Math.max(\n 0,\n Math.min(\n this.viewportStart,\n tickets.length - 1,\n ),\n )\n\n let hasMoreAbove = this.viewportStart > 0\n let tentativeCount =\n availableRows - (hasMoreAbove ? 1 : 0)\n let hasMoreBelow =\n this.viewportStart + tentativeCount <\n tickets.length\n let ticketsToShow = Math.max(\n 0,\n tentativeCount - (hasMoreBelow ? 1 : 0),\n )\n\n const maxStart = Math.max(\n 0,\n tickets.length - Math.max(1, ticketsToShow),\n )\n if (this.viewportStart > maxStart) {\n this.viewportStart = maxStart\n hasMoreAbove = this.viewportStart > 0\n tentativeCount =\n availableRows - (hasMoreAbove ? 1 : 0)\n hasMoreBelow =\n this.viewportStart + tentativeCount <\n tickets.length\n ticketsToShow = Math.max(\n 0,\n tentativeCount -\n (hasMoreBelow ? 1 : 0),\n )\n }\n\n // Show \"more above\" indicator if needed\n if (hasMoreAbove) {\n const moreCount = this.viewportStart\n output += ` ... ${moreCount} more above\\n`\n }\n\n // Get visible tickets and render them\n const visibleTickets = tickets.slice(\n this.viewportStart,\n this.viewportStart + ticketsToShow,\n )\n\n for (const ticket of visibleTickets) {\n const dot = renderDot(ticket.status)\n output += `${dot} ${ticket.filename}\\n`\n }\n\n // Show \"more below\" indicator if needed\n if (hasMoreBelow) {\n const moreCount =\n tickets.length -\n (this.viewportStart + ticketsToShow)\n output += ` ... ${moreCount} more below\\n`\n }\n\n renderedTicketCount = (hasMoreAbove ? 1 : 0) +\n visibleTickets.length +\n (hasMoreBelow ? 1 : 0)\n }\n\n output += '-'.repeat(50) + '\\n'\n\n const msgLines = Math.max(\n 0,\n this.config.messageLines - this.messages.length,\n )\n for (const msg of this.messages) {\n output += `${msg}\\n`\n }\n for (let i = 0; i < msgLines; i++) {\n output += '\\n'\n }\n\n this.renderedLines = 2 + renderedTicketCount + this.config.messageLines\n this.config.stream.write(output)\n }\n}\n\nexport class PlainDisplay implements Display {\n startWave (\n waveNum:number,\n _totalWaves:number,\n tickets:ReadonlyArray<{\n num:number\n filename:string\n }>\n ):void {\n console.log(\n `\\nWave ${waveNum}: ` +\n tickets.map(t => t.filename).join(', ')\n )\n }\n\n setTicketStatus (\n _num:number,\n _status:TicketStatus\n ):void {\n // no-op: plain mode reports via log()\n }\n\n log (message:string):void {\n console.log(message)\n }\n\n cleanup ():void {\n // no-op\n }\n}\n\nexport function createDisplay (\n stream?:NodeJS.WriteStream\n):Display {\n const s = stream ?? process.stdout\n if (s.isTTY) {\n return new StatusDisplay({ stream:s })\n }\n return new PlainDisplay()\n}\n"],
|
|
5
|
-
"mappings": ";;
|
|
4
|
+
"sourcesContent": ["export type TicketStatus =\n | 'pending'\n | 'in-progress'\n | 'done'\n | 'failed'\n\nexport type DisplayTicket = {\n readonly num:number\n readonly filename:string\n status:TicketStatus\n}\n\nexport type WaveState = {\n readonly waveNum:number\n readonly totalWaves:number\n readonly tickets:Array<DisplayTicket>\n}\n\nexport type DisplayConfig = {\n readonly messageLines:number\n readonly stream:NodeJS.WriteStream\n}\n\n/**\n * Progress across the whole run, not just the current wave.\n * `completed` counts tickets done before the wave started;\n * tickets that finish during the wave are added on top.\n */\nexport type RunProgress = {\n readonly completed:number\n readonly total:number\n}\n\nexport type Display = {\n startWave(\n waveNum:number,\n totalWaves:number,\n tickets:ReadonlyArray<{\n num:number\n filename:string\n }>,\n progress?:RunProgress\n ):void\n setTicketStatus(num:number, status:TicketStatus):void\n log(message:string):void\n cleanup():void\n}\n\nconst ESC = '\\x1b['\n\nexport const ansi = {\n cursorUp (n:number):string {\n return `${ESC}${n}A`\n },\n cursorTo (col:number):string {\n return `${ESC}${col}G`\n },\n eraseLine ():string {\n return `${ESC}2K`\n },\n eraseLines (n:number):string {\n let out = ''\n for (let i = 0; i < n; i++) {\n out += ansi.eraseLine()\n if (i < n - 1) out += ansi.cursorUp(1)\n }\n return out\n },\n cursorHide ():string {\n return `${ESC}?25l`\n },\n cursorShow ():string {\n return `${ESC}?25h`\n },\n} as const\n\nexport const color = {\n green (text:string):string {\n return `${ESC}32m${text}${ESC}0m`\n },\n yellow (text:string):string {\n return `${ESC}33m${text}${ESC}0m`\n },\n red (text:string):string {\n return `${ESC}31m${text}${ESC}0m`\n },\n dim (text:string):string {\n return `${ESC}2m${text}${ESC}0m`\n },\n} as const\n\nconst FILLED_DOT = '\u25CF'\nconst EMPTY_DOT = '\u25CB'\nconst SEPARATOR_WIDTH = 50\nconst DEFAULT_COLUMNS = 80\n\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\\x1b\\[[0-9;?]*[A-Za-z]/g\n\n/**\n * Clamp a line to `width` visible columns so it can never wrap.\n * A wrapped line occupies more terminal rows than `render`\n * counts, which leaves stale rows behind on the next erase.\n */\nexport function fitLine (line:string, width:number):string {\n const visible = line.replace(ANSI_RE, '')\n if (visible.length <= width) return line\n return visible.slice(0, width)\n}\n\nexport function renderDot (status:TicketStatus):string {\n switch (status) {\n case 'pending':\n return color.dim(EMPTY_DOT)\n case 'in-progress':\n return color.yellow(FILLED_DOT)\n case 'done':\n return color.green(FILLED_DOT)\n case 'failed':\n return color.red(FILLED_DOT)\n }\n}\n\nexport class StatusDisplay implements Display {\n private wave:WaveState|null = null\n private progress:RunProgress|null = null\n private messages:Array<string> = []\n private renderedLines:number = 0\n private config:DisplayConfig\n private viewportStart:number = 0\n private resizeListener:(() => void)|null = null\n private signalHandler:(() => void)|null = null\n\n constructor (config?:Readonly<Partial<DisplayConfig>>) {\n this.config = {\n messageLines:config?.messageLines ?? 10,\n stream:config?.stream ?? process.stdout,\n }\n }\n\n private getAvailableRows ():number {\n const termHeight = this.config.stream.rows ?? 24\n const overhead = 2 + this.config.messageLines\n const available = termHeight - overhead\n return Math.max(1, available)\n }\n\n startWave (\n waveNum:number,\n totalWaves:number,\n tickets:Array<Readonly<{ num:number; filename:string }>>,\n progress?:RunProgress,\n ):void {\n this.wave = {\n waveNum,\n totalWaves,\n tickets:tickets.map(t => ({\n ...t,\n status:'pending' as const,\n })),\n }\n this.progress = progress ?? null\n this.messages = []\n this.viewportStart = 0\n this.config.stream.write(ansi.cursorHide())\n\n if (this.config.stream.isTTY) {\n if (this.resizeListener) {\n this.config.stream.removeListener(\n 'resize', this.resizeListener,\n )\n }\n this.resizeListener = () => this.render()\n this.config.stream.on(\n 'resize', this.resizeListener,\n )\n }\n\n // Register signal handler to restore cursor on interruption\n if (this.signalHandler) {\n process.removeListener('SIGINT', this.signalHandler)\n process.removeListener('SIGTERM', this.signalHandler)\n }\n this.signalHandler = () => this.cleanup()\n process.on('SIGINT', this.signalHandler)\n process.on('SIGTERM', this.signalHandler)\n\n this.render()\n }\n\n setTicketStatus (num:number, status:TicketStatus):void {\n if (!this.wave) return\n\n const ticket = this.wave.tickets.find(t => t.num === num)\n if (ticket) {\n ticket.status = status\n\n if (status === 'in-progress') {\n const ticketIndex = this.wave.tickets.indexOf(ticket)\n const availableRows = this.getAvailableRows()\n const totalTickets = this.wave.tickets.length\n\n if (ticketIndex < this.viewportStart) {\n this.viewportStart = ticketIndex\n } else if (ticketIndex >= this.viewportStart + availableRows) {\n // offset by 3 to keep context visible below\n // the active ticket\n this.viewportStart = ticketIndex - availableRows + 3\n }\n this.viewportStart = Math.max(\n 0,\n Math.min(this.viewportStart, totalTickets - 1),\n )\n }\n\n this.render()\n }\n }\n\n log (message:string):void {\n this.messages.push(message)\n if (this.messages.length > this.config.messageLines) {\n this.messages.shift()\n }\n this.render()\n }\n\n cleanup ():void {\n // Remove resize listener if it exists\n if (this.resizeListener && this.config.stream.isTTY) {\n this.config.stream.removeListener('resize', this.resizeListener)\n }\n // Remove signal handlers\n if (this.signalHandler) {\n process.removeListener('SIGINT', this.signalHandler)\n process.removeListener('SIGTERM', this.signalHandler)\n }\n this.config.stream.write(ansi.cursorShow())\n }\n\n private render ():void {\n if (!this.wave) return\n\n let output = ''\n\n if (this.renderedLines > 0) {\n output += ansi.eraseLines(this.renderedLines + 1)\n }\n\n const doneInWave = this.wave.tickets.filter(\n t => t.status === 'done',\n ).length\n const completed = doneInWave + (this.progress?.completed ?? 0)\n const total = this.progress?.total ?? this.wave.tickets.length\n const pct = total === 0 ? 0 : Math.round((completed / total) * 100)\n const width = Math.max(\n 1,\n (this.config.stream.columns ?? DEFAULT_COLUMNS) - 1,\n )\n\n output += `Wave ${this.wave.waveNum}/` +\n `${this.wave.totalWaves} - ` +\n `${pct}% complete\\n`\n\n // Pagination logic\n const availableRows = this.getAvailableRows()\n const tickets = this.wave.tickets\n let renderedTicketCount = 0\n\n if (tickets.length <= availableRows) {\n // All tickets fit, no pagination\n for (const ticket of tickets) {\n const dot = renderDot(ticket.status)\n const name = fitLine(ticket.filename, width - 2)\n output += `${dot} ${name}\\n`\n }\n renderedTicketCount = tickets.length\n } else {\n // Need pagination -- iterative clamp\n this.viewportStart = Math.max(\n 0,\n Math.min(\n this.viewportStart,\n tickets.length - 1,\n ),\n )\n\n let hasMoreAbove = this.viewportStart > 0\n let tentativeCount =\n availableRows - (hasMoreAbove ? 1 : 0)\n let hasMoreBelow =\n this.viewportStart + tentativeCount <\n tickets.length\n let ticketsToShow = Math.max(\n 0,\n tentativeCount - (hasMoreBelow ? 1 : 0),\n )\n\n const maxStart = Math.max(\n 0,\n tickets.length - Math.max(1, ticketsToShow),\n )\n if (this.viewportStart > maxStart) {\n this.viewportStart = maxStart\n hasMoreAbove = this.viewportStart > 0\n tentativeCount = availableRows - (hasMoreAbove ? 1 : 0)\n hasMoreBelow = (\n this.viewportStart +\n tentativeCount < tickets.length\n )\n ticketsToShow = Math.max(\n 0,\n tentativeCount - (hasMoreBelow ? 1 : 0)\n )\n }\n\n // Show \"more above\" indicator if needed\n if (hasMoreAbove) {\n const moreCount = this.viewportStart\n output += ` ... ${moreCount} more above\\n`\n }\n\n // Get visible tickets and render them\n const visibleTickets = tickets.slice(\n this.viewportStart,\n this.viewportStart + ticketsToShow,\n )\n\n for (const ticket of visibleTickets) {\n const dot = renderDot(ticket.status)\n const name = fitLine(ticket.filename, width - 2)\n output += `${dot} ${name}\\n`\n }\n\n // Show \"more below\" indicator if needed\n if (hasMoreBelow) {\n const moreCount = (\n tickets.length -\n (this.viewportStart + ticketsToShow)\n )\n output += ` ... ${moreCount} more below\\n`\n }\n\n renderedTicketCount = (hasMoreAbove ? 1 : 0) +\n visibleTickets.length +\n (hasMoreBelow ? 1 : 0)\n }\n\n output += '-'.repeat(Math.min(SEPARATOR_WIDTH, width)) + '\\n'\n\n const msgLines = Math.max(\n 0,\n this.config.messageLines - this.messages.length,\n )\n for (const msg of this.messages) {\n output += `${fitLine(msg, width)}\\n`\n }\n for (let i = 0; i < msgLines; i++) {\n output += '\\n'\n }\n\n this.renderedLines = 2 + renderedTicketCount + this.config.messageLines\n this.config.stream.write(output)\n }\n}\n\nexport class PlainDisplay implements Display {\n startWave (\n waveNum:number,\n _totalWaves:number,\n tickets:ReadonlyArray<{ num:number; filename:string }>,\n progress?:RunProgress,\n ):void {\n const pct = progress && progress.total > 0 ?\n ` (${progress.completed}/${progress.total} done)` :\n ''\n console.log(\n `\\nWave ${waveNum}${pct}: ` +\n tickets.map(t => t.filename).join(', ')\n )\n }\n\n setTicketStatus (_num:number, _status:TicketStatus):void {\n // no-op: plain mode reports via log()\n }\n\n log (message:string):void {\n console.log(message)\n }\n\n cleanup ():void {\n // no-op\n }\n}\n\nexport function createDisplay (stream?:NodeJS.WriteStream):Display {\n const s = stream ?? process.stdout\n if (s.isTTY) {\n return new StatusDisplay({ stream:s })\n }\n\n return new PlainDisplay()\n}\n"],
|
|
5
|
+
"mappings": ";;AAgDA,MAAM,MAAM;AAEL,MAAM,OAAO;AAAA,EAChB,SAAU,GAAiB;AACvB,WAAO,GAAG,GAAG,GAAG,CAAC;AAAA,EACrB;AAAA,EACA,SAAU,KAAmB;AACzB,WAAO,GAAG,GAAG,GAAG,GAAG;AAAA,EACvB;AAAA,EACA,YAAoB;AAChB,WAAO,GAAG,GAAG;AAAA,EACjB;AAAA,EACA,WAAY,GAAiB;AACzB,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AACxB,aAAO,KAAK,UAAU;AACtB,UAAI,IAAI,IAAI,EAAG,QAAO,KAAK,SAAS,CAAC;AAAA,IACzC;AACA,WAAO;AAAA,EACX;AAAA,EACA,aAAqB;AACjB,WAAO,GAAG,GAAG;AAAA,EACjB;AAAA,EACA,aAAqB;AACjB,WAAO,GAAG,GAAG;AAAA,EACjB;AACJ;AAEO,MAAM,QAAQ;AAAA,EACjB,MAAO,MAAoB;AACvB,WAAO,GAAG,GAAG,MAAM,IAAI,GAAG,GAAG;AAAA,EACjC;AAAA,EACA,OAAQ,MAAoB;AACxB,WAAO,GAAG,GAAG,MAAM,IAAI,GAAG,GAAG;AAAA,EACjC;AAAA,EACA,IAAK,MAAoB;AACrB,WAAO,GAAG,GAAG,MAAM,IAAI,GAAG,GAAG;AAAA,EACjC;AAAA,EACA,IAAK,MAAoB;AACrB,WAAO,GAAG,GAAG,KAAK,IAAI,GAAG,GAAG;AAAA,EAChC;AACJ;AAEA,MAAM,aAAa;AACnB,MAAM,YAAY;AAClB,MAAM,kBAAkB;AACxB,MAAM,kBAAkB;AAGxB,MAAM,UAAU;AAOT,SAAS,QAAS,MAAa,OAAqB;AACvD,QAAM,UAAU,KAAK,QAAQ,SAAS,EAAE;AACxC,MAAI,QAAQ,UAAU,MAAO,QAAO;AACpC,SAAO,QAAQ,MAAM,GAAG,KAAK;AACjC;AAJgB;AAMT,SAAS,UAAW,QAA4B;AACnD,UAAQ,QAAQ;AAAA,IACZ,KAAK;AACD,aAAO,MAAM,IAAI,SAAS;AAAA,IAC9B,KAAK;AACD,aAAO,MAAM,OAAO,UAAU;AAAA,IAClC,KAAK;AACD,aAAO,MAAM,MAAM,UAAU;AAAA,IACjC,KAAK;AACD,aAAO,MAAM,IAAI,UAAU;AAAA,EACnC;AACJ;AAXgB;AAaT,MAAM,cAAiC;AAAA,EA3H9C,OA2H8C;AAAA;AAAA;AAAA,EAClC,OAAsB;AAAA,EACtB,WAA4B;AAAA,EAC5B,WAAyB,CAAC;AAAA,EAC1B,gBAAuB;AAAA,EACvB;AAAA,EACA,gBAAuB;AAAA,EACvB,iBAAmC;AAAA,EACnC,gBAAkC;AAAA,EAE1C,YAAa,QAA0C;AACnD,SAAK,SAAS;AAAA,MACV,cAAa,QAAQ,gBAAgB;AAAA,MACrC,QAAO,QAAQ,UAAU,QAAQ;AAAA,IACrC;AAAA,EACJ;AAAA,EAEQ,mBAA2B;AAC/B,UAAM,aAAa,KAAK,OAAO,OAAO,QAAQ;AAC9C,UAAM,WAAW,IAAI,KAAK,OAAO;AACjC,UAAM,YAAY,aAAa;AAC/B,WAAO,KAAK,IAAI,GAAG,SAAS;AAAA,EAChC;AAAA,EAEA,UACI,SACA,YACA,SACA,UACG;AACH,SAAK,OAAO;AAAA,MACR;AAAA,MACA;AAAA,MACA,SAAQ,QAAQ,IAAI,QAAM;AAAA,QACtB,GAAG;AAAA,QACH,QAAO;AAAA,MACX,EAAE;AAAA,IACN;AACA,SAAK,WAAW,YAAY;AAC5B,SAAK,WAAW,CAAC;AACjB,SAAK,gBAAgB;AACrB,SAAK,OAAO,OAAO,MAAM,KAAK,WAAW,CAAC;AAE1C,QAAI,KAAK,OAAO,OAAO,OAAO;AAC1B,UAAI,KAAK,gBAAgB;AACrB,aAAK,OAAO,OAAO;AAAA,UACf;AAAA,UAAU,KAAK;AAAA,QACnB;AAAA,MACJ;AACA,WAAK,iBAAiB,MAAM,KAAK,OAAO;AACxC,WAAK,OAAO,OAAO;AAAA,QACf;AAAA,QAAU,KAAK;AAAA,MACnB;AAAA,IACJ;AAGA,QAAI,KAAK,eAAe;AACpB,cAAQ,eAAe,UAAU,KAAK,aAAa;AACnD,cAAQ,eAAe,WAAW,KAAK,aAAa;AAAA,IACxD;AACA,SAAK,gBAAgB,MAAM,KAAK,QAAQ;AACxC,YAAQ,GAAG,UAAU,KAAK,aAAa;AACvC,YAAQ,GAAG,WAAW,KAAK,aAAa;AAExC,SAAK,OAAO;AAAA,EAChB;AAAA,EAEA,gBAAiB,KAAY,QAA0B;AACnD,QAAI,CAAC,KAAK,KAAM;AAEhB,UAAM,SAAS,KAAK,KAAK,QAAQ,KAAK,OAAK,EAAE,QAAQ,GAAG;AACxD,QAAI,QAAQ;AACR,aAAO,SAAS;AAEhB,UAAI,WAAW,eAAe;AAC1B,cAAM,cAAc,KAAK,KAAK,QAAQ,QAAQ,MAAM;AACpD,cAAM,gBAAgB,KAAK,iBAAiB;AAC5C,cAAM,eAAe,KAAK,KAAK,QAAQ;AAEvC,YAAI,cAAc,KAAK,eAAe;AAClC,eAAK,gBAAgB;AAAA,QACzB,WAAW,eAAe,KAAK,gBAAgB,eAAe;AAG1D,eAAK,gBAAgB,cAAc,gBAAgB;AAAA,QACvD;AACA,aAAK,gBAAgB,KAAK;AAAA,UACtB;AAAA,UACA,KAAK,IAAI,KAAK,eAAe,eAAe,CAAC;AAAA,QACjD;AAAA,MACJ;AAEA,WAAK,OAAO;AAAA,IAChB;AAAA,EACJ;AAAA,EAEA,IAAK,SAAqB;AACtB,SAAK,SAAS,KAAK,OAAO;AAC1B,QAAI,KAAK,SAAS,SAAS,KAAK,OAAO,cAAc;AACjD,WAAK,SAAS,MAAM;AAAA,IACxB;AACA,SAAK,OAAO;AAAA,EAChB;AAAA,EAEA,UAAgB;AAEZ,QAAI,KAAK,kBAAkB,KAAK,OAAO,OAAO,OAAO;AACjD,WAAK,OAAO,OAAO,eAAe,UAAU,KAAK,cAAc;AAAA,IACnE;AAEA,QAAI,KAAK,eAAe;AACpB,cAAQ,eAAe,UAAU,KAAK,aAAa;AACnD,cAAQ,eAAe,WAAW,KAAK,aAAa;AAAA,IACxD;AACA,SAAK,OAAO,OAAO,MAAM,KAAK,WAAW,CAAC;AAAA,EAC9C;AAAA,EAEQ,SAAe;AACnB,QAAI,CAAC,KAAK,KAAM;AAEhB,QAAI,SAAS;AAEb,QAAI,KAAK,gBAAgB,GAAG;AACxB,gBAAU,KAAK,WAAW,KAAK,gBAAgB,CAAC;AAAA,IACpD;AAEA,UAAM,aAAa,KAAK,KAAK,QAAQ;AAAA,MACjC,OAAK,EAAE,WAAW;AAAA,IACtB,EAAE;AACF,UAAM,YAAY,cAAc,KAAK,UAAU,aAAa;AAC5D,UAAM,QAAQ,KAAK,UAAU,SAAS,KAAK,KAAK,QAAQ;AACxD,UAAM,MAAM,UAAU,IAAI,IAAI,KAAK,MAAO,YAAY,QAAS,GAAG;AAClE,UAAM,QAAQ,KAAK;AAAA,MACf;AAAA,OACC,KAAK,OAAO,OAAO,WAAW,mBAAmB;AAAA,IACtD;AAEA,cAAU,QAAQ,KAAK,KAAK,OAAO,IAC5B,KAAK,KAAK,UAAU,MACpB,GAAG;AAAA;AAGV,UAAM,gBAAgB,KAAK,iBAAiB;AAC5C,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,sBAAsB;AAE1B,QAAI,QAAQ,UAAU,eAAe;AAEjC,iBAAW,UAAU,SAAS;AAC1B,cAAM,MAAM,UAAU,OAAO,MAAM;AACnC,cAAM,OAAO,QAAQ,OAAO,UAAU,QAAQ,CAAC;AAC/C,kBAAU,GAAG,GAAG,IAAI,IAAI;AAAA;AAAA,MAC5B;AACA,4BAAsB,QAAQ;AAAA,IAClC,OAAO;AAEH,WAAK,gBAAgB,KAAK;AAAA,QACtB;AAAA,QACA,KAAK;AAAA,UACD,KAAK;AAAA,UACL,QAAQ,SAAS;AAAA,QACrB;AAAA,MACJ;AAEA,UAAI,eAAe,KAAK,gBAAgB;AACxC,UAAI,iBACA,iBAAiB,eAAe,IAAI;AACxC,UAAI,eACA,KAAK,gBAAgB,iBACrB,QAAQ;AACZ,UAAI,gBAAgB,KAAK;AAAA,QACrB;AAAA,QACA,kBAAkB,eAAe,IAAI;AAAA,MACzC;AAEA,YAAM,WAAW,KAAK;AAAA,QAClB;AAAA,QACA,QAAQ,SAAS,KAAK,IAAI,GAAG,aAAa;AAAA,MAC9C;AACA,UAAI,KAAK,gBAAgB,UAAU;AAC/B,aAAK,gBAAgB;AACrB,uBAAe,KAAK,gBAAgB;AACpC,yBAAiB,iBAAiB,eAAe,IAAI;AACrD,uBACI,KAAK,gBACL,iBAAiB,QAAQ;AAE7B,wBAAgB,KAAK;AAAA,UACjB;AAAA,UACA,kBAAkB,eAAe,IAAI;AAAA,QACzC;AAAA,MACJ;AAGA,UAAI,cAAc;AACd,cAAM,YAAY,KAAK;AACvB,kBAAU,SAAS,SAAS;AAAA;AAAA,MAChC;AAGA,YAAM,iBAAiB,QAAQ;AAAA,QAC3B,KAAK;AAAA,QACL,KAAK,gBAAgB;AAAA,MACzB;AAEA,iBAAW,UAAU,gBAAgB;AACjC,cAAM,MAAM,UAAU,OAAO,MAAM;AACnC,cAAM,OAAO,QAAQ,OAAO,UAAU,QAAQ,CAAC;AAC/C,kBAAU,GAAG,GAAG,IAAI,IAAI;AAAA;AAAA,MAC5B;AAGA,UAAI,cAAc;AACd,cAAM,YACF,QAAQ,UACP,KAAK,gBAAgB;AAE1B,kBAAU,SAAS,SAAS;AAAA;AAAA,MAChC;AAEA,6BAAuB,eAAe,IAAI,KACtC,eAAe,UACd,eAAe,IAAI;AAAA,IAC5B;AAEA,cAAU,IAAI,OAAO,KAAK,IAAI,iBAAiB,KAAK,CAAC,IAAI;AAEzD,UAAM,WAAW,KAAK;AAAA,MAClB;AAAA,MACA,KAAK,OAAO,eAAe,KAAK,SAAS;AAAA,IAC7C;AACA,eAAW,OAAO,KAAK,UAAU;AAC7B,gBAAU,GAAG,QAAQ,KAAK,KAAK,CAAC;AAAA;AAAA,IACpC;AACA,aAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AAC/B,gBAAU;AAAA,IACd;AAEA,SAAK,gBAAgB,IAAI,sBAAsB,KAAK,OAAO;AAC3D,SAAK,OAAO,OAAO,MAAM,MAAM;AAAA,EACnC;AACJ;AAEO,MAAM,aAAgC;AAAA,EA9W7C,OA8W6C;AAAA;AAAA;AAAA,EACzC,UACI,SACA,aACA,SACA,UACG;AACH,UAAM,MAAM,YAAY,SAAS,QAAQ,IACrC,KAAK,SAAS,SAAS,IAAI,SAAS,KAAK,WACzC;AACJ,YAAQ;AAAA,MACJ;AAAA,OAAU,OAAO,GAAG,GAAG,OACvB,QAAQ,IAAI,OAAK,EAAE,QAAQ,EAAE,KAAK,IAAI;AAAA,IAC1C;AAAA,EACJ;AAAA,EAEA,gBAAiB,MAAa,SAA2B;AAAA,EAEzD;AAAA,EAEA,IAAK,SAAqB;AACtB,YAAQ,IAAI,OAAO;AAAA,EACvB;AAAA,EAEA,UAAgB;AAAA,EAEhB;AACJ;AAEO,SAAS,cAAe,QAAoC;AAC/D,QAAM,IAAI,UAAU,QAAQ;AAC5B,MAAI,EAAE,OAAO;AACT,WAAO,IAAI,cAAc,EAAE,QAAO,EAAE,CAAC;AAAA,EACzC;AAEA,SAAO,IAAI,aAAa;AAC5B;AAPgB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/index.js
CHANGED
|
@@ -720,6 +720,15 @@ var color = {
|
|
|
720
720
|
};
|
|
721
721
|
var FILLED_DOT = "\u25CF";
|
|
722
722
|
var EMPTY_DOT = "\u25CB";
|
|
723
|
+
var SEPARATOR_WIDTH = 50;
|
|
724
|
+
var DEFAULT_COLUMNS = 80;
|
|
725
|
+
var ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
726
|
+
function fitLine(line, width) {
|
|
727
|
+
const visible = line.replace(ANSI_RE, "");
|
|
728
|
+
if (visible.length <= width) return line;
|
|
729
|
+
return visible.slice(0, width);
|
|
730
|
+
}
|
|
731
|
+
__name(fitLine, "fitLine");
|
|
723
732
|
function renderDot(status) {
|
|
724
733
|
switch (status) {
|
|
725
734
|
case "pending":
|
|
@@ -738,6 +747,7 @@ var StatusDisplay = class {
|
|
|
738
747
|
__name(this, "StatusDisplay");
|
|
739
748
|
}
|
|
740
749
|
wave = null;
|
|
750
|
+
progress = null;
|
|
741
751
|
messages = [];
|
|
742
752
|
renderedLines = 0;
|
|
743
753
|
config;
|
|
@@ -756,7 +766,7 @@ var StatusDisplay = class {
|
|
|
756
766
|
const available = termHeight - overhead;
|
|
757
767
|
return Math.max(1, available);
|
|
758
768
|
}
|
|
759
|
-
startWave(waveNum, totalWaves, tickets) {
|
|
769
|
+
startWave(waveNum, totalWaves, tickets, progress) {
|
|
760
770
|
this.wave = {
|
|
761
771
|
waveNum,
|
|
762
772
|
totalWaves,
|
|
@@ -765,6 +775,7 @@ var StatusDisplay = class {
|
|
|
765
775
|
status: "pending"
|
|
766
776
|
}))
|
|
767
777
|
};
|
|
778
|
+
this.progress = progress ?? null;
|
|
768
779
|
this.messages = [];
|
|
769
780
|
this.viewportStart = 0;
|
|
770
781
|
this.config.stream.write(ansi.cursorHide());
|
|
@@ -806,10 +817,7 @@ var StatusDisplay = class {
|
|
|
806
817
|
}
|
|
807
818
|
this.viewportStart = Math.max(
|
|
808
819
|
0,
|
|
809
|
-
Math.min(
|
|
810
|
-
this.viewportStart,
|
|
811
|
-
totalTickets - 1
|
|
812
|
-
)
|
|
820
|
+
Math.min(this.viewportStart, totalTickets - 1)
|
|
813
821
|
);
|
|
814
822
|
}
|
|
815
823
|
this.render();
|
|
@@ -838,12 +846,17 @@ var StatusDisplay = class {
|
|
|
838
846
|
if (this.renderedLines > 0) {
|
|
839
847
|
output += ansi.eraseLines(this.renderedLines + 1);
|
|
840
848
|
}
|
|
841
|
-
const
|
|
849
|
+
const doneInWave = this.wave.tickets.filter(
|
|
842
850
|
(t) => t.status === "done"
|
|
843
851
|
).length;
|
|
844
|
-
const
|
|
852
|
+
const completed = doneInWave + (this.progress?.completed ?? 0);
|
|
853
|
+
const total = this.progress?.total ?? this.wave.tickets.length;
|
|
845
854
|
const pct = total === 0 ? 0 : Math.round(completed / total * 100);
|
|
846
|
-
|
|
855
|
+
const width = Math.max(
|
|
856
|
+
1,
|
|
857
|
+
(this.config.stream.columns ?? DEFAULT_COLUMNS) - 1
|
|
858
|
+
);
|
|
859
|
+
output += `Wave ${this.wave.waveNum}/${this.wave.totalWaves} - ${pct}% complete
|
|
847
860
|
`;
|
|
848
861
|
const availableRows = this.getAvailableRows();
|
|
849
862
|
const tickets = this.wave.tickets;
|
|
@@ -851,7 +864,8 @@ var StatusDisplay = class {
|
|
|
851
864
|
if (tickets.length <= availableRows) {
|
|
852
865
|
for (const ticket of tickets) {
|
|
853
866
|
const dot = renderDot(ticket.status);
|
|
854
|
-
|
|
867
|
+
const name = fitLine(ticket.filename, width - 2);
|
|
868
|
+
output += `${dot} ${name}
|
|
855
869
|
`;
|
|
856
870
|
}
|
|
857
871
|
renderedTicketCount = tickets.length;
|
|
@@ -895,7 +909,8 @@ var StatusDisplay = class {
|
|
|
895
909
|
);
|
|
896
910
|
for (const ticket of visibleTickets) {
|
|
897
911
|
const dot = renderDot(ticket.status);
|
|
898
|
-
|
|
912
|
+
const name = fitLine(ticket.filename, width - 2);
|
|
913
|
+
output += `${dot} ${name}
|
|
899
914
|
`;
|
|
900
915
|
}
|
|
901
916
|
if (hasMoreBelow) {
|
|
@@ -905,13 +920,13 @@ var StatusDisplay = class {
|
|
|
905
920
|
}
|
|
906
921
|
renderedTicketCount = (hasMoreAbove ? 1 : 0) + visibleTickets.length + (hasMoreBelow ? 1 : 0);
|
|
907
922
|
}
|
|
908
|
-
output += "-".repeat(
|
|
923
|
+
output += "-".repeat(Math.min(SEPARATOR_WIDTH, width)) + "\n";
|
|
909
924
|
const msgLines = Math.max(
|
|
910
925
|
0,
|
|
911
926
|
this.config.messageLines - this.messages.length
|
|
912
927
|
);
|
|
913
928
|
for (const msg of this.messages) {
|
|
914
|
-
output += `${msg}
|
|
929
|
+
output += `${fitLine(msg, width)}
|
|
915
930
|
`;
|
|
916
931
|
}
|
|
917
932
|
for (let i = 0; i < msgLines; i++) {
|
|
@@ -925,10 +940,11 @@ var PlainDisplay = class {
|
|
|
925
940
|
static {
|
|
926
941
|
__name(this, "PlainDisplay");
|
|
927
942
|
}
|
|
928
|
-
startWave(waveNum, _totalWaves, tickets) {
|
|
943
|
+
startWave(waveNum, _totalWaves, tickets, progress) {
|
|
944
|
+
const pct = progress && progress.total > 0 ? ` (${progress.completed}/${progress.total} done)` : "";
|
|
929
945
|
console.log(
|
|
930
946
|
`
|
|
931
|
-
Wave ${waveNum}: ` + tickets.map((t) => t.filename).join(", ")
|
|
947
|
+
Wave ${waveNum}${pct}: ` + tickets.map((t) => t.filename).join(", ")
|
|
932
948
|
);
|
|
933
949
|
}
|
|
934
950
|
setTicketStatus(_num, _status) {
|
|
@@ -1012,6 +1028,7 @@ function findReadyTickets(tickets) {
|
|
|
1012
1028
|
}
|
|
1013
1029
|
__name(findReadyTickets, "findReadyTickets");
|
|
1014
1030
|
var COMPLETED_RE = /^\[x\]\s+(.+)$/i;
|
|
1031
|
+
var TOOL_USE_RE = /^\[[\w.]+\]/;
|
|
1015
1032
|
function checkItems(ticket, completedItems) {
|
|
1016
1033
|
if (completedItems.length === 0) return;
|
|
1017
1034
|
let body = ticket.body;
|
|
@@ -1315,6 +1332,7 @@ function spawnAgent(opts) {
|
|
|
1315
1332
|
proc.stderr.pipe(logStream);
|
|
1316
1333
|
const rlErr = createInterface({ input: proc.stderr });
|
|
1317
1334
|
rlErr.on("line", (line) => {
|
|
1335
|
+
if (isClaude && !TOOL_USE_RE.test(line)) return;
|
|
1318
1336
|
const output = `${prefix}${line}`;
|
|
1319
1337
|
if (opts.onLine) {
|
|
1320
1338
|
opts.onLine(output);
|
|
@@ -1484,6 +1502,13 @@ async function respawnAndRetryMerge(opts) {
|
|
|
1484
1502
|
return merged;
|
|
1485
1503
|
}
|
|
1486
1504
|
__name(respawnAndRetryMerge, "respawnAndRetryMerge");
|
|
1505
|
+
function runProgress(tickets) {
|
|
1506
|
+
return {
|
|
1507
|
+
completed: tickets.filter((t) => t.status === "done").length,
|
|
1508
|
+
total: tickets.length
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
__name(runProgress, "runProgress");
|
|
1487
1512
|
async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, display) {
|
|
1488
1513
|
mkdirSync2(logsDir, { recursive: true });
|
|
1489
1514
|
const d = display ?? createDisplay();
|
|
@@ -1501,7 +1526,8 @@ async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, di
|
|
|
1501
1526
|
ready.map((t) => ({
|
|
1502
1527
|
num: t.num,
|
|
1503
1528
|
filename: t.filename
|
|
1504
|
-
}))
|
|
1529
|
+
})),
|
|
1530
|
+
runProgress(tickets)
|
|
1505
1531
|
);
|
|
1506
1532
|
const results = await Promise.allSettled(
|
|
1507
1533
|
ready.map(async (t) => {
|
|
@@ -1600,7 +1626,8 @@ async function runWavesIsolated(opts) {
|
|
|
1600
1626
|
ready.map((t) => ({
|
|
1601
1627
|
num: t.num,
|
|
1602
1628
|
filename: t.filename
|
|
1603
|
-
}))
|
|
1629
|
+
})),
|
|
1630
|
+
runProgress(opts.tickets)
|
|
1604
1631
|
);
|
|
1605
1632
|
const worktreeSettled = await Promise.allSettled(
|
|
1606
1633
|
ready.map((t) => createWorktree({
|