nightralph 0.0.16 → 0.0.18

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 CHANGED
@@ -17,7 +17,7 @@ This is inspired by [nightshift](https://github.com/Shaurya-Sethi/nightshift/).
17
17
  I made it because I noticed the API in `nightshift` had already drifted
18
18
  from the skill definitions. This module bundles its skill dependencies,
19
19
  so they can't drift as long as you install the skills with this package.
20
- (See [the section on the `setup` command](#install-dependency-skills).)
20
+ (See [the section on the `init` command](#install-dependency-skills).)
21
21
 
22
22
  <!-- toc -->
23
23
 
@@ -44,10 +44,10 @@ npm i -S nightralph
44
44
  ### Install Dependency Skills
45
45
 
46
46
  ```sh
47
- npx nightralph setup
47
+ npx nightralph init
48
48
  ```
49
49
 
50
- Use the `setup` subcommand in this package to add the required
50
+ Use the `init` subcommand in this package to add the required
51
51
  [Pocock skills](https://github.com/mattpocock/skills) to your repo.
52
52
  This command will copy some files in this package to a specific directory
53
53
  in your repo. The upstream
@@ -78,7 +78,7 @@ grill -> to-spec -> to-tickets -> tdd
78
78
  First create issues.
79
79
 
80
80
  After installing this package, use
81
- [the `npx nightralph setup` command](#install-dependency-skills)
81
+ [the `npx nightralph init` command](#install-dependency-skills)
82
82
  to add the required skills to your local `.agents/skills/` folder.
83
83
 
84
84
  ### Create Issues
@@ -214,6 +214,9 @@ This is expected to run for a long time, like overnight.
214
214
 
215
215
  ### Mac OS
216
216
 
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
+
217
220
  #### Claude Opus 4.6
218
221
 
219
222
  ```sh
@@ -232,5 +235,8 @@ caffeinate -i npx nightralph claude -m claude-sonnet-5
232
235
  caffeinate -i npx nightralph pi -m openrouter/z-ai/glm-5.3-flash
233
236
  ```
234
237
 
235
- That command will prevent your machine from sleeping, but the display can still
236
- go dark.
238
+ #### `codex` + Luna
239
+
240
+ ```sh
241
+ caffeinate -i npx nightralph codex -m gpt-5.6-luna
242
+ ```
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 success = await mergeBranch(
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 rebaseSuccess = await rebaseBranch(
646
+ const rebaseResult = await rebaseBranch(
639
647
  repoRoot,
640
648
  branch,
641
649
  baseBranch,
642
650
  worktreePath
643
651
  );
644
- if (!rebaseSuccess) {
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 mergeSuccess = await mergeBranch(
664
+ const mergeResult = await mergeBranch(
656
665
  repoRoot,
657
666
  branch
658
667
  );
659
- mergingResult.merged = mergeSuccess;
660
- mergingResult.conflicted = !mergeSuccess;
661
- if (!mergeSuccess) {
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);
@@ -1089,6 +1101,29 @@ function renderPrompt(specBody, ticket) {
1089
1101
  ].join("\n");
1090
1102
  }
1091
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");
1092
1127
  function listTickets(tickets) {
1093
1128
  console.log("\nTickets:");
1094
1129
  for (const t of tickets) {
@@ -1498,7 +1533,10 @@ async function respawnAndRetryMerge(opts) {
1498
1533
  } else {
1499
1534
  console.log(msg1);
1500
1535
  }
1501
- const prompt = renderPrompt(opts.specBody, opts.ticket);
1536
+ const prompt = renderMergePrompt(
1537
+ opts.specBody,
1538
+ opts.ticket
1539
+ );
1502
1540
  const logPath = join3(
1503
1541
  opts.logsDir,
1504
1542
  opts.ticket.filename.replace(/\.md$/, ".respawn.log")
@@ -1526,19 +1564,27 @@ async function respawnAndRetryMerge(opts) {
1526
1564
  }
1527
1565
  return false;
1528
1566
  }
1529
- const merged = await mergeBranch(
1567
+ const mergeResult = await mergeBranch(
1530
1568
  opts.repoRoot,
1531
1569
  opts.branch
1532
1570
  );
1533
- if (!merged) {
1571
+ if (!mergeResult.success) {
1534
1572
  const msg3 = ` ${opts.ticket.filename}: merge still conflicted after respawn`;
1535
1573
  if (opts.onLine) {
1536
1574
  opts.onLine(msg3);
1537
1575
  } else {
1538
1576
  console.log(msg3);
1539
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
+ }
1540
1586
  }
1541
- return merged;
1587
+ return mergeResult.success;
1542
1588
  }
1543
1589
  __name(respawnAndRetryMerge, "respawnAndRetryMerge");
1544
1590
  function runProgress(tickets) {
@@ -1559,6 +1605,10 @@ async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, di
1559
1605
  const ready = findReadyTickets(tickets);
1560
1606
  if (ready.length === 0) break;
1561
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
+ );
1562
1612
  d.startWave(
1563
1613
  waveNum,
1564
1614
  totalWaves,
@@ -1566,7 +1616,11 @@ async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, di
1566
1616
  num: t.num,
1567
1617
  filename: t.filename
1568
1618
  })),
1569
- runProgress(tickets)
1619
+ runProgress(tickets),
1620
+ remaining2.map((t) => ({
1621
+ num: t.num,
1622
+ filename: t.filename
1623
+ }))
1570
1624
  );
1571
1625
  const results = await Promise.allSettled(
1572
1626
  ready.map(async (t) => {
@@ -1639,6 +1693,11 @@ async function runWavesIsolated(opts) {
1639
1693
  const repoRoot = getRepoRoot();
1640
1694
  const repoName = getRepoName();
1641
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
+ }
1642
1701
  const progressPath = join3(
1643
1702
  dirname(opts.logsDir),
1644
1703
  "progress.md"
@@ -1659,6 +1718,10 @@ async function runWavesIsolated(opts) {
1659
1718
  const ready = findReadyTickets(opts.tickets);
1660
1719
  if (ready.length === 0) break;
1661
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
+ );
1662
1725
  d.startWave(
1663
1726
  waveNum,
1664
1727
  totalWaves,
@@ -1666,7 +1729,11 @@ async function runWavesIsolated(opts) {
1666
1729
  num: t.num,
1667
1730
  filename: t.filename
1668
1731
  })),
1669
- runProgress(opts.tickets)
1732
+ runProgress(opts.tickets),
1733
+ remaining2.map((t) => ({
1734
+ num: t.num,
1735
+ filename: t.filename
1736
+ }))
1670
1737
  );
1671
1738
  const worktreeSettled = await Promise.allSettled(
1672
1739
  ready.map((t) => createWorktree({
@@ -1879,6 +1946,22 @@ async function runWavesIsolated(opts) {
1879
1946
  d.log(
1880
1947
  ` ${ticket.filename}: merge failed, staying ready-for-agent`
1881
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
+ );
1882
1965
  }
1883
1966
  }
1884
1967
  }
@@ -1996,12 +2079,12 @@ __name(resolveIssuesDir, "resolveIssuesDir");
1996
2079
  // src/index.ts
1997
2080
  var scriptDir = getScriptDir(import.meta.url);
1998
2081
  yargs(hideBin(process.argv)).scriptName("nightralph").usage("$0 <command>").command(
1999
- "setup",
2082
+ "init",
2000
2083
  "Install bundled skills and doc templates",
2001
2084
  (y) => y.option("claude", {
2002
2085
  type: "boolean",
2003
2086
  describe: "Also create .claude/skills/ symlinks",
2004
- default: false
2087
+ default: true
2005
2088
  }),
2006
2089
  async (argv) => {
2007
2090
  const tracker = await select2({