@neriros/ralphy 3.10.4 → 3.10.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.
Files changed (2) hide show
  1. package/dist/shell/index.js +504 -104
  2. package/package.json +1 -1
@@ -18928,8 +18928,8 @@ import { readFileSync } from "fs";
18928
18928
  import { resolve } from "path";
18929
18929
  function getVersion() {
18930
18930
  try {
18931
- if ("3.10.4")
18932
- return "3.10.4";
18931
+ if ("3.10.6")
18932
+ return "3.10.6";
18933
18933
  } catch {}
18934
18934
  const dirsToTry = [];
18935
18935
  try {
@@ -80505,7 +80505,21 @@ var init_zod = __esm(() => {
80505
80505
  });
80506
80506
 
80507
80507
  // packages/workflow/src/schema.ts
80508
- var CURRENT_WORKFLOW_VERSION = 2, MarkerSchema, SET_INDICATOR_KEYS, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, ProjectSchema, CommandsSchema, DEFAULT_META_ONLY_FILES, BoundariesSchema, WorkflowConfigSchema;
80508
+ function foldLegacyAssignee(v) {
80509
+ if (!v || typeof v !== "object" || Array.isArray(v))
80510
+ return v;
80511
+ const obj = v;
80512
+ if (!("assignee" in obj))
80513
+ return v;
80514
+ const { assignee, ...rest2 } = obj;
80515
+ if (rest2["filter"] === undefined) {
80516
+ const raw = typeof assignee === "string" ? assignee.trim() : "";
80517
+ const value = raw === "" || raw.toLowerCase() === "unassigned" ? "unassigned" : raw;
80518
+ rest2["filter"] = `assignee = ${value}`;
80519
+ }
80520
+ return rest2;
80521
+ }
80522
+ var CURRENT_WORKFLOW_VERSION = 3, MarkerSchema, SET_INDICATOR_KEYS, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, ProjectSchema, CommandsSchema, DEFAULT_META_ONLY_FILES, BoundariesSchema, WorkflowConfigSchema;
80509
80523
  var init_schema = __esm(() => {
80510
80524
  init_zod();
80511
80525
  MarkerSchema = exports_external.discriminatedUnion("type", [
@@ -80635,9 +80649,9 @@ var init_schema = __esm(() => {
80635
80649
  ignoreCiChecks: exports_external.array(exports_external.string()).default([]),
80636
80650
  engine: exports_external.enum(["claude", "codex"]).default("claude"),
80637
80651
  model: exports_external.enum(["haiku", "sonnet", "opus"]).default("opus"),
80638
- linear: exports_external.object({
80652
+ linear: exports_external.preprocess(foldLegacyAssignee, exports_external.object({
80639
80653
  team: exports_external.string().optional(),
80640
- assignee: exports_external.string().optional(),
80654
+ filter: exports_external.string().default("assignee = me"),
80641
80655
  postComments: exports_external.boolean().default(true),
80642
80656
  updateEveryIterations: exports_external.number().int().nonnegative().default(10),
80643
80657
  mentionTrigger: exports_external.boolean().default(true),
@@ -80657,7 +80671,8 @@ var init_schema = __esm(() => {
80657
80671
  maxConfirmationRounds: 3
80658
80672
  }),
80659
80673
  indicators: IndicatorsSchema.default({})
80660
- }).strict().default({
80674
+ }).strict()).default({
80675
+ filter: "assignee = me",
80661
80676
  postComments: true,
80662
80677
  updateEveryIterations: 10,
80663
80678
  mentionTrigger: true,
@@ -80746,7 +80761,7 @@ var init_schema = __esm(() => {
80746
80761
  var FRONTMATTER_RE, DEFAULT_WORKFLOW_MD = `---
80747
80762
  # WORKFLOW.md schema version \u2014 managed by \`ralphy init\`. When a newer version
80748
80763
  # ships, re-running init migrates this file and fills in the new settings.
80749
- version: 1
80764
+ version: 2
80750
80765
 
80751
80766
  project:
80752
80767
  name: ralphy
@@ -80810,6 +80825,7 @@ preExistingErrorCheck:
80810
80825
  outputCharLimit: 4000
80811
80826
 
80812
80827
  linear:
80828
+ filter: assignee = me
80813
80829
  postComments: true
80814
80830
  updateEveryIterations: 10
80815
80831
  mentionTrigger: true
@@ -81056,7 +81072,7 @@ function modelOptionValues() {
81056
81072
  const field = findField("model");
81057
81073
  return field && field.spec.kind === "select" ? field.spec.options.map((o) => o.value) : [];
81058
81074
  }
81059
- var PROMPT_BODY_FIELD_ID = "promptBody", REPO_LINK_FIELD_ID = "repo.link", yes = () => ({ kind: "confirm", defaultChoice: "confirm" }), no = () => ({ kind: "confirm", defaultChoice: "cancel" }), PROJECT_NAME, LINEAR_TEAM, REPO_LINK, LINEAR_ASSIGNEE, QUICK_FIELDS, isOn = (id) => (answers) => answers[id] === true, CUSTOMIZED_FIELDS, COMMON_CLI_OPTIONS, FIELD_DESCRIPTIONS;
81075
+ var PROMPT_BODY_FIELD_ID = "promptBody", REPO_LINK_FIELD_ID = "repo.link", yes = () => ({ kind: "confirm", defaultChoice: "confirm" }), no = () => ({ kind: "confirm", defaultChoice: "cancel" }), PROJECT_NAME, LINEAR_TEAM, REPO_LINK, LINEAR_FILTER, QUICK_FIELDS, isOn = (id) => (answers) => answers[id] === true, CUSTOMIZED_FIELDS, COMMON_CLI_OPTIONS, FIELD_DESCRIPTIONS;
81060
81076
  var init_fields = __esm(() => {
81061
81077
  PROJECT_NAME = {
81062
81078
  id: "project.name",
@@ -81079,15 +81095,15 @@ var init_fields = __esm(() => {
81079
81095
  spec: yes(),
81080
81096
  when: (answers) => typeof answers["repo.name"] === "string" && answers["repo.name"] !== ""
81081
81097
  };
81082
- LINEAR_ASSIGNEE = {
81083
- id: "linear.assignee",
81084
- label: "Linear assignee",
81085
- hint: "user id, email, 'me', 'any', or 'unassigned' \u2014 blank means unassigned",
81086
- description: "Which issues to pick up by assignee. Use a Linear user id, email, or 'me' for a specific person; 'any' to pick up issues regardless of who they're assigned to; 'unassigned' (or blank) to pick up only unassigned issues.",
81087
- emptyLabel: "unassigned",
81088
- spec: { kind: "text" }
81098
+ LINEAR_FILTER = {
81099
+ id: "linear.filter",
81100
+ label: "Linear filter",
81101
+ hint: "e.g. 'assignee = me', 'assignee = any', 'assignee = unassigned', or an email/user-id",
81102
+ description: "Global filter applied to every Linear ticket fetch. The only clause today is 'assignee = <value>', where <value> is 'me' (issues assigned to you), 'any' (regardless of assignee), 'unassigned', a Linear user id, or an email. Blank defaults to 'assignee = me'.",
81103
+ emptyLabel: "assignee = me",
81104
+ spec: { kind: "text", placeholder: "assignee = me" }
81089
81105
  };
81090
- QUICK_FIELDS = [PROJECT_NAME, LINEAR_TEAM, REPO_LINK, LINEAR_ASSIGNEE];
81106
+ QUICK_FIELDS = [PROJECT_NAME, LINEAR_TEAM, REPO_LINK, LINEAR_FILTER];
81091
81107
  CUSTOMIZED_FIELDS = [
81092
81108
  PROJECT_NAME,
81093
81109
  {
@@ -81336,7 +81352,7 @@ var init_fields = __esm(() => {
81336
81352
  },
81337
81353
  LINEAR_TEAM,
81338
81354
  REPO_LINK,
81339
- LINEAR_ASSIGNEE,
81355
+ LINEAR_FILTER,
81340
81356
  {
81341
81357
  id: "linear.postComments",
81342
81358
  label: "Post progress comments on the Linear issue?",
@@ -81725,6 +81741,34 @@ function describeApprovalMarker(indicator) {
81725
81741
  return `${phrases.slice(0, -1).join(", ")}, or ${phrases[phrases.length - 1]}`;
81726
81742
  }
81727
81743
 
81744
+ // packages/workflow/src/linear-filter.ts
81745
+ function parseLinearFilter(filter2) {
81746
+ const trimmed = filter2.trim();
81747
+ if (trimmed === "")
81748
+ return { assignee: "me" };
81749
+ const eq = trimmed.indexOf("=");
81750
+ if (eq < 0) {
81751
+ throw new Error(`Invalid linear.filter "${filter2}": expected "<key> = <value>" (e.g. "assignee = me").`);
81752
+ }
81753
+ const key = trimmed.slice(0, eq).trim().toLowerCase();
81754
+ const value = trimmed.slice(eq + 1).trim();
81755
+ if (!SUPPORTED_KEYS.has(key)) {
81756
+ throw new Error(`Unrecognized linear.filter key "${key}" in "${filter2}". Supported keys: ${[...SUPPORTED_KEYS].join(", ")}.`);
81757
+ }
81758
+ const lower = value.toLowerCase();
81759
+ if (lower === "any")
81760
+ return { anyAssignee: true };
81761
+ if (lower === "" || lower === "unassigned")
81762
+ return { assignee: "unassigned" };
81763
+ if (lower === "me")
81764
+ return { assignee: "me" };
81765
+ return { assignee: value };
81766
+ }
81767
+ var SUPPORTED_KEYS;
81768
+ var init_linear_filter = __esm(() => {
81769
+ SUPPORTED_KEYS = new Set(["assignee"]);
81770
+ });
81771
+
81728
81772
  // packages/workflow/src/workflow.ts
81729
81773
  var exports_workflow = {};
81730
81774
  __export(exports_workflow, {
@@ -81733,6 +81777,7 @@ __export(exports_workflow, {
81733
81777
  renderWorkflowPrompt: () => renderWorkflowPrompt,
81734
81778
  renderTemplate: () => renderTemplate,
81735
81779
  parseWorkflow: () => parseWorkflow,
81780
+ parseLinearFilter: () => parseLinearFilter,
81736
81781
  matchesIndicator: () => matchesIndicator,
81737
81782
  loadWorkflow: () => loadWorkflow,
81738
81783
  ensureWorkflow: () => ensureWorkflow,
@@ -81896,6 +81941,7 @@ var init_workflow = __esm(() => {
81896
81941
  init_wizard();
81897
81942
  init_schema();
81898
81943
  init_default();
81944
+ init_linear_filter();
81899
81945
  import_yaml2 = __toESM(require_dist(), 1);
81900
81946
  });
81901
81947
 
@@ -83968,6 +84014,11 @@ var init_migrations = __esm(() => {
83968
84014
  version: 2,
83969
84015
  description: "Ralphy now detects the current git repo and records it in WORKFLOW.md, " + "linking it to your Linear team. Confirm the detected repo to adopt it.",
83970
84016
  fields: ["repo.link"]
84017
+ },
84018
+ {
84019
+ version: 3,
84020
+ description: "The per-workflow `linear.assignee` setting is replaced by a global " + "`linear.filter` expression (e.g. `assignee = me`) applied to every " + "ticket fetch. Existing `assignee` values are folded in automatically; " + "note that an empty filter now defaults to `assignee = me` (it previously " + "meant unassigned-only).",
84021
+ fields: ["linear.filter"]
83971
84022
  }
83972
84023
  ];
83973
84024
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max2, migration) => Math.max(max2, migration.version), 0);
@@ -84055,8 +84106,8 @@ function initialValuesFromConfig(config2) {
84055
84106
  values2["useWorktree"] = config2.useWorktree;
84056
84107
  if (config2.linear.team)
84057
84108
  values2["linear.team"] = config2.linear.team;
84058
- if (config2.linear.assignee)
84059
- values2["linear.assignee"] = config2.linear.assignee;
84109
+ if (config2.linear.filter)
84110
+ values2["linear.filter"] = config2.linear.filter;
84060
84111
  return values2;
84061
84112
  }
84062
84113
  async function promptEditOrExit() {
@@ -101403,6 +101454,7 @@ async function parseAgentArgs(argv) {
101403
101454
  ...common2,
101404
101455
  mode: "agent",
101405
101456
  linearTeam: "",
101457
+ linearFilter: "",
101406
101458
  linearAssignee: "",
101407
101459
  pollInterval: 0,
101408
101460
  concurrency: 0,
@@ -101419,10 +101471,12 @@ async function parseAgentArgs(argv) {
101419
101471
  noTmux: false,
101420
101472
  checks: false,
101421
101473
  review: false,
101474
+ agentDebug: false,
101422
101475
  ticketTokens: []
101423
101476
  };
101424
101477
  const state = emptyParseState();
101425
101478
  let expectLinearTeam = false;
101479
+ let expectLinearFilter = false;
101426
101480
  let expectLinearAssignee = false;
101427
101481
  let expectPollInterval = false;
101428
101482
  let expectConcurrency = false;
@@ -101436,6 +101490,11 @@ async function parseAgentArgs(argv) {
101436
101490
  expectLinearTeam = false;
101437
101491
  continue;
101438
101492
  }
101493
+ if (expectLinearFilter) {
101494
+ result2.linearFilter = arg;
101495
+ expectLinearFilter = false;
101496
+ continue;
101497
+ }
101439
101498
  if (expectLinearAssignee) {
101440
101499
  result2.linearAssignee = arg;
101441
101500
  expectLinearAssignee = false;
@@ -101481,6 +101540,9 @@ async function parseAgentArgs(argv) {
101481
101540
  case "--linear-team":
101482
101541
  expectLinearTeam = true;
101483
101542
  break;
101543
+ case "--linear-filter":
101544
+ expectLinearFilter = true;
101545
+ break;
101484
101546
  case "--linear-assignee":
101485
101547
  expectLinearAssignee = true;
101486
101548
  break;
@@ -101532,6 +101594,9 @@ async function parseAgentArgs(argv) {
101532
101594
  case "--debug":
101533
101595
  result2.debug = true;
101534
101596
  break;
101597
+ case "--agent-debug":
101598
+ result2.agentDebug = true;
101599
+ break;
101535
101600
  case "--pre-existing-error-check":
101536
101601
  result2.preExistingErrorCheck = true;
101537
101602
  break;
@@ -101605,7 +101670,8 @@ var init_cli2 = __esm(() => {
101605
101670
  " --log Log raw engine stream",
101606
101671
  " --verbose Verbose output",
101607
101672
  " --linear-team <key> Linear team key (e.g. ENG)",
101608
- " --linear-assignee <id> Filter by assignee (user id, email, or 'me')",
101673
+ " --linear-filter <expr> Global Linear filter (e.g. 'assignee = me', 'assignee = any')",
101674
+ " --linear-assignee <id> [deprecated] Filter by assignee; use --linear-filter instead",
101609
101675
  " --poll-interval <s> Seconds between Linear polls (default: 60)",
101610
101676
  " --concurrency <n> Max concurrent task loops (default: 1)",
101611
101677
  " --worktree Run each task in its own git worktree",
@@ -101630,6 +101696,7 @@ var init_cli2 = __esm(() => {
101630
101696
  " --checks List mode: show failing CI check names per PR",
101631
101697
  " --review List mode: show unresolved review comment count per PR",
101632
101698
  " --debug List mode: explain why a Linear ticket was not picked up (use with --name)",
101699
+ " --agent-debug After each ticket finishes, run a one-shot self-review and write a report to ~/.ralph/retro/",
101633
101700
  " --help, -h Show this help message",
101634
101701
  "",
101635
101702
  "Examples:",
@@ -102290,9 +102357,11 @@ async function fetchMentionScanIssues(apiKey, spec) {
102290
102357
  const where = branches.length === 1 ? { ...branches[0] } : { or: branches };
102291
102358
  if (spec.team)
102292
102359
  where.team = { key: { eq: spec.team } };
102293
- if (spec.assignee) {
102360
+ if (spec.anyAssignee || spec.assignee === "any") {} else if (spec.assignee) {
102294
102361
  if (spec.assignee === "me")
102295
102362
  where.assignee = { isMe: { eq: true } };
102363
+ else if (spec.assignee === "unassigned")
102364
+ where.assignee = { null: true };
102296
102365
  else if (spec.assignee.includes("@"))
102297
102366
  where.assignee = { email: { eq: spec.assignee } };
102298
102367
  else
@@ -104487,6 +104556,15 @@ async function runPostTask(input, deps) {
104487
104556
  emit3(succeeded ? "done" : "gave-up", succeeded ? undefined : `exit ${effectiveCode}`);
104488
104557
  if (!succeeded)
104489
104558
  await recordGaveUp(stateFilePath, log3, changeName);
104559
+ await deps.runRetrospective?.({
104560
+ changeName,
104561
+ cwd: cwd2,
104562
+ changeDir,
104563
+ stateFilePath,
104564
+ branch,
104565
+ issue: issue2,
104566
+ effectiveCode
104567
+ });
104490
104568
  await runWorktreeCleanupPhase({ changeName, cwd: cwd2, projectRoot, useWorktree, effectiveCode, cfg }, { git: git2, log: log3, emit: emit3 });
104491
104569
  await runTeardownPhase({ cwd: cwd2, teardownScript: cfg.teardownScript }, { runScript, log: log3, emit: emit3 });
104492
104570
  return effectiveCode;
@@ -104893,6 +104971,7 @@ class AgentCoordinator {
104893
104971
  ciFailed: 0,
104894
104972
  review: 0,
104895
104973
  mentions: mentions.length,
104974
+ quarantined: 0,
104896
104975
  awaiting: awaitingCount
104897
104976
  };
104898
104977
  const found2 = buckets2.todo + buckets2.inProgress + buckets2.mentions + buckets2.awaiting;
@@ -104987,6 +105066,7 @@ class AgentCoordinator {
104987
105066
  ciFailed: prStatus.ciFailed,
104988
105067
  review: 0,
104989
105068
  mentions: mentions.length,
105069
+ quarantined: prStatus.quarantined,
104990
105070
  awaiting: awaitingCount
104991
105071
  };
104992
105072
  const found = buckets.todo + buckets.inProgress + buckets.conflicted + buckets.ciFailed + buckets.mentions + buckets.awaiting;
@@ -105152,6 +105232,32 @@ class AgentCoordinator {
105152
105232
  if (!this.deps.syncTasks || !this.deps.getIterationCount)
105153
105233
  return;
105154
105234
  for (const w of this.workers) {
105235
+ if (this.deps.getTasksFingerprint) {
105236
+ let fingerprint;
105237
+ try {
105238
+ fingerprint = await this.deps.getTasksFingerprint(w.changeName);
105239
+ } catch (err) {
105240
+ this.deps.onLog(`! tasks fingerprint read failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
105241
+ continue;
105242
+ }
105243
+ if (fingerprint === null || fingerprint === w.lastSyncedTasksFingerprint) {
105244
+ continue;
105245
+ }
105246
+ let iteration;
105247
+ try {
105248
+ iteration = await this.deps.getIterationCount(w.changeName);
105249
+ } catch (err) {
105250
+ this.deps.onLog(`! iteration count read failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
105251
+ continue;
105252
+ }
105253
+ try {
105254
+ await this.deps.syncTasks(w, iteration);
105255
+ w.lastSyncedTasksFingerprint = fingerprint;
105256
+ } catch (err) {
105257
+ this.deps.onLog(`! sync-tasks (poll) failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
105258
+ }
105259
+ continue;
105260
+ }
105155
105261
  let count;
105156
105262
  try {
105157
105263
  count = await this.deps.getIterationCount(w.changeName);
@@ -105182,6 +105288,7 @@ class AgentCoordinator {
105182
105288
  return counts;
105183
105289
  const preQueue = this.queue.map((q) => ({ id: q.issue.id, trigger: q.trigger }));
105184
105290
  const preWorkers = this.workers.map((w) => ({ id: w.issueId, trigger: w.trigger }));
105291
+ const tracker = this.opts.prTracker;
105185
105292
  for (const issue2 of candidates) {
105186
105293
  if (this.workers.some((w) => w.issueId === issue2.id))
105187
105294
  continue;
@@ -105189,6 +105296,13 @@ class AgentCoordinator {
105189
105296
  continue;
105190
105297
  if (this.queue.some((q) => q.issue.id === issue2.id))
105191
105298
  continue;
105299
+ if (tracker?.isBailed(issue2.identifier) && this.errorMarkerCleared(issue2)) {
105300
+ await tracker.clear(issue2.identifier).catch(() => {});
105301
+ this.conflictNotified.delete(issue2.id);
105302
+ this.ciFailedNotified.delete(issue2.id);
105303
+ this.conflictPromoted.delete(issue2.id);
105304
+ this.deps.onLog(` ${issue2.identifier}: pr-tracker bail cleared (ticket back in Todo) \u2014 retrying recovery`, "cyan");
105305
+ }
105192
105306
  let pr;
105193
105307
  try {
105194
105308
  pr = await this.deps.checkPrStatus(issue2);
@@ -105208,10 +105322,18 @@ class AgentCoordinator {
105208
105322
  }
105209
105323
  }
105210
105324
  if (pr.status === "conflicted") {
105325
+ if (tracker?.isBailed(issue2.identifier)) {
105326
+ counts.quarantined += 1;
105327
+ continue;
105328
+ }
105329
+ counts.conflicted += 1;
105211
105330
  if (this.conflictNotified.has(issue2.id))
105212
105331
  continue;
105213
- if (await this.prTrackerBail(issue2, pr.url, "conflicting"))
105332
+ if (await this.prTrackerBail(issue2, pr.url, "conflicting")) {
105333
+ counts.conflicted -= 1;
105334
+ counts.quarantined += 1;
105214
105335
  continue;
105336
+ }
105215
105337
  emitCapture(this.bus, "agent_conflict_detected", { issue_identifier: issue2.identifier });
105216
105338
  this.conflictNotified.add(issue2.id);
105217
105339
  this.deps.onLog(` ${issue2.identifier}: PR ${pr.url} conflicting \u2014 queued (conflict-fix)`, "yellow");
@@ -105230,14 +105352,21 @@ class AgentCoordinator {
105230
105352
  trigger: "conflict-fix",
105231
105353
  priority: defaultPriorityFor("conflict-fix")
105232
105354
  });
105233
- counts.conflicted += 1;
105234
105355
  continue;
105235
105356
  }
105236
105357
  if (pr.status === "ci_failed") {
105358
+ if (tracker?.isBailed(issue2.identifier)) {
105359
+ counts.quarantined += 1;
105360
+ continue;
105361
+ }
105362
+ counts.ciFailed += 1;
105237
105363
  if (this.ciFailedNotified.has(issue2.id))
105238
105364
  continue;
105239
- if (await this.prTrackerBail(issue2, pr.url, "ci_failed"))
105365
+ if (await this.prTrackerBail(issue2, pr.url, "ci_failed")) {
105366
+ counts.ciFailed -= 1;
105367
+ counts.quarantined += 1;
105240
105368
  continue;
105369
+ }
105241
105370
  emitCapture(this.bus, "agent_ci_failed_detected", { issue_identifier: issue2.identifier });
105242
105371
  this.ciFailedNotified.add(issue2.id);
105243
105372
  this.deps.onLog(` ${issue2.identifier}: PR ${pr.url} CI failing \u2014 queued (ci-fix)`, "yellow");
@@ -105256,7 +105385,6 @@ class AgentCoordinator {
105256
105385
  trigger: "ci-fix",
105257
105386
  priority: defaultPriorityFor("ci-fix")
105258
105387
  });
105259
- counts.ciFailed += 1;
105260
105388
  }
105261
105389
  }
105262
105390
  for (const q of preQueue) {
@@ -105273,6 +105401,16 @@ class AgentCoordinator {
105273
105401
  }
105274
105402
  return counts;
105275
105403
  }
105404
+ errorMarkerCleared(issue2) {
105405
+ const se = this.opts.setError;
105406
+ if (!se)
105407
+ return false;
105408
+ const wantLabels = markersOf(se).filter((m) => m.type === "label").map((m) => m.value.toLowerCase());
105409
+ if (wantLabels.length === 0)
105410
+ return false;
105411
+ const have = new Set(issue2.labels.map((l) => l.toLowerCase()));
105412
+ return !wantLabels.some((v) => have.has(v));
105413
+ }
105276
105414
  async prTrackerBail(issue2, prUrl, reason) {
105277
105415
  const tracker = this.opts.prTracker;
105278
105416
  if (!tracker)
@@ -105399,6 +105537,7 @@ class AgentCoordinator {
105399
105537
  kill: handle.kill,
105400
105538
  lastReportedIteration: 0,
105401
105539
  lastSyncedIteration: 0,
105540
+ lastSyncedTasksFingerprint: null,
105402
105541
  restarting: false,
105403
105542
  reapedForAwaiting: false
105404
105543
  };
@@ -105562,6 +105701,7 @@ class AgentCoordinator {
105562
105701
  kill: () => {},
105563
105702
  lastReportedIteration: 0,
105564
105703
  lastSyncedIteration: 0,
105704
+ lastSyncedTasksFingerprint: null,
105565
105705
  restarting: false,
105566
105706
  reapedForAwaiting: false
105567
105707
  };
@@ -105652,7 +105792,12 @@ function triggerToFlowId(trigger) {
105652
105792
  return "review-followup";
105653
105793
  return "implement";
105654
105794
  }
105655
- var emptyPrStatus = () => ({ mergeable: 0, conflicted: 0, ciFailed: 0 }), emptyPollResult = () => ({
105795
+ var emptyPrStatus = () => ({
105796
+ mergeable: 0,
105797
+ conflicted: 0,
105798
+ ciFailed: 0,
105799
+ quarantined: 0
105800
+ }), emptyPollResult = () => ({
105656
105801
  found: 0,
105657
105802
  added: 0,
105658
105803
  buckets: {
@@ -105662,6 +105807,7 @@ var emptyPrStatus = () => ({ mergeable: 0, conflicted: 0, ciFailed: 0 }), emptyP
105662
105807
  ciFailed: 0,
105663
105808
  review: 0,
105664
105809
  mentions: 0,
105810
+ quarantined: 0,
105665
105811
  awaiting: 0
105666
105812
  },
105667
105813
  prStatus: emptyPrStatus(),
@@ -105669,6 +105815,7 @@ var emptyPrStatus = () => ({ mergeable: 0, conflicted: 0, ciFailed: 0 }), emptyP
105669
105815
  flow: {}
105670
105816
  });
105671
105817
  var init_coordinator = __esm(() => {
105818
+ init_types2();
105672
105819
  init_post_task();
105673
105820
  init_queue_order();
105674
105821
  init_src();
@@ -106550,10 +106697,10 @@ function unionMarkers(...sets) {
106550
106697
  }
106551
106698
  return out;
106552
106699
  }
106553
- function describeIndicators(indicators, team, assignee) {
106700
+ function describeIndicators(indicators, team, assignee, anyAssignee) {
106554
106701
  const parts = [];
106555
106702
  parts.push(`team=${team ?? "*"}`);
106556
- parts.push(`assignee=${assignee ?? "*"}`);
106703
+ parts.push(`assignee=${anyAssignee ? "any" : assignee ?? "*"}`);
106557
106704
  if (indicators.getTodo) {
106558
106705
  parts.push(`todo=[${indicators.getTodo.filter.map((m) => `${m.type}:${m.value}`).join(",")}]`);
106559
106706
  }
@@ -106568,7 +106715,7 @@ var init_indicators = __esm(() => {
106568
106715
 
106569
106716
  // apps/agent/src/agent/wire/linear-resolvers.ts
106570
106717
  function createLinearResolvers(input) {
106571
- const { apiKey, team, assignee, diag } = input;
106718
+ const { apiKey, team, assignee, anyAssignee, diag } = input;
106572
106719
  const ticketNumbers = input.ticketNumbers ?? [];
106573
106720
  const stateCache = new Map;
106574
106721
  const labelCache = new Map;
@@ -106684,6 +106831,7 @@ function createLinearResolvers(input) {
106684
106831
  const spec = {
106685
106832
  team,
106686
106833
  assignee,
106834
+ anyAssignee,
106687
106835
  include,
106688
106836
  exclude: excl,
106689
106837
  ...ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
@@ -107417,6 +107565,7 @@ function createMentionScanner(input) {
107417
107565
  cfg,
107418
107566
  team,
107419
107567
  assignee,
107568
+ anyAssignee,
107420
107569
  indicators,
107421
107570
  projectRoot,
107422
107571
  useWorktree,
@@ -107440,6 +107589,7 @@ function createMentionScanner(input) {
107440
107589
  candidates = await fetchMentionScanIssues(apiKey, {
107441
107590
  team,
107442
107591
  assignee,
107592
+ anyAssignee,
107443
107593
  ...ticketNumbers && ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {},
107444
107594
  indicators: {
107445
107595
  ...indicators.getTodo !== undefined ? { getTodo: indicators.getTodo } : {},
@@ -107659,8 +107809,190 @@ var init_default2 = __esm(() => {
107659
107809
  init_log();
107660
107810
  });
107661
107811
 
107812
+ // apps/agent/src/agent/state/agent-run-state.ts
107813
+ import { basename as basename3, join as join29 } from "path";
107814
+ import { homedir as homedir6 } from "os";
107815
+ import { mkdir as mkdir11, writeFile } from "fs/promises";
107816
+ function agentRunStatePath(projectRoot) {
107817
+ return join29(homedir6(), ".ralph", basename3(projectRoot), "agent-state.json");
107818
+ }
107819
+ async function writeAgentRunState(state) {
107820
+ const path = agentRunStatePath(state.projectRoot);
107821
+ try {
107822
+ await mkdir11(join29(homedir6(), ".ralph", basename3(state.projectRoot)), { recursive: true });
107823
+ await writeFile(path, JSON.stringify(state, null, 2) + `
107824
+ `, "utf-8");
107825
+ } catch {}
107826
+ }
107827
+ var init_agent_run_state = () => {};
107828
+
107829
+ // packages/retro/src/disposition.ts
107830
+ function dispositionFromExitCode(code) {
107831
+ switch (code) {
107832
+ case 0:
107833
+ return "done";
107834
+ case NO_CHANGES_EXIT2:
107835
+ return "no-changes";
107836
+ case CI_FAILED_EXIT2:
107837
+ return "ci-failed";
107838
+ case PR_FAILED_EXIT2:
107839
+ return "pr-failed";
107840
+ default:
107841
+ return "error";
107842
+ }
107843
+ }
107844
+ var CI_FAILED_EXIT2 = 70, PR_FAILED_EXIT2 = 71, NO_CHANGES_EXIT2 = 72;
107845
+
107846
+ // packages/retro/src/paths.ts
107847
+ import { homedir as homedir7 } from "os";
107848
+ import { mkdir as mkdir12 } from "fs/promises";
107849
+ import { join as join30 } from "path";
107850
+ function retroDir() {
107851
+ return join30(homedir7(), ".ralph", "retro");
107852
+ }
107853
+ async function resolveRetroOutputPath(identifier, date5, dir = retroDir()) {
107854
+ await mkdir12(dir, { recursive: true });
107855
+ const base2 = join30(dir, `${identifier}-${date5}.md`);
107856
+ if (!await Bun.file(base2).exists())
107857
+ return base2;
107858
+ for (let n = 2;; n++) {
107859
+ const candidate = join30(dir, `${identifier}-${date5}-${n}.md`);
107860
+ if (!await Bun.file(candidate).exists())
107861
+ return candidate;
107862
+ }
107863
+ }
107864
+ var init_paths2 = () => {};
107865
+
107866
+ // packages/retro/src/prompt.ts
107867
+ function buildRetroPrompt(ctx, outputPath) {
107868
+ const disposition = dispositionFromExitCode(ctx.exitCode);
107869
+ const { paths } = ctx;
107870
+ const line = (label, value) => value ? `- ${label}: ${value}` : `- ${label}: (unavailable \u2014 note this in the report)`;
107871
+ return [
107872
+ `You are a retrospective analysis agent reviewing a finished automated ticket run.`,
107873
+ `Your job is to read the run's artifacts and write a thorough, honest self-review`,
107874
+ `to a markdown file. You are NOT fixing anything \u2014 this is analysis only.`,
107875
+ ``,
107876
+ `## Ticket`,
107877
+ ``,
107878
+ `- Identifier: ${ctx.identifier}`,
107879
+ `- Change name: ${ctx.changeName}`,
107880
+ `- Terminal disposition: ${disposition} (worker exit code ${ctx.exitCode})`,
107881
+ ctx.prUrl ? `- Pull request: ${ctx.prUrl}` : `- Pull request: none was opened`,
107882
+ `- Date: ${ctx.date}`,
107883
+ ``,
107884
+ `### Ticket details`,
107885
+ ``,
107886
+ ctx.ticketDigest,
107887
+ ``,
107888
+ `## Data sources`,
107889
+ ``,
107890
+ `Read whatever of the following exist. If a path is missing or empty, say so`,
107891
+ `explicitly in the report rather than guessing.`,
107892
+ ``,
107893
+ line("Change directory (proposal/design/tasks/specs)", paths.changeDir),
107894
+ line("Loop state file", paths.stateFilePath),
107895
+ line("Worker log", paths.logFile),
107896
+ line("JSON event log", paths.jsonLogFile),
107897
+ line("Agent run state", paths.agentStateFile),
107898
+ ctx.prUrl ? `- You may inspect the PR read-only with \`gh pr view ${ctx.prUrl}\` and \`gh pr diff ${ctx.prUrl}\`.` : `- No PR exists; skip the PR section and note "no PR".`,
107899
+ ``,
107900
+ `## Required report structure`,
107901
+ ``,
107902
+ `Write GitHub-flavored markdown with these sections:`,
107903
+ `1. **Summary** \u2014 what the ticket asked for and how the run ended.`,
107904
+ `2. **What went well** \u2014 concrete things the run did right.`,
107905
+ `3. **What went wrong / friction** \u2014 failures, retries, wasted iterations,`,
107906
+ ` wrong turns, anything that cost time or quality.`,
107907
+ `4. **Root-cause analysis** \u2014 for each problem, why it happened.`,
107908
+ `5. **Recommendations** \u2014 specific, actionable improvements (to the prompt,`,
107909
+ ` the tasks, the codebase, or the workflow).`,
107910
+ `6. **Data gaps** \u2014 which data sources were unavailable or unread.`,
107911
+ ``,
107912
+ `## Output`,
107913
+ ``,
107914
+ `Write the complete report to this exact path using your file-write tool:`,
107915
+ ``,
107916
+ ` ${outputPath}`,
107917
+ ``,
107918
+ `## Hard rules`,
107919
+ ``,
107920
+ `- Do NOT run any git mutation: no commit, add, push, rebase, reset, checkout,`,
107921
+ ` branch, merge, tag, or stash.`,
107922
+ `- Do NOT create, edit, comment on, close, or merge any pull request or issue.`,
107923
+ `- Do NOT modify any source file. The ONLY file you may write is the report at`,
107924
+ ` the path above.`,
107925
+ `- Read-only inspection commands (\`git log\`, \`git diff\`, \`gh pr view\`,`,
107926
+ ` \`gh pr diff\`, reading files) are allowed.`
107927
+ ].join(`
107928
+ `);
107929
+ }
107930
+ var init_prompt = () => {};
107931
+
107932
+ // packages/retro/src/retro.ts
107933
+ async function runRetrospective(ctx, deps) {
107934
+ const { log: log3, runEngine: runEngine2, seen } = deps;
107935
+ const disposition = dispositionFromExitCode(ctx.exitCode);
107936
+ const key = `${ctx.identifier}:${disposition}:${ctx.date}`;
107937
+ if (seen.has(key)) {
107938
+ log3(` retrospective skipped for ${ctx.identifier} (already generated this run)`, "gray");
107939
+ return { written: false, skipped: "duplicate", disposition };
107940
+ }
107941
+ seen.add(key);
107942
+ try {
107943
+ const outputPath = await resolveRetroOutputPath(ctx.identifier, ctx.date);
107944
+ const prompt = buildRetroPrompt(ctx, outputPath);
107945
+ log3(` running retrospective for ${ctx.identifier} (${disposition}) \u2192 ${outputPath}`, "cyan");
107946
+ await runEngine2({
107947
+ engine: ctx.engine,
107948
+ model: ctx.model,
107949
+ prompt,
107950
+ cwd: ctx.cwd,
107951
+ onOutput: (l) => log3(l, "gray")
107952
+ });
107953
+ const written = await Bun.file(outputPath).exists();
107954
+ if (written) {
107955
+ log3(` retrospective written: ${outputPath}`, "green");
107956
+ } else {
107957
+ log3(`! retrospective engine finished but no report was written at ${outputPath}`, "yellow");
107958
+ }
107959
+ return { written, outputPath, disposition };
107960
+ } catch (err) {
107961
+ log3(`! retrospective failed for ${ctx.identifier}: ${err.message}`, "yellow");
107962
+ return { written: false, disposition };
107963
+ }
107964
+ }
107965
+ var init_retro = __esm(() => {
107966
+ init_paths2();
107967
+ init_prompt();
107968
+ init_paths2();
107969
+ init_prompt();
107970
+ });
107971
+
107662
107972
  // apps/agent/src/agent/wire/spawn/worker.ts
107663
- import { join as join29 } from "path";
107973
+ import { join as join31 } from "path";
107974
+ function localDateStamp(d) {
107975
+ const y = d.getFullYear();
107976
+ const m = String(d.getMonth() + 1).padStart(2, "0");
107977
+ const day = String(d.getDate()).padStart(2, "0");
107978
+ return `${y}-${m}-${day}`;
107979
+ }
107980
+ function buildTicketDigest(issue2, comments) {
107981
+ if (!issue2)
107982
+ return "(ticket details unavailable)";
107983
+ const lines = [`Title: ${issue2.title}`, "", issue2.description?.trim() || "(no description)"];
107984
+ if (comments.length > 0) {
107985
+ lines.push("", "Comments:");
107986
+ for (const c of comments) {
107987
+ lines.push(`- ${c.user?.name ?? "unknown"}: ${c.body}`);
107988
+ }
107989
+ }
107990
+ return lines.join(`
107991
+ `);
107992
+ }
107993
+ function retroDepEntry(agentDebug, hook) {
107994
+ return agentDebug ? { runRetrospective: hook } : {};
107995
+ }
107664
107996
  function createSpawnWorker(input) {
107665
107997
  const {
107666
107998
  args,
@@ -107739,10 +108071,52 @@ function createSpawnWorker(input) {
107739
108071
  c.push("--from-agent");
107740
108072
  return c;
107741
108073
  }
108074
+ const retroSeen = new Set;
108075
+ const runRetrospectiveHook = async (info) => {
108076
+ try {
108077
+ const identifier = info.issue?.identifier ?? info.changeName;
108078
+ const prUrl = prByChange?.get(info.changeName) ?? null;
108079
+ let digest = "(ticket details unavailable)";
108080
+ if (info.issue) {
108081
+ let comments = [];
108082
+ try {
108083
+ comments = await fetchIssueComments(apiKey, info.issue.id);
108084
+ } catch {}
108085
+ digest = buildTicketDigest(info.issue, comments);
108086
+ }
108087
+ const engine = args.engineSet ? args.engine : cfg.engine;
108088
+ const model = args.engineSet ? args.model : cfg.model;
108089
+ const ctx = {
108090
+ identifier,
108091
+ changeName: info.changeName,
108092
+ cwd: info.cwd,
108093
+ engine,
108094
+ model,
108095
+ exitCode: info.effectiveCode,
108096
+ prUrl,
108097
+ date: localDateStamp(new Date),
108098
+ ticketDigest: digest,
108099
+ paths: {
108100
+ changeDir: info.changeDir,
108101
+ stateFilePath: info.stateFilePath,
108102
+ logFile: join31(logsDir, `${info.changeName}.log`),
108103
+ jsonLogFile: args.jsonLogFile ?? null,
108104
+ agentStateFile: agentRunStatePath(projectRoot)
108105
+ }
108106
+ };
108107
+ await runRetrospective(ctx, {
108108
+ runEngine: (opts) => runEngine(opts),
108109
+ log: onLog,
108110
+ seen: retroSeen
108111
+ });
108112
+ } catch (err) {
108113
+ onLog(`! retrospective failed: ${err.message}`, "yellow");
108114
+ }
108115
+ };
107742
108116
  return function spawnWorker(changeName, _issue, trigger) {
107743
108117
  const cwd2 = cwdByChange.get(changeName) ?? projectRoot;
107744
108118
  const injected = runners?.spawnWorker;
107745
- const missionTasksPath = join29(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
108119
+ const missionTasksPath = join31(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
107746
108120
  const prevTasksPromise = (async () => {
107747
108121
  const f2 = Bun.file(missionTasksPath);
107748
108122
  return await f2.exists() ? await f2.text() : "";
@@ -107750,7 +108124,7 @@ function createSpawnWorker(input) {
107750
108124
  let logFilePath;
107751
108125
  let handle;
107752
108126
  if (injected) {
107753
- logFilePath = join29(logsDir, `${changeName}.log`);
108127
+ logFilePath = join31(logsDir, `${changeName}.log`);
107754
108128
  handle = injected(buildTaskCmdFor(changeName), cwd2);
107755
108129
  } else {
107756
108130
  const r = defaultSpawn(changeName, buildTaskCmdFor(changeName), cwd2, logsDir, onWorkerOutput, `spawn at ${new Date().toISOString()}`);
@@ -107772,7 +108146,7 @@ function createSpawnWorker(input) {
107772
108146
  const wantAutoMerge = issueForChange ? issueMatchesGetIndicator(issueForChange, indicators.getAutoMerge) : false;
107773
108147
  const wrapped = handle.exited.then(async (code) => {
107774
108148
  const workerLayout = projectLayout(cwd2);
107775
- const validateSpecPath = join29(workerLayout.changeDir(changeName), "specs", "validate.md");
108149
+ const validateSpecPath = join31(workerLayout.changeDir(changeName), "specs", "validate.md");
107776
108150
  const hasValidateSpec = await Bun.file(validateSpecPath).exists();
107777
108151
  const wantValidateOnly = hasValidateSpec && !wantPrBase;
107778
108152
  if (hasValidateSpec) {
@@ -107857,6 +108231,7 @@ function createSpawnWorker(input) {
107857
108231
  git: gitRunner,
107858
108232
  log: onLog,
107859
108233
  runScript,
108234
+ ...retroDepEntry(args.agentDebug, runRetrospectiveHook),
107860
108235
  registerPr: (cn, url2) => onPrRegistered(cn, url2),
107861
108236
  ...onWorkerPhase && {
107862
108237
  onPhase: (phase2, detail) => onWorkerPhase(changeName, phase2, detail)
@@ -107892,6 +108267,9 @@ var init_worker = __esm(() => {
107892
108267
  init_runners();
107893
108268
  init_pr_helpers();
107894
108269
  init_wait_for_mergeability();
108270
+ init_agent_run_state();
108271
+ init_retro();
108272
+ init_engine();
107895
108273
  });
107896
108274
 
107897
108275
  // apps/agent/src/agent/baseline/runner.ts
@@ -108234,8 +108612,8 @@ var init_linear_sync = __esm(() => {
108234
108612
  });
108235
108613
 
108236
108614
  // apps/agent/src/agent/linear-sync/comment-sync.ts
108237
- import { dirname as dirname13, join as join30 } from "path";
108238
- import { mkdir as mkdir11, rename, unlink as unlink2 } from "fs/promises";
108615
+ import { dirname as dirname13, join as join32 } from "path";
108616
+ import { mkdir as mkdir13, rename, unlink as unlink2 } from "fs/promises";
108239
108617
  async function readStateJson(statePath) {
108240
108618
  const file2 = Bun.file(statePath);
108241
108619
  if (!await file2.exists())
@@ -108247,7 +108625,7 @@ async function readStateJson(statePath) {
108247
108625
  }
108248
108626
  }
108249
108627
  async function writeStateJson(statePath, state) {
108250
- await mkdir11(dirname13(statePath), { recursive: true });
108628
+ await mkdir13(dirname13(statePath), { recursive: true });
108251
108629
  const tmp = `${statePath}.tmp-${process.pid}-${writeStateSeq++}`;
108252
108630
  try {
108253
108631
  await Bun.write(tmp, JSON.stringify(state, null, 2) + `
@@ -108286,7 +108664,7 @@ function isCommentNotFoundError(err) {
108286
108664
  return text.includes("not found") || text.includes("could not find") || text.includes("entity not found");
108287
108665
  }
108288
108666
  async function readTasksMd(changeDir, log3) {
108289
- const file2 = Bun.file(join30(changeDir, "tasks.md"));
108667
+ const file2 = Bun.file(join32(changeDir, "tasks.md"));
108290
108668
  if (!await file2.exists()) {
108291
108669
  log3(` comment-sync: tasks.md missing in ${changeDir}, skipping`, "gray");
108292
108670
  return null;
@@ -108394,14 +108772,14 @@ async function postPlanCommentOnce(deps) {
108394
108772
  const check2 = parsePlanningSection(tasksMd);
108395
108773
  if (!check2.allChecked)
108396
108774
  return null;
108397
- const proposalPath = join30(deps.changeDir, "proposal.md");
108775
+ const proposalPath = join32(deps.changeDir, "proposal.md");
108398
108776
  const why = await readSection(proposalPath, "Why");
108399
108777
  const whatChanges = await readSection(proposalPath, "What Changes");
108400
108778
  if (!why && !whatChanges) {
108401
108779
  deps.log(` comment-sync: proposal.md has no Why/What Changes, skipping plan comment`, "gray");
108402
108780
  return null;
108403
108781
  }
108404
- const designSummary = await readFirstParagraph(join30(deps.changeDir, "design.md"));
108782
+ const designSummary = await readFirstParagraph(join32(deps.changeDir, "design.md"));
108405
108783
  const parts = [`### ${PLAN_COMMENT_TITLE} \u2014 \`${deps.changeName}\``];
108406
108784
  if (why) {
108407
108785
  parts.push("", "**Why**", "", why);
@@ -260677,7 +261055,7 @@ var init_render_pdf = __esm(() => {
260677
261055
  });
260678
261056
 
260679
261057
  // apps/agent/src/agent/linear-sync/spec-attachments.ts
260680
- import { dirname as dirname14, join as join31 } from "path";
261058
+ import { dirname as dirname14, join as join33 } from "path";
260681
261059
  function describeLinearError(err) {
260682
261060
  const e = err;
260683
261061
  const parts = [e.message ?? String(err)];
@@ -260769,7 +261147,7 @@ async function syncSlot(deps, slot) {
260769
261147
  const [primaryName, ...trailingNames] = spec.sourceFiles;
260770
261148
  if (!primaryName)
260771
261149
  return;
260772
- const primary = Bun.file(join31(deps.changeDir, primaryName));
261150
+ const primary = Bun.file(join33(deps.changeDir, primaryName));
260773
261151
  if (!await primary.exists()) {
260774
261152
  deps.log(` spec-attachments: ${primaryName} missing, skipping`, "gray");
260775
261153
  return;
@@ -260788,7 +261166,7 @@ async function syncSlot(deps, slot) {
260788
261166
  const parts = [primaryBytes];
260789
261167
  const enc = new TextEncoder;
260790
261168
  for (const name of trailingNames) {
260791
- const f2 = Bun.file(join31(deps.changeDir, name));
261169
+ const f2 = Bun.file(join33(deps.changeDir, name));
260792
261170
  if (!await f2.exists())
260793
261171
  continue;
260794
261172
  try {
@@ -261053,9 +261431,9 @@ var init_comment_sync2 = __esm(() => {
261053
261431
  });
261054
261432
 
261055
261433
  // apps/agent/src/features/pr-tracker/state.ts
261056
- import { join as join32 } from "path";
261434
+ import { join as join34 } from "path";
261057
261435
  async function readState2(projectRoot) {
261058
- const path = join32(projectRoot, PR_TRACKER_STATE_RELPATH);
261436
+ const path = join34(projectRoot, PR_TRACKER_STATE_RELPATH);
261059
261437
  const file2 = Bun.file(path);
261060
261438
  if (!await file2.exists())
261061
261439
  return {};
@@ -261071,7 +261449,7 @@ async function readState2(projectRoot) {
261071
261449
  }
261072
261450
  }
261073
261451
  async function writeState2(projectRoot, state) {
261074
- const path = join32(projectRoot, PR_TRACKER_STATE_RELPATH);
261452
+ const path = join34(projectRoot, PR_TRACKER_STATE_RELPATH);
261075
261453
  await Bun.write(path, JSON.stringify(state, null, 2));
261076
261454
  }
261077
261455
  var PR_TRACKER_STATE_RELPATH = ".ralph/pr-tracker-state.json";
@@ -261150,7 +261528,7 @@ var init_pr_tracker = __esm(() => {
261150
261528
  });
261151
261529
 
261152
261530
  // apps/agent/src/agent/wire.ts
261153
- import { join as join33 } from "path";
261531
+ import { join as join35 } from "path";
261154
261532
  function buildAgentCoordinator(input) {
261155
261533
  const {
261156
261534
  args,
@@ -261169,7 +261547,7 @@ function buildAgentCoordinator(input) {
261169
261547
  onWorkerCmd,
261170
261548
  onAwaitingTicket
261171
261549
  } = input;
261172
- const logsDir = join33(projectRoot, ".ralph", "logs");
261550
+ const logsDir = join35(projectRoot, ".ralph", "logs");
261173
261551
  const bus = createBus();
261174
261552
  subscribeAgentDiag(bus, onLog);
261175
261553
  const diag = (area, message, color) => {
@@ -261179,7 +261557,8 @@ function buildAgentCoordinator(input) {
261179
261557
  const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
261180
261558
  const indicators = mergeIndicators(cfg.linear.indicators, args.indicators);
261181
261559
  const team = args.linearTeam || cfg.linear.team;
261182
- const assignee = args.linearAssignee || cfg.linear.assignee;
261560
+ const effectiveFilter = args.linearFilter || (args.linearAssignee ? `assignee = ${args.linearAssignee}` : "") || cfg.linear.filter;
261561
+ const { assignee, anyAssignee } = parseLinearFilter(effectiveFilter);
261183
261562
  const ticketNumbers = resolveTicketNumbers(args.ticketTokens, team);
261184
261563
  const excludeFromTodo = unionMarkers(indicators.setDone, indicators.setError);
261185
261564
  const gitRunner = input.runners?.git ?? bunGitRunner;
@@ -261216,6 +261595,7 @@ function buildAgentCoordinator(input) {
261216
261595
  apiKey,
261217
261596
  team,
261218
261597
  assignee,
261598
+ anyAssignee,
261219
261599
  diag,
261220
261600
  ...ticketNumbers.length > 0 ? { ticketNumbers } : {}
261221
261601
  });
@@ -261253,6 +261633,7 @@ function buildAgentCoordinator(input) {
261253
261633
  cfg,
261254
261634
  team,
261255
261635
  assignee,
261636
+ anyAssignee,
261256
261637
  indicators,
261257
261638
  projectRoot,
261258
261639
  useWorktree,
@@ -261382,6 +261763,18 @@ function buildAgentCoordinator(input) {
261382
261763
  const json2 = await file2.json();
261383
261764
  return json2.iteration ?? 0;
261384
261765
  },
261766
+ getTasksFingerprint: async (changeName) => {
261767
+ const root = cwdByChange.get(changeName) ?? projectRoot;
261768
+ const changeDir = projectLayout(root).changeDir(changeName);
261769
+ const parts = [];
261770
+ for (const name of ["tasks.md", "proposal.md", "design.md"]) {
261771
+ const file2 = Bun.file(join35(changeDir, name));
261772
+ if (!await file2.exists())
261773
+ continue;
261774
+ parts.push(`${name}:${file2.lastModified}:${file2.size}`);
261775
+ }
261776
+ return parts.length > 0 ? parts.join("|") : null;
261777
+ },
261385
261778
  ...commentSync.enabled && commentSync.syncTasks ? { syncTasks: commentSync.syncTasks } : {},
261386
261779
  ...commentSync.enabled && commentSync.onSteeringAppended ? { onSteeringAppended: commentSync.onSteeringAppended } : {}
261387
261780
  }, {
@@ -261396,7 +261789,7 @@ function buildAgentCoordinator(input) {
261396
261789
  ...prTracker ? { prTracker } : {}
261397
261790
  });
261398
261791
  coordRef.current = coord;
261399
- const filterDesc = describeIndicators(indicators, team, assignee);
261792
+ const filterDesc = describeIndicators(indicators, team, assignee, anyAssignee);
261400
261793
  const runBaselineGateOnce = createBaselineGateRunner({
261401
261794
  args,
261402
261795
  cfg,
@@ -261420,7 +261813,7 @@ function buildAgentCoordinator(input) {
261420
261813
  getGaveUpTotal: async () => {
261421
261814
  let total = 0;
261422
261815
  for (const [changeName, root] of cwdByChange) {
261423
- const file2 = Bun.file(join33(projectLayout(root).taskStateDir(changeName), GAVEUP_COUNT_FILE));
261816
+ const file2 = Bun.file(join35(projectLayout(root).taskStateDir(changeName), GAVEUP_COUNT_FILE));
261424
261817
  if (!await file2.exists())
261425
261818
  continue;
261426
261819
  try {
@@ -261432,6 +261825,7 @@ function buildAgentCoordinator(input) {
261432
261825
  };
261433
261826
  }
261434
261827
  var init_wire = __esm(() => {
261828
+ init_workflow();
261435
261829
  init_src2();
261436
261830
  init_coordinator2();
261437
261831
  init_linear();
@@ -261454,14 +261848,14 @@ var init_wire = __esm(() => {
261454
261848
  });
261455
261849
 
261456
261850
  // apps/agent/src/agent/json-log/json-log-file.ts
261457
- import { mkdir as mkdir12, appendFile as appendFile2 } from "fs/promises";
261851
+ import { mkdir as mkdir14, appendFile as appendFile2 } from "fs/promises";
261458
261852
  import { dirname as dirname15 } from "path";
261459
261853
  function createJsonLogFileSink(path) {
261460
261854
  if (!path)
261461
261855
  return { emit: () => {} };
261462
261856
  let chain = (async () => {
261463
261857
  try {
261464
- await mkdir12(dirname15(path), { recursive: true });
261858
+ await mkdir14(dirname15(path), { recursive: true });
261465
261859
  await Bun.write(path, "");
261466
261860
  } catch {}
261467
261861
  })();
@@ -261707,7 +262101,7 @@ var init_output_utils = __esm(() => {
261707
262101
  });
261708
262102
 
261709
262103
  // apps/agent/src/agent/state/worker-state-poll.ts
261710
- import { join as join34 } from "path";
262104
+ import { join as join36 } from "path";
261711
262105
  function parseSubtasks(tasksMd) {
261712
262106
  const out = [];
261713
262107
  let skipSection = false;
@@ -261740,7 +262134,7 @@ function initialWorkerSnapshot() {
261740
262134
  async function readWorkerSnapshot(input) {
261741
262135
  const next = { ...input.prev };
261742
262136
  try {
261743
- const file2 = Bun.file(join34(input.statesDir, input.changeName, ".ralph-state.json"));
262137
+ const file2 = Bun.file(join36(input.statesDir, input.changeName, ".ralph-state.json"));
261744
262138
  if (await file2.exists()) {
261745
262139
  const json2 = await file2.json();
261746
262140
  next.iter = json2.iteration ?? next.iter;
@@ -261749,10 +262143,10 @@ async function readWorkerSnapshot(input) {
261749
262143
  } catch {}
261750
262144
  if (input.changeDir) {
261751
262145
  try {
261752
- const tasksFile = Bun.file(join34(input.changeDir, "tasks.md"));
261753
- const proposalFile = Bun.file(join34(input.changeDir, "proposal.md"));
261754
- const designFile = Bun.file(join34(input.changeDir, "design.md"));
261755
- const reviewFindingsFile = Bun.file(join34(input.changeDir, "review-findings.md"));
262146
+ const tasksFile = Bun.file(join36(input.changeDir, "tasks.md"));
262147
+ const proposalFile = Bun.file(join36(input.changeDir, "proposal.md"));
262148
+ const designFile = Bun.file(join36(input.changeDir, "design.md"));
262149
+ const reviewFindingsFile = Bun.file(join36(input.changeDir, "review-findings.md"));
261756
262150
  const [tasksText, proposalText, designText, reviewFindingsText] = await Promise.all([
261757
262151
  tasksFile.exists().then((ok) => ok ? tasksFile.text() : null),
261758
262152
  proposalFile.exists().then((ok) => ok ? proposalFile.text() : null),
@@ -261815,7 +262209,7 @@ var init_worker_state_poll = __esm(() => {
261815
262209
  });
261816
262210
 
261817
262211
  // apps/agent/src/components/AgentMode.tsx
261818
- import { join as join35 } from "path";
262212
+ import { join as join37 } from "path";
261819
262213
  async function appendSteeringImpl(changeDir, message) {
261820
262214
  await runWithContext(createDefaultContext(), async () => {
261821
262215
  appendSteeringMessage(changeDir, message);
@@ -262753,6 +263147,19 @@ function AgentMode({
262753
263147
  /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
262754
263148
  color: pollStatus.lastPrStatus.ciFailed > 0 ? "red" : "white",
262755
263149
  children: pollStatus.lastPrStatus.ciFailed
263150
+ }, undefined, false, undefined, this),
263151
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
263152
+ dimColor: true,
263153
+ children: "\xB7"
263154
+ }, undefined, false, undefined, this),
263155
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
263156
+ dimColor: true,
263157
+ children: "quarantined"
263158
+ }, undefined, false, undefined, this),
263159
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
263160
+ color: pollStatus.lastPrStatus.quarantined > 0 ? "magenta" : "white",
263161
+ bold: true,
263162
+ children: pollStatus.lastPrStatus.quarantined
262756
263163
  }, undefined, false, undefined, this)
262757
263164
  ]
262758
263165
  }, undefined, true, undefined, this)
@@ -263375,7 +263782,7 @@ function AgentMode({
263375
263782
  },
263376
263783
  onSubmit: async (message) => {
263377
263784
  try {
263378
- await appendSteering2(join35(tasksDir, w2.changeName), message);
263785
+ await appendSteering2(join37(tasksDir, w2.changeName), message);
263379
263786
  fileEmit({ type: "steering_submitted", changeName: w2.changeName, message });
263380
263787
  } catch (err) {
263381
263788
  const text = err.message;
@@ -263489,7 +263896,7 @@ function shouldFallbackToJsonOutput(args, stdinIsTty) {
263489
263896
  }
263490
263897
 
263491
263898
  // apps/agent/src/runtime/tmux.ts
263492
- import { basename as basename3 } from "path";
263899
+ import { basename as basename4 } from "path";
263493
263900
  function tmuxAvailable() {
263494
263901
  const result2 = Bun.spawnSync({ cmd: ["tmux", "-V"], stderr: "pipe" });
263495
263902
  return result2.exitCode === 0;
@@ -263498,7 +263905,7 @@ function sessionName(projectRoot) {
263498
263905
  const override = process.env["RALPH_SESSION_NAME"];
263499
263906
  if (override)
263500
263907
  return override;
263501
- return `ralphy-agent-${basename3(projectRoot)}`;
263908
+ return `ralphy-agent-${basename4(projectRoot)}`;
263502
263909
  }
263503
263910
  function sessionExists(name) {
263504
263911
  const result2 = Bun.spawnSync({ cmd: ["tmux", "has-session", "-t", name], stderr: "pipe" });
@@ -263679,7 +264086,7 @@ __export(exports_list, {
263679
264086
  buildBuckets: () => buildBuckets,
263680
264087
  backlogRankByIssueId: () => backlogRankByIssueId
263681
264088
  });
263682
- import { join as join36 } from "path";
264089
+ import { join as join38 } from "path";
263683
264090
  function countTaskItems(content) {
263684
264091
  const checked = (content.match(/^- \[x\]/gm) ?? []).length;
263685
264092
  const unchecked = (content.match(/^- \[ \]/gm) ?? []).length;
@@ -263695,13 +264102,13 @@ function buildLocalRows() {
263695
264102
  const sources = [{ dir: statesDir, label: "main" }];
263696
264103
  const worktreesRoot = worktreesDir2(projectRoot);
263697
264104
  for (const wt of storage.list(worktreesRoot)) {
263698
- sources.push({ dir: join36(worktreesRoot, wt, ".ralph", "tasks"), label: `wt:${wt}` });
264105
+ sources.push({ dir: join38(worktreesRoot, wt, ".ralph", "tasks"), label: `wt:${wt}` });
263699
264106
  }
263700
264107
  for (const { dir, label } of sources) {
263701
264108
  for (const entry of storage.list(dir)) {
263702
264109
  if (seen.has(entry))
263703
264110
  continue;
263704
- const raw = storage.read(join36(dir, entry, ".ralph-state.json"));
264111
+ const raw = storage.read(join38(dir, entry, ".ralph-state.json"));
263705
264112
  if (raw === null)
263706
264113
  continue;
263707
264114
  let state;
@@ -263716,7 +264123,7 @@ function buildLocalRows() {
263716
264123
  const firstLine = promptRaw.split(`
263717
264124
  `).find((l3) => l3.trim() !== "") ?? "";
263718
264125
  let progress = "\u2014";
263719
- const tasksContent = storage.read(join36(dir, entry, "tasks.md"));
264126
+ const tasksContent = storage.read(join38(dir, entry, "tasks.md"));
263720
264127
  if (tasksContent !== null) {
263721
264128
  const { checked, unchecked } = countTaskItems(tasksContent);
263722
264129
  const total = checked + unchecked;
@@ -263779,18 +264186,23 @@ function buildBuckets(indicators) {
263779
264186
  { label: "auto-merge", indicator: indicators.getAutoMerge, exclude: [] }
263780
264187
  ];
263781
264188
  }
263782
- async function fetchBucketIssues(apiKey, bucket, team, assignee, ticketNumbers) {
264189
+ async function fetchBucketIssues(apiKey, bucket, team, assignee, anyAssignee, ticketNumbers) {
263783
264190
  if (!bucket.indicator || bucket.indicator.filter.length === 0)
263784
264191
  return [];
263785
264192
  const spec = {
263786
264193
  team,
263787
264194
  assignee,
264195
+ anyAssignee,
263788
264196
  include: bucket.indicator.filter,
263789
264197
  exclude: bucket.exclude,
263790
264198
  ...ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
263791
264199
  };
263792
264200
  return fetchOpenIssues(apiKey, spec);
263793
264201
  }
264202
+ function resolveLinearFilter(filterOverride, assigneeOverride, configFilter) {
264203
+ const effective = filterOverride || (assigneeOverride ? `assignee = ${assigneeOverride}` : "") || configFilter;
264204
+ return parseLinearFilter(effective);
264205
+ }
263794
264206
  function formatReviewCell(prUrl, count) {
263795
264207
  if (!prUrl)
263796
264208
  return "-";
@@ -263837,13 +264249,13 @@ function backlogRankByIssueId(issues) {
263837
264249
  ordered.forEach((o, i) => rankById.set(o.id, i));
263838
264250
  return rankById;
263839
264251
  }
263840
- async function fetchAndPrintLinear(apiKey, buckets, team, assignee, cwd2, runner, ignoreCiChecks = [], checks3 = false, review = false, ticketNumbers = []) {
264252
+ async function fetchAndPrintLinear(apiKey, buckets, team, assignee, anyAssignee, cwd2, runner, ignoreCiChecks = [], checks3 = false, review = false, ticketNumbers = []) {
263841
264253
  const bucketResults = await Promise.all(buckets.map(async (bucket) => {
263842
264254
  if (!bucket.indicator || bucket.indicator.filter.length === 0) {
263843
264255
  return { bucket, issues: [], error: null };
263844
264256
  }
263845
264257
  try {
263846
- const issues = await fetchBucketIssues(apiKey, bucket, team, assignee, ticketNumbers);
264258
+ const issues = await fetchBucketIssues(apiKey, bucket, team, assignee, anyAssignee, ticketNumbers);
263847
264259
  return { bucket, issues, error: null };
263848
264260
  } catch (err) {
263849
264261
  return {
@@ -263969,6 +264381,7 @@ async function runList(input) {
263969
264381
  identifier: name,
263970
264382
  projectRoot,
263971
264383
  linearTeamOverride: input.linearTeamOverride,
264384
+ linearFilterOverride: input.linearFilterOverride,
263972
264385
  linearAssigneeOverride: input.linearAssigneeOverride
263973
264386
  });
263974
264387
  return;
@@ -263979,7 +264392,7 @@ async function runList(input) {
263979
264392
  const apiKey = process.env["LINEAR_API_KEY"];
263980
264393
  const indicators = cfg.linear.indicators;
263981
264394
  const team = input.linearTeamOverride || cfg.linear.team;
263982
- const assignee = input.linearAssigneeOverride || cfg.linear.assignee;
264395
+ const { assignee, anyAssignee } = resolveLinearFilter(input.linearFilterOverride, input.linearAssigneeOverride, cfg.linear.filter);
263983
264396
  const buckets = buildBuckets(indicators);
263984
264397
  const anyConfigured = buckets.some((b2) => b2.indicator && b2.indicator.filter.length > 0);
263985
264398
  if (!anyConfigured) {
@@ -264014,13 +264427,12 @@ Linear: LINEAR_API_KEY not set \u2014 cannot fetch tickets. Configured buckets:
264014
264427
  process.stdout.write(`
264015
264428
  team: ${team}
264016
264429
  `);
264017
- if (assignee)
264018
- process.stdout.write(`assignee: ${assignee}
264430
+ process.stdout.write(`assignee: ${anyAssignee ? "any" : assignee ?? "*"}
264019
264431
  `);
264020
264432
  if (ticketNumbers.length > 0)
264021
264433
  process.stdout.write(`ticket: ${ticketNumbers.join(", ")}
264022
264434
  `);
264023
- await fetchAndPrintLinear(apiKey, buckets, team, assignee, projectRoot, localCmdRunner, cfg.ignoreCiChecks, input.checks, input.review, ticketNumbers);
264435
+ await fetchAndPrintLinear(apiKey, buckets, team, assignee, anyAssignee, projectRoot, localCmdRunner, cfg.ignoreCiChecks, input.checks, input.review, ticketNumbers);
264024
264436
  }
264025
264437
  function normalizeIdentifier(input) {
264026
264438
  let parsed;
@@ -264077,8 +264489,10 @@ function markerMatches(issue2, marker) {
264077
264489
  }
264078
264490
  return false;
264079
264491
  }
264080
- function assigneeMatches(issue2, assignee) {
264081
- if (!assignee)
264492
+ function assigneeMatches(issue2, assignee, anyAssignee) {
264493
+ if (anyAssignee)
264494
+ return true;
264495
+ if (!assignee || assignee === "unassigned")
264082
264496
  return issue2.assignee === null;
264083
264497
  const a = issue2.assignee;
264084
264498
  if (!a)
@@ -264101,7 +264515,8 @@ async function runListDebug(input) {
264101
264515
  const cfg = await loadRalphyConfig(projectRoot);
264102
264516
  const indicators = cfg.linear.indicators;
264103
264517
  const team = input.linearTeamOverride || cfg.linear.team;
264104
- const assignee = input.linearAssigneeOverride || cfg.linear.assignee;
264518
+ const { assignee, anyAssignee } = resolveLinearFilter(input.linearFilterOverride, input.linearAssigneeOverride, cfg.linear.filter);
264519
+ const assigneeLabel = anyAssignee ? "any" : assignee ?? "*";
264105
264520
  const normalized = normalizeIdentifier(identifier);
264106
264521
  if (!normalized) {
264107
264522
  process.stdout.write(`Error: '${identifier}' does not look like a Linear identifier (expected e.g. DOO-6, or a local change name beginning with one).
@@ -264141,8 +264556,8 @@ Per-bucket diagnostics:
264141
264556
  if (team && issue2.team?.key && issue2.team.key !== team) {
264142
264557
  reasons.push(`team mismatch: issue=${issue2.team.key}, config=${team}`);
264143
264558
  }
264144
- if (!assigneeMatches(issue2, assignee)) {
264145
- reasons.push(`assignee mismatch: issue=${issue2.assignee ? issue2.assignee.email ?? issue2.assignee.id : "unassigned"}, config=${assignee}`);
264559
+ if (!assigneeMatches(issue2, assignee, anyAssignee)) {
264560
+ reasons.push(`assignee mismatch: issue=${issue2.assignee ? issue2.assignee.email ?? issue2.assignee.id : "unassigned"}, config=${assigneeLabel}`);
264146
264561
  }
264147
264562
  const includeMatches = bucket.indicator.filter.some((m2) => markerMatches(issue2, m2));
264148
264563
  if (!includeMatches) {
@@ -264175,6 +264590,7 @@ Per-bucket diagnostics:
264175
264590
  var localCmdRunner;
264176
264591
  var init_list = __esm(() => {
264177
264592
  init_context();
264593
+ init_workflow();
264178
264594
  init_worktree();
264179
264595
  init_config();
264180
264596
  init_linear();
@@ -264202,31 +264618,14 @@ var init_list = __esm(() => {
264202
264618
  };
264203
264619
  });
264204
264620
 
264205
- // apps/agent/src/agent/state/agent-run-state.ts
264206
- import { basename as basename4, join as join37 } from "path";
264207
- import { homedir as homedir6 } from "os";
264208
- import { mkdir as mkdir13, writeFile } from "fs/promises";
264209
- function agentRunStatePath(projectRoot) {
264210
- return join37(homedir6(), ".ralph", basename4(projectRoot), "agent-state.json");
264211
- }
264212
- async function writeAgentRunState(state) {
264213
- const path = agentRunStatePath(state.projectRoot);
264214
- try {
264215
- await mkdir13(join37(homedir6(), ".ralph", basename4(state.projectRoot)), { recursive: true });
264216
- await writeFile(path, JSON.stringify(state, null, 2) + `
264217
- `, "utf-8");
264218
- } catch {}
264219
- }
264220
- var init_agent_run_state = () => {};
264221
-
264222
264621
  // apps/agent/src/agent/json-runner.ts
264223
264622
  var exports_json_runner = {};
264224
264623
  __export(exports_json_runner, {
264225
264624
  runAgentJson: () => runAgentJson
264226
264625
  });
264227
- import { join as join38 } from "path";
264228
- import { mkdir as mkdir14 } from "fs/promises";
264229
- import { homedir as homedir7 } from "os";
264626
+ import { join as join39 } from "path";
264627
+ import { mkdir as mkdir15 } from "fs/promises";
264628
+ import { homedir as homedir8 } from "os";
264230
264629
  function makeEmit(fileSink) {
264231
264630
  return (event) => {
264232
264631
  const payload = { ts: Date.now(), ...event };
@@ -264246,7 +264645,7 @@ async function runAgentJson({
264246
264645
  tasksDir,
264247
264646
  runPreflight: runPreflight2 = runPreflight
264248
264647
  }) {
264249
- await mkdir14(join38(homedir7(), ".ralph"), { recursive: true }).catch(() => {
264648
+ await mkdir15(join39(homedir8(), ".ralph"), { recursive: true }).catch(() => {
264250
264649
  return;
264251
264650
  });
264252
264651
  const fileSink = createJsonLogFileSink(args.jsonLogFile);
@@ -264462,8 +264861,8 @@ var exports_src3 = {};
264462
264861
  __export(exports_src3, {
264463
264862
  main: () => main3
264464
264863
  });
264465
- import { mkdir as mkdir15 } from "fs/promises";
264466
- import { join as join39 } from "path";
264864
+ import { mkdir as mkdir16 } from "fs/promises";
264865
+ import { join as join40 } from "path";
264467
264866
  async function main3(argv) {
264468
264867
  if (argv.includes("--help") || argv.includes("-h")) {
264469
264868
  printAgentHelp();
@@ -264488,6 +264887,7 @@ async function main3(argv) {
264488
264887
  await runWithContext(createDefaultContext({ layout, args }), async () => {
264489
264888
  await runList2({
264490
264889
  linearTeamOverride: args.linearTeam,
264890
+ linearFilterOverride: args.linearFilter,
264491
264891
  linearAssigneeOverride: args.linearAssignee,
264492
264892
  debug: args.debug,
264493
264893
  name: args.name,
@@ -264526,9 +264926,9 @@ async function main3(argv) {
264526
264926
  return 1;
264527
264927
  }
264528
264928
  }
264529
- await mkdir15(statesDir, { recursive: true });
264530
- await mkdir15(tasksDir, { recursive: true });
264531
- await mkdir15(join39(projectRoot, ".ralph"), { recursive: true });
264929
+ await mkdir16(statesDir, { recursive: true });
264930
+ await mkdir16(tasksDir, { recursive: true });
264931
+ await mkdir16(join40(projectRoot, ".ralph"), { recursive: true });
264532
264932
  if (shouldFallbackToJsonOutput(args, process.stdin.isTTY)) {
264533
264933
  process.stderr.write(`agent: stdin is not a TTY \u2014 falling back to --json-output mode.
264534
264934
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "3.10.4",
3
+ "version": "3.10.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",