nightralph 0.0.15 → 0.0.17
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 +16 -4
- package/dist/display.js +11 -3
- package/dist/display.js.map +2 -2
- package/dist/index.js +119 -28
- package/dist/index.js.map +3 -3
- package/dist/merge.js +31 -19
- package/dist/merge.js.map +2 -2
- package/dist/meta.json +8 -8
- package/dist/orchestrator.js +78 -6
- package/dist/orchestrator.js.map +3 -3
- package/dist/src/display.d.ts +13 -3
- package/dist/src/display.d.ts.map +1 -1
- package/dist/src/merge.d.ts +7 -2
- package/dist/src/merge.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/README.md
CHANGED
|
@@ -214,17 +214,29 @@ This is expected to run for a long time, like overnight.
|
|
|
214
214
|
|
|
215
215
|
### Mac OS
|
|
216
216
|
|
|
217
|
-
|
|
217
|
+
Use `caffeinate` to prevent your machine from sleeping. These examples will
|
|
218
|
+
keep the machine awake, but the display can still go dark.
|
|
219
|
+
|
|
220
|
+
#### Claude Opus 4.6
|
|
218
221
|
|
|
219
222
|
```sh
|
|
220
223
|
caffeinate -i npx nightralph claude -m claude-opus-4-6
|
|
221
224
|
```
|
|
222
225
|
|
|
223
|
-
|
|
226
|
+
#### Claude Sonnet 5
|
|
227
|
+
|
|
228
|
+
```sh
|
|
229
|
+
caffeinate -i npx nightralph claude -m claude-sonnet-5
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### `pi` + OpenRouter model
|
|
224
233
|
|
|
225
234
|
```sh
|
|
226
235
|
caffeinate -i npx nightralph pi -m openrouter/z-ai/glm-5.3-flash
|
|
227
236
|
```
|
|
228
237
|
|
|
229
|
-
|
|
230
|
-
|
|
238
|
+
#### `codex` + Luna
|
|
239
|
+
|
|
240
|
+
```sh
|
|
241
|
+
caffeinate -i npx nightralph codex -m gpt-5.6-luna
|
|
242
|
+
```
|
package/dist/display.js
CHANGED
|
@@ -70,6 +70,7 @@ class StatusDisplay {
|
|
|
70
70
|
}
|
|
71
71
|
wave = null;
|
|
72
72
|
progress = null;
|
|
73
|
+
remaining = [];
|
|
73
74
|
messages = [];
|
|
74
75
|
renderedLines = 0;
|
|
75
76
|
config;
|
|
@@ -88,7 +89,7 @@ class StatusDisplay {
|
|
|
88
89
|
const available = termHeight - overhead;
|
|
89
90
|
return Math.max(1, available);
|
|
90
91
|
}
|
|
91
|
-
startWave(waveNum, totalWaves, tickets, progress) {
|
|
92
|
+
startWave(waveNum, totalWaves, tickets, progress, remaining) {
|
|
92
93
|
this.wave = {
|
|
93
94
|
waveNum,
|
|
94
95
|
totalWaves,
|
|
@@ -97,6 +98,10 @@ class StatusDisplay {
|
|
|
97
98
|
status: "pending"
|
|
98
99
|
}))
|
|
99
100
|
};
|
|
101
|
+
this.remaining = (remaining ?? []).map((t) => ({
|
|
102
|
+
...t,
|
|
103
|
+
status: "pending"
|
|
104
|
+
}));
|
|
100
105
|
this.progress = progress ?? null;
|
|
101
106
|
this.messages = [];
|
|
102
107
|
this.viewportStart = 0;
|
|
@@ -181,7 +186,10 @@ class StatusDisplay {
|
|
|
181
186
|
output += `Wave ${this.wave.waveNum}/${this.wave.totalWaves} - ${pct}% complete
|
|
182
187
|
`;
|
|
183
188
|
const availableRows = this.getAvailableRows();
|
|
184
|
-
const tickets =
|
|
189
|
+
const tickets = [
|
|
190
|
+
...this.wave.tickets,
|
|
191
|
+
...this.remaining
|
|
192
|
+
];
|
|
185
193
|
let renderedTicketCount = 0;
|
|
186
194
|
if (tickets.length <= availableRows) {
|
|
187
195
|
for (const ticket of tickets) {
|
|
@@ -262,7 +270,7 @@ class PlainDisplay {
|
|
|
262
270
|
static {
|
|
263
271
|
__name(this, "PlainDisplay");
|
|
264
272
|
}
|
|
265
|
-
startWave(waveNum, _totalWaves, tickets, progress) {
|
|
273
|
+
startWave(waveNum, _totalWaves, tickets, progress, _remaining) {
|
|
266
274
|
const pct = progress && progress.total > 0 ? ` (${progress.completed}/${progress.total} done)` : "";
|
|
267
275
|
console.log(
|
|
268
276
|
`
|
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\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": ";;
|
|
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 remaining?: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'\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 remaining:Array<DisplayTicket> = []\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 remaining?:ReadonlyArray<{\n num:number\n filename:string\n }>,\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.remaining = (remaining ?? []).map(t => ({\n ...t,\n status:'pending' as const,\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:ReadonlyArray<DisplayTicket> = [\n ...this.wave.tickets,\n ...this.remaining,\n ]\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 _remaining?:ReadonlyArray<{\n num:number\n filename:string\n }>,\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": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/index.js
CHANGED
|
@@ -507,8 +507,10 @@ async function mergeBranch(repoRoot, branch) {
|
|
|
507
507
|
["merge", branch, "--no-edit"],
|
|
508
508
|
{ cwd: repoRoot }
|
|
509
509
|
);
|
|
510
|
-
return true;
|
|
511
|
-
} catch {
|
|
510
|
+
return { success: true };
|
|
511
|
+
} catch (err) {
|
|
512
|
+
const execErr = err;
|
|
513
|
+
const stderr = execErr.stderr || execErr.stdout;
|
|
512
514
|
try {
|
|
513
515
|
await execFileAsync3(
|
|
514
516
|
"git",
|
|
@@ -517,7 +519,7 @@ async function mergeBranch(repoRoot, branch) {
|
|
|
517
519
|
);
|
|
518
520
|
} catch {
|
|
519
521
|
}
|
|
520
|
-
return false;
|
|
522
|
+
return { success: false, stderr };
|
|
521
523
|
}
|
|
522
524
|
}
|
|
523
525
|
__name(mergeBranch, "mergeBranch");
|
|
@@ -538,8 +540,10 @@ async function rebaseBranch(repoRoot, branch, onto, worktreePath) {
|
|
|
538
540
|
["rebase", onto],
|
|
539
541
|
{ cwd: worktreePath }
|
|
540
542
|
);
|
|
541
|
-
return true;
|
|
542
|
-
} catch {
|
|
543
|
+
return { success: true };
|
|
544
|
+
} catch (err) {
|
|
545
|
+
const execErr = err;
|
|
546
|
+
const stderr = execErr.stderr || execErr.stdout;
|
|
543
547
|
try {
|
|
544
548
|
await execFileAsync3(
|
|
545
549
|
"git",
|
|
@@ -548,7 +552,7 @@ async function rebaseBranch(repoRoot, branch, onto, worktreePath) {
|
|
|
548
552
|
);
|
|
549
553
|
} catch {
|
|
550
554
|
}
|
|
551
|
-
return false;
|
|
555
|
+
return { success: false, stderr };
|
|
552
556
|
}
|
|
553
557
|
}
|
|
554
558
|
try {
|
|
@@ -567,8 +571,9 @@ async function rebaseBranch(repoRoot, branch, onto, worktreePath) {
|
|
|
567
571
|
["checkout", onto],
|
|
568
572
|
{ cwd: repoRoot }
|
|
569
573
|
);
|
|
570
|
-
return true;
|
|
571
|
-
} catch {
|
|
574
|
+
return { success: true };
|
|
575
|
+
} catch (err) {
|
|
576
|
+
const stderr = err.stderr;
|
|
572
577
|
try {
|
|
573
578
|
await execFileAsync3(
|
|
574
579
|
"git",
|
|
@@ -585,7 +590,7 @@ async function rebaseBranch(repoRoot, branch, onto, worktreePath) {
|
|
|
585
590
|
);
|
|
586
591
|
} catch {
|
|
587
592
|
}
|
|
588
|
-
return false;
|
|
593
|
+
return { success: false, stderr };
|
|
589
594
|
}
|
|
590
595
|
}
|
|
591
596
|
__name(rebaseBranch, "rebaseBranch");
|
|
@@ -619,13 +624,16 @@ async function mergeWave(opts) {
|
|
|
619
624
|
conflicted: false
|
|
620
625
|
};
|
|
621
626
|
if (i === 0) {
|
|
622
|
-
const
|
|
627
|
+
const result = await mergeBranch(
|
|
623
628
|
repoRoot,
|
|
624
629
|
branch
|
|
625
630
|
);
|
|
626
|
-
mergingResult.merged = success;
|
|
627
|
-
mergingResult.conflicted = !success;
|
|
628
|
-
if (!success) {
|
|
631
|
+
mergingResult.merged = result.success;
|
|
632
|
+
mergingResult.conflicted = !result.success;
|
|
633
|
+
if (!result.success) {
|
|
634
|
+
mergingResult.stderr = result.stderr;
|
|
635
|
+
}
|
|
636
|
+
if (!result.success) {
|
|
629
637
|
if (strategy === "stop") {
|
|
630
638
|
stoppedAt = mergingResult;
|
|
631
639
|
merged.push(mergingResult);
|
|
@@ -635,14 +643,15 @@ async function mergeWave(opts) {
|
|
|
635
643
|
continue;
|
|
636
644
|
}
|
|
637
645
|
} else {
|
|
638
|
-
const
|
|
646
|
+
const rebaseResult = await rebaseBranch(
|
|
639
647
|
repoRoot,
|
|
640
648
|
branch,
|
|
641
649
|
baseBranch,
|
|
642
650
|
worktreePath
|
|
643
651
|
);
|
|
644
|
-
if (!
|
|
652
|
+
if (!rebaseResult.success) {
|
|
645
653
|
mergingResult.conflicted = true;
|
|
654
|
+
mergingResult.stderr = rebaseResult.stderr;
|
|
646
655
|
if (strategy === "stop") {
|
|
647
656
|
stoppedAt = mergingResult;
|
|
648
657
|
merged.push(mergingResult);
|
|
@@ -652,13 +661,16 @@ async function mergeWave(opts) {
|
|
|
652
661
|
continue;
|
|
653
662
|
}
|
|
654
663
|
}
|
|
655
|
-
const
|
|
664
|
+
const mergeResult = await mergeBranch(
|
|
656
665
|
repoRoot,
|
|
657
666
|
branch
|
|
658
667
|
);
|
|
659
|
-
mergingResult.merged =
|
|
660
|
-
mergingResult.conflicted = !
|
|
661
|
-
if (!
|
|
668
|
+
mergingResult.merged = mergeResult.success;
|
|
669
|
+
mergingResult.conflicted = !mergeResult.success;
|
|
670
|
+
if (!mergeResult.success) {
|
|
671
|
+
mergingResult.stderr = mergeResult.stderr;
|
|
672
|
+
}
|
|
673
|
+
if (!mergeResult.success) {
|
|
662
674
|
if (strategy === "stop") {
|
|
663
675
|
stoppedAt = mergingResult;
|
|
664
676
|
merged.push(mergingResult);
|
|
@@ -748,6 +760,7 @@ var StatusDisplay = class {
|
|
|
748
760
|
}
|
|
749
761
|
wave = null;
|
|
750
762
|
progress = null;
|
|
763
|
+
remaining = [];
|
|
751
764
|
messages = [];
|
|
752
765
|
renderedLines = 0;
|
|
753
766
|
config;
|
|
@@ -766,7 +779,7 @@ var StatusDisplay = class {
|
|
|
766
779
|
const available = termHeight - overhead;
|
|
767
780
|
return Math.max(1, available);
|
|
768
781
|
}
|
|
769
|
-
startWave(waveNum, totalWaves, tickets, progress) {
|
|
782
|
+
startWave(waveNum, totalWaves, tickets, progress, remaining) {
|
|
770
783
|
this.wave = {
|
|
771
784
|
waveNum,
|
|
772
785
|
totalWaves,
|
|
@@ -775,6 +788,10 @@ var StatusDisplay = class {
|
|
|
775
788
|
status: "pending"
|
|
776
789
|
}))
|
|
777
790
|
};
|
|
791
|
+
this.remaining = (remaining ?? []).map((t) => ({
|
|
792
|
+
...t,
|
|
793
|
+
status: "pending"
|
|
794
|
+
}));
|
|
778
795
|
this.progress = progress ?? null;
|
|
779
796
|
this.messages = [];
|
|
780
797
|
this.viewportStart = 0;
|
|
@@ -859,7 +876,10 @@ var StatusDisplay = class {
|
|
|
859
876
|
output += `Wave ${this.wave.waveNum}/${this.wave.totalWaves} - ${pct}% complete
|
|
860
877
|
`;
|
|
861
878
|
const availableRows = this.getAvailableRows();
|
|
862
|
-
const tickets =
|
|
879
|
+
const tickets = [
|
|
880
|
+
...this.wave.tickets,
|
|
881
|
+
...this.remaining
|
|
882
|
+
];
|
|
863
883
|
let renderedTicketCount = 0;
|
|
864
884
|
if (tickets.length <= availableRows) {
|
|
865
885
|
for (const ticket of tickets) {
|
|
@@ -940,7 +960,7 @@ var PlainDisplay = class {
|
|
|
940
960
|
static {
|
|
941
961
|
__name(this, "PlainDisplay");
|
|
942
962
|
}
|
|
943
|
-
startWave(waveNum, _totalWaves, tickets, progress) {
|
|
963
|
+
startWave(waveNum, _totalWaves, tickets, progress, _remaining) {
|
|
944
964
|
const pct = progress && progress.total > 0 ? ` (${progress.completed}/${progress.total} done)` : "";
|
|
945
965
|
console.log(
|
|
946
966
|
`
|
|
@@ -1081,6 +1101,29 @@ function renderPrompt(specBody, ticket) {
|
|
|
1081
1101
|
].join("\n");
|
|
1082
1102
|
}
|
|
1083
1103
|
__name(renderPrompt, "renderPrompt");
|
|
1104
|
+
function renderMergePrompt(specBody, ticket) {
|
|
1105
|
+
return [
|
|
1106
|
+
"---- SPEC CONTEXT ----",
|
|
1107
|
+
specBody,
|
|
1108
|
+
"",
|
|
1109
|
+
"---- TICKET ----",
|
|
1110
|
+
ticket.filename,
|
|
1111
|
+
ticket.body,
|
|
1112
|
+
"",
|
|
1113
|
+
"---- AGENT INSTRUCTIONS ----",
|
|
1114
|
+
"You are an autonomous coding agent resolving a merge conflict.",
|
|
1115
|
+
"",
|
|
1116
|
+
"Another agent already implemented this ticket on a feature branch. The branch was then merged with the base branch, but conflicts arose. The working tree may contain conflict markers that need resolution.",
|
|
1117
|
+
"",
|
|
1118
|
+
"Your task:",
|
|
1119
|
+
"1. Find and resolve all conflict markers (<<<<<<< / ======= / >>>>>>>).",
|
|
1120
|
+
"2. Make sure the code compiles and tests pass.",
|
|
1121
|
+
"3. Commit the resolved files.",
|
|
1122
|
+
"",
|
|
1123
|
+
"Do NOT re-implement the ticket from scratch."
|
|
1124
|
+
].join("\n");
|
|
1125
|
+
}
|
|
1126
|
+
__name(renderMergePrompt, "renderMergePrompt");
|
|
1084
1127
|
function listTickets(tickets) {
|
|
1085
1128
|
console.log("\nTickets:");
|
|
1086
1129
|
for (const t of tickets) {
|
|
@@ -1490,7 +1533,10 @@ async function respawnAndRetryMerge(opts) {
|
|
|
1490
1533
|
} else {
|
|
1491
1534
|
console.log(msg1);
|
|
1492
1535
|
}
|
|
1493
|
-
const prompt =
|
|
1536
|
+
const prompt = renderMergePrompt(
|
|
1537
|
+
opts.specBody,
|
|
1538
|
+
opts.ticket
|
|
1539
|
+
);
|
|
1494
1540
|
const logPath = join3(
|
|
1495
1541
|
opts.logsDir,
|
|
1496
1542
|
opts.ticket.filename.replace(/\.md$/, ".respawn.log")
|
|
@@ -1518,19 +1564,27 @@ async function respawnAndRetryMerge(opts) {
|
|
|
1518
1564
|
}
|
|
1519
1565
|
return false;
|
|
1520
1566
|
}
|
|
1521
|
-
const
|
|
1567
|
+
const mergeResult = await mergeBranch(
|
|
1522
1568
|
opts.repoRoot,
|
|
1523
1569
|
opts.branch
|
|
1524
1570
|
);
|
|
1525
|
-
if (!
|
|
1571
|
+
if (!mergeResult.success) {
|
|
1526
1572
|
const msg3 = ` ${opts.ticket.filename}: merge still conflicted after respawn`;
|
|
1527
1573
|
if (opts.onLine) {
|
|
1528
1574
|
opts.onLine(msg3);
|
|
1529
1575
|
} else {
|
|
1530
1576
|
console.log(msg3);
|
|
1531
1577
|
}
|
|
1578
|
+
if (mergeResult.stderr) {
|
|
1579
|
+
const stderrMsg = ` git: ${mergeResult.stderr.trim()}`;
|
|
1580
|
+
if (opts.onLine) {
|
|
1581
|
+
opts.onLine(stderrMsg);
|
|
1582
|
+
} else {
|
|
1583
|
+
console.log(stderrMsg);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1532
1586
|
}
|
|
1533
|
-
return
|
|
1587
|
+
return mergeResult.success;
|
|
1534
1588
|
}
|
|
1535
1589
|
__name(respawnAndRetryMerge, "respawnAndRetryMerge");
|
|
1536
1590
|
function runProgress(tickets) {
|
|
@@ -1551,6 +1605,10 @@ async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, di
|
|
|
1551
1605
|
const ready = findReadyTickets(tickets);
|
|
1552
1606
|
if (ready.length === 0) break;
|
|
1553
1607
|
waveNum++;
|
|
1608
|
+
const readyNums = new Set(ready.map((t) => t.num));
|
|
1609
|
+
const remaining2 = tickets.filter(
|
|
1610
|
+
(t) => t.status === "ready-for-agent" && !readyNums.has(t.num)
|
|
1611
|
+
);
|
|
1554
1612
|
d.startWave(
|
|
1555
1613
|
waveNum,
|
|
1556
1614
|
totalWaves,
|
|
@@ -1558,7 +1616,11 @@ async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, di
|
|
|
1558
1616
|
num: t.num,
|
|
1559
1617
|
filename: t.filename
|
|
1560
1618
|
})),
|
|
1561
|
-
runProgress(tickets)
|
|
1619
|
+
runProgress(tickets),
|
|
1620
|
+
remaining2.map((t) => ({
|
|
1621
|
+
num: t.num,
|
|
1622
|
+
filename: t.filename
|
|
1623
|
+
}))
|
|
1562
1624
|
);
|
|
1563
1625
|
const results = await Promise.allSettled(
|
|
1564
1626
|
ready.map(async (t) => {
|
|
@@ -1631,6 +1693,11 @@ async function runWavesIsolated(opts) {
|
|
|
1631
1693
|
const repoRoot = getRepoRoot();
|
|
1632
1694
|
const repoName = getRepoName();
|
|
1633
1695
|
const baseBranch = getCurrentBranch();
|
|
1696
|
+
if (isDirty(repoRoot)) {
|
|
1697
|
+
throw new Error(
|
|
1698
|
+
"Working tree is dirty; commit or stash changes before running"
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1634
1701
|
const progressPath = join3(
|
|
1635
1702
|
dirname(opts.logsDir),
|
|
1636
1703
|
"progress.md"
|
|
@@ -1651,6 +1718,10 @@ async function runWavesIsolated(opts) {
|
|
|
1651
1718
|
const ready = findReadyTickets(opts.tickets);
|
|
1652
1719
|
if (ready.length === 0) break;
|
|
1653
1720
|
waveNum++;
|
|
1721
|
+
const readyNums = new Set(ready.map((t) => t.num));
|
|
1722
|
+
const remaining2 = opts.tickets.filter(
|
|
1723
|
+
(t) => t.status === "ready-for-agent" && !readyNums.has(t.num)
|
|
1724
|
+
);
|
|
1654
1725
|
d.startWave(
|
|
1655
1726
|
waveNum,
|
|
1656
1727
|
totalWaves,
|
|
@@ -1658,7 +1729,11 @@ async function runWavesIsolated(opts) {
|
|
|
1658
1729
|
num: t.num,
|
|
1659
1730
|
filename: t.filename
|
|
1660
1731
|
})),
|
|
1661
|
-
runProgress(opts.tickets)
|
|
1732
|
+
runProgress(opts.tickets),
|
|
1733
|
+
remaining2.map((t) => ({
|
|
1734
|
+
num: t.num,
|
|
1735
|
+
filename: t.filename
|
|
1736
|
+
}))
|
|
1662
1737
|
);
|
|
1663
1738
|
const worktreeSettled = await Promise.allSettled(
|
|
1664
1739
|
ready.map((t) => createWorktree({
|
|
@@ -1871,6 +1946,22 @@ async function runWavesIsolated(opts) {
|
|
|
1871
1946
|
d.log(
|
|
1872
1947
|
` ${ticket.filename}: merge failed, staying ready-for-agent`
|
|
1873
1948
|
);
|
|
1949
|
+
if (branchResult.stderr) {
|
|
1950
|
+
d.log(
|
|
1951
|
+
" git: " + branchResult.stderr.trim()
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
checkTicket(progress, ticket.num, {
|
|
1955
|
+
branch: branchResult.branch,
|
|
1956
|
+
exitCode: 0,
|
|
1957
|
+
done: false
|
|
1958
|
+
});
|
|
1959
|
+
writeProgress(progressPath, progress);
|
|
1960
|
+
await commitProgress(
|
|
1961
|
+
repoRoot,
|
|
1962
|
+
progressPath,
|
|
1963
|
+
`nightralph: ${ticket.filename} merge failed`
|
|
1964
|
+
);
|
|
1874
1965
|
}
|
|
1875
1966
|
}
|
|
1876
1967
|
}
|