nightralph 0.0.27 → 0.0.31

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
@@ -26,12 +26,18 @@ so they can't drift as long as you install the skills with this package.
26
26
  - [Workflow](#workflow)
27
27
  * [Create Issues](#create-issues)
28
28
  * [Examples](#examples)
29
+ * [Interface](#interface)
30
+ + [`--spec ` option](#--spec--option)
29
31
  - [How It Works](#how-it-works)
30
32
  * [Select a ticket](#select-a-ticket)
31
33
  * [Worktrees](#worktrees)
32
34
  * [Progress](#progress)
33
35
  - [Keep Your Machine Awake](#keep-your-machine-awake)
34
36
  * [Mac OS](#mac-os)
37
+ + [Claude Opus 4.6](#claude-opus-46)
38
+ + [Claude Sonnet 5](#claude-sonnet-5)
39
+ + [`pi` + OpenRouter model](#pi--openrouter-model)
40
+ + [`codex` + Luna](#codex--luna)
35
41
 
36
42
  <!-- tocstop -->
37
43
 
@@ -107,7 +113,6 @@ before a ticket can be marked `done`.
107
113
  > then follow with `to-tickets`.
108
114
  >
109
115
 
110
-
111
116
  ### Examples
112
117
 
113
118
  After you have the tickets, use the `nightralph` loop to execute.
@@ -124,6 +129,9 @@ npx nightralph pi -m openrouter/z-ai/glm-5.3-flash
124
129
  # Dry run to inspect the dependency graph and prompt
125
130
  npx nightralph codex --dry-run
126
131
 
132
+ # Use codex with a specific model
133
+ npx nightralph codex -m gpt-5.6-luna
134
+
127
135
  # Specify a feature by name
128
136
  npx nightralph claude -m claude-opus-4-6 --spec my-feature
129
137
 
@@ -133,15 +141,11 @@ npx nightralph claude -m claude-opus-4-6 --max-turns 30
133
141
  # Same thing, using the positional shorthand
134
142
  npx nightralph claude -m claude-opus-4-6 30
135
143
 
136
- # Start pi at medium thinking and allow one escalated retry
137
- npx nightralph pi -m openrouter/z-ai/glm-5.3-flash --thinking medium --retries 1
144
+ # Start pi at medium effort and allow one escalated retry
145
+ npx nightralph pi -m openrouter/z-ai/glm-5.3-flash --effort medium --retries 1
138
146
  ```
139
147
 
140
- #### `--spec <name>` option
141
-
142
- A feature name that resolves to `.scratch/<name>/`.
143
- The spec file is `.scratch/<name>/spec.md` and the issues directory
144
- is `.scratch/<name>/issues/`.
148
+ ### Interface
145
149
 
146
150
  Flags:
147
151
 
@@ -156,8 +160,8 @@ Flags:
156
160
  | `--timeout` | no | `3600` | Kill the agent after N seconds. A killed agent is not a failure: its work is kept (see Notes) |
157
161
  | `--test-cmd <cmd>` | no | auto | Run in each worktree after the agent exits; non-zero exit rejects the ticket. Auto-detects `npm test`. Pass `""` to disable. Shares the agent's `--timeout`. The command runs in a fresh git worktree, which contains only tracked files, so a project that needs an install step should include it, for example `--test-cmd 'npm ci && npm test'` |
158
162
  | `--max-turns <n>` | no | -- | Max agentic turns per ticket. Forwarded verbatim as `--max-turns <n>` to the provider CLI. Only `claude` accepts it; `codex` and `pi` reject it as an unknown option and the agent exits immediately. Applies to every agent spawn, including a re-spawn after a merge conflict. When omitted, `claude` applies no turn limit, so `--timeout` is the only cap. Reaching the limit is an error exit, so the ticket is recorded as failed |
159
- | `--thinking <level>` | no | -- | Starting thinking level for `pi`, one of `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Forwarded as `--thinking <level>`. Omitted = pi's own default. Ignored for other providers. Each retry steps up from here (see `--retries`) |
160
- | `--retries <n>` | no | `2` | Extra attempts per ticket after a failed run. Each retry starts from a fresh worktree; with `pi` it also raises the thinking level (see Notes). `0` disables retries |
163
+ | `--effort <level>` | no | -- | Starting effort/reasoning level. Each provider has its own ladder: `claude` supports `low`, `medium`, `high`, `xhigh`, `max` (forwarded as `--effort`); `codex` supports `low`, `medium`, `high` (forwarded as `-c model_reasoning_effort=<level>`); `pi` supports `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max` (forwarded as `--thinking`). Omitted = provider default. Each retry steps up from here (see `--retries`) |
164
+ | `--retries <n>` | no | `3` | Extra attempts per ticket after a failed run. Each retry starts from a fresh worktree and raises the effort level one rung (see Notes). `0` disables retries |
161
165
  | `--retry-delay <n>` | no | `30` | Seconds to wait before the first retry; doubles each further retry (30s, 60s, 120s, ...). `0` disables the wait |
162
166
 
163
167
 
@@ -185,15 +189,15 @@ Flags:
185
189
  ticket is recorded as failed. Non-zero exit, exit 0 with no new
186
190
  commits, and a red test command all count as failures; a timeout
187
191
  does not (see below). Each retry discards the previous worktree
188
- and starts from a fresh one. With `pi`, each retry raises the
189
- thinking level: without `--thinking` the first retry uses `high`
190
- and the second `max`; with `--thinking <level>` each retry steps
191
- one level up from the previous attempt, stopping at `max`.
192
- `claude` and `codex` are retried at the same settings. Retries
193
- are spaced with exponential backoff controlled by `--retry-delay`:
194
- the first retry waits `--retry-delay` seconds, the second waits
195
- double that, and so on. This lets provider outages clear before
196
- burning the next attempt. Retry
192
+ and starts from a fresh one. Each retry also raises the effort
193
+ level one rung up the provider's ladder: without `--effort` the
194
+ first retry uses `high`; with `--effort <level>` each retry
195
+ steps one level up from the previous attempt, stopping at the
196
+ provider's max (`max` for claude/pi, `high` for codex).
197
+ Retries are spaced with exponential backoff controlled by
198
+ `--retry-delay`: the first retry waits `--retry-delay` seconds,
199
+ the second waits double that, and so on. This lets provider
200
+ outages clear before burning the next attempt. Retry
197
201
  logs are written to `<dir>/../logs/<ticket>.attempt<n>.log`
198
202
  (and `.attempt<n>.test.log` for the test command) so the first
199
203
  attempt's log is kept.
@@ -237,6 +241,13 @@ Flags:
237
241
  tool-use proposals so the agent runs non-interactively)
238
242
 
239
243
 
244
+ #### `--spec <name>` option
245
+
246
+ A feature name that resolves to `.scratch/<name>/`.
247
+ The spec file is `.scratch/<name>/spec.md` and the issues directory
248
+ is `.scratch/<name>/issues/`.
249
+
250
+
240
251
  ## How It Works
241
252
 
242
253
  All currently eligible tickets are grouped into a
package/dist/index.js CHANGED
@@ -1359,7 +1359,26 @@ function getProviderArgs(cmd) {
1359
1359
  return PROVIDER_ARGS[name] ?? [];
1360
1360
  }
1361
1361
  __name(getProviderArgs, "getProviderArgs");
1362
- var THINKING_LEVELS = [
1362
+ var EFFORT_LADDERS = {
1363
+ pi: [
1364
+ "off",
1365
+ "minimal",
1366
+ "low",
1367
+ "medium",
1368
+ "high",
1369
+ "xhigh",
1370
+ "max"
1371
+ ],
1372
+ claude: [
1373
+ "low",
1374
+ "medium",
1375
+ "high",
1376
+ "xhigh",
1377
+ "max"
1378
+ ],
1379
+ codex: ["low", "medium", "high"]
1380
+ };
1381
+ var ALL_EFFORT_LEVELS = [
1363
1382
  "off",
1364
1383
  "minimal",
1365
1384
  "low",
@@ -1368,12 +1387,14 @@ var THINKING_LEVELS = [
1368
1387
  "xhigh",
1369
1388
  "max"
1370
1389
  ];
1371
- function escalateThinking(level) {
1390
+ function escalateEffort(provider, level) {
1372
1391
  if (!level) return "high";
1373
- const i = THINKING_LEVELS.indexOf(level);
1374
- return THINKING_LEVELS[Math.min(i + 1, THINKING_LEVELS.length - 1)];
1392
+ const ladder = EFFORT_LADDERS[provider];
1393
+ const i = ladder.indexOf(level);
1394
+ if (i === -1) return level;
1395
+ return ladder[Math.min(i + 1, ladder.length - 1)];
1375
1396
  }
1376
- __name(escalateThinking, "escalateThinking");
1397
+ __name(escalateEffort, "escalateEffort");
1377
1398
  function formatStreamLine(line) {
1378
1399
  let obj;
1379
1400
  try {
@@ -1509,14 +1530,39 @@ function spawnAgent(opts) {
1509
1530
  if (opts.model) {
1510
1531
  args.push("--model", opts.model);
1511
1532
  }
1512
- if (opts.maxTurns) {
1513
- args.push("--max-turns", String(opts.maxTurns));
1514
- }
1515
1533
  const providerName = basename2(opts.agentCmd);
1516
1534
  const isClaude = providerName === "claude";
1517
1535
  const isPi = providerName === "pi";
1518
- if (isPi && opts.thinking) {
1519
- args.push("--thinking", opts.thinking);
1536
+ if (opts.maxTurns) {
1537
+ if (isClaude) {
1538
+ args.push(
1539
+ "--max-turns",
1540
+ String(opts.maxTurns)
1541
+ );
1542
+ } else if (opts.onLine) {
1543
+ opts.onLine(
1544
+ `Warning: --max-turns not supported by ${providerName}, skipping`
1545
+ );
1546
+ }
1547
+ }
1548
+ if (opts.effort && providerName in EFFORT_LADDERS) {
1549
+ const ladder = EFFORT_LADDERS[providerName];
1550
+ if (!ladder.includes(opts.effort)) {
1551
+ if (opts.onLine) {
1552
+ opts.onLine(
1553
+ `Warning: effort '${opts.effort}' not supported by ${providerName}, skipping`
1554
+ );
1555
+ }
1556
+ } else if (isPi) {
1557
+ args.push("--thinking", opts.effort);
1558
+ } else if (isClaude) {
1559
+ args.push("--effort", opts.effort);
1560
+ } else if (providerName === "codex") {
1561
+ args.push(
1562
+ "-c",
1563
+ `model_reasoning_effort=${opts.effort}`
1564
+ );
1565
+ }
1520
1566
  }
1521
1567
  const prefix = opts.label ? ` [${opts.label}] ` : " ";
1522
1568
  let promptFile = null;
@@ -1747,7 +1793,7 @@ Prompt for first ready ticket (${firstReady[0].filename}):
1747
1793
  Test command: ${opts.testCmd ?? "(none)"}`
1748
1794
  );
1749
1795
  console.log(
1750
- `Thinking: ${opts.thinking ?? "(provider default)"}`
1796
+ `Effort: ${opts.effort ?? "(provider default)"}`
1751
1797
  );
1752
1798
  console.log(
1753
1799
  `Retries: ${opts.retries ?? DEFAULT_RETRIES}`
@@ -1876,7 +1922,7 @@ async function respawnAndRetryMerge(opts) {
1876
1922
  return mergeResult.success;
1877
1923
  }
1878
1924
  __name(respawnAndRetryMerge, "respawnAndRetryMerge");
1879
- var DEFAULT_RETRIES = 2;
1925
+ var DEFAULT_RETRIES = 3;
1880
1926
  var DEFAULT_RETRY_DELAY = 30;
1881
1927
  function retryDelayFor(base, attempt) {
1882
1928
  return base * 2 ** (attempt - 2);
@@ -1924,7 +1970,7 @@ async function runTicketAttempt(opts) {
1924
1970
  cwd: worktree.worktreePath,
1925
1971
  label: ticket.filename.replace(/\.md$/, ""),
1926
1972
  maxTurns: opts.maxTurns,
1927
- thinking: opts.thinking,
1973
+ effort: opts.effort,
1928
1974
  onLine: /* @__PURE__ */ __name((line) => d.log(line), "onLine")
1929
1975
  });
1930
1976
  if (agentResult.timedOut) {
@@ -2003,8 +2049,9 @@ __name(runTicketAttempt, "runTicketAttempt");
2003
2049
  async function runTicketAttempts(opts) {
2004
2050
  const { ticket, d } = opts;
2005
2051
  const maxAttempts = opts.retries + 1;
2006
- const isPi = basename2(opts.agentCmd) === "pi";
2007
- let thinking = opts.thinking;
2052
+ const providerName = basename2(opts.agentCmd);
2053
+ const isKnown = providerName in EFFORT_LADDERS;
2054
+ let effort = opts.effort;
2008
2055
  let outcome = {
2009
2056
  kind: "no-commits",
2010
2057
  worktreePath: "",
@@ -2014,10 +2061,15 @@ async function runTicketAttempts(opts) {
2014
2061
  if (attempt === 1) {
2015
2062
  d.log(` Starting ${ticket.filename}`);
2016
2063
  } else {
2017
- thinking = escalateThinking(thinking);
2064
+ if (isKnown) {
2065
+ effort = escalateEffort(
2066
+ providerName,
2067
+ effort
2068
+ );
2069
+ }
2018
2070
  const delay = retryDelayFor(opts.retryDelay, attempt);
2019
2071
  d.log(
2020
- ` ${ticket.filename}: retrying (attempt ${attempt}/${maxAttempts}` + (isPi ? `, thinking ${thinking}` : "") + (delay > 0 ? `, waiting ${delay}s` : "") + ")"
2072
+ ` ${ticket.filename}: retrying (attempt ${attempt}/${maxAttempts}` + (isKnown && effort ? `, effort ${effort}` : "") + (delay > 0 ? `, waiting ${delay}s` : "") + ")"
2021
2073
  );
2022
2074
  if (delay > 0) await sleep(delay * 1e3);
2023
2075
  }
@@ -2025,7 +2077,7 @@ async function runTicketAttempts(opts) {
2025
2077
  outcome = await runTicketAttempt({
2026
2078
  ...opts,
2027
2079
  attempt,
2028
- thinking
2080
+ effort
2029
2081
  });
2030
2082
  } catch (error) {
2031
2083
  d.log(
@@ -2058,12 +2110,13 @@ function runProgress(tickets) {
2058
2110
  };
2059
2111
  }
2060
2112
  __name(runProgress, "runProgress");
2061
- async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, display, maxTurns, thinking, retries, retryDelay) {
2113
+ async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, display, maxTurns, effort, retries, retryDelay) {
2062
2114
  mkdirSync2(logsDir, { recursive: true });
2063
2115
  const d = display ?? createDisplay();
2064
2116
  const maxAttempts = (retries ?? DEFAULT_RETRIES) + 1;
2065
2117
  const delayBase = retryDelay ?? 0;
2066
- const isPi = basename2(agentCmd) === "pi";
2118
+ const providerName = basename2(agentCmd);
2119
+ const isKnown = providerName in EFFORT_LADDERS;
2067
2120
  let totalWaves = 0;
2068
2121
  for (const _ of simulateWaves(tickets)) totalWaves++;
2069
2122
  let totalCompleted = 0;
@@ -2101,7 +2154,7 @@ async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, di
2101
2154
  );
2102
2155
  d.setTicketStatus(t.num, "in-progress");
2103
2156
  d.log(` Starting ${t.filename}`);
2104
- let level = thinking;
2157
+ let level = effort;
2105
2158
  let result = await spawnAgent({
2106
2159
  prompt,
2107
2160
  agentCmd,
@@ -2110,14 +2163,19 @@ async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, di
2110
2163
  logPath: attemptLogPath(logsDir, t, 1, ".log"),
2111
2164
  label,
2112
2165
  maxTurns,
2113
- thinking: level,
2166
+ effort: level,
2114
2167
  onLine: /* @__PURE__ */ __name((line) => d.log(line), "onLine")
2115
2168
  });
2116
2169
  for (let attempt = 2; result.exitCode !== 0 && !result.timedOut && attempt <= maxAttempts; attempt++) {
2117
- level = escalateThinking(level);
2170
+ if (isKnown) {
2171
+ level = escalateEffort(
2172
+ providerName,
2173
+ level
2174
+ );
2175
+ }
2118
2176
  const delay = retryDelayFor(delayBase, attempt);
2119
2177
  d.log(
2120
- ` ${t.filename}: agent exited ${result.exitCode}; retrying (attempt ${attempt}/${maxAttempts}` + (isPi ? `, thinking ${level}` : "") + (delay > 0 ? `, waiting ${delay}s` : "") + ")"
2178
+ ` ${t.filename}: agent exited ${result.exitCode}; retrying (attempt ${attempt}/${maxAttempts}` + (isKnown && level ? `, effort ${level}` : "") + (delay > 0 ? `, waiting ${delay}s` : "") + ")"
2121
2179
  );
2122
2180
  if (delay > 0) await sleep(delay * 1e3);
2123
2181
  result = await spawnAgent({
@@ -2133,7 +2191,7 @@ async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, di
2133
2191
  ),
2134
2192
  label,
2135
2193
  maxTurns,
2136
- thinking: level,
2194
+ effort: level,
2137
2195
  onLine: /* @__PURE__ */ __name((line) => d.log(line), "onLine")
2138
2196
  });
2139
2197
  }
@@ -2251,7 +2309,7 @@ async function runWavesIsolated(opts) {
2251
2309
  logsDir: opts.logsDir,
2252
2310
  testCmd: opts.testCmd,
2253
2311
  maxTurns: opts.maxTurns,
2254
- thinking: opts.thinking,
2312
+ effort: opts.effort,
2255
2313
  retries,
2256
2314
  retryDelay,
2257
2315
  d
@@ -2662,14 +2720,14 @@ yargs(hideBin(process.argv)).scriptName("nightralph").usage("$0 <command>").comm
2662
2720
  }).option("max-turns", {
2663
2721
  type: "number",
2664
2722
  describe: "Max agentic turns per ticket (forwarded to the agent CLI)"
2665
- }).option("thinking", {
2723
+ }).option("effort", {
2666
2724
  type: "string",
2667
- choices: THINKING_LEVELS,
2668
- describe: "Starting thinking level for the pi provider. Omitted = pi's default. Retries step up from here."
2725
+ choices: ALL_EFFORT_LEVELS,
2726
+ describe: "Starting effort level. Omitted = provider default. Retries step up from here."
2669
2727
  }).option("retries", {
2670
2728
  type: "number",
2671
2729
  default: DEFAULT_RETRIES,
2672
- describe: "Extra attempts per ticket after a failed run, each with a higher thinking level (pi). 0 disables retries."
2730
+ describe: "Extra attempts per ticket after a failed run, each with a higher effort level. 0 disables retries."
2673
2731
  }).option("retry-delay", {
2674
2732
  type: "number",
2675
2733
  default: DEFAULT_RETRY_DELAY,
@@ -2685,7 +2743,7 @@ yargs(hideBin(process.argv)).scriptName("nightralph").usage("$0 <command>").comm
2685
2743
  testCmd: argv.testCmd,
2686
2744
  stopOnConflict: argv.X,
2687
2745
  maxTurns: argv.maxTurns ?? argv.turns,
2688
- thinking: argv.thinking,
2746
+ effort: argv.effort,
2689
2747
  retries: argv.retries,
2690
2748
  retryDelay: argv.retryDelay
2691
2749
  });
@@ -2768,7 +2826,7 @@ async function runExecute(argv) {
2768
2826
  conflictStrategy,
2769
2827
  progressPath,
2770
2828
  testCmd,
2771
- thinking: argv.thinking,
2829
+ effort: argv.effort,
2772
2830
  retries: argv.retries,
2773
2831
  retryDelay: argv.retryDelay
2774
2832
  });
@@ -2800,7 +2858,7 @@ async function runExecute(argv) {
2800
2858
  display,
2801
2859
  testCmd,
2802
2860
  maxTurns: argv.maxTurns,
2803
- thinking: argv.thinking,
2861
+ effort: argv.effort,
2804
2862
  retries: argv.retries,
2805
2863
  retryDelay: argv.retryDelay
2806
2864
  });
@@ -2814,7 +2872,7 @@ async function runExecute(argv) {
2814
2872
  logsDir,
2815
2873
  display,
2816
2874
  argv.maxTurns,
2817
- argv.thinking,
2875
+ argv.effort,
2818
2876
  argv.retries,
2819
2877
  argv.retryDelay
2820
2878
  );