nightralph 0.0.7 → 0.0.9
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 +2 -1
- package/dist/demo-display.js +298 -0
- package/dist/display.js +280 -0
- package/dist/display.js.map +7 -0
- package/dist/index.js +6 -2
- package/dist/index.js.map +2 -2
- package/dist/meta.json +49 -8
- package/dist/orchestrator.js +158 -60
- package/dist/orchestrator.js.map +2 -2
- package/dist/src/display.d.ts +69 -0
- package/dist/src/display.d.ts.map +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/orchestrator.d.ts +12 -3
- package/dist/src/orchestrator.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport yargs from 'yargs'\nimport { hideBin } from 'yargs/helpers'\nimport { select } from '@inquirer/prompts'\nimport { setup, checkGhAuth, type TrackerKind } from './setup.js'\nimport {\n scanTickets,\n dryRun,\n dryRunIsolated,\n runWaves,\n runWavesIsolated,\n} from './orchestrator.js'\nimport {\n findFeatureDirs,\n resolveIssuesDir,\n getScriptDir,\n} from './resolve.js'\nimport { getRepoRoot, getRepoName } from './worktree.js'\n\nexport { findFeatureDirs, resolveIssuesDir }\n\nconst scriptDir = getScriptDir(import.meta.url)\n\nyargs(hideBin(process.argv))\n .scriptName('nightralph')\n .usage('$0 <command>')\n .command(\n 'setup',\n 'Install bundled skills and doc templates',\n y => y.option('claude', {\n type:'boolean',\n describe:'Also create .claude/skills/ symlinks',\n default:false,\n }),\n async argv => {\n const tracker = await select<TrackerKind>({\n message:'Issue tracker format',\n choices:[\n {\n name:'Local Markdown',\n value:'local' as const,\n description:'Issues as .md files in ' +\n '.scratch/',\n },\n {\n name:'GitHub Issues',\n value:'github' as const,\n description:'Issues via gh CLI',\n },\n ],\n })\n\n if (tracker === 'github') {\n if (!checkGhAuth()) {\n console.error(\n 'GitHub Issues requires the gh CLI, ' +\n 'authenticated.\\n' +\n 'Install: https://cli.github.com\\n' +\n 'Then run: gh auth login'\n )\n process.exit(2)\n }\n }\n\n setup({\n bundledSkillsDir:join(scriptDir, 'skills'),\n docsTemplatesDir:join(\n scriptDir, 'docs-templates'\n ),\n projectDir:process.cwd(),\n claude:argv.claude,\n tracker,\n })\n }\n )\n .command(\n '$0 <provider>',\n 'Execute tickets with the given provider',\n y => y\n .positional('provider', {\n type:'string',\n describe:'Agent CLI to spawn ' +\n '(e.g. claude, codex, pi)',\n demandOption:true,\n })\n .option('m', {\n alias:'model',\n type:'string',\n describe:'Model passed to the agent',\n })\n .option('spec', {\n type:'string',\n describe:'Feature name ' +\n '(resolves .scratch/<name>/)',\n })\n .option('dry-run', {\n type:'boolean',\n describe:'Show wave order without running',\n default:false,\n })\n .option('timeout', {\n type:'number',\n describe:'Kill agent after N seconds',\n default:900,\n })\n .option('X', {\n type:'boolean',\n describe:'Stop on merge conflict instead of ' +\n 're-spawning',\n default:false,\n }),\n async argv => {\n await runExecute({\n provider:argv.provider as string,\n model:argv.model as string|undefined,\n spec:argv.spec as string|undefined,\n dryRun:argv.dryRun as boolean|undefined,\n timeout:argv.timeout as\n number|undefined,\n stopOnConflict:argv.X as\n boolean|undefined,\n })\n }\n )\n .strict()\n .demandCommand(1, 'Specify a command or provider')\n .help()\n .parse()\n\nfunction isGitRepo ():boolean {\n try {\n getRepoRoot()\n return true\n } catch {\n return false\n }\n}\n\nfunction extractFeatureName (spec:string):string {\n const parts = spec.split(\n /[\\\\/]/\n )\n const scratchIndex = parts.indexOf('.scratch')\n if (scratchIndex >= 0 && scratchIndex < parts.length - 1) {\n return parts[scratchIndex + 1]\n }\n return parts[parts.length - 2] ??\n parts[parts.length - 1] ?? 'feature'\n}\n\nasync function runExecute (argv:{\n provider:string\n model?:string\n spec?:string\n dryRun?:boolean\n timeout?:number\n stopOnConflict?:boolean\n}):Promise<void> {\n const { dir, spec } = await resolveIssuesDir(\n argv.spec\n )\n\n if (!existsSync(dir)) {\n console.error(`Directory not found: ${dir}`)\n process.exit(2)\n }\n if (!existsSync(spec)) {\n console.error(`Spec file not found: ${spec}`)\n process.exit(2)\n }\n\n const specBody = readFileSync(spec, 'utf8')\n const tickets = scanTickets(dir)\n\n if (tickets.length === 0) {\n console.log('No ticket files found. Exiting.')\n return\n }\n\n console.log(`Found ${tickets.length} tickets`)\n\n const knownNums = new Set(tickets.map(t => t.num))\n for (const t of tickets) {\n const unknown = t.blockers.filter(\n b => !knownNums.has(b)\n )\n if (unknown.length > 0) {\n console.warn(\n `${t.filename}: blockers ` +\n `[${unknown.join(', ')}] ` +\n 'not found in directory'\n )\n }\n t.blockers = t.blockers.filter(\n b => knownNums.has(b)\n )\n }\n\n const logsDir = join(dirname(dir), 'logs')\n const timeout = argv.timeout ?? 300\n const featureName = extractFeatureName(spec)\n const conflictStrategy = argv.stopOnConflict ?\n 'stop' :\n 'respawn'\n const progressPath = join(\n dirname(dir), 'progress.md'\n )\n\n if (argv.dryRun) {\n if (isGitRepo()) {\n const repoName = getRepoName()\n dryRunIsolated({\n tickets,\n specBody,\n repoName,\n conflictStrategy,\n progressPath,\n })\n } else {\n dryRun(tickets, specBody)\n }\n return\n }\n\n let result:{ allDone:boolean }\n\n if (isGitRepo()) {\n result = await runWavesIsolated({\n tickets,\n specBody,\n agentCmd:argv.provider,\n model:argv.model ?? null,\n timeout,\n logsDir,\n featureName,\n conflictStrategy,\n })\n } else {\n result = await runWaves(\n tickets, specBody,\n argv.provider, argv.model ?? null,\n timeout,\n logsDir,\n )\n }\n\n if (!result.allDone) {\n process.exit(1)\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport yargs from 'yargs'\nimport { hideBin } from 'yargs/helpers'\nimport { select } from '@inquirer/prompts'\nimport { setup, checkGhAuth, type TrackerKind } from './setup.js'\nimport {\n scanTickets,\n dryRun,\n dryRunIsolated,\n runWaves,\n runWavesIsolated,\n} from './orchestrator.js'\nimport { createDisplay } from './display.js'\nimport {\n findFeatureDirs,\n resolveIssuesDir,\n getScriptDir,\n} from './resolve.js'\nimport { getRepoRoot, getRepoName } from './worktree.js'\n\nexport { findFeatureDirs, resolveIssuesDir }\n\nconst scriptDir = getScriptDir(import.meta.url)\n\nyargs(hideBin(process.argv))\n .scriptName('nightralph')\n .usage('$0 <command>')\n .command(\n 'setup',\n 'Install bundled skills and doc templates',\n y => y.option('claude', {\n type:'boolean',\n describe:'Also create .claude/skills/ symlinks',\n default:false,\n }),\n async argv => {\n const tracker = await select<TrackerKind>({\n message:'Issue tracker format',\n choices:[\n {\n name:'Local Markdown',\n value:'local' as const,\n description:'Issues as .md files in ' +\n '.scratch/',\n },\n {\n name:'GitHub Issues',\n value:'github' as const,\n description:'Issues via gh CLI',\n },\n ],\n })\n\n if (tracker === 'github') {\n if (!checkGhAuth()) {\n console.error(\n 'GitHub Issues requires the gh CLI, ' +\n 'authenticated.\\n' +\n 'Install: https://cli.github.com\\n' +\n 'Then run: gh auth login'\n )\n process.exit(2)\n }\n }\n\n setup({\n bundledSkillsDir:join(scriptDir, 'skills'),\n docsTemplatesDir:join(\n scriptDir, 'docs-templates'\n ),\n projectDir:process.cwd(),\n claude:argv.claude,\n tracker,\n })\n }\n )\n .command(\n '$0 <provider>',\n 'Execute tickets with the given provider',\n y => y\n .positional('provider', {\n type:'string',\n describe:'Agent CLI to spawn ' +\n '(e.g. claude, codex, pi)',\n demandOption:true,\n })\n .option('m', {\n alias:'model',\n type:'string',\n describe:'Model passed to the agent',\n })\n .option('spec', {\n type:'string',\n describe:'Feature name ' +\n '(resolves .scratch/<name>/)',\n })\n .option('dry-run', {\n type:'boolean',\n describe:'Show wave order without running',\n default:false,\n })\n .option('timeout', {\n type:'number',\n describe:'Kill agent after N seconds',\n default:900,\n })\n .option('X', {\n type:'boolean',\n describe:'Stop on merge conflict instead of ' +\n 're-spawning',\n default:false,\n }),\n async argv => {\n await runExecute({\n provider:argv.provider as string,\n model:argv.model as string|undefined,\n spec:argv.spec as string|undefined,\n dryRun:argv.dryRun as boolean|undefined,\n timeout:argv.timeout as\n number|undefined,\n stopOnConflict:argv.X as\n boolean|undefined,\n })\n }\n )\n .strict()\n .demandCommand(1, 'Specify a command or provider')\n .help()\n .parse()\n\nfunction isGitRepo ():boolean {\n try {\n getRepoRoot()\n return true\n } catch {\n return false\n }\n}\n\nfunction extractFeatureName (spec:string):string {\n const parts = spec.split(\n /[\\\\/]/\n )\n const scratchIndex = parts.indexOf('.scratch')\n if (scratchIndex >= 0 && scratchIndex < parts.length - 1) {\n return parts[scratchIndex + 1]\n }\n return parts[parts.length - 2] ??\n parts[parts.length - 1] ?? 'feature'\n}\n\nasync function runExecute (argv:{\n provider:string\n model?:string\n spec?:string\n dryRun?:boolean\n timeout?:number\n stopOnConflict?:boolean\n}):Promise<void> {\n const { dir, spec } = await resolveIssuesDir(\n argv.spec\n )\n\n if (!existsSync(dir)) {\n console.error(`Directory not found: ${dir}`)\n process.exit(2)\n }\n if (!existsSync(spec)) {\n console.error(`Spec file not found: ${spec}`)\n process.exit(2)\n }\n\n const specBody = readFileSync(spec, 'utf8')\n const tickets = scanTickets(dir)\n\n if (tickets.length === 0) {\n console.log('No ticket files found. Exiting.')\n return\n }\n\n console.log(`Found ${tickets.length} tickets`)\n\n const knownNums = new Set(tickets.map(t => t.num))\n for (const t of tickets) {\n const unknown = t.blockers.filter(\n b => !knownNums.has(b)\n )\n if (unknown.length > 0) {\n console.warn(\n `${t.filename}: blockers ` +\n `[${unknown.join(', ')}] ` +\n 'not found in directory'\n )\n }\n t.blockers = t.blockers.filter(\n b => knownNums.has(b)\n )\n }\n\n const logsDir = join(dirname(dir), 'logs')\n const timeout = argv.timeout ?? 300\n const featureName = extractFeatureName(spec)\n const conflictStrategy = argv.stopOnConflict ?\n 'stop' :\n 'respawn'\n const progressPath = join(\n dirname(dir), 'progress.md'\n )\n\n if (argv.dryRun) {\n if (isGitRepo()) {\n const repoName = getRepoName()\n dryRunIsolated({\n tickets,\n specBody,\n repoName,\n conflictStrategy,\n progressPath,\n })\n } else {\n dryRun(tickets, specBody)\n }\n return\n }\n\n const display = createDisplay()\n let result:{ allDone:boolean }\n\n if (isGitRepo()) {\n result = await runWavesIsolated({\n tickets,\n specBody,\n agentCmd:argv.provider,\n model:argv.model ?? null,\n timeout,\n logsDir,\n featureName,\n conflictStrategy,\n display,\n })\n } else {\n result = await runWaves(\n tickets, specBody,\n argv.provider, argv.model ?? null,\n timeout,\n logsDir,\n display,\n )\n }\n\n if (!result.allDone) {\n process.exit(1)\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/meta.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"inputs": {
|
|
3
3
|
"src/index.ts": {
|
|
4
|
-
"bytes":
|
|
4
|
+
"bytes": 7134,
|
|
5
5
|
"imports": [],
|
|
6
6
|
"format": "esm"
|
|
7
7
|
},
|
|
8
8
|
"src/orchestrator.ts": {
|
|
9
|
-
"bytes":
|
|
9
|
+
"bytes": 33865,
|
|
10
10
|
"imports": [],
|
|
11
11
|
"format": "esm"
|
|
12
12
|
},
|
|
@@ -34,6 +34,11 @@
|
|
|
34
34
|
"bytes": 6529,
|
|
35
35
|
"imports": [],
|
|
36
36
|
"format": "esm"
|
|
37
|
+
},
|
|
38
|
+
"src/display.ts": {
|
|
39
|
+
"bytes": 10791,
|
|
40
|
+
"imports": [],
|
|
41
|
+
"format": "esm"
|
|
37
42
|
}
|
|
38
43
|
},
|
|
39
44
|
"outputs": {
|
|
@@ -41,7 +46,7 @@
|
|
|
41
46
|
"imports": [],
|
|
42
47
|
"exports": [],
|
|
43
48
|
"inputs": {},
|
|
44
|
-
"bytes":
|
|
49
|
+
"bytes": 11126
|
|
45
50
|
},
|
|
46
51
|
"dist/index.js": {
|
|
47
52
|
"imports": [
|
|
@@ -80,6 +85,11 @@
|
|
|
80
85
|
"kind": "import-statement",
|
|
81
86
|
"external": true
|
|
82
87
|
},
|
|
88
|
+
{
|
|
89
|
+
"path": "./display.js",
|
|
90
|
+
"kind": "import-statement",
|
|
91
|
+
"external": true
|
|
92
|
+
},
|
|
83
93
|
{
|
|
84
94
|
"path": "./resolve.js",
|
|
85
95
|
"kind": "import-statement",
|
|
@@ -98,16 +108,16 @@
|
|
|
98
108
|
"entryPoint": "src/index.ts",
|
|
99
109
|
"inputs": {
|
|
100
110
|
"src/index.ts": {
|
|
101
|
-
"bytesInOutput":
|
|
111
|
+
"bytesInOutput": 5338
|
|
102
112
|
}
|
|
103
113
|
},
|
|
104
|
-
"bytes":
|
|
114
|
+
"bytes": 5571
|
|
105
115
|
},
|
|
106
116
|
"dist/orchestrator.js.map": {
|
|
107
117
|
"imports": [],
|
|
108
118
|
"exports": [],
|
|
109
119
|
"inputs": {},
|
|
110
|
-
"bytes":
|
|
120
|
+
"bytes": 52948
|
|
111
121
|
},
|
|
112
122
|
"dist/orchestrator.js": {
|
|
113
123
|
"imports": [
|
|
@@ -150,10 +160,17 @@
|
|
|
150
160
|
"path": "./merge.js",
|
|
151
161
|
"kind": "import-statement",
|
|
152
162
|
"external": true
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"path": "./display.js",
|
|
166
|
+
"kind": "import-statement",
|
|
167
|
+
"external": true
|
|
153
168
|
}
|
|
154
169
|
],
|
|
155
170
|
"exports": [
|
|
171
|
+
"COMPLETED_RE",
|
|
156
172
|
"TICKET_RE",
|
|
173
|
+
"checkItems",
|
|
157
174
|
"createPiStreamFormatter",
|
|
158
175
|
"dryRun",
|
|
159
176
|
"dryRunIsolated",
|
|
@@ -174,10 +191,10 @@
|
|
|
174
191
|
"entryPoint": "src/orchestrator.ts",
|
|
175
192
|
"inputs": {
|
|
176
193
|
"src/orchestrator.ts": {
|
|
177
|
-
"bytesInOutput":
|
|
194
|
+
"bytesInOutput": 26158
|
|
178
195
|
}
|
|
179
196
|
},
|
|
180
|
-
"bytes":
|
|
197
|
+
"bytes": 26662
|
|
181
198
|
},
|
|
182
199
|
"dist/setup.js.map": {
|
|
183
200
|
"imports": [],
|
|
@@ -381,6 +398,30 @@
|
|
|
381
398
|
}
|
|
382
399
|
},
|
|
383
400
|
"bytes": 4616
|
|
401
|
+
},
|
|
402
|
+
"dist/display.js.map": {
|
|
403
|
+
"imports": [],
|
|
404
|
+
"exports": [],
|
|
405
|
+
"inputs": {},
|
|
406
|
+
"bytes": 16701
|
|
407
|
+
},
|
|
408
|
+
"dist/display.js": {
|
|
409
|
+
"imports": [],
|
|
410
|
+
"exports": [
|
|
411
|
+
"PlainDisplay",
|
|
412
|
+
"StatusDisplay",
|
|
413
|
+
"ansi",
|
|
414
|
+
"color",
|
|
415
|
+
"createDisplay",
|
|
416
|
+
"renderDot"
|
|
417
|
+
],
|
|
418
|
+
"entryPoint": "src/display.ts",
|
|
419
|
+
"inputs": {
|
|
420
|
+
"src/display.ts": {
|
|
421
|
+
"bytesInOutput": 7276
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
"bytes": 7532
|
|
384
425
|
}
|
|
385
426
|
}
|
|
386
427
|
}
|
package/dist/orchestrator.js
CHANGED
|
@@ -35,6 +35,9 @@ import {
|
|
|
35
35
|
mergeWave,
|
|
36
36
|
updateWorktreeFromBase
|
|
37
37
|
} from "./merge.js";
|
|
38
|
+
import {
|
|
39
|
+
createDisplay
|
|
40
|
+
} from "./display.js";
|
|
38
41
|
function formatErrorMessage(error) {
|
|
39
42
|
return error instanceof Error ? error.message : String(error);
|
|
40
43
|
}
|
|
@@ -97,6 +100,27 @@ function findReadyTickets(tickets) {
|
|
|
97
100
|
});
|
|
98
101
|
}
|
|
99
102
|
__name(findReadyTickets, "findReadyTickets");
|
|
103
|
+
const COMPLETED_RE = /^\[x\]\s+(.+)$/i;
|
|
104
|
+
function checkItems(ticket, completedItems) {
|
|
105
|
+
if (completedItems.length === 0) return;
|
|
106
|
+
let body = ticket.body;
|
|
107
|
+
for (const item of completedItems) {
|
|
108
|
+
const escaped = item.replace(
|
|
109
|
+
/[.*+?^${}()|[\]\\]/g,
|
|
110
|
+
"\\$&"
|
|
111
|
+
);
|
|
112
|
+
body = body.replace(
|
|
113
|
+
new RegExp(
|
|
114
|
+
`- \\[ \\] ${escaped}`,
|
|
115
|
+
"i"
|
|
116
|
+
),
|
|
117
|
+
`- [x] ${item}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
writeFileSync(ticket.filepath, body);
|
|
121
|
+
ticket.body = body;
|
|
122
|
+
}
|
|
123
|
+
__name(checkItems, "checkItems");
|
|
100
124
|
function markDone(ticket) {
|
|
101
125
|
const updated = ticket.body.replace(
|
|
102
126
|
/\*\*Status:\*\*\s*.+/i,
|
|
@@ -124,6 +148,7 @@ function renderPrompt(specBody, ticket) {
|
|
|
124
148
|
"- Do NOT research external APIs or services via web search. Use the spec and ticket body as your sole reference for API shapes.",
|
|
125
149
|
"- Start writing code as soon as you understand the immediate context. You can read more files as needed while implementing.",
|
|
126
150
|
"- Commit your work when finished.",
|
|
151
|
+
"- After completing each checklist item in the ticket, print a line: [x] <item text> (matching the checklist text exactly).",
|
|
127
152
|
""
|
|
128
153
|
].join("\n");
|
|
129
154
|
}
|
|
@@ -254,7 +279,6 @@ function formatStreamLine(line) {
|
|
|
254
279
|
__name(formatStreamLine, "formatStreamLine");
|
|
255
280
|
function createPiStreamFormatter() {
|
|
256
281
|
const calls = /* @__PURE__ */ new Map();
|
|
257
|
-
let toolNum = 0;
|
|
258
282
|
return (line) => {
|
|
259
283
|
let obj;
|
|
260
284
|
try {
|
|
@@ -270,7 +294,6 @@ function createPiStreamFormatter() {
|
|
|
270
294
|
return evt.delta ?? null;
|
|
271
295
|
}
|
|
272
296
|
if (type === "toolcall_start") {
|
|
273
|
-
toolNum++;
|
|
274
297
|
calls.set(ci, {
|
|
275
298
|
name: evt.toolName ?? "?",
|
|
276
299
|
buf: ""
|
|
@@ -338,26 +361,55 @@ function spawnAgent(opts) {
|
|
|
338
361
|
});
|
|
339
362
|
proc.stdout.pipe(logStream);
|
|
340
363
|
const piFormatter = isPi ? createPiStreamFormatter() : null;
|
|
364
|
+
const completedItems = [];
|
|
365
|
+
function collectCompleted(text) {
|
|
366
|
+
const match = text.match(COMPLETED_RE);
|
|
367
|
+
if (match) completedItems.push(match[1]);
|
|
368
|
+
}
|
|
369
|
+
__name(collectCompleted, "collectCompleted");
|
|
341
370
|
const rl = createInterface({ input: proc.stdout });
|
|
342
371
|
rl.on("line", (line) => {
|
|
343
372
|
if (isClaude) {
|
|
344
373
|
const formatted = formatStreamLine(line);
|
|
345
374
|
if (formatted !== null) {
|
|
346
|
-
|
|
375
|
+
collectCompleted(formatted);
|
|
376
|
+
const output = `${prefix}${formatted}`;
|
|
377
|
+
if (opts.onLine) {
|
|
378
|
+
opts.onLine(output);
|
|
379
|
+
} else {
|
|
380
|
+
console.log(output);
|
|
381
|
+
}
|
|
347
382
|
}
|
|
348
383
|
} else if (piFormatter) {
|
|
349
384
|
const formatted = piFormatter(line);
|
|
350
385
|
if (formatted !== null) {
|
|
351
|
-
|
|
386
|
+
collectCompleted(formatted);
|
|
387
|
+
const output = `${prefix}${formatted}`;
|
|
388
|
+
if (opts.onLine) {
|
|
389
|
+
opts.onLine(output);
|
|
390
|
+
} else {
|
|
391
|
+
console.log(output);
|
|
392
|
+
}
|
|
352
393
|
}
|
|
353
394
|
} else {
|
|
354
|
-
|
|
395
|
+
collectCompleted(line);
|
|
396
|
+
const output = `${prefix}${line}`;
|
|
397
|
+
if (opts.onLine) {
|
|
398
|
+
opts.onLine(output);
|
|
399
|
+
} else {
|
|
400
|
+
console.log(output);
|
|
401
|
+
}
|
|
355
402
|
}
|
|
356
403
|
});
|
|
357
404
|
proc.stderr.pipe(logStream);
|
|
358
405
|
const rlErr = createInterface({ input: proc.stderr });
|
|
359
406
|
rlErr.on("line", (line) => {
|
|
360
|
-
|
|
407
|
+
const output = `${prefix}${line}`;
|
|
408
|
+
if (opts.onLine) {
|
|
409
|
+
opts.onLine(output);
|
|
410
|
+
} else {
|
|
411
|
+
console.error(output);
|
|
412
|
+
}
|
|
361
413
|
});
|
|
362
414
|
const timeout = setTimeout(() => {
|
|
363
415
|
if (!proc.killed) {
|
|
@@ -378,7 +430,10 @@ function spawnAgent(opts) {
|
|
|
378
430
|
rlErr.close();
|
|
379
431
|
logStream.end();
|
|
380
432
|
if (promptFile) cleanupPromptFile(promptFile);
|
|
381
|
-
resolve(
|
|
433
|
+
resolve({
|
|
434
|
+
exitCode: code ?? 1,
|
|
435
|
+
completedItems
|
|
436
|
+
});
|
|
382
437
|
});
|
|
383
438
|
proc.on("error", (err) => {
|
|
384
439
|
clearTimeout(timeout);
|
|
@@ -390,7 +445,7 @@ function spawnAgent(opts) {
|
|
|
390
445
|
"Agent process error:",
|
|
391
446
|
formatErrorMessage(err)
|
|
392
447
|
);
|
|
393
|
-
resolve(1);
|
|
448
|
+
resolve({ exitCode: 1, completedItems });
|
|
394
449
|
});
|
|
395
450
|
});
|
|
396
451
|
}
|
|
@@ -469,9 +524,12 @@ async function respawnAndRetryMerge(opts) {
|
|
|
469
524
|
opts.worktreePath,
|
|
470
525
|
opts.baseBranch
|
|
471
526
|
);
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
527
|
+
const msg1 = ` ${opts.ticket.filename}: respawning agent to resolve merge conflict`;
|
|
528
|
+
if (opts.onLine) {
|
|
529
|
+
opts.onLine(msg1);
|
|
530
|
+
} else {
|
|
531
|
+
console.log(msg1);
|
|
532
|
+
}
|
|
475
533
|
const prompt = renderPrompt(opts.specBody, opts.ticket);
|
|
476
534
|
const logPath = join(
|
|
477
535
|
opts.logsDir,
|
|
@@ -481,19 +539,23 @@ async function respawnAndRetryMerge(opts) {
|
|
|
481
539
|
/\.md$/,
|
|
482
540
|
""
|
|
483
541
|
);
|
|
484
|
-
const
|
|
542
|
+
const result = await spawnAgent({
|
|
485
543
|
prompt,
|
|
486
544
|
agentCmd: opts.agentCmd,
|
|
487
545
|
model: opts.model,
|
|
488
546
|
timeout: opts.timeout,
|
|
489
547
|
logPath,
|
|
490
548
|
cwd: opts.worktreePath,
|
|
491
|
-
label
|
|
549
|
+
label,
|
|
550
|
+
onLine: opts.onLine
|
|
492
551
|
});
|
|
493
|
-
if (exitCode !== 0) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
552
|
+
if (result.exitCode !== 0) {
|
|
553
|
+
const msg2 = ` ${opts.ticket.filename}: respawned agent exited ${result.exitCode}`;
|
|
554
|
+
if (opts.onLine) {
|
|
555
|
+
opts.onLine(msg2);
|
|
556
|
+
} else {
|
|
557
|
+
console.log(msg2);
|
|
558
|
+
}
|
|
497
559
|
return false;
|
|
498
560
|
}
|
|
499
561
|
const merged = await mergeBranch(
|
|
@@ -501,24 +563,34 @@ async function respawnAndRetryMerge(opts) {
|
|
|
501
563
|
opts.branch
|
|
502
564
|
);
|
|
503
565
|
if (!merged) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
566
|
+
const msg3 = ` ${opts.ticket.filename}: merge still conflicted after respawn`;
|
|
567
|
+
if (opts.onLine) {
|
|
568
|
+
opts.onLine(msg3);
|
|
569
|
+
} else {
|
|
570
|
+
console.log(msg3);
|
|
571
|
+
}
|
|
507
572
|
}
|
|
508
573
|
return merged;
|
|
509
574
|
}
|
|
510
575
|
__name(respawnAndRetryMerge, "respawnAndRetryMerge");
|
|
511
|
-
async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir) {
|
|
576
|
+
async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, display) {
|
|
512
577
|
mkdirSync(logsDir, { recursive: true });
|
|
578
|
+
const d = display ?? createDisplay();
|
|
579
|
+
let totalWaves = 0;
|
|
580
|
+
for (const _ of simulateWaves(tickets)) totalWaves++;
|
|
513
581
|
let totalCompleted = 0;
|
|
514
582
|
let waveNum = 0;
|
|
515
583
|
while (true) {
|
|
516
584
|
const ready = findReadyTickets(tickets);
|
|
517
585
|
if (ready.length === 0) break;
|
|
518
586
|
waveNum++;
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
587
|
+
d.startWave(
|
|
588
|
+
waveNum,
|
|
589
|
+
totalWaves,
|
|
590
|
+
ready.map((t) => ({
|
|
591
|
+
num: t.num,
|
|
592
|
+
filename: t.filename
|
|
593
|
+
}))
|
|
522
594
|
);
|
|
523
595
|
const results = await Promise.allSettled(
|
|
524
596
|
ready.map(async (t) => {
|
|
@@ -531,37 +603,42 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
531
603
|
/\.md$/,
|
|
532
604
|
""
|
|
533
605
|
);
|
|
534
|
-
|
|
535
|
-
|
|
606
|
+
d.setTicketStatus(t.num, "in-progress");
|
|
607
|
+
d.log(` Starting ${t.filename}`);
|
|
608
|
+
const result = await spawnAgent({
|
|
536
609
|
prompt,
|
|
537
610
|
agentCmd,
|
|
538
611
|
model,
|
|
539
612
|
timeout,
|
|
540
613
|
logPath,
|
|
541
|
-
label
|
|
614
|
+
label,
|
|
615
|
+
onLine: /* @__PURE__ */ __name((line) => d.log(line), "onLine")
|
|
542
616
|
});
|
|
543
|
-
return { ticket: t,
|
|
617
|
+
return { ticket: t, result };
|
|
544
618
|
})
|
|
545
619
|
);
|
|
546
620
|
let waveCompleted = 0;
|
|
547
621
|
for (const r of results) {
|
|
548
622
|
if (r.status === "rejected") continue;
|
|
549
|
-
const { ticket,
|
|
550
|
-
if (exitCode !== 0) {
|
|
551
|
-
|
|
552
|
-
|
|
623
|
+
const { ticket, result } = r.value;
|
|
624
|
+
if (result.exitCode !== 0) {
|
|
625
|
+
d.setTicketStatus(ticket.num, "failed");
|
|
626
|
+
d.log(
|
|
627
|
+
` ${ticket.filename}: agent exited ${result.exitCode}`
|
|
553
628
|
);
|
|
554
629
|
continue;
|
|
555
630
|
}
|
|
631
|
+
checkItems(ticket, result.completedItems);
|
|
556
632
|
markDone(ticket);
|
|
557
633
|
waveCompleted++;
|
|
558
634
|
totalCompleted++;
|
|
559
|
-
|
|
635
|
+
d.setTicketStatus(ticket.num, "done");
|
|
636
|
+
d.log(
|
|
560
637
|
` ${ticket.filename}: done`
|
|
561
638
|
);
|
|
562
639
|
}
|
|
563
640
|
if (waveCompleted === 0) {
|
|
564
|
-
|
|
641
|
+
d.log(
|
|
565
642
|
"\nNo tickets completed this wave. Stopping."
|
|
566
643
|
);
|
|
567
644
|
break;
|
|
@@ -570,15 +647,19 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
570
647
|
const remaining = tickets.filter(
|
|
571
648
|
(t) => t.status === "ready-for-agent"
|
|
572
649
|
);
|
|
573
|
-
|
|
650
|
+
d.log(
|
|
574
651
|
`
|
|
575
652
|
Finished: ${totalCompleted} completed` + (remaining.length > 0 ? `, ${remaining.length} remaining` : "")
|
|
576
653
|
);
|
|
654
|
+
d.cleanup();
|
|
577
655
|
return { allDone: remaining.length === 0 };
|
|
578
656
|
}
|
|
579
657
|
__name(runWaves, "runWaves");
|
|
580
658
|
async function runWavesIsolated(opts) {
|
|
581
659
|
mkdirSync(opts.logsDir, { recursive: true });
|
|
660
|
+
const d = opts.display ?? createDisplay();
|
|
661
|
+
let totalWaves = 0;
|
|
662
|
+
for (const _ of simulateWaves(opts.tickets)) totalWaves++;
|
|
582
663
|
const repoRoot = getRepoRoot();
|
|
583
664
|
const repoName = getRepoName();
|
|
584
665
|
const baseBranch = getCurrentBranch();
|
|
@@ -602,9 +683,13 @@ async function runWavesIsolated(opts) {
|
|
|
602
683
|
const ready = findReadyTickets(opts.tickets);
|
|
603
684
|
if (ready.length === 0) break;
|
|
604
685
|
waveNum++;
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
686
|
+
d.startWave(
|
|
687
|
+
waveNum,
|
|
688
|
+
totalWaves,
|
|
689
|
+
ready.map((t) => ({
|
|
690
|
+
num: t.num,
|
|
691
|
+
filename: t.filename
|
|
692
|
+
}))
|
|
608
693
|
);
|
|
609
694
|
const worktreeSettled = await Promise.allSettled(
|
|
610
695
|
ready.map((t) => createWorktree({
|
|
@@ -620,7 +705,8 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
620
705
|
const settled = worktreeSettled[i];
|
|
621
706
|
const ticket = ready[i];
|
|
622
707
|
if (settled.status === "rejected") {
|
|
623
|
-
|
|
708
|
+
d.setTicketStatus(ticket.num, "failed");
|
|
709
|
+
d.log(
|
|
624
710
|
` ${ticket.filename}: failed to create worktree: ${formatErrorMessage(settled.reason)}`
|
|
625
711
|
);
|
|
626
712
|
continue;
|
|
@@ -639,7 +725,8 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
639
725
|
/\.md$/,
|
|
640
726
|
""
|
|
641
727
|
);
|
|
642
|
-
|
|
728
|
+
d.setTicketStatus(t.num, "in-progress");
|
|
729
|
+
d.log(` Starting ${t.filename}`);
|
|
643
730
|
return spawnAgent({
|
|
644
731
|
prompt,
|
|
645
732
|
agentCmd: opts.agentCmd,
|
|
@@ -647,7 +734,8 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
647
734
|
timeout: opts.timeout,
|
|
648
735
|
logPath,
|
|
649
736
|
cwd: worktrees[i].worktreePath,
|
|
650
|
-
label
|
|
737
|
+
label,
|
|
738
|
+
onLine: /* @__PURE__ */ __name((line) => d.log(line), "onLine")
|
|
651
739
|
});
|
|
652
740
|
})
|
|
653
741
|
);
|
|
@@ -658,16 +746,18 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
658
746
|
for (let i = 0; i < results.length; i++) {
|
|
659
747
|
const r = results[i];
|
|
660
748
|
if (r.status === "rejected") continue;
|
|
661
|
-
const
|
|
749
|
+
const agentResult = r.value;
|
|
662
750
|
const ticket = activeTickets[i];
|
|
663
751
|
const worktree = worktrees[i];
|
|
664
|
-
if (exitCode !== 0) {
|
|
665
|
-
|
|
666
|
-
|
|
752
|
+
if (agentResult.exitCode !== 0) {
|
|
753
|
+
d.setTicketStatus(ticket.num, "failed");
|
|
754
|
+
d.log(
|
|
755
|
+
` ${ticket.filename}: agent exited ${agentResult.exitCode}`
|
|
667
756
|
);
|
|
757
|
+
checkItems(ticket, agentResult.completedItems);
|
|
668
758
|
checkTicket(progress, ticket.num, {
|
|
669
759
|
branch: worktree.branchName,
|
|
670
|
-
exitCode,
|
|
760
|
+
exitCode: agentResult.exitCode,
|
|
671
761
|
done: false
|
|
672
762
|
});
|
|
673
763
|
writeProgress(progressPath, progress);
|
|
@@ -679,7 +769,7 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
679
769
|
try {
|
|
680
770
|
await removeWorktree(worktree.worktreePath);
|
|
681
771
|
} catch (err) {
|
|
682
|
-
|
|
772
|
+
d.log(
|
|
683
773
|
` Failed to remove worktree ${worktree.worktreePath}: ` + formatErrorMessage(err)
|
|
684
774
|
);
|
|
685
775
|
}
|
|
@@ -691,7 +781,7 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
691
781
|
`nightralph: auto-commit ${ticket.filename} remaining changes`
|
|
692
782
|
);
|
|
693
783
|
if (committed) {
|
|
694
|
-
|
|
784
|
+
d.log(
|
|
695
785
|
` ${ticket.filename}: auto-committed remaining changes`
|
|
696
786
|
);
|
|
697
787
|
}
|
|
@@ -701,12 +791,14 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
701
791
|
baseBranch,
|
|
702
792
|
worktree.branchName
|
|
703
793
|
)) {
|
|
704
|
-
|
|
794
|
+
d.setTicketStatus(ticket.num, "failed");
|
|
795
|
+
d.log(
|
|
705
796
|
` ${ticket.filename}: agent exited 0 but made no changes; leaving ticket ready-for-agent`
|
|
706
797
|
);
|
|
707
798
|
continue;
|
|
708
799
|
}
|
|
709
|
-
|
|
800
|
+
checkItems(ticket, agentResult.completedItems);
|
|
801
|
+
d.log(
|
|
710
802
|
` ${ticket.filename}: agent finished, awaiting merge`
|
|
711
803
|
);
|
|
712
804
|
ticketsByNum.set(ticket.num, ticket);
|
|
@@ -736,7 +828,8 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
736
828
|
markDone(ticket);
|
|
737
829
|
waveCompleted++;
|
|
738
830
|
totalCompleted++;
|
|
739
|
-
|
|
831
|
+
d.setTicketStatus(ticket.num, "done");
|
|
832
|
+
d.log(
|
|
740
833
|
` ${ticket.filename}: done`
|
|
741
834
|
);
|
|
742
835
|
checkTicket(progress, ticket.num, {
|
|
@@ -756,7 +849,7 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
756
849
|
try {
|
|
757
850
|
await removeWorktree(worktreePath);
|
|
758
851
|
} catch (err) {
|
|
759
|
-
|
|
852
|
+
d.log(
|
|
760
853
|
` Failed to remove worktree ${worktreePath}: ` + formatErrorMessage(err)
|
|
761
854
|
);
|
|
762
855
|
}
|
|
@@ -775,13 +868,15 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
775
868
|
agentCmd: opts.agentCmd,
|
|
776
869
|
model: opts.model,
|
|
777
870
|
timeout: opts.timeout,
|
|
778
|
-
logsDir: opts.logsDir
|
|
871
|
+
logsDir: opts.logsDir,
|
|
872
|
+
onLine: /* @__PURE__ */ __name((msg) => d.log(msg), "onLine")
|
|
779
873
|
}) : false;
|
|
780
874
|
if (recovered) {
|
|
781
875
|
markDone(ticket);
|
|
782
876
|
waveCompleted++;
|
|
783
877
|
totalCompleted++;
|
|
784
|
-
|
|
878
|
+
d.setTicketStatus(ticket.num, "done");
|
|
879
|
+
d.log(
|
|
785
880
|
` ${ticket.filename}: done (resolved via respawn)`
|
|
786
881
|
);
|
|
787
882
|
checkTicket(progress, ticket.num, {
|
|
@@ -798,20 +893,20 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
798
893
|
try {
|
|
799
894
|
await removeWorktree(worktreePath);
|
|
800
895
|
} catch (err) {
|
|
801
|
-
|
|
896
|
+
d.log(
|
|
802
897
|
` Failed to remove worktree ${worktreePath}: ` + formatErrorMessage(err)
|
|
803
898
|
);
|
|
804
899
|
}
|
|
805
900
|
}
|
|
806
901
|
} else {
|
|
807
|
-
|
|
902
|
+
d.log(
|
|
808
903
|
` ${ticket.filename}: merge failed, staying ready-for-agent`
|
|
809
904
|
);
|
|
810
905
|
}
|
|
811
906
|
}
|
|
812
907
|
}
|
|
813
908
|
if (mergeResult.stoppedAt) {
|
|
814
|
-
|
|
909
|
+
d.log(
|
|
815
910
|
`
|
|
816
911
|
Merge stopped at ${mergeResult.stoppedAt.branch}`
|
|
817
912
|
);
|
|
@@ -821,7 +916,7 @@ Merge stopped at ${mergeResult.stoppedAt.branch}`
|
|
|
821
916
|
try {
|
|
822
917
|
await removeWorktree(sb.worktreePath);
|
|
823
918
|
} catch (err) {
|
|
824
|
-
|
|
919
|
+
d.log(
|
|
825
920
|
` Failed to remove worktree ${sb.worktreePath}: ` + formatErrorMessage(err)
|
|
826
921
|
);
|
|
827
922
|
}
|
|
@@ -830,7 +925,7 @@ Merge stopped at ${mergeResult.stoppedAt.branch}`
|
|
|
830
925
|
}
|
|
831
926
|
}
|
|
832
927
|
if (waveCompleted === 0) {
|
|
833
|
-
|
|
928
|
+
d.log(
|
|
834
929
|
"\nNo tickets completed this wave. Stopping."
|
|
835
930
|
);
|
|
836
931
|
break;
|
|
@@ -839,15 +934,18 @@ Merge stopped at ${mergeResult.stoppedAt.branch}`
|
|
|
839
934
|
const remaining = opts.tickets.filter(
|
|
840
935
|
(t) => t.status === "ready-for-agent"
|
|
841
936
|
);
|
|
842
|
-
|
|
937
|
+
d.log(
|
|
843
938
|
`
|
|
844
939
|
Finished: ${totalCompleted} completed` + (remaining.length > 0 ? `, ${remaining.length} remaining` : "")
|
|
845
940
|
);
|
|
941
|
+
d.cleanup();
|
|
846
942
|
return { allDone: remaining.length === 0 };
|
|
847
943
|
}
|
|
848
944
|
__name(runWavesIsolated, "runWavesIsolated");
|
|
849
945
|
export {
|
|
946
|
+
COMPLETED_RE,
|
|
850
947
|
TICKET_RE,
|
|
948
|
+
checkItems,
|
|
851
949
|
createPiStreamFormatter,
|
|
852
950
|
dryRun,
|
|
853
951
|
dryRunIsolated,
|