@neriros/ralphy 2.17.3 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/cli/index.js +128 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -173,6 +173,7 @@ A default `ralphy.config.json` is written on first run with every defaulted sett
|
|
|
173
173
|
"getTodo": { "filter": [{ "type": "status", "value": "Todo" }] },
|
|
174
174
|
"getInProgress": { "filter": [{ "type": "status", "value": "In Progress" }] },
|
|
175
175
|
"getConflicted": { "filter": [{ "type": "label", "value": "ralph:conflicted" }] },
|
|
176
|
+
"getReview": { "filter": [{ "type": "label", "value": "ralph:review" }] },
|
|
176
177
|
"setInProgress": { "type": "status", "value": "In Progress" },
|
|
177
178
|
"setDone": {
|
|
178
179
|
"apply": [
|
|
@@ -183,6 +184,7 @@ A default `ralphy.config.json` is written on first run with every defaulted sett
|
|
|
183
184
|
"setError": { "type": "label", "value": "ralph:error" },
|
|
184
185
|
"setConflicted": { "type": "label", "value": "ralph:conflicted" },
|
|
185
186
|
"clearConflicted": { "type": "label", "value": "ralph:conflicted" },
|
|
187
|
+
"clearReview": { "type": "label", "value": "ralph:review" },
|
|
186
188
|
},
|
|
187
189
|
},
|
|
188
190
|
"useWorktree": true,
|
|
@@ -205,9 +207,11 @@ A default `ralphy.config.json` is written on first run with every defaulted sett
|
|
|
205
207
|
|
|
206
208
|
Linear is the source of truth for which issues Ralph has touched. Each `linear.indicators` key names a lifecycle event:
|
|
207
209
|
|
|
208
|
-
- `getTodo` / `getInProgress` / `getConflicted` — `{ filter: [...] }` selectors used to find issues to pick up, resume, or
|
|
210
|
+
- `getTodo` / `getInProgress` / `getConflicted` / `getReview` — `{ filter: [...] }` selectors used to find issues to pick up, resume, repair, or follow up on after review.
|
|
209
211
|
- `setInProgress` / `setDone` / `setError` / `setConflicted` — single marker `{ type, value }` or `{ apply: [...] }` for multi-marker.
|
|
210
|
-
- `clearConflicted` — labels to remove once a conflicted PR is fixed (status removal is not supported).
|
|
212
|
+
- `clearConflicted` / `clearReview` — labels to remove once a conflicted PR is fixed or a review-mode issue is picked back up (status removal is not supported).
|
|
213
|
+
|
|
214
|
+
**Review follow-ups.** When a Linear issue is in a "done" state and a reviewer adds the `getReview` marker (typically a label like `ralph:review` after leaving comments), Ralph picks it up, applies `setInProgress`, removes the `clearReview` label so the same trigger doesn't re-fire, fetches the comment thread, filters out Ralph's own comments, and prepends those reviewer comments as a new task at the top of `tasks.md`. The worker addresses them in the same change branch and `setDone` is re-applied on success.
|
|
211
215
|
|
|
212
216
|
Marker types are `"label"` or `"status"`. Combine markers under `apply` when one event needs to set multiple — e.g. `setDone` flipping a status _and_ adding a "shipped" label.
|
|
213
217
|
|
package/dist/cli/index.js
CHANGED
|
@@ -35029,8 +35029,8 @@ import { readFileSync as readFileSync2 } from "fs";
|
|
|
35029
35029
|
import { resolve } from "path";
|
|
35030
35030
|
function getVersion() {
|
|
35031
35031
|
try {
|
|
35032
|
-
if ("2.
|
|
35033
|
-
return "2.
|
|
35032
|
+
if ("2.18.0")
|
|
35033
|
+
return "2.18.0";
|
|
35034
35034
|
} catch {}
|
|
35035
35035
|
const dirsToTry = [];
|
|
35036
35036
|
try {
|
|
@@ -35356,13 +35356,20 @@ var init_cli = __esm(() => {
|
|
|
35356
35356
|
"getTodo",
|
|
35357
35357
|
"getInProgress",
|
|
35358
35358
|
"getConflicted",
|
|
35359
|
+
"getReview",
|
|
35359
35360
|
"setInProgress",
|
|
35360
35361
|
"setDone",
|
|
35361
35362
|
"setError",
|
|
35362
35363
|
"setConflicted",
|
|
35363
|
-
"clearConflicted"
|
|
35364
|
+
"clearConflicted",
|
|
35365
|
+
"clearReview"
|
|
35366
|
+
]);
|
|
35367
|
+
GET_KEYS = new Set([
|
|
35368
|
+
"getTodo",
|
|
35369
|
+
"getInProgress",
|
|
35370
|
+
"getConflicted",
|
|
35371
|
+
"getReview"
|
|
35364
35372
|
]);
|
|
35365
|
-
GET_KEYS = new Set(["getTodo", "getInProgress", "getConflicted"]);
|
|
35366
35373
|
HELP_TEXT = [
|
|
35367
35374
|
`ralph v${VERSION}`,
|
|
35368
35375
|
"",
|
|
@@ -35404,8 +35411,9 @@ var init_cli = __esm(() => {
|
|
|
35404
35411
|
" --indicator getTodo:status:Todo",
|
|
35405
35412
|
" --indicator setDone:label:shipped",
|
|
35406
35413
|
" --indicator setDone:status:Done (combined with above \u2192 multi-marker)",
|
|
35407
|
-
" Keys: getTodo, getInProgress, getConflicted,",
|
|
35408
|
-
" setInProgress, setDone, setError, setConflicted,
|
|
35414
|
+
" Keys: getTodo, getInProgress, getConflicted, getReview,",
|
|
35415
|
+
" setInProgress, setDone, setError, setConflicted,",
|
|
35416
|
+
" clearConflicted, clearReview",
|
|
35409
35417
|
" Types: label, status",
|
|
35410
35418
|
" --create-pr Push the worker branch and open a GitHub PR on success (needs --worktree)",
|
|
35411
35419
|
" --fix-ci After opening the PR, re-run on CI failures until green (needs --create-pr)",
|
|
@@ -59626,6 +59634,10 @@ var MarkerSchema, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, Ralp
|
|
|
59626
59634
|
// Issues whose PR has a merge conflict (Ralph will attempt a re-fix run).
|
|
59627
59635
|
// "getConflicted": { "filter": [{ "type": "label", "value": "ralph:conflict" }] },
|
|
59628
59636
|
|
|
59637
|
+
// Done issues with new review comments to address (Ralph will re-open
|
|
59638
|
+
// and prepend a task that ingests the non-Ralph comments).
|
|
59639
|
+
// "getReview": { "filter": [{ "type": "label", "value": "ralph:review" }] },
|
|
59640
|
+
|
|
59629
59641
|
// Applied when Ralph picks up an issue.
|
|
59630
59642
|
// "setInProgress": { "type": "label", "value": "ralph:in-progress" },
|
|
59631
59643
|
|
|
@@ -59639,7 +59651,10 @@ var MarkerSchema, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, Ralp
|
|
|
59639
59651
|
// "setConflicted": { "type": "label", "value": "ralph:conflict" },
|
|
59640
59652
|
|
|
59641
59653
|
// Label removed once the conflict is fixed (status removal is not supported here).
|
|
59642
|
-
// "clearConflicted": { "type": "label", "value": "ralph:conflict" }
|
|
59654
|
+
// "clearConflicted": { "type": "label", "value": "ralph:conflict" },
|
|
59655
|
+
|
|
59656
|
+
// Label removed when Ralph picks up a review-mode issue (status removal not supported).
|
|
59657
|
+
// "clearReview": { "type": "label", "value": "ralph:review" }
|
|
59643
59658
|
}
|
|
59644
59659
|
}
|
|
59645
59660
|
}
|
|
@@ -59661,24 +59676,28 @@ var init_config = __esm(() => {
|
|
|
59661
59676
|
getTodo: GetIndicatorSchema.optional(),
|
|
59662
59677
|
getInProgress: GetIndicatorSchema.optional(),
|
|
59663
59678
|
getConflicted: GetIndicatorSchema.optional(),
|
|
59679
|
+
getReview: GetIndicatorSchema.optional(),
|
|
59664
59680
|
setInProgress: SetIndicatorSchema.optional(),
|
|
59665
59681
|
setDone: SetIndicatorSchema.optional(),
|
|
59666
59682
|
setError: SetIndicatorSchema.optional(),
|
|
59667
59683
|
setConflicted: SetIndicatorSchema.optional(),
|
|
59668
|
-
clearConflicted: SetIndicatorSchema.optional()
|
|
59684
|
+
clearConflicted: SetIndicatorSchema.optional(),
|
|
59685
|
+
clearReview: SetIndicatorSchema.optional()
|
|
59669
59686
|
}).superRefine((value, ctx) => {
|
|
59670
|
-
const
|
|
59671
|
-
|
|
59672
|
-
|
|
59673
|
-
|
|
59674
|
-
|
|
59675
|
-
|
|
59676
|
-
|
|
59677
|
-
|
|
59678
|
-
|
|
59679
|
-
|
|
59680
|
-
|
|
59681
|
-
|
|
59687
|
+
for (const key of ["clearConflicted", "clearReview"]) {
|
|
59688
|
+
const clear = value[key];
|
|
59689
|
+
if (!clear)
|
|
59690
|
+
continue;
|
|
59691
|
+
const markers = "apply" in clear ? clear.apply : [clear];
|
|
59692
|
+
for (const m of markers) {
|
|
59693
|
+
if (m.type !== "label") {
|
|
59694
|
+
ctx.addIssue({
|
|
59695
|
+
code: exports_external.ZodIssueCode.custom,
|
|
59696
|
+
path: [key],
|
|
59697
|
+
message: `${key} markers must be label-typed (status removal is not supported)`
|
|
59698
|
+
});
|
|
59699
|
+
break;
|
|
59700
|
+
}
|
|
59682
59701
|
}
|
|
59683
59702
|
}
|
|
59684
59703
|
});
|
|
@@ -60050,19 +60069,21 @@ class AgentCoordinator {
|
|
|
60050
60069
|
let todo = [];
|
|
60051
60070
|
let inProgress = [];
|
|
60052
60071
|
let conflicted = [];
|
|
60072
|
+
let review = [];
|
|
60053
60073
|
try {
|
|
60054
|
-
[todo, inProgress, conflicted] = await Promise.all([
|
|
60074
|
+
[todo, inProgress, conflicted, review] = await Promise.all([
|
|
60055
60075
|
this.deps.fetchTodo(),
|
|
60056
60076
|
this.deps.fetchInProgress(),
|
|
60057
|
-
this.deps.fetchConflicted()
|
|
60077
|
+
this.deps.fetchConflicted(),
|
|
60078
|
+
this.deps.fetchReview()
|
|
60058
60079
|
]);
|
|
60059
60080
|
} catch (err) {
|
|
60060
60081
|
this.deps.onLog(`! Linear poll failed: ${err.message}`, "red");
|
|
60061
60082
|
capture("agent_linear_poll_failed", { error: err.message });
|
|
60062
60083
|
return { found: 0, added: 0 };
|
|
60063
60084
|
}
|
|
60064
|
-
if (todo.length + inProgress.length + conflicted.length > 0) {
|
|
60065
|
-
this.deps.onLog(` poll: ${todo.length} todo, ${inProgress.length} in-progress, ${conflicted.length} conflicted`, "gray");
|
|
60085
|
+
if (todo.length + inProgress.length + conflicted.length + review.length > 0) {
|
|
60086
|
+
this.deps.onLog(` poll: ${todo.length} todo, ${inProgress.length} in-progress, ${conflicted.length} conflicted, ${review.length} review`, "gray");
|
|
60066
60087
|
}
|
|
60067
60088
|
const queuedIds = new Set(this.queue.map((q) => q.issue.id));
|
|
60068
60089
|
const activeIds = new Set(this.workers.map((w) => w.issueId));
|
|
@@ -60097,6 +60118,16 @@ class AgentCoordinator {
|
|
|
60097
60118
|
added += 1;
|
|
60098
60119
|
this.deps.onLog(` \u21B3 ${issue.identifier} queued (conflict-fix)`, "gray");
|
|
60099
60120
|
}
|
|
60121
|
+
for (const issue of review) {
|
|
60122
|
+
if (atTicketLimit())
|
|
60123
|
+
break;
|
|
60124
|
+
if (!eligible(issue.id))
|
|
60125
|
+
continue;
|
|
60126
|
+
this.queue.push({ issue, mode: "review" });
|
|
60127
|
+
queuedIds.add(issue.id);
|
|
60128
|
+
added += 1;
|
|
60129
|
+
this.deps.onLog(` \u21B3 ${issue.identifier} queued (review)`, "gray");
|
|
60130
|
+
}
|
|
60100
60131
|
for (const issue of todo) {
|
|
60101
60132
|
if (atTicketLimit())
|
|
60102
60133
|
break;
|
|
@@ -60113,7 +60144,8 @@ class AgentCoordinator {
|
|
|
60113
60144
|
const modeRank = {
|
|
60114
60145
|
resume: 0,
|
|
60115
60146
|
"conflict-fix": 1,
|
|
60116
|
-
|
|
60147
|
+
review: 2,
|
|
60148
|
+
fresh: 3
|
|
60117
60149
|
};
|
|
60118
60150
|
this.queue.sort((a, b) => {
|
|
60119
60151
|
const pa = a.issue.priority === 0 ? Infinity : a.issue.priority;
|
|
@@ -60126,7 +60158,7 @@ class AgentCoordinator {
|
|
|
60126
60158
|
this.spawnNext();
|
|
60127
60159
|
await this.scanDoneForConflicts();
|
|
60128
60160
|
await this.reportProgress();
|
|
60129
|
-
const found = todo.length + inProgress.length + conflicted.length;
|
|
60161
|
+
const found = todo.length + inProgress.length + conflicted.length + review.length;
|
|
60130
60162
|
return { found, added };
|
|
60131
60163
|
}
|
|
60132
60164
|
dependenciesResolved(issue) {
|
|
@@ -60270,6 +60302,26 @@ class AgentCoordinator {
|
|
|
60270
60302
|
});
|
|
60271
60303
|
}
|
|
60272
60304
|
}
|
|
60305
|
+
if (mode === "review" && this.opts.clearReview) {
|
|
60306
|
+
try {
|
|
60307
|
+
await this.deps.removeIndicator(issue, this.opts.clearReview);
|
|
60308
|
+
this.deps.onLog(` ${issue.identifier}: clearReview applied`, "gray");
|
|
60309
|
+
} catch (err) {
|
|
60310
|
+
this.deps.onLog(`! Linear clearReview failed for ${issue.identifier}: ${err.message}`, "yellow");
|
|
60311
|
+
capture("agent_indicator_failed", {
|
|
60312
|
+
indicator: "clearReview",
|
|
60313
|
+
issue_identifier: issue.identifier,
|
|
60314
|
+
error: err.message
|
|
60315
|
+
});
|
|
60316
|
+
}
|
|
60317
|
+
}
|
|
60318
|
+
if (mode === "review" && this.opts.postComments !== false) {
|
|
60319
|
+
try {
|
|
60320
|
+
await this.deps.postComment(issue, `\uD83D\uDD01 Ralph picked up new review comments. Tracking change: \`${prep.changeName}\``);
|
|
60321
|
+
} catch (err) {
|
|
60322
|
+
this.deps.onLog(`! Linear review comment failed for ${issue.identifier}: ${err.message}`, "yellow");
|
|
60323
|
+
}
|
|
60324
|
+
}
|
|
60273
60325
|
if (mode === "fresh" && this.opts.postComments !== false) {
|
|
60274
60326
|
let alreadyPosted = false;
|
|
60275
60327
|
try {
|
|
@@ -61032,6 +61084,30 @@ function mergeIndicators(cfg, cli) {
|
|
|
61032
61084
|
}
|
|
61033
61085
|
return out;
|
|
61034
61086
|
}
|
|
61087
|
+
function isRalphComment(body) {
|
|
61088
|
+
const trimmed = body.trimStart();
|
|
61089
|
+
return /^(\uD83E\uDD16|\uD83D\uDD04|\u2705|\u2717|\u26A0|\uD83D\uDD01)\s*Ralph\b/.test(trimmed);
|
|
61090
|
+
}
|
|
61091
|
+
function buildReviewTaskBody(comments, url) {
|
|
61092
|
+
if (comments.length === 0) {
|
|
61093
|
+
return `No non-Ralph reviewer comments were found on ${url}. Recheck the issue manually before continuing.`;
|
|
61094
|
+
}
|
|
61095
|
+
const blocks = comments.map((c) => {
|
|
61096
|
+
const author = c.user?.name ?? "unknown";
|
|
61097
|
+
return `**${author}** \u2014 ${c.createdAt}
|
|
61098
|
+
|
|
61099
|
+
${c.body.trim()}`;
|
|
61100
|
+
});
|
|
61101
|
+
return [
|
|
61102
|
+
`Reviewer comments left on the Linear issue (${url}):`,
|
|
61103
|
+
"",
|
|
61104
|
+
...blocks,
|
|
61105
|
+
"",
|
|
61106
|
+
"Address every concrete request above. If a comment is ambiguous, note",
|
|
61107
|
+
"your interpretation in proposal.md `## Steering` before acting."
|
|
61108
|
+
].join(`
|
|
61109
|
+
`);
|
|
61110
|
+
}
|
|
61035
61111
|
function unionMarkers(...sets) {
|
|
61036
61112
|
const out = [];
|
|
61037
61113
|
const seen = new Set;
|
|
@@ -61071,6 +61147,7 @@ function buildAgentCoordinator(input) {
|
|
|
61071
61147
|
const team = args.linearTeam || cfg.linear.team;
|
|
61072
61148
|
const assignee = args.linearAssignee || cfg.linear.assignee;
|
|
61073
61149
|
const excludeFromTodo = unionMarkers(indicators.setDone, indicators.setError, indicators.setConflicted);
|
|
61150
|
+
const excludeFromReview = unionMarkers(indicators.setInProgress, indicators.setError, indicators.setConflicted);
|
|
61074
61151
|
const gitRunner = input.runners?.git ?? bunGitRunner;
|
|
61075
61152
|
const cmdRunner = input.runners?.cmd ?? bunCmdRunner;
|
|
61076
61153
|
const stateCache = new Map;
|
|
@@ -61244,7 +61321,8 @@ function buildAgentCoordinator(input) {
|
|
|
61244
61321
|
async function prepare(issue, mode) {
|
|
61245
61322
|
const { workerCwd, scaffoldTasksDir, scaffoldStatesDir, branch } = await setupWorktree(issue);
|
|
61246
61323
|
let changeName;
|
|
61247
|
-
|
|
61324
|
+
const isFresh = mode === "fresh";
|
|
61325
|
+
if (isFresh) {
|
|
61248
61326
|
let comments = [];
|
|
61249
61327
|
try {
|
|
61250
61328
|
comments = await fetchIssueComments(apiKey, issue.id);
|
|
@@ -61264,7 +61342,24 @@ function buildAgentCoordinator(input) {
|
|
|
61264
61342
|
issueByChange.set(changeName, issue);
|
|
61265
61343
|
if (branch)
|
|
61266
61344
|
branchByChange.set(changeName, branch);
|
|
61267
|
-
if (mode === "
|
|
61345
|
+
if (mode === "review") {
|
|
61346
|
+
const wtLayout = projectLayout(workerCwd);
|
|
61347
|
+
const tasksFile = join16(wtLayout.changeDir(changeName), "tasks.md");
|
|
61348
|
+
let comments = [];
|
|
61349
|
+
try {
|
|
61350
|
+
comments = await fetchIssueComments(apiKey, issue.id);
|
|
61351
|
+
} catch (err) {
|
|
61352
|
+
onLog(`! Linear comment fetch failed for ${issue.identifier}: ${err.message}`, "yellow");
|
|
61353
|
+
}
|
|
61354
|
+
const reviewerComments = comments.filter((c) => !isRalphComment(c.body));
|
|
61355
|
+
const body = buildReviewTaskBody(reviewerComments, issue.url);
|
|
61356
|
+
try {
|
|
61357
|
+
await prependFixTask(tasksFile, "Address reviewer comments", body);
|
|
61358
|
+
} catch (err) {
|
|
61359
|
+
onLog(`! could not prepend review task: ${err.message}`, "red");
|
|
61360
|
+
}
|
|
61361
|
+
await reactivateState2(wtLayout.stateFile(changeName), changeName);
|
|
61362
|
+
} else if (mode === "conflict-fix") {
|
|
61268
61363
|
const wtLayout = projectLayout(workerCwd);
|
|
61269
61364
|
const tasksFile = join16(wtLayout.changeDir(changeName), "tasks.md");
|
|
61270
61365
|
const prUrl = prByChange.get(changeName);
|
|
@@ -61539,6 +61634,7 @@ PR: ${prUrl}` : ""
|
|
|
61539
61634
|
fetchTodo: () => fetchByGet(indicators.getTodo, excludeFromTodo),
|
|
61540
61635
|
fetchInProgress: () => fetchByGet(indicators.getInProgress, []),
|
|
61541
61636
|
fetchConflicted: () => fetchByGet(indicators.getConflicted, []),
|
|
61637
|
+
fetchReview: () => fetchByGet(indicators.getReview, excludeFromReview),
|
|
61542
61638
|
fetchDoneCandidates,
|
|
61543
61639
|
prepare,
|
|
61544
61640
|
spawnWorker,
|
|
@@ -61567,6 +61663,7 @@ PR: ${prUrl}` : ""
|
|
|
61567
61663
|
...indicators.setError !== undefined ? { setError: indicators.setError } : {},
|
|
61568
61664
|
...indicators.setConflicted !== undefined ? { setConflicted: indicators.setConflicted } : {},
|
|
61569
61665
|
...indicators.clearConflicted !== undefined ? { clearConflicted: indicators.clearConflicted } : {},
|
|
61666
|
+
...indicators.clearReview !== undefined ? { clearReview: indicators.clearReview } : {},
|
|
61570
61667
|
postComments: cfg.linear.postComments,
|
|
61571
61668
|
commentEveryIterations: cfg.linear.updateEveryIterations,
|
|
61572
61669
|
...args.maxTickets > 0 ? { maxTickets: args.maxTickets } : {}
|
|
@@ -61593,6 +61690,9 @@ function describeIndicators(indicators, team, assignee) {
|
|
|
61593
61690
|
if (indicators.getConflicted) {
|
|
61594
61691
|
parts.push(`conflicted=[${indicators.getConflicted.filter.map((m) => `${m.type}:${m.value}`).join(",")}]`);
|
|
61595
61692
|
}
|
|
61693
|
+
if (indicators.getReview) {
|
|
61694
|
+
parts.push(`review=[${indicators.getReview.filter.map((m) => `${m.type}:${m.value}`).join(",")}]`);
|
|
61695
|
+
}
|
|
61596
61696
|
return parts.join(", ");
|
|
61597
61697
|
}
|
|
61598
61698
|
var bunGitRunner, bunCmdRunner;
|