@neriros/ralphy 2.13.7 โ†’ 2.13.8

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
@@ -82,12 +82,15 @@ ralph status --name fix-auth # Detailed view of one task
82
82
  ```bash
83
83
  export LINEAR_API_KEY=lin_api_xxx
84
84
  ralph agent --linear-team ENG --linear-assignee me --concurrency 3 --poll-interval 60
85
+
86
+ # Limit the number of tickets processed in this run
87
+ ralph agent --max-tickets 5 --linear-team ENG --linear-assignee me
85
88
  ```
86
89
 
87
90
  What it does on each tick:
88
91
 
89
92
  1. Polls Linear for open issues matching the filter (team / assignee / status / labels)
90
- 2. Dedupes against `.ralph/agent-state.json` (already processed) plus any in-flight workers
93
+ 2. Dedupes against in-flight workers and any already-active issues
91
94
  3. For each new issue: fetches existing comments, scaffolds `openspec/changes/<id-slug>/{proposal.md,tasks.md,design.md}` (with the comments embedded so the worker sees prior discussion), then spawns `ralph task --name <id-slug>` up to the concurrency cap
92
95
  4. Posts a "๐Ÿค– started" comment on the Linear issue and applies the `setInProgress` indicator (if configured)
93
96
  5. On worker exit, posts a success/failure comment and applies the `setDone` indicator on success or `setError` on failure (if configured)
@@ -196,11 +199,41 @@ Failed workers (non-zero exit) are not marked processed, so they'll be retried o
196
199
  | `--linear-assignee <id>` | Filter by assignee (user id, email, or `me`) |
197
200
  | `--poll-interval <s>` | Seconds between Linear polls (default: 60) |
198
201
  | `--concurrency <n>` | Max concurrent task loops (default: 1) |
202
+ | `--max-tickets <n>` | Stop picking up new issues after N have been started this run (0 = unlimited) |
199
203
  | `--worktree` | Run each task in its own git worktree |
200
204
  | `--indicator <k>:<t>:<v>` | Override a `linear.indicators` entry; repeatable (e.g. `setDone:status:Done`) |
201
205
  | `--create-pr` | Push worker branch + open a GitHub PR on success (needs `--worktree`) |
202
206
  | `--fix-ci` | After PR opens, re-run task on CI failures until green (needs `--create-pr`) |
203
207
 
208
+ #### `--max-tickets`
209
+
210
+ Use `--max-tickets N` to cap how many issues ralph picks up in a single agent run. Once N issues have been started (across fresh, resume, and conflict-fix modes), the coordinator stops enqueuing new work. In-flight workers continue to completion. The limit resets each time you restart `ralph agent`.
211
+
212
+ ```bash
213
+ # Process at most 3 issues this session, then idle
214
+ ralph agent --max-tickets 3 --linear-team ENG
215
+ ```
216
+
217
+ When the limit is reached, ralph logs a yellow notice and the dashboard header shows `โ”‚ tickets โ‰คN`. Polling continues (to handle conflict re-fixes on already-started issues), but no new issues are queued.
218
+
219
+ #### Dashboard
220
+
221
+ The `ralph agent` terminal dashboard shows a full-terminal layout with three always-visible panels:
222
+
223
+ - **RALPH AGENT** (blue box): engine/model, concurrency, poll interval, active limits (`iter โ‰คN`, `cost โ‰ค$N`, `tickets โ‰คN`), feature flags (โ— PR โ— fixCI โ— worktree), and the Linear filter on the second line
224
+ - **POLL STATUS + WORKERS** (side-by-side): last-poll counts and next-poll countdown; active/queued worker totals with colored counts
225
+ - **TASKS tab bar** (when multiple workers run): numbered worker tabs with priority glyph and phase โ€” Tab/โ† โ†’ to switch, 1-9 to jump
226
+
227
+ Each worker card shows:
228
+
229
+ - Priority badge (`โ–ฒ URGENT` / `โ†‘ HIGH` / `ยท MED` / `โ†“ LOW`) + issue identifier + title + mode badge (`[NEW]` / `[RESUME]` / `[FIX]`)
230
+ - `โ†— LINEAR ISSUE-ID` and `โ†— PR #N` (short labels, not full URLs)
231
+ - `โ–ถ TASK` โ€” first unchecked task from `tasks.md`, updated every second
232
+ - `PHASE` with color (cyan = working, yellow = git ops, blue = CI, green = done, red = gave-up) + time in phase
233
+ - `โต CMD` when a shell command is in flight (shows the command and how long it's been running)
234
+ - `LOG` โ€” path to the worker's log file for `tail -f`
235
+ - `โ”€ OUTPUT โ”€` section with live stdout/stderr (scales to fill remaining terminal height for the focused worker)
236
+
204
237
  ## OpenSpec Flow
205
238
 
206
239
  There are no phases. One loop, one prompt, one `tasks.md` checklist.
package/dist/cli/index.js CHANGED
@@ -56409,7 +56409,7 @@ function log(msg) {
56409
56409
  // package.json
56410
56410
  var package_default = {
56411
56411
  name: "@neriros/ralphy",
56412
- version: "2.13.7",
56412
+ version: "2.13.8",
56413
56413
  description: "An iterative AI task execution framework. Orchestrates multi-phase autonomous work using Claude or Codex engines.",
56414
56414
  keywords: [
56415
56415
  "agent",
@@ -56559,6 +56559,7 @@ var HELP_TEXT = [
56559
56559
  " Types: label, status",
56560
56560
  " --create-pr Push the worker branch and open a GitHub PR on success (needs --worktree)",
56561
56561
  " --fix-ci After opening the PR, re-run on CI failures until green (needs --create-pr)",
56562
+ " --max-tickets <n> Stop picking up new issues after N have been started (0 = unlimited)",
56562
56563
  "",
56563
56564
  " --help, -h Show this help message",
56564
56565
  "",
@@ -56643,7 +56644,8 @@ async function parseArgs(argv) {
56643
56644
  worktree: false,
56644
56645
  indicators: {},
56645
56646
  createPr: false,
56646
- fixCi: false
56647
+ fixCi: false,
56648
+ maxTickets: 0
56647
56649
  };
56648
56650
  let expectModel = false;
56649
56651
  let expectModelFlag = false;
@@ -56659,6 +56661,7 @@ async function parseArgs(argv) {
56659
56661
  let expectLinearAssignee = false;
56660
56662
  let expectPollInterval = false;
56661
56663
  let expectConcurrency = false;
56664
+ let expectMaxTickets = false;
56662
56665
  let expectIndicator = false;
56663
56666
  for (const arg of argv) {
56664
56667
  if (expectModel) {
@@ -56737,6 +56740,11 @@ async function parseArgs(argv) {
56737
56740
  expectConcurrency = false;
56738
56741
  continue;
56739
56742
  }
56743
+ if (expectMaxTickets) {
56744
+ result2.maxTickets = parseInt(arg, 10);
56745
+ expectMaxTickets = false;
56746
+ continue;
56747
+ }
56740
56748
  if (expectIndicator) {
56741
56749
  const { key, marker } = parseIndicatorArg(arg);
56742
56750
  mergeIndicator(result2.indicators, key, marker);
@@ -56807,6 +56815,9 @@ async function parseArgs(argv) {
56807
56815
  case "--concurrency":
56808
56816
  expectConcurrency = true;
56809
56817
  break;
56818
+ case "--max-tickets":
56819
+ expectMaxTickets = true;
56820
+ break;
56810
56821
  case "--worktree":
56811
56822
  result2.worktree = true;
56812
56823
  break;
@@ -70562,6 +70573,7 @@ class AgentCoordinator {
70562
70573
  queue = [];
70563
70574
  stopped = false;
70564
70575
  conflictNotified = new Set;
70576
+ ticketsStarted = 0;
70565
70577
  constructor(deps, opts) {
70566
70578
  this.deps = deps;
70567
70579
  this.opts = opts;
@@ -70575,6 +70587,9 @@ class AgentCoordinator {
70575
70587
  get activeWorkers() {
70576
70588
  return this.workers;
70577
70589
  }
70590
+ get ticketsStartedCount() {
70591
+ return this.ticketsStarted;
70592
+ }
70578
70593
  async init() {}
70579
70594
  async pollOnce() {
70580
70595
  if (this.stopped)
@@ -70596,8 +70611,17 @@ class AgentCoordinator {
70596
70611
  const queuedIds = new Set(this.queue.map((q) => q.issue.id));
70597
70612
  const activeIds = new Set(this.workers.map((w) => w.issueId));
70598
70613
  const eligible = (id) => !queuedIds.has(id) && !activeIds.has(id) && !this.pendingIds.has(id);
70614
+ const maxT = this.opts.maxTickets ?? 0;
70615
+ const atTicketLimit = () => {
70616
+ if (maxT === 0)
70617
+ return false;
70618
+ const inFlight = this.ticketsStarted + this.queue.length + this.workers.length + this.pendingIds.size;
70619
+ return inFlight >= maxT;
70620
+ };
70599
70621
  let added = 0;
70600
70622
  for (const issue of inProgress) {
70623
+ if (atTicketLimit())
70624
+ break;
70601
70625
  if (!eligible(issue.id))
70602
70626
  continue;
70603
70627
  if (!this.dependenciesResolved(issue))
@@ -70607,6 +70631,8 @@ class AgentCoordinator {
70607
70631
  added += 1;
70608
70632
  }
70609
70633
  for (const issue of conflicted) {
70634
+ if (atTicketLimit())
70635
+ break;
70610
70636
  if (!eligible(issue.id))
70611
70637
  continue;
70612
70638
  this.queue.push({ issue, mode: "conflict-fix" });
@@ -70614,6 +70640,8 @@ class AgentCoordinator {
70614
70640
  added += 1;
70615
70641
  }
70616
70642
  for (const issue of todo) {
70643
+ if (atTicketLimit())
70644
+ break;
70617
70645
  if (!eligible(issue.id))
70618
70646
  continue;
70619
70647
  if (!this.dependenciesResolved(issue))
@@ -70808,6 +70836,11 @@ class AgentCoordinator {
70808
70836
  };
70809
70837
  this.workers.push(worker);
70810
70838
  this.pendingIds.delete(issue.id);
70839
+ this.ticketsStarted += 1;
70840
+ const maxT = this.opts.maxTickets ?? 0;
70841
+ if (maxT > 0 && this.ticketsStarted >= maxT) {
70842
+ this.deps.onLog(` ticket limit reached (${maxT}) \u2014 no new issues will be picked up`, "yellow");
70843
+ }
70811
70844
  capture("agent_worker_spawned", {
70812
70845
  spawn_mode: mode,
70813
70846
  issue_identifier: issue.identifier
@@ -72054,7 +72087,8 @@ PR: ${prUrl}` : ""
72054
72087
  ...indicators.setConflicted !== undefined ? { setConflicted: indicators.setConflicted } : {},
72055
72088
  ...indicators.clearConflicted !== undefined ? { clearConflicted: indicators.clearConflicted } : {},
72056
72089
  postComments: cfg.linear.postComments,
72057
- commentEveryIterations: cfg.linear.updateEveryIterations
72090
+ commentEveryIterations: cfg.linear.updateEveryIterations,
72091
+ ...args.maxTickets > 0 ? { maxTickets: args.maxTickets } : {}
72058
72092
  });
72059
72093
  const filterDesc = describeIndicators(indicators, team, assignee);
72060
72094
  return {
@@ -72496,6 +72530,13 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72496
72530
  cfg.maxCostUsdPerTask
72497
72531
  ]
72498
72532
  }, undefined, true, undefined, this),
72533
+ args.maxTickets > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72534
+ color: "yellow",
72535
+ children: [
72536
+ " \u2502 tickets \u2264",
72537
+ args.maxTickets
72538
+ ]
72539
+ }, undefined, true, undefined, this),
72499
72540
  cfg.createPrOnSuccess && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72500
72541
  color: "green",
72501
72542
  children: " \u25CF PR"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.13.7",
3
+ "version": "2.13.8",
4
4
  "description": "An iterative AI task execution framework. Orchestrates multi-phase autonomous work using Claude or Codex engines.",
5
5
  "keywords": [
6
6
  "agent",