@neriros/ralphy 2.7.4 → 2.7.6

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
@@ -104,11 +104,13 @@ Defaults are written to `ralphy.config.json` on first run; CLI flags override co
104
104
  "doneStatus": "In Review",
105
105
  "doneLabel": "ralphy-done",
106
106
  "postComments": true,
107
+ "updateEveryIterations": 10,
107
108
  },
108
109
  "useWorktree": true,
109
110
  "cleanupWorktreeOnSuccess": false,
110
111
  "setupScript": "bun install",
111
112
  "teardownScript": "git status",
113
+ "appendPrompt": "Always run lint before committing.",
112
114
  }
113
115
  ```
114
116
 
@@ -120,6 +122,10 @@ With `--worktree` (or `useWorktree: true` in config) each task runs in an isolat
120
122
 
121
123
  Use `setupScript` (run inside the worktree right after scaffolding) to install dependencies, copy `.env`, etc. Use `teardownScript` (run after the loop exits, before any worktree cleanup) to gather artifacts or roll back local mutations. Both run via `sh -c`; failures are logged but never block the loop. With `cleanupWorktreeOnSuccess: true` the worktree is removed when the worker exits 0 — failed workers always keep their worktree (and branch) for human inspection.
122
124
 
125
+ **`appendPrompt`** (or `--prompt` in agent mode) is appended to every scaffolded `proposal.md` under an `## Additional instructions` section — use it for cross-cutting guidance every task should see.
126
+
127
+ **`updateEveryIterations`** (default `10`, `0` disables) posts a "🔄 Ralph progress update: iteration N" comment on the Linear issue every N task iterations. Requires `postComments: true`.
128
+
123
129
  Failed workers (non-zero exit) are not marked processed, so they'll be retried on the next poll. SIGINT/SIGTERM cleanly stops polling and kills active workers. All Linear side effects are best-effort — failures log a warning but never block the task loop.
124
130
 
125
131
  ## CLI Options
@@ -143,15 +149,18 @@ Failed workers (non-zero exit) are not marked processed, so they'll be retried o
143
149
 
144
150
  ### Agent mode flags
145
151
 
146
- | Option | Description |
147
- | ------------------------ | -------------------------------------------- |
148
- | `--linear-team <key>` | Linear team key (e.g. `ENG`) |
149
- | `--linear-assignee <id>` | Filter by assignee (user id, email, or `me`) |
150
- | `--linear-status <name>` | Filter by status name (repeatable) |
151
- | `--linear-label <name>` | Filter by label name (repeatable, any-of) |
152
- | `--poll-interval <s>` | Seconds between Linear polls (default: 60) |
153
- | `--concurrency <n>` | Max concurrent task loops (default: 1) |
154
- | `--worktree` | Run each task in its own git worktree |
152
+ | Option | Description |
153
+ | ----------------------------- | --------------------------------------------- |
154
+ | `--linear-team <key>` | Linear team key (e.g. `ENG`) |
155
+ | `--linear-assignee <id>` | Filter by assignee (user id, email, or `me`) |
156
+ | `--linear-status <name>` | Filter by status name (repeatable) |
157
+ | `--linear-label <name>` | Filter by label name (repeatable, any-of) |
158
+ | `--poll-interval <s>` | Seconds between Linear polls (default: 60) |
159
+ | `--concurrency <n>` | Max concurrent task loops (default: 1) |
160
+ | `--worktree` | Run each task in its own git worktree |
161
+ | `--in-progress-status <name>` | Linear status to set when work starts |
162
+ | `--done-status <name>` | Linear status to set on successful completion |
163
+ | `--done-label <name>` | Linear label to add on successful completion |
155
164
 
156
165
  ## OpenSpec Flow
157
166
 
package/dist/cli/index.js CHANGED
@@ -56131,7 +56131,7 @@ var HELP_TEXT = [
56131
56131
  "",
56132
56132
  "Options:",
56133
56133
  " --name <name> Change name (required for most commands)",
56134
- " --prompt <text> Task description",
56134
+ " --prompt <text> Task description (in agent mode: appended to every scaffolded proposal)",
56135
56135
  " --prompt-file <path> Read prompt from file",
56136
56136
  " --model <model> Set model (haiku|sonnet|opus)",
56137
56137
  " --claude [model] Use Claude engine (haiku|sonnet|opus, default: opus)",
@@ -56153,6 +56153,9 @@ var HELP_TEXT = [
56153
56153
  " --poll-interval <s> Seconds between Linear polls (default: 60)",
56154
56154
  " --concurrency <n> Max concurrent task loops (default: 1)",
56155
56155
  " --worktree Run each task in its own git worktree (.ralph/worktrees/<name>)",
56156
+ " --in-progress-status <name> Linear status to set when work starts on an issue",
56157
+ " --done-status <name> Linear status to set when work completes successfully",
56158
+ " --done-label <name> Linear label to add when work completes successfully",
56156
56159
  "",
56157
56160
  " --help, -h Show this help message",
56158
56161
  "",
@@ -56189,7 +56192,10 @@ async function parseArgs(argv) {
56189
56192
  linearLabel: [],
56190
56193
  pollInterval: 60,
56191
56194
  concurrency: 1,
56192
- worktree: false
56195
+ worktree: false,
56196
+ inProgressStatus: "",
56197
+ doneStatus: "",
56198
+ doneLabel: ""
56193
56199
  };
56194
56200
  let expectModel = false;
56195
56201
  let expectModelFlag = false;
@@ -56209,6 +56215,9 @@ async function parseArgs(argv) {
56209
56215
  let expectLinearLabel = false;
56210
56216
  let expectPollInterval = false;
56211
56217
  let expectConcurrency = false;
56218
+ let expectInProgressStatus = false;
56219
+ let expectDoneStatus = false;
56220
+ let expectDoneLabel = false;
56212
56221
  for (const arg of argv) {
56213
56222
  if (expectModel) {
56214
56223
  if (VALID_MODELS.has(arg)) {
@@ -56304,6 +56313,21 @@ async function parseArgs(argv) {
56304
56313
  expectConcurrency = false;
56305
56314
  continue;
56306
56315
  }
56316
+ if (expectInProgressStatus) {
56317
+ result2.inProgressStatus = arg;
56318
+ expectInProgressStatus = false;
56319
+ continue;
56320
+ }
56321
+ if (expectDoneStatus) {
56322
+ result2.doneStatus = arg;
56323
+ expectDoneStatus = false;
56324
+ continue;
56325
+ }
56326
+ if (expectDoneLabel) {
56327
+ result2.doneLabel = arg;
56328
+ expectDoneLabel = false;
56329
+ continue;
56330
+ }
56307
56331
  switch (arg) {
56308
56332
  case "--claude":
56309
56333
  if (result2.engineSet && result2.engine !== "claude") {
@@ -56383,6 +56407,15 @@ async function parseArgs(argv) {
56383
56407
  case "--worktree":
56384
56408
  result2.worktree = true;
56385
56409
  break;
56410
+ case "--in-progress-status":
56411
+ expectInProgressStatus = true;
56412
+ break;
56413
+ case "--done-status":
56414
+ expectDoneStatus = true;
56415
+ break;
56416
+ case "--done-label":
56417
+ expectDoneLabel = true;
56418
+ break;
56386
56419
  default:
56387
56420
  if (VALID_MODES.has(arg)) {
56388
56421
  result2.mode = arg;
@@ -69739,7 +69772,7 @@ function changeNameForIssue(issue) {
69739
69772
  const slug = issue.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
69740
69773
  return slug ? `${issue.identifier.toLowerCase()}-${slug}` : issue.identifier.toLowerCase();
69741
69774
  }
69742
- async function scaffoldChangeForIssue(tasksDir, statesDir, issue, comments = []) {
69775
+ async function scaffoldChangeForIssue(tasksDir, statesDir, issue, comments = [], appendPrompt = "") {
69743
69776
  const name = changeNameForIssue(issue);
69744
69777
  const changeDir = join11(tasksDir, name);
69745
69778
  const stateDir = join11(statesDir, name);
@@ -69769,6 +69802,7 @@ async function scaffoldChangeForIssue(tasksDir, statesDir, issue, comments = [])
69769
69802
  "",
69770
69803
  issue.description?.trim() || "_No description provided in Linear._",
69771
69804
  ...commentsBlock,
69805
+ ...appendPrompt.trim() ? ["", "## Additional instructions", "", appendPrompt.trim()] : [],
69772
69806
  "",
69773
69807
  "## Steering",
69774
69808
  "",
@@ -69810,6 +69844,7 @@ var RalphyConfigSchema = exports_external.object({
69810
69844
  cleanupWorktreeOnSuccess: exports_external.boolean().default(false),
69811
69845
  setupScript: exports_external.string().optional(),
69812
69846
  teardownScript: exports_external.string().optional(),
69847
+ appendPrompt: exports_external.string().optional(),
69813
69848
  engine: exports_external.enum(["claude", "codex"]).default("claude"),
69814
69849
  model: exports_external.enum(["haiku", "sonnet", "opus"]).default("opus"),
69815
69850
  linear: exports_external.object({
@@ -69820,7 +69855,8 @@ var RalphyConfigSchema = exports_external.object({
69820
69855
  inProgressStatus: exports_external.string().optional(),
69821
69856
  doneStatus: exports_external.string().optional(),
69822
69857
  doneLabel: exports_external.string().optional(),
69823
- postComments: exports_external.boolean().default(true)
69858
+ postComments: exports_external.boolean().default(true),
69859
+ updateEveryIterations: exports_external.number().int().nonnegative().default(10)
69824
69860
  }).default({ statuses: [], labels: [], postComments: true })
69825
69861
  }).default({
69826
69862
  concurrency: 1,
@@ -69906,8 +69942,37 @@ class AgentCoordinator {
69906
69942
  state.lastPollAt = new Date().toISOString();
69907
69943
  await this.deps.saveState(state);
69908
69944
  this.spawnNext();
69945
+ await this.reportProgress();
69909
69946
  return { found: issues.length, added };
69910
69947
  }
69948
+ async reportProgress() {
69949
+ const updater = this.deps.updater;
69950
+ const everyN = this.opts.commentEveryIterations ?? 0;
69951
+ if (everyN <= 0 || !updater || this.opts.postComments === false || !this.deps.getIterationCount) {
69952
+ return;
69953
+ }
69954
+ for (const w of this.workers) {
69955
+ let count;
69956
+ try {
69957
+ count = await this.deps.getIterationCount(w.changeName);
69958
+ } catch (err) {
69959
+ this.deps.onLog(`! iteration count read failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
69960
+ continue;
69961
+ }
69962
+ if (count < everyN)
69963
+ continue;
69964
+ const currMilestone = Math.floor(count / everyN);
69965
+ const lastMilestone = Math.floor(w.lastReportedIteration / everyN);
69966
+ if (currMilestone <= lastMilestone)
69967
+ continue;
69968
+ try {
69969
+ await updater.postComment(w.issue, `\uD83D\uDD04 Ralph progress update: iteration ${count} on \`${w.changeName}\``);
69970
+ w.lastReportedIteration = count;
69971
+ } catch (err) {
69972
+ this.deps.onLog(`! Linear progress comment failed for ${w.issueIdentifier}: ${err.message}`, "red");
69973
+ }
69974
+ }
69975
+ }
69911
69976
  spawnNext() {
69912
69977
  if (this.stopped || !this.state)
69913
69978
  return;
@@ -69937,7 +70002,9 @@ class AgentCoordinator {
69937
70002
  changeName,
69938
70003
  issueId: issue.id,
69939
70004
  issueIdentifier: issue.identifier,
69940
- kill: handle.kill
70005
+ issue,
70006
+ kill: handle.kill,
70007
+ lastReportedIteration: 0
69941
70008
  };
69942
70009
  this.workers.push(worker);
69943
70010
  this.pendingIds.delete(issue.id);
@@ -70124,6 +70191,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70124
70191
  const teamKeyOf = (issue) => issue.identifier.split("-")[0];
70125
70192
  const useWorktree = args.worktree || cfg.useWorktree;
70126
70193
  const cwdByChange = new Map;
70194
+ const statesDirByChange = new Map;
70127
70195
  async function runScript(label, cmd, cwd2) {
70128
70196
  appendLog(` ${label}: ${cmd}`, "gray");
70129
70197
  const proc = Bun.spawn({
@@ -70164,8 +70232,10 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70164
70232
  appendLog(`! worktree create failed for ${issue.identifier}: ${err.message} \u2014 falling back to project root`, "yellow");
70165
70233
  }
70166
70234
  }
70167
- const changeName = await scaffoldChangeForIssue(scaffoldTasksDir, scaffoldStatesDir, issue, comments);
70235
+ const appendPrompt = args.prompt || cfg.appendPrompt || "";
70236
+ const changeName = await scaffoldChangeForIssue(scaffoldTasksDir, scaffoldStatesDir, issue, comments, appendPrompt);
70168
70237
  cwdByChange.set(changeName, workerCwd);
70238
+ statesDirByChange.set(changeName, scaffoldStatesDir);
70169
70239
  if (cfg.setupScript) {
70170
70240
  await runScript("setup", cfg.setupScript, workerCwd);
70171
70241
  }
@@ -70213,6 +70283,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70213
70283
  }
70214
70284
  }
70215
70285
  cwdByChange.delete(changeName);
70286
+ statesDirByChange.delete(changeName);
70216
70287
  return code;
70217
70288
  });
70218
70289
  return { exited: wrapped, kill: () => proc.kill() };
@@ -70221,6 +70292,14 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70221
70292
  saveState: (s) => writeAgentState(projectRoot, s),
70222
70293
  onLog: appendLog,
70223
70294
  onWorkersChanged: () => setTick((t) => t + 1),
70295
+ getIterationCount: async (changeName) => {
70296
+ const dir = statesDirByChange.get(changeName) ?? statesDir;
70297
+ const file = Bun.file(join14(dir, changeName, ".ralph-state.json"));
70298
+ if (!await file.exists())
70299
+ return 0;
70300
+ const json = await file.json();
70301
+ return json.iteration ?? 0;
70302
+ },
70224
70303
  updater: {
70225
70304
  postComment: (issue, body) => addIssueComment(apiKey, issue.id, body),
70226
70305
  setState: (issue, stateId) => updateIssueState(apiKey, issue.id, stateId),
@@ -70249,10 +70328,11 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70249
70328
  }, {
70250
70329
  concurrency,
70251
70330
  filter: filter2,
70252
- inProgressStatus: cfg.linear.inProgressStatus,
70253
- doneStatus: cfg.linear.doneStatus,
70254
- doneLabel: cfg.linear.doneLabel,
70255
- postComments: cfg.linear.postComments
70331
+ inProgressStatus: args.inProgressStatus || cfg.linear.inProgressStatus,
70332
+ doneStatus: args.doneStatus || cfg.linear.doneStatus,
70333
+ doneLabel: args.doneLabel || cfg.linear.doneLabel,
70334
+ postComments: cfg.linear.postComments,
70335
+ commentEveryIterations: cfg.linear.updateEveryIterations
70256
70336
  });
70257
70337
  coordRef.current = coord2;
70258
70338
  await coord2.init();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.7.4",
3
+ "version": "2.7.6",
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",