nightralph 0.0.53 → 0.0.56
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 +181 -19
- package/dist/blockers.js +149 -0
- package/dist/blockers.js.map +7 -0
- package/dist/display.js +12 -4
- package/dist/display.js.map +2 -2
- package/dist/docs-templates/issue-tracker.md +9 -1
- package/dist/index.js +2735 -415
- package/dist/index.js.map +4 -4
- package/dist/learnings.js +360 -0
- package/dist/learnings.js.map +7 -0
- package/dist/meta.json +289 -16
- package/dist/orchestrator.js +753 -188
- package/dist/orchestrator.js.map +3 -3
- package/dist/preflight.js +34 -0
- package/dist/preflight.js.map +7 -0
- package/dist/progress.js +5 -4
- package/dist/progress.js.map +2 -2
- package/dist/resolve.js +98 -9
- package/dist/resolve.js.map +2 -2
- package/dist/retry.js +83 -0
- package/dist/retry.js.map +7 -0
- package/dist/review.js +669 -0
- package/dist/review.js.map +7 -0
- package/dist/rules.js +67 -0
- package/dist/rules.js.map +7 -0
- package/dist/skills/to-tickets/SKILL.md +5 -5
- package/dist/src/blockers.d.ts +45 -0
- package/dist/src/blockers.d.ts.map +1 -0
- package/dist/src/display.d.ts +9 -0
- package/dist/src/display.d.ts.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/learnings.d.ts +70 -0
- package/dist/src/learnings.d.ts.map +1 -0
- package/dist/src/orchestrator.d.ts +85 -28
- package/dist/src/orchestrator.d.ts.map +1 -1
- package/dist/src/preflight.d.ts +6 -0
- package/dist/src/preflight.d.ts.map +1 -0
- package/dist/src/progress.d.ts +1 -1
- package/dist/src/progress.d.ts.map +1 -1
- package/dist/src/resolve.d.ts +24 -0
- package/dist/src/resolve.d.ts.map +1 -1
- package/dist/src/retry.d.ts +26 -0
- package/dist/src/retry.d.ts.map +1 -0
- package/dist/src/review.d.ts +131 -0
- package/dist/src/review.d.ts.map +1 -0
- package/dist/src/rules.d.ts +11 -0
- package/dist/src/rules.d.ts.map +1 -0
- package/dist/src/stats.d.ts +43 -0
- package/dist/src/stats.d.ts.map +1 -0
- package/dist/src/testcmd.d.ts +1 -0
- package/dist/src/testcmd.d.ts.map +1 -1
- package/dist/src/worktree.d.ts +30 -1
- package/dist/src/worktree.d.ts.map +1 -1
- package/dist/stats.js +111 -0
- package/dist/stats.js.map +7 -0
- package/dist/testcmd.js +13 -0
- package/dist/testcmd.js.map +2 -2
- package/dist/worktree.js +236 -32
- package/dist/worktree.js.map +2 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -22,22 +22,28 @@ so they can't drift as long as you install the skills with this package.
|
|
|
22
22
|
<!-- toc -->
|
|
23
23
|
|
|
24
24
|
- [Install](#install)
|
|
25
|
+
* [Requirements](#requirements)
|
|
25
26
|
* [Install Dependency Skills](#install-dependency-skills)
|
|
26
27
|
- [Workflow](#workflow)
|
|
27
28
|
* [Create Issues](#create-issues)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
- [Examples](#examples)
|
|
30
|
+
- [Command Line Arguments](#command-line-arguments)
|
|
31
|
+
* [Notes](#notes)
|
|
32
|
+
* [`--spec ` option](#--spec--option)
|
|
33
|
+
* [`--prefer-subagent` option](#--prefer-subagent-option)
|
|
31
34
|
- [How It Works](#how-it-works)
|
|
32
35
|
* [Select a ticket](#select-a-ticket)
|
|
33
36
|
* [Worktrees](#worktrees)
|
|
34
37
|
* [Progress](#progress)
|
|
38
|
+
* [Integration branch](#integration-branch)
|
|
39
|
+
* [Review](#review)
|
|
35
40
|
* [Status display](#status-display)
|
|
36
41
|
- [Keep Your Machine Awake](#keep-your-machine-awake)
|
|
37
42
|
* [Mac OS](#mac-os)
|
|
38
43
|
+ [Claude Opus 4.6](#claude-opus-46)
|
|
39
44
|
+ [Claude Opus 5.5](#claude-opus-55)
|
|
40
|
-
+ [Claude Sonnet 5](#claude-sonnet-
|
|
45
|
+
+ [Claude Sonnet 5.5](#claude-sonnet-55)
|
|
46
|
+
+ [Claude Haiku 5.5](#claude-haiku-55)
|
|
41
47
|
+ [Claude Fable 5.1](#claude-fable-51)
|
|
42
48
|
+ [`pi` + OpenRouter model](#pi--openrouter-model)
|
|
43
49
|
+ [`codex` + Luna](#codex--luna)
|
|
@@ -52,6 +58,19 @@ so they can't drift as long as you install the skills with this package.
|
|
|
52
58
|
npm i -S nightralph
|
|
53
59
|
```
|
|
54
60
|
|
|
61
|
+
### Requirements
|
|
62
|
+
|
|
63
|
+
- Node 24 or newer (see `engines` in `package.json`) and git.
|
|
64
|
+
- The agent CLI you name on the command line (`claude`, `codex` or
|
|
65
|
+
`pi`) installed and on your `PATH`. After it finds tickets,
|
|
66
|
+
nightralph checks for this command before it touches git, and
|
|
67
|
+
exits with code 2 if the command is not found. A run with no
|
|
68
|
+
tickets stops before the check. With `--dry-run` it prints the
|
|
69
|
+
problem and keeps going.
|
|
70
|
+
- macOS or Linux. nightralph runs agents in their own process group
|
|
71
|
+
and uses `sh -c` for test and install commands, so it does not
|
|
72
|
+
run on Windows.
|
|
73
|
+
|
|
55
74
|
### Install Dependency Skills
|
|
56
75
|
|
|
57
76
|
```sh
|
|
@@ -100,8 +119,8 @@ a prose text description.
|
|
|
100
119
|
The `nightralph` CLI does the `tdd` part of the workflow: the agent
|
|
101
120
|
prompt tells the agent to write a failing test per acceptance
|
|
102
121
|
criterion at the seams named in the spec's Testing Decisions, and
|
|
103
|
-
the orchestrator runs the project's test command
|
|
104
|
-
before a ticket can be marked `done`.
|
|
122
|
+
the orchestrator runs the project's test command, when there is
|
|
123
|
+
one, in the worktree before a ticket can be marked `done`.
|
|
105
124
|
|
|
106
125
|
>
|
|
107
126
|
> [!NOTE]
|
|
@@ -118,7 +137,7 @@ before a ticket can be marked `done`.
|
|
|
118
137
|
> then follow with `to-tickets`.
|
|
119
138
|
>
|
|
120
139
|
|
|
121
|
-
|
|
140
|
+
## Examples
|
|
122
141
|
|
|
123
142
|
After you have the tickets, use the `nightralph` loop to execute.
|
|
124
143
|
The `to-tickets` skill produces markdown files at
|
|
@@ -150,29 +169,31 @@ npx nightralph claude -m claude-opus-4-6 30
|
|
|
150
169
|
npx nightralph pi -m openrouter/z-ai/glm-5.3-flash --effort medium --retries 1
|
|
151
170
|
```
|
|
152
171
|
|
|
153
|
-
|
|
172
|
+
## Command Line Arguments
|
|
154
173
|
|
|
155
174
|
Flags:
|
|
156
175
|
|
|
157
176
|
| Flag | Required | Default | Description |
|
|
158
177
|
|------|----------|---------|-------------|
|
|
159
178
|
| `<provider>` | yes | -- | Agent harness to spawn (e.g. `claude`, `codex`, `pi`) |
|
|
160
|
-
| `[turns]` | no | -- | Max agentic turns per
|
|
179
|
+
| `[turns]` | no | -- | Max agentic turns per agent run (positional shorthand for `--max-turns`) |
|
|
161
180
|
| `-m`, `--model` | no | -- | Model flag passed to the agent |
|
|
162
181
|
| `--spec <name>` | no | auto | Feature name (resolves `.scratch/<name>/`) |
|
|
163
182
|
| `--dry-run` | no | -- | Show wave order and prompt without running |
|
|
183
|
+
| `--no-review` | no | review on | Skip the review and fix pass at the end of a run |
|
|
164
184
|
| `-X` | no | -- | Stop on merge conflict instead of re-spawning |
|
|
165
185
|
| `--timeout` | no | `3600` | Kill the agent after N seconds. A killed agent is not a failure: its work is kept (see Notes) |
|
|
166
|
-
| `--test-cmd <cmd>` | no | auto | Run in each worktree after the agent exits; non-zero exit rejects the ticket.
|
|
186
|
+
| `--test-cmd <cmd>` | no | auto | Run in each worktree after the agent exits; non-zero exit rejects the ticket. Defaults to `npm test` when `package.json` has a `test` script. When nothing is detected the run has no test gate and says so at startup. Pass `""` to run without a gate. Shares the agent's `--timeout`. Dependencies are already installed by `--install-cmd` |
|
|
167
187
|
| `--install-cmd <cmd>` | no | auto | Run in each fresh worktree before the agent starts, and again after a merge-conflict rebase; non-zero exit fails the attempt (it is retried like any other failure). Auto-detects from the lockfile: `pnpm-lock.yaml` -> `pnpm install --prefer-offline --frozen-lockfile`, `package-lock.json` -> `npm ci --prefer-offline --no-audit --no-fund`, a `package.json` with no lockfile -> `npm install --prefer-offline --no-audit --no-fund --no-package-lock`, no `package.json` -> no install. Pass `""` to disable. Shares the agent's `--timeout`. Output goes to `<ticket>.install.log` |
|
|
168
|
-
| `--max-turns <n>` | no | -- | Max agentic turns per
|
|
188
|
+
| `--max-turns <n>` | no | -- | Max agentic turns per agent run. Forwarded as `--max-turns <n>` to `claude` only; for `codex` and `pi` the flag is skipped with a warning and the agent runs with no turn limit. The limit is per spawn, not a total for the ticket: every retry and every re-spawn after a merge conflict starts with a fresh budget of `n` turns, so with `--retries 3` one ticket can use up to 4 x `n`. When omitted (or `0`), no limit is passed, so `--timeout` is the only cap. Takes precedence over the `[turns]` positional when both are given. Reaching the limit is an error exit, so the attempt fails and is retried like any other failure |
|
|
169
189
|
| `--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`) |
|
|
190
|
+
| `--prefer-subagent <model>` | no | -- | `claude` only. Sets `CLAUDE_CODE_SUBAGENT_MODEL=<model>` for every claude agent nightralph spawns (tickets, merge-conflict re-spawns, the reviewer and the fixer) and adds a prompt rule: hand read-only work (exploring the codebase, searching code, test runs with long output) to subagents, and make every edit and commit yourself. The reviewer gets the rule without the edit and commit line, since it never edits. `""` is the same as omitting the flag. For `codex` and `pi` it is skipped with one warning at startup (`--prefer-subagent not supported by <provider>, skipping`). `--dry-run` prints `Prefer subagent: <model>`, or `(none)`. See [`--prefer-subagent` option](#--prefer-subagent-option) |
|
|
170
191
|
| `--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 |
|
|
171
192
|
| `--retry-delay <n>` | no | `30` | Seconds to wait before the first retry; doubles each further retry (30s, 60s, 120s, ...). `0` disables the wait |
|
|
172
193
|
| `--concurrency <n>` | no | `5` | Max agents running at once within a wave. A wave with more ready tickets than this runs them through a pool: as one agent exits, the next ticket starts. The wave still finishes fully before its branches are merged |
|
|
173
194
|
|
|
174
195
|
|
|
175
|
-
|
|
196
|
+
### Notes
|
|
176
197
|
|
|
177
198
|
* When `--spec <name>` is omitted, the CLI looks for
|
|
178
199
|
`.scratch/<feature>/issues/` directories. If multiple features exist,
|
|
@@ -184,7 +205,8 @@ Flags:
|
|
|
184
205
|
tickets start in the next wave.
|
|
185
206
|
* An agent run only counts as a success when the agent exits 0,
|
|
186
207
|
its worktree branch has new commits relative to the base branch,
|
|
187
|
-
and the test command (see `--test-cmd`)
|
|
208
|
+
and the test command (see `--test-cmd`), when there is one, exits
|
|
209
|
+
0 in that worktree.
|
|
188
210
|
Uncommitted changes left by the agent are auto-committed before
|
|
189
211
|
the check. On success, the ticket's status is updated to `done`
|
|
190
212
|
in-place. If the agent exits 0 but made no commits, or the test
|
|
@@ -209,6 +231,20 @@ Flags:
|
|
|
209
231
|
logs are written to `<dir>/../logs/<ticket>.attempt<n>.log`
|
|
210
232
|
(and `.attempt<n>.test.log` for the test command) so the first
|
|
211
233
|
attempt's log is kept.
|
|
234
|
+
* Each attempt at a ticket appends an entry to
|
|
235
|
+
`.scratch/<feature>/learnings.md`, next to `progress.md`. The
|
|
236
|
+
entry records what the orchestrator saw (the outcome, the agent's
|
|
237
|
+
exit code, the checklist items it reported, and the last lines of
|
|
238
|
+
the test or install log when one of those failed) and, when an
|
|
239
|
+
agent ran, the agent's own notes. The file is committed along with
|
|
240
|
+
`progress.md` and persists across runs. Each agent's prompt
|
|
241
|
+
includes the most
|
|
242
|
+
recent entries for its own ticket and the last successful entry of
|
|
243
|
+
each ticket that directly blocks it, so a retry can see why the
|
|
244
|
+
previous attempt failed. Agents keep their notes in
|
|
245
|
+
`.nightralph/notes.md` inside the worktree. nightralph collects
|
|
246
|
+
that file when the agent exits, and a self-ignoring
|
|
247
|
+
`.nightralph/.gitignore` keeps it out of the agent's commits.
|
|
212
248
|
* A ticket that exhausts all its retry attempts is excluded from
|
|
213
249
|
later waves in the same run. It stays `ready-for-agent` on disk
|
|
214
250
|
so a future run can pick it up, but it will not be re-started
|
|
@@ -249,13 +285,95 @@ Flags:
|
|
|
249
285
|
tool-use proposals so the agent runs non-interactively)
|
|
250
286
|
|
|
251
287
|
|
|
252
|
-
|
|
288
|
+
### `--spec <name>` option
|
|
253
289
|
|
|
254
290
|
A feature name that resolves to `.scratch/<name>/`.
|
|
255
291
|
The spec file is `.scratch/<name>/spec.md` and the issues directory
|
|
256
292
|
is `.scratch/<name>/issues/`.
|
|
257
293
|
|
|
258
294
|
|
|
295
|
+
### `--prefer-subagent` option
|
|
296
|
+
|
|
297
|
+
Use this to use a strong model on the ticket while a cheaper, faster
|
|
298
|
+
model handles the reading. The main agent writes the code and makes
|
|
299
|
+
every commit. Subagents explore the codebase, search for code, and run
|
|
300
|
+
tests whose output would flood the main agent's context, then report
|
|
301
|
+
back a short answer.
|
|
302
|
+
|
|
303
|
+
The flag does two things for every claude agent nightralph spawns
|
|
304
|
+
(tickets, merge-conflict re-spawns, the reviewer and the fixer):
|
|
305
|
+
|
|
306
|
+
1. It sets `CLAUDE_CODE_SUBAGENT_MODEL=<model>` in the agent's
|
|
307
|
+
environment.
|
|
308
|
+
2. It adds a rule to the prompt: hand read-only work to subagents,
|
|
309
|
+
brief each one fully, do not delegate a lookup that takes a single
|
|
310
|
+
tool call, and never let a subagent edit files or commit. The
|
|
311
|
+
reviewer gets the same rule without the edit and commit line.
|
|
312
|
+
|
|
313
|
+
`<model>` is anything Claude Code accepts as a model: an alias such as
|
|
314
|
+
`sonnet` or `haiku`, or a full model ID. Sonnet is the safer choice
|
|
315
|
+
when subagents need judgment, such as tracing a call path across
|
|
316
|
+
modules. Haiku is cheaper and faster, and suits mechanical lookups and
|
|
317
|
+
summarizing long test output, but it is more likely to miss something.
|
|
318
|
+
|
|
319
|
+
```sh
|
|
320
|
+
# Opus writes the code, Sonnet subagents explore and run tests
|
|
321
|
+
npx nightralph claude -m claude-opus-4-6 --prefer-subagent sonnet
|
|
322
|
+
|
|
323
|
+
# Cheaper subagents for searches and noisy test runs
|
|
324
|
+
npx nightralph claude -m claude-opus-4-6 --prefer-subagent haiku
|
|
325
|
+
|
|
326
|
+
# A Sonnet main agent with Haiku subagents
|
|
327
|
+
npx nightralph claude -m claude-sonnet-4-6 --prefer-subagent haiku
|
|
328
|
+
|
|
329
|
+
# Pin an exact model ID instead of an alias
|
|
330
|
+
npx nightralph claude -m claude-opus-4-6 --prefer-subagent claude-sonnet-5-5
|
|
331
|
+
|
|
332
|
+
# Check the setting and the prompt rule without running anything
|
|
333
|
+
npx nightralph claude --prefer-subagent claude-haiku-5-5 --dry-run
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Only the `claude` provider supports this flag. With `codex` or `pi` it
|
|
337
|
+
is skipped with one warning at startup.
|
|
338
|
+
|
|
339
|
+
`--prefer-subagent <model>` sets the model that a claude agent's
|
|
340
|
+
subagents default to. It's a fallback, not a mandate. A model passed on
|
|
341
|
+
an individual subagent call wins, and so does the `model` in a subagent
|
|
342
|
+
definition. The built-in Explore subagent picks its own model.
|
|
343
|
+
nightralph never sets `CLAUDE_CODE_SUBAGENT_MODEL_FORCE`, so without
|
|
344
|
+
the flag a value you exported in your shell still applies.
|
|
345
|
+
|
|
346
|
+
Subagent turns do not count toward `--max-turns`. That was observed on
|
|
347
|
+
Claude Code 2.1.296 and is not documented by Anthropic, so it may
|
|
348
|
+
change. Subagent time does count against `--timeout`, because the
|
|
349
|
+
timeout covers the whole agent process.
|
|
350
|
+
|
|
351
|
+
Spawned agents load your global `~/.claude` plugins and hooks. Those
|
|
352
|
+
can spend turns and steer delegation their own way, and nightralph does
|
|
353
|
+
not isolate agents from them.
|
|
354
|
+
|
|
355
|
+
Subagent stats are recorded for every claude run, whether or not
|
|
356
|
+
`--prefer-subagent` is set. When a claude agent ran, the attempt's
|
|
357
|
+
entry in `learnings.md` includes `Subagents: <n>.` and `Cost: $<x>.`,
|
|
358
|
+
each left out when the agent did not report it. The
|
|
359
|
+
ticket's row in the status display shows the running total across its
|
|
360
|
+
attempts, as in `a/03 - 2 subagents, $0.42`. After the finish summary,
|
|
361
|
+
one line totals the subagents, agent runs and cost over every ticket
|
|
362
|
+
attempt, merge-conflict re-spawn, the reviewer and the fixer:
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
Subagents: 5 started across 7 agent runs, cost $3.18 (cost from 6 of 7 runs)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
The part in parentheses appears only when some runs reported no cost.
|
|
369
|
+
A claude agent reports cost only when it finishes, so one killed at
|
|
370
|
+
`--timeout` usually reports none. Codex and pi runs report nothing, so
|
|
371
|
+
a run that used only them gets no stats line. Stats are advisory: they
|
|
372
|
+
never change a ticket's outcome or the exit code.
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
259
377
|
## How It Works
|
|
260
378
|
|
|
261
379
|
Tickets run in waves. A wave is a batch of tickets whose blockers are
|
|
@@ -287,6 +405,13 @@ completes, successful branches are merged back into the base branch.
|
|
|
287
405
|
The first branch merges directly, and subsequent branches rebase
|
|
288
406
|
onto the updated base before merging.
|
|
289
407
|
|
|
408
|
+
Worktrees are created next to the repository, not inside it: a
|
|
409
|
+
ticket's worktree is the sibling directory
|
|
410
|
+
`<repo>.<feature>-<num>-<slug>` on the branch
|
|
411
|
+
`<repo>--<feature>-<num>-<slug>`. A run that is interrupted leaves
|
|
412
|
+
them in place; the next run removes a stale worktree and branch of
|
|
413
|
+
the same name before creating a new one.
|
|
414
|
+
|
|
290
415
|
A fresh worktree has only tracked files, so there is no
|
|
291
416
|
`node_modules`. Before the agent starts, the orchestrator installs
|
|
292
417
|
dependencies with the command that matches the project's lockfile
|
|
@@ -304,6 +429,33 @@ A file `progress.md` is written to the `.scratch/<feature>/` directory
|
|
|
304
429
|
and committed after each ticket completes, so you can monitor the run from
|
|
305
430
|
another terminal by reading the file.
|
|
306
431
|
|
|
432
|
+
### Integration branch
|
|
433
|
+
|
|
434
|
+
Launched from your default branch (`main`), nightralph does not merge
|
|
435
|
+
into it. It creates `nightralph--<feature>` (or `nightralph--all` for
|
|
436
|
+
several features), merges every ticket there, and leaves you on that
|
|
437
|
+
branch. Running again from `main` resumes the same branch, so finished
|
|
438
|
+
tickets stay finished. Launched from any other branch, it merges into
|
|
439
|
+
that branch.
|
|
440
|
+
|
|
441
|
+
After you merge the integration branch into `main`, the next run
|
|
442
|
+
fast-forwards it to `main` and resumes. If the branch has commits `main`
|
|
443
|
+
lacks, nightralph reports how far behind it is; merge `main` into it or
|
|
444
|
+
delete it with `git branch -D`.
|
|
445
|
+
|
|
446
|
+
If `.scratch/` is gitignored, the bookkeeping files (tickets, progress,
|
|
447
|
+
learnings, review) are force-added and live on the integration branch, so
|
|
448
|
+
they disappear from disk when you check out `main`.
|
|
449
|
+
|
|
450
|
+
### Review
|
|
451
|
+
|
|
452
|
+
After the last wave, an agent reviews everything merged since the last
|
|
453
|
+
review and writes its findings to `.scratch/<feature>/review.md` (`.scratch/review.md`
|
|
454
|
+
for a multi-feature run). A
|
|
455
|
+
second agent fixes them test-first; its work merges only if the test
|
|
456
|
+
command passes. Findings it could not fix stay unchecked in
|
|
457
|
+
`review.md`. Skip this with `--no-review`.
|
|
458
|
+
|
|
307
459
|
### Status display
|
|
308
460
|
|
|
309
461
|
In a terminal, nightralph shows a header line, one row per ticket, and
|
|
@@ -327,6 +479,9 @@ Each row starts with a dot that shows the ticket's state:
|
|
|
327
479
|
run in this session. The row names the blocking tickets, as in
|
|
328
480
|
`auth/03 (blocked by auth/01)`.
|
|
329
481
|
|
|
482
|
+
A claude ticket's row can end with its subagent count and cost, as in
|
|
483
|
+
`auth/02 - 2 subagents, $0.42` (see [`--prefer-subagent` option](#--prefer-subagent-option)).
|
|
484
|
+
|
|
330
485
|
The header looks like this:
|
|
331
486
|
|
|
332
487
|
```
|
|
@@ -360,24 +515,31 @@ keep the machine awake, but the display can still go dark.
|
|
|
360
515
|
#### Claude Opus 4.6
|
|
361
516
|
|
|
362
517
|
```sh
|
|
363
|
-
caffeinate -i npx nightralph claude -m claude-opus-4-6 --effort
|
|
518
|
+
caffeinate -i npx nightralph claude -m claude-opus-4-6 --effort medium
|
|
364
519
|
```
|
|
365
520
|
|
|
366
521
|
#### Claude Opus 5.5
|
|
367
522
|
|
|
368
523
|
```sh
|
|
369
|
-
caffeinate -i npx nightralph claude -m claude-opus-5-5 --effort
|
|
524
|
+
caffeinate -i npx nightralph claude -m claude-opus-5-5 --effort medium
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
#### Claude Sonnet 5.5
|
|
528
|
+
|
|
529
|
+
```sh
|
|
530
|
+
caffeinate -i npx nightralph claude -m claude-sonnet-5-5 --effort medium
|
|
370
531
|
```
|
|
371
|
-
|
|
532
|
+
|
|
533
|
+
#### Claude Haiku 5.5
|
|
372
534
|
|
|
373
535
|
```sh
|
|
374
|
-
caffeinate -i npx nightralph claude -m claude-
|
|
536
|
+
caffeinate -i npx nightralph claude -m claude-haiku-5-5 --effort high
|
|
375
537
|
```
|
|
376
538
|
|
|
377
539
|
#### Claude Fable 5.1
|
|
378
540
|
|
|
379
541
|
```sh
|
|
380
|
-
caffeinate -i npx nightralph claude -m claude-fable-5-1 --effort
|
|
542
|
+
caffeinate -i npx nightralph claude -m claude-fable-5-1 --effort medium
|
|
381
543
|
```
|
|
382
544
|
|
|
383
545
|
#### `pi` + OpenRouter model
|
package/dist/blockers.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
const FIELD_RE = /\*\*Blocked by:\*\*/i;
|
|
4
|
+
const NEXT_FIELD_RE = /^\s*(?:[-*]\s+)?\*\*[^*\n]+:\*\*/;
|
|
5
|
+
const HEADING_RE = /^\s*#{1,6}\s/;
|
|
6
|
+
const CHECKLIST_RE = /^\s*[-*+]\s+\[[ xX]\]/;
|
|
7
|
+
const TOKEN_RE = /[A-Za-z0-9_-]+/g;
|
|
8
|
+
function endsField(line) {
|
|
9
|
+
return line.trim() === "" || NEXT_FIELD_RE.test(line) || HEADING_RE.test(line) || CHECKLIST_RE.test(line);
|
|
10
|
+
}
|
|
11
|
+
__name(endsField, "endsField");
|
|
12
|
+
function fieldText(body) {
|
|
13
|
+
const lines = body.split(/\r?\n/);
|
|
14
|
+
const start = lines.findIndex((l) => FIELD_RE.test(l));
|
|
15
|
+
if (start === -1) return null;
|
|
16
|
+
const first = lines[start];
|
|
17
|
+
const m = FIELD_RE.exec(first);
|
|
18
|
+
const parts = [first.slice(m.index + m[0].length)];
|
|
19
|
+
for (const line of lines.slice(start + 1)) {
|
|
20
|
+
if (endsField(line)) break;
|
|
21
|
+
parts.push(line);
|
|
22
|
+
}
|
|
23
|
+
return parts.join("\n");
|
|
24
|
+
}
|
|
25
|
+
__name(fieldText, "fieldText");
|
|
26
|
+
function stripNotes(text) {
|
|
27
|
+
const chars = [...text];
|
|
28
|
+
const drop = new Array(chars.length).fill(false);
|
|
29
|
+
const ticks = [];
|
|
30
|
+
chars.forEach((ch, i) => {
|
|
31
|
+
if (ch === "`") ticks.push(i);
|
|
32
|
+
});
|
|
33
|
+
for (let i = 0; i + 1 < ticks.length; i += 2) {
|
|
34
|
+
for (let j = ticks[i]; j <= ticks[i + 1]; j++) {
|
|
35
|
+
drop[j] = true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const open = [];
|
|
39
|
+
chars.forEach((ch, i) => {
|
|
40
|
+
if (drop[i]) return;
|
|
41
|
+
if (ch === "(") {
|
|
42
|
+
open.push(i);
|
|
43
|
+
} else if (ch === ")" && open.length > 0) {
|
|
44
|
+
const from = open.pop();
|
|
45
|
+
for (let j = from; j <= i; j++) drop[j] = true;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return chars.map((ch, i) => drop[i] ? " " : ch).join("");
|
|
49
|
+
}
|
|
50
|
+
__name(stripNotes, "stripNotes");
|
|
51
|
+
function looksLikeFeature(word) {
|
|
52
|
+
const segments = word.split("/");
|
|
53
|
+
return (segments.length > 1 || word.includes("-")) && /[a-z]/.test(word) && segments.every((s) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(s));
|
|
54
|
+
}
|
|
55
|
+
__name(looksLikeFeature, "looksLikeFeature");
|
|
56
|
+
function wordBefore(text, start) {
|
|
57
|
+
let i = start;
|
|
58
|
+
while (i > 0 && /\s/.test(text[i - 1])) i--;
|
|
59
|
+
if (i === start) return null;
|
|
60
|
+
const end = i;
|
|
61
|
+
while (i > 0 && /[A-Za-z0-9_/-]/.test(text[i - 1])) i--;
|
|
62
|
+
return i === end ? null : text.slice(i, end);
|
|
63
|
+
}
|
|
64
|
+
__name(wordBefore, "wordBefore");
|
|
65
|
+
function parseBlockerField(body, knownFeatures) {
|
|
66
|
+
const text = fieldText(body);
|
|
67
|
+
if (text === null) return { refs: [], typos: [] };
|
|
68
|
+
const clean = stripNotes(text);
|
|
69
|
+
if (/^\s*none\b/i.test(clean)) return { refs: [], typos: [] };
|
|
70
|
+
const refs = [];
|
|
71
|
+
const typos = [];
|
|
72
|
+
const seen = /* @__PURE__ */ new Set();
|
|
73
|
+
const add = /* @__PURE__ */ __name((num, feature) => {
|
|
74
|
+
const id = `${feature ?? ""}:${num}`;
|
|
75
|
+
if (seen.has(id)) return;
|
|
76
|
+
seen.add(id);
|
|
77
|
+
refs.push(feature === void 0 ? { num } : { feature, num });
|
|
78
|
+
}, "add");
|
|
79
|
+
for (const m of clean.matchAll(TOKEN_RE)) {
|
|
80
|
+
const word = m[0];
|
|
81
|
+
if (/^\d+(?:-\d+)*$/.test(word)) {
|
|
82
|
+
const before = wordBefore(clean, m.index ?? 0);
|
|
83
|
+
let feature;
|
|
84
|
+
if (before !== null && knownFeatures.has(before)) {
|
|
85
|
+
feature = before;
|
|
86
|
+
} else if (before !== null && looksLikeFeature(before) && !typos.includes(before)) {
|
|
87
|
+
typos.push(before);
|
|
88
|
+
}
|
|
89
|
+
for (const n of word.split("-").map(Number)) add(n, feature);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { refs, typos };
|
|
93
|
+
}
|
|
94
|
+
__name(parseBlockerField, "parseBlockerField");
|
|
95
|
+
function refId(feature, num) {
|
|
96
|
+
return `${feature}:${num}`;
|
|
97
|
+
}
|
|
98
|
+
__name(refId, "refId");
|
|
99
|
+
function resolveBlockers(raw, ownFeature, selected, lookup) {
|
|
100
|
+
const out = [];
|
|
101
|
+
const seen = /* @__PURE__ */ new Set();
|
|
102
|
+
for (const r of raw) {
|
|
103
|
+
const feature = r.feature ?? ownFeature;
|
|
104
|
+
const id = refId(feature, r.num);
|
|
105
|
+
if (seen.has(id)) continue;
|
|
106
|
+
seen.add(id);
|
|
107
|
+
const nums = selected.get(feature);
|
|
108
|
+
if (nums) {
|
|
109
|
+
out.push({
|
|
110
|
+
feature,
|
|
111
|
+
num: r.num,
|
|
112
|
+
where: nums.has(r.num) ? "run" : "missing"
|
|
113
|
+
});
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const status = lookup(feature, r.num);
|
|
117
|
+
if (status === void 0) {
|
|
118
|
+
out.push({ feature, num: r.num, where: "missing" });
|
|
119
|
+
} else {
|
|
120
|
+
out.push({
|
|
121
|
+
feature,
|
|
122
|
+
num: r.num,
|
|
123
|
+
where: status === "done" ? "done" : "pending",
|
|
124
|
+
status
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
__name(resolveBlockers, "resolveBlockers");
|
|
131
|
+
function provisionalRefs(raw, ownFeature) {
|
|
132
|
+
const out = [];
|
|
133
|
+
const seen = /* @__PURE__ */ new Set();
|
|
134
|
+
for (const r of raw) {
|
|
135
|
+
const feature = r.feature ?? ownFeature;
|
|
136
|
+
const id = refId(feature, r.num);
|
|
137
|
+
if (seen.has(id)) continue;
|
|
138
|
+
seen.add(id);
|
|
139
|
+
out.push({ feature, num: r.num, where: "run" });
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
__name(provisionalRefs, "provisionalRefs");
|
|
144
|
+
export {
|
|
145
|
+
parseBlockerField,
|
|
146
|
+
provisionalRefs,
|
|
147
|
+
resolveBlockers
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=blockers.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/blockers.ts"],
|
|
4
|
+
"sourcesContent": ["// Blocker references in a ticket's **Blocked by:** field. Pure:\n// no file or process access and no imports, so orchestrator.ts\n// and resolve.ts can both depend on it.\n\nexport type RawRef = {\n feature?:string; // absent -> the ticket's own feature\n num:number;\n}\n\nexport type BlockerParse = {\n refs:RawRef[];\n typos:string[]; // feature-shaped words that name no feature\n}\n\nconst FIELD_RE = /\\*\\*Blocked by:\\*\\*/i\nconst NEXT_FIELD_RE = /^\\s*(?:[-*]\\s+)?\\*\\*[^*\\n]+:\\*\\*/\nconst HEADING_RE = /^\\s*#{1,6}\\s/\nconst CHECKLIST_RE = /^\\s*[-*+]\\s+\\[[ xX]\\]/\nconst TOKEN_RE = /[A-Za-z0-9_-]+/g\n\nfunction endsField (line:string):boolean {\n return line.trim() === '' ||\n NEXT_FIELD_RE.test(line) ||\n HEADING_RE.test(line) ||\n CHECKLIST_RE.test(line)\n}\n\n/**\n * The text of the **Blocked by:** field: the rest of its line\n * plus continuation lines, up to a blank line, the next\n * `**Field:**` line, a heading or a checklist item. `null` when\n * the body has no such field.\n */\nfunction fieldText (body:string):string|null {\n const lines = body.split(/\\r?\\n/)\n const start = lines.findIndex(l => FIELD_RE.test(l))\n if (start === -1) return null\n const first = lines[start]\n const m = FIELD_RE.exec(first)!\n const parts = [first.slice(m.index + m[0].length)]\n for (const line of lines.slice(start + 1)) {\n if (endsField(line)) break\n parts.push(line)\n }\n return parts.join('\\n')\n}\n\n/**\n * Blanks out backtick spans and `(...)` spans (nested, across\n * lines). Only paired delimiters count; an unpaired `(` or\n * backtick stays as text. Each blanked character becomes a space,\n * so the words on either side never merge.\n */\nfunction stripNotes (text:string):string {\n const chars = [...text]\n const drop = new Array<boolean>(chars.length).fill(false)\n const ticks:number[] = []\n chars.forEach((ch, i) => {\n if (ch === '`') ticks.push(i)\n })\n for (let i = 0; i + 1 < ticks.length; i += 2) {\n for (let j = ticks[i]; j <= ticks[i + 1]; j++) {\n drop[j] = true\n }\n }\n const open:number[] = []\n chars.forEach((ch, i) => {\n if (drop[i]) return\n if (ch === '(') {\n open.push(i)\n } else if (ch === ')' && open.length > 0) {\n const from = open.pop()!\n for (let j = from; j <= i; j++) drop[j] = true\n }\n })\n return chars.map((ch, i) => (drop[i] ? ' ' : ch)).join('')\n}\n\n/**\n * A word shaped like a feature dir name: lowercase letters and\n * digits joined by hyphens, or a nested path of such segments\n * joined by `/`, with at least one letter. Doc IDs such as\n * `FDR-018` are not.\n */\nfunction looksLikeFeature (word:string):boolean {\n const segments = word.split('/')\n return (segments.length > 1 || word.includes('-')) &&\n /[a-z]/.test(word) &&\n segments.every(s => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(s))\n}\n\n/**\n * The word that ends right before `start`, separated from it by\n * whitespace only. A word may contain `/`, so a nested feature such\n * as `rollout/billing` is one word. `null` when the gap is empty or\n * holds anything but whitespace.\n */\nfunction wordBefore (text:string, start:number):string|null {\n let i = start\n while (i > 0 && /\\s/.test(text[i - 1])) i--\n if (i === start) return null\n const end = i\n while (i > 0 && /[A-Za-z0-9_/-]/.test(text[i - 1])) i--\n return i === end ? null : text.slice(i, end)\n}\n\n/**\n * Reads the **Blocked by:** field of a ticket body. A number\n * belongs to the feature named by the word directly before it\n * when that word is in `knownFeatures` (the names of\n * `.scratch/<name>/issues` dirs, `<name>` possibly nested, as in\n * `rollout/billing`); otherwise to the ticket's own feature.\n * Feature-shaped words that name no feature are returned as\n * `typos`.\n */\nexport function parseBlockerField (\n body:string,\n knownFeatures:ReadonlySet<string>\n):BlockerParse {\n const text = fieldText(body)\n if (text === null) return { refs:[], typos:[] }\n const clean = stripNotes(text)\n if (/^\\s*none\\b/i.test(clean)) return { refs:[], typos:[] }\n\n const refs:RawRef[] = []\n const typos:string[] = []\n const seen = new Set<string>()\n const add = (num:number, feature?:string) => {\n const id = `${feature ?? ''}:${num}`\n if (seen.has(id)) return\n seen.add(id)\n refs.push(feature === undefined ? { num } : { feature, num })\n }\n for (const m of clean.matchAll(TOKEN_RE)) {\n const word = m[0]\n if (/^\\d+(?:-\\d+)*$/.test(word)) {\n const before = wordBefore(clean, m.index ?? 0)\n let feature:string|undefined\n if (before !== null && knownFeatures.has(before)) {\n feature = before\n } else if (\n before !== null &&\n looksLikeFeature(before) &&\n !typos.includes(before)\n ) {\n typos.push(before)\n }\n // both ends of a range belong to the same feature\n for (const n of word.split('-').map(Number)) add(n, feature)\n }\n }\n return { refs, typos }\n}\n\nexport type BlockerWhere = 'run'|'done'|'pending'|'missing'\n\nexport type BlockerRef = {\n feature:string;\n num:number;\n // 'run' -> a ticket in this run; its live status decides\n // 'done' -> outside the run, done when loaded\n // 'pending' -> outside the run, not done (dead this session)\n // 'missing' -> no ticket matches (dead, reported)\n where:BlockerWhere;\n status?:string; // the outside ticket's status, for messages\n}\n\n// Same text as ticketKey in orchestrator.ts, which this module\n// must not import.\nfunction refId (feature:string, num:number):string {\n return `${feature}:${num}`\n}\n\n/**\n * Classifies raw refs. `selected` maps each feature in the run to\n * its ticket numbers; `lookup` returns the status of a ticket in a\n * feature outside the run, or undefined when there is none.\n * De-duplicates by (feature, num) after the own feature is filled\n * in.\n */\nexport function resolveBlockers (\n raw:ReadonlyArray<RawRef>,\n ownFeature:string,\n selected:ReadonlyMap<string, ReadonlySet<number>>,\n lookup:(feature:string, num:number) => string|undefined\n):BlockerRef[] {\n const out:BlockerRef[] = []\n const seen = new Set<string>()\n for (const r of raw) {\n const feature = r.feature ?? ownFeature\n const id = refId(feature, r.num)\n if (seen.has(id)) continue\n seen.add(id)\n const nums = selected.get(feature)\n if (nums) {\n out.push({\n feature,\n num:r.num,\n where:nums.has(r.num) ? 'run' : 'missing',\n })\n continue\n }\n const status = lookup(feature, r.num)\n if (status === undefined) {\n out.push({ feature, num:r.num, where:'missing' })\n } else {\n out.push({\n feature,\n num:r.num,\n where:status === 'done' ? 'done' : 'pending',\n status,\n })\n }\n }\n return out\n}\n\n/**\n * Refs for a bare scan, before the run is known: every ref is\n * `run`, so one that matches no ticket in the list is never\n * satisfied.\n */\nexport function provisionalRefs (\n raw:ReadonlyArray<RawRef>,\n ownFeature:string\n):BlockerRef[] {\n const out:BlockerRef[] = []\n const seen = new Set<string>()\n for (const r of raw) {\n const feature = r.feature ?? ownFeature\n const id = refId(feature, r.num)\n if (seen.has(id)) continue\n seen.add(id)\n out.push({ feature, num:r.num, where:'run' })\n }\n return out\n}\n\nexport type BlockerWarning = {\n ticket:string; // ticketLabel of the ticket with the problem\n kind:'missing'|'typo';\n text:string; // the ref label or the unknown word\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/display.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import { formatStats } from "./stats.js";
|
|
3
4
|
const ESC = "\x1B[";
|
|
4
5
|
const ansi = {
|
|
5
6
|
cursorUp(n) {
|
|
@@ -60,10 +61,9 @@ const MAX_MESSAGE_BUFFER = 200;
|
|
|
60
61
|
const ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
61
62
|
function rowText(ticket) {
|
|
62
63
|
const label = ticketLabel(ticket);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
return `${label} (blocked by ${ticket.blockedBy.join(", ")})`;
|
|
64
|
+
const base = ticket.status === "blocked" && ticket.blockedBy?.length ? `${label} (blocked by ${ticket.blockedBy.join(", ")})` : label;
|
|
65
|
+
const suffix = formatStats(ticket.stats);
|
|
66
|
+
return suffix === null ? base : `${base} - ${suffix}`;
|
|
67
67
|
}
|
|
68
68
|
__name(rowText, "rowText");
|
|
69
69
|
function fitLine(line, width) {
|
|
@@ -192,6 +192,12 @@ class StatusDisplay {
|
|
|
192
192
|
ticket.blockedBy = [...blockedBy];
|
|
193
193
|
this.render();
|
|
194
194
|
}
|
|
195
|
+
setTicketStats(num, feature, stats) {
|
|
196
|
+
const ticket = this.findTicket(num, feature);
|
|
197
|
+
if (!ticket) return;
|
|
198
|
+
ticket.stats = { ...stats };
|
|
199
|
+
this.render();
|
|
200
|
+
}
|
|
195
201
|
log(message) {
|
|
196
202
|
if (!this.wave) {
|
|
197
203
|
this.config.stream.write(message + "\n");
|
|
@@ -330,6 +336,8 @@ Wave ${waveNum}${pct}: ` + tickets.map((t) => ticketLabel(t)).join(", ")
|
|
|
330
336
|
}
|
|
331
337
|
setBlocked(_num, _feature, _blockedBy) {
|
|
332
338
|
}
|
|
339
|
+
setTicketStats(_num, _feature, _stats) {
|
|
340
|
+
}
|
|
333
341
|
log(message) {
|
|
334
342
|
console.log(message);
|
|
335
343
|
}
|