@neriros/ralphy 3.10.15 → 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.
- package/dist/shell/index.js +755 -93
- package/package.json +2 -2
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.10.
|
|
18932
|
-
return "3.10.
|
|
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 =
|
|
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:
|
|
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
|
-
//
|
|
102604
|
-
function
|
|
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 (
|
|
102787
|
+
if (trimmed.startsWith(RALPHY_BRAND))
|
|
102788
|
+
return true;
|
|
102789
|
+
if (parseRalphyMarker(body))
|
|
102790
|
+
return true;
|
|
102791
|
+
if (LEGACY_RALPH_LEAD.test(trimmed))
|
|
102607
102792
|
return true;
|
|
102608
|
-
return
|
|
102793
|
+
return LEGACY_MENTION_ACK.test(trimmed);
|
|
102609
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")
|
|
102807
|
+
return true;
|
|
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);
|
|
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
|
-
|
|
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
|
|
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
|
|
105509
|
-
|
|
105510
|
-
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
|
|
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(
|
|
106708
|
-
const
|
|
106709
|
-
|
|
106710
|
-
|
|
106711
|
-
|
|
106712
|
-
|
|
106713
|
-
|
|
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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) {
|
|
@@ -107194,33 +107559,39 @@ async function processAwaitingForIssue(issue2, deps) {
|
|
|
107194
107559
|
return false;
|
|
107195
107560
|
}
|
|
107196
107561
|
if (!hasUnchecked(tasks2 ?? "")) {
|
|
107197
|
-
deps.awaitingChangeSet.delete(changeName);
|
|
107198
|
-
await releaseAwaitingMarker(issue2, statePath, {
|
|
107562
|
+
const wasTracked = deps.awaitingChangeSet.delete(changeName);
|
|
107563
|
+
const released = await releaseAwaitingMarker(issue2, statePath, {
|
|
107199
107564
|
indicators,
|
|
107200
107565
|
applyIndicator: deps.applyIndicator,
|
|
107201
107566
|
onLog: deps.onLog
|
|
107202
107567
|
});
|
|
107203
|
-
|
|
107568
|
+
if (wasTracked || released) {
|
|
107569
|
+
deps.onLog(` ${issue2.identifier}: confirmation detect released \u2014 tasks-empty`);
|
|
107570
|
+
}
|
|
107204
107571
|
return false;
|
|
107205
107572
|
}
|
|
107206
107573
|
if (!planningComplete(tasks2 ?? "")) {
|
|
107207
|
-
deps.awaitingChangeSet.delete(changeName);
|
|
107208
|
-
await releaseAwaitingMarker(issue2, statePath, {
|
|
107574
|
+
const wasTracked = deps.awaitingChangeSet.delete(changeName);
|
|
107575
|
+
const released = await releaseAwaitingMarker(issue2, statePath, {
|
|
107209
107576
|
indicators,
|
|
107210
107577
|
applyIndicator: deps.applyIndicator,
|
|
107211
107578
|
onLog: deps.onLog
|
|
107212
107579
|
});
|
|
107213
|
-
|
|
107580
|
+
if (wasTracked || released) {
|
|
107581
|
+
deps.onLog(` ${issue2.identifier}: confirmation detect released \u2014 planning-incomplete`);
|
|
107582
|
+
}
|
|
107214
107583
|
return false;
|
|
107215
107584
|
}
|
|
107216
107585
|
if (isStubArtifact(proposal) || isStubArtifact(design)) {
|
|
107217
|
-
deps.awaitingChangeSet.delete(changeName);
|
|
107218
|
-
await releaseAwaitingMarker(issue2, statePath, {
|
|
107586
|
+
const wasTracked = deps.awaitingChangeSet.delete(changeName);
|
|
107587
|
+
const released = await releaseAwaitingMarker(issue2, statePath, {
|
|
107219
107588
|
indicators,
|
|
107220
107589
|
applyIndicator: deps.applyIndicator,
|
|
107221
107590
|
onLog: deps.onLog
|
|
107222
107591
|
});
|
|
107223
|
-
|
|
107592
|
+
if (wasTracked || released) {
|
|
107593
|
+
deps.onLog(` ${issue2.identifier}: confirmation detect released \u2014 proposal/design not yet filled in`);
|
|
107594
|
+
}
|
|
107224
107595
|
return false;
|
|
107225
107596
|
}
|
|
107226
107597
|
deps.awaitingChangeSet.add(changeName);
|
|
@@ -107301,11 +107672,13 @@ async function processAwaitingForIssue(issue2, deps) {
|
|
|
107301
107672
|
}
|
|
107302
107673
|
var init_awaiting = __esm(() => {
|
|
107303
107674
|
init_layout();
|
|
107675
|
+
init_src8();
|
|
107304
107676
|
init_detections();
|
|
107305
107677
|
init_phase();
|
|
107306
107678
|
init_worktree();
|
|
107307
107679
|
init_scaffold();
|
|
107308
107680
|
init_linear();
|
|
107681
|
+
init_ralph_comment();
|
|
107309
107682
|
init_types2();
|
|
107310
107683
|
init_workflow();
|
|
107311
107684
|
init_state2();
|
|
@@ -107532,10 +107905,13 @@ function unionMarkers(...sets) {
|
|
|
107532
107905
|
}
|
|
107533
107906
|
return out;
|
|
107534
107907
|
}
|
|
107535
|
-
function describeIndicators(indicators, team, assignee, anyAssignee) {
|
|
107908
|
+
function describeIndicators(indicators, team, assignee, anyAssignee, requireAllLabels) {
|
|
107536
107909
|
const parts = [];
|
|
107537
107910
|
parts.push(`team=${team ?? "*"}`);
|
|
107538
107911
|
parts.push(`assignee=${anyAssignee ? "any" : assignee ?? "*"}`);
|
|
107912
|
+
if (requireAllLabels && requireAllLabels.length > 0) {
|
|
107913
|
+
parts.push(`labels=[${requireAllLabels.join(",")}]`);
|
|
107914
|
+
}
|
|
107539
107915
|
if (indicators.getTodo) {
|
|
107540
107916
|
parts.push(`todo=[${indicators.getTodo.filter.map((m) => `${m.type}:${m.value}`).join(",")}]`);
|
|
107541
107917
|
}
|
|
@@ -107555,7 +107931,7 @@ function createLinearResolvers(input) {
|
|
|
107555
107931
|
const stateCache = new Map;
|
|
107556
107932
|
const labelCache = new Map;
|
|
107557
107933
|
const teamIdCache = new Map;
|
|
107558
|
-
const teamKeyOf = (issue2) =>
|
|
107934
|
+
const teamKeyOf = (issue2) => linearIdentifierStrategy.scopeKey(issue2);
|
|
107559
107935
|
async function resolveStateId(issue2, name) {
|
|
107560
107936
|
const t = teamKeyOf(issue2);
|
|
107561
107937
|
let map3 = stateCache.get(t);
|
|
@@ -107601,6 +107977,22 @@ function createLinearResolvers(input) {
|
|
|
107601
107977
|
return null;
|
|
107602
107978
|
}
|
|
107603
107979
|
}
|
|
107980
|
+
async function stripSiblingGroupLabels(issue2, group, keepId) {
|
|
107981
|
+
const map3 = labelCache.get(teamKeyOf(issue2));
|
|
107982
|
+
if (!map3)
|
|
107983
|
+
return;
|
|
107984
|
+
for (const name of issue2.labels) {
|
|
107985
|
+
const siblingId = map3.get(`${group}:${name}`.toLowerCase());
|
|
107986
|
+
if (!siblingId || siblingId === keepId)
|
|
107987
|
+
continue;
|
|
107988
|
+
try {
|
|
107989
|
+
await removeLabelFromIssue(apiKey, issue2.id, siblingId);
|
|
107990
|
+
diag("linear-marker", ` \u2192 ${issue2.identifier} -label='${group}:${name}' (group swap)`, "gray");
|
|
107991
|
+
} catch (err) {
|
|
107992
|
+
diag("linear-marker", `! could not remove sibling label '${group}:${name}' from ${issue2.identifier}: ${err.message}`, "yellow");
|
|
107993
|
+
}
|
|
107994
|
+
}
|
|
107995
|
+
}
|
|
107604
107996
|
async function applyMarker(issue2, m) {
|
|
107605
107997
|
if (m.type === "status") {
|
|
107606
107998
|
const id = await resolveStateId(issue2, m.value);
|
|
@@ -107634,6 +108026,8 @@ function createLinearResolvers(input) {
|
|
|
107634
108026
|
err.issue = issue2.identifier;
|
|
107635
108027
|
throw err;
|
|
107636
108028
|
}
|
|
108029
|
+
if (m.group)
|
|
108030
|
+
await stripSiblingGroupLabels(issue2, m.group, id);
|
|
107637
108031
|
await addLabelToIssue(apiKey, issue2.id, id);
|
|
107638
108032
|
diag("linear-marker", ` \u2192 ${issue2.identifier} +label='${display}'`, "gray");
|
|
107639
108033
|
}
|
|
@@ -107733,6 +108127,201 @@ async function fetchDoneCandidatesWith(apiKey, team, assignee, anyAssignee, requ
|
|
|
107733
108127
|
var init_linear_resolvers = __esm(() => {
|
|
107734
108128
|
init_types2();
|
|
107735
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();
|
|
107736
108325
|
});
|
|
107737
108326
|
|
|
107738
108327
|
// apps/agent/src/agent/wire/prepare.ts
|
|
@@ -108347,7 +108936,12 @@ async function maybePingStaleReviewer(issue2, prUrl, state, newestReviewerActivi
|
|
|
108347
108936
|
if (!m)
|
|
108348
108937
|
return;
|
|
108349
108938
|
const [, owner, repo, num] = m;
|
|
108350
|
-
const body =
|
|
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
|
+
});
|
|
108351
108945
|
try {
|
|
108352
108946
|
await deps.cmdRunner.run(["gh", "api", `repos/${owner}/${repo}/issues/${num}/comments`, "-f", `body=${body}`], deps.projectRoot);
|
|
108353
108947
|
deps.stalePingedAt.set(prUrl, now2);
|
|
@@ -108413,6 +109007,7 @@ async function scanCodeReview(issue2, prUrl, lastRalphPickup, deps) {
|
|
|
108413
109007
|
var init_scan = __esm(() => {
|
|
108414
109008
|
init_layout();
|
|
108415
109009
|
init_store();
|
|
109010
|
+
init_src8();
|
|
108416
109011
|
init_scaffold();
|
|
108417
109012
|
init_worktree();
|
|
108418
109013
|
});
|
|
@@ -108462,7 +109057,7 @@ async function fetchPrIssueComments(cmdRunner, projectRoot, prUrl, onLog) {
|
|
|
108462
109057
|
return [];
|
|
108463
109058
|
}
|
|
108464
109059
|
}
|
|
108465
|
-
var
|
|
109060
|
+
var init_github2 = __esm(() => {
|
|
108466
109061
|
init_linear();
|
|
108467
109062
|
init_task_bodies();
|
|
108468
109063
|
});
|
|
@@ -108470,6 +109065,19 @@ var init_github = __esm(() => {
|
|
|
108470
109065
|
// apps/agent/src/agent/wire/mention-scan.ts
|
|
108471
109066
|
import { readdir as readdir2 } from "fs/promises";
|
|
108472
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
|
+
}
|
|
108473
109081
|
function createMentionScanner(input) {
|
|
108474
109082
|
const {
|
|
108475
109083
|
apiKey,
|
|
@@ -108531,13 +109139,14 @@ function createMentionScanner(input) {
|
|
|
108531
109139
|
for (const issue2 of candidates) {
|
|
108532
109140
|
const comments = issue2.comments ?? [];
|
|
108533
109141
|
const lastRalphPickup = findLastRalphPickupISO(comments);
|
|
109142
|
+
const linearMentionGate = latestIso(lastRalphPickup, findLastMentionAckISO(comments));
|
|
108534
109143
|
if (wantMention) {
|
|
108535
109144
|
for (const c of comments) {
|
|
108536
109145
|
if (isRalphComment(c.body))
|
|
108537
109146
|
continue;
|
|
108538
109147
|
if (!containsHandle(c.body, handle))
|
|
108539
109148
|
continue;
|
|
108540
|
-
if (
|
|
109149
|
+
if (linearMentionGate && c.createdAt <= linearMentionGate)
|
|
108541
109150
|
continue;
|
|
108542
109151
|
out.push({
|
|
108543
109152
|
issue: issue2,
|
|
@@ -108557,7 +109166,9 @@ function createMentionScanner(input) {
|
|
|
108557
109166
|
queued.add(issue2.id);
|
|
108558
109167
|
break;
|
|
108559
109168
|
}
|
|
108560
|
-
|
|
109169
|
+
if (!isAlreadyReactedError(err)) {
|
|
109170
|
+
diag("mention", `! mention scan: Linear reaction failed for ${issue2.identifier}: ${formatLinearError(err)}`, "yellow");
|
|
109171
|
+
}
|
|
108561
109172
|
}
|
|
108562
109173
|
if (cfg.linear.postComments !== false) {
|
|
108563
109174
|
try {
|
|
@@ -108581,11 +109192,14 @@ function createMentionScanner(input) {
|
|
|
108581
109192
|
continue;
|
|
108582
109193
|
if (wantMention) {
|
|
108583
109194
|
const ghComments = await fetchPrIssueComments(cmdRunner, projectRoot, prUrl, onLog);
|
|
109195
|
+
const ghMentionGate = latestIso(lastRalphPickup, findLastMentionAckISO(ghComments));
|
|
108584
109196
|
const prMatch = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/\d+/.exec(prUrl);
|
|
108585
109197
|
for (const c of ghComments) {
|
|
109198
|
+
if (isRalphComment(c.body))
|
|
109199
|
+
continue;
|
|
108586
109200
|
if (!containsHandle(c.body, handle))
|
|
108587
109201
|
continue;
|
|
108588
|
-
if (
|
|
109202
|
+
if (ghMentionGate && c.createdAt <= ghMentionGate)
|
|
108589
109203
|
continue;
|
|
108590
109204
|
out.push({
|
|
108591
109205
|
issue: issue2,
|
|
@@ -108602,7 +109216,9 @@ function createMentionScanner(input) {
|
|
|
108602
109216
|
try {
|
|
108603
109217
|
await addGithubReactionToComment(cmdRunner, projectRoot, { owner, repo, kind: "issue" }, c.id, "\uD83D\uDC40");
|
|
108604
109218
|
} catch (err) {
|
|
108605
|
-
|
|
109219
|
+
if (!isAlreadyReactedError(err)) {
|
|
109220
|
+
diag("mention", `! mention scan: GitHub reaction failed for ${prUrl}: ${formatLinearError(err)}`, "yellow");
|
|
109221
|
+
}
|
|
108606
109222
|
}
|
|
108607
109223
|
if (cfg.linear.postComments !== false) {
|
|
108608
109224
|
await postGithubPrComment(cmdRunner, projectRoot, prUrl, buildMentionAckComment(c.body, c.author), onLog);
|
|
@@ -108656,7 +109272,7 @@ var init_mention_scan = __esm(() => {
|
|
|
108656
109272
|
init_detections();
|
|
108657
109273
|
init_scaffold();
|
|
108658
109274
|
init_scan();
|
|
108659
|
-
|
|
109275
|
+
init_github2();
|
|
108660
109276
|
init_task_bodies();
|
|
108661
109277
|
});
|
|
108662
109278
|
|
|
@@ -109664,7 +110280,12 @@ async function readTasksMd(changeDir, log3) {
|
|
|
109664
110280
|
}
|
|
109665
110281
|
}
|
|
109666
110282
|
function renderTasksCommentBody(tasksMd, changeName, iteration) {
|
|
109667
|
-
return
|
|
110283
|
+
return buildRalphyComment({
|
|
110284
|
+
type: "tasks",
|
|
110285
|
+
action: "task progress",
|
|
110286
|
+
body: renderTasksBlock(tasksMd, { changeName, iteration }),
|
|
110287
|
+
fields: { change: changeName, iter: iteration }
|
|
110288
|
+
});
|
|
109668
110289
|
}
|
|
109669
110290
|
async function postOrUpdateTasksComment(deps) {
|
|
109670
110291
|
const tasksMd = await readTasksMd(deps.changeDir, deps.log);
|
|
@@ -109775,8 +110396,13 @@ async function postPlanCommentOnce(deps) {
|
|
|
109775
110396
|
if (designSummary) {
|
|
109776
110397
|
parts.push("", "**Design**", "", designSummary);
|
|
109777
110398
|
}
|
|
109778
|
-
const body =
|
|
109779
|
-
|
|
110399
|
+
const body = buildRalphyComment({
|
|
110400
|
+
type: "plan",
|
|
110401
|
+
action: "plan",
|
|
110402
|
+
body: parts.join(`
|
|
110403
|
+
`),
|
|
110404
|
+
fields: { change: deps.changeName }
|
|
110405
|
+
});
|
|
109780
110406
|
let id;
|
|
109781
110407
|
try {
|
|
109782
110408
|
id = await deps.mutations.createIssueComment(deps.apiKey, deps.issueId, body);
|
|
@@ -109793,9 +110419,14 @@ async function postPlanCommentOnce(deps) {
|
|
|
109793
110419
|
}
|
|
109794
110420
|
async function postSteeringAndRefreshTasks(deps) {
|
|
109795
110421
|
const firstLine = deps.message.split(/\r?\n/, 1)[0].trim() || deps.message.trim();
|
|
109796
|
-
const steeringBody =
|
|
110422
|
+
const steeringBody = buildRalphyComment({
|
|
110423
|
+
type: "steering",
|
|
110424
|
+
action: "steering",
|
|
110425
|
+
body: `### ${STEERING_COMMENT_TITLE}
|
|
109797
110426
|
|
|
109798
|
-
${deps.message.trim()}
|
|
110427
|
+
${deps.message.trim()}`,
|
|
110428
|
+
fields: { change: deps.changeName }
|
|
110429
|
+
});
|
|
109799
110430
|
try {
|
|
109800
110431
|
await deps.mutations.createIssueComment(deps.apiKey, deps.issueId, steeringBody);
|
|
109801
110432
|
deps.log(` comment-sync: posted steering comment (${firstLine})`, "gray");
|
|
@@ -109828,6 +110459,7 @@ ${deps.message.trim()}`;
|
|
|
109828
110459
|
var PLAN_COMMENT_TITLE = "\uD83D\uDCCB Ralph plan", STEERING_COMMENT_TITLE = "\uD83E\uDDED Ralph steering";
|
|
109829
110460
|
var init_comment_sync = __esm(() => {
|
|
109830
110461
|
init_store();
|
|
110462
|
+
init_src8();
|
|
109831
110463
|
init_linear_sync();
|
|
109832
110464
|
});
|
|
109833
110465
|
|
|
@@ -262694,11 +263326,12 @@ function buildAgentCoordinator(input) {
|
|
|
262694
263326
|
};
|
|
262695
263327
|
const concurrency = args.concurrency || cfg.concurrency;
|
|
262696
263328
|
const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
|
|
262697
|
-
const
|
|
263329
|
+
const isGithubTracker = cfg.tracker.kind === "github";
|
|
263330
|
+
const indicators = isGithubTracker ? githubIndicators(cfg.github?.issues) : mergeIndicators(cfg.linear.indicators, args.indicators);
|
|
262698
263331
|
const team = args.linearTeam || cfg.linear.team;
|
|
262699
263332
|
const { assignee, anyAssignee, requireAllLabels } = resolveLinearFilter(applyAssigneeOverride(cfg.linear.filter, args.linearAssignee));
|
|
262700
263333
|
const ticketNumbers = resolveTicketNumbers(args.ticketTokens, team);
|
|
262701
|
-
const excludeFromTodo = unionMarkers(indicators.setDone, indicators.setError);
|
|
263334
|
+
const excludeFromTodo = isGithubTracker ? unionMarkers(indicators.setDone, indicators.setError, indicators.setInProgress) : unionMarkers(indicators.setDone, indicators.setError);
|
|
262702
263335
|
const gitRunner = input.runners?.git ?? bunGitRunner;
|
|
262703
263336
|
const cmdRunner = input.runners?.cmd ?? bunCmdRunner;
|
|
262704
263337
|
const cwdByChange = new Map;
|
|
@@ -262733,7 +263366,7 @@ function buildAgentCoordinator(input) {
|
|
|
262733
263366
|
}
|
|
262734
263367
|
return code;
|
|
262735
263368
|
});
|
|
262736
|
-
const resolvers = createLinearResolvers({
|
|
263369
|
+
const resolvers = isGithubTracker ? null : createLinearResolvers({
|
|
262737
263370
|
apiKey,
|
|
262738
263371
|
team,
|
|
262739
263372
|
assignee,
|
|
@@ -262742,6 +263375,15 @@ function buildAgentCoordinator(input) {
|
|
|
262742
263375
|
diag,
|
|
262743
263376
|
...ticketNumbers.length > 0 ? { ticketNumbers } : {}
|
|
262744
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
|
+
};
|
|
262745
263387
|
if (ticketNumbers.length > 0) {
|
|
262746
263388
|
const hasGetIndicator = [indicators.getTodo, indicators.getInProgress].some((ind) => ind && ind.filter.length > 0);
|
|
262747
263389
|
if (!hasGetIndicator) {
|
|
@@ -262772,7 +263414,7 @@ function buildAgentCoordinator(input) {
|
|
|
262772
263414
|
scriptRunner,
|
|
262773
263415
|
...input.runners?.worktree ? { worktreeProvider: input.runners.worktree } : {}
|
|
262774
263416
|
});
|
|
262775
|
-
const fetchMentions = createMentionScanner({
|
|
263417
|
+
const fetchMentions = isGithubTracker ? async () => [] : createMentionScanner({
|
|
262776
263418
|
apiKey,
|
|
262777
263419
|
args,
|
|
262778
263420
|
cfg,
|
|
@@ -262792,6 +263434,27 @@ function buildAgentCoordinator(input) {
|
|
|
262792
263434
|
lastHandledReviewActivity,
|
|
262793
263435
|
resolvePrUrlForIssue: prDiscovery.resolvePrUrlForIssue
|
|
262794
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
|
+
});
|
|
262795
263458
|
const spawnWorker = createSpawnWorker({
|
|
262796
263459
|
args,
|
|
262797
263460
|
cfg,
|
|
@@ -262803,7 +263466,7 @@ function buildAgentCoordinator(input) {
|
|
|
262803
263466
|
indicators,
|
|
262804
263467
|
cmdRunner,
|
|
262805
263468
|
gitRunner,
|
|
262806
|
-
applyIndicator:
|
|
263469
|
+
applyIndicator: provider.applyIndicator,
|
|
262807
263470
|
bus,
|
|
262808
263471
|
onLog,
|
|
262809
263472
|
diag,
|
|
@@ -262847,8 +263510,8 @@ function buildAgentCoordinator(input) {
|
|
|
262847
263510
|
cwdOf: (cn) => cwdByChange.get(cn),
|
|
262848
263511
|
awaitingChangeSet,
|
|
262849
263512
|
reapForAwaiting: (cn) => coordRef.current?.reapForAwaiting(cn),
|
|
262850
|
-
applyIndicator:
|
|
262851
|
-
applyMarker:
|
|
263513
|
+
applyIndicator: provider.applyIndicator,
|
|
263514
|
+
applyMarker: provider.applyMarker,
|
|
262852
263515
|
openDraftPr,
|
|
262853
263516
|
...onAwaitingTicket ? { onAwaitingTicket } : {},
|
|
262854
263517
|
onLog
|
|
@@ -262878,7 +263541,7 @@ function buildAgentCoordinator(input) {
|
|
|
262878
263541
|
projectRoot,
|
|
262879
263542
|
maxRecoveryAttempts: cfg.prRecovery.maxRecoverySessions
|
|
262880
263543
|
}) : null;
|
|
262881
|
-
const commentSync = createCommentSyncHooks({
|
|
263544
|
+
const commentSync = isGithubTracker ? { enabled: false } : createCommentSyncHooks({
|
|
262882
263545
|
apiKey,
|
|
262883
263546
|
cfg,
|
|
262884
263547
|
projectRoot,
|
|
@@ -262891,20 +263554,18 @@ function buildAgentCoordinator(input) {
|
|
|
262891
263554
|
beforePoll: () => {
|
|
262892
263555
|
pollContext = new PollContext;
|
|
262893
263556
|
},
|
|
262894
|
-
fetchTodo:
|
|
262895
|
-
fetchInProgress:
|
|
262896
|
-
fetchMentions,
|
|
262897
|
-
fetchDoneCandidates:
|
|
263557
|
+
fetchTodo: tracker.fetchTodo,
|
|
263558
|
+
fetchInProgress: tracker.fetchInProgress,
|
|
263559
|
+
fetchMentions: tracker.fetchMentions,
|
|
263560
|
+
fetchDoneCandidates: tracker.fetchDoneCandidates,
|
|
263561
|
+
fetchReview: tracker.fetchReview,
|
|
262898
263562
|
prepare: prep.prepare,
|
|
262899
263563
|
prepareTaskForTrigger: prep.prepareTaskForTrigger,
|
|
262900
263564
|
spawnWorker,
|
|
262901
|
-
applyIndicator:
|
|
262902
|
-
removeIndicator:
|
|
262903
|
-
postComment:
|
|
262904
|
-
fetchComments:
|
|
262905
|
-
const c = await fetchIssueComments(apiKey, issueId);
|
|
262906
|
-
return c.map((x2) => ({ body: x2.body }));
|
|
262907
|
-
},
|
|
263565
|
+
applyIndicator: tracker.applyIndicator,
|
|
263566
|
+
removeIndicator: tracker.removeIndicator,
|
|
263567
|
+
postComment: tracker.postComment,
|
|
263568
|
+
fetchComments: tracker.fetchComments,
|
|
262908
263569
|
checkPrStatus: prDiscovery.checkPrStatus,
|
|
262909
263570
|
hasPrForChange: (changeName) => prByChange.has(changeName),
|
|
262910
263571
|
isChangeArchivedForIssue: (issue2) => isChangeArchivedForIssue(issue2, cwdByChange, projectRoot),
|
|
@@ -262952,7 +263613,7 @@ function buildAgentCoordinator(input) {
|
|
|
262952
263613
|
}
|
|
262953
263614
|
});
|
|
262954
263615
|
coordRef.current = coord;
|
|
262955
|
-
const filterDesc = describeIndicators(indicators, team, assignee, anyAssignee);
|
|
263616
|
+
const filterDesc = describeIndicators(indicators, team, assignee, anyAssignee, requireAllLabels);
|
|
262956
263617
|
const runBaselineGateOnce = createBaselineGateRunner({
|
|
262957
263618
|
args,
|
|
262958
263619
|
cfg,
|
|
@@ -262963,7 +263624,7 @@ function buildAgentCoordinator(input) {
|
|
|
262963
263624
|
gitRunner,
|
|
262964
263625
|
coord,
|
|
262965
263626
|
onLog,
|
|
262966
|
-
resolveLabelIdForTeam:
|
|
263627
|
+
resolveLabelIdForTeam: provider.resolveLabelIdForTeam
|
|
262967
263628
|
});
|
|
262968
263629
|
return {
|
|
262969
263630
|
coord,
|
|
@@ -262991,7 +263652,6 @@ var init_wire = __esm(() => {
|
|
|
262991
263652
|
init_workflow();
|
|
262992
263653
|
init_src2();
|
|
262993
263654
|
init_coordinator2();
|
|
262994
|
-
init_linear();
|
|
262995
263655
|
init_layout();
|
|
262996
263656
|
init_scaffold();
|
|
262997
263657
|
init_awaiting();
|
|
@@ -263000,6 +263660,8 @@ var init_wire = __esm(() => {
|
|
|
263000
263660
|
init_indicators();
|
|
263001
263661
|
init_task_bodies();
|
|
263002
263662
|
init_linear_resolvers();
|
|
263663
|
+
init_github();
|
|
263664
|
+
init_linear_tracker_provider();
|
|
263003
263665
|
init_linear_client();
|
|
263004
263666
|
init_prepare();
|
|
263005
263667
|
init_pr_discovery();
|
|
@@ -266163,7 +266825,7 @@ async function main3(argv) {
|
|
|
266163
266825
|
return typeof process.exitCode === "number" ? process.exitCode : 0;
|
|
266164
266826
|
}
|
|
266165
266827
|
var import_react64;
|
|
266166
|
-
var
|
|
266828
|
+
var init_src9 = __esm(async () => {
|
|
266167
266829
|
init_context();
|
|
266168
266830
|
init_layout();
|
|
266169
266831
|
init_paths();
|
|
@@ -266221,7 +266883,7 @@ async function dispatch(subcommand, rest2) {
|
|
|
266221
266883
|
return main4(rest2);
|
|
266222
266884
|
}
|
|
266223
266885
|
if (subcommand === "agent") {
|
|
266224
|
-
const { main: main4 } = await
|
|
266886
|
+
const { main: main4 } = await init_src9().then(() => exports_src3);
|
|
266225
266887
|
return main4(rest2);
|
|
266226
266888
|
}
|
|
266227
266889
|
if (subcommand === "task") {
|