@neriros/ralphy 3.3.0 → 3.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/shell/index.js +347 -98
- package/package.json +1 -1
package/dist/shell/index.js
CHANGED
|
@@ -18928,8 +18928,8 @@ import { readFileSync } from "fs";
|
|
|
18928
18928
|
import { resolve } from "path";
|
|
18929
18929
|
function getVersion() {
|
|
18930
18930
|
try {
|
|
18931
|
-
if ("3.3.
|
|
18932
|
-
return "3.3.
|
|
18931
|
+
if ("3.3.2")
|
|
18932
|
+
return "3.3.2";
|
|
18933
18933
|
} catch {}
|
|
18934
18934
|
const dirsToTry = [];
|
|
18935
18935
|
try {
|
|
@@ -63946,6 +63946,21 @@ function readState(changeDir) {
|
|
|
63946
63946
|
throw new Error(".ralph-state.json not found");
|
|
63947
63947
|
return StateSchema.parse(JSON.parse(raw));
|
|
63948
63948
|
}
|
|
63949
|
+
function tryReadStateRaw(changeDir) {
|
|
63950
|
+
const filePath = join7(changeDir, STATE_FILE2);
|
|
63951
|
+
const text = getStorage().read(filePath);
|
|
63952
|
+
if (text === null)
|
|
63953
|
+
return { state: null, raw: null };
|
|
63954
|
+
let parsed;
|
|
63955
|
+
try {
|
|
63956
|
+
parsed = JSON.parse(text);
|
|
63957
|
+
} catch {
|
|
63958
|
+
return { state: null, raw: null };
|
|
63959
|
+
}
|
|
63960
|
+
const raw = parsed && typeof parsed === "object" ? parsed : {};
|
|
63961
|
+
const result2 = StateSchema.safeParse(parsed);
|
|
63962
|
+
return { state: result2.success ? result2.data : null, raw };
|
|
63963
|
+
}
|
|
63949
63964
|
function writeState(changeDir, state) {
|
|
63950
63965
|
const filePath = join7(changeDir, STATE_FILE2);
|
|
63951
63966
|
getStorage().write(filePath, JSON.stringify(state, null, 2) + `
|
|
@@ -70613,9 +70628,9 @@ function useLoop(opts) {
|
|
|
70613
70628
|
const tasksDir = join11(opts.tasksDir, opts.name);
|
|
70614
70629
|
const storage = getStorage();
|
|
70615
70630
|
let currentState;
|
|
70616
|
-
const
|
|
70617
|
-
if (
|
|
70618
|
-
currentState =
|
|
70631
|
+
const { state: parsedState, raw: rawState } = tryReadStateRaw(stateDir);
|
|
70632
|
+
if (parsedState !== null) {
|
|
70633
|
+
currentState = parsedState;
|
|
70619
70634
|
if (currentState.engine !== opts.engine || currentState.model !== opts.model) {
|
|
70620
70635
|
currentState = {
|
|
70621
70636
|
...currentState,
|
|
@@ -70625,6 +70640,9 @@ function useLoop(opts) {
|
|
|
70625
70640
|
writeState(stateDir, currentState);
|
|
70626
70641
|
}
|
|
70627
70642
|
} else {
|
|
70643
|
+
if (rawState !== null) {
|
|
70644
|
+
addInfo(`.ralph-state.json was malformed \u2014 reinitialising. External fields (linearComments) preserved.`);
|
|
70645
|
+
}
|
|
70628
70646
|
currentState = buildInitialState({
|
|
70629
70647
|
name: opts.name,
|
|
70630
70648
|
prompt: opts.prompt,
|
|
@@ -70633,6 +70651,9 @@ function useLoop(opts) {
|
|
|
70633
70651
|
manualTest: opts.manualTest,
|
|
70634
70652
|
createPr: opts.createPr ?? false
|
|
70635
70653
|
});
|
|
70654
|
+
if (rawState !== null && rawState.linearComments) {
|
|
70655
|
+
currentState.linearComments = rawState.linearComments;
|
|
70656
|
+
}
|
|
70636
70657
|
writeState(stateDir, currentState);
|
|
70637
70658
|
}
|
|
70638
70659
|
const isResume2 = currentState.iteration > 0;
|
|
@@ -70661,6 +70682,27 @@ function useLoop(opts) {
|
|
|
70661
70682
|
}
|
|
70662
70683
|
const tasksContent = storage.read(join11(tasksDir, MISSION_TASKS_FILENAME));
|
|
70663
70684
|
const agentTasksContent = storage.read(join11(tasksDir, AGENT_TASKS_FILENAME));
|
|
70685
|
+
if (tasksContent === null && currentState.iteration > 0 && typeof opts.changeStore.listChanges === "function") {
|
|
70686
|
+
let stillActive = true;
|
|
70687
|
+
try {
|
|
70688
|
+
const active = await opts.changeStore.listChanges();
|
|
70689
|
+
stillActive = active.includes(opts.name);
|
|
70690
|
+
} catch {
|
|
70691
|
+
stillActive = true;
|
|
70692
|
+
}
|
|
70693
|
+
if (!stillActive) {
|
|
70694
|
+
addInfo(`tasks.md not found and change "${opts.name}" is no longer active \u2014 it was archived externally. Exiting.`);
|
|
70695
|
+
currentState = {
|
|
70696
|
+
...currentState,
|
|
70697
|
+
status: "completed",
|
|
70698
|
+
lastModified: new Date().toISOString()
|
|
70699
|
+
};
|
|
70700
|
+
writeState(stateDir, currentState);
|
|
70701
|
+
setState(currentState);
|
|
70702
|
+
finalStopReason = "completed";
|
|
70703
|
+
break;
|
|
70704
|
+
}
|
|
70705
|
+
}
|
|
70664
70706
|
if (tasksContent !== null) {
|
|
70665
70707
|
const remaining = countUnchecked(tasksContent);
|
|
70666
70708
|
const agentRemaining = agentTasksContent !== null ? countUnchecked(agentTasksContent) : 0;
|
|
@@ -92471,7 +92513,7 @@ var MarkerSchema, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, Proj
|
|
|
92471
92513
|
var init_schema = __esm(() => {
|
|
92472
92514
|
init_zod2();
|
|
92473
92515
|
MarkerSchema = exports_external2.object({
|
|
92474
|
-
type: exports_external2.enum(["label", "status", "attachment"]),
|
|
92516
|
+
type: exports_external2.enum(["label", "status", "attachment", "project"]),
|
|
92475
92517
|
value: exports_external2.string().min(1)
|
|
92476
92518
|
});
|
|
92477
92519
|
GetIndicatorSchema = exports_external2.object({
|
|
@@ -92796,6 +92838,17 @@ linear:
|
|
|
92796
92838
|
# setError:
|
|
92797
92839
|
# type: label
|
|
92798
92840
|
# value: "ralph:error"
|
|
92841
|
+
#
|
|
92842
|
+
# # Project-based filter / assignment
|
|
92843
|
+
# # getTodo can filter by Linear project name, and setInProgress can
|
|
92844
|
+
# # reassign the issue into a different project.
|
|
92845
|
+
# getTodo:
|
|
92846
|
+
# filter:
|
|
92847
|
+
# - type: project
|
|
92848
|
+
# value: "Ralph Queue"
|
|
92849
|
+
# setInProgress:
|
|
92850
|
+
# type: project
|
|
92851
|
+
# value: "Ralph In Progress"
|
|
92799
92852
|
---
|
|
92800
92853
|
You are working on {{ issue.identifier }}: {{ issue.title }}.
|
|
92801
92854
|
|
|
@@ -93603,15 +93656,18 @@ function partition2(markers) {
|
|
|
93603
93656
|
const statuses = [];
|
|
93604
93657
|
const labels = [];
|
|
93605
93658
|
const attachmentSubtitles = [];
|
|
93659
|
+
const projects = [];
|
|
93606
93660
|
for (const m of markers) {
|
|
93607
93661
|
if (m.type === "status")
|
|
93608
93662
|
statuses.push(m.value);
|
|
93609
93663
|
else if (m.type === "label")
|
|
93610
93664
|
labels.push(m.value);
|
|
93611
|
-
else
|
|
93665
|
+
else if (m.type === "attachment")
|
|
93612
93666
|
attachmentSubtitles.push(m.value);
|
|
93667
|
+
else if (m.type === "project")
|
|
93668
|
+
projects.push(m.value);
|
|
93613
93669
|
}
|
|
93614
|
-
return { statuses, labels, attachmentSubtitles };
|
|
93670
|
+
return { statuses, labels, attachmentSubtitles, projects };
|
|
93615
93671
|
}
|
|
93616
93672
|
function buildIssueFilter(spec) {
|
|
93617
93673
|
const where = {};
|
|
@@ -93627,7 +93683,7 @@ function buildIssueFilter(spec) {
|
|
|
93627
93683
|
}
|
|
93628
93684
|
const inc = spec.include ?? [];
|
|
93629
93685
|
if (inc.length > 0) {
|
|
93630
|
-
const { statuses, labels, attachmentSubtitles } = partition2(inc);
|
|
93686
|
+
const { statuses, labels, attachmentSubtitles, projects } = partition2(inc);
|
|
93631
93687
|
const branches = [];
|
|
93632
93688
|
if (statuses.length > 0)
|
|
93633
93689
|
branches.push({ state: { name: { in: statuses } } });
|
|
@@ -93643,6 +93699,8 @@ function buildIssueFilter(spec) {
|
|
|
93643
93699
|
}
|
|
93644
93700
|
});
|
|
93645
93701
|
}
|
|
93702
|
+
if (projects.length > 0)
|
|
93703
|
+
branches.push({ project: { name: { in: projects } } });
|
|
93646
93704
|
for (const b of branches)
|
|
93647
93705
|
Object.assign(where, b);
|
|
93648
93706
|
} else {
|
|
@@ -93650,7 +93708,23 @@ function buildIssueFilter(spec) {
|
|
|
93650
93708
|
}
|
|
93651
93709
|
const exc = spec.exclude ?? [];
|
|
93652
93710
|
if (exc.length > 0) {
|
|
93653
|
-
const {
|
|
93711
|
+
const {
|
|
93712
|
+
statuses,
|
|
93713
|
+
labels,
|
|
93714
|
+
attachmentSubtitles: excludedSubtitles,
|
|
93715
|
+
projects: excludedProjects
|
|
93716
|
+
} = partition2(exc);
|
|
93717
|
+
if (excludedProjects.length > 0) {
|
|
93718
|
+
const current = where.project;
|
|
93719
|
+
const noProject = { project: { name: { nin: excludedProjects } } };
|
|
93720
|
+
if (current === undefined)
|
|
93721
|
+
Object.assign(where, noProject);
|
|
93722
|
+
else {
|
|
93723
|
+
const existingAnd = where.and ?? [];
|
|
93724
|
+
where.and = [...existingAnd, { project: current }, noProject];
|
|
93725
|
+
delete where.project;
|
|
93726
|
+
}
|
|
93727
|
+
}
|
|
93654
93728
|
if (excludedSubtitles.length > 0) {
|
|
93655
93729
|
const existingAnd = where.and ?? [];
|
|
93656
93730
|
where.and = [
|
|
@@ -93709,10 +93783,14 @@ async function fetchMentionScanIssues(apiKey, spec) {
|
|
|
93709
93783
|
id identifier title description url priority createdAt
|
|
93710
93784
|
state { name type }
|
|
93711
93785
|
assignee { id email name }
|
|
93786
|
+
project { id name }
|
|
93712
93787
|
labels { nodes { name } }
|
|
93713
93788
|
relations(first: 50) {
|
|
93714
93789
|
nodes { type relatedIssue { id state { type } } }
|
|
93715
93790
|
}
|
|
93791
|
+
comments(first: 50) {
|
|
93792
|
+
nodes { id body createdAt user { name email } }
|
|
93793
|
+
}
|
|
93716
93794
|
}
|
|
93717
93795
|
}
|
|
93718
93796
|
}`;
|
|
@@ -93728,10 +93806,12 @@ async function fetchMentionScanIssues(apiKey, spec) {
|
|
|
93728
93806
|
url: n.url,
|
|
93729
93807
|
state: n.state,
|
|
93730
93808
|
assignee: n.assignee,
|
|
93809
|
+
project: n.project ?? null,
|
|
93731
93810
|
labels: n.labels.nodes.map((l) => l.name),
|
|
93732
93811
|
priority: n.priority,
|
|
93733
93812
|
createdAt: n.createdAt ?? "",
|
|
93734
|
-
blockedByIds: (n.relations?.nodes ?? []).filter((r) => r.type === "blocked_by" && !DONE_STATE_TYPES.has(r.relatedIssue.state.type)).map((r) => r.relatedIssue.id)
|
|
93813
|
+
blockedByIds: (n.relations?.nodes ?? []).filter((r) => r.type === "blocked_by" && !DONE_STATE_TYPES.has(r.relatedIssue.state.type)).map((r) => r.relatedIssue.id),
|
|
93814
|
+
comments: n.comments?.nodes ?? []
|
|
93735
93815
|
}));
|
|
93736
93816
|
}
|
|
93737
93817
|
async function fetchOpenIssues(apiKey, spec) {
|
|
@@ -93742,6 +93822,7 @@ async function fetchOpenIssues(apiKey, spec) {
|
|
|
93742
93822
|
id identifier title description url priority createdAt
|
|
93743
93823
|
state { name type }
|
|
93744
93824
|
assignee { id email name }
|
|
93825
|
+
project { id name }
|
|
93745
93826
|
labels { nodes { name } }
|
|
93746
93827
|
relations(first: 50) {
|
|
93747
93828
|
nodes {
|
|
@@ -93764,6 +93845,7 @@ async function fetchOpenIssues(apiKey, spec) {
|
|
|
93764
93845
|
url: n.url,
|
|
93765
93846
|
state: n.state,
|
|
93766
93847
|
assignee: n.assignee,
|
|
93848
|
+
project: n.project ?? null,
|
|
93767
93849
|
labels: n.labels.nodes.map((l) => l.name),
|
|
93768
93850
|
priority: n.priority,
|
|
93769
93851
|
createdAt: n.createdAt ?? "",
|
|
@@ -93956,25 +94038,55 @@ async function updateAttachmentSubtitle(apiKey, attachmentId, subtitle) {
|
|
|
93956
94038
|
});
|
|
93957
94039
|
}
|
|
93958
94040
|
async function upsertRalphyAttachment(apiKey, issueId, issueUrl, subtitle) {
|
|
93959
|
-
const attachments = await fetchIssueAttachments(apiKey, issueId
|
|
93960
|
-
|
|
94041
|
+
const attachments = await fetchIssueAttachments(apiKey, issueId, {
|
|
94042
|
+
titleFilter: RALPHY_ATTACHMENT_TITLE
|
|
94043
|
+
});
|
|
94044
|
+
const existing = attachments[0];
|
|
93961
94045
|
if (existing) {
|
|
93962
94046
|
await updateAttachmentSubtitle(apiKey, existing.id, subtitle);
|
|
93963
94047
|
} else {
|
|
93964
94048
|
await createRalphyAttachment(apiKey, issueId, issueUrl, subtitle);
|
|
93965
94049
|
}
|
|
93966
94050
|
}
|
|
93967
|
-
async function fetchIssueAttachments(apiKey, issueId) {
|
|
93968
|
-
const
|
|
94051
|
+
async function fetchIssueAttachments(apiKey, issueId, options) {
|
|
94052
|
+
const titleFilter = options?.titleFilter;
|
|
94053
|
+
const query = titleFilter !== undefined ? `query IssueAttachments($id: String!, $titleFilter: String!) {
|
|
94054
|
+
issue(id: $id) {
|
|
94055
|
+
attachments(filter: { title: { eq: $titleFilter } }, first: 25) {
|
|
94056
|
+
nodes { id url sourceType title }
|
|
94057
|
+
}
|
|
94058
|
+
}
|
|
94059
|
+
}` : `query IssueAttachments($id: String!) {
|
|
93969
94060
|
issue(id: $id) {
|
|
93970
94061
|
attachments(first: 25) {
|
|
93971
94062
|
nodes { id url sourceType title }
|
|
93972
94063
|
}
|
|
93973
94064
|
}
|
|
93974
94065
|
}`;
|
|
93975
|
-
const
|
|
94066
|
+
const variables = titleFilter !== undefined ? { id: issueId, titleFilter } : { id: issueId };
|
|
94067
|
+
const data = await linearRequest(apiKey, query, variables);
|
|
93976
94068
|
return data.issue?.attachments?.nodes ?? [];
|
|
93977
94069
|
}
|
|
94070
|
+
async function fetchAttachmentsForIssues(apiKey, issueIds) {
|
|
94071
|
+
const out = new Map;
|
|
94072
|
+
if (issueIds.length === 0)
|
|
94073
|
+
return out;
|
|
94074
|
+
const query = `query IssuesAttachments($ids: [ID!]!) {
|
|
94075
|
+
issues(filter: { id: { in: $ids } }, first: 250) {
|
|
94076
|
+
nodes {
|
|
94077
|
+
id
|
|
94078
|
+
attachments(first: 25) {
|
|
94079
|
+
nodes { id url sourceType title }
|
|
94080
|
+
}
|
|
94081
|
+
}
|
|
94082
|
+
}
|
|
94083
|
+
}`;
|
|
94084
|
+
const data = await linearRequest(apiKey, query, { ids: issueIds });
|
|
94085
|
+
for (const node2 of data.issues.nodes) {
|
|
94086
|
+
out.set(node2.id, node2.attachments?.nodes ?? []);
|
|
94087
|
+
}
|
|
94088
|
+
return out;
|
|
94089
|
+
}
|
|
93978
94090
|
async function fetchWorkflowStates(apiKey, teamKey) {
|
|
93979
94091
|
const query = `query States($team: String!) {
|
|
93980
94092
|
workflowStates(filter: { team: { key: { eq: $team } } }, first: 50) {
|
|
@@ -94073,14 +94185,40 @@ function issueMatchesGetIndicator(issue2, indicator) {
|
|
|
94073
94185
|
return false;
|
|
94074
94186
|
const labels = new Set(issue2.labels.map((l) => l.toLowerCase()));
|
|
94075
94187
|
const stateName = issue2.state.name.toLowerCase();
|
|
94188
|
+
const projectName = issue2.project?.name.toLowerCase() ?? null;
|
|
94076
94189
|
return indicator.filter.some((m) => {
|
|
94077
94190
|
if (m.type === "label")
|
|
94078
94191
|
return labels.has(m.value.toLowerCase());
|
|
94079
94192
|
if (m.type === "status")
|
|
94080
94193
|
return stateName === m.value.toLowerCase();
|
|
94194
|
+
if (m.type === "project") {
|
|
94195
|
+
if (projectName === null)
|
|
94196
|
+
return false;
|
|
94197
|
+
return projectName === m.value.toLowerCase();
|
|
94198
|
+
}
|
|
94081
94199
|
return false;
|
|
94082
94200
|
});
|
|
94083
94201
|
}
|
|
94202
|
+
async function fetchProjectIdByName(apiKey, name) {
|
|
94203
|
+
const query = `query ProjectId($name: String!) {
|
|
94204
|
+
projects(filter: { name: { eq: $name } }, first: 1) {
|
|
94205
|
+
nodes { id }
|
|
94206
|
+
}
|
|
94207
|
+
}`;
|
|
94208
|
+
const data = await linearRequest(apiKey, query, {
|
|
94209
|
+
name
|
|
94210
|
+
});
|
|
94211
|
+
return data.projects.nodes[0]?.id ?? null;
|
|
94212
|
+
}
|
|
94213
|
+
async function setIssueProject(apiKey, issueId, projectId) {
|
|
94214
|
+
const mutation = `mutation SetProject($id: String!, $projectId: String!) {
|
|
94215
|
+
issueUpdate(id: $id, input: { projectId: $projectId }) { success }
|
|
94216
|
+
}`;
|
|
94217
|
+
await linearRequest(apiKey, mutation, {
|
|
94218
|
+
id: issueId,
|
|
94219
|
+
projectId
|
|
94220
|
+
});
|
|
94221
|
+
}
|
|
94084
94222
|
async function createIssue(apiKey, input) {
|
|
94085
94223
|
const mutation = `mutation CreateIssue($input: IssueCreateInput!) {
|
|
94086
94224
|
issueCreate(input: $input) {
|
|
@@ -94917,6 +95055,64 @@ async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
|
|
|
94917
95055
|
}
|
|
94918
95056
|
var init_worktree = () => {};
|
|
94919
95057
|
|
|
95058
|
+
// apps/agent/src/agent/pr-url/index.ts
|
|
95059
|
+
function escapeRegex2(s) {
|
|
95060
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
95061
|
+
}
|
|
95062
|
+
async function discoverPrUrlFromGitHub(identifier, runner, cwd2, onLog) {
|
|
95063
|
+
if (!identifier)
|
|
95064
|
+
return null;
|
|
95065
|
+
const slug = identifier.toLowerCase();
|
|
95066
|
+
let rows;
|
|
95067
|
+
try {
|
|
95068
|
+
const res = await runner.run([
|
|
95069
|
+
"gh",
|
|
95070
|
+
"pr",
|
|
95071
|
+
"list",
|
|
95072
|
+
"--search",
|
|
95073
|
+
`${identifier} in:title`,
|
|
95074
|
+
"--state",
|
|
95075
|
+
"all",
|
|
95076
|
+
"--json",
|
|
95077
|
+
"url,state,headRefName,title,updatedAt"
|
|
95078
|
+
], cwd2);
|
|
95079
|
+
const text = res.stdout.trim();
|
|
95080
|
+
rows = text ? JSON.parse(text) : [];
|
|
95081
|
+
} catch (err) {
|
|
95082
|
+
onLog?.(`! gh pr list (${identifier}) failed: ${err.message}`, "yellow");
|
|
95083
|
+
return null;
|
|
95084
|
+
}
|
|
95085
|
+
const idRe = new RegExp(`\\b${escapeRegex2(identifier)}\\b`, "i");
|
|
95086
|
+
const matches2 = rows.filter((r) => Boolean(r.url) && (idRe.test(r.title ?? "") || (r.headRefName ?? "").toLowerCase().includes(slug)));
|
|
95087
|
+
if (matches2.length === 0)
|
|
95088
|
+
return null;
|
|
95089
|
+
const open = matches2.filter((r) => r.state === "OPEN");
|
|
95090
|
+
const pool = open.length > 0 ? open : matches2;
|
|
95091
|
+
pool.sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""));
|
|
95092
|
+
return pool[0]?.url ?? null;
|
|
95093
|
+
}
|
|
95094
|
+
function createPrUrlCache(ttlMs = 5 * 60 * 1000, now2 = Date.now) {
|
|
95095
|
+
const map3 = new Map;
|
|
95096
|
+
return {
|
|
95097
|
+
get(issueId) {
|
|
95098
|
+
const e = map3.get(issueId);
|
|
95099
|
+
if (!e)
|
|
95100
|
+
return;
|
|
95101
|
+
if (now2() - e.fetchedAt >= ttlMs) {
|
|
95102
|
+
map3.delete(issueId);
|
|
95103
|
+
return;
|
|
95104
|
+
}
|
|
95105
|
+
return e.url;
|
|
95106
|
+
},
|
|
95107
|
+
set(issueId, url2) {
|
|
95108
|
+
map3.set(issueId, { url: url2, fetchedAt: now2() });
|
|
95109
|
+
},
|
|
95110
|
+
invalidate(issueId) {
|
|
95111
|
+
map3.delete(issueId);
|
|
95112
|
+
}
|
|
95113
|
+
};
|
|
95114
|
+
}
|
|
95115
|
+
|
|
94920
95116
|
// apps/agent/src/agent/ci.ts
|
|
94921
95117
|
async function runGhWithRetry(cmd, runner, cwd2, onRetry, sleep2 = (ms) => new Promise((r) => setTimeout(r, ms))) {
|
|
94922
95118
|
let lastErr;
|
|
@@ -94988,6 +95184,14 @@ ${truncated}`);
|
|
|
94988
95184
|
|
|
94989
95185
|
`);
|
|
94990
95186
|
}
|
|
95187
|
+
async function safeSha(getHeadSha) {
|
|
95188
|
+
try {
|
|
95189
|
+
const sha = (await getHeadSha()).trim();
|
|
95190
|
+
return sha || null;
|
|
95191
|
+
} catch {
|
|
95192
|
+
return null;
|
|
95193
|
+
}
|
|
95194
|
+
}
|
|
94991
95195
|
async function fixCiUntilGreen(deps, opts) {
|
|
94992
95196
|
for (let attempt2 = 1;attempt2 <= opts.maxAttempts; attempt2++) {
|
|
94993
95197
|
let pollN = 0;
|
|
@@ -95017,10 +95221,18 @@ async function fixCiUntilGreen(deps, opts) {
|
|
|
95017
95221
|
\`\`\`
|
|
95018
95222
|
${logs}
|
|
95019
95223
|
\`\`\``;
|
|
95224
|
+
const shaBefore = deps.getHeadSha ? await safeSha(deps.getHeadSha) : null;
|
|
95020
95225
|
const code = await deps.runTaskWithSteering(steering);
|
|
95021
95226
|
if (code !== 0) {
|
|
95022
95227
|
deps.log(`! task loop exited code ${code} during CI fix attempt ${attempt2}`, "red");
|
|
95023
95228
|
}
|
|
95229
|
+
if (shaBefore !== null) {
|
|
95230
|
+
const shaAfter = await safeSha(deps.getHeadSha);
|
|
95231
|
+
if (shaAfter !== null && shaAfter === shaBefore) {
|
|
95232
|
+
deps.log(`! worker produced no new commits on CI fix attempt ${attempt2} \u2014 failure looks external (e.g. rate-limited deploy). Giving up CI watch.`, "yellow");
|
|
95233
|
+
return { success: false, attempts: attempt2, reason: "no-progress" };
|
|
95234
|
+
}
|
|
95235
|
+
}
|
|
95024
95236
|
try {
|
|
95025
95237
|
deps.onPhase?.("ci-fix", `attempt ${attempt2}/${opts.maxAttempts} \xB7 pushing fix`);
|
|
95026
95238
|
await deps.pushBranch();
|
|
@@ -95128,6 +95340,33 @@ async function classifyDiffAgainstMeta(runner, cwd2, base2, metaOnlyFiles) {
|
|
|
95128
95340
|
const onlyMeta = files.every((f2) => metaSet.has(f2.replace(/\\/g, "/")));
|
|
95129
95341
|
return { files, onlyMeta };
|
|
95130
95342
|
}
|
|
95343
|
+
async function branchAlreadyMerged(runner, cwd2, branch, base2) {
|
|
95344
|
+
try {
|
|
95345
|
+
const r = await runner.run([
|
|
95346
|
+
"gh",
|
|
95347
|
+
"pr",
|
|
95348
|
+
"list",
|
|
95349
|
+
"--head",
|
|
95350
|
+
branch,
|
|
95351
|
+
"--state",
|
|
95352
|
+
"merged",
|
|
95353
|
+
"--json",
|
|
95354
|
+
"number",
|
|
95355
|
+
"--jq",
|
|
95356
|
+
".[0].number // empty"
|
|
95357
|
+
], cwd2);
|
|
95358
|
+
if (r.stdout.trim() !== "")
|
|
95359
|
+
return true;
|
|
95360
|
+
} catch {}
|
|
95361
|
+
try {
|
|
95362
|
+
const r = await runner.run(["git", "cherry", base2, "HEAD"], cwd2);
|
|
95363
|
+
const lines = r.stdout.split(`
|
|
95364
|
+
`).map((s) => s.trim()).filter(Boolean);
|
|
95365
|
+
if (lines.length > 0 && lines.every((l) => l.startsWith("-")))
|
|
95366
|
+
return true;
|
|
95367
|
+
} catch {}
|
|
95368
|
+
return false;
|
|
95369
|
+
}
|
|
95131
95370
|
async function createPullRequest(input, runner) {
|
|
95132
95371
|
const base2 = input.base ?? "main";
|
|
95133
95372
|
const log2 = await runner.run(["git", "log", "--oneline", `${base2}..HEAD`, "--no-merges"], input.cwd);
|
|
@@ -95137,6 +95376,9 @@ async function createPullRequest(input, runner) {
|
|
|
95137
95376
|
if (metaOnlyFiles.length > 0) {
|
|
95138
95377
|
const classification = await classifyDiffAgainstMeta(runner, input.cwd, base2, metaOnlyFiles);
|
|
95139
95378
|
if (classification.onlyMeta && classification.files.length > 0) {
|
|
95379
|
+
if (await branchAlreadyMerged(runner, input.cwd, input.branch, base2)) {
|
|
95380
|
+
return null;
|
|
95381
|
+
}
|
|
95140
95382
|
return {
|
|
95141
95383
|
url: null,
|
|
95142
95384
|
created: false,
|
|
@@ -95431,6 +95673,10 @@ async function fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict) {
|
|
|
95431
95673
|
pushBranch: async () => {
|
|
95432
95674
|
await ctx.cmd.run(["git", "push", "origin", ctx.branch], ctx.cwd);
|
|
95433
95675
|
},
|
|
95676
|
+
getHeadSha: async () => {
|
|
95677
|
+
const r = await ctx.cmd.run(["git", "rev-parse", "HEAD"], ctx.cwd);
|
|
95678
|
+
return r.stdout.trim();
|
|
95679
|
+
},
|
|
95434
95680
|
log: ctx.log,
|
|
95435
95681
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms))
|
|
95436
95682
|
}, {
|
|
@@ -96259,6 +96505,46 @@ async function pickOpenPrUrlFromAttachments(urls, issueIdent, cmd, cwd2, onLog)
|
|
|
96259
96505
|
}
|
|
96260
96506
|
return { url: null, sawNonOpenPr };
|
|
96261
96507
|
}
|
|
96508
|
+
async function resolveDependencyBaseBranchImpl(issue2, runner, runnerCwd, deps) {
|
|
96509
|
+
const blockerIds = issue2.blockedByIds;
|
|
96510
|
+
if (blockerIds.length === 0)
|
|
96511
|
+
return null;
|
|
96512
|
+
let attachmentsByBlocker;
|
|
96513
|
+
try {
|
|
96514
|
+
attachmentsByBlocker = await fetchAttachmentsForIssues(deps.apiKey, blockerIds);
|
|
96515
|
+
} catch (err) {
|
|
96516
|
+
deps.onLog(`! could not fetch attachments for blockers of ${issue2.identifier}: ${err.message}`, "yellow");
|
|
96517
|
+
return null;
|
|
96518
|
+
}
|
|
96519
|
+
const candidates = [];
|
|
96520
|
+
for (const blockerId of blockerIds) {
|
|
96521
|
+
const attachments = attachmentsByBlocker.get(blockerId) ?? [];
|
|
96522
|
+
const prUrls = attachments.map((a) => a.url).filter((url2) => /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/.test(url2));
|
|
96523
|
+
const openHeads = [];
|
|
96524
|
+
for (const url2 of prUrls) {
|
|
96525
|
+
try {
|
|
96526
|
+
const res = await runner.run(["gh", "pr", "view", url2, "--json", "state,headRefName", "--jq", "."], runnerCwd);
|
|
96527
|
+
const parsed = JSON.parse(res.stdout.trim());
|
|
96528
|
+
if (parsed.state === "OPEN" && parsed.headRefName) {
|
|
96529
|
+
openHeads.push(parsed.headRefName);
|
|
96530
|
+
}
|
|
96531
|
+
} catch (err) {
|
|
96532
|
+
deps.onLog(`! gh pr view failed for ${url2} (blocker of ${issue2.identifier}): ${err.message}`, "yellow");
|
|
96533
|
+
}
|
|
96534
|
+
}
|
|
96535
|
+
if (openHeads.length === 1) {
|
|
96536
|
+
candidates.push(openHeads[0]);
|
|
96537
|
+
} else if (openHeads.length > 1) {
|
|
96538
|
+
deps.onLog(` ${issue2.identifier}: blocker ${blockerId} has ${openHeads.length} open PRs \u2014 skipping dependency base resolution`, "gray");
|
|
96539
|
+
}
|
|
96540
|
+
}
|
|
96541
|
+
if (candidates.length === 1)
|
|
96542
|
+
return candidates[0];
|
|
96543
|
+
if (candidates.length > 1) {
|
|
96544
|
+
deps.onLog(` ${issue2.identifier}: ${candidates.length} blockers have open PRs \u2014 falling back to default base`, "gray");
|
|
96545
|
+
}
|
|
96546
|
+
return null;
|
|
96547
|
+
}
|
|
96262
96548
|
function githubReactionSlug(emoji3) {
|
|
96263
96549
|
switch (emoji3) {
|
|
96264
96550
|
case "\uD83D\uDC40":
|
|
@@ -96333,7 +96619,7 @@ ${c.body.trim()}`;
|
|
|
96333
96619
|
].join(`
|
|
96334
96620
|
`);
|
|
96335
96621
|
}
|
|
96336
|
-
function
|
|
96622
|
+
function escapeRegex3(s) {
|
|
96337
96623
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
96338
96624
|
}
|
|
96339
96625
|
function buildMentionTaskBody(trigger, issueUrl) {
|
|
@@ -96477,6 +96763,16 @@ function buildAgentCoordinator(input) {
|
|
|
96477
96763
|
} else if (m.type === "attachment") {
|
|
96478
96764
|
await upsertRalphyAttachment(apiKey, issue2.id, issue2.url, m.value);
|
|
96479
96765
|
onLog(` \u2192 ${issue2.identifier} attachment='${m.value}'`, "gray");
|
|
96766
|
+
} else if (m.type === "project") {
|
|
96767
|
+
const projectId = await fetchProjectIdByName(apiKey, m.value);
|
|
96768
|
+
if (!projectId) {
|
|
96769
|
+
const err = new Error("Linear project not found");
|
|
96770
|
+
err.project = m.value;
|
|
96771
|
+
err.issue = issue2.identifier;
|
|
96772
|
+
throw err;
|
|
96773
|
+
}
|
|
96774
|
+
await setIssueProject(apiKey, issue2.id, projectId);
|
|
96775
|
+
onLog(` \u2192 ${issue2.identifier} project='${m.value}'`, "gray");
|
|
96480
96776
|
} else {
|
|
96481
96777
|
const id = await resolveLabelId(issue2, m.value);
|
|
96482
96778
|
if (!id) {
|
|
@@ -96527,6 +96823,7 @@ function buildAgentCoordinator(input) {
|
|
|
96527
96823
|
const prByChange = new Map;
|
|
96528
96824
|
const prUnavailable = new Map;
|
|
96529
96825
|
const PR_UNAVAILABLE_TTL_MS = 10 * 60 * 1000;
|
|
96826
|
+
const prUrlByIssue = createPrUrlCache(5 * 60 * 1000);
|
|
96530
96827
|
const stalePingedAt = new Map;
|
|
96531
96828
|
const lastHandledReviewActivity = new Map;
|
|
96532
96829
|
const useWorktree = args.worktree || cfg.useWorktree;
|
|
@@ -96872,6 +97169,9 @@ PR: ${prUrl}` : ""
|
|
|
96872
97169
|
registerPr: (cn, url2) => {
|
|
96873
97170
|
prByChange.set(cn, url2);
|
|
96874
97171
|
prUnavailable.delete(cn);
|
|
97172
|
+
const issue2 = issueByChange.get(cn);
|
|
97173
|
+
if (issue2)
|
|
97174
|
+
prUrlByIssue.invalidate(issue2.id);
|
|
96875
97175
|
input.onWorkerPr?.(cn, url2);
|
|
96876
97176
|
},
|
|
96877
97177
|
...onWorkerPhase && {
|
|
@@ -96929,6 +97229,7 @@ PR: ${prUrl}` : ""
|
|
|
96929
97229
|
}
|
|
96930
97230
|
if (state && state !== "OPEN") {
|
|
96931
97231
|
markPrUnavailable(changeName);
|
|
97232
|
+
prUrlByIssue.invalidate(issue2.id);
|
|
96932
97233
|
return null;
|
|
96933
97234
|
}
|
|
96934
97235
|
if (m && m !== "UNKNOWN") {
|
|
@@ -96966,32 +97267,9 @@ PR: ${prUrl}` : ""
|
|
|
96966
97267
|
prUnavailable.set(changeName, Date.now() + PR_UNAVAILABLE_TTL_MS);
|
|
96967
97268
|
}
|
|
96968
97269
|
async function discoverPrUrl(issue2, changeName) {
|
|
96969
|
-
const
|
|
96970
|
-
|
|
96971
|
-
|
|
96972
|
-
const res = await cmdRunner.run(args2, projectRoot);
|
|
96973
|
-
const found = res.stdout.trim();
|
|
96974
|
-
return found || null;
|
|
96975
|
-
} catch (err) {
|
|
96976
|
-
onLog(`! gh ${args2[1] ?? ""} failed for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
96977
|
-
return null;
|
|
96978
|
-
}
|
|
96979
|
-
};
|
|
96980
|
-
const byBranch = await tryGh([
|
|
96981
|
-
"gh",
|
|
96982
|
-
"pr",
|
|
96983
|
-
"list",
|
|
96984
|
-
"--head",
|
|
96985
|
-
branch,
|
|
96986
|
-
"--state",
|
|
96987
|
-
"open",
|
|
96988
|
-
"--json",
|
|
96989
|
-
"url",
|
|
96990
|
-
"--jq",
|
|
96991
|
-
".[0].url // empty"
|
|
96992
|
-
]);
|
|
96993
|
-
if (byBranch)
|
|
96994
|
-
return byBranch;
|
|
97270
|
+
const fromGitHub = await discoverPrUrlFromGitHub(issue2.identifier, cmdRunner, projectRoot, onLog);
|
|
97271
|
+
if (fromGitHub)
|
|
97272
|
+
return fromGitHub;
|
|
96995
97273
|
const fromLinear = await discoverPrUrlFromLinear(issue2);
|
|
96996
97274
|
if (fromLinear.url) {
|
|
96997
97275
|
onLog(` ${issue2.identifier}: PR discovered via Linear attachment (${fromLinear.url})`, "gray");
|
|
@@ -97001,48 +97279,12 @@ PR: ${prUrl}` : ""
|
|
|
97001
97279
|
markPrUnavailable(changeName);
|
|
97002
97280
|
return null;
|
|
97003
97281
|
}
|
|
97004
|
-
onLog(` ${issue2.identifier}: no
|
|
97282
|
+
onLog(` ${issue2.identifier}: no PR found via GitHub search or Linear attachments; conflict scan skipped for ${PR_UNAVAILABLE_TTL_MS / 60000}m`, "gray");
|
|
97005
97283
|
markPrUnavailable(changeName);
|
|
97006
97284
|
return null;
|
|
97007
97285
|
}
|
|
97008
97286
|
async function resolveDependencyBaseBranch(issue2, runner, runnerCwd) {
|
|
97009
|
-
|
|
97010
|
-
if (blockerIds.length === 0)
|
|
97011
|
-
return null;
|
|
97012
|
-
const candidates = [];
|
|
97013
|
-
for (const blockerId of blockerIds) {
|
|
97014
|
-
let attachments;
|
|
97015
|
-
try {
|
|
97016
|
-
attachments = await fetchIssueAttachments(apiKey, blockerId);
|
|
97017
|
-
} catch (err) {
|
|
97018
|
-
onLog(`! could not fetch attachments for blocker ${blockerId} of ${issue2.identifier}: ${err.message}`, "yellow");
|
|
97019
|
-
continue;
|
|
97020
|
-
}
|
|
97021
|
-
const prUrls = attachments.map((a) => a.url).filter((url2) => /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/.test(url2));
|
|
97022
|
-
const openHeads = [];
|
|
97023
|
-
for (const url2 of prUrls) {
|
|
97024
|
-
try {
|
|
97025
|
-
const res = await runner.run(["gh", "pr", "view", url2, "--json", "state,headRefName", "--jq", "."], runnerCwd);
|
|
97026
|
-
const parsed = JSON.parse(res.stdout.trim());
|
|
97027
|
-
if (parsed.state === "OPEN" && parsed.headRefName) {
|
|
97028
|
-
openHeads.push(parsed.headRefName);
|
|
97029
|
-
}
|
|
97030
|
-
} catch (err) {
|
|
97031
|
-
onLog(`! gh pr view failed for ${url2} (blocker of ${issue2.identifier}): ${err.message}`, "yellow");
|
|
97032
|
-
}
|
|
97033
|
-
}
|
|
97034
|
-
if (openHeads.length === 1) {
|
|
97035
|
-
candidates.push(openHeads[0]);
|
|
97036
|
-
} else if (openHeads.length > 1) {
|
|
97037
|
-
onLog(` ${issue2.identifier}: blocker ${blockerId} has ${openHeads.length} open PRs \u2014 skipping dependency base resolution`, "gray");
|
|
97038
|
-
}
|
|
97039
|
-
}
|
|
97040
|
-
if (candidates.length === 1)
|
|
97041
|
-
return candidates[0];
|
|
97042
|
-
if (candidates.length > 1) {
|
|
97043
|
-
onLog(` ${issue2.identifier}: ${candidates.length} blockers have open PRs \u2014 falling back to default base`, "gray");
|
|
97044
|
-
}
|
|
97045
|
-
return null;
|
|
97287
|
+
return resolveDependencyBaseBranchImpl(issue2, runner, runnerCwd, { apiKey, onLog });
|
|
97046
97288
|
}
|
|
97047
97289
|
async function discoverPrUrlFromLinear(issue2) {
|
|
97048
97290
|
let attachments;
|
|
@@ -97090,17 +97332,7 @@ PR: ${prUrl}` : ""
|
|
|
97090
97332
|
onLog(`! mention scan: rate limited, deferring rest of scan to next poll`, "yellow");
|
|
97091
97333
|
};
|
|
97092
97334
|
for (const issue2 of candidates) {
|
|
97093
|
-
|
|
97094
|
-
try {
|
|
97095
|
-
comments = await fetchIssueComments(apiKey, issue2.id);
|
|
97096
|
-
} catch (err) {
|
|
97097
|
-
if (isRateLimitedError(err)) {
|
|
97098
|
-
logRateLimited();
|
|
97099
|
-
break;
|
|
97100
|
-
}
|
|
97101
|
-
onLog(`! mention scan: Linear comments failed for ${issue2.identifier}: ${formatLinearError(err)}`, "yellow");
|
|
97102
|
-
continue;
|
|
97103
|
-
}
|
|
97335
|
+
const comments = issue2.comments ?? [];
|
|
97104
97336
|
const lastRalphPickup = findLastRalphPickupISO(comments);
|
|
97105
97337
|
if (wantMention) {
|
|
97106
97338
|
for (const c of comments) {
|
|
@@ -97322,17 +97554,21 @@ PR: ${prUrl}` : ""
|
|
|
97322
97554
|
return latest;
|
|
97323
97555
|
}
|
|
97324
97556
|
function containsHandle(body, handle) {
|
|
97325
|
-
const re = new RegExp(`(^|\\s|[^A-Za-z0-9_])${
|
|
97557
|
+
const re = new RegExp(`(^|\\s|[^A-Za-z0-9_])${escapeRegex3(handle)}\\b`, "i");
|
|
97326
97558
|
return re.test(body);
|
|
97327
97559
|
}
|
|
97328
97560
|
async function resolvePrUrlForIssue(issue2) {
|
|
97329
97561
|
const changeName = changeNameForIssue(issue2);
|
|
97330
97562
|
if (isPrUnavailable(changeName))
|
|
97331
97563
|
return null;
|
|
97332
|
-
const
|
|
97333
|
-
if (
|
|
97564
|
+
const inflight = prByChange.get(changeName);
|
|
97565
|
+
if (inflight)
|
|
97566
|
+
return inflight;
|
|
97567
|
+
const cached2 = prUrlByIssue.get(issue2.id);
|
|
97568
|
+
if (cached2 !== undefined)
|
|
97334
97569
|
return cached2;
|
|
97335
97570
|
const found = await discoverPrUrl(issue2, changeName);
|
|
97571
|
+
prUrlByIssue.set(issue2.id, found);
|
|
97336
97572
|
if (found)
|
|
97337
97573
|
prByChange.set(changeName, found);
|
|
97338
97574
|
return found;
|
|
@@ -99309,7 +99545,7 @@ function bucketChecks(rollup, prState) {
|
|
|
99309
99545
|
return "fail";
|
|
99310
99546
|
return "pass";
|
|
99311
99547
|
}
|
|
99312
|
-
async function fetchPrStatus(url2, runner, cwd2) {
|
|
99548
|
+
async function fetchPrStatus(url2, runner, cwd2, transition) {
|
|
99313
99549
|
let stdout;
|
|
99314
99550
|
try {
|
|
99315
99551
|
const out = await runner.run(["gh", "pr", "view", url2, "--json", PR_VIEW_FIELDS], cwd2);
|
|
@@ -99330,6 +99566,9 @@ async function fetchPrStatus(url2, runner, cwd2) {
|
|
|
99330
99566
|
const state = stateUpper === "OPEN" || stateUpper === "CLOSED" || stateUpper === "MERGED" ? stateUpper : "OPEN";
|
|
99331
99567
|
const mergeableUpper = (raw.mergeable ?? "UNKNOWN").toUpperCase();
|
|
99332
99568
|
const mergeable = mergeableUpper === "MERGEABLE" || mergeableUpper === "CONFLICTING" ? mergeableUpper : "UNKNOWN";
|
|
99569
|
+
if (transition && transition.priorState !== state) {
|
|
99570
|
+
transition.onTransition(state);
|
|
99571
|
+
}
|
|
99333
99572
|
return {
|
|
99334
99573
|
kind: "ok",
|
|
99335
99574
|
state,
|
|
@@ -99583,10 +99822,20 @@ ${bucket.label}: error fetching from Linear \u2014 ${error48}
|
|
|
99583
99822
|
}
|
|
99584
99823
|
}
|
|
99585
99824
|
const rows = [...seen.values()];
|
|
99825
|
+
try {
|
|
99826
|
+
const attachmentsByIssue = await fetchAttachmentsForIssues(apiKey, rows.map((r) => r.issueId));
|
|
99827
|
+
for (const row of rows) {
|
|
99828
|
+
const attachments = attachmentsByIssue.get(row.issueId) ?? [];
|
|
99829
|
+
row.prUrl = findPullRequestUrl(attachments);
|
|
99830
|
+
}
|
|
99831
|
+
} catch {}
|
|
99586
99832
|
await Promise.all(rows.map(async (row) => {
|
|
99833
|
+
if (row.prUrl)
|
|
99834
|
+
return;
|
|
99587
99835
|
try {
|
|
99588
|
-
const
|
|
99589
|
-
|
|
99836
|
+
const fromGitHub = await discoverPrUrlFromGitHub(row.identifier, runner, cwd2);
|
|
99837
|
+
if (fromGitHub)
|
|
99838
|
+
row.prUrl = fromGitHub;
|
|
99590
99839
|
} catch {}
|
|
99591
99840
|
}));
|
|
99592
99841
|
await Promise.all(rows.map(async (row) => {
|