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/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": 7011,
4
+ "bytes": 7134,
5
5
  "imports": [],
6
6
  "format": "esm"
7
7
  },
8
8
  "src/orchestrator.ts": {
9
- "bytes": 30428,
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": 10940
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": 5227
111
+ "bytesInOutput": 5338
102
112
  }
103
113
  },
104
- "bytes": 5460
114
+ "bytes": 5571
105
115
  },
106
116
  "dist/orchestrator.js.map": {
107
117
  "imports": [],
108
118
  "exports": [],
109
119
  "inputs": {},
110
- "bytes": 47439
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": 23279
194
+ "bytesInOutput": 26158
178
195
  }
179
196
  },
180
- "bytes": 23753
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
  }
@@ -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
- console.log(`${prefix}${formatted}`);
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
- console.log(`${prefix}${formatted}`);
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
- console.log(`${prefix}${line}`);
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
- console.error(`${prefix}${line}`);
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(code ?? 1);
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
- console.log(
473
- ` ${opts.ticket.filename}: respawning agent to resolve merge conflict`
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 exitCode = await spawnAgent({
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
- console.log(
495
- ` ${opts.ticket.filename}: respawned agent exited ${exitCode}`
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
- console.log(
505
- ` ${opts.ticket.filename}: merge still conflicted after respawn`
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
- console.log(
520
- `
521
- Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
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
- console.log(` Starting ${t.filename}`);
535
- const code = await spawnAgent({
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, exitCode: code };
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, exitCode } = r.value;
550
- if (exitCode !== 0) {
551
- console.log(
552
- ` ${ticket.filename}: agent exited ${exitCode}`
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
- console.log(
635
+ d.setTicketStatus(ticket.num, "done");
636
+ d.log(
560
637
  ` ${ticket.filename}: done`
561
638
  );
562
639
  }
563
640
  if (waveCompleted === 0) {
564
- console.log(
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
- console.log(
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
- console.log(
606
- `
607
- Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
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
- console.log(
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
- console.log(` Starting ${t.filename}`);
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 exitCode = r.value;
749
+ const agentResult = r.value;
662
750
  const ticket = activeTickets[i];
663
751
  const worktree = worktrees[i];
664
- if (exitCode !== 0) {
665
- console.log(
666
- ` ${ticket.filename}: agent exited ${exitCode}`
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
- console.error(
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
- console.log(
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
- console.log(
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
- console.log(
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
- console.log(
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
- console.error(
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
- console.log(
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
- console.error(
896
+ d.log(
802
897
  ` Failed to remove worktree ${worktreePath}: ` + formatErrorMessage(err)
803
898
  );
804
899
  }
805
900
  }
806
901
  } else {
807
- console.log(
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
- console.log(
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
- console.error(
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
- console.log(
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
- console.log(
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,