@neriros/ralphy 3.10.16 → 3.10.17

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 +722 -84
  2. package/package.json +2 -2
@@ -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.16")
18932
- return "3.10.16";
18931
+ if ("3.10.17")
18932
+ return "3.10.17";
18933
18933
  } catch {}
18934
18934
  const dirsToTry = [];
18935
18935
  try {
@@ -19395,7 +19395,7 @@ function modelOptionValues() {
19395
19395
  const field = findField("model");
19396
19396
  return field && field.spec.kind === "select" ? field.spec.options.map((o) => o.value) : [];
19397
19397
  }
19398
- 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_CHOICE_FIELD_ID = "linear.assigneeChoice", LINEAR_ASSIGNEE_VALUE_FIELD_ID = "linear.assigneeValue", LINEAR_FILTER_DESCRIPTION, LINEAR_ASSIGNEE_CHOICE, LINEAR_ASSIGNEE_VALUE, QUICK_FIELDS, isOn = (id) => (answers) => answers[id] === true, concurrencyForcesWorktree = (answers) => {
19398
+ 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_CHOICE_FIELD_ID = "linear.assigneeChoice", LINEAR_ASSIGNEE_VALUE_FIELD_ID = "linear.assigneeValue", LINEAR_FILTER_DESCRIPTION, LINEAR_ASSIGNEE_CHOICE, LINEAR_ASSIGNEE_VALUE, QUICK_FIELDS, isOn = (id) => (answers) => answers[id] === true, trackerIsGithub = (answers) => answers["tracker.kind"] === "github", concurrencyForcesWorktree = (answers) => {
19399
19399
  const value = answers["concurrency"];
19400
19400
  return typeof value === "number" && value > 1;
19401
19401
  }, worktreeEnabled = (answers) => answers["useWorktree"] === true || concurrencyForcesWorktree(answers), HIDDEN_FIELD_IDS, CUSTOMIZED_FIELDS, COMMON_CLI_OPTIONS, FIELD_DESCRIPTIONS;
@@ -19537,6 +19537,66 @@ var init_fields = __esm(() => {
19537
19537
  description: "Show detailed per-task output (passes --verbose to the task sub-process) for extra diagnostics.",
19538
19538
  spec: no()
19539
19539
  },
19540
+ {
19541
+ id: "tracker.kind",
19542
+ label: "Issue tracker",
19543
+ description: "Which issue tracker drives the loop: 'linear' (the default) or 'github' (GitHub Issues, via the gh CLI).",
19544
+ spec: {
19545
+ kind: "select",
19546
+ options: [
19547
+ { label: "Linear", value: "linear" },
19548
+ { label: "GitHub", value: "github" }
19549
+ ]
19550
+ }
19551
+ },
19552
+ {
19553
+ id: "github.issues.repo",
19554
+ label: "GitHub repository (owner/name)",
19555
+ hint: "blank = detected from origin",
19556
+ description: "The owner/name of the GitHub repo whose issues drive the loop. Leave blank to use the repo detected from the git 'origin' remote.",
19557
+ emptyLabel: "detected from origin",
19558
+ spec: { kind: "text", placeholder: "owner/name" },
19559
+ when: trackerIsGithub
19560
+ },
19561
+ {
19562
+ id: "github.issues.label",
19563
+ label: "Todo label",
19564
+ hint: "blank = any open issue",
19565
+ description: "Only pick up GitHub issues carrying this label. Leave blank to consider every open issue.",
19566
+ emptyLabel: "any open issue",
19567
+ spec: { kind: "text", placeholder: "ralph:todo" },
19568
+ when: trackerIsGithub
19569
+ },
19570
+ {
19571
+ id: "github.issues.assignee",
19572
+ label: "Assignee filter",
19573
+ hint: "blank = any assignee; @me = you",
19574
+ description: "Only pick up GitHub issues assigned to this login. Use '@me' for yourself; leave blank to ignore the assignee.",
19575
+ emptyLabel: "any assignee",
19576
+ spec: { kind: "text", placeholder: "@me" },
19577
+ when: trackerIsGithub
19578
+ },
19579
+ {
19580
+ id: "github.issues.statusLabels.inProgress",
19581
+ label: "In-progress label",
19582
+ description: "GitHub label applied to an issue while Ralphy is actively working on it (and removed from the todo label).",
19583
+ spec: { kind: "text", placeholder: "ralph:in-progress" },
19584
+ when: trackerIsGithub
19585
+ },
19586
+ {
19587
+ id: "github.issues.statusLabels.done",
19588
+ label: "Done label",
19589
+ description: "GitHub label applied to an issue when its work completes; the issue is also closed.",
19590
+ spec: { kind: "text", placeholder: "ralph:done" },
19591
+ when: trackerIsGithub
19592
+ },
19593
+ {
19594
+ id: "github.issues.statusLabels.error",
19595
+ label: "Error label",
19596
+ description: "GitHub label applied to an issue when Ralphy quarantines it after repeated failures.",
19597
+ spec: { kind: "text", placeholder: "ralph:error" },
19598
+ when: trackerIsGithub
19599
+ },
19540
19600
  {
19541
19601
  id: "concurrency",
19542
19602
  label: "Concurrency (parallel tasks)",
@@ -81255,7 +81315,7 @@ function foldLegacyAssignee(v) {
81255
81315
  }
81256
81316
  return rest2;
81257
81317
  }
81258
- var CURRENT_WORKFLOW_VERSION = 6, MarkerSchema, FilterMarkerSchema, LinearFilterSchema, SET_INDICATOR_KEYS, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, ProjectSchema, CommandsSchema, DEFAULT_META_ONLY_FILES, BoundariesSchema, WorkflowConfigSchema;
81318
+ var CURRENT_WORKFLOW_VERSION = 7, MarkerSchema, FilterMarkerSchema, LinearFilterSchema, SET_INDICATOR_KEYS, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, ProjectSchema, CommandsSchema, DEFAULT_META_ONLY_FILES, BoundariesSchema, WorkflowConfigSchema;
81259
81319
  var init_schema = __esm(() => {
81260
81320
  init_zod();
81261
81321
  MarkerSchema = exports_external.discriminatedUnion("type", [
@@ -81401,6 +81461,9 @@ var init_schema = __esm(() => {
81401
81461
  finalizeNoOpAsDone: exports_external.boolean().default(true),
81402
81462
  engine: exports_external.enum(["claude", "codex"]).default("claude"),
81403
81463
  model: exports_external.enum(["haiku", "sonnet", "opus"]).default("opus"),
81464
+ tracker: exports_external.object({
81465
+ kind: exports_external.enum(["linear", "github"]).default("linear")
81466
+ }).strict().default({ kind: "linear" }),
81404
81467
  linear: exports_external.preprocess(foldLegacyAssignee, exports_external.object({
81405
81468
  team: exports_external.string().optional(),
81406
81469
  filter: LinearFilterSchema,
@@ -81445,7 +81508,21 @@ var init_schema = __esm(() => {
81445
81508
  }),
81446
81509
  github: exports_external.object({
81447
81510
  base_branch: exports_external.string().optional(),
81448
- auto_merge_strategy: exports_external.enum(["squash", "merge", "rebase"]).optional()
81511
+ auto_merge_strategy: exports_external.enum(["squash", "merge", "rebase"]).optional(),
81512
+ issues: exports_external.object({
81513
+ repo: exports_external.string().optional(),
81514
+ label: exports_external.string().optional(),
81515
+ assignee: exports_external.string().optional(),
81516
+ statusLabels: exports_external.object({
81517
+ inProgress: exports_external.string().default("ralph:in-progress"),
81518
+ done: exports_external.string().default("ralph:done"),
81519
+ error: exports_external.string().default("ralph:error")
81520
+ }).strict().default({
81521
+ inProgress: "ralph:in-progress",
81522
+ done: "ralph:done",
81523
+ error: "ralph:error"
81524
+ })
81525
+ }).strict().optional()
81449
81526
  }).strict().optional(),
81450
81527
  agent: exports_external.object({
81451
81528
  engine: exports_external.enum(["claude", "codex"]).optional(),
@@ -81514,7 +81591,7 @@ var init_schema = __esm(() => {
81514
81591
  var FRONTMATTER_RE, DEFAULT_WORKFLOW_MD = `---
81515
81592
  # WORKFLOW.md schema version \u2014 managed by \`ralphy init\`. When a newer version
81516
81593
  # ships, re-running init migrates this file and fills in the new settings.
81517
- version: 6
81594
+ version: 7
81518
81595
 
81519
81596
  project:
81520
81597
  name: ralphy
@@ -81581,6 +81658,16 @@ preExistingErrorCheck:
81581
81658
  label: "ralph:pre-existing-error"
81582
81659
  outputCharLimit: 4000
81583
81660
 
81661
+ tracker:
81662
+ kind: linear
81663
+
81664
+ github:
81665
+ issues:
81666
+ statusLabels:
81667
+ inProgress: "ralph:in-progress"
81668
+ done: "ralph:done"
81669
+ error: "ralph:error"
81670
+
81584
81671
  linear:
81585
81672
  # Global filter ANDed into every Linear query (and the GitHub PR searches
81586
81673
  # rooted at those issues). A marker list of \`assignee\` and \`label\` clauses;
@@ -83405,6 +83492,12 @@ function buildFromAnswers(mode, answers, build = buildWorkflowMarkdown) {
83405
83492
  }
83406
83493
  }
83407
83494
  }
83495
+ if (values2["tracker.kind"] !== "github") {
83496
+ for (const id of Object.keys(values2)) {
83497
+ if (id.startsWith("github.issues."))
83498
+ delete values2[id];
83499
+ }
83500
+ }
83408
83501
  const linkRepo = values2[REPO_LINK_FIELD_ID] === true;
83409
83502
  delete values2[REPO_LINK_FIELD_ID];
83410
83503
  if (!linkRepo) {
@@ -84585,6 +84678,19 @@ var init_migrations = __esm(() => {
84585
84678
  version: 6,
84586
84679
  description: "PR recovery is unified under one `prRecovery` block (replacing " + "`prTracker`, `fixCiOnFailure`, `maxCiFixAttempts`, `ciPollIntervalSeconds`, " + "and `ignoreCiChecks`). Workers now open the PR and leave the ticket " + "in-review; a single background watcher advances it to done once the PR is " + "mergeable (CI green, no conflicts) and recovers red PRs \u2014 resolving merge " + "conflicts AND fixing failing CI (both `prRecovery.fixConflicts` and " + "`prRecovery.fixCi` default on; tune them in WORKFLOW.md). " + "`prRecovery.enabled: false` turns the watcher off everywhere and marks the " + "ticket done immediately on PR open. Your old values are migrated " + "automatically; review them here or keep them.",
84587
84680
  fields: ["prRecovery.enabled", "prRecovery.maxRecoverySessions", "prRecovery.ignoreChecks"]
84681
+ },
84682
+ {
84683
+ version: 7,
84684
+ description: "Pick your issue tracker: Linear (default) or GitHub Issues. The new " + "`tracker.kind` switch selects the provider; choosing GitHub drives the " + "loop off `gh` issues, filtered by `github.issues.label`/`assignee` and " + "moved through the `github.issues.statusLabels` (in-progress / done / " + "error). Existing files default to Linear, so nothing changes unless you " + "switch.",
84685
+ fields: [
84686
+ "tracker.kind",
84687
+ "github.issues.repo",
84688
+ "github.issues.label",
84689
+ "github.issues.assignee",
84690
+ "github.issues.statusLabels.inProgress",
84691
+ "github.issues.statusLabels.done",
84692
+ "github.issues.statusLabels.error"
84693
+ ]
84588
84694
  }
84589
84695
  ];
84590
84696
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max2, migration) => Math.max(max2, migration.version), 0);
@@ -100164,7 +100270,7 @@ function normalizeNewlyAppendedSectionWithReport(previous, current) {
100164
100270
  });
100165
100271
  return { text: count > 0 ? out.join("") : current, headings, count };
100166
100272
  }
100167
- var MISSION_TASKS_FILENAME = "tasks.md", AGENT_TASKS_FILENAME = "agent-tasks.md", FLOW_TASK_HEADING_PREFIXES;
100273
+ var MISSION_TASKS_FILENAME = "tasks.md", AGENT_TASKS_FILENAME = "agent-tasks.md", HANDOFF_FILENAME = "handoff.md", FLOW_TASK_HEADING_PREFIXES;
100168
100274
  var init_tasks_md = __esm(() => {
100169
100275
  FLOW_TASK_HEADING_PREFIXES = [
100170
100276
  "Fix failing CI checks",
@@ -100417,6 +100523,20 @@ function buildTaskPrompt(state, taskDir, reviewPhase) {
100417
100523
  `;
100418
100524
  }
100419
100525
  }
100526
+ const handoffContent = storage.read(join15(taskDir, HANDOFF_FILENAME));
100527
+ if (handoffContent !== null && handoffContent.trim()) {
100528
+ prompt += `---
100529
+ `;
100530
+ prompt += `# Previous Iteration Handoff (context from the last iteration)
100531
+
100532
+ `;
100533
+ prompt += handoffContent.trim() + `
100534
+
100535
+ `;
100536
+ prompt += `---
100537
+
100538
+ `;
100539
+ }
100420
100540
  const agentTasksPath = join15(taskDir, AGENT_TASKS_FILENAME);
100421
100541
  const missionTasksPath = join15(taskDir, MISSION_TASKS_FILENAME);
100422
100542
  const agentTasksContent = storage.read(agentTasksPath);
@@ -100564,6 +100684,22 @@ When all tasks are complete and all files are committed, push your branch and op
100564
100684
  prompt += `Use the change name as the PR title and write a concise summary of the implementation in the body.
100565
100685
  `;
100566
100686
  }
100687
+ const handoffPath = join15(taskDir, HANDOFF_FILENAME);
100688
+ prompt += `
100689
+ ---
100690
+
100691
+ ## Write Handoff (do this LAST, before you finish)
100692
+ `;
100693
+ prompt += `Before ending this iteration, record a handoff for the next iteration:
100694
+ `;
100695
+ prompt += `- If a \`handoff\` skill is available, invoke it; otherwise write the document yourself.
100696
+ `;
100697
+ prompt += `- Save it to \`${handoffPath}\` (OVERWRITE the existing file \u2014 do NOT append, do NOT save to a temp dir).
100698
+ `;
100699
+ prompt += `- Keep it compact: what you did this iteration, what remains, key decisions, and any blockers/gotchas. Reference artifacts by path instead of duplicating them.
100700
+ `;
100701
+ prompt += `- This file is loop scratch \u2014 do NOT commit it.
100702
+ `;
100567
100703
  return prompt;
100568
100704
  }
100569
100705
  function buildSteeringBlock(taskDir) {
@@ -102600,13 +102736,91 @@ class PollContext {
102600
102736
  }
102601
102737
  }
102602
102738
 
102603
- // apps/agent/src/shared/utils/ralph-comment.ts
102604
- function isRalphComment(body) {
102739
+ // packages/comms/src/index.ts
102740
+ function sanitizeMarkerValue(value) {
102741
+ return value.replace(/--+>?/g, "-").replace(/\s+/g, "_").trim();
102742
+ }
102743
+ function buildRalphyMarker(type, fields) {
102744
+ const pairs = [`v=${RALPHY_MARKER_VERSION}`, `type=${type}`];
102745
+ for (const [key, raw] of Object.entries(fields ?? {})) {
102746
+ if (raw === undefined || raw === null || raw === "")
102747
+ continue;
102748
+ pairs.push(`${key}=${sanitizeMarkerValue(String(raw))}`);
102749
+ }
102750
+ return `<!-- ralphy:${pairs.join(" ")} -->`;
102751
+ }
102752
+ function parseRalphyMarker(body) {
102753
+ const match = /<!--\s*ralphy:([^>]*?\btype=[^>]*?)\s*-->/.exec(body);
102754
+ if (!match)
102755
+ return null;
102756
+ const fields = {};
102757
+ let type = "";
102758
+ let version3 = 0;
102759
+ for (const token of match[1].trim().split(/\s+/)) {
102760
+ const eq = token.indexOf("=");
102761
+ if (eq < 0)
102762
+ continue;
102763
+ const key = token.slice(0, eq);
102764
+ const value = token.slice(eq + 1);
102765
+ if (key === "v")
102766
+ version3 = Number(value) || 0;
102767
+ else if (key === "type")
102768
+ type = value;
102769
+ else
102770
+ fields[key] = value;
102771
+ }
102772
+ if (!type)
102773
+ return null;
102774
+ return { version: version3, type, fields };
102775
+ }
102776
+ function buildRalphyComment(input) {
102777
+ const lines = [`${RALPHY_TITLE_PREFIX}${input.action}`];
102778
+ const body = input.body?.trim();
102779
+ if (body)
102780
+ lines.push("", body);
102781
+ lines.push("", buildRalphyMarker(input.type, input.fields));
102782
+ return lines.join(`
102783
+ `);
102784
+ }
102785
+ function isRalphyComment(body) {
102605
102786
  const trimmed = body.trimStart();
102606
- if (/^(\uD83E\uDD16|\uD83D\uDD04|\u2705|\u2717|\u274C|\u26A0|\uD83D\uDD01|\uD83D\uDCCB|\u23F0)\s*Ralphy?\b/.test(trimmed))
102787
+ if (trimmed.startsWith(RALPHY_BRAND))
102788
+ return true;
102789
+ if (parseRalphyMarker(body))
102790
+ return true;
102791
+ if (LEGACY_RALPH_LEAD.test(trimmed))
102792
+ return true;
102793
+ return LEGACY_MENTION_ACK.test(trimmed);
102794
+ }
102795
+ function isPickupComment(body) {
102796
+ if (parseRalphyMarker(body)?.type === "review-pickup")
102797
+ return true;
102798
+ return /^\uD83D\uDD01\s*Ralph picked up/u.test(body.trimStart());
102799
+ }
102800
+ function isStartedComment(body) {
102801
+ if (parseRalphyMarker(body)?.type === "started")
102802
+ return true;
102803
+ return /^\uD83E\uDD16\s*Ralph started working/u.test(body.trimStart());
102804
+ }
102805
+ function isMentionAckComment(body) {
102806
+ if (parseRalphyMarker(body)?.type === "mention-ack")
102607
102807
  return true;
102608
- return /^\uD83D\uDC40\s*(Got it\b|Acknowledged\b)/.test(trimmed);
102808
+ return LEGACY_MENTION_ACK.test(body.trimStart());
102809
+ }
102810
+ var RALPHY_BRAND = "\uD83E\uDD16 Ralphy", RALPHY_TITLE_PREFIX, RALPHY_MARKER_VERSION = 1, LEGACY_RALPH_LEAD, LEGACY_MENTION_ACK;
102811
+ var init_src8 = __esm(() => {
102812
+ RALPHY_TITLE_PREFIX = `${RALPHY_BRAND} \xB7 `;
102813
+ LEGACY_RALPH_LEAD = /^(\uD83E\uDD16|\uD83D\uDD04|\u2705|\u2717|\u274C|\u26A0\uFE0F?|\uD83D\uDD01|\uD83D\uDCCB|\u23F0|\u2139\uFE0F)\s*Ralphy?\b/u;
102814
+ LEGACY_MENTION_ACK = /^\uD83D\uDC40\s*(Got it\b|Acknowledged\b)/u;
102815
+ });
102816
+
102817
+ // apps/agent/src/shared/utils/ralph-comment.ts
102818
+ function isRalphComment(body) {
102819
+ return isRalphyComment(body);
102609
102820
  }
102821
+ var init_ralph_comment = __esm(() => {
102822
+ init_src8();
102823
+ });
102610
102824
 
102611
102825
  // apps/agent/src/shared/capabilities/linear-client.ts
102612
102826
  var exports_linear_client = {};
@@ -103543,6 +103757,7 @@ async function removeLabelFromIssue(apiKey, issueId, labelId) {
103543
103757
  var LINEAR_API = "https://api.linear.app/graphql", TICKET_IDENTIFIER_RE, TICKET_BARE_NUMBER_RE, RALPHY_ATTACHMENT_TITLE_FILTER = "Ralphy", linearRequestInternals, MAX_LINEAR_ATTEMPTS = 3, MAX_RETRY_AFTER_MS = 2000, BODY_TRUNCATE_CHARS = 512, RALPHY_ATTACHMENT_TITLE = "Ralphy", BRANCH_LABEL_PREFIX = "ralph:branch:";
103544
103758
  var init_linear_client = __esm(() => {
103545
103759
  init_types2();
103760
+ init_ralph_comment();
103546
103761
  TICKET_IDENTIFIER_RE = /^([A-Za-z]+)-(\d+)(?:-.*)?$/;
103547
103762
  TICKET_BARE_NUMBER_RE = /^(\d+)$/;
103548
103763
  linearRequestInternals = {
@@ -103885,7 +104100,7 @@ function stackedOnLine(stackedOn) {
103885
104100
  return `> \uD83E\uDD5E Stacked on ${prRef}${ticket} \u2014 review/merge that PR first. Base is its branch, not the default branch.`;
103886
104101
  }
103887
104102
  function defaultBody(issue2, branch, stackedOn) {
103888
- return [
104103
+ const detail = [
103889
104104
  `Auto-generated by Ralph for ${issue2.identifier}.`,
103890
104105
  stackedOn ? stackedOnLine(stackedOn) : "",
103891
104106
  `Source: ${issue2.url}`,
@@ -103896,6 +104111,12 @@ function defaultBody(issue2, branch, stackedOn) {
103896
104111
  ${issue2.description.trim()}` : ""
103897
104112
  ].filter(Boolean).join(`
103898
104113
  `);
104114
+ return buildRalphyComment({
104115
+ type: "pr-body",
104116
+ action: "opened PR",
104117
+ body: detail,
104118
+ fields: { issue: issue2.identifier }
104119
+ });
103899
104120
  }
103900
104121
  async function diffFilesAgainstBase(runner, cwd2, base2) {
103901
104122
  let raw = "";
@@ -104015,7 +104236,9 @@ async function createPullRequest(input, runner) {
104015
104236
  `).pop() ?? "";
104016
104237
  return { url: url2, created: true };
104017
104238
  }
104018
- var init_pr = () => {};
104239
+ var init_pr = __esm(() => {
104240
+ init_src8();
104241
+ });
104019
104242
 
104020
104243
  // apps/agent/src/shared/pr/ci-classify.ts
104021
104244
  async function runGhWithRetry(cmd, runner, cwd2, onRetry, sleep2 = (ms) => new Promise((r) => setTimeout(r, ms))) {
@@ -105126,6 +105349,25 @@ async function runPostTask(input, deps) {
105126
105349
  }
105127
105350
  if (input.mode === "conflict-fix" && effectiveCode === 0) {
105128
105351
  const identifier = issue2?.identifier ?? changeName;
105352
+ if (branch) {
105353
+ let aheadCount = 0;
105354
+ let checked = true;
105355
+ try {
105356
+ const r = await cmd.run(["git", "rev-list", "--count", `origin/${branch}..HEAD`], cwd2);
105357
+ aheadCount = Number.parseInt(r.stdout.trim(), 10) || 0;
105358
+ } catch (err) {
105359
+ checked = false;
105360
+ log3(`! ${identifier}: could not check for unpushed conflict-fix commits: ${err.message}`, "yellow");
105361
+ }
105362
+ if (checked && aheadCount > 0) {
105363
+ log3(`! ${identifier}: conflict-fix worker left ${aheadCount} unpushed commit(s) ahead of ` + `origin/${branch} \u2014 the resolution never reached the PR. Failing the iteration so it ` + `is retried instead of reported as resolved.`, "red");
105364
+ emit3("gave-up", "unpushed conflict resolution");
105365
+ await recordGaveUp(stateFilePath, log3, changeName);
105366
+ await runWorktreeCleanupPhase({ changeName, cwd: cwd2, projectRoot, useWorktree, effectiveCode: PR_FAILED_EXIT, cfg }, { git: git2, log: log3, emit: emit3 });
105367
+ await runTeardownPhase({ cwd: cwd2, teardownScript: cfg.teardownScript }, { runScript, log: log3, emit: emit3 });
105368
+ return PR_FAILED_EXIT;
105369
+ }
105370
+ }
105129
105371
  let prUrl = input.prUrl ?? null;
105130
105372
  if (!prUrl && branch) {
105131
105373
  prUrl = await findExistingOpenPrUrl(cmd, cwd2, branch);
@@ -105500,19 +105742,37 @@ function emitCapture(bus, event, properties) {
105500
105742
  function completionCommentBody(args) {
105501
105743
  const { noChanges, ok, trigger, changeName, code } = args;
105502
105744
  if (noChanges) {
105503
- return `\u2139\uFE0F Ralph completed all tasks for this issue but produced no code changes \u2014 ` + `the requested work appears to already be present on the base branch (or was a ` + `no-op). No PR was opened. Change: \`${changeName}\`
105745
+ return buildRalphyComment({
105746
+ type: "completed-noop",
105747
+ action: "completed \u2014 no code changes",
105748
+ body: `Completed all tasks for this issue but produced no code changes \u2014 the requested ` + `work appears to already be present on the base branch (or was a no-op). No PR was ` + `opened. Change: \`${changeName}\`
105504
105749
 
105505
- ` + `Marking this done; please verify the work is genuinely in place. If it is not, ` + `reopen the issue with more specifics.`;
105750
+ ` + `Marking this done; please verify the work is genuinely in place. If it is not, ` + `reopen the issue with more specifics.`,
105751
+ fields: { change: changeName }
105752
+ });
105506
105753
  }
105507
105754
  if (!ok) {
105508
- return `\u2717 Ralph exited with code ${code} on this issue. Change: \`${changeName}\`
105509
-
105510
- ` + `This issue has been quarantined and will not be auto-resumed on the next poll. ` + `Inspect the worktree at \`~/.ralph/<project>/worktrees/${changeName}\`, fix the ` + `underlying failure, then remove the error marker on this Linear issue (or run ` + `\`ralph clean --name ${changeName}\`) to clear the quarantine.`;
105755
+ return buildRalphyComment({
105756
+ type: "exited",
105757
+ action: `exited with code ${code}`,
105758
+ body: `This issue has been quarantined and will not be auto-resumed on the next poll. ` + `Inspect the worktree at \`~/.ralph/<project>/worktrees/${changeName}\`, fix the ` + `underlying failure, then remove the error marker on this Linear issue (or run ` + `\`ralph clean --name ${changeName}\`) to clear the quarantine. Change: \`${changeName}\``,
105759
+ fields: { change: changeName, code }
105760
+ });
105511
105761
  }
105512
105762
  if (trigger === "conflict-fix") {
105513
- return `\u2705 Ralph resolved merge conflicts on this issue. Change: \`${changeName}\``;
105763
+ return buildRalphyComment({
105764
+ type: "conflicts-resolved",
105765
+ action: "resolved merge conflicts",
105766
+ body: `Change: \`${changeName}\``,
105767
+ fields: { change: changeName }
105768
+ });
105514
105769
  }
105515
- return `\u2705 Ralph completed work on this issue. Change: \`${changeName}\``;
105770
+ return buildRalphyComment({
105771
+ type: "completed",
105772
+ action: "completed work",
105773
+ body: `Change: \`${changeName}\``,
105774
+ fields: { change: changeName }
105775
+ });
105516
105776
  }
105517
105777
  function extractPrNumber(url2) {
105518
105778
  const m = /\/pull\/(\d+)(?:[/?#]|$)/.exec(url2);
@@ -105811,7 +106071,12 @@ class AgentCoordinator {
105811
106071
  const prNum = extractPrNumber(pr.url);
105812
106072
  const ref = prNum !== null ? `PR #${prNum}` : `PR ${pr.url}`;
105813
106073
  try {
105814
- await this.deps.postComment(issue2, `\u26A0\uFE0F ${ref} is ${stateLabel} \u2014 promoted to ${trigger} flow.`);
106074
+ await this.deps.postComment(issue2, buildRalphyComment({
106075
+ type: "promoted",
106076
+ action: `promoted to ${trigger} flow`,
106077
+ body: `${ref} is ${stateLabel} \u2014 promoted to ${trigger} flow.`,
106078
+ fields: { trigger, pr: extractPrNumber(pr.url) ?? pr.url }
106079
+ }));
105815
106080
  this.deps.onLog(` ${issue2.identifier}: posted ${trigger}-promotion comment`, "gray");
105816
106081
  } catch (err) {
105817
106082
  this.deps.onLog(`! Linear ${trigger}-promotion comment failed for ${issue2.identifier}: ${err.message}`, "yellow");
@@ -105864,7 +106129,12 @@ class AgentCoordinator {
105864
106129
  if (currMilestone <= lastMilestone)
105865
106130
  continue;
105866
106131
  try {
105867
- await this.deps.postComment(w.issue, `\uD83D\uDD04 Ralph progress update: iteration ${count} on \`${w.changeName}\``);
106132
+ await this.deps.postComment(w.issue, buildRalphyComment({
106133
+ type: "progress",
106134
+ action: `progress update \u2014 iteration ${count}`,
106135
+ body: `Iteration ${count} on \`${w.changeName}\``,
106136
+ fields: { change: w.changeName, iter: count }
106137
+ }));
105868
106138
  w.lastReportedIteration = count;
105869
106139
  this.deps.onLog(` ${w.issueIdentifier}: posted progress comment (iteration ${count})`, "gray");
105870
106140
  } catch (err) {
@@ -106004,7 +106274,12 @@ class AgentCoordinator {
106004
106274
  this.deps.onLog(` ${issue2.identifier}: PR ${pr.url} conflicting \u2014 queued (conflict-fix)`, "yellow");
106005
106275
  if (this.opts.postComments !== false) {
106006
106276
  try {
106007
- await this.deps.postComment(issue2, `\u26A0 Ralph detected merge conflicts on this PR (${pr.url}) \u2014 re-running to resolve`);
106277
+ await this.deps.postComment(issue2, buildRalphyComment({
106278
+ type: "conflict-detected",
106279
+ action: "detected merge conflicts",
106280
+ body: `Detected merge conflicts on this PR (${pr.url}) \u2014 re-running to resolve.`,
106281
+ fields: { pr: extractPrNumber(pr.url) ?? pr.url }
106282
+ }));
106008
106283
  } catch (err) {
106009
106284
  this.deps.onLog(`! Linear conflict comment failed for ${issue2.identifier}: ${err.message}`, "yellow");
106010
106285
  }
@@ -106039,7 +106314,12 @@ class AgentCoordinator {
106039
106314
  this.deps.onLog(` ${issue2.identifier}: PR ${pr.url} CI failing \u2014 queued (ci-fix)`, "yellow");
106040
106315
  if (this.opts.postComments !== false) {
106041
106316
  try {
106042
- await this.deps.postComment(issue2, `\u26A0 Ralph detected failing CI on this PR (${pr.url}) \u2014 re-running to fix`);
106317
+ await this.deps.postComment(issue2, buildRalphyComment({
106318
+ type: "ci-failed",
106319
+ action: "detected failing CI",
106320
+ body: `Detected failing CI on this PR (${pr.url}) \u2014 re-running to fix.`,
106321
+ fields: { pr: extractPrNumber(pr.url) ?? pr.url }
106322
+ }));
106043
106323
  } catch (err) {
106044
106324
  this.deps.onLog(`! Linear ci-failed comment failed for ${issue2.identifier}: ${err.message}`, "yellow");
106045
106325
  }
@@ -106105,7 +106385,12 @@ class AgentCoordinator {
106105
106385
  }
106106
106386
  if (this.opts.postComments !== false) {
106107
106387
  try {
106108
- await this.deps.postComment(issue2, `\u2705 Ralph verified this PR (${prUrl}) is mergeable (CI green, no conflicts) \u2014 moving to done`);
106388
+ await this.deps.postComment(issue2, buildRalphyComment({
106389
+ type: "verified",
106390
+ action: "verified PR mergeable",
106391
+ body: `Verified this PR (${prUrl}) is mergeable (CI green, no conflicts) \u2014 moving to done.`,
106392
+ fields: { pr: extractPrNumber(prUrl) ?? prUrl }
106393
+ }));
106109
106394
  } catch (err) {
106110
106395
  this.deps.onLog(`! Linear done comment failed for ${issue2.identifier}: ${err.message}`, "yellow");
106111
106396
  }
@@ -106151,7 +106436,12 @@ class AgentCoordinator {
106151
106436
  if (this.opts.postComments !== false) {
106152
106437
  const human = reason === "conflicting" ? "merge conflicts" : "failing CI";
106153
106438
  try {
106154
- await this.deps.postComment(issue2, `\u274C Ralph gave up auto-recovering this PR (${prUrl}) after ${decision.attempts} attempts \u2014 last failure: ${human}. The \`ralph:error\` label has been applied; clear it (or merge the PR) once a human has looked at it.`);
106439
+ await this.deps.postComment(issue2, buildRalphyComment({
106440
+ type: "recovery-gaveup",
106441
+ action: "gave up auto-recovering PR",
106442
+ body: `Gave up auto-recovering this PR (${prUrl}) after ${decision.attempts} attempts \u2014 last failure: ${human}. The \`ralph:error\` label has been applied; clear it (or merge the PR) once a human has looked at it.`,
106443
+ fields: { pr: extractPrNumber(prUrl) ?? prUrl, attempts: decision.attempts }
106444
+ }));
106155
106445
  } catch (err) {
106156
106446
  this.deps.onLog(`! Linear bail comment failed for ${issue2.identifier}: ${err.message}`, "yellow");
106157
106447
  }
@@ -106238,7 +106528,12 @@ class AgentCoordinator {
106238
106528
  if (trigger === "review" && this.opts.postComments !== false) {
106239
106529
  const sourceTag = mention ? mention.source === "github" ? " (GitHub @mention)" : mention.source === "github-review" ? " (GitHub code review)" : " (Linear @mention)" : "";
106240
106530
  try {
106241
- await this.deps.postComment(issue2, `\uD83D\uDD01 Ralph picked up new review comments${sourceTag}. Tracking change: \`${prep.changeName}\``);
106531
+ await this.deps.postComment(issue2, buildRalphyComment({
106532
+ type: "review-pickup",
106533
+ action: "picked up review comments",
106534
+ body: `Picked up new review comments${sourceTag}. Tracking change: \`${prep.changeName}\``,
106535
+ fields: { change: prep.changeName }
106536
+ }));
106242
106537
  } catch (err) {
106243
106538
  this.deps.onLog(`! Linear review comment failed for ${issue2.identifier}: ${err.message}`, "yellow");
106244
106539
  }
@@ -106247,13 +106542,18 @@ class AgentCoordinator {
106247
106542
  let alreadyPosted = false;
106248
106543
  try {
106249
106544
  const comments = await this.deps.fetchComments(issue2.id);
106250
- alreadyPosted = comments.some((c) => c.body.startsWith("\uD83E\uDD16 Ralph started working"));
106545
+ alreadyPosted = comments.some((c) => isStartedComment(c.body));
106251
106546
  } catch (err) {
106252
106547
  this.deps.onLog(`! Linear comment fetch failed for ${issue2.identifier}: ${err.message}`, "yellow");
106253
106548
  }
106254
106549
  if (!alreadyPosted) {
106255
106550
  try {
106256
- await this.deps.postComment(issue2, `\uD83E\uDD16 Ralph started working on this issue. Tracking change: \`${prep.changeName}\``);
106551
+ await this.deps.postComment(issue2, buildRalphyComment({
106552
+ type: "started",
106553
+ action: "started working",
106554
+ body: `Tracking change: \`${prep.changeName}\``,
106555
+ fields: { change: prep.changeName }
106556
+ }));
106257
106557
  this.deps.onLog(` ${issue2.identifier}: posted "started" comment`, "gray");
106258
106558
  } catch (err) {
106259
106559
  this.deps.onLog(`! Linear comment failed for ${issue2.identifier}: ${err.message}`, "red");
@@ -106568,6 +106868,7 @@ var init_coordinator = __esm(() => {
106568
106868
  init_registry();
106569
106869
  init_run_feature();
106570
106870
  init_machines();
106871
+ init_src8();
106571
106872
  });
106572
106873
 
106573
106874
  // apps/agent/src/agent/coordinator.ts
@@ -106575,11 +106876,38 @@ var init_coordinator2 = __esm(() => {
106575
106876
  init_coordinator();
106576
106877
  });
106577
106878
 
106879
+ // apps/agent/src/shared/capabilities/gh-client.ts
106880
+ var init_gh_client = () => {};
106881
+
106882
+ // apps/agent/src/shared/capabilities/github/github-client.ts
106883
+ var STARTED_LABEL_NAMES, ISSUE_FIELDS = "id,number,title,body,state,stateReason,labels,assignees,author,createdAt,url", ISSUE_FIELDS_WITH_COMMENTS;
106884
+ var init_github_client = __esm(() => {
106885
+ init_worktree();
106886
+ init_gh_client();
106887
+ STARTED_LABEL_NAMES = new Set(["in progress", "in-progress", "started"]);
106888
+ ISSUE_FIELDS_WITH_COMMENTS = `${ISSUE_FIELDS},comments`;
106889
+ });
106890
+
106891
+ // apps/agent/src/shared/capabilities/github/identifier-strategy.ts
106892
+ function linearChangeName(issue2) {
106893
+ const slug = issue2.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40).replace(/^-+|-+$/g, "");
106894
+ return slug ? `${issue2.identifier.toLowerCase()}-${slug}` : issue2.identifier.toLowerCase();
106895
+ }
106896
+ var linearIdentifierStrategy;
106897
+ var init_identifier_strategy = __esm(() => {
106898
+ init_worktree();
106899
+ init_github_client();
106900
+ linearIdentifierStrategy = {
106901
+ scopeKey: (issue2) => issue2.identifier.split("-")[0],
106902
+ changeName: linearChangeName,
106903
+ branchName: (issue2) => branchForChange(linearChangeName(issue2))
106904
+ };
106905
+ });
106906
+
106578
106907
  // apps/agent/src/agent/scaffold.ts
106579
106908
  import { join as join24 } from "path";
106580
106909
  function changeNameForIssue(issue2) {
106581
- const slug = issue2.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40).replace(/^-+|-+$/g, "");
106582
- return slug ? `${issue2.identifier.toLowerCase()}-${slug}` : issue2.identifier.toLowerCase();
106910
+ return linearIdentifierStrategy.changeName(issue2);
106583
106911
  }
106584
106912
  async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = [], appendPrompt = "", attachments = []) {
106585
106913
  const name = changeNameForIssue(issue2);
@@ -106661,6 +106989,7 @@ async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = []
106661
106989
  }
106662
106990
  var init_scaffold = __esm(() => {
106663
106991
  init_fs_change();
106992
+ init_identifier_strategy();
106664
106993
  });
106665
106994
 
106666
106995
  // packages/core/src/detections/tasks.ts
@@ -106704,19 +107033,21 @@ function gateActive(inputs) {
106704
107033
  }
106705
107034
 
106706
107035
  // packages/core/src/detections/mention.ts
106707
- function buildMentionAckComment(body, author) {
106708
- const firstLine = body.split(`
106709
- `)[0];
106710
- const truncated = firstLine.slice(0, 200);
106711
- const excerpt = truncated + (truncated.length < firstLine.length ? "\u2026" : "");
106712
- const greeting = author ? `\uD83D\uDC40 Got it, ${author}! I've picked up your mention and queued a review pass.` : `\uD83D\uDC40 Acknowledged! I've picked up your mention and queued a review pass.`;
106713
- return `${greeting}
106714
-
106715
- > ${excerpt}`;
107036
+ function buildMentionAckComment(_body, author) {
107037
+ const greeting = author ? `Got it, ${author} \u2014 picked up your mention and queued a review pass.` : `Acknowledged \u2014 picked up your mention and queued a review pass.`;
107038
+ return buildRalphyComment({
107039
+ type: "mention-ack",
107040
+ action: "picked up your mention",
107041
+ body: greeting
107042
+ });
106716
107043
  }
107044
+ var init_mention2 = __esm(() => {
107045
+ init_src8();
107046
+ });
106717
107047
  // packages/core/src/detections/index.ts
106718
107048
  var init_detections = __esm(() => {
106719
107049
  init_phase2();
107050
+ init_mention2();
106720
107051
  });
106721
107052
 
106722
107053
  // apps/agent/src/features/confirmation/state.ts
@@ -106839,7 +107170,17 @@ function buildMentionTaskBody(trigger, issueUrl) {
106839
107170
  function findLastRalphPickupISO(comments) {
106840
107171
  let latest = null;
106841
107172
  for (const c of comments) {
106842
- if (!/^\uD83D\uDD01\s*Ralph picked up/.test(c.body.trimStart()))
107173
+ if (!isPickupComment(c.body))
107174
+ continue;
107175
+ if (latest === null || c.createdAt > latest)
107176
+ latest = c.createdAt;
107177
+ }
107178
+ return latest;
107179
+ }
107180
+ function findLastMentionAckISO(comments) {
107181
+ let latest = null;
107182
+ for (const c of comments) {
107183
+ if (!isMentionAckComment(c.body))
106843
107184
  continue;
106844
107185
  if (latest === null || c.createdAt > latest)
106845
107186
  latest = c.createdAt;
@@ -106875,7 +107216,10 @@ function githubReactionSlug(emoji3) {
106875
107216
  return emoji3;
106876
107217
  }
106877
107218
  }
106878
- var init_task_bodies = () => {};
107219
+ var init_task_bodies = __esm(() => {
107220
+ init_src8();
107221
+ init_ralph_comment();
107222
+ });
106879
107223
 
106880
107224
  // apps/agent/src/features/confirmation/inspect.ts
106881
107225
  function buildReviseRegex(handle) {
@@ -106902,7 +107246,12 @@ async function inspectAwaitingTicket(state, cfg, deps) {
106902
107246
  if (!next.stuckPostedAt) {
106903
107247
  if (cfg.postComments) {
106904
107248
  try {
106905
- await deps.postComment(`\u26A0 Ralphy: confirmation gate stuck after ${next.rounds} revise round(s) ` + `(max ${cfg.maxConfirmationRounds}). Applying \`ralph:stuck\` \u2014 ` + `clear the label to retry, or apply the approval marker to proceed.`);
107249
+ await deps.postComment(buildRalphyComment({
107250
+ type: "confirmation-stuck",
107251
+ action: "confirmation gate stuck",
107252
+ body: `Confirmation gate stuck after ${next.rounds} revise round(s) ` + `(max ${cfg.maxConfirmationRounds}). Applying \`ralph:stuck\` \u2014 ` + `clear the label to retry, or apply the approval marker to proceed.`,
107253
+ fields: { rounds: next.rounds }
107254
+ }));
106906
107255
  } catch (err) {
106907
107256
  deps.log(`! plan-stuck comment failed: ${err.message}`, "yellow");
106908
107257
  }
@@ -106947,7 +107296,12 @@ ${revise.reason}`);
106947
107296
  }
106948
107297
  if (cfg.postComments) {
106949
107298
  try {
106950
- await deps.postComment(`\uD83D\uDD01 Ralphy: revise request acknowledged \u2014 restarting at design (round ${next.rounds + 1}/${cfg.maxConfirmationRounds}).`);
107299
+ await deps.postComment(buildRalphyComment({
107300
+ type: "revise-ack",
107301
+ action: "revise request acknowledged",
107302
+ body: `Revise request acknowledged \u2014 restarting at design (round ${next.rounds + 1}/${cfg.maxConfirmationRounds}).`,
107303
+ fields: { round: next.rounds + 1 }
107304
+ }));
106951
107305
  } catch (err) {
106952
107306
  deps.log(`! revise ack comment failed: ${err.message}`, "yellow");
106953
107307
  }
@@ -106976,7 +107330,12 @@ ${revise.reason}`);
106976
107330
  const limitMs = cfg.timeoutHours * 60 * 60 * 1000;
106977
107331
  if (elapsedMs >= limitMs && cfg.postComments) {
106978
107332
  try {
106979
- await deps.postComment(`\u23F0 Ralphy: still awaiting confirmation on this plan (round ${next.rounds + 1}/${cfg.maxConfirmationRounds}). ` + `Approve to continue or reply \`${cfg.mentionHandle} revise: <reason>\` to send it back.`);
107333
+ await deps.postComment(buildRalphyComment({
107334
+ type: "confirmation-reminder",
107335
+ action: "awaiting confirmation",
107336
+ body: `Still awaiting confirmation on this plan (round ${next.rounds + 1}/${cfg.maxConfirmationRounds}). ` + `Approve to continue or reply \`${cfg.mentionHandle} revise: <reason>\` to send it back.`,
107337
+ fields: { round: next.rounds + 1 }
107338
+ }));
106980
107339
  next.lastReminderAt = nowIso;
106981
107340
  } catch (err) {
106982
107341
  deps.log(`! reminder comment failed: ${err.message}`, "yellow");
@@ -106986,6 +107345,7 @@ ${revise.reason}`);
106986
107345
  return { outcome: "stay-awaiting", next };
106987
107346
  }
106988
107347
  var init_inspect = __esm(() => {
107348
+ init_src8();
106989
107349
  init_task_bodies();
106990
107350
  });
106991
107351
 
@@ -107028,7 +107388,12 @@ async function postPlanReadyCommentOnce(issue2, statePath, changeName, deps) {
107028
107388
  return;
107029
107389
  const approvalSentence = describeApprovalMarker(deps.cfg.linear.indicators.getApproved);
107030
107390
  const handle = deps.cfg.linear.mentionHandle;
107031
- const body = `\uD83D\uDCCB Ralphy plan ready for \`${changeName}\` \u2014 review proposal.md / design.md / tasks.md ` + `and ${approvalSentence} to continue, ` + `or reply with \`${handle} revise: <reason>\` to send it back to design.`;
107391
+ const body = buildRalphyComment({
107392
+ type: "plan-ready",
107393
+ action: "plan ready",
107394
+ body: `Plan ready for \`${changeName}\` \u2014 review proposal.md / design.md / tasks.md ` + `and ${approvalSentence} to continue, ` + `or reply with \`${handle} revise: <reason>\` to send it back to design.`,
107395
+ fields: { change: changeName }
107396
+ });
107032
107397
  try {
107033
107398
  await addIssueComment(deps.apiKey, issue2.id, body);
107034
107399
  } catch (err) {
@@ -107307,11 +107672,13 @@ async function processAwaitingForIssue(issue2, deps) {
107307
107672
  }
107308
107673
  var init_awaiting = __esm(() => {
107309
107674
  init_layout();
107675
+ init_src8();
107310
107676
  init_detections();
107311
107677
  init_phase();
107312
107678
  init_worktree();
107313
107679
  init_scaffold();
107314
107680
  init_linear();
107681
+ init_ralph_comment();
107315
107682
  init_types2();
107316
107683
  init_workflow();
107317
107684
  init_state2();
@@ -107538,10 +107905,13 @@ function unionMarkers(...sets) {
107538
107905
  }
107539
107906
  return out;
107540
107907
  }
107541
- function describeIndicators(indicators, team, assignee, anyAssignee) {
107908
+ function describeIndicators(indicators, team, assignee, anyAssignee, requireAllLabels) {
107542
107909
  const parts = [];
107543
107910
  parts.push(`team=${team ?? "*"}`);
107544
107911
  parts.push(`assignee=${anyAssignee ? "any" : assignee ?? "*"}`);
107912
+ if (requireAllLabels && requireAllLabels.length > 0) {
107913
+ parts.push(`labels=[${requireAllLabels.join(",")}]`);
107914
+ }
107545
107915
  if (indicators.getTodo) {
107546
107916
  parts.push(`todo=[${indicators.getTodo.filter.map((m) => `${m.type}:${m.value}`).join(",")}]`);
107547
107917
  }
@@ -107561,7 +107931,7 @@ function createLinearResolvers(input) {
107561
107931
  const stateCache = new Map;
107562
107932
  const labelCache = new Map;
107563
107933
  const teamIdCache = new Map;
107564
- const teamKeyOf = (issue2) => issue2.identifier.split("-")[0];
107934
+ const teamKeyOf = (issue2) => linearIdentifierStrategy.scopeKey(issue2);
107565
107935
  async function resolveStateId(issue2, name) {
107566
107936
  const t = teamKeyOf(issue2);
107567
107937
  let map3 = stateCache.get(t);
@@ -107757,6 +108127,201 @@ async function fetchDoneCandidatesWith(apiKey, team, assignee, anyAssignee, requ
107757
108127
  var init_linear_resolvers = __esm(() => {
107758
108128
  init_types2();
107759
108129
  init_linear();
108130
+ init_identifier_strategy();
108131
+ });
108132
+
108133
+ // apps/agent/src/agent/wire/tracker/github.ts
108134
+ function identifierForNumber(n) {
108135
+ return `issue-${n}`;
108136
+ }
108137
+ function toLinearIssue(gh) {
108138
+ const open = (gh.state ?? "OPEN").toUpperCase() === "OPEN";
108139
+ return {
108140
+ id: String(gh.number),
108141
+ identifier: identifierForNumber(gh.number),
108142
+ title: gh.title,
108143
+ description: gh.body ?? null,
108144
+ url: gh.url,
108145
+ state: { name: open ? "Open" : "Closed", type: open ? "started" : "completed" },
108146
+ assignee: null,
108147
+ project: null,
108148
+ labels: (gh.labels ?? []).map((l) => l.name),
108149
+ priority: 0,
108150
+ createdAt: gh.createdAt ?? "",
108151
+ blockedByIds: []
108152
+ };
108153
+ }
108154
+ function labelValues(markers) {
108155
+ return markers.filter((m) => m.type === "label").map((m) => m.value);
108156
+ }
108157
+ function createGithubTrackerProvider(input) {
108158
+ const { cmdRunner, projectRoot, diag } = input;
108159
+ const statusLabels = input.issues?.statusLabels ?? DEFAULT_STATUS_LABELS;
108160
+ const todoLabel = input.issues?.label;
108161
+ const assignee = input.issues?.assignee;
108162
+ const configuredRepo = input.issues?.repo;
108163
+ let repoPromise = null;
108164
+ async function repo() {
108165
+ if (configuredRepo && configuredRepo.trim() !== "")
108166
+ return configuredRepo.trim();
108167
+ if (!repoPromise) {
108168
+ repoPromise = (async () => {
108169
+ try {
108170
+ const { stdout } = await cmdRunner.run(["gh", "repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"], projectRoot);
108171
+ const detected = stdout.trim();
108172
+ if (!detected)
108173
+ throw new Error("empty");
108174
+ diag("github-tracker", ` using detected GitHub repo ${detected}`, "gray");
108175
+ return detected;
108176
+ } catch (err) {
108177
+ throw new Error("github tracker: could not determine the repository \u2014 set " + "`github.issues.repo` (owner/name) in WORKFLOW.md or run inside a " + `repo with a GitHub 'origin' remote (${err.message})`);
108178
+ }
108179
+ })();
108180
+ }
108181
+ return repoPromise;
108182
+ }
108183
+ async function listIssues(args) {
108184
+ const r = await repo();
108185
+ const { stdout } = await cmdRunner.run([
108186
+ "gh",
108187
+ "issue",
108188
+ "list",
108189
+ "--repo",
108190
+ r,
108191
+ "--state",
108192
+ "open",
108193
+ "--json",
108194
+ "number,title,url,body,state,createdAt,labels",
108195
+ "--limit",
108196
+ "100",
108197
+ ...args
108198
+ ], projectRoot);
108199
+ const parsed = JSON.parse(stdout.trim() || "[]");
108200
+ return parsed.map(toLinearIssue);
108201
+ }
108202
+ async function fetchByGet(inc, excl) {
108203
+ if (!inc)
108204
+ return [];
108205
+ const include = !Array.isArray(inc) && "filter" in inc ? inc.filter : [];
108206
+ const wantLabels = labelValues(include);
108207
+ const args = [];
108208
+ for (const label of wantLabels)
108209
+ args.push("--label", label);
108210
+ if (assignee && assignee.trim() !== "")
108211
+ args.push("--assignee", assignee.trim());
108212
+ const issues = await listIssues(args);
108213
+ const excludeLabels = new Set(labelValues(excl));
108214
+ if (excludeLabels.size === 0)
108215
+ return issues;
108216
+ return issues.filter((issue2) => !issue2.labels.some((l) => excludeLabels.has(l)));
108217
+ }
108218
+ async function ghIssue(issueNumber, ...args) {
108219
+ const r = await repo();
108220
+ await cmdRunner.run(["gh", "issue", ...args, issueNumber, "--repo", r], projectRoot);
108221
+ }
108222
+ async function applyMarker(issue2, m) {
108223
+ if (m.type === "comment") {
108224
+ await ghIssue(issue2.id, "comment", "--body", m.value);
108225
+ diag("github-marker", ` \u2192 ${issue2.identifier} comment`, "gray");
108226
+ return;
108227
+ }
108228
+ if (m.type !== "label") {
108229
+ diag("github-marker", `! ${issue2.identifier}: '${m.type}' markers are not supported by the GitHub tracker \u2014 skipped`, "yellow");
108230
+ return;
108231
+ }
108232
+ await ghIssue(issue2.id, "edit", "--add-label", m.value);
108233
+ diag("github-marker", ` \u2192 ${issue2.identifier} +label='${m.value}'`, "gray");
108234
+ if (m.value === statusLabels.inProgress && todoLabel && todoLabel.trim() !== "") {
108235
+ await ghIssue(issue2.id, "edit", "--remove-label", todoLabel.trim());
108236
+ diag("github-marker", ` \u2192 ${issue2.identifier} -label='${todoLabel.trim()}'`, "gray");
108237
+ }
108238
+ if (m.value === statusLabels.done) {
108239
+ await ghIssue(issue2.id, "close");
108240
+ diag("github-marker", ` \u2192 ${issue2.identifier} closed`, "gray");
108241
+ }
108242
+ }
108243
+ async function applyIndicator(issue2, ind) {
108244
+ for (const m of markersOf(ind))
108245
+ await applyMarker(issue2, m);
108246
+ }
108247
+ async function removeIndicator(issue2, ind) {
108248
+ for (const m of markersOf(ind)) {
108249
+ if (m.type !== "label")
108250
+ continue;
108251
+ await ghIssue(issue2.id, "edit", "--remove-label", m.value);
108252
+ diag("github-marker", ` \u2192 ${issue2.identifier} -label='${m.value}'`, "gray");
108253
+ }
108254
+ }
108255
+ async function fetchDoneCandidates() {
108256
+ return listIssues(["--label", statusLabels.inProgress]);
108257
+ }
108258
+ async function resolveLabelIdForTeam() {
108259
+ return null;
108260
+ }
108261
+ return {
108262
+ fetchByGet,
108263
+ applyIndicator,
108264
+ removeIndicator,
108265
+ applyMarker,
108266
+ fetchDoneCandidates,
108267
+ resolveLabelIdForTeam
108268
+ };
108269
+ }
108270
+ function githubIndicators(issues) {
108271
+ const statusLabels = issues?.statusLabels ?? DEFAULT_STATUS_LABELS;
108272
+ const todoLabel = issues?.label?.trim();
108273
+ return {
108274
+ getTodo: { filter: todoLabel ? [{ type: "label", value: todoLabel }] : [] },
108275
+ getInProgress: { filter: [{ type: "label", value: statusLabels.inProgress }] },
108276
+ setInProgress: { type: "label", value: statusLabels.inProgress },
108277
+ setDone: { type: "label", value: statusLabels.done },
108278
+ setError: { type: "label", value: statusLabels.error }
108279
+ };
108280
+ }
108281
+ var DEFAULT_STATUS_LABELS;
108282
+ var init_github = __esm(() => {
108283
+ init_types2();
108284
+ DEFAULT_STATUS_LABELS = {
108285
+ inProgress: "ralph:in-progress",
108286
+ done: "ralph:done",
108287
+ error: "ralph:error"
108288
+ };
108289
+ });
108290
+
108291
+ // apps/agent/src/agent/wire/tracker/linear-tracker-provider.ts
108292
+ function createLinearTrackerProvider(input) {
108293
+ const {
108294
+ apiKey,
108295
+ team,
108296
+ assignee,
108297
+ anyAssignee,
108298
+ requireAllLabels,
108299
+ indicators,
108300
+ resolvers,
108301
+ fetchMentions,
108302
+ ticketNumbers
108303
+ } = input;
108304
+ const excludeFromTodo = unionMarkers(indicators.setDone, indicators.setError);
108305
+ const excludeFromInProgress = unionMarkers(indicators.setError);
108306
+ return {
108307
+ fetchTodo: () => resolvers.fetchByGet(indicators.getTodo, excludeFromTodo),
108308
+ fetchInProgress: () => resolvers.fetchByGet(indicators.getInProgress, excludeFromInProgress),
108309
+ fetchReview: () => resolvers.fetchByGet(indicators.getReview, []),
108310
+ fetchMentions,
108311
+ fetchDoneCandidates: () => fetchDoneCandidatesWith(apiKey, team, assignee, anyAssignee, requireAllLabels, indicators, ticketNumbers && ticketNumbers.length > 0 ? ticketNumbers : undefined),
108312
+ applyIndicator: resolvers.applyIndicator,
108313
+ removeIndicator: resolvers.removeIndicator,
108314
+ postComment: (issue2, body) => addIssueComment(apiKey, issue2.id, body),
108315
+ fetchComments: async (issueId) => {
108316
+ const c = await fetchIssueComments(apiKey, issueId);
108317
+ return c.map((x) => ({ body: x.body }));
108318
+ }
108319
+ };
108320
+ }
108321
+ var init_linear_tracker_provider = __esm(() => {
108322
+ init_linear();
108323
+ init_indicators();
108324
+ init_linear_resolvers();
107760
108325
  });
107761
108326
 
107762
108327
  // apps/agent/src/agent/wire/prepare.ts
@@ -108371,7 +108936,12 @@ async function maybePingStaleReviewer(issue2, prUrl, state, newestReviewerActivi
108371
108936
  if (!m)
108372
108937
  return;
108373
108938
  const [, owner, repo, num] = m;
108374
- const body = `\uD83D\uDD14 @${reviewer} \u2014 Ralph has been waiting ${elapsedH.toFixed(0)}h on a re-review for ${prUrl}. Could you take another look when you have a moment?`;
108939
+ const body = buildRalphyComment({
108940
+ type: "reviewer-ping",
108941
+ action: "nudging reviewer",
108942
+ body: `@${reviewer} \u2014 Ralph has been waiting ${elapsedH.toFixed(0)}h on a re-review for ${prUrl}. Could you take another look when you have a moment?`,
108943
+ fields: { reviewer }
108944
+ });
108375
108945
  try {
108376
108946
  await deps.cmdRunner.run(["gh", "api", `repos/${owner}/${repo}/issues/${num}/comments`, "-f", `body=${body}`], deps.projectRoot);
108377
108947
  deps.stalePingedAt.set(prUrl, now2);
@@ -108437,6 +109007,7 @@ async function scanCodeReview(issue2, prUrl, lastRalphPickup, deps) {
108437
109007
  var init_scan = __esm(() => {
108438
109008
  init_layout();
108439
109009
  init_store();
109010
+ init_src8();
108440
109011
  init_scaffold();
108441
109012
  init_worktree();
108442
109013
  });
@@ -108486,7 +109057,7 @@ async function fetchPrIssueComments(cmdRunner, projectRoot, prUrl, onLog) {
108486
109057
  return [];
108487
109058
  }
108488
109059
  }
108489
- var init_github = __esm(() => {
109060
+ var init_github2 = __esm(() => {
108490
109061
  init_linear();
108491
109062
  init_task_bodies();
108492
109063
  });
@@ -108494,6 +109065,19 @@ var init_github = __esm(() => {
108494
109065
  // apps/agent/src/agent/wire/mention-scan.ts
108495
109066
  import { readdir as readdir2 } from "fs/promises";
108496
109067
  import { join as join29 } from "path";
109068
+ function latestIso(...values2) {
109069
+ let latest = null;
109070
+ for (const value of values2) {
109071
+ if (value && (latest === null || value > latest))
109072
+ latest = value;
109073
+ }
109074
+ return latest;
109075
+ }
109076
+ function isAlreadyReactedError(err) {
109077
+ const e = err;
109078
+ const text = [...e?.messages ?? [], e?.message ?? ""].join(" ").toLowerCase();
109079
+ return text.includes("conflict on insert of reaction") || text.includes("already exists") || text.includes("already reacted");
109080
+ }
108497
109081
  function createMentionScanner(input) {
108498
109082
  const {
108499
109083
  apiKey,
@@ -108555,13 +109139,14 @@ function createMentionScanner(input) {
108555
109139
  for (const issue2 of candidates) {
108556
109140
  const comments = issue2.comments ?? [];
108557
109141
  const lastRalphPickup = findLastRalphPickupISO(comments);
109142
+ const linearMentionGate = latestIso(lastRalphPickup, findLastMentionAckISO(comments));
108558
109143
  if (wantMention) {
108559
109144
  for (const c of comments) {
108560
109145
  if (isRalphComment(c.body))
108561
109146
  continue;
108562
109147
  if (!containsHandle(c.body, handle))
108563
109148
  continue;
108564
- if (lastRalphPickup && c.createdAt <= lastRalphPickup)
109149
+ if (linearMentionGate && c.createdAt <= linearMentionGate)
108565
109150
  continue;
108566
109151
  out.push({
108567
109152
  issue: issue2,
@@ -108581,7 +109166,9 @@ function createMentionScanner(input) {
108581
109166
  queued.add(issue2.id);
108582
109167
  break;
108583
109168
  }
108584
- diag("mention", `! mention scan: Linear reaction failed for ${issue2.identifier}: ${formatLinearError(err)}`, "yellow");
109169
+ if (!isAlreadyReactedError(err)) {
109170
+ diag("mention", `! mention scan: Linear reaction failed for ${issue2.identifier}: ${formatLinearError(err)}`, "yellow");
109171
+ }
108585
109172
  }
108586
109173
  if (cfg.linear.postComments !== false) {
108587
109174
  try {
@@ -108605,11 +109192,14 @@ function createMentionScanner(input) {
108605
109192
  continue;
108606
109193
  if (wantMention) {
108607
109194
  const ghComments = await fetchPrIssueComments(cmdRunner, projectRoot, prUrl, onLog);
109195
+ const ghMentionGate = latestIso(lastRalphPickup, findLastMentionAckISO(ghComments));
108608
109196
  const prMatch = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/\d+/.exec(prUrl);
108609
109197
  for (const c of ghComments) {
109198
+ if (isRalphComment(c.body))
109199
+ continue;
108610
109200
  if (!containsHandle(c.body, handle))
108611
109201
  continue;
108612
- if (lastRalphPickup && c.createdAt <= lastRalphPickup)
109202
+ if (ghMentionGate && c.createdAt <= ghMentionGate)
108613
109203
  continue;
108614
109204
  out.push({
108615
109205
  issue: issue2,
@@ -108626,7 +109216,9 @@ function createMentionScanner(input) {
108626
109216
  try {
108627
109217
  await addGithubReactionToComment(cmdRunner, projectRoot, { owner, repo, kind: "issue" }, c.id, "\uD83D\uDC40");
108628
109218
  } catch (err) {
108629
- diag("mention", `! mention scan: GitHub reaction failed for ${prUrl}: ${formatLinearError(err)}`, "yellow");
109219
+ if (!isAlreadyReactedError(err)) {
109220
+ diag("mention", `! mention scan: GitHub reaction failed for ${prUrl}: ${formatLinearError(err)}`, "yellow");
109221
+ }
108630
109222
  }
108631
109223
  if (cfg.linear.postComments !== false) {
108632
109224
  await postGithubPrComment(cmdRunner, projectRoot, prUrl, buildMentionAckComment(c.body, c.author), onLog);
@@ -108680,7 +109272,7 @@ var init_mention_scan = __esm(() => {
108680
109272
  init_detections();
108681
109273
  init_scaffold();
108682
109274
  init_scan();
108683
- init_github();
109275
+ init_github2();
108684
109276
  init_task_bodies();
108685
109277
  });
108686
109278
 
@@ -109688,7 +110280,12 @@ async function readTasksMd(changeDir, log3) {
109688
110280
  }
109689
110281
  }
109690
110282
  function renderTasksCommentBody(tasksMd, changeName, iteration) {
109691
- return renderTasksBlock(tasksMd, { changeName, iteration });
110283
+ return buildRalphyComment({
110284
+ type: "tasks",
110285
+ action: "task progress",
110286
+ body: renderTasksBlock(tasksMd, { changeName, iteration }),
110287
+ fields: { change: changeName, iter: iteration }
110288
+ });
109692
110289
  }
109693
110290
  async function postOrUpdateTasksComment(deps) {
109694
110291
  const tasksMd = await readTasksMd(deps.changeDir, deps.log);
@@ -109799,8 +110396,13 @@ async function postPlanCommentOnce(deps) {
109799
110396
  if (designSummary) {
109800
110397
  parts.push("", "**Design**", "", designSummary);
109801
110398
  }
109802
- const body = parts.join(`
109803
- `);
110399
+ const body = buildRalphyComment({
110400
+ type: "plan",
110401
+ action: "plan",
110402
+ body: parts.join(`
110403
+ `),
110404
+ fields: { change: deps.changeName }
110405
+ });
109804
110406
  let id;
109805
110407
  try {
109806
110408
  id = await deps.mutations.createIssueComment(deps.apiKey, deps.issueId, body);
@@ -109817,9 +110419,14 @@ async function postPlanCommentOnce(deps) {
109817
110419
  }
109818
110420
  async function postSteeringAndRefreshTasks(deps) {
109819
110421
  const firstLine = deps.message.split(/\r?\n/, 1)[0].trim() || deps.message.trim();
109820
- const steeringBody = `### ${STEERING_COMMENT_TITLE}
110422
+ const steeringBody = buildRalphyComment({
110423
+ type: "steering",
110424
+ action: "steering",
110425
+ body: `### ${STEERING_COMMENT_TITLE}
109821
110426
 
109822
- ${deps.message.trim()}`;
110427
+ ${deps.message.trim()}`,
110428
+ fields: { change: deps.changeName }
110429
+ });
109823
110430
  try {
109824
110431
  await deps.mutations.createIssueComment(deps.apiKey, deps.issueId, steeringBody);
109825
110432
  deps.log(` comment-sync: posted steering comment (${firstLine})`, "gray");
@@ -109852,6 +110459,7 @@ ${deps.message.trim()}`;
109852
110459
  var PLAN_COMMENT_TITLE = "\uD83D\uDCCB Ralph plan", STEERING_COMMENT_TITLE = "\uD83E\uDDED Ralph steering";
109853
110460
  var init_comment_sync = __esm(() => {
109854
110461
  init_store();
110462
+ init_src8();
109855
110463
  init_linear_sync();
109856
110464
  });
109857
110465
 
@@ -262718,11 +263326,12 @@ function buildAgentCoordinator(input) {
262718
263326
  };
262719
263327
  const concurrency = args.concurrency || cfg.concurrency;
262720
263328
  const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
262721
- const indicators = mergeIndicators(cfg.linear.indicators, args.indicators);
263329
+ const isGithubTracker = cfg.tracker.kind === "github";
263330
+ const indicators = isGithubTracker ? githubIndicators(cfg.github?.issues) : mergeIndicators(cfg.linear.indicators, args.indicators);
262722
263331
  const team = args.linearTeam || cfg.linear.team;
262723
263332
  const { assignee, anyAssignee, requireAllLabels } = resolveLinearFilter(applyAssigneeOverride(cfg.linear.filter, args.linearAssignee));
262724
263333
  const ticketNumbers = resolveTicketNumbers(args.ticketTokens, team);
262725
- const excludeFromTodo = unionMarkers(indicators.setDone, indicators.setError);
263334
+ const excludeFromTodo = isGithubTracker ? unionMarkers(indicators.setDone, indicators.setError, indicators.setInProgress) : unionMarkers(indicators.setDone, indicators.setError);
262726
263335
  const gitRunner = input.runners?.git ?? bunGitRunner;
262727
263336
  const cmdRunner = input.runners?.cmd ?? bunCmdRunner;
262728
263337
  const cwdByChange = new Map;
@@ -262757,7 +263366,7 @@ function buildAgentCoordinator(input) {
262757
263366
  }
262758
263367
  return code;
262759
263368
  });
262760
- const resolvers = createLinearResolvers({
263369
+ const resolvers = isGithubTracker ? null : createLinearResolvers({
262761
263370
  apiKey,
262762
263371
  team,
262763
263372
  assignee,
@@ -262766,6 +263375,15 @@ function buildAgentCoordinator(input) {
262766
263375
  diag,
262767
263376
  ...ticketNumbers.length > 0 ? { ticketNumbers } : {}
262768
263377
  });
263378
+ const provider = isGithubTracker ? createGithubTrackerProvider({
263379
+ issues: cfg.github?.issues,
263380
+ cmdRunner,
263381
+ projectRoot,
263382
+ diag
263383
+ }) : {
263384
+ ...resolvers,
263385
+ fetchDoneCandidates: () => fetchDoneCandidatesWith(apiKey, team, assignee, anyAssignee, requireAllLabels, indicators, ticketNumbers.length > 0 ? ticketNumbers : undefined)
263386
+ };
262769
263387
  if (ticketNumbers.length > 0) {
262770
263388
  const hasGetIndicator = [indicators.getTodo, indicators.getInProgress].some((ind) => ind && ind.filter.length > 0);
262771
263389
  if (!hasGetIndicator) {
@@ -262796,7 +263414,7 @@ function buildAgentCoordinator(input) {
262796
263414
  scriptRunner,
262797
263415
  ...input.runners?.worktree ? { worktreeProvider: input.runners.worktree } : {}
262798
263416
  });
262799
- const fetchMentions = createMentionScanner({
263417
+ const fetchMentions = isGithubTracker ? async () => [] : createMentionScanner({
262800
263418
  apiKey,
262801
263419
  args,
262802
263420
  cfg,
@@ -262816,6 +263434,27 @@ function buildAgentCoordinator(input) {
262816
263434
  lastHandledReviewActivity,
262817
263435
  resolvePrUrlForIssue: prDiscovery.resolvePrUrlForIssue
262818
263436
  });
263437
+ const tracker = isGithubTracker ? {
263438
+ fetchTodo: () => provider.fetchByGet(indicators.getTodo, excludeFromTodo),
263439
+ fetchInProgress: () => provider.fetchByGet(indicators.getInProgress, unionMarkers(indicators.setError)),
263440
+ fetchReview: async () => [],
263441
+ fetchMentions,
263442
+ fetchDoneCandidates: () => provider.fetchDoneCandidates(),
263443
+ applyIndicator: provider.applyIndicator,
263444
+ removeIndicator: provider.removeIndicator,
263445
+ postComment: (issue2, body) => provider.applyMarker(issue2, { type: "comment", value: body }),
263446
+ fetchComments: async () => []
263447
+ } : createLinearTrackerProvider({
263448
+ apiKey,
263449
+ team,
263450
+ assignee,
263451
+ anyAssignee,
263452
+ requireAllLabels,
263453
+ indicators,
263454
+ resolvers,
263455
+ fetchMentions,
263456
+ ...ticketNumbers.length > 0 ? { ticketNumbers } : {}
263457
+ });
262819
263458
  const spawnWorker = createSpawnWorker({
262820
263459
  args,
262821
263460
  cfg,
@@ -262827,7 +263466,7 @@ function buildAgentCoordinator(input) {
262827
263466
  indicators,
262828
263467
  cmdRunner,
262829
263468
  gitRunner,
262830
- applyIndicator: resolvers.applyIndicator,
263469
+ applyIndicator: provider.applyIndicator,
262831
263470
  bus,
262832
263471
  onLog,
262833
263472
  diag,
@@ -262871,8 +263510,8 @@ function buildAgentCoordinator(input) {
262871
263510
  cwdOf: (cn) => cwdByChange.get(cn),
262872
263511
  awaitingChangeSet,
262873
263512
  reapForAwaiting: (cn) => coordRef.current?.reapForAwaiting(cn),
262874
- applyIndicator: resolvers.applyIndicator,
262875
- applyMarker: resolvers.applyMarker,
263513
+ applyIndicator: provider.applyIndicator,
263514
+ applyMarker: provider.applyMarker,
262876
263515
  openDraftPr,
262877
263516
  ...onAwaitingTicket ? { onAwaitingTicket } : {},
262878
263517
  onLog
@@ -262902,7 +263541,7 @@ function buildAgentCoordinator(input) {
262902
263541
  projectRoot,
262903
263542
  maxRecoveryAttempts: cfg.prRecovery.maxRecoverySessions
262904
263543
  }) : null;
262905
- const commentSync = createCommentSyncHooks({
263544
+ const commentSync = isGithubTracker ? { enabled: false } : createCommentSyncHooks({
262906
263545
  apiKey,
262907
263546
  cfg,
262908
263547
  projectRoot,
@@ -262915,20 +263554,18 @@ function buildAgentCoordinator(input) {
262915
263554
  beforePoll: () => {
262916
263555
  pollContext = new PollContext;
262917
263556
  },
262918
- fetchTodo: () => resolvers.fetchByGet(indicators.getTodo, excludeFromTodo),
262919
- fetchInProgress: () => resolvers.fetchByGet(indicators.getInProgress, unionMarkers(indicators.setError)),
262920
- fetchMentions,
262921
- fetchDoneCandidates: () => fetchDoneCandidatesWith(apiKey, team, assignee, anyAssignee, requireAllLabels, indicators, ticketNumbers.length > 0 ? ticketNumbers : undefined),
263557
+ fetchTodo: tracker.fetchTodo,
263558
+ fetchInProgress: tracker.fetchInProgress,
263559
+ fetchMentions: tracker.fetchMentions,
263560
+ fetchDoneCandidates: tracker.fetchDoneCandidates,
263561
+ fetchReview: tracker.fetchReview,
262922
263562
  prepare: prep.prepare,
262923
263563
  prepareTaskForTrigger: prep.prepareTaskForTrigger,
262924
263564
  spawnWorker,
262925
- applyIndicator: resolvers.applyIndicator,
262926
- removeIndicator: resolvers.removeIndicator,
262927
- postComment: (issue2, body) => addIssueComment(apiKey, issue2.id, body),
262928
- fetchComments: async (issueId) => {
262929
- const c = await fetchIssueComments(apiKey, issueId);
262930
- return c.map((x2) => ({ body: x2.body }));
262931
- },
263565
+ applyIndicator: tracker.applyIndicator,
263566
+ removeIndicator: tracker.removeIndicator,
263567
+ postComment: tracker.postComment,
263568
+ fetchComments: tracker.fetchComments,
262932
263569
  checkPrStatus: prDiscovery.checkPrStatus,
262933
263570
  hasPrForChange: (changeName) => prByChange.has(changeName),
262934
263571
  isChangeArchivedForIssue: (issue2) => isChangeArchivedForIssue(issue2, cwdByChange, projectRoot),
@@ -262976,7 +263613,7 @@ function buildAgentCoordinator(input) {
262976
263613
  }
262977
263614
  });
262978
263615
  coordRef.current = coord;
262979
- const filterDesc = describeIndicators(indicators, team, assignee, anyAssignee);
263616
+ const filterDesc = describeIndicators(indicators, team, assignee, anyAssignee, requireAllLabels);
262980
263617
  const runBaselineGateOnce = createBaselineGateRunner({
262981
263618
  args,
262982
263619
  cfg,
@@ -262987,7 +263624,7 @@ function buildAgentCoordinator(input) {
262987
263624
  gitRunner,
262988
263625
  coord,
262989
263626
  onLog,
262990
- resolveLabelIdForTeam: resolvers.resolveLabelIdForTeam
263627
+ resolveLabelIdForTeam: provider.resolveLabelIdForTeam
262991
263628
  });
262992
263629
  return {
262993
263630
  coord,
@@ -263015,7 +263652,6 @@ var init_wire = __esm(() => {
263015
263652
  init_workflow();
263016
263653
  init_src2();
263017
263654
  init_coordinator2();
263018
- init_linear();
263019
263655
  init_layout();
263020
263656
  init_scaffold();
263021
263657
  init_awaiting();
@@ -263024,6 +263660,8 @@ var init_wire = __esm(() => {
263024
263660
  init_indicators();
263025
263661
  init_task_bodies();
263026
263662
  init_linear_resolvers();
263663
+ init_github();
263664
+ init_linear_tracker_provider();
263027
263665
  init_linear_client();
263028
263666
  init_prepare();
263029
263667
  init_pr_discovery();
@@ -266187,7 +266825,7 @@ async function main3(argv) {
266187
266825
  return typeof process.exitCode === "number" ? process.exitCode : 0;
266188
266826
  }
266189
266827
  var import_react64;
266190
- var init_src8 = __esm(async () => {
266828
+ var init_src9 = __esm(async () => {
266191
266829
  init_context();
266192
266830
  init_layout();
266193
266831
  init_paths();
@@ -266245,7 +266883,7 @@ async function dispatch(subcommand, rest2) {
266245
266883
  return main4(rest2);
266246
266884
  }
266247
266885
  if (subcommand === "agent") {
266248
- const { main: main4 } = await init_src8().then(() => exports_src3);
266886
+ const { main: main4 } = await init_src9().then(() => exports_src3);
266249
266887
  return main4(rest2);
266250
266888
  }
266251
266889
  if (subcommand === "task") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "3.10.16",
3
+ "version": "3.10.17",
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",
@@ -51,7 +51,7 @@
51
51
  "check:circular:ci": "depcruise packages/*/src apps/*/src --config .dependency-cruiser.cjs",
52
52
  "check:unused:ci": "knip",
53
53
  "check:outdated:ci": "bun scripts/check-outdated.ts",
54
- "check:structure": "bun scripts/check-prop-drilling.ts && bun scripts/check-hooks-location.ts && bun scripts/check-folder-size.ts && bun scripts/check-filename-case.ts && bun scripts/check-single-component.ts && bun scripts/check-no-reexport-tsx.ts && bun scripts/check-test-location.ts && bun scripts/check-static-error-messages.ts && bash scripts/check-no-unsafe-casts.sh",
54
+ "check:structure": "bun scripts/check-prop-drilling.ts && bun scripts/check-hooks-location.ts && bun scripts/check-folder-size.ts && bun scripts/check-filename-case.ts && bun scripts/check-single-component.ts && bun scripts/check-no-reexport-tsx.ts && bun scripts/check-test-location.ts && bun scripts/check-static-error-messages.ts && bash scripts/check-no-unsafe-casts.sh && bun scripts/check-tracker-seam.ts",
55
55
  "check:shell": "bash scripts/check-shell.sh",
56
56
  "check:duplicates": "bun scripts/check-duplicate-declarations.ts --diff",
57
57
  "check:duplicates:all": "bun scripts/check-duplicate-declarations.ts --all",