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 +30 -19
- package/dist/index.js +92 -34
- package/dist/index.js.map +2 -2
- package/dist/meta.json +6 -6
- package/dist/orchestrator.js +87 -28
- package/dist/orchestrator.js.map +2 -2
- package/dist/src/orchestrator.d.ts +14 -8
- package/dist/src/orchestrator.d.ts.map +1 -1
- package/package.json +3 -3
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
|
|
137
|
-
npx nightralph pi -m openrouter/z-ai/glm-5.3-flash --
|
|
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
|
-
|
|
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
|
-
| `--
|
|
160
|
-
| `--retries <n>` | no | `
|
|
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.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
one level up from the previous attempt, stopping at
|
|
192
|
-
|
|
193
|
-
are spaced with exponential backoff controlled by
|
|
194
|
-
the first retry waits `--retry-delay` seconds,
|
|
195
|
-
double that, and so on. This lets provider
|
|
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
|
|
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
|
|
1390
|
+
function escalateEffort(provider, level) {
|
|
1372
1391
|
if (!level) return "high";
|
|
1373
|
-
const
|
|
1374
|
-
|
|
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(
|
|
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 (
|
|
1519
|
-
|
|
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
|
-
`
|
|
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 =
|
|
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
|
-
|
|
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
|
|
2007
|
-
|
|
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
|
-
|
|
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}` + (
|
|
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
|
-
|
|
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,
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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}` + (
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
2723
|
+
}).option("effort", {
|
|
2666
2724
|
type: "string",
|
|
2667
|
-
choices:
|
|
2668
|
-
describe: "Starting
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
2875
|
+
argv.effort,
|
|
2818
2876
|
argv.retries,
|
|
2819
2877
|
argv.retryDelay
|
|
2820
2878
|
);
|