@neriros/ralphy 3.10.13 → 3.10.15
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 +2 -3
- package/dist/shell/index.js +1231 -916
- package/package.json +1 -1
package/dist/shell/index.js
CHANGED
|
@@ -18928,8 +18928,8 @@ import { readFileSync } from "fs";
|
|
|
18928
18928
|
import { resolve } from "path";
|
|
18929
18929
|
function getVersion() {
|
|
18930
18930
|
try {
|
|
18931
|
-
if ("3.10.
|
|
18932
|
-
return "3.10.
|
|
18931
|
+
if ("3.10.15")
|
|
18932
|
+
return "3.10.15";
|
|
18933
18933
|
} catch {}
|
|
18934
18934
|
const dirsToTry = [];
|
|
18935
18935
|
try {
|
|
@@ -19421,7 +19421,7 @@ var init_fields = __esm(() => {
|
|
|
19421
19421
|
spec: yes(),
|
|
19422
19422
|
when: (answers) => typeof answers["repo.name"] === "string" && answers["repo.name"] !== ""
|
|
19423
19423
|
};
|
|
19424
|
-
LINEAR_FILTER_DESCRIPTION = "Global filter
|
|
19424
|
+
LINEAR_FILTER_DESCRIPTION = "Global filter ANDed into every Linear ticket fetch: a marker list of 'assignee' and " + "'label' clauses (all required). assignee value is 'me' (assigned to you), 'any' " + "(regardless of assignee), 'unassigned', or a specific Linear user (email or user-id). " + "Add 'label' clauses to require the ticket carry those labels. Defaults to assignee = me.";
|
|
19425
19425
|
LINEAR_ASSIGNEE_CHOICE = {
|
|
19426
19426
|
id: LINEAR_ASSIGNEE_CHOICE_FIELD_ID,
|
|
19427
19427
|
label: "Linear assignee filter",
|
|
@@ -19674,30 +19674,24 @@ var init_fields = __esm(() => {
|
|
|
19674
19674
|
spec: yes()
|
|
19675
19675
|
},
|
|
19676
19676
|
{
|
|
19677
|
-
id: "
|
|
19678
|
-
label: "
|
|
19679
|
-
description: "After
|
|
19680
|
-
spec:
|
|
19681
|
-
},
|
|
19682
|
-
{
|
|
19683
|
-
id: "maxCiFixAttempts",
|
|
19684
|
-
label: "Max CI-fix attempts per task",
|
|
19685
|
-
description: "Stop trying to fix failing CI after this many attempts.",
|
|
19686
|
-
spec: { kind: "number", placeholder: "5" },
|
|
19687
|
-
when: isOn("fixCiOnFailure")
|
|
19677
|
+
id: "prRecovery.enabled",
|
|
19678
|
+
label: "Enable PR recovery (conflicts + CI)?",
|
|
19679
|
+
description: "After a worker opens a PR, keep watching it: advance the ticket to done once the PR is mergeable (CI green, no conflicts), and auto-recover red PRs by re-running the agent \u2014 resolving merge conflicts AND fixing failing CI checks. Turn off to mark the ticket done immediately on PR open and do no watching anywhere. (Fine-grained `fixCi` / `fixConflicts` toggles live in WORKFLOW.md, both on by default.)",
|
|
19680
|
+
spec: yes()
|
|
19688
19681
|
},
|
|
19689
19682
|
{
|
|
19690
|
-
id: "
|
|
19691
|
-
label: "
|
|
19692
|
-
description: "
|
|
19693
|
-
spec: { kind: "number", placeholder: "
|
|
19694
|
-
when: isOn("
|
|
19683
|
+
id: "prRecovery.maxRecoverySessions",
|
|
19684
|
+
label: "Max PR recovery sessions",
|
|
19685
|
+
description: "Give up auto-recovering a red PR after this many recovery sessions, then flag it for a human.",
|
|
19686
|
+
spec: { kind: "number", placeholder: "3" },
|
|
19687
|
+
when: isOn("prRecovery.enabled")
|
|
19695
19688
|
},
|
|
19696
19689
|
{
|
|
19697
|
-
id: "
|
|
19690
|
+
id: "prRecovery.ignoreChecks",
|
|
19698
19691
|
label: "CI checks to ignore",
|
|
19699
19692
|
description: "Names of CI checks to ignore when deciding whether a PR is green \u2014 e.g. known-flaky jobs.",
|
|
19700
|
-
spec: { kind: "list", placeholder: "check name" }
|
|
19693
|
+
spec: { kind: "list", placeholder: "check name" },
|
|
19694
|
+
when: isOn("prRecovery.enabled")
|
|
19701
19695
|
},
|
|
19702
19696
|
{
|
|
19703
19697
|
id: "rules",
|
|
@@ -19831,26 +19825,6 @@ var init_fields = __esm(() => {
|
|
|
19831
19825
|
spec: { kind: "text", placeholder: "ralph:pre-existing-error" },
|
|
19832
19826
|
when: isOn("preExistingErrorCheck.enabled")
|
|
19833
19827
|
},
|
|
19834
|
-
{
|
|
19835
|
-
id: "prTracker.enabled",
|
|
19836
|
-
label: "Enable the PR tracker?",
|
|
19837
|
-
description: "Keep watching the PRs Ralphy opened and automatically try to recover any whose merge state goes red (conflicts or failing CI).",
|
|
19838
|
-
spec: yes()
|
|
19839
|
-
},
|
|
19840
|
-
{
|
|
19841
|
-
id: "prTracker.maxRecoveryAttempts",
|
|
19842
|
-
label: "PR tracker max recovery attempts",
|
|
19843
|
-
description: "Give up auto-recovering a red PR after this many attempts, then flag it for a human.",
|
|
19844
|
-
spec: { kind: "number", placeholder: "3" },
|
|
19845
|
-
when: isOn("prTracker.enabled")
|
|
19846
|
-
},
|
|
19847
|
-
{
|
|
19848
|
-
id: "prTracker.advanceMergedToDone",
|
|
19849
|
-
label: "Advance merged PRs to done automatically?",
|
|
19850
|
-
description: "Move an issue to its done state as soon as its PR is merged.",
|
|
19851
|
-
spec: no(),
|
|
19852
|
-
when: isOn("prTracker.enabled")
|
|
19853
|
-
},
|
|
19854
19828
|
{
|
|
19855
19829
|
id: "metaPrompt.enabled",
|
|
19856
19830
|
label: "Enable the meta-prompt addendum?",
|
|
@@ -81277,11 +81251,11 @@ function foldLegacyAssignee(v) {
|
|
|
81277
81251
|
if (rest2["filter"] === undefined) {
|
|
81278
81252
|
const raw = typeof assignee === "string" ? assignee.trim() : "";
|
|
81279
81253
|
const value = raw === "" || raw.toLowerCase() === "unassigned" ? "unassigned" : raw;
|
|
81280
|
-
rest2["filter"] =
|
|
81254
|
+
rest2["filter"] = [{ type: "assignee", value }];
|
|
81281
81255
|
}
|
|
81282
81256
|
return rest2;
|
|
81283
81257
|
}
|
|
81284
|
-
var CURRENT_WORKFLOW_VERSION =
|
|
81258
|
+
var CURRENT_WORKFLOW_VERSION = 6, MarkerSchema, FilterMarkerSchema, LinearFilterSchema, SET_INDICATOR_KEYS, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, ProjectSchema, CommandsSchema, DEFAULT_META_ONLY_FILES, BoundariesSchema, WorkflowConfigSchema;
|
|
81285
81259
|
var init_schema = __esm(() => {
|
|
81286
81260
|
init_zod();
|
|
81287
81261
|
MarkerSchema = exports_external.discriminatedUnion("type", [
|
|
@@ -81295,6 +81269,19 @@ var init_schema = __esm(() => {
|
|
|
81295
81269
|
exports_external.object({ type: exports_external.literal("project"), value: exports_external.string().min(1) }).strict(),
|
|
81296
81270
|
exports_external.object({ type: exports_external.literal("comment"), value: exports_external.string().min(1) }).strict()
|
|
81297
81271
|
]);
|
|
81272
|
+
FilterMarkerSchema = exports_external.discriminatedUnion("type", [
|
|
81273
|
+
exports_external.object({ type: exports_external.literal("label"), value: exports_external.string().min(1) }).strict(),
|
|
81274
|
+
exports_external.object({ type: exports_external.literal("assignee"), value: exports_external.string().min(1) }).strict()
|
|
81275
|
+
]);
|
|
81276
|
+
LinearFilterSchema = exports_external.array(FilterMarkerSchema).superRefine((markers, ctx) => {
|
|
81277
|
+
const assigneeCount = markers.filter((m) => m.type === "assignee").length;
|
|
81278
|
+
if (assigneeCount > 1) {
|
|
81279
|
+
ctx.addIssue({
|
|
81280
|
+
code: exports_external.ZodIssueCode.custom,
|
|
81281
|
+
message: `linear.filter allows at most one "assignee" clause, found ${assigneeCount}.`
|
|
81282
|
+
});
|
|
81283
|
+
}
|
|
81284
|
+
}).default([{ type: "assignee", value: "me" }]);
|
|
81298
81285
|
SET_INDICATOR_KEYS = [
|
|
81299
81286
|
"setInProgress",
|
|
81300
81287
|
"setDone",
|
|
@@ -81412,15 +81399,11 @@ var init_schema = __esm(() => {
|
|
|
81412
81399
|
autoMergeStrategy: exports_external.enum(["squash", "merge", "rebase"]).default("squash"),
|
|
81413
81400
|
manualMergeWhenAutoMergeDisabled: exports_external.boolean().default(true),
|
|
81414
81401
|
finalizeNoOpAsDone: exports_external.boolean().default(true),
|
|
81415
|
-
fixCiOnFailure: exports_external.boolean().default(false),
|
|
81416
|
-
maxCiFixAttempts: exports_external.number().int().positive().default(5),
|
|
81417
|
-
ciPollIntervalSeconds: exports_external.number().int().positive().default(30),
|
|
81418
|
-
ignoreCiChecks: exports_external.array(exports_external.string()).default([]),
|
|
81419
81402
|
engine: exports_external.enum(["claude", "codex"]).default("claude"),
|
|
81420
81403
|
model: exports_external.enum(["haiku", "sonnet", "opus"]).default("opus"),
|
|
81421
81404
|
linear: exports_external.preprocess(foldLegacyAssignee, exports_external.object({
|
|
81422
81405
|
team: exports_external.string().optional(),
|
|
81423
|
-
filter:
|
|
81406
|
+
filter: LinearFilterSchema,
|
|
81424
81407
|
postComments: exports_external.boolean().default(true),
|
|
81425
81408
|
updateEveryIterations: exports_external.number().int().nonnegative().default(10),
|
|
81426
81409
|
mentionTrigger: exports_external.boolean().default(true),
|
|
@@ -81442,7 +81425,7 @@ var init_schema = __esm(() => {
|
|
|
81442
81425
|
}),
|
|
81443
81426
|
indicators: IndicatorsSchema.default({})
|
|
81444
81427
|
}).strict()).default({
|
|
81445
|
-
filter: "assignee
|
|
81428
|
+
filter: [{ type: "assignee", value: "me" }],
|
|
81446
81429
|
postComments: true,
|
|
81447
81430
|
updateEveryIterations: 10,
|
|
81448
81431
|
mentionTrigger: true,
|
|
@@ -81476,11 +81459,6 @@ var init_schema = __esm(() => {
|
|
|
81476
81459
|
cleanup_on_success: exports_external.boolean().optional(),
|
|
81477
81460
|
setup_script: exports_external.string().optional()
|
|
81478
81461
|
}).strict().optional(),
|
|
81479
|
-
ci: exports_external.object({
|
|
81480
|
-
fix_on_failure: exports_external.boolean().optional(),
|
|
81481
|
-
max_attempts: exports_external.number().int().positive().optional(),
|
|
81482
|
-
poll_interval_seconds: exports_external.number().int().positive().optional()
|
|
81483
|
-
}).strict().optional(),
|
|
81484
81462
|
preExistingErrorCheck: exports_external.object({
|
|
81485
81463
|
enabled: exports_external.boolean().default(false),
|
|
81486
81464
|
commands: exports_external.array(exports_external.string()).default([]),
|
|
@@ -81494,14 +81472,18 @@ var init_schema = __esm(() => {
|
|
|
81494
81472
|
label: "ralph:pre-existing-error",
|
|
81495
81473
|
outputCharLimit: 4000
|
|
81496
81474
|
}),
|
|
81497
|
-
|
|
81475
|
+
prRecovery: exports_external.object({
|
|
81498
81476
|
enabled: exports_external.boolean().default(true),
|
|
81499
|
-
|
|
81500
|
-
|
|
81477
|
+
fixCi: exports_external.boolean().default(true),
|
|
81478
|
+
fixConflicts: exports_external.boolean().default(true),
|
|
81479
|
+
maxRecoverySessions: exports_external.number().int().positive().default(3),
|
|
81480
|
+
ignoreChecks: exports_external.array(exports_external.string()).default([])
|
|
81501
81481
|
}).strict().default({
|
|
81502
81482
|
enabled: true,
|
|
81503
|
-
|
|
81504
|
-
|
|
81483
|
+
fixCi: true,
|
|
81484
|
+
fixConflicts: true,
|
|
81485
|
+
maxRecoverySessions: 3,
|
|
81486
|
+
ignoreChecks: []
|
|
81505
81487
|
}),
|
|
81506
81488
|
metaPrompt: exports_external.object({
|
|
81507
81489
|
enabled: exports_external.boolean().default(true),
|
|
@@ -81532,7 +81514,7 @@ var init_schema = __esm(() => {
|
|
|
81532
81514
|
var FRONTMATTER_RE, DEFAULT_WORKFLOW_MD = `---
|
|
81533
81515
|
# WORKFLOW.md schema version \u2014 managed by \`ralphy init\`. When a newer version
|
|
81534
81516
|
# ships, re-running init migrates this file and fills in the new settings.
|
|
81535
|
-
version:
|
|
81517
|
+
version: 6
|
|
81536
81518
|
|
|
81537
81519
|
project:
|
|
81538
81520
|
name: ralphy
|
|
@@ -81585,9 +81567,12 @@ prBaseBranch: main
|
|
|
81585
81567
|
stackPrsOnDependencies: false
|
|
81586
81568
|
autoMergeStrategy: squash
|
|
81587
81569
|
|
|
81588
|
-
|
|
81589
|
-
|
|
81590
|
-
|
|
81570
|
+
prRecovery:
|
|
81571
|
+
enabled: true
|
|
81572
|
+
fixCi: true
|
|
81573
|
+
fixConflicts: true
|
|
81574
|
+
maxRecoverySessions: 3
|
|
81575
|
+
ignoreChecks: []
|
|
81591
81576
|
|
|
81592
81577
|
preExistingErrorCheck:
|
|
81593
81578
|
enabled: false
|
|
@@ -81597,7 +81582,18 @@ preExistingErrorCheck:
|
|
|
81597
81582
|
outputCharLimit: 4000
|
|
81598
81583
|
|
|
81599
81584
|
linear:
|
|
81600
|
-
filter
|
|
81585
|
+
# Global filter ANDed into every Linear query (and the GitHub PR searches
|
|
81586
|
+
# rooted at those issues). A marker list of \`assignee\` and \`label\` clauses;
|
|
81587
|
+
# all are required. \`assignee\` value is me / any / unassigned / <email> / <id>.
|
|
81588
|
+
# Example with a required label:
|
|
81589
|
+
# filter:
|
|
81590
|
+
# - type: assignee
|
|
81591
|
+
# value: me
|
|
81592
|
+
# - type: label
|
|
81593
|
+
# value: ralph
|
|
81594
|
+
filter:
|
|
81595
|
+
- type: assignee
|
|
81596
|
+
value: me
|
|
81601
81597
|
postComments: true
|
|
81602
81598
|
updateEveryIterations: 10
|
|
81603
81599
|
mentionTrigger: true
|
|
@@ -81858,7 +81854,8 @@ function withPresets(answers) {
|
|
|
81858
81854
|
const values2 = { ...answers.values };
|
|
81859
81855
|
if (answers.mode === "permissive") {
|
|
81860
81856
|
values2["createPrOnSuccess"] = true;
|
|
81861
|
-
values2["
|
|
81857
|
+
values2["prRecovery.enabled"] = true;
|
|
81858
|
+
values2["prRecovery.fixCi"] = true;
|
|
81862
81859
|
values2["manualMergeWhenAutoMergeDisabled"] = false;
|
|
81863
81860
|
}
|
|
81864
81861
|
return values2;
|
|
@@ -82010,6 +82007,88 @@ var init_normalize = __esm(() => {
|
|
|
82010
82007
|
};
|
|
82011
82008
|
});
|
|
82012
82009
|
|
|
82010
|
+
// packages/workflow/src/migrate/pr-recovery.ts
|
|
82011
|
+
function hasLegacyPrRecoveryKey(document2) {
|
|
82012
|
+
if (!import_yaml3.default.isMap(document2.contents))
|
|
82013
|
+
return false;
|
|
82014
|
+
return LEGACY_TOP_LEVEL_KEYS.some((key) => document2.hasIn([key]));
|
|
82015
|
+
}
|
|
82016
|
+
function hasLegacyLinearFilter(document2) {
|
|
82017
|
+
if (!import_yaml3.default.isMap(document2.contents))
|
|
82018
|
+
return false;
|
|
82019
|
+
if (!document2.hasIn(["linear", "filter"]))
|
|
82020
|
+
return false;
|
|
82021
|
+
return import_yaml3.default.isScalar(document2.getIn(["linear", "filter"], true));
|
|
82022
|
+
}
|
|
82023
|
+
function scalarFilterToMarkers(raw) {
|
|
82024
|
+
const text = typeof raw === "string" ? raw.trim() : "";
|
|
82025
|
+
let value = "me";
|
|
82026
|
+
if (text !== "") {
|
|
82027
|
+
const equals = text.indexOf("=");
|
|
82028
|
+
const key = equals >= 0 ? text.slice(0, equals).trim().toLowerCase() : "assignee";
|
|
82029
|
+
const candidate = equals >= 0 ? text.slice(equals + 1).trim() : text;
|
|
82030
|
+
if (key === "assignee" && candidate !== "")
|
|
82031
|
+
value = candidate;
|
|
82032
|
+
}
|
|
82033
|
+
const lower = value.toLowerCase();
|
|
82034
|
+
if (lower === "unassigned" || lower === "any" || lower === "me")
|
|
82035
|
+
value = lower;
|
|
82036
|
+
return [{ type: "assignee", value }];
|
|
82037
|
+
}
|
|
82038
|
+
function migrateWorkflowMarkdown(markdown) {
|
|
82039
|
+
const match = FRONTMATTER_RE.exec(markdown);
|
|
82040
|
+
if (!match)
|
|
82041
|
+
return { markdown, changed: false };
|
|
82042
|
+
const document2 = import_yaml3.default.parseDocument(match[1] ?? "");
|
|
82043
|
+
if (!import_yaml3.default.isMap(document2.contents))
|
|
82044
|
+
return { markdown, changed: false };
|
|
82045
|
+
const prRecoveryLegacy = hasLegacyPrRecoveryKey(document2);
|
|
82046
|
+
const linearFilterLegacy = hasLegacyLinearFilter(document2);
|
|
82047
|
+
if (!prRecoveryLegacy && !linearFilterLegacy)
|
|
82048
|
+
return { markdown, changed: false };
|
|
82049
|
+
const body = match[2] ?? "";
|
|
82050
|
+
if (prRecoveryLegacy) {
|
|
82051
|
+
if (!document2.hasIn(["prRecovery"])) {
|
|
82052
|
+
const trackerEnabled = document2.getIn(["prTracker", "enabled"]);
|
|
82053
|
+
const enabled2 = trackerEnabled !== false;
|
|
82054
|
+
const maxRecovery = document2.getIn(["prTracker", "maxRecoveryAttempts"]);
|
|
82055
|
+
const ignoreChecks = document2.getIn(["ignoreCiChecks"]);
|
|
82056
|
+
document2.setIn(["prRecovery", "enabled"], enabled2);
|
|
82057
|
+
document2.setIn(["prRecovery", "fixCi"], enabled2);
|
|
82058
|
+
document2.setIn(["prRecovery", "fixConflicts"], enabled2);
|
|
82059
|
+
document2.setIn(["prRecovery", "maxRecoverySessions"], typeof maxRecovery === "number" ? maxRecovery : 3);
|
|
82060
|
+
document2.setIn(["prRecovery", "ignoreChecks"], import_yaml3.default.isSeq(ignoreChecks) ? ignoreChecks : []);
|
|
82061
|
+
}
|
|
82062
|
+
for (const key of LEGACY_TOP_LEVEL_KEYS)
|
|
82063
|
+
document2.deleteIn([key]);
|
|
82064
|
+
}
|
|
82065
|
+
if (linearFilterLegacy) {
|
|
82066
|
+
const markers = scalarFilterToMarkers(document2.getIn(["linear", "filter"]));
|
|
82067
|
+
document2.setIn(["linear", "filter"], document2.createNode(markers, { flow: false }));
|
|
82068
|
+
document2.deleteIn(["linear", "assignee"]);
|
|
82069
|
+
}
|
|
82070
|
+
document2.setIn(["version"], CURRENT_WORKFLOW_VERSION);
|
|
82071
|
+
const frontmatter = document2.toString({ flowCollectionPadding: false }).replace(/\n+$/, "");
|
|
82072
|
+
return { markdown: `---
|
|
82073
|
+
${frontmatter}
|
|
82074
|
+
---
|
|
82075
|
+
${body}`, changed: true };
|
|
82076
|
+
}
|
|
82077
|
+
var import_yaml3, LEGACY_TOP_LEVEL_KEYS;
|
|
82078
|
+
var init_pr_recovery = __esm(() => {
|
|
82079
|
+
init_default();
|
|
82080
|
+
init_schema();
|
|
82081
|
+
import_yaml3 = __toESM(require_dist(), 1);
|
|
82082
|
+
LEGACY_TOP_LEVEL_KEYS = [
|
|
82083
|
+
"prTracker",
|
|
82084
|
+
"fixCiOnFailure",
|
|
82085
|
+
"maxCiFixAttempts",
|
|
82086
|
+
"ciPollIntervalSeconds",
|
|
82087
|
+
"ignoreCiChecks",
|
|
82088
|
+
"ci"
|
|
82089
|
+
];
|
|
82090
|
+
});
|
|
82091
|
+
|
|
82013
82092
|
// packages/workflow/src/confirmation.ts
|
|
82014
82093
|
function matchesIndicator(indicator, ticket) {
|
|
82015
82094
|
if (!indicator || indicator.filter.length === 0)
|
|
@@ -82081,48 +82160,64 @@ function describeApprovalMarker(indicator) {
|
|
|
82081
82160
|
}
|
|
82082
82161
|
|
|
82083
82162
|
// packages/workflow/src/linear-filter.ts
|
|
82084
|
-
function
|
|
82085
|
-
const
|
|
82086
|
-
if (
|
|
82087
|
-
|
|
82088
|
-
|
|
82089
|
-
|
|
82090
|
-
|
|
82091
|
-
|
|
82092
|
-
|
|
82093
|
-
|
|
82094
|
-
|
|
82095
|
-
|
|
82163
|
+
function resolveLinearFilter(filter2) {
|
|
82164
|
+
const assigneeClauses = filter2.filter((marker) => marker.type === "assignee");
|
|
82165
|
+
if (assigneeClauses.length > 1) {
|
|
82166
|
+
throw new Error(`Invalid linear.filter: at most one "assignee" clause is allowed, found ${assigneeClauses.length}.`);
|
|
82167
|
+
}
|
|
82168
|
+
const requireAllLabels = [];
|
|
82169
|
+
const seenLabels = new Set;
|
|
82170
|
+
for (const marker of filter2) {
|
|
82171
|
+
if (marker.type !== "label")
|
|
82172
|
+
continue;
|
|
82173
|
+
if (seenLabels.has(marker.value))
|
|
82174
|
+
continue;
|
|
82175
|
+
seenLabels.add(marker.value);
|
|
82176
|
+
requireAllLabels.push(marker.value);
|
|
82096
82177
|
}
|
|
82178
|
+
const assigneeClause = assigneeClauses[0];
|
|
82179
|
+
if (!assigneeClause)
|
|
82180
|
+
return { requireAllLabels };
|
|
82181
|
+
const value = assigneeClause.value.trim();
|
|
82097
82182
|
const lower = value.toLowerCase();
|
|
82098
82183
|
if (lower === "any")
|
|
82099
|
-
return { anyAssignee: true };
|
|
82100
|
-
if (lower === "" || lower === "unassigned")
|
|
82101
|
-
return { assignee: "unassigned" };
|
|
82184
|
+
return { anyAssignee: true, requireAllLabels };
|
|
82185
|
+
if (lower === "" || lower === "unassigned") {
|
|
82186
|
+
return { assignee: "unassigned", requireAllLabels };
|
|
82187
|
+
}
|
|
82102
82188
|
if (lower === "me")
|
|
82103
|
-
return { assignee: "me" };
|
|
82104
|
-
return { assignee: value };
|
|
82189
|
+
return { assignee: "me", requireAllLabels };
|
|
82190
|
+
return { assignee: value, requireAllLabels };
|
|
82191
|
+
}
|
|
82192
|
+
function applyAssigneeOverride(filter2, assignee) {
|
|
82193
|
+
const trimmed = assignee.trim();
|
|
82194
|
+
if (trimmed === "")
|
|
82195
|
+
return filter2;
|
|
82196
|
+
return [
|
|
82197
|
+
...filter2.filter((marker) => marker.type !== "assignee"),
|
|
82198
|
+
{ type: "assignee", value: trimmed }
|
|
82199
|
+
];
|
|
82105
82200
|
}
|
|
82106
|
-
var SUPPORTED_KEYS;
|
|
82107
|
-
var init_linear_filter = __esm(() => {
|
|
82108
|
-
SUPPORTED_KEYS = new Set(["assignee"]);
|
|
82109
|
-
});
|
|
82110
82201
|
|
|
82111
82202
|
// packages/workflow/src/workflow.ts
|
|
82112
82203
|
var exports_workflow = {};
|
|
82113
82204
|
__export(exports_workflow, {
|
|
82114
82205
|
workflowPath: () => workflowPath,
|
|
82206
|
+
workflowNeedsUpgrade: () => workflowNeedsUpgrade,
|
|
82207
|
+
resolveLinearFilter: () => resolveLinearFilter,
|
|
82115
82208
|
resolveBaselineCommands: () => resolveBaselineCommands,
|
|
82116
82209
|
renderWorkflowPrompt: () => renderWorkflowPrompt,
|
|
82117
82210
|
renderTemplate: () => renderTemplate,
|
|
82211
|
+
readWorkflowVersion: () => readWorkflowVersion,
|
|
82118
82212
|
parseWorkflow: () => parseWorkflow,
|
|
82119
|
-
parseLinearFilter: () => parseLinearFilter,
|
|
82120
82213
|
normalizeWorkflowMarkdown: () => normalizeWorkflowMarkdown,
|
|
82214
|
+
migrateWorkflowMarkdown: () => migrateWorkflowMarkdown,
|
|
82121
82215
|
matchesIndicator: () => matchesIndicator,
|
|
82122
82216
|
loadWorkflow: () => loadWorkflow,
|
|
82123
82217
|
ensureWorkflow: () => ensureWorkflow,
|
|
82124
82218
|
describeApprovalMarker: () => describeApprovalMarker,
|
|
82125
82219
|
computeConfirmationFlags: () => computeConfirmationFlags,
|
|
82220
|
+
applyAssigneeOverride: () => applyAssigneeOverride,
|
|
82126
82221
|
WorkflowConfigSchema: () => WorkflowConfigSchema,
|
|
82127
82222
|
WORKFLOW_FILE: () => WORKFLOW_FILE,
|
|
82128
82223
|
FRONTMATTER_RE: () => FRONTMATTER_RE,
|
|
@@ -82141,7 +82236,7 @@ function parseWorkflow(text, path = "") {
|
|
|
82141
82236
|
const body = m[2] ?? "";
|
|
82142
82237
|
let raw;
|
|
82143
82238
|
try {
|
|
82144
|
-
raw =
|
|
82239
|
+
raw = import_yaml4.default.parse(yamlText, { schema: "core" });
|
|
82145
82240
|
} catch (err) {
|
|
82146
82241
|
throw new Error(`WORKFLOW.md frontmatter is not valid YAML.
|
|
82147
82242
|
` + (path ? ` File: ${path}
|
|
@@ -82219,14 +82314,32 @@ function applyAliases(cfg) {
|
|
|
82219
82314
|
cfg.setupScript = cfg.worktree.setup_script;
|
|
82220
82315
|
}
|
|
82221
82316
|
}
|
|
82222
|
-
|
|
82223
|
-
|
|
82224
|
-
|
|
82225
|
-
|
|
82226
|
-
|
|
82227
|
-
|
|
82228
|
-
|
|
82229
|
-
|
|
82317
|
+
}
|
|
82318
|
+
function readWorkflowVersion(text) {
|
|
82319
|
+
const match = FRONTMATTER_RE.exec(text);
|
|
82320
|
+
if (!match)
|
|
82321
|
+
return 0;
|
|
82322
|
+
try {
|
|
82323
|
+
const raw = import_yaml4.default.parse(match[1] ?? "", { schema: "core" });
|
|
82324
|
+
return typeof raw?.version === "number" ? raw.version : 0;
|
|
82325
|
+
} catch {
|
|
82326
|
+
return 0;
|
|
82327
|
+
}
|
|
82328
|
+
}
|
|
82329
|
+
function workflowNeedsUpgrade(text) {
|
|
82330
|
+
let migrated;
|
|
82331
|
+
try {
|
|
82332
|
+
migrated = migrateWorkflowMarkdown(text);
|
|
82333
|
+
} catch {
|
|
82334
|
+
return true;
|
|
82335
|
+
}
|
|
82336
|
+
if (migrated.changed)
|
|
82337
|
+
return true;
|
|
82338
|
+
try {
|
|
82339
|
+
parseWorkflow(normalizeWorkflowMarkdown(migrated.markdown).markdown);
|
|
82340
|
+
return false;
|
|
82341
|
+
} catch {
|
|
82342
|
+
return true;
|
|
82230
82343
|
}
|
|
82231
82344
|
}
|
|
82232
82345
|
function workflowPath(projectRoot, workflowFile) {
|
|
@@ -82240,9 +82353,11 @@ async function loadWorkflow(projectRoot, workflowFile, options = {}) {
|
|
|
82240
82353
|
return { config: config2, body: extractDefaultBody(), path };
|
|
82241
82354
|
}
|
|
82242
82355
|
const text = await file2.text();
|
|
82243
|
-
const
|
|
82244
|
-
|
|
82356
|
+
const migrated = migrateWorkflowMarkdown(text);
|
|
82357
|
+
const normalized = normalizeWorkflowMarkdown(migrated.markdown);
|
|
82358
|
+
if ((migrated.changed || normalized.changed) && options.persist) {
|
|
82245
82359
|
await Bun.write(path, normalized.markdown);
|
|
82360
|
+
}
|
|
82246
82361
|
return parseWorkflow(normalized.markdown, path);
|
|
82247
82362
|
}
|
|
82248
82363
|
async function ensureWorkflow(projectRoot, workflowFile) {
|
|
@@ -82278,17 +82393,18 @@ function renderWorkflowPrompt(workflow, ctx) {
|
|
|
82278
82393
|
};
|
|
82279
82394
|
return renderTemplate(workflow.body, fullCtx);
|
|
82280
82395
|
}
|
|
82281
|
-
var
|
|
82396
|
+
var import_yaml4, WORKFLOW_FILE = "WORKFLOW.md";
|
|
82282
82397
|
var init_workflow = __esm(() => {
|
|
82283
82398
|
init_schema();
|
|
82284
82399
|
init_default();
|
|
82285
82400
|
init_wizard();
|
|
82286
82401
|
init_normalize();
|
|
82402
|
+
init_pr_recovery();
|
|
82287
82403
|
init_schema();
|
|
82288
82404
|
init_default();
|
|
82289
|
-
init_linear_filter();
|
|
82290
82405
|
init_normalize();
|
|
82291
|
-
|
|
82406
|
+
init_pr_recovery();
|
|
82407
|
+
import_yaml4 = __toESM(require_dist(), 1);
|
|
82292
82408
|
});
|
|
82293
82409
|
|
|
82294
82410
|
// packages/core/src/repo/index.ts
|
|
@@ -83252,8 +83368,11 @@ function buildFromAnswers(mode, answers, build = buildWorkflowMarkdown) {
|
|
|
83252
83368
|
} else {
|
|
83253
83369
|
assignee = assigneeChoice;
|
|
83254
83370
|
}
|
|
83371
|
+
const existing = Array.isArray(values2["linear.filter"]) ? values2["linear.filter"] : [];
|
|
83255
83372
|
if (assignee)
|
|
83256
|
-
values2["linear.filter"] =
|
|
83373
|
+
values2["linear.filter"] = applyAssigneeOverride(existing, assignee);
|
|
83374
|
+
else if (existing.length > 0)
|
|
83375
|
+
values2["linear.filter"] = existing;
|
|
83257
83376
|
}
|
|
83258
83377
|
delete values2[LINEAR_ASSIGNEE_CHOICE_FIELD_ID];
|
|
83259
83378
|
delete values2[LINEAR_ASSIGNEE_VALUE_FIELD_ID];
|
|
@@ -83341,8 +83460,8 @@ function computeEditing(field, stored, multilineFallback = "") {
|
|
|
83341
83460
|
return {
|
|
83342
83461
|
draft: textLike && stored !== undefined ? String(stored) : field.spec.kind === "multiline" ? typeof stored === "string" ? stored : multilineFallback : "",
|
|
83343
83462
|
optionIndex: initialOptionIndex(field, stored),
|
|
83344
|
-
listItems: field.spec.kind === "list" && Array.isArray(stored) ?
|
|
83345
|
-
selected: field.spec.kind === "multiselect" && Array.isArray(stored) ? new Set(stored) : new Set
|
|
83463
|
+
listItems: field.spec.kind === "list" && Array.isArray(stored) ? stored.filter((item) => typeof item === "string") : [],
|
|
83464
|
+
selected: field.spec.kind === "multiselect" && Array.isArray(stored) ? new Set(stored.filter((item) => typeof item === "string")) : new Set
|
|
83346
83465
|
};
|
|
83347
83466
|
}
|
|
83348
83467
|
function initialOptionIndex(field, stored) {
|
|
@@ -83471,6 +83590,12 @@ function SetupWizard({
|
|
|
83471
83590
|
setVisited((prev) => new Set(prev).add(nextFields[index + 1].id));
|
|
83472
83591
|
goTo(index + 1, source);
|
|
83473
83592
|
};
|
|
83593
|
+
import_react22.useEffect(() => {
|
|
83594
|
+
if (mode !== null && !building && fields.length === 0) {
|
|
83595
|
+
onComplete(buildFromAnswers(mode, valuesToWrite(answers), buildMarkdown));
|
|
83596
|
+
exit();
|
|
83597
|
+
}
|
|
83598
|
+
}, [mode, building, fields.length]);
|
|
83474
83599
|
use_input_default((input, key) => {
|
|
83475
83600
|
if (key.escape) {
|
|
83476
83601
|
onCancel?.();
|
|
@@ -84297,6 +84422,7 @@ var import_react22, jsx_dev_runtime, REPO_ANSWER_IDS, MODE_OPTIONS, INDICATOR_OP
|
|
|
84297
84422
|
var init_SetupWizard = __esm(async () => {
|
|
84298
84423
|
init_version();
|
|
84299
84424
|
init_wizard();
|
|
84425
|
+
init_workflow();
|
|
84300
84426
|
init_fields();
|
|
84301
84427
|
await init_build2();
|
|
84302
84428
|
import_react22 = __toESM(require_react(), 1);
|
|
@@ -84429,9 +84555,6 @@ var init_migrations = __esm(() => {
|
|
|
84429
84555
|
"preExistingErrorCheck.commands",
|
|
84430
84556
|
"preExistingErrorCheck.baseBranch",
|
|
84431
84557
|
"preExistingErrorCheck.label",
|
|
84432
|
-
"prTracker.enabled",
|
|
84433
|
-
"prTracker.maxRecoveryAttempts",
|
|
84434
|
-
"prTracker.advanceMergedToDone",
|
|
84435
84558
|
"openspec.reviewPhase.enabled",
|
|
84436
84559
|
"openspec.reviewPhase.maxRounds",
|
|
84437
84560
|
"openspec.reviewPhase.reviewerModel",
|
|
@@ -84457,6 +84580,11 @@ var init_migrations = __esm(() => {
|
|
|
84457
84580
|
version: 5,
|
|
84458
84581
|
description: "A new `linear.specAttachmentRevisions` setting controls the sealed " + "design attachment: 'replace' (default) overwrites the single canonical " + "attachment in place; 'append' publishes each change as a new " + "'Ralph design #N' attachment. Config-file-only \u2014 set it in WORKFLOW.md " + "if you want the append audit trail.",
|
|
84459
84582
|
fields: []
|
|
84583
|
+
},
|
|
84584
|
+
{
|
|
84585
|
+
version: 6,
|
|
84586
|
+
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
|
+
fields: ["prRecovery.enabled", "prRecovery.maxRecoverySessions", "prRecovery.ignoreChecks"]
|
|
84460
84588
|
}
|
|
84461
84589
|
];
|
|
84462
84590
|
LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max2, migration) => Math.max(max2, migration.version), 0);
|
|
@@ -84588,7 +84716,7 @@ var init_project_detect = __esm(() => {
|
|
|
84588
84716
|
// apps/init/src/index.ts
|
|
84589
84717
|
var exports_src = {};
|
|
84590
84718
|
__export(exports_src, {
|
|
84591
|
-
|
|
84719
|
+
maybeUpgradeWorkflow: () => maybeUpgradeWorkflow,
|
|
84592
84720
|
maybeRunSetupWizard: () => maybeRunSetupWizard,
|
|
84593
84721
|
main: () => main
|
|
84594
84722
|
});
|
|
@@ -84681,6 +84809,22 @@ async function maybeRunSetupWizard(projectRoot, workflowFile) {
|
|
|
84681
84809
|
...Object.keys(detected).length > 0 ? { initialValues: detected } : {}
|
|
84682
84810
|
});
|
|
84683
84811
|
}
|
|
84812
|
+
async function maybeUpgradeWorkflow(projectRoot, workflowFile) {
|
|
84813
|
+
const root = projectRoot ?? await findProjectRoot();
|
|
84814
|
+
const path = workflowPath(root, workflowFile);
|
|
84815
|
+
const file2 = Bun.file(path);
|
|
84816
|
+
if (!await file2.exists())
|
|
84817
|
+
return false;
|
|
84818
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
84819
|
+
return false;
|
|
84820
|
+
if (!workflowNeedsUpgrade(await file2.text()))
|
|
84821
|
+
return false;
|
|
84822
|
+
process.stdout.write(`WORKFLOW.md needs an upgrade. Starting init\u2026
|
|
84823
|
+
`);
|
|
84824
|
+
const initArgv = ["--project-root", root, ...workflowFile ? ["--workflow", workflowFile] : []];
|
|
84825
|
+
await main(initArgv);
|
|
84826
|
+
return true;
|
|
84827
|
+
}
|
|
84684
84828
|
function initialValuesFromConfig(config2) {
|
|
84685
84829
|
const values2 = {};
|
|
84686
84830
|
if (config2.project.name)
|
|
@@ -84702,13 +84846,16 @@ function initialValuesFromConfig(config2) {
|
|
|
84702
84846
|
values2["concurrency"] = config2.concurrency;
|
|
84703
84847
|
values2["createPrOnSuccess"] = config2.createPrOnSuccess;
|
|
84704
84848
|
values2["prBaseBranch"] = config2.prBaseBranch;
|
|
84705
|
-
values2["
|
|
84849
|
+
values2["prRecovery.enabled"] = config2.prRecovery.enabled;
|
|
84850
|
+
values2["prRecovery.fixCi"] = config2.prRecovery.fixCi;
|
|
84851
|
+
values2["prRecovery.maxRecoverySessions"] = config2.prRecovery.maxRecoverySessions;
|
|
84706
84852
|
values2["useWorktree"] = config2.useWorktree;
|
|
84707
84853
|
if (config2.linear.team)
|
|
84708
84854
|
values2["linear.team"] = config2.linear.team;
|
|
84709
|
-
if (config2.linear.filter) {
|
|
84710
|
-
|
|
84711
|
-
const
|
|
84855
|
+
if (config2.linear.filter && config2.linear.filter.length > 0) {
|
|
84856
|
+
values2["linear.filter"] = config2.linear.filter;
|
|
84857
|
+
const assigneeMarker = config2.linear.filter.find((marker) => marker.type === "assignee");
|
|
84858
|
+
const assignee = assigneeMarker?.value.trim() ?? "";
|
|
84712
84859
|
if (assignee === "me" || assignee === "any" || assignee === "unassigned") {
|
|
84713
84860
|
values2["linear.assigneeChoice"] = assignee;
|
|
84714
84861
|
} else if (assignee !== "") {
|
|
@@ -84766,7 +84913,7 @@ async function promptMigrate(fromVersion) {
|
|
|
84766
84913
|
return choice;
|
|
84767
84914
|
}
|
|
84768
84915
|
async function editExisting(projectRoot, path, config2, workflowFile, onlyFields) {
|
|
84769
|
-
const existing = await Bun.file(path).text();
|
|
84916
|
+
const { markdown: existing } = migrateWorkflowMarkdown(await Bun.file(path).text());
|
|
84770
84917
|
const detectedRepo = await detectRepoIdentity(projectRoot);
|
|
84771
84918
|
const detected = await detectInitialValues(projectRoot);
|
|
84772
84919
|
const wrote = await runSetupWizard(projectRoot, {
|
|
@@ -84823,14 +84970,15 @@ Setup cancelled \u2014 no file written.
|
|
|
84823
84970
|
`);
|
|
84824
84971
|
return 0;
|
|
84825
84972
|
}
|
|
84826
|
-
|
|
84827
|
-
|
|
84973
|
+
const diskVersion = readWorkflowVersion(await Bun.file(path).text());
|
|
84974
|
+
if (needsMigration(diskVersion)) {
|
|
84975
|
+
const choice2 = await promptMigrate(diskVersion);
|
|
84828
84976
|
if (choice2 === "exit") {
|
|
84829
84977
|
process.stdout.write(`Exited \u2014 WORKFLOW.md unchanged.
|
|
84830
84978
|
`);
|
|
84831
84979
|
return 0;
|
|
84832
84980
|
}
|
|
84833
|
-
const onlyFields = choice2 === "diff" ? fieldsAddedSince(
|
|
84981
|
+
const onlyFields = choice2 === "diff" ? fieldsAddedSince(diskVersion) : undefined;
|
|
84834
84982
|
return editExisting(projectRoot, path, config2, workflowFile, onlyFields);
|
|
84835
84983
|
}
|
|
84836
84984
|
const choice = await promptEditOrExit();
|
|
@@ -98288,7 +98436,14 @@ function scrubClaudeEnv(env3 = process.env) {
|
|
|
98288
98436
|
}
|
|
98289
98437
|
return copy;
|
|
98290
98438
|
}
|
|
98291
|
-
|
|
98439
|
+
function scrubGithubAppTokenEnv(env3 = process.env) {
|
|
98440
|
+
const copy = { ...env3 };
|
|
98441
|
+
for (const key of GITHUB_APP_TOKEN_KEYS) {
|
|
98442
|
+
delete copy[key];
|
|
98443
|
+
}
|
|
98444
|
+
return copy;
|
|
98445
|
+
}
|
|
98446
|
+
var CLAUDE_ENV_KEYS_TO_SCRUB, GITHUB_APP_TOKEN_KEYS;
|
|
98292
98447
|
var init_env = __esm(() => {
|
|
98293
98448
|
CLAUDE_ENV_KEYS_TO_SCRUB = [
|
|
98294
98449
|
"CLAUDECODE",
|
|
@@ -98297,6 +98452,7 @@ var init_env = __esm(() => {
|
|
|
98297
98452
|
"CLAUDE_CODE_ENTRYPOINT",
|
|
98298
98453
|
"AI_AGENT"
|
|
98299
98454
|
];
|
|
98455
|
+
GITHUB_APP_TOKEN_KEYS = ["GITHUB_TOKEN"];
|
|
98300
98456
|
});
|
|
98301
98457
|
|
|
98302
98458
|
// packages/engine/src/preflight/gh.ts
|
|
@@ -98304,6 +98460,7 @@ async function checkGhAuth() {
|
|
|
98304
98460
|
try {
|
|
98305
98461
|
const proc = spawn({
|
|
98306
98462
|
cmd: ["gh", "auth", "status"],
|
|
98463
|
+
env: scrubGithubAppTokenEnv(),
|
|
98307
98464
|
stdout: "ignore",
|
|
98308
98465
|
stderr: "ignore"
|
|
98309
98466
|
});
|
|
@@ -98319,6 +98476,7 @@ async function checkGhAuth() {
|
|
|
98319
98476
|
var GH_AUTH_FAIL_MESSAGE = "gh is not authenticated. Run `gh auth login` (or set GH_TOKEN), then restart the agent.";
|
|
98320
98477
|
var init_gh = __esm(() => {
|
|
98321
98478
|
init_spawn();
|
|
98479
|
+
init_env();
|
|
98322
98480
|
});
|
|
98323
98481
|
|
|
98324
98482
|
// packages/engine/src/preflight/claude.ts
|
|
@@ -98348,19 +98506,69 @@ var init_claude = __esm(() => {
|
|
|
98348
98506
|
NOT_LOGGED_IN_RE = /Not logged in|Please run \/login/;
|
|
98349
98507
|
});
|
|
98350
98508
|
|
|
98509
|
+
// packages/engine/src/preflight/repo.ts
|
|
98510
|
+
async function checkRepoWriteAccess(cwd2) {
|
|
98511
|
+
let blob = "";
|
|
98512
|
+
try {
|
|
98513
|
+
const proc = spawn({
|
|
98514
|
+
cmd: [
|
|
98515
|
+
"gh",
|
|
98516
|
+
"api",
|
|
98517
|
+
"-X",
|
|
98518
|
+
"POST",
|
|
98519
|
+
"repos/{owner}/{repo}/git/refs",
|
|
98520
|
+
"-f",
|
|
98521
|
+
`ref=${PROBE_REF}`,
|
|
98522
|
+
"-f",
|
|
98523
|
+
`sha=${ZERO_SHA}`
|
|
98524
|
+
],
|
|
98525
|
+
cwd: cwd2,
|
|
98526
|
+
env: scrubGithubAppTokenEnv(),
|
|
98527
|
+
stdout: "pipe",
|
|
98528
|
+
stderr: "pipe"
|
|
98529
|
+
});
|
|
98530
|
+
const stdout = await new Response(proc.stdout).text();
|
|
98531
|
+
const stderr = await new Response(proc.stderr).text();
|
|
98532
|
+
await proc.exited;
|
|
98533
|
+
blob = `${stdout}
|
|
98534
|
+
${stderr}`;
|
|
98535
|
+
} catch {
|
|
98536
|
+
return { ok: true };
|
|
98537
|
+
}
|
|
98538
|
+
if (HAS_WRITE_RE.test(blob))
|
|
98539
|
+
return { ok: true };
|
|
98540
|
+
if (NO_WRITE_RE.test(blob))
|
|
98541
|
+
return { ok: false, tool: "repo", message: REPO_WRITE_FAIL_MESSAGE };
|
|
98542
|
+
return { ok: true };
|
|
98543
|
+
}
|
|
98544
|
+
var REPO_WRITE_FAIL_MESSAGE, PROBE_REF = "refs/heads/ralphy-preflight-write-probe", ZERO_SHA = "0000000000000000000000000000000000000000", HAS_WRITE_RE, NO_WRITE_RE;
|
|
98545
|
+
var init_repo2 = __esm(() => {
|
|
98546
|
+
init_spawn();
|
|
98547
|
+
init_env();
|
|
98548
|
+
REPO_WRITE_FAIL_MESSAGE = "No write access to this repository \u2014 the active credential can read it but cannot " + "push, so every issue would fail at PR creation (and be re-queued). Ralphy uses gh's " + "auth for all GitHub operations: grant push access to `GH_TOKEN` (or `gh auth login`), " + "or, if you rely on a fine-grained PAT, give it Contents: write + Pull requests: write " + "+ Commit statuses: read. Then restart the agent.";
|
|
98549
|
+
HAS_WRITE_RE = /"status":\s*"422"|Object does not exist|Unprocessable/i;
|
|
98550
|
+
NO_WRITE_RE = /"status":\s*"403"|not accessible by personal access token|Write access to repository not granted/i;
|
|
98551
|
+
});
|
|
98552
|
+
|
|
98351
98553
|
// packages/engine/src/preflight/run.ts
|
|
98352
|
-
async function runPreflight() {
|
|
98554
|
+
async function runPreflight(opts = {}) {
|
|
98353
98555
|
const gh = await checkGhAuth();
|
|
98354
98556
|
if (!gh.ok)
|
|
98355
98557
|
return gh;
|
|
98356
98558
|
const claude = await checkClaudeAuth();
|
|
98357
98559
|
if (!claude.ok)
|
|
98358
98560
|
return claude;
|
|
98561
|
+
if (opts.requireRepoWrite && opts.repoCwd) {
|
|
98562
|
+
const repo = await checkRepoWriteAccess(opts.repoCwd);
|
|
98563
|
+
if (!repo.ok)
|
|
98564
|
+
return repo;
|
|
98565
|
+
}
|
|
98359
98566
|
return { ok: true };
|
|
98360
98567
|
}
|
|
98361
98568
|
var init_run = __esm(() => {
|
|
98362
98569
|
init_gh();
|
|
98363
98570
|
init_claude();
|
|
98571
|
+
init_repo2();
|
|
98364
98572
|
});
|
|
98365
98573
|
|
|
98366
98574
|
// packages/engine/src/preflight/index.ts
|
|
@@ -98368,6 +98576,7 @@ var init_preflight = __esm(() => {
|
|
|
98368
98576
|
init_env();
|
|
98369
98577
|
init_gh();
|
|
98370
98578
|
init_claude();
|
|
98579
|
+
init_repo2();
|
|
98371
98580
|
init_run();
|
|
98372
98581
|
});
|
|
98373
98582
|
|
|
@@ -99485,6 +99694,7 @@ var init_flow_machine = __esm(() => {
|
|
|
99485
99694
|
AWAITING_DETECTED: "awaiting",
|
|
99486
99695
|
CONFLICT_DETECTED: "conflict-fix",
|
|
99487
99696
|
CI_FAILED_DETECTED: "ci-fix",
|
|
99697
|
+
PR_OPENED: "awaiting-ci",
|
|
99488
99698
|
WORKER_SUCCEEDED: "done",
|
|
99489
99699
|
WORKER_FAILED: "error",
|
|
99490
99700
|
PREEMPT: {
|
|
@@ -99504,7 +99714,7 @@ var init_flow_machine = __esm(() => {
|
|
|
99504
99714
|
},
|
|
99505
99715
|
"conflict-fix": {
|
|
99506
99716
|
on: {
|
|
99507
|
-
WORKER_SUCCEEDED: "
|
|
99717
|
+
WORKER_SUCCEEDED: "awaiting-ci",
|
|
99508
99718
|
WORKER_FAILED: "error",
|
|
99509
99719
|
PREEMPT: {
|
|
99510
99720
|
target: "preempting",
|
|
@@ -99523,7 +99733,7 @@ var init_flow_machine = __esm(() => {
|
|
|
99523
99733
|
},
|
|
99524
99734
|
"ci-fix": {
|
|
99525
99735
|
on: {
|
|
99526
|
-
WORKER_SUCCEEDED: "
|
|
99736
|
+
WORKER_SUCCEEDED: "awaiting-ci",
|
|
99527
99737
|
WORKER_FAILED: "error",
|
|
99528
99738
|
PREEMPT: {
|
|
99529
99739
|
target: "preempting",
|
|
@@ -99551,9 +99761,31 @@ var init_flow_machine = __esm(() => {
|
|
|
99551
99761
|
}
|
|
99552
99762
|
}
|
|
99553
99763
|
},
|
|
99764
|
+
"awaiting-ci": {
|
|
99765
|
+
on: {
|
|
99766
|
+
PR_PASSED: "done",
|
|
99767
|
+
CONFLICT_DETECTED: "conflict-fix",
|
|
99768
|
+
CI_FAILED_DETECTED: "ci-fix",
|
|
99769
|
+
REVIEW_TRIGGERED: "review",
|
|
99770
|
+
PREEMPT: {
|
|
99771
|
+
target: "preempting",
|
|
99772
|
+
actions: import_xstate_development_cjs.assign({
|
|
99773
|
+
pendingAssignment: ({ event }) => event.newAssignment
|
|
99774
|
+
})
|
|
99775
|
+
},
|
|
99776
|
+
WORKER_SPAWNED: {
|
|
99777
|
+
actions: import_xstate_development_cjs.assign(({ event }) => ({
|
|
99778
|
+
worker: event.worker,
|
|
99779
|
+
teardown: event.teardown ?? undefined,
|
|
99780
|
+
currentAssignment: event.assignment
|
|
99781
|
+
}))
|
|
99782
|
+
}
|
|
99783
|
+
}
|
|
99784
|
+
},
|
|
99554
99785
|
review: {
|
|
99555
99786
|
on: {
|
|
99556
99787
|
WORKER_SUCCEEDED: "done",
|
|
99788
|
+
PR_OPENED: "awaiting-ci",
|
|
99557
99789
|
WORKER_FAILED: "error",
|
|
99558
99790
|
WORKER_SPAWNED: {
|
|
99559
99791
|
actions: import_xstate_development_cjs.assign(({ event }) => ({
|
|
@@ -99599,7 +99831,11 @@ var init_flow_machine = __esm(() => {
|
|
|
99599
99831
|
target: "ci-fix"
|
|
99600
99832
|
},
|
|
99601
99833
|
{
|
|
99602
|
-
guard: ({ context }) => context.pendingAssignment?.flowId === "awaiting-ci"
|
|
99834
|
+
guard: ({ context }) => context.pendingAssignment?.flowId === "awaiting-ci",
|
|
99835
|
+
target: "awaiting-ci"
|
|
99836
|
+
},
|
|
99837
|
+
{
|
|
99838
|
+
guard: ({ context }) => context.pendingAssignment?.flowId === "confirmation",
|
|
99603
99839
|
target: "awaiting"
|
|
99604
99840
|
},
|
|
99605
99841
|
{
|
|
@@ -101030,6 +101266,36 @@ var init_useLoop = __esm(() => {
|
|
|
101030
101266
|
import_react57 = __toESM(require_react(), 1);
|
|
101031
101267
|
});
|
|
101032
101268
|
|
|
101269
|
+
// packages/ui-shared/src/useHoldToClose.ts
|
|
101270
|
+
function useHoldToClose({ finished, hold, onClose }) {
|
|
101271
|
+
const { exit } = use_app_default();
|
|
101272
|
+
const { isRawModeSupported } = use_stdin_default();
|
|
101273
|
+
const [awaitingClose, setAwaitingClose] = import_react58.useState(false);
|
|
101274
|
+
const close = () => {
|
|
101275
|
+
onClose?.();
|
|
101276
|
+
exit();
|
|
101277
|
+
};
|
|
101278
|
+
import_react58.useEffect(() => {
|
|
101279
|
+
if (!finished)
|
|
101280
|
+
return;
|
|
101281
|
+
if (hold && isRawModeSupported) {
|
|
101282
|
+
setAwaitingClose(true);
|
|
101283
|
+
return;
|
|
101284
|
+
}
|
|
101285
|
+
close();
|
|
101286
|
+
}, [finished, hold, isRawModeSupported]);
|
|
101287
|
+
use_input_default((_input, key) => {
|
|
101288
|
+
if (key.return)
|
|
101289
|
+
close();
|
|
101290
|
+
}, { isActive: awaitingClose });
|
|
101291
|
+
return { awaitingClose };
|
|
101292
|
+
}
|
|
101293
|
+
var import_react58;
|
|
101294
|
+
var init_useHoldToClose = __esm(async () => {
|
|
101295
|
+
await init_build2();
|
|
101296
|
+
import_react58 = __toESM(require_react(), 1);
|
|
101297
|
+
});
|
|
101298
|
+
|
|
101033
101299
|
// apps/loop/src/components/TaskLoop.tsx
|
|
101034
101300
|
function LogLine({ entry, verbose }) {
|
|
101035
101301
|
switch (entry.kind) {
|
|
@@ -101081,10 +101347,10 @@ function handleSteerKeyInput(key, history, currentIndex) {
|
|
|
101081
101347
|
return navigateHistory(history, currentIndex, dir);
|
|
101082
101348
|
}
|
|
101083
101349
|
function SteerInput({ onSubmit }) {
|
|
101084
|
-
const [inputKey, setInputKey] =
|
|
101085
|
-
const [defaultValue, setDefaultValue] =
|
|
101086
|
-
const historyRef =
|
|
101087
|
-
const historyIndexRef =
|
|
101350
|
+
const [inputKey, setInputKey] = import_react59.useState(0);
|
|
101351
|
+
const [defaultValue, setDefaultValue] = import_react59.useState("");
|
|
101352
|
+
const historyRef = import_react59.useRef([]);
|
|
101353
|
+
const historyIndexRef = import_react59.useRef(-1);
|
|
101088
101354
|
use_input_default((_input, key) => {
|
|
101089
101355
|
const result2 = handleSteerKeyInput(key, historyRef.current, historyIndexRef.current);
|
|
101090
101356
|
if (result2) {
|
|
@@ -101113,21 +101379,19 @@ function SteerInput({ onSubmit }) {
|
|
|
101113
101379
|
}, undefined, true, undefined, this);
|
|
101114
101380
|
}
|
|
101115
101381
|
function TaskLoop({ opts }) {
|
|
101116
|
-
const { exit } = use_app_default();
|
|
101117
101382
|
const loop = useLoop(opts);
|
|
101118
101383
|
const { isRawModeSupported } = use_stdin_default();
|
|
101119
101384
|
const { resizeKey } = useTerminalSize();
|
|
101120
|
-
const bannerItem =
|
|
101121
|
-
const [stateDir] =
|
|
101122
|
-
const feedItems =
|
|
101385
|
+
const bannerItem = import_react59.useRef({ id: "__banner__", kind: "banner" });
|
|
101386
|
+
const [stateDir] = import_react59.useState(() => getLayout().taskStateDir(opts.name));
|
|
101387
|
+
const feedItems = import_react59.useMemo(() => [
|
|
101123
101388
|
bannerItem.current,
|
|
101124
101389
|
...loop.logLines.map((e) => ({ id: e.id, kind: "entry", entry: e }))
|
|
101125
101390
|
], [loop.logLines]);
|
|
101126
|
-
|
|
101127
|
-
|
|
101128
|
-
|
|
101129
|
-
|
|
101130
|
-
}, [loop.isRunning, exit]);
|
|
101391
|
+
const { awaitingClose } = useHoldToClose({
|
|
101392
|
+
finished: !loop.isRunning,
|
|
101393
|
+
hold: loop.stopReason !== null && loop.stopReason !== "completed"
|
|
101394
|
+
});
|
|
101131
101395
|
if (!loop.state)
|
|
101132
101396
|
return null;
|
|
101133
101397
|
return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
|
|
@@ -101188,13 +101452,21 @@ function TaskLoop({ opts }) {
|
|
|
101188
101452
|
maxCostUsd: opts.maxCostUsd,
|
|
101189
101453
|
maxRuntimeMinutes: opts.maxRuntimeMinutes,
|
|
101190
101454
|
consecutiveFailures: loop.consecutiveFailures
|
|
101191
|
-
}, undefined, false, undefined, this)
|
|
101455
|
+
}, undefined, false, undefined, this),
|
|
101456
|
+
awaitingClose && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
|
|
101457
|
+
color: "cyan",
|
|
101458
|
+
children: [
|
|
101459
|
+
`
|
|
101460
|
+
`,
|
|
101461
|
+
"Press Enter to close\u2026"
|
|
101462
|
+
]
|
|
101463
|
+
}, undefined, true, undefined, this)
|
|
101192
101464
|
]
|
|
101193
101465
|
}, undefined, true, undefined, this)
|
|
101194
101466
|
]
|
|
101195
101467
|
}, resizeKey, true, undefined, this);
|
|
101196
101468
|
}
|
|
101197
|
-
var
|
|
101469
|
+
var import_react59, jsx_dev_runtime8;
|
|
101198
101470
|
var init_TaskLoop = __esm(async () => {
|
|
101199
101471
|
init_useLoop();
|
|
101200
101472
|
init_useTerminalSize();
|
|
@@ -101206,9 +101478,10 @@ var init_TaskLoop = __esm(async () => {
|
|
|
101206
101478
|
init_IterationHeader(),
|
|
101207
101479
|
init_FeedLine(),
|
|
101208
101480
|
init_StatusBar(),
|
|
101209
|
-
init_StopMessage()
|
|
101481
|
+
init_StopMessage(),
|
|
101482
|
+
init_useHoldToClose()
|
|
101210
101483
|
]);
|
|
101211
|
-
|
|
101484
|
+
import_react59 = __toESM(require_react(), 1);
|
|
101212
101485
|
jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
|
|
101213
101486
|
});
|
|
101214
101487
|
|
|
@@ -101216,7 +101489,7 @@ var init_TaskLoop = __esm(async () => {
|
|
|
101216
101489
|
import { join as join17 } from "path";
|
|
101217
101490
|
function ExitAfterRender({ children }) {
|
|
101218
101491
|
const { exit } = use_app_default();
|
|
101219
|
-
|
|
101492
|
+
import_react60.useEffect(() => {
|
|
101220
101493
|
exit();
|
|
101221
101494
|
}, [exit]);
|
|
101222
101495
|
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
|
|
@@ -101225,7 +101498,7 @@ function ExitAfterRender({ children }) {
|
|
|
101225
101498
|
}
|
|
101226
101499
|
function ErrorMessage({ message }) {
|
|
101227
101500
|
const { exit } = use_app_default();
|
|
101228
|
-
|
|
101501
|
+
import_react60.useEffect(() => {
|
|
101229
101502
|
process.exitCode = 1;
|
|
101230
101503
|
exit();
|
|
101231
101504
|
}, [exit]);
|
|
@@ -101304,7 +101577,7 @@ function App2({ args, taskPhase }) {
|
|
|
101304
101577
|
}
|
|
101305
101578
|
}
|
|
101306
101579
|
}
|
|
101307
|
-
var
|
|
101580
|
+
var import_react60, jsx_dev_runtime9;
|
|
101308
101581
|
var init_App2 = __esm(async () => {
|
|
101309
101582
|
init_store();
|
|
101310
101583
|
init_context();
|
|
@@ -101314,7 +101587,7 @@ var init_App2 = __esm(async () => {
|
|
|
101314
101587
|
init_TaskStatus(),
|
|
101315
101588
|
init_TaskLoop()
|
|
101316
101589
|
]);
|
|
101317
|
-
|
|
101590
|
+
import_react60 = __toESM(require_react(), 1);
|
|
101318
101591
|
jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
|
|
101319
101592
|
});
|
|
101320
101593
|
|
|
@@ -101942,7 +102215,7 @@ async function main2(argv) {
|
|
|
101942
102215
|
await ensureRalphGitignore(projectRoot);
|
|
101943
102216
|
}
|
|
101944
102217
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
101945
|
-
const { waitUntilExit } = render_default(
|
|
102218
|
+
const { waitUntilExit } = render_default(import_react61.createElement(App2, { args }));
|
|
101946
102219
|
await waitUntilExit();
|
|
101947
102220
|
});
|
|
101948
102221
|
return typeof process.exitCode === "number" ? process.exitCode : 0;
|
|
@@ -101970,7 +102243,7 @@ async function taskMain(argv) {
|
|
|
101970
102243
|
await mkdir6(join20(tasksDir, args.name), { recursive: true });
|
|
101971
102244
|
await ensureRalphGitignore(projectRoot);
|
|
101972
102245
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
101973
|
-
const { waitUntilExit } = render_default(
|
|
102246
|
+
const { waitUntilExit } = render_default(import_react61.createElement(App2, {
|
|
101974
102247
|
args,
|
|
101975
102248
|
taskPhase: args.phase
|
|
101976
102249
|
}));
|
|
@@ -101978,7 +102251,7 @@ async function taskMain(argv) {
|
|
|
101978
102251
|
});
|
|
101979
102252
|
return typeof process.exitCode === "number" ? process.exitCode : 0;
|
|
101980
102253
|
}
|
|
101981
|
-
var
|
|
102254
|
+
var import_react61;
|
|
101982
102255
|
var init_src7 = __esm(async () => {
|
|
101983
102256
|
init_context();
|
|
101984
102257
|
init_layout();
|
|
@@ -101991,7 +102264,7 @@ var init_src7 = __esm(async () => {
|
|
|
101991
102264
|
init_build2(),
|
|
101992
102265
|
init_App2()
|
|
101993
102266
|
]);
|
|
101994
|
-
|
|
102267
|
+
import_react61 = __toESM(require_react(), 1);
|
|
101995
102268
|
});
|
|
101996
102269
|
|
|
101997
102270
|
// apps/agent/src/cli.ts
|
|
@@ -102045,14 +102318,12 @@ async function parseAgentArgs(argv) {
|
|
|
102045
102318
|
...common2,
|
|
102046
102319
|
mode: "agent",
|
|
102047
102320
|
linearTeam: "",
|
|
102048
|
-
linearFilter: "",
|
|
102049
102321
|
linearAssignee: "",
|
|
102050
102322
|
pollInterval: 0,
|
|
102051
102323
|
concurrency: 0,
|
|
102052
102324
|
worktree: false,
|
|
102053
102325
|
indicators: {},
|
|
102054
102326
|
createPr: false,
|
|
102055
|
-
fixCi: false,
|
|
102056
102327
|
stackPrs: false,
|
|
102057
102328
|
codeReview: false,
|
|
102058
102329
|
maxTickets: 0,
|
|
@@ -102067,7 +102338,6 @@ async function parseAgentArgs(argv) {
|
|
|
102067
102338
|
};
|
|
102068
102339
|
const state = emptyParseState();
|
|
102069
102340
|
let expectLinearTeam = false;
|
|
102070
|
-
let expectLinearFilter = false;
|
|
102071
102341
|
let expectLinearAssignee = false;
|
|
102072
102342
|
let expectPollInterval = false;
|
|
102073
102343
|
let expectConcurrency = false;
|
|
@@ -102081,11 +102351,6 @@ async function parseAgentArgs(argv) {
|
|
|
102081
102351
|
expectLinearTeam = false;
|
|
102082
102352
|
continue;
|
|
102083
102353
|
}
|
|
102084
|
-
if (expectLinearFilter) {
|
|
102085
|
-
result2.linearFilter = arg;
|
|
102086
|
-
expectLinearFilter = false;
|
|
102087
|
-
continue;
|
|
102088
|
-
}
|
|
102089
102354
|
if (expectLinearAssignee) {
|
|
102090
102355
|
result2.linearAssignee = arg;
|
|
102091
102356
|
expectLinearAssignee = false;
|
|
@@ -102131,9 +102396,6 @@ async function parseAgentArgs(argv) {
|
|
|
102131
102396
|
case "--linear-team":
|
|
102132
102397
|
expectLinearTeam = true;
|
|
102133
102398
|
break;
|
|
102134
|
-
case "--linear-filter":
|
|
102135
|
-
expectLinearFilter = true;
|
|
102136
|
-
break;
|
|
102137
102399
|
case "--linear-assignee":
|
|
102138
102400
|
expectLinearAssignee = true;
|
|
102139
102401
|
break;
|
|
@@ -102158,9 +102420,6 @@ async function parseAgentArgs(argv) {
|
|
|
102158
102420
|
case "--create-pr":
|
|
102159
102421
|
result2.createPr = true;
|
|
102160
102422
|
break;
|
|
102161
|
-
case "--fix-ci":
|
|
102162
|
-
result2.fixCi = true;
|
|
102163
|
-
break;
|
|
102164
102423
|
case "--stack-prs":
|
|
102165
102424
|
result2.stackPrs = true;
|
|
102166
102425
|
break;
|
|
@@ -102194,11 +102453,11 @@ async function parseAgentArgs(argv) {
|
|
|
102194
102453
|
case "--no-tmux":
|
|
102195
102454
|
result2.noTmux = true;
|
|
102196
102455
|
break;
|
|
102197
|
-
case "--no-pr-
|
|
102198
|
-
result2.
|
|
102456
|
+
case "--no-pr-recovery":
|
|
102457
|
+
result2.prRecoveryEnabled = false;
|
|
102199
102458
|
break;
|
|
102200
|
-
case "--pr-
|
|
102201
|
-
result2.
|
|
102459
|
+
case "--pr-recovery":
|
|
102460
|
+
result2.prRecoveryEnabled = true;
|
|
102202
102461
|
break;
|
|
102203
102462
|
default:
|
|
102204
102463
|
if (VALID_MODES2.has(arg)) {
|
|
@@ -102211,9 +102470,6 @@ async function parseAgentArgs(argv) {
|
|
|
102211
102470
|
}
|
|
102212
102471
|
await resolvePromptFile(result2, state);
|
|
102213
102472
|
resolveWorkflowFile(result2, state);
|
|
102214
|
-
if (result2.fixCi && !result2.createPr) {
|
|
102215
|
-
throw new Error("--fix-ci requires --create-pr");
|
|
102216
|
-
}
|
|
102217
102473
|
if (result2.stackPrs && !result2.createPr) {
|
|
102218
102474
|
throw new Error("--stack-prs requires --create-pr");
|
|
102219
102475
|
}
|
|
@@ -102264,8 +102520,7 @@ var init_cli2 = __esm(() => {
|
|
|
102264
102520
|
" --log Log raw engine stream",
|
|
102265
102521
|
" --verbose Verbose output",
|
|
102266
102522
|
" --linear-team <key> Linear team key (e.g. ENG)",
|
|
102267
|
-
" --linear-
|
|
102268
|
-
" --linear-assignee <id> [deprecated] Filter by assignee; use --linear-filter instead",
|
|
102523
|
+
" --linear-assignee <id> Assignee override (me / any / unassigned / <email> / <id>); overrides linear.filter's assignee clause",
|
|
102269
102524
|
" --poll-interval <s> Seconds between Linear polls (default: 60)",
|
|
102270
102525
|
" --concurrency <n> Max concurrent task loops (default: 1)",
|
|
102271
102526
|
" --worktree Run each task in its own git worktree",
|
|
@@ -102277,13 +102532,12 @@ var init_cli2 = __esm(() => {
|
|
|
102277
102532
|
" --indicator setPrReady:status:In Review (additive ready marker)",
|
|
102278
102533
|
" (attachment upserts a single 'Ralphy' entry; value = subtitle)",
|
|
102279
102534
|
" --create-pr Push the worker branch and open a GitHub PR on success (needs --worktree)",
|
|
102280
|
-
" --fix-ci After opening the PR, re-run on CI failures until green (needs --create-pr)",
|
|
102281
102535
|
" --stack-prs Base the PR on a blocker issue's open-PR head branch when present (needs --create-pr)",
|
|
102282
102536
|
" --code-review Watch open tracked PRs for unresolved review comments",
|
|
102283
102537
|
" --max-tickets <n> Stop picking up new issues after N have been started (0 = unlimited)",
|
|
102284
102538
|
" --ticket <id> Restrict issue discovery to specific ticket(s); repeatable or comma-separated (e.g. RLF-208 or 208)",
|
|
102285
102539
|
" --no-tmux Disable tmux session management; run agent in the foreground directly",
|
|
102286
|
-
" --no-pr-
|
|
102540
|
+
" --no-pr-recovery Disable PR recovery (conflict + CI watcher) for this run; --pr-recovery forces it on",
|
|
102287
102541
|
" --json-output Emit JSONL to stdout instead of the Ink dashboard (for scripting/CI)",
|
|
102288
102542
|
" (auto-enabled when stdin is not a TTY, e.g. pipes / nohup / CI)",
|
|
102289
102543
|
" --json-log-file <path> Mirror JSONL events to a file (works alongside TUI or --json-output)",
|
|
@@ -102346,330 +102600,12 @@ class PollContext {
|
|
|
102346
102600
|
}
|
|
102347
102601
|
}
|
|
102348
102602
|
|
|
102349
|
-
// apps/agent/src/shared/capabilities/types.ts
|
|
102350
|
-
var NO_RETRY;
|
|
102351
|
-
var init_types4 = __esm(() => {
|
|
102352
|
-
NO_RETRY = {
|
|
102353
|
-
maxAttempts: 1,
|
|
102354
|
-
isRetryable: () => false,
|
|
102355
|
-
delayMs: () => 0
|
|
102356
|
-
};
|
|
102357
|
-
});
|
|
102358
|
-
|
|
102359
|
-
// apps/agent/src/shared/capabilities/format-error.ts
|
|
102360
|
-
function formatError2(err) {
|
|
102361
|
-
if (err instanceof Error)
|
|
102362
|
-
return err.message;
|
|
102363
|
-
try {
|
|
102364
|
-
return String(err);
|
|
102365
|
-
} catch {
|
|
102366
|
-
return "unknown error";
|
|
102367
|
-
}
|
|
102368
|
-
}
|
|
102369
|
-
|
|
102370
|
-
// apps/agent/src/shared/capabilities/fs-change.ts
|
|
102371
|
-
import { join as join21, dirname as dirname8 } from "path";
|
|
102372
|
-
import { mkdir as mkdir7 } from "fs/promises";
|
|
102373
|
-
var scaffold, prependTask, appendSteering, fsChange;
|
|
102374
|
-
var init_fs_change = __esm(() => {
|
|
102375
|
-
init_tasks_md();
|
|
102376
|
-
init_types4();
|
|
102377
|
-
scaffold = {
|
|
102378
|
-
name: "fs.change.scaffold",
|
|
102379
|
-
required: false,
|
|
102380
|
-
retryPolicy: NO_RETRY,
|
|
102381
|
-
errorFormatter: formatError2,
|
|
102382
|
-
run: async (args) => {
|
|
102383
|
-
await mkdir7(args.changeDir, { recursive: true });
|
|
102384
|
-
await mkdir7(join21(args.changeDir, "specs"), { recursive: true });
|
|
102385
|
-
await mkdir7(args.stateDir, { recursive: true });
|
|
102386
|
-
await Bun.write(join21(args.changeDir, "proposal.md"), args.proposal);
|
|
102387
|
-
await Bun.write(join21(args.changeDir, "tasks.md"), args.tasks);
|
|
102388
|
-
await Bun.write(join21(args.changeDir, "design.md"), args.design);
|
|
102389
|
-
}
|
|
102390
|
-
};
|
|
102391
|
-
prependTask = {
|
|
102392
|
-
name: "fs.change.task.prepend",
|
|
102393
|
-
required: false,
|
|
102394
|
-
retryPolicy: NO_RETRY,
|
|
102395
|
-
errorFormatter: formatError2,
|
|
102396
|
-
run: async (args) => {
|
|
102397
|
-
await prependFixTask(args.tasksPath, args.heading, args.failureOutput);
|
|
102398
|
-
}
|
|
102399
|
-
};
|
|
102400
|
-
appendSteering = {
|
|
102401
|
-
name: "fs.change.steering.append",
|
|
102402
|
-
required: false,
|
|
102403
|
-
retryPolicy: NO_RETRY,
|
|
102404
|
-
errorFormatter: formatError2,
|
|
102405
|
-
run: async (args) => {
|
|
102406
|
-
const path = join21(args.changeDir, "steering.md");
|
|
102407
|
-
const f2 = Bun.file(path);
|
|
102408
|
-
const existing = await f2.exists() ? await f2.text() : null;
|
|
102409
|
-
const updated = existing ? `${args.message}
|
|
102410
|
-
|
|
102411
|
-
${existing.trimStart()}` : `${args.message}
|
|
102412
|
-
`;
|
|
102413
|
-
await mkdir7(dirname8(path), { recursive: true });
|
|
102414
|
-
await Bun.write(path, updated);
|
|
102415
|
-
}
|
|
102416
|
-
};
|
|
102417
|
-
fsChange = { scaffold, prependTask, appendSteering };
|
|
102418
|
-
});
|
|
102419
|
-
|
|
102420
|
-
// apps/agent/src/agent/worktree.ts
|
|
102421
|
-
import { basename as basename2, join as join22 } from "path";
|
|
102422
|
-
import { homedir as homedir5 } from "os";
|
|
102423
|
-
import { exists as exists3 } from "fs/promises";
|
|
102424
|
-
function worktreesDir2(projectRoot) {
|
|
102425
|
-
return join22(homedir5(), ".ralph", basename2(projectRoot), "worktrees");
|
|
102426
|
-
}
|
|
102427
|
-
function branchForChange(changeName) {
|
|
102428
|
-
return `ralph/${changeName}`;
|
|
102429
|
-
}
|
|
102430
|
-
function worktreeDirNameForIssue(issue2) {
|
|
102431
|
-
return issue2.identifier.toLowerCase();
|
|
102432
|
-
}
|
|
102433
|
-
function withRepoLock(projectRoot, fn) {
|
|
102434
|
-
const prev = repoWorktreeLocks.get(projectRoot) ?? Promise.resolve();
|
|
102435
|
-
const result2 = prev.then(fn, fn);
|
|
102436
|
-
repoWorktreeLocks.set(projectRoot, result2.then(() => {}, () => {}));
|
|
102437
|
-
return result2;
|
|
102438
|
-
}
|
|
102439
|
-
function createWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
102440
|
-
return withRepoLock(projectRoot, () => provisionWorktree(projectRoot, changeName, baseBranch, runner));
|
|
102441
|
-
}
|
|
102442
|
-
async function provisionWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
102443
|
-
const dir = worktreesDir2(projectRoot);
|
|
102444
|
-
const cwd2 = join22(dir, changeName);
|
|
102445
|
-
const branch = branchForChange(changeName);
|
|
102446
|
-
const list = await runner.run(["worktree", "list", "--porcelain"], projectRoot);
|
|
102447
|
-
if (list.stdout.includes(`worktree ${cwd2}
|
|
102448
|
-
`)) {
|
|
102449
|
-
await installPrePushHook(cwd2, runner);
|
|
102450
|
-
return { cwd: cwd2, branch };
|
|
102451
|
-
}
|
|
102452
|
-
let branchExists = true;
|
|
102453
|
-
try {
|
|
102454
|
-
await runner.run(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`], projectRoot);
|
|
102455
|
-
} catch {
|
|
102456
|
-
branchExists = false;
|
|
102457
|
-
}
|
|
102458
|
-
if (branchExists) {
|
|
102459
|
-
await runner.run(["worktree", "add", cwd2, branch], projectRoot);
|
|
102460
|
-
await installPrePushHook(cwd2, runner);
|
|
102461
|
-
return { cwd: cwd2, branch };
|
|
102462
|
-
}
|
|
102463
|
-
await runner.run(["fetch", "origin", baseBranch], projectRoot);
|
|
102464
|
-
await runner.run(["worktree", "add", "-b", branch, cwd2, `origin/${baseBranch}`], projectRoot);
|
|
102465
|
-
await installPrePushHook(cwd2, runner);
|
|
102466
|
-
return { cwd: cwd2, branch };
|
|
102467
|
-
}
|
|
102468
|
-
async function installPrePushHook(cwd2, runner) {
|
|
102469
|
-
const hookPath = join22(cwd2, ".ralph-hooks", "pre-push");
|
|
102470
|
-
await Bun.write(hookPath, PRE_PUSH_HOOK_SCRIPT);
|
|
102471
|
-
const chmod = Bun.spawn(["chmod", "+x", hookPath]);
|
|
102472
|
-
await chmod.exited;
|
|
102473
|
-
await runner.run(["config", "core.hooksPath", ".ralph-hooks"], cwd2);
|
|
102474
|
-
}
|
|
102475
|
-
async function removeWorktree(projectRoot, cwd2, runner) {
|
|
102476
|
-
await runner.run(["worktree", "remove", "--force", cwd2], projectRoot);
|
|
102477
|
-
}
|
|
102478
|
-
async function isWorktreeSafeToRemove(cwd2, base2, runner) {
|
|
102479
|
-
const status = await runner.run(["status", "--porcelain"], cwd2);
|
|
102480
|
-
const dirty = status.stdout.trim();
|
|
102481
|
-
let unpushedCommits = "";
|
|
102482
|
-
try {
|
|
102483
|
-
const log3 = await runner.run(["log", "--oneline", `${base2}..HEAD`, "--no-merges"], cwd2);
|
|
102484
|
-
unpushedCommits = log3.stdout.trim();
|
|
102485
|
-
} catch {
|
|
102486
|
-
unpushedCommits = "<unknown: failed to compare against base>";
|
|
102487
|
-
}
|
|
102488
|
-
if (dirty && unpushedCommits) {
|
|
102489
|
-
return {
|
|
102490
|
-
safe: false,
|
|
102491
|
-
reason: "uncommitted changes AND unpushed commits present",
|
|
102492
|
-
dirty,
|
|
102493
|
-
unpushedCommits
|
|
102494
|
-
};
|
|
102495
|
-
}
|
|
102496
|
-
if (dirty) {
|
|
102497
|
-
return {
|
|
102498
|
-
safe: false,
|
|
102499
|
-
reason: "uncommitted or untracked files present",
|
|
102500
|
-
dirty,
|
|
102501
|
-
unpushedCommits
|
|
102502
|
-
};
|
|
102503
|
-
}
|
|
102504
|
-
if (unpushedCommits) {
|
|
102505
|
-
return {
|
|
102506
|
-
safe: false,
|
|
102507
|
-
reason: `commits ahead of ${base2} were not pushed/PR'd`,
|
|
102508
|
-
dirty,
|
|
102509
|
-
unpushedCommits
|
|
102510
|
-
};
|
|
102511
|
-
}
|
|
102512
|
-
return { safe: true, dirty, unpushedCommits };
|
|
102513
|
-
}
|
|
102514
|
-
async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
|
|
102515
|
-
const dst = join22(worktreeCwd, ".mcp.json");
|
|
102516
|
-
const src = join22(projectRoot, ".mcp.json");
|
|
102517
|
-
const source = await exists3(dst) ? dst : await exists3(src) ? src : null;
|
|
102518
|
-
if (!source)
|
|
102519
|
-
return;
|
|
102520
|
-
let parsed;
|
|
102521
|
-
try {
|
|
102522
|
-
parsed = await Bun.file(source).json();
|
|
102523
|
-
} catch {
|
|
102524
|
-
return;
|
|
102525
|
-
}
|
|
102526
|
-
const servers = parsed.mcpServers;
|
|
102527
|
-
if (servers && typeof servers === "object") {
|
|
102528
|
-
for (const cfg of Object.values(servers)) {
|
|
102529
|
-
if (Array.isArray(cfg.args)) {
|
|
102530
|
-
cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ? join22(projectRoot, a) : a);
|
|
102531
|
-
}
|
|
102532
|
-
}
|
|
102533
|
-
}
|
|
102534
|
-
await Bun.write(dst, JSON.stringify(parsed, null, 2) + `
|
|
102535
|
-
`);
|
|
102536
|
-
}
|
|
102537
|
-
var repoWorktreeLocks, PRE_PUSH_HOOK_SCRIPT = `#!/usr/bin/env bash
|
|
102538
|
-
# Installed by ralphy createWorktree (RLF-107).
|
|
102539
|
-
# Rejects any push whose remote ref is not refs/heads/ralph/*,
|
|
102540
|
-
# and rejects force pushes unless RALPH_ALLOW_FORCE_PUSH=1.
|
|
102541
|
-
set -euo pipefail
|
|
102542
|
-
ZERO="0000000000000000000000000000000000000000"
|
|
102543
|
-
while read local_ref local_sha remote_ref remote_sha; do
|
|
102544
|
-
case "$remote_ref" in
|
|
102545
|
-
refs/heads/ralph/*) ;;
|
|
102546
|
-
*) echo "ralph: refusing push to $remote_ref (only refs/heads/ralph/* allowed)" >&2; exit 1 ;;
|
|
102547
|
-
esac
|
|
102548
|
-
if [ "$remote_sha" != "$ZERO" ] && [ "\${RALPH_ALLOW_FORCE_PUSH:-0}" != "1" ]; then
|
|
102549
|
-
if ! git merge-base --is-ancestor "$remote_sha" "$local_sha" 2>/dev/null; then
|
|
102550
|
-
echo "ralph: refusing force-push to $remote_ref (set RALPH_ALLOW_FORCE_PUSH=1 to override)" >&2
|
|
102551
|
-
exit 1
|
|
102552
|
-
fi
|
|
102553
|
-
fi
|
|
102554
|
-
done
|
|
102555
|
-
exit 0
|
|
102556
|
-
`;
|
|
102557
|
-
var init_worktree = __esm(() => {
|
|
102558
|
-
repoWorktreeLocks = new Map;
|
|
102559
|
-
});
|
|
102560
|
-
|
|
102561
|
-
// apps/agent/src/shared/capabilities/git.ts
|
|
102562
|
-
var createWorktree2, removeWorktree2, seedWorktreeMcpConfig2, git;
|
|
102563
|
-
var init_git2 = __esm(() => {
|
|
102564
|
-
init_types4();
|
|
102565
|
-
init_worktree();
|
|
102566
|
-
createWorktree2 = {
|
|
102567
|
-
name: "git.worktree.create",
|
|
102568
|
-
required: true,
|
|
102569
|
-
retryPolicy: NO_RETRY,
|
|
102570
|
-
errorFormatter: formatError2,
|
|
102571
|
-
run: (args) => createWorktree(args.projectRoot, args.changeName, args.baseBranch, args.runner)
|
|
102572
|
-
};
|
|
102573
|
-
removeWorktree2 = {
|
|
102574
|
-
name: "git.worktree.remove",
|
|
102575
|
-
required: false,
|
|
102576
|
-
retryPolicy: NO_RETRY,
|
|
102577
|
-
errorFormatter: formatError2,
|
|
102578
|
-
run: (args) => removeWorktree(args.projectRoot, args.cwd, args.runner)
|
|
102579
|
-
};
|
|
102580
|
-
seedWorktreeMcpConfig2 = {
|
|
102581
|
-
name: "git.worktree.seedMcpConfig",
|
|
102582
|
-
required: false,
|
|
102583
|
-
retryPolicy: NO_RETRY,
|
|
102584
|
-
errorFormatter: formatError2,
|
|
102585
|
-
run: (args) => seedWorktreeMcpConfig(args.projectRoot, args.worktreeCwd)
|
|
102586
|
-
};
|
|
102587
|
-
git = { createWorktree: createWorktree2, removeWorktree: removeWorktree2, seedWorktreeMcpConfig: seedWorktreeMcpConfig2 };
|
|
102588
|
-
});
|
|
102589
|
-
|
|
102590
|
-
// apps/agent/src/shared/capabilities/run-capability.ts
|
|
102591
|
-
function emit2(bus, ev) {
|
|
102592
|
-
if (!bus)
|
|
102593
|
-
return;
|
|
102594
|
-
bus.emit(ev);
|
|
102595
|
-
}
|
|
102596
|
-
async function runCapability(cap, args, ctx = {}) {
|
|
102597
|
-
const { bus } = ctx;
|
|
102598
|
-
emit2(bus, { type: `${cap.name}.started` });
|
|
102599
|
-
let lastError;
|
|
102600
|
-
for (let attempt2 = 1;attempt2 <= cap.retryPolicy.maxAttempts; attempt2++) {
|
|
102601
|
-
try {
|
|
102602
|
-
const raw = await cap.run(args);
|
|
102603
|
-
const result2 = cap.adopt ? cap.adopt(raw) : raw;
|
|
102604
|
-
emit2(bus, { type: `${cap.name}.fetched` });
|
|
102605
|
-
return result2;
|
|
102606
|
-
} catch (err) {
|
|
102607
|
-
lastError = err;
|
|
102608
|
-
const canRetry = attempt2 < cap.retryPolicy.maxAttempts && cap.retryPolicy.isRetryable(err);
|
|
102609
|
-
if (!canRetry)
|
|
102610
|
-
break;
|
|
102611
|
-
const delay2 = Math.max(0, cap.retryPolicy.delayMs(attempt2, err));
|
|
102612
|
-
if (delay2 > 0)
|
|
102613
|
-
await sleepMs(delay2);
|
|
102614
|
-
}
|
|
102615
|
-
}
|
|
102616
|
-
const message = cap.errorFormatter(lastError);
|
|
102617
|
-
emit2(bus, { type: `${cap.name}.failed`, error: message });
|
|
102618
|
-
if (cap.required) {
|
|
102619
|
-
throw lastError;
|
|
102620
|
-
}
|
|
102621
|
-
throw lastError;
|
|
102622
|
-
}
|
|
102623
|
-
function sleepMs(ms) {
|
|
102624
|
-
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
102625
|
-
}
|
|
102626
|
-
|
|
102627
|
-
// packages/workflow/src/boundaries.ts
|
|
102628
|
-
function globToRegex(pattern) {
|
|
102629
|
-
let re = "^";
|
|
102630
|
-
for (let i = 0;i < pattern.length; i++) {
|
|
102631
|
-
const c = pattern[i];
|
|
102632
|
-
if (c === "*") {
|
|
102633
|
-
if (pattern[i + 1] === "*") {
|
|
102634
|
-
re += ".*";
|
|
102635
|
-
i++;
|
|
102636
|
-
if (pattern[i + 1] === "/")
|
|
102637
|
-
i++;
|
|
102638
|
-
} else {
|
|
102639
|
-
re += "[^/]*";
|
|
102640
|
-
}
|
|
102641
|
-
} else if (c === "?") {
|
|
102642
|
-
re += "[^/]";
|
|
102643
|
-
} else if (/[.+^${}()|[\]\\]/.test(c)) {
|
|
102644
|
-
re += "\\" + c;
|
|
102645
|
-
} else {
|
|
102646
|
-
re += c;
|
|
102647
|
-
}
|
|
102648
|
-
}
|
|
102649
|
-
re += "$";
|
|
102650
|
-
return new RegExp(re);
|
|
102651
|
-
}
|
|
102652
|
-
function findBoundaryViolations(changedFiles, patterns) {
|
|
102653
|
-
if (patterns.length === 0 || changedFiles.length === 0)
|
|
102654
|
-
return [];
|
|
102655
|
-
const compiled = patterns.map((p) => ({ pattern: p, re: globToRegex(p) }));
|
|
102656
|
-
const out = [];
|
|
102657
|
-
for (const file2 of changedFiles) {
|
|
102658
|
-
const norm = file2.replace(/\\/g, "/");
|
|
102659
|
-
for (const { pattern, re } of compiled) {
|
|
102660
|
-
if (re.test(norm)) {
|
|
102661
|
-
out.push({ file: norm, pattern });
|
|
102662
|
-
break;
|
|
102663
|
-
}
|
|
102664
|
-
}
|
|
102665
|
-
}
|
|
102666
|
-
return out;
|
|
102667
|
-
}
|
|
102668
|
-
|
|
102669
102603
|
// apps/agent/src/shared/utils/ralph-comment.ts
|
|
102670
102604
|
function isRalphComment(body) {
|
|
102671
102605
|
const trimmed = body.trimStart();
|
|
102672
|
-
|
|
102606
|
+
if (/^(\uD83E\uDD16|\uD83D\uDD04|\u2705|\u2717|\u274C|\u26A0|\uD83D\uDD01|\uD83D\uDCCB|\u23F0)\s*Ralphy?\b/.test(trimmed))
|
|
102607
|
+
return true;
|
|
102608
|
+
return /^\uD83D\uDC40\s*(Got it\b|Acknowledged\b)/.test(trimmed);
|
|
102673
102609
|
}
|
|
102674
102610
|
|
|
102675
102611
|
// apps/agent/src/shared/capabilities/linear-client.ts
|
|
@@ -102700,6 +102636,7 @@ __export(exports_linear_client, {
|
|
|
102700
102636
|
fetchIssueLabels: () => fetchIssueLabels,
|
|
102701
102637
|
fetchIssueComments: () => fetchIssueComments,
|
|
102702
102638
|
fetchIssueAttachments: () => fetchIssueAttachments,
|
|
102639
|
+
fetchBlockedByForIssues: () => fetchBlockedByForIssues,
|
|
102703
102640
|
fetchAttachmentsForIssues: () => fetchAttachmentsForIssues,
|
|
102704
102641
|
deleteIssueComment: () => deleteIssueComment,
|
|
102705
102642
|
deleteAttachment: () => deleteAttachment,
|
|
@@ -102787,6 +102724,19 @@ function partition2(markers) {
|
|
|
102787
102724
|
}
|
|
102788
102725
|
return { statuses, labels, attachmentSubtitles, projects };
|
|
102789
102726
|
}
|
|
102727
|
+
function applyRequiredLabels(where, requireAllLabels) {
|
|
102728
|
+
if (!requireAllLabels || requireAllLabels.length === 0)
|
|
102729
|
+
return;
|
|
102730
|
+
const and2 = where.and ?? [];
|
|
102731
|
+
if (where.labels !== undefined) {
|
|
102732
|
+
and2.push({ labels: where.labels });
|
|
102733
|
+
delete where.labels;
|
|
102734
|
+
}
|
|
102735
|
+
for (const label of requireAllLabels) {
|
|
102736
|
+
and2.push({ labels: { some: { name: { eq: label } } } });
|
|
102737
|
+
}
|
|
102738
|
+
where.and = and2;
|
|
102739
|
+
}
|
|
102790
102740
|
function buildIssueFilter(spec) {
|
|
102791
102741
|
const where = {};
|
|
102792
102742
|
if (spec.team)
|
|
@@ -102885,6 +102835,7 @@ function buildIssueFilter(spec) {
|
|
|
102885
102835
|
}
|
|
102886
102836
|
}
|
|
102887
102837
|
}
|
|
102838
|
+
applyRequiredLabels(where, spec.requireAllLabels);
|
|
102888
102839
|
return where;
|
|
102889
102840
|
}
|
|
102890
102841
|
function clauseFromMarkers(markers) {
|
|
@@ -102965,6 +102916,7 @@ async function fetchMentionScanIssues(apiKey, spec) {
|
|
|
102965
102916
|
if (spec.numbers && spec.numbers.length > 0) {
|
|
102966
102917
|
where.number = { in: spec.numbers };
|
|
102967
102918
|
}
|
|
102919
|
+
applyRequiredLabels(where, spec.requireAllLabels);
|
|
102968
102920
|
const query = `query MentionScanIssues($filter: IssueFilter) {
|
|
102969
102921
|
issues(filter: $filter, first: 50) {
|
|
102970
102922
|
nodes {
|
|
@@ -103114,11 +103066,21 @@ function formatLinearError(err) {
|
|
|
103114
103066
|
async function linearRequest(apiKey, query, variables) {
|
|
103115
103067
|
let lastHttpError;
|
|
103116
103068
|
for (let attempt2 = 1;attempt2 <= MAX_LINEAR_ATTEMPTS; attempt2++) {
|
|
103117
|
-
|
|
103118
|
-
|
|
103119
|
-
|
|
103120
|
-
|
|
103121
|
-
|
|
103069
|
+
let res;
|
|
103070
|
+
try {
|
|
103071
|
+
res = await fetch(LINEAR_API, {
|
|
103072
|
+
method: "POST",
|
|
103073
|
+
headers: { "Content-Type": "application/json", Authorization: apiKey },
|
|
103074
|
+
body: JSON.stringify({ query, variables })
|
|
103075
|
+
});
|
|
103076
|
+
} catch (netErr) {
|
|
103077
|
+
lastHttpError = netErr;
|
|
103078
|
+
if (attempt2 < MAX_LINEAR_ATTEMPTS) {
|
|
103079
|
+
await linearRequestInternals.sleep(Math.min(backoffMs(attempt2), MAX_RETRY_AFTER_MS));
|
|
103080
|
+
continue;
|
|
103081
|
+
}
|
|
103082
|
+
throw netErr;
|
|
103083
|
+
}
|
|
103122
103084
|
if (!res.ok) {
|
|
103123
103085
|
const err = new Error("Linear API request failed");
|
|
103124
103086
|
err.status = res.status;
|
|
@@ -103346,6 +103308,28 @@ async function fetchAttachmentsForIssues(apiKey, issueIds) {
|
|
|
103346
103308
|
}
|
|
103347
103309
|
return out;
|
|
103348
103310
|
}
|
|
103311
|
+
async function fetchBlockedByForIssues(apiKey, issueIds) {
|
|
103312
|
+
const out = new Map;
|
|
103313
|
+
if (issueIds.length === 0)
|
|
103314
|
+
return out;
|
|
103315
|
+
const query = `query IssuesBlockedBy($ids: [ID!]!) {
|
|
103316
|
+
issues(filter: { id: { in: $ids } }, first: 250) {
|
|
103317
|
+
nodes {
|
|
103318
|
+
id
|
|
103319
|
+
relations(first: 50) {
|
|
103320
|
+
nodes { type relatedIssue { id identifier state { type } } }
|
|
103321
|
+
}
|
|
103322
|
+
}
|
|
103323
|
+
}
|
|
103324
|
+
}`;
|
|
103325
|
+
const data = await linearRequest(apiKey, query, { ids: issueIds });
|
|
103326
|
+
const DONE_STATE_TYPES = new Set(["completed", "cancelled"]);
|
|
103327
|
+
for (const node2 of data.issues.nodes) {
|
|
103328
|
+
const blockers = (node2.relations?.nodes ?? []).filter((r) => r.type === "blocked_by" && !DONE_STATE_TYPES.has(r.relatedIssue.state.type)).map((r) => ({ id: r.relatedIssue.id, identifier: r.relatedIssue.identifier }));
|
|
103329
|
+
out.set(node2.id, blockers);
|
|
103330
|
+
}
|
|
103331
|
+
return out;
|
|
103332
|
+
}
|
|
103349
103333
|
async function findIssueAttachmentByTitle(apiKey, issueId, title) {
|
|
103350
103334
|
const query = `query IssueAttachmentByTitle($id: String!) {
|
|
103351
103335
|
issue(id: $id) {
|
|
@@ -103566,6 +103550,326 @@ var init_linear_client = __esm(() => {
|
|
|
103566
103550
|
};
|
|
103567
103551
|
});
|
|
103568
103552
|
|
|
103553
|
+
// apps/agent/src/shared/capabilities/types.ts
|
|
103554
|
+
var NO_RETRY;
|
|
103555
|
+
var init_types4 = __esm(() => {
|
|
103556
|
+
NO_RETRY = {
|
|
103557
|
+
maxAttempts: 1,
|
|
103558
|
+
isRetryable: () => false,
|
|
103559
|
+
delayMs: () => 0
|
|
103560
|
+
};
|
|
103561
|
+
});
|
|
103562
|
+
|
|
103563
|
+
// apps/agent/src/shared/capabilities/format-error.ts
|
|
103564
|
+
function formatError2(err) {
|
|
103565
|
+
if (err instanceof Error)
|
|
103566
|
+
return err.message;
|
|
103567
|
+
try {
|
|
103568
|
+
return String(err);
|
|
103569
|
+
} catch {
|
|
103570
|
+
return "unknown error";
|
|
103571
|
+
}
|
|
103572
|
+
}
|
|
103573
|
+
|
|
103574
|
+
// apps/agent/src/shared/capabilities/fs-change.ts
|
|
103575
|
+
import { join as join21, dirname as dirname8 } from "path";
|
|
103576
|
+
import { mkdir as mkdir7 } from "fs/promises";
|
|
103577
|
+
var scaffold, prependTask, appendSteering, fsChange;
|
|
103578
|
+
var init_fs_change = __esm(() => {
|
|
103579
|
+
init_tasks_md();
|
|
103580
|
+
init_types4();
|
|
103581
|
+
scaffold = {
|
|
103582
|
+
name: "fs.change.scaffold",
|
|
103583
|
+
required: false,
|
|
103584
|
+
retryPolicy: NO_RETRY,
|
|
103585
|
+
errorFormatter: formatError2,
|
|
103586
|
+
run: async (args) => {
|
|
103587
|
+
await mkdir7(args.changeDir, { recursive: true });
|
|
103588
|
+
await mkdir7(join21(args.changeDir, "specs"), { recursive: true });
|
|
103589
|
+
await mkdir7(args.stateDir, { recursive: true });
|
|
103590
|
+
await Bun.write(join21(args.changeDir, "proposal.md"), args.proposal);
|
|
103591
|
+
await Bun.write(join21(args.changeDir, "tasks.md"), args.tasks);
|
|
103592
|
+
await Bun.write(join21(args.changeDir, "design.md"), args.design);
|
|
103593
|
+
}
|
|
103594
|
+
};
|
|
103595
|
+
prependTask = {
|
|
103596
|
+
name: "fs.change.task.prepend",
|
|
103597
|
+
required: false,
|
|
103598
|
+
retryPolicy: NO_RETRY,
|
|
103599
|
+
errorFormatter: formatError2,
|
|
103600
|
+
run: async (args) => {
|
|
103601
|
+
await prependFixTask(args.tasksPath, args.heading, args.failureOutput);
|
|
103602
|
+
}
|
|
103603
|
+
};
|
|
103604
|
+
appendSteering = {
|
|
103605
|
+
name: "fs.change.steering.append",
|
|
103606
|
+
required: false,
|
|
103607
|
+
retryPolicy: NO_RETRY,
|
|
103608
|
+
errorFormatter: formatError2,
|
|
103609
|
+
run: async (args) => {
|
|
103610
|
+
const path = join21(args.changeDir, "steering.md");
|
|
103611
|
+
const f2 = Bun.file(path);
|
|
103612
|
+
const existing = await f2.exists() ? await f2.text() : null;
|
|
103613
|
+
const updated = existing ? `${args.message}
|
|
103614
|
+
|
|
103615
|
+
${existing.trimStart()}` : `${args.message}
|
|
103616
|
+
`;
|
|
103617
|
+
await mkdir7(dirname8(path), { recursive: true });
|
|
103618
|
+
await Bun.write(path, updated);
|
|
103619
|
+
}
|
|
103620
|
+
};
|
|
103621
|
+
fsChange = { scaffold, prependTask, appendSteering };
|
|
103622
|
+
});
|
|
103623
|
+
|
|
103624
|
+
// apps/agent/src/agent/worktree.ts
|
|
103625
|
+
import { basename as basename2, join as join22 } from "path";
|
|
103626
|
+
import { homedir as homedir5 } from "os";
|
|
103627
|
+
import { exists as exists3 } from "fs/promises";
|
|
103628
|
+
function worktreesDir2(projectRoot) {
|
|
103629
|
+
return join22(homedir5(), ".ralph", basename2(projectRoot), "worktrees");
|
|
103630
|
+
}
|
|
103631
|
+
function branchForChange(changeName) {
|
|
103632
|
+
return `ralph/${changeName}`;
|
|
103633
|
+
}
|
|
103634
|
+
function worktreeDirNameForIssue(issue2) {
|
|
103635
|
+
return issue2.identifier.toLowerCase();
|
|
103636
|
+
}
|
|
103637
|
+
function withRepoLock(projectRoot, fn) {
|
|
103638
|
+
const prev = repoWorktreeLocks.get(projectRoot) ?? Promise.resolve();
|
|
103639
|
+
const result2 = prev.then(fn, fn);
|
|
103640
|
+
repoWorktreeLocks.set(projectRoot, result2.then(() => {}, () => {}));
|
|
103641
|
+
return result2;
|
|
103642
|
+
}
|
|
103643
|
+
function createWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
103644
|
+
return withRepoLock(projectRoot, () => provisionWorktree(projectRoot, changeName, baseBranch, runner));
|
|
103645
|
+
}
|
|
103646
|
+
async function provisionWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
103647
|
+
const dir = worktreesDir2(projectRoot);
|
|
103648
|
+
const cwd2 = join22(dir, changeName);
|
|
103649
|
+
const branch = branchForChange(changeName);
|
|
103650
|
+
const list = await runner.run(["worktree", "list", "--porcelain"], projectRoot);
|
|
103651
|
+
if (list.stdout.includes(`worktree ${cwd2}
|
|
103652
|
+
`)) {
|
|
103653
|
+
await installPrePushHook(cwd2, runner);
|
|
103654
|
+
return { cwd: cwd2, branch };
|
|
103655
|
+
}
|
|
103656
|
+
let branchExists = true;
|
|
103657
|
+
try {
|
|
103658
|
+
await runner.run(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`], projectRoot);
|
|
103659
|
+
} catch {
|
|
103660
|
+
branchExists = false;
|
|
103661
|
+
}
|
|
103662
|
+
if (branchExists) {
|
|
103663
|
+
await runner.run(["worktree", "add", cwd2, branch], projectRoot);
|
|
103664
|
+
await installPrePushHook(cwd2, runner);
|
|
103665
|
+
return { cwd: cwd2, branch };
|
|
103666
|
+
}
|
|
103667
|
+
await runner.run(["fetch", "origin", baseBranch], projectRoot);
|
|
103668
|
+
await runner.run(["worktree", "add", "-b", branch, cwd2, `origin/${baseBranch}`], projectRoot);
|
|
103669
|
+
await installPrePushHook(cwd2, runner);
|
|
103670
|
+
return { cwd: cwd2, branch };
|
|
103671
|
+
}
|
|
103672
|
+
async function installPrePushHook(cwd2, runner) {
|
|
103673
|
+
const hookPath = join22(cwd2, ".ralph-hooks", "pre-push");
|
|
103674
|
+
await Bun.write(hookPath, PRE_PUSH_HOOK_SCRIPT);
|
|
103675
|
+
const chmod = Bun.spawn(["chmod", "+x", hookPath]);
|
|
103676
|
+
await chmod.exited;
|
|
103677
|
+
await runner.run(["config", "core.hooksPath", ".ralph-hooks"], cwd2);
|
|
103678
|
+
}
|
|
103679
|
+
async function removeWorktree(projectRoot, cwd2, runner) {
|
|
103680
|
+
await runner.run(["worktree", "remove", "--force", cwd2], projectRoot);
|
|
103681
|
+
}
|
|
103682
|
+
async function isWorktreeSafeToRemove(cwd2, base2, runner) {
|
|
103683
|
+
const status = await runner.run(["status", "--porcelain"], cwd2);
|
|
103684
|
+
const dirty = status.stdout.trim();
|
|
103685
|
+
let unpushedCommits = "";
|
|
103686
|
+
try {
|
|
103687
|
+
const log3 = await runner.run(["log", "--oneline", `${base2}..HEAD`, "--no-merges"], cwd2);
|
|
103688
|
+
unpushedCommits = log3.stdout.trim();
|
|
103689
|
+
} catch {
|
|
103690
|
+
unpushedCommits = "<unknown: failed to compare against base>";
|
|
103691
|
+
}
|
|
103692
|
+
if (dirty && unpushedCommits) {
|
|
103693
|
+
return {
|
|
103694
|
+
safe: false,
|
|
103695
|
+
reason: "uncommitted changes AND unpushed commits present",
|
|
103696
|
+
dirty,
|
|
103697
|
+
unpushedCommits
|
|
103698
|
+
};
|
|
103699
|
+
}
|
|
103700
|
+
if (dirty) {
|
|
103701
|
+
return {
|
|
103702
|
+
safe: false,
|
|
103703
|
+
reason: "uncommitted or untracked files present",
|
|
103704
|
+
dirty,
|
|
103705
|
+
unpushedCommits
|
|
103706
|
+
};
|
|
103707
|
+
}
|
|
103708
|
+
if (unpushedCommits) {
|
|
103709
|
+
return {
|
|
103710
|
+
safe: false,
|
|
103711
|
+
reason: `commits ahead of ${base2} were not pushed/PR'd`,
|
|
103712
|
+
dirty,
|
|
103713
|
+
unpushedCommits
|
|
103714
|
+
};
|
|
103715
|
+
}
|
|
103716
|
+
return { safe: true, dirty, unpushedCommits };
|
|
103717
|
+
}
|
|
103718
|
+
async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
|
|
103719
|
+
const dst = join22(worktreeCwd, ".mcp.json");
|
|
103720
|
+
const src = join22(projectRoot, ".mcp.json");
|
|
103721
|
+
const source = await exists3(dst) ? dst : await exists3(src) ? src : null;
|
|
103722
|
+
if (!source)
|
|
103723
|
+
return;
|
|
103724
|
+
let parsed;
|
|
103725
|
+
try {
|
|
103726
|
+
parsed = await Bun.file(source).json();
|
|
103727
|
+
} catch {
|
|
103728
|
+
return;
|
|
103729
|
+
}
|
|
103730
|
+
const servers = parsed.mcpServers;
|
|
103731
|
+
if (servers && typeof servers === "object") {
|
|
103732
|
+
for (const cfg of Object.values(servers)) {
|
|
103733
|
+
if (Array.isArray(cfg.args)) {
|
|
103734
|
+
cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ? join22(projectRoot, a) : a);
|
|
103735
|
+
}
|
|
103736
|
+
}
|
|
103737
|
+
}
|
|
103738
|
+
await Bun.write(dst, JSON.stringify(parsed, null, 2) + `
|
|
103739
|
+
`);
|
|
103740
|
+
}
|
|
103741
|
+
var repoWorktreeLocks, PRE_PUSH_HOOK_SCRIPT = `#!/usr/bin/env bash
|
|
103742
|
+
# Installed by ralphy createWorktree (RLF-107).
|
|
103743
|
+
# Rejects any push whose remote ref is not refs/heads/ralph/*,
|
|
103744
|
+
# and rejects force pushes unless RALPH_ALLOW_FORCE_PUSH=1.
|
|
103745
|
+
set -euo pipefail
|
|
103746
|
+
ZERO="0000000000000000000000000000000000000000"
|
|
103747
|
+
while read local_ref local_sha remote_ref remote_sha; do
|
|
103748
|
+
case "$remote_ref" in
|
|
103749
|
+
refs/heads/ralph/*) ;;
|
|
103750
|
+
*) echo "ralph: refusing push to $remote_ref (only refs/heads/ralph/* allowed)" >&2; exit 1 ;;
|
|
103751
|
+
esac
|
|
103752
|
+
if [ "$remote_sha" != "$ZERO" ] && [ "\${RALPH_ALLOW_FORCE_PUSH:-0}" != "1" ]; then
|
|
103753
|
+
if ! git merge-base --is-ancestor "$remote_sha" "$local_sha" 2>/dev/null; then
|
|
103754
|
+
echo "ralph: refusing force-push to $remote_ref (set RALPH_ALLOW_FORCE_PUSH=1 to override)" >&2
|
|
103755
|
+
exit 1
|
|
103756
|
+
fi
|
|
103757
|
+
fi
|
|
103758
|
+
done
|
|
103759
|
+
exit 0
|
|
103760
|
+
`;
|
|
103761
|
+
var init_worktree = __esm(() => {
|
|
103762
|
+
repoWorktreeLocks = new Map;
|
|
103763
|
+
});
|
|
103764
|
+
|
|
103765
|
+
// apps/agent/src/shared/capabilities/git.ts
|
|
103766
|
+
var createWorktree2, removeWorktree2, seedWorktreeMcpConfig2, git;
|
|
103767
|
+
var init_git2 = __esm(() => {
|
|
103768
|
+
init_types4();
|
|
103769
|
+
init_worktree();
|
|
103770
|
+
createWorktree2 = {
|
|
103771
|
+
name: "git.worktree.create",
|
|
103772
|
+
required: true,
|
|
103773
|
+
retryPolicy: NO_RETRY,
|
|
103774
|
+
errorFormatter: formatError2,
|
|
103775
|
+
run: (args) => createWorktree(args.projectRoot, args.changeName, args.baseBranch, args.runner)
|
|
103776
|
+
};
|
|
103777
|
+
removeWorktree2 = {
|
|
103778
|
+
name: "git.worktree.remove",
|
|
103779
|
+
required: false,
|
|
103780
|
+
retryPolicy: NO_RETRY,
|
|
103781
|
+
errorFormatter: formatError2,
|
|
103782
|
+
run: (args) => removeWorktree(args.projectRoot, args.cwd, args.runner)
|
|
103783
|
+
};
|
|
103784
|
+
seedWorktreeMcpConfig2 = {
|
|
103785
|
+
name: "git.worktree.seedMcpConfig",
|
|
103786
|
+
required: false,
|
|
103787
|
+
retryPolicy: NO_RETRY,
|
|
103788
|
+
errorFormatter: formatError2,
|
|
103789
|
+
run: (args) => seedWorktreeMcpConfig(args.projectRoot, args.worktreeCwd)
|
|
103790
|
+
};
|
|
103791
|
+
git = { createWorktree: createWorktree2, removeWorktree: removeWorktree2, seedWorktreeMcpConfig: seedWorktreeMcpConfig2 };
|
|
103792
|
+
});
|
|
103793
|
+
|
|
103794
|
+
// apps/agent/src/shared/capabilities/run-capability.ts
|
|
103795
|
+
function emit2(bus, ev) {
|
|
103796
|
+
if (!bus)
|
|
103797
|
+
return;
|
|
103798
|
+
bus.emit(ev);
|
|
103799
|
+
}
|
|
103800
|
+
async function runCapability(cap, args, ctx = {}) {
|
|
103801
|
+
const { bus } = ctx;
|
|
103802
|
+
emit2(bus, { type: `${cap.name}.started` });
|
|
103803
|
+
let lastError;
|
|
103804
|
+
for (let attempt2 = 1;attempt2 <= cap.retryPolicy.maxAttempts; attempt2++) {
|
|
103805
|
+
try {
|
|
103806
|
+
const raw = await cap.run(args);
|
|
103807
|
+
const result2 = cap.adopt ? cap.adopt(raw) : raw;
|
|
103808
|
+
emit2(bus, { type: `${cap.name}.fetched` });
|
|
103809
|
+
return result2;
|
|
103810
|
+
} catch (err) {
|
|
103811
|
+
lastError = err;
|
|
103812
|
+
const canRetry = attempt2 < cap.retryPolicy.maxAttempts && cap.retryPolicy.isRetryable(err);
|
|
103813
|
+
if (!canRetry)
|
|
103814
|
+
break;
|
|
103815
|
+
const delay2 = Math.max(0, cap.retryPolicy.delayMs(attempt2, err));
|
|
103816
|
+
if (delay2 > 0)
|
|
103817
|
+
await sleepMs(delay2);
|
|
103818
|
+
}
|
|
103819
|
+
}
|
|
103820
|
+
const message = cap.errorFormatter(lastError);
|
|
103821
|
+
emit2(bus, { type: `${cap.name}.failed`, error: message });
|
|
103822
|
+
if (cap.required) {
|
|
103823
|
+
throw lastError;
|
|
103824
|
+
}
|
|
103825
|
+
throw lastError;
|
|
103826
|
+
}
|
|
103827
|
+
function sleepMs(ms) {
|
|
103828
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
103829
|
+
}
|
|
103830
|
+
|
|
103831
|
+
// packages/workflow/src/boundaries.ts
|
|
103832
|
+
function globToRegex(pattern) {
|
|
103833
|
+
let re = "^";
|
|
103834
|
+
for (let i = 0;i < pattern.length; i++) {
|
|
103835
|
+
const c = pattern[i];
|
|
103836
|
+
if (c === "*") {
|
|
103837
|
+
if (pattern[i + 1] === "*") {
|
|
103838
|
+
re += ".*";
|
|
103839
|
+
i++;
|
|
103840
|
+
if (pattern[i + 1] === "/")
|
|
103841
|
+
i++;
|
|
103842
|
+
} else {
|
|
103843
|
+
re += "[^/]*";
|
|
103844
|
+
}
|
|
103845
|
+
} else if (c === "?") {
|
|
103846
|
+
re += "[^/]";
|
|
103847
|
+
} else if (/[.+^${}()|[\]\\]/.test(c)) {
|
|
103848
|
+
re += "\\" + c;
|
|
103849
|
+
} else {
|
|
103850
|
+
re += c;
|
|
103851
|
+
}
|
|
103852
|
+
}
|
|
103853
|
+
re += "$";
|
|
103854
|
+
return new RegExp(re);
|
|
103855
|
+
}
|
|
103856
|
+
function findBoundaryViolations(changedFiles, patterns) {
|
|
103857
|
+
if (patterns.length === 0 || changedFiles.length === 0)
|
|
103858
|
+
return [];
|
|
103859
|
+
const compiled = patterns.map((p) => ({ pattern: p, re: globToRegex(p) }));
|
|
103860
|
+
const out = [];
|
|
103861
|
+
for (const file2 of changedFiles) {
|
|
103862
|
+
const norm = file2.replace(/\\/g, "/");
|
|
103863
|
+
for (const { pattern, re } of compiled) {
|
|
103864
|
+
if (re.test(norm)) {
|
|
103865
|
+
out.push({ file: norm, pattern });
|
|
103866
|
+
break;
|
|
103867
|
+
}
|
|
103868
|
+
}
|
|
103869
|
+
}
|
|
103870
|
+
return out;
|
|
103871
|
+
}
|
|
103872
|
+
|
|
103569
103873
|
// apps/agent/src/agent/linear.ts
|
|
103570
103874
|
var init_linear = __esm(() => {
|
|
103571
103875
|
init_linear_client();
|
|
@@ -103760,131 +104064,14 @@ function classifyGhBucket(bucket) {
|
|
|
103760
104064
|
return "pending";
|
|
103761
104065
|
return "pass";
|
|
103762
104066
|
}
|
|
103763
|
-
var TRANSIENT_GH_RE, NO_CHECKS_RE, GH_RETRY_DELAYS;
|
|
104067
|
+
var TRANSIENT_GH_RE, NO_CHECKS_RE, PARTIAL_ACCESS_RE, GH_RETRY_DELAYS;
|
|
103764
104068
|
var init_ci_classify = __esm(() => {
|
|
103765
104069
|
TRANSIENT_GH_RE = /HTTP 5\d\d|Gateway Timeout|Bad Gateway|Service Unavailable|connection reset|ECONNRESET|ETIMEDOUT|getaddrinfo|EAI_AGAIN|could not resolve host/i;
|
|
103766
104070
|
NO_CHECKS_RE = /no checks reported/i;
|
|
104071
|
+
PARTIAL_ACCESS_RE = /Resource not accessible by personal access token/i;
|
|
103767
104072
|
GH_RETRY_DELAYS = [5000, 15000, 45000];
|
|
103768
104073
|
});
|
|
103769
104074
|
|
|
103770
|
-
// apps/agent/src/agent/ci.ts
|
|
103771
|
-
async function getPrChecksStatus(prRef, runner, cwd2, onTransientRetry, ignoreCiChecks = []) {
|
|
103772
|
-
let out;
|
|
103773
|
-
try {
|
|
103774
|
-
out = await runGhWithRetry(["gh", "pr", "checks", prRef, "--json", PR_CHECKS_FIELDS], runner, cwd2, onTransientRetry);
|
|
103775
|
-
} catch (err) {
|
|
103776
|
-
const e = err;
|
|
103777
|
-
const blob = `${e.message}
|
|
103778
|
-
${e.stderr ?? ""}
|
|
103779
|
-
${e.stdout ?? ""}`;
|
|
103780
|
-
if (NO_CHECKS_RE.test(blob))
|
|
103781
|
-
return { bucket: "pass", failedRunIds: [], failedCheckNames: [] };
|
|
103782
|
-
throw err;
|
|
103783
|
-
}
|
|
103784
|
-
const ignoredLower = ignoreCiChecks.map((n) => n.toLowerCase());
|
|
103785
|
-
const checks3 = JSON.parse(out.stdout || "[]").filter((c) => !ignoredLower.includes(c.name.toLowerCase())).filter((c) => classifyGhBucket(c.bucket) !== "skip");
|
|
103786
|
-
if (checks3.some((c) => classifyGhBucket(c.bucket) === "pending")) {
|
|
103787
|
-
return { bucket: "pending", failedRunIds: [], failedCheckNames: [] };
|
|
103788
|
-
}
|
|
103789
|
-
const failed = checks3.filter((c) => classifyGhBucket(c.bucket) === "fail");
|
|
103790
|
-
if (failed.length === 0)
|
|
103791
|
-
return { bucket: "pass", failedRunIds: [], failedCheckNames: [] };
|
|
103792
|
-
const ids = new Set;
|
|
103793
|
-
for (const c of failed) {
|
|
103794
|
-
const m = c.link?.match(/\/actions\/runs\/(\d+)/);
|
|
103795
|
-
if (m)
|
|
103796
|
-
ids.add(m[1]);
|
|
103797
|
-
}
|
|
103798
|
-
return { bucket: "fail", failedRunIds: [...ids], failedCheckNames: failed.map((c) => c.name) };
|
|
103799
|
-
}
|
|
103800
|
-
async function fetchFailedRunLogs(runIds, runner, cwd2, maxCharsPerRun = 4000) {
|
|
103801
|
-
const chunks = [];
|
|
103802
|
-
for (const id of runIds) {
|
|
103803
|
-
try {
|
|
103804
|
-
const r = await runner.run(["gh", "run", "view", id, "--log-failed"], cwd2);
|
|
103805
|
-
const text = r.stdout.trim();
|
|
103806
|
-
const truncated = text.length > maxCharsPerRun ? text.slice(0, maxCharsPerRun) + `
|
|
103807
|
-
\u2026[truncated ${text.length - maxCharsPerRun} chars]` : text;
|
|
103808
|
-
chunks.push(`--- run ${id} ---
|
|
103809
|
-
${truncated}`);
|
|
103810
|
-
} catch (err) {
|
|
103811
|
-
chunks.push(`--- run ${id} ---
|
|
103812
|
-
(failed to fetch logs: ${err.message})`);
|
|
103813
|
-
}
|
|
103814
|
-
}
|
|
103815
|
-
return chunks.join(`
|
|
103816
|
-
|
|
103817
|
-
`);
|
|
103818
|
-
}
|
|
103819
|
-
async function safeSha(getHeadSha) {
|
|
103820
|
-
try {
|
|
103821
|
-
const sha = (await getHeadSha()).trim();
|
|
103822
|
-
return sha || null;
|
|
103823
|
-
} catch {
|
|
103824
|
-
return null;
|
|
103825
|
-
}
|
|
103826
|
-
}
|
|
103827
|
-
async function fixCiUntilGreen(deps, opts) {
|
|
103828
|
-
for (let attempt2 = 1;attempt2 <= opts.maxAttempts; attempt2++) {
|
|
103829
|
-
let pollN = 0;
|
|
103830
|
-
while (true) {
|
|
103831
|
-
if (deps.cancelled?.())
|
|
103832
|
-
return { success: false, attempts: attempt2 - 1, reason: "cancelled" };
|
|
103833
|
-
pollN += 1;
|
|
103834
|
-
deps.onPhase?.("ci-poll", `attempt ${attempt2}/${opts.maxAttempts} \xB7 poll ${pollN}`);
|
|
103835
|
-
let s;
|
|
103836
|
-
try {
|
|
103837
|
-
s = await deps.getStatus();
|
|
103838
|
-
} catch (err) {
|
|
103839
|
-
deps.log(`! gh pr checks failed permanently: ${err.message} \u2014 giving up CI watch`, "red");
|
|
103840
|
-
return { success: false, attempts: attempt2 - 1, reason: "gh-failed" };
|
|
103841
|
-
}
|
|
103842
|
-
if (s.bucket === "pass") {
|
|
103843
|
-
deps.log(`\u2713 CI green for PR (after ${attempt2 - 1} fix attempts)`, "green");
|
|
103844
|
-
return { success: true, attempts: attempt2 - 1 };
|
|
103845
|
-
}
|
|
103846
|
-
if (s.bucket === "fail") {
|
|
103847
|
-
deps.log(`\u2717 CI failing (attempt ${attempt2}/${opts.maxAttempts}) \u2014 fetching logs and re-running task`, "yellow");
|
|
103848
|
-
deps.onPhase?.("ci-fix", `attempt ${attempt2}/${opts.maxAttempts} \xB7 fetching logs`);
|
|
103849
|
-
const logs = await deps.getFailedLogs(s.failedRunIds);
|
|
103850
|
-
deps.onPhase?.("ci-fix", `attempt ${attempt2}/${opts.maxAttempts} \xB7 re-running worker`);
|
|
103851
|
-
const steering = `CI is failing on this PR. Investigate and fix:
|
|
103852
|
-
|
|
103853
|
-
\`\`\`
|
|
103854
|
-
${logs}
|
|
103855
|
-
\`\`\``;
|
|
103856
|
-
const shaBefore = deps.getHeadSha ? await safeSha(deps.getHeadSha) : null;
|
|
103857
|
-
const code = await deps.runTaskWithSteering(steering);
|
|
103858
|
-
if (code !== 0) {
|
|
103859
|
-
deps.log(`! task loop exited code ${code} during CI fix attempt ${attempt2}`, "red");
|
|
103860
|
-
}
|
|
103861
|
-
if (shaBefore !== null) {
|
|
103862
|
-
const shaAfter = await safeSha(deps.getHeadSha);
|
|
103863
|
-
if (shaAfter !== null && shaAfter === shaBefore) {
|
|
103864
|
-
deps.log(`! worker produced no new commits on CI fix attempt ${attempt2} \u2014 failure looks external (e.g. rate-limited deploy). Giving up CI watch.`, "yellow");
|
|
103865
|
-
return { success: false, attempts: attempt2, reason: "no-progress" };
|
|
103866
|
-
}
|
|
103867
|
-
}
|
|
103868
|
-
try {
|
|
103869
|
-
deps.onPhase?.("ci-fix", `attempt ${attempt2}/${opts.maxAttempts} \xB7 pushing fix`);
|
|
103870
|
-
await deps.pushBranch();
|
|
103871
|
-
} catch (err) {
|
|
103872
|
-
deps.log(`! push failed during CI fix: ${err.message}`, "red");
|
|
103873
|
-
return { success: false, attempts: attempt2, reason: "push-failed" };
|
|
103874
|
-
}
|
|
103875
|
-
break;
|
|
103876
|
-
}
|
|
103877
|
-
deps.onPhase?.("ci-poll", `attempt ${attempt2}/${opts.maxAttempts} \xB7 pending, waiting`);
|
|
103878
|
-
await deps.sleep(opts.pollIntervalSeconds * 1000);
|
|
103879
|
-
}
|
|
103880
|
-
}
|
|
103881
|
-
return { success: false, attempts: opts.maxAttempts, reason: "max-attempts" };
|
|
103882
|
-
}
|
|
103883
|
-
var PR_CHECKS_FIELDS = "name,bucket,link,workflow,event";
|
|
103884
|
-
var init_ci = __esm(() => {
|
|
103885
|
-
init_ci_classify();
|
|
103886
|
-
});
|
|
103887
|
-
|
|
103888
104075
|
// apps/agent/src/pr-status.ts
|
|
103889
104076
|
function bucketChecks(rollup, prState, ignoreCiChecks = []) {
|
|
103890
104077
|
if (rollup === null || rollup === undefined) {
|
|
@@ -104488,33 +104675,9 @@ async function runWorkerWithFixTask(ctx, heading, body) {
|
|
|
104488
104675
|
}
|
|
104489
104676
|
return code;
|
|
104490
104677
|
}
|
|
104491
|
-
async function pushBranchSafely(ctx) {
|
|
104492
|
-
try {
|
|
104493
|
-
ctx.emit("pushing", "after conflict resolution");
|
|
104494
|
-
await ctx.cmd.run(["git", "push", "origin", ctx.branch], ctx.cwd);
|
|
104495
|
-
return true;
|
|
104496
|
-
} catch (pushErr) {
|
|
104497
|
-
const pe = pushErr;
|
|
104498
|
-
const blob = `${pe.message}
|
|
104499
|
-
${pe.stderr ?? ""}`;
|
|
104500
|
-
if (!/non-fast-forward|Updates were rejected/i.test(blob)) {
|
|
104501
|
-
ctx.log(`! push after conflict fix failed: ${pe.message}`, "red");
|
|
104502
|
-
return false;
|
|
104503
|
-
}
|
|
104504
|
-
try {
|
|
104505
|
-
await ctx.cmd.run(["git", "fetch", "origin", ctx.branch], ctx.cwd);
|
|
104506
|
-
await ctx.cmd.run(["git", "merge", "--no-edit", `origin/${ctx.branch}`], ctx.cwd);
|
|
104507
|
-
await ctx.cmd.run(["git", "push", "origin", ctx.branch], ctx.cwd);
|
|
104508
|
-
return true;
|
|
104509
|
-
} catch (retryErr) {
|
|
104510
|
-
ctx.log(`! push after merging origin/${ctx.branch} failed: ${retryErr.message}`, "red");
|
|
104511
|
-
return false;
|
|
104512
|
-
}
|
|
104513
|
-
}
|
|
104514
|
-
}
|
|
104515
104678
|
async function createPrWithRetry(ctx, issue2) {
|
|
104516
104679
|
const base2 = ctx.base;
|
|
104517
|
-
const maxAttempts =
|
|
104680
|
+
const maxAttempts = MAX_PR_CREATE_ATTEMPTS;
|
|
104518
104681
|
let hookFixAttempt = 0;
|
|
104519
104682
|
let nonFfRebaseAttempted = false;
|
|
104520
104683
|
let pr = null;
|
|
@@ -104627,86 +104790,6 @@ ${reBlob.trim()}`);
|
|
|
104627
104790
|
}
|
|
104628
104791
|
}
|
|
104629
104792
|
}
|
|
104630
|
-
async function fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict) {
|
|
104631
|
-
const wantConflictLoop = !!checkPrConflict;
|
|
104632
|
-
const maxOuterAttempts = ctx.cfg.maxCiFixAttempts;
|
|
104633
|
-
let outerAttempt = 0;
|
|
104634
|
-
let ciConfirmedGreen = false;
|
|
104635
|
-
while (outerAttempt < maxOuterAttempts) {
|
|
104636
|
-
if (wantConflictLoop) {
|
|
104637
|
-
ctx.emit("conflict-check");
|
|
104638
|
-
let conflicting = false;
|
|
104639
|
-
try {
|
|
104640
|
-
conflicting = await checkPrConflict(prUrl);
|
|
104641
|
-
} catch (err) {
|
|
104642
|
-
ctx.log(`! conflict check failed: ${err.message}`, "yellow");
|
|
104643
|
-
}
|
|
104644
|
-
if (!conflicting && ciConfirmedGreen)
|
|
104645
|
-
return 0;
|
|
104646
|
-
if (conflicting) {
|
|
104647
|
-
outerAttempt++;
|
|
104648
|
-
ciConfirmedGreen = false;
|
|
104649
|
-
ctx.emit("conflict-fix-inner", `attempt ${outerAttempt}/${maxOuterAttempts}`);
|
|
104650
|
-
ctx.log(` merge conflicts on PR (attempt ${outerAttempt}/${maxOuterAttempts}) \u2014 spawning resolution task`, "yellow");
|
|
104651
|
-
const conflictCode = await runWorkerWithFixTask(ctx, "Resolve PR merge conflicts", [
|
|
104652
|
-
`The PR ${prUrl} has merge conflicts with \`${ctx.base}\`.`,
|
|
104653
|
-
"",
|
|
104654
|
-
"Steps:",
|
|
104655
|
-
`1. \`git fetch origin ${ctx.base}\` then merge \`${ctx.base}\` into the current branch (\`git merge origin/${ctx.base}\`). Do NOT rebase and do NOT amend existing commits.`,
|
|
104656
|
-
"2. Resolve conflicts in the files git lists.",
|
|
104657
|
-
"3. Stage and commit the resolution as a new merge commit."
|
|
104658
|
-
].join(`
|
|
104659
|
-
`));
|
|
104660
|
-
if (conflictCode !== 0) {
|
|
104661
|
-
ctx.log(`! conflict resolution worker exited code ${conflictCode} \u2014 giving up`, "red");
|
|
104662
|
-
return PR_FAILED_EXIT;
|
|
104663
|
-
}
|
|
104664
|
-
const pushed = await pushBranchSafely(ctx);
|
|
104665
|
-
if (!pushed)
|
|
104666
|
-
return PR_FAILED_EXIT;
|
|
104667
|
-
continue;
|
|
104668
|
-
}
|
|
104669
|
-
}
|
|
104670
|
-
if (!wantFixCi)
|
|
104671
|
-
break;
|
|
104672
|
-
if (!ciConfirmedGreen) {
|
|
104673
|
-
ctx.log(` watching CI for ${prUrl} (max ${ctx.cfg.maxCiFixAttempts} fix attempts)`, "gray");
|
|
104674
|
-
ctx.emit("ci-poll", "starting");
|
|
104675
|
-
const result2 = await fixCiUntilGreen({
|
|
104676
|
-
onPhase: (p, d) => ctx.emit(p, d),
|
|
104677
|
-
getStatus: () => getPrChecksStatus(prUrl, ctx.cmd, ctx.cwd, (n, ms, why) => ctx.log(` gh transient (try ${n}) \u2014 retry in ${Math.round(ms / 1000)}s \xB7 ${why}`, "yellow"), ctx.cfg.ignoreCiChecks),
|
|
104678
|
-
getFailedLogs: (ids) => fetchFailedRunLogs(ids, ctx.cmd, ctx.cwd),
|
|
104679
|
-
runTaskWithSteering: (steering) => runWorkerWithFixTask(ctx, "Fix failing CI checks", steering),
|
|
104680
|
-
pushBranch: async () => {
|
|
104681
|
-
await ctx.cmd.run(["git", "push", "origin", ctx.branch], ctx.cwd);
|
|
104682
|
-
},
|
|
104683
|
-
getHeadSha: async () => {
|
|
104684
|
-
const r = await ctx.cmd.run(["git", "rev-parse", "HEAD"], ctx.cwd);
|
|
104685
|
-
return r.stdout.trim();
|
|
104686
|
-
},
|
|
104687
|
-
log: ctx.log,
|
|
104688
|
-
sleep: (ms) => new Promise((r) => setTimeout(r, ms))
|
|
104689
|
-
}, {
|
|
104690
|
-
maxAttempts: ctx.cfg.maxCiFixAttempts,
|
|
104691
|
-
pollIntervalSeconds: ctx.cfg.ciPollIntervalSeconds
|
|
104692
|
-
});
|
|
104693
|
-
if (!result2.success) {
|
|
104694
|
-
ctx.log(`! CI fix loop gave up after ${result2.attempts} attempts (${result2.reason ?? "unknown"}) \u2014 withholding done-status until CI passes`, "red");
|
|
104695
|
-
return CI_FAILED_EXIT;
|
|
104696
|
-
}
|
|
104697
|
-
ciConfirmedGreen = true;
|
|
104698
|
-
}
|
|
104699
|
-
if (wantConflictLoop) {
|
|
104700
|
-
continue;
|
|
104701
|
-
}
|
|
104702
|
-
return 0;
|
|
104703
|
-
}
|
|
104704
|
-
if (outerAttempt >= maxOuterAttempts) {
|
|
104705
|
-
ctx.log(`! outer fix loop exhausted ${maxOuterAttempts} attempts \u2014 giving up`, "red");
|
|
104706
|
-
return CI_FAILED_EXIT;
|
|
104707
|
-
}
|
|
104708
|
-
return 0;
|
|
104709
|
-
}
|
|
104710
104793
|
async function findNeverTouchViolations(cmd, cwd2, base2, neverTouch) {
|
|
104711
104794
|
if (neverTouch.length === 0)
|
|
104712
104795
|
return [];
|
|
@@ -104727,27 +104810,8 @@ async function findNeverTouchViolations(cmd, cwd2, base2, neverTouch) {
|
|
|
104727
104810
|
return findBoundaryViolations(files, neverTouch);
|
|
104728
104811
|
}
|
|
104729
104812
|
async function runPrPhase(input, deps) {
|
|
104730
|
-
const {
|
|
104731
|
-
|
|
104732
|
-
cwd: cwd2,
|
|
104733
|
-
branch,
|
|
104734
|
-
changeDir,
|
|
104735
|
-
stateFilePath,
|
|
104736
|
-
issue: issue2,
|
|
104737
|
-
wantFixCi,
|
|
104738
|
-
wantAutoMerge,
|
|
104739
|
-
cfg
|
|
104740
|
-
} = input;
|
|
104741
|
-
const {
|
|
104742
|
-
cmd,
|
|
104743
|
-
log: log3,
|
|
104744
|
-
emit: emit3,
|
|
104745
|
-
respawnWorker,
|
|
104746
|
-
registerPr,
|
|
104747
|
-
onPrReady,
|
|
104748
|
-
checkPrConflict,
|
|
104749
|
-
resolveDependencyBaseBranch
|
|
104750
|
-
} = deps;
|
|
104813
|
+
const { changeName, cwd: cwd2, branch, changeDir, stateFilePath, issue: issue2, wantAutoMerge, cfg } = input;
|
|
104814
|
+
const { cmd, log: log3, emit: emit3, respawnWorker, registerPr, onPrReady, resolveDependencyBaseBranch } = deps;
|
|
104751
104815
|
if (!branch || !issue2) {
|
|
104752
104816
|
log3(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
|
|
104753
104817
|
return PR_FAILED_EXIT;
|
|
@@ -104814,7 +104878,7 @@ ${indented}${suffix}`, "yellow");
|
|
|
104814
104878
|
}
|
|
104815
104879
|
return PR_FAILED_EXIT;
|
|
104816
104880
|
}
|
|
104817
|
-
const maxOuterAttempts =
|
|
104881
|
+
const maxOuterAttempts = MAX_PR_CREATE_ATTEMPTS;
|
|
104818
104882
|
let onlyMetaAttempts = 0;
|
|
104819
104883
|
let pr = null;
|
|
104820
104884
|
const finalizeNoOpAsDone = cfg.finalizeNoOpAsDone !== false;
|
|
@@ -104889,36 +104953,8 @@ ${indented}${suffix}`, "yellow");
|
|
|
104889
104953
|
}
|
|
104890
104954
|
log3(` ${pr.created ? "opened" : "found existing"} PR: ${prUrl}`, "green");
|
|
104891
104955
|
registerPr?.(changeName, prUrl);
|
|
104892
|
-
let
|
|
104893
|
-
|
|
104894
|
-
if (!prReadyNeeded && wantAutoMerge) {
|
|
104895
|
-
const fallbackEnabled = cfg.manualMergeWhenAutoMergeDisabled !== false;
|
|
104896
|
-
const repoAllowsAutoMerge = await detectRepoAutoMergeAllowed(prUrl, cmd, cwd2, log3);
|
|
104897
|
-
if (repoAllowsAutoMerge === false && fallbackEnabled) {
|
|
104898
|
-
log3(` repo has auto-merge disabled \u2014 will poll ${prUrl} and merge via gh pr merge once checks pass`, "yellow");
|
|
104899
|
-
manualMergePending = true;
|
|
104900
|
-
} else {
|
|
104901
|
-
try {
|
|
104902
|
-
await cmd.run(["gh", "pr", "merge", prUrl, "--auto", `--${cfg.autoMergeStrategy}`], cwd2);
|
|
104903
|
-
log3(` enabled auto-merge (${cfg.autoMergeStrategy}) on ${prUrl}`, "green");
|
|
104904
|
-
emit3("auto-merge-enabled", cfg.autoMergeStrategy);
|
|
104905
|
-
} catch (err) {
|
|
104906
|
-
const e = err;
|
|
104907
|
-
const detail = e.stderr?.trim() || e.message;
|
|
104908
|
-
log3(`! failed to enable auto-merge on ${prUrl}: ${detail}`, "yellow");
|
|
104909
|
-
if (fallbackEnabled && /auto[- ]merge/i.test(detail)) {
|
|
104910
|
-
log3(` falling back to manual merge after CI passes for ${prUrl}`, "yellow");
|
|
104911
|
-
manualMergePending = true;
|
|
104912
|
-
}
|
|
104913
|
-
}
|
|
104914
|
-
}
|
|
104915
|
-
} else if (prReadyNeeded && wantAutoMerge) {
|
|
104916
|
-
manualMergePending = true;
|
|
104917
|
-
}
|
|
104918
|
-
const ciResult = await fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict);
|
|
104919
|
-
if (ciResult !== 0)
|
|
104920
|
-
return ciResult;
|
|
104921
|
-
if (prReadyNeeded) {
|
|
104956
|
+
let readyOk = true;
|
|
104957
|
+
if (cfg.prDraft === true) {
|
|
104922
104958
|
emit3("pr-ready");
|
|
104923
104959
|
try {
|
|
104924
104960
|
await cmd.run(["gh", "pr", "ready", prUrl], cwd2);
|
|
@@ -104926,22 +104962,25 @@ ${indented}${suffix}`, "yellow");
|
|
|
104926
104962
|
} catch (err) {
|
|
104927
104963
|
const e = err;
|
|
104928
104964
|
log3(`! gh pr ready failed for ${prUrl}: ${e.stderr?.trim() || e.message}`, "yellow");
|
|
104929
|
-
|
|
104965
|
+
readyOk = false;
|
|
104930
104966
|
}
|
|
104931
104967
|
}
|
|
104932
|
-
if (
|
|
104933
|
-
|
|
104934
|
-
|
|
104935
|
-
log3(`
|
|
104936
|
-
|
|
104937
|
-
|
|
104938
|
-
|
|
104939
|
-
|
|
104968
|
+
if (wantAutoMerge && readyOk) {
|
|
104969
|
+
const repoAllowsAutoMerge = await detectRepoAutoMergeAllowed(prUrl, cmd, cwd2, log3);
|
|
104970
|
+
if (repoAllowsAutoMerge === false) {
|
|
104971
|
+
log3(cfg.manualMergeWhenAutoMergeDisabled !== false ? ` repo has auto-merge disabled \u2014 leaving ${prUrl} open for manual merge once checks pass` : ` repo has auto-merge disabled (manual-merge fallback off) \u2014 ${prUrl} will not auto-merge`, "yellow");
|
|
104972
|
+
} else {
|
|
104973
|
+
try {
|
|
104974
|
+
await cmd.run(["gh", "pr", "merge", prUrl, "--auto", `--${cfg.autoMergeStrategy}`], cwd2);
|
|
104975
|
+
log3(` enabled auto-merge (${cfg.autoMergeStrategy}) on ${prUrl}`, "green");
|
|
104976
|
+
emit3("auto-merge-enabled", cfg.autoMergeStrategy);
|
|
104977
|
+
} catch (err) {
|
|
104978
|
+
const e = err;
|
|
104979
|
+
log3(`! failed to enable auto-merge on ${prUrl}: ${e.stderr?.trim() || e.message}`, "yellow");
|
|
104980
|
+
}
|
|
104940
104981
|
}
|
|
104941
104982
|
}
|
|
104942
|
-
|
|
104943
|
-
await onPrReady?.(prUrl);
|
|
104944
|
-
}
|
|
104983
|
+
await onPrReady?.(prUrl);
|
|
104945
104984
|
return 0;
|
|
104946
104985
|
}
|
|
104947
104986
|
async function runWorktreeCleanupPhase(input, deps) {
|
|
@@ -105048,7 +105087,6 @@ async function runPostTask(input, deps) {
|
|
|
105048
105087
|
exitCode,
|
|
105049
105088
|
useWorktree,
|
|
105050
105089
|
wantPr,
|
|
105051
|
-
wantFixCi,
|
|
105052
105090
|
wantAutoMerge,
|
|
105053
105091
|
wantValidateOnly,
|
|
105054
105092
|
cfg,
|
|
@@ -105138,7 +105176,6 @@ async function runPostTask(input, deps) {
|
|
|
105138
105176
|
changeDir,
|
|
105139
105177
|
stateFilePath,
|
|
105140
105178
|
issue: issue2,
|
|
105141
|
-
wantFixCi,
|
|
105142
105179
|
wantAutoMerge,
|
|
105143
105180
|
cfg
|
|
105144
105181
|
}, {
|
|
@@ -105148,7 +105185,6 @@ async function runPostTask(input, deps) {
|
|
|
105148
105185
|
respawnWorker,
|
|
105149
105186
|
...deps.registerPr !== undefined ? { registerPr: deps.registerPr } : {},
|
|
105150
105187
|
...deps.onPrReady !== undefined ? { onPrReady: deps.onPrReady } : {},
|
|
105151
|
-
...deps.checkPrConflict !== undefined ? { checkPrConflict: deps.checkPrConflict } : {},
|
|
105152
105188
|
...deps.resolveDependencyBaseBranch !== undefined ? { resolveDependencyBaseBranch: deps.resolveDependencyBaseBranch } : {}
|
|
105153
105189
|
});
|
|
105154
105190
|
}
|
|
@@ -105169,7 +105205,7 @@ async function runPostTask(input, deps) {
|
|
|
105169
105205
|
await runTeardownPhase({ cwd: cwd2, teardownScript: cfg.teardownScript }, { runScript, log: log3, emit: emit3 });
|
|
105170
105206
|
return effectiveCode;
|
|
105171
105207
|
}
|
|
105172
|
-
var
|
|
105208
|
+
var PR_FAILED_EXIT = 71, MAX_PR_CREATE_ATTEMPTS = 5, NO_CHANGES_EXIT = 72, repoAutoMergeCache, defaultRunCommand = async (cmd, cwd2) => {
|
|
105173
105209
|
const proc = Bun.spawnSync({
|
|
105174
105210
|
cmd: ["sh", "-c", cmd],
|
|
105175
105211
|
cwd: cwd2,
|
|
@@ -105188,7 +105224,6 @@ var init_post_task = __esm(() => {
|
|
|
105188
105224
|
init_git2();
|
|
105189
105225
|
init_linear();
|
|
105190
105226
|
init_pr();
|
|
105191
|
-
init_ci();
|
|
105192
105227
|
init_pr_status();
|
|
105193
105228
|
init_wait_for_mergeability();
|
|
105194
105229
|
init_worktree();
|
|
@@ -105593,10 +105628,12 @@ class AgentCoordinator {
|
|
|
105593
105628
|
continue;
|
|
105594
105629
|
if (!this.dependenciesResolved(issue2))
|
|
105595
105630
|
continue;
|
|
105596
|
-
if (await this.maybePromoteFinishedConflicted(issue2))
|
|
105597
|
-
continue;
|
|
105598
105631
|
const changeDir = this.deps.getChangeDir?.(issue2) ?? undefined;
|
|
105599
105632
|
const actor = await this.flowStore.getActor(issue2.id, changeDir);
|
|
105633
|
+
if (actor.getSnapshot().value === "awaiting-ci")
|
|
105634
|
+
continue;
|
|
105635
|
+
if (await this.maybePromoteFinishedConflicted(issue2))
|
|
105636
|
+
continue;
|
|
105600
105637
|
actor.send({ type: "RESUME_DETECTED" });
|
|
105601
105638
|
if (changeDir) {
|
|
105602
105639
|
await this.flowStore.persistActor(issue2.id, changeDir).catch(() => {});
|
|
@@ -105741,6 +105778,12 @@ class AgentCoordinator {
|
|
|
105741
105778
|
return false;
|
|
105742
105779
|
if (pr.status !== "conflicted" && pr.status !== "ci_failed")
|
|
105743
105780
|
return false;
|
|
105781
|
+
if (!this.opts.prRecovery?.enabled)
|
|
105782
|
+
return false;
|
|
105783
|
+
if (pr.status === "conflicted" && !this.opts.prRecovery.fixConflicts)
|
|
105784
|
+
return false;
|
|
105785
|
+
if (pr.status === "ci_failed" && !this.opts.prRecovery.fixCi)
|
|
105786
|
+
return false;
|
|
105744
105787
|
const stateLabel = pr.status === "conflicted" ? "conflicting with main" : "failing CI";
|
|
105745
105788
|
if (this.conflictPromoted.has(issue2.id))
|
|
105746
105789
|
return true;
|
|
@@ -105878,6 +105921,8 @@ class AgentCoordinator {
|
|
|
105878
105921
|
}
|
|
105879
105922
|
async scanPrMergeStates() {
|
|
105880
105923
|
const counts = emptyPrStatus();
|
|
105924
|
+
if (!this.opts.prRecovery?.enabled)
|
|
105925
|
+
return counts;
|
|
105881
105926
|
let candidates = [];
|
|
105882
105927
|
try {
|
|
105883
105928
|
candidates = await this.deps.fetchDoneCandidates();
|
|
@@ -105922,7 +105967,26 @@ class AgentCoordinator {
|
|
|
105922
105967
|
this.deps.onLog(`! pr-tracker clear failed for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
105923
105968
|
}
|
|
105924
105969
|
}
|
|
105970
|
+
if (pr.status === "mergeable") {
|
|
105971
|
+
const changeDir = this.deps.getChangeDir?.(issue2) ?? undefined;
|
|
105972
|
+
const actor = await this.flowStore.getActor(issue2.id, changeDir);
|
|
105973
|
+
if (actor.getSnapshot().value === "awaiting-ci") {
|
|
105974
|
+
if (this.issueInSetDoneState(issue2)) {
|
|
105975
|
+
actor.send({ type: "PR_PASSED" });
|
|
105976
|
+
if (changeDir)
|
|
105977
|
+
await this.flowStore.persistActor(issue2.id, changeDir).catch(() => {});
|
|
105978
|
+
if (actor.getSnapshot().value === "done") {
|
|
105979
|
+
this.flowStore.disposeActor(issue2.id);
|
|
105980
|
+
}
|
|
105981
|
+
} else {
|
|
105982
|
+
await this.advancePrToDone(issue2, pr.url, actor, changeDir);
|
|
105983
|
+
}
|
|
105984
|
+
}
|
|
105985
|
+
continue;
|
|
105986
|
+
}
|
|
105925
105987
|
if (pr.status === "conflicted") {
|
|
105988
|
+
if (!this.opts.prRecovery?.fixConflicts)
|
|
105989
|
+
continue;
|
|
105926
105990
|
if (tracker?.isBailed(issue2.identifier)) {
|
|
105927
105991
|
counts.quarantined += 1;
|
|
105928
105992
|
continue;
|
|
@@ -105956,6 +106020,8 @@ class AgentCoordinator {
|
|
|
105956
106020
|
continue;
|
|
105957
106021
|
}
|
|
105958
106022
|
if (pr.status === "ci_failed") {
|
|
106023
|
+
if (!this.opts.prRecovery?.fixCi)
|
|
106024
|
+
continue;
|
|
105959
106025
|
if (tracker?.isBailed(issue2.identifier)) {
|
|
105960
106026
|
counts.quarantined += 1;
|
|
105961
106027
|
continue;
|
|
@@ -106002,6 +106068,49 @@ class AgentCoordinator {
|
|
|
106002
106068
|
}
|
|
106003
106069
|
return counts;
|
|
106004
106070
|
}
|
|
106071
|
+
issueInSetDoneState(issue2) {
|
|
106072
|
+
const sd = this.opts.setDone;
|
|
106073
|
+
if (!sd)
|
|
106074
|
+
return false;
|
|
106075
|
+
return issueMatchesGetIndicator(issue2, { filter: markersOf(sd) });
|
|
106076
|
+
}
|
|
106077
|
+
async advancePrToDone(issue2, prUrl, actor, changeDir) {
|
|
106078
|
+
this.deps.onLog(` ${issue2.identifier}: PR ${prUrl} mergeable \u2014 moving to done`, "green");
|
|
106079
|
+
if (this.opts.setDone) {
|
|
106080
|
+
try {
|
|
106081
|
+
await this.deps.applyIndicator(issue2, this.opts.setDone);
|
|
106082
|
+
this.deps.onLog(` ${issue2.identifier}: setDone applied`, "gray");
|
|
106083
|
+
} catch (err) {
|
|
106084
|
+
this.deps.onLog(`! Linear setDone failed for ${issue2.identifier}: ${err.message}`, "red");
|
|
106085
|
+
emitCapture(this.bus, "agent_indicator_failed", {
|
|
106086
|
+
indicator: "setDone",
|
|
106087
|
+
issue_identifier: issue2.identifier,
|
|
106088
|
+
error: err.message
|
|
106089
|
+
});
|
|
106090
|
+
return;
|
|
106091
|
+
}
|
|
106092
|
+
if (this.opts.setInProgress) {
|
|
106093
|
+
try {
|
|
106094
|
+
await this.deps.removeIndicator(issue2, this.opts.setInProgress);
|
|
106095
|
+
this.deps.onLog(` ${issue2.identifier}: clearInProgress applied`, "gray");
|
|
106096
|
+
} catch {}
|
|
106097
|
+
}
|
|
106098
|
+
}
|
|
106099
|
+
actor.send({ type: "PR_PASSED" });
|
|
106100
|
+
if (changeDir) {
|
|
106101
|
+
await this.flowStore.persistActor(issue2.id, changeDir).catch(() => {});
|
|
106102
|
+
}
|
|
106103
|
+
if (actor.getSnapshot().value === "done") {
|
|
106104
|
+
this.flowStore.disposeActor(issue2.id);
|
|
106105
|
+
}
|
|
106106
|
+
if (this.opts.postComments !== false) {
|
|
106107
|
+
try {
|
|
106108
|
+
await this.deps.postComment(issue2, `\u2705 Ralph verified this PR (${prUrl}) is mergeable (CI green, no conflicts) \u2014 moving to done`);
|
|
106109
|
+
} catch (err) {
|
|
106110
|
+
this.deps.onLog(`! Linear done comment failed for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
106111
|
+
}
|
|
106112
|
+
}
|
|
106113
|
+
}
|
|
106005
106114
|
errorMarkerCleared(issue2) {
|
|
106006
106115
|
const se = this.opts.setError;
|
|
106007
106116
|
if (!se)
|
|
@@ -106310,9 +106419,12 @@ class AgentCoordinator {
|
|
|
106310
106419
|
async notifyExited(issue2, changeName, code, trigger, workerCwd) {
|
|
106311
106420
|
const noChanges = code === NO_CHANGES_EXIT;
|
|
106312
106421
|
const ok = code === 0 || noChanges;
|
|
106422
|
+
const isRecoveryTrigger = trigger === "conflict-fix" || trigger === "ci-fix";
|
|
106423
|
+
const prOpened = this.deps.hasPrForChange?.(changeName) ?? false;
|
|
106424
|
+
const deferDone = ok && !isRecoveryTrigger && !!this.opts.createsPrs && prOpened && !!this.opts.prRecovery?.enabled;
|
|
106313
106425
|
const changeDir = this.deps.getChangeDir?.(issue2) ?? undefined;
|
|
106314
106426
|
const exitActor = await this.flowStore.getActor(issue2.id, changeDir);
|
|
106315
|
-
exitActor.send({ type: ok ? "
|
|
106427
|
+
exitActor.send({ type: !ok ? "WORKER_FAILED" : deferDone ? "PR_OPENED" : "WORKER_SUCCEEDED" });
|
|
106316
106428
|
if (changeDir) {
|
|
106317
106429
|
await this.flowStore.persistActor(issue2.id, changeDir).catch(() => {});
|
|
106318
106430
|
}
|
|
@@ -106365,6 +106477,8 @@ class AgentCoordinator {
|
|
|
106365
106477
|
} else if (trigger === "ci-fix") {
|
|
106366
106478
|
this.ciFailedNotified.delete(issue2.id);
|
|
106367
106479
|
this.conflictPromoted.delete(issue2.id);
|
|
106480
|
+
} else if (deferDone) {
|
|
106481
|
+
this.deps.onLog(` ${issue2.identifier}: PR open \u2014 deferring setDone to the PR-recovery watcher`, "gray");
|
|
106368
106482
|
} else if (this.opts.setDone) {
|
|
106369
106483
|
try {
|
|
106370
106484
|
await this.deps.applyIndicator(issue2, this.opts.setDone);
|
|
@@ -106446,6 +106560,7 @@ var emptyPrStatus = () => ({
|
|
|
106446
106560
|
});
|
|
106447
106561
|
var init_coordinator = __esm(() => {
|
|
106448
106562
|
init_types2();
|
|
106563
|
+
init_linear_client();
|
|
106449
106564
|
init_post_task();
|
|
106450
106565
|
init_queue_order();
|
|
106451
106566
|
init_src();
|
|
@@ -106983,7 +107098,7 @@ function issueInAwaitingStatus(issue2, indicators) {
|
|
|
106983
107098
|
async function releaseAwaitingMarker(issue2, statePath, deps) {
|
|
106984
107099
|
const { stateObj, confirmation } = await readConfirmationState(statePath);
|
|
106985
107100
|
if (!confirmation.awaitingMarkerAppliedAt && !issueInAwaitingStatus(issue2, deps.indicators)) {
|
|
106986
|
-
return;
|
|
107101
|
+
return false;
|
|
106987
107102
|
}
|
|
106988
107103
|
if (deps.indicators.clearAwaitingConfirmation) {
|
|
106989
107104
|
try {
|
|
@@ -107005,6 +107120,7 @@ async function releaseAwaitingMarker(issue2, statePath, deps) {
|
|
|
107005
107120
|
} catch (err) {
|
|
107006
107121
|
deps.onLog(`! persist cleared awaitingMarkerAppliedAt for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
107007
107122
|
}
|
|
107123
|
+
return true;
|
|
107008
107124
|
}
|
|
107009
107125
|
function confirmationUsesCommentIndicator(cfg) {
|
|
107010
107126
|
const { getApproved, getAutoApprove, getConfirmGate } = cfg.linear.indicators;
|
|
@@ -107066,13 +107182,15 @@ async function processAwaitingForIssue(issue2, deps) {
|
|
|
107066
107182
|
persistedConfirmation: confirmation
|
|
107067
107183
|
});
|
|
107068
107184
|
if (!active) {
|
|
107069
|
-
deps.awaitingChangeSet.delete(changeName);
|
|
107070
|
-
await releaseAwaitingMarker(issue2, statePath, {
|
|
107185
|
+
const wasTracked = deps.awaitingChangeSet.delete(changeName);
|
|
107186
|
+
const released = await releaseAwaitingMarker(issue2, statePath, {
|
|
107071
107187
|
indicators,
|
|
107072
107188
|
applyIndicator: deps.applyIndicator,
|
|
107073
107189
|
onLog: deps.onLog
|
|
107074
107190
|
});
|
|
107075
|
-
|
|
107191
|
+
if (wasTracked || released) {
|
|
107192
|
+
deps.onLog(` ${issue2.identifier}: confirmation detect released \u2014 gate-cleared`);
|
|
107193
|
+
}
|
|
107076
107194
|
return false;
|
|
107077
107195
|
}
|
|
107078
107196
|
if (!hasUnchecked(tasks2 ?? "")) {
|
|
@@ -107216,8 +107334,28 @@ function parsePrNumber(url2) {
|
|
|
107216
107334
|
const m = PR_NUMBER_RE.exec(url2);
|
|
107217
107335
|
return m ? Number(m[1]) : null;
|
|
107218
107336
|
}
|
|
107337
|
+
function pickDependencyTip(candidates, blockedByOfCandidate) {
|
|
107338
|
+
const candidateIds = new Set(candidates.map((c) => c.blockerId));
|
|
107339
|
+
const upstream = new Set;
|
|
107340
|
+
for (const c of candidates) {
|
|
107341
|
+
const blockers = blockedByOfCandidate.get(c.blockerId) ?? new Set;
|
|
107342
|
+
for (const otherId of blockers) {
|
|
107343
|
+
if (otherId !== c.blockerId && candidateIds.has(otherId))
|
|
107344
|
+
upstream.add(otherId);
|
|
107345
|
+
}
|
|
107346
|
+
}
|
|
107347
|
+
const tips = candidates.filter((c) => !upstream.has(c.blockerId));
|
|
107348
|
+
return tips.length === 1 ? tips[0] : null;
|
|
107349
|
+
}
|
|
107219
107350
|
async function resolveDependencyBaseBranchImpl(issue2, runner, runnerCwd, deps) {
|
|
107220
|
-
|
|
107351
|
+
let blockerIds;
|
|
107352
|
+
try {
|
|
107353
|
+
const live = await fetchBlockedByForIssues(deps.apiKey, [issue2.id]);
|
|
107354
|
+
blockerIds = (live.get(issue2.id) ?? []).map((b) => b.id);
|
|
107355
|
+
} catch (err) {
|
|
107356
|
+
deps.onLog(`! could not refresh blockers for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
107357
|
+
blockerIds = issue2.blockedByIds;
|
|
107358
|
+
}
|
|
107221
107359
|
if (blockerIds.length === 0)
|
|
107222
107360
|
return null;
|
|
107223
107361
|
let attachmentsByBlocker;
|
|
@@ -107251,17 +107389,30 @@ async function resolveDependencyBaseBranchImpl(issue2, runner, runnerCwd, deps)
|
|
|
107251
107389
|
}
|
|
107252
107390
|
}
|
|
107253
107391
|
if (openPrs.length === 1) {
|
|
107254
|
-
candidates.push(openPrs[0]);
|
|
107392
|
+
candidates.push({ blockerId, base: openPrs[0] });
|
|
107255
107393
|
} else if (openPrs.length > 1) {
|
|
107256
107394
|
deps.onLog(` ${issue2.identifier}: blocker ${blockerId} has ${openPrs.length} open PRs \u2014 skipping dependency base resolution`, "gray");
|
|
107257
107395
|
}
|
|
107258
107396
|
}
|
|
107259
107397
|
if (candidates.length === 1)
|
|
107260
|
-
return candidates[0];
|
|
107261
|
-
if (candidates.length
|
|
107262
|
-
|
|
107398
|
+
return candidates[0].base;
|
|
107399
|
+
if (candidates.length === 0)
|
|
107400
|
+
return null;
|
|
107401
|
+
let blockedByOfCandidate;
|
|
107402
|
+
try {
|
|
107403
|
+
const map3 = await fetchBlockedByForIssues(deps.apiKey, candidates.map((c) => c.blockerId));
|
|
107404
|
+
blockedByOfCandidate = new Map([...map3.entries()].map(([id, refs]) => [id, new Set(refs.map((r) => r.id))]));
|
|
107405
|
+
} catch (err) {
|
|
107406
|
+
deps.onLog(`! could not resolve dependency order for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
107407
|
+
return null;
|
|
107263
107408
|
}
|
|
107264
|
-
|
|
107409
|
+
const tip = pickDependencyTip(candidates, blockedByOfCandidate);
|
|
107410
|
+
if (!tip) {
|
|
107411
|
+
deps.onLog(` ${issue2.identifier}: ${candidates.length} blockers have open PRs with no single dependency tip \u2014 falling back to default base`, "gray");
|
|
107412
|
+
return null;
|
|
107413
|
+
}
|
|
107414
|
+
deps.onLog(` ${issue2.identifier}: ${candidates.length} blockers have open PRs \u2014 stacking onto tip ${tip.base.blockerIdentifier ?? tip.blockerId}`, "gray");
|
|
107415
|
+
return tip.base;
|
|
107265
107416
|
}
|
|
107266
107417
|
function createOpenDraftPr(deps) {
|
|
107267
107418
|
const create3 = deps.createPr ?? createPullRequest;
|
|
@@ -107305,11 +107456,18 @@ function traceCmdRunner(base2, onStart, onEnd) {
|
|
|
107305
107456
|
}
|
|
107306
107457
|
};
|
|
107307
107458
|
}
|
|
107308
|
-
var bunGitRunner, bunCmdRunner;
|
|
107459
|
+
var ghAuthEnv = () => scrubGithubAppTokenEnv(), bunGitRunner, bunCmdRunner;
|
|
107309
107460
|
var init_runners = __esm(() => {
|
|
107461
|
+
init_preflight();
|
|
107310
107462
|
bunGitRunner = {
|
|
107311
107463
|
run: async (args, cwd2) => {
|
|
107312
|
-
const proc = Bun.spawn({
|
|
107464
|
+
const proc = Bun.spawn({
|
|
107465
|
+
cmd: ["git", ...args],
|
|
107466
|
+
cwd: cwd2,
|
|
107467
|
+
env: ghAuthEnv(),
|
|
107468
|
+
stdout: "pipe",
|
|
107469
|
+
stderr: "pipe"
|
|
107470
|
+
});
|
|
107313
107471
|
const stdout = await new Response(proc.stdout).text();
|
|
107314
107472
|
const stderr = await new Response(proc.stderr).text();
|
|
107315
107473
|
const code = await proc.exited;
|
|
@@ -107327,7 +107485,7 @@ var init_runners = __esm(() => {
|
|
|
107327
107485
|
};
|
|
107328
107486
|
bunCmdRunner = {
|
|
107329
107487
|
run: async (cmd, cwd2) => {
|
|
107330
|
-
const proc = Bun.spawn({ cmd, cwd: cwd2, stdout: "pipe", stderr: "pipe" });
|
|
107488
|
+
const proc = Bun.spawn({ cmd, cwd: cwd2, env: ghAuthEnv(), stdout: "pipe", stderr: "pipe" });
|
|
107331
107489
|
const stdout = await new Response(proc.stdout).text();
|
|
107332
107490
|
const stderr = await new Response(proc.stderr).text();
|
|
107333
107491
|
const code = await proc.exited;
|
|
@@ -107392,7 +107550,7 @@ var init_indicators = __esm(() => {
|
|
|
107392
107550
|
|
|
107393
107551
|
// apps/agent/src/agent/wire/linear-resolvers.ts
|
|
107394
107552
|
function createLinearResolvers(input) {
|
|
107395
|
-
const { apiKey, team, assignee, anyAssignee, diag } = input;
|
|
107553
|
+
const { apiKey, team, assignee, anyAssignee, requireAllLabels, diag } = input;
|
|
107396
107554
|
const ticketNumbers = input.ticketNumbers ?? [];
|
|
107397
107555
|
const stateCache = new Map;
|
|
107398
107556
|
const labelCache = new Map;
|
|
@@ -107509,6 +107667,7 @@ function createLinearResolvers(input) {
|
|
|
107509
107667
|
team,
|
|
107510
107668
|
assignee,
|
|
107511
107669
|
anyAssignee,
|
|
107670
|
+
requireAllLabels,
|
|
107512
107671
|
include,
|
|
107513
107672
|
exclude: excl,
|
|
107514
107673
|
...ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
|
|
@@ -107531,7 +107690,18 @@ function createLinearResolvers(input) {
|
|
|
107531
107690
|
resolveLabelIdForTeam
|
|
107532
107691
|
};
|
|
107533
107692
|
}
|
|
107534
|
-
|
|
107693
|
+
function doneCandidateSpec(team, assignee, anyAssignee, requireAllLabels, include, ticketNumbers) {
|
|
107694
|
+
return {
|
|
107695
|
+
team,
|
|
107696
|
+
assignee,
|
|
107697
|
+
anyAssignee,
|
|
107698
|
+
requireAllLabels,
|
|
107699
|
+
include,
|
|
107700
|
+
exclude: [],
|
|
107701
|
+
...ticketNumbers && ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
|
|
107702
|
+
};
|
|
107703
|
+
}
|
|
107704
|
+
async function fetchDoneCandidatesWith(apiKey, team, assignee, anyAssignee, requireAllLabels, indicators, ticketNumbers) {
|
|
107535
107705
|
const getIndicators = [
|
|
107536
107706
|
indicators.getTodo,
|
|
107537
107707
|
indicators.getInProgress,
|
|
@@ -107550,13 +107720,7 @@ async function fetchDoneCandidatesWith(apiKey, team, _assignee, indicators, tick
|
|
|
107550
107720
|
const include = ind.filter ?? [];
|
|
107551
107721
|
if (include.length === 0)
|
|
107552
107722
|
return;
|
|
107553
|
-
const issues = await fetchOpenIssues(apiKey,
|
|
107554
|
-
team,
|
|
107555
|
-
anyAssignee: true,
|
|
107556
|
-
include,
|
|
107557
|
-
exclude: [],
|
|
107558
|
-
...ticketNumbers && ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
|
|
107559
|
-
});
|
|
107723
|
+
const issues = await fetchOpenIssues(apiKey, doneCandidateSpec(team, assignee, anyAssignee, requireAllLabels, include, ticketNumbers));
|
|
107560
107724
|
for (const issue2 of issues) {
|
|
107561
107725
|
if (!seen.has(issue2.id)) {
|
|
107562
107726
|
seen.add(issue2.id);
|
|
@@ -107593,6 +107757,7 @@ function createPrepareHelpers(input) {
|
|
|
107593
107757
|
maps,
|
|
107594
107758
|
scriptRunner
|
|
107595
107759
|
} = input;
|
|
107760
|
+
const worktreeProvider = input.worktreeProvider ?? defaultWorktreeProvider;
|
|
107596
107761
|
async function runScript(label, cmd, cwd2) {
|
|
107597
107762
|
diag("script", ` ${label}: ${cmd}`, "gray");
|
|
107598
107763
|
const code = await scriptRunner(cmd, cwd2);
|
|
@@ -107611,7 +107776,7 @@ function createPrepareHelpers(input) {
|
|
|
107611
107776
|
const baseBranch = baseBranchFromLabels(issue2.labels) ?? cfg.prBaseBranch;
|
|
107612
107777
|
let wt;
|
|
107613
107778
|
try {
|
|
107614
|
-
wt = await
|
|
107779
|
+
wt = await worktreeProvider.create({
|
|
107615
107780
|
projectRoot,
|
|
107616
107781
|
changeName: probeName,
|
|
107617
107782
|
baseBranch,
|
|
@@ -107628,7 +107793,7 @@ function createPrepareHelpers(input) {
|
|
|
107628
107793
|
scaffoldStatesDir = wtLayout.statesDir;
|
|
107629
107794
|
diag("worktree", ` ${issue2.identifier} worktree: ${wt.cwd} (${wt.branch})`, "gray");
|
|
107630
107795
|
try {
|
|
107631
|
-
await
|
|
107796
|
+
await worktreeProvider.seedMcpConfig({
|
|
107632
107797
|
projectRoot,
|
|
107633
107798
|
worktreeCwd: wt.cwd
|
|
107634
107799
|
});
|
|
@@ -107824,6 +107989,7 @@ PR: ${ciPrUrl}` : ""
|
|
|
107824
107989
|
}
|
|
107825
107990
|
return { prepare, prepareTaskForTrigger, runScript, reactivateState: reactivateState2 };
|
|
107826
107991
|
}
|
|
107992
|
+
var defaultWorktreeProvider;
|
|
107827
107993
|
var init_prepare = __esm(() => {
|
|
107828
107994
|
init_layout();
|
|
107829
107995
|
init_tasks_md();
|
|
@@ -107834,6 +108000,10 @@ var init_prepare = __esm(() => {
|
|
|
107834
108000
|
init_scaffold();
|
|
107835
108001
|
init_worktree();
|
|
107836
108002
|
init_task_bodies();
|
|
108003
|
+
defaultWorktreeProvider = {
|
|
108004
|
+
create: (args) => runCapability(git.createWorktree, args),
|
|
108005
|
+
seedMcpConfig: (args) => runCapability(git.seedWorktreeMcpConfig, args)
|
|
108006
|
+
};
|
|
107837
108007
|
});
|
|
107838
108008
|
|
|
107839
108009
|
// apps/agent/src/agent/pr-url/index.ts
|
|
@@ -107893,9 +108063,65 @@ var init_pr_url = __esm(() => {
|
|
|
107893
108063
|
init_task_bodies();
|
|
107894
108064
|
});
|
|
107895
108065
|
|
|
108066
|
+
// apps/agent/src/agent/ci.ts
|
|
108067
|
+
function parseChecks(stdout) {
|
|
108068
|
+
try {
|
|
108069
|
+
const parsed = JSON.parse(stdout || "[]");
|
|
108070
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
108071
|
+
} catch {
|
|
108072
|
+
return [];
|
|
108073
|
+
}
|
|
108074
|
+
}
|
|
108075
|
+
async function getPrChecksStatus(prRef, runner, cwd2, onTransientRetry, ignoreCiChecks = []) {
|
|
108076
|
+
let out;
|
|
108077
|
+
try {
|
|
108078
|
+
out = await runGhWithRetry(["gh", "pr", "checks", prRef, "--json", PR_CHECKS_FIELDS], runner, cwd2, onTransientRetry);
|
|
108079
|
+
} catch (err) {
|
|
108080
|
+
const e = err;
|
|
108081
|
+
const blob = `${e.message}
|
|
108082
|
+
${e.stderr ?? ""}
|
|
108083
|
+
${e.stdout ?? ""}`;
|
|
108084
|
+
if (NO_CHECKS_RE.test(blob))
|
|
108085
|
+
return { bucket: "pass", failedRunIds: [], failedCheckNames: [] };
|
|
108086
|
+
if (PARTIAL_ACCESS_RE.test(blob) && parseChecks(e.stdout).length > 0) {
|
|
108087
|
+
out = { stdout: e.stdout, stderr: e.stderr ?? "" };
|
|
108088
|
+
} else {
|
|
108089
|
+
throw err;
|
|
108090
|
+
}
|
|
108091
|
+
}
|
|
108092
|
+
const ignoredLower = ignoreCiChecks.map((n) => n.toLowerCase());
|
|
108093
|
+
const checks3 = parseChecks(out.stdout).filter((c) => !ignoredLower.includes(c.name.toLowerCase())).filter((c) => classifyGhBucket(c.bucket) !== "skip");
|
|
108094
|
+
if (checks3.some((c) => classifyGhBucket(c.bucket) === "pending")) {
|
|
108095
|
+
return { bucket: "pending", failedRunIds: [], failedCheckNames: [] };
|
|
108096
|
+
}
|
|
108097
|
+
const failed = checks3.filter((c) => classifyGhBucket(c.bucket) === "fail");
|
|
108098
|
+
if (failed.length === 0)
|
|
108099
|
+
return { bucket: "pass", failedRunIds: [], failedCheckNames: [] };
|
|
108100
|
+
const ids = new Set;
|
|
108101
|
+
for (const c of failed) {
|
|
108102
|
+
const m = c.link?.match(/\/actions\/runs\/(\d+)/);
|
|
108103
|
+
if (m)
|
|
108104
|
+
ids.add(m[1]);
|
|
108105
|
+
}
|
|
108106
|
+
return { bucket: "fail", failedRunIds: [...ids], failedCheckNames: failed.map((c) => c.name) };
|
|
108107
|
+
}
|
|
108108
|
+
var PR_CHECKS_FIELDS = "name,bucket,link,workflow,event";
|
|
108109
|
+
var init_ci = __esm(() => {
|
|
108110
|
+
init_ci_classify();
|
|
108111
|
+
});
|
|
108112
|
+
|
|
107896
108113
|
// apps/agent/src/agent/wire/pr-discovery.ts
|
|
107897
108114
|
function createPrDiscovery(input) {
|
|
107898
|
-
const {
|
|
108115
|
+
const {
|
|
108116
|
+
apiKey,
|
|
108117
|
+
projectRoot,
|
|
108118
|
+
cmdRunner,
|
|
108119
|
+
onLog,
|
|
108120
|
+
diag,
|
|
108121
|
+
prByChange,
|
|
108122
|
+
getPollContext,
|
|
108123
|
+
ignoreCiChecks
|
|
108124
|
+
} = input;
|
|
107899
108125
|
const prUnavailable = new Map;
|
|
107900
108126
|
const prUrlByIssue = createPrUrlCache(5 * 60 * 1000);
|
|
107901
108127
|
function isPrUnavailable(changeName) {
|
|
@@ -107967,9 +108193,11 @@ function createPrDiscovery(input) {
|
|
|
107967
108193
|
if (outcome.kind === "conflicting")
|
|
107968
108194
|
return { url: prUrl, status: "conflicted" };
|
|
107969
108195
|
try {
|
|
107970
|
-
const ci = await getPrChecksStatus(prUrl, cmdRunner, projectRoot);
|
|
108196
|
+
const ci = await getPrChecksStatus(prUrl, cmdRunner, projectRoot, undefined, ignoreCiChecks);
|
|
107971
108197
|
if (ci.bucket === "fail")
|
|
107972
108198
|
return { url: prUrl, status: "ci_failed" };
|
|
108199
|
+
if (ci.bucket === "pending")
|
|
108200
|
+
return { url: prUrl, status: "unknown" };
|
|
107973
108201
|
} catch (err) {
|
|
107974
108202
|
diag("ci", `! gh pr checks ${prUrl} failed (PR scan): ${err.message}`, "yellow");
|
|
107975
108203
|
}
|
|
@@ -108250,6 +108478,7 @@ function createMentionScanner(input) {
|
|
|
108250
108478
|
team,
|
|
108251
108479
|
assignee,
|
|
108252
108480
|
anyAssignee,
|
|
108481
|
+
requireAllLabels,
|
|
108253
108482
|
indicators,
|
|
108254
108483
|
projectRoot,
|
|
108255
108484
|
useWorktree,
|
|
@@ -108274,6 +108503,7 @@ function createMentionScanner(input) {
|
|
|
108274
108503
|
team,
|
|
108275
108504
|
assignee,
|
|
108276
108505
|
anyAssignee,
|
|
108506
|
+
...requireAllLabels && requireAllLabels.length > 0 ? { requireAllLabels } : {},
|
|
108277
108507
|
...ticketNumbers && ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {},
|
|
108278
108508
|
indicators: {
|
|
108279
108509
|
...indicators.getTodo !== undefined ? { getTodo: indicators.getTodo } : {},
|
|
@@ -108430,6 +108660,31 @@ var init_mention_scan = __esm(() => {
|
|
|
108430
108660
|
init_task_bodies();
|
|
108431
108661
|
});
|
|
108432
108662
|
|
|
108663
|
+
// packages/core/src/main-checkout-sentinel/index.ts
|
|
108664
|
+
function isEmptySentinel(s) {
|
|
108665
|
+
return s.head === "" && s.entries.length === 0;
|
|
108666
|
+
}
|
|
108667
|
+
async function snapshotCheckout(root, runner) {
|
|
108668
|
+
try {
|
|
108669
|
+
const head3 = await runner.run(["rev-parse", "HEAD"], root);
|
|
108670
|
+
const status = await runner.run(["status", "--porcelain"], root);
|
|
108671
|
+
const entries = status.stdout.split(`
|
|
108672
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0).sort();
|
|
108673
|
+
return { head: head3.stdout.trim(), entries };
|
|
108674
|
+
} catch {
|
|
108675
|
+
return { head: "", entries: [] };
|
|
108676
|
+
}
|
|
108677
|
+
}
|
|
108678
|
+
function detectCheckoutLeak(before2, after2) {
|
|
108679
|
+
if (isEmptySentinel(before2) || isEmptySentinel(after2)) {
|
|
108680
|
+
return { leaked: false, headMoved: false, newEntries: [] };
|
|
108681
|
+
}
|
|
108682
|
+
const beforeSet = new Set(before2.entries);
|
|
108683
|
+
const newEntries = after2.entries.filter((e) => !beforeSet.has(e));
|
|
108684
|
+
const headMoved = before2.head !== "" && after2.head !== "" && before2.head !== after2.head;
|
|
108685
|
+
return { leaked: newEntries.length > 0 || headMoved, headMoved, newEntries };
|
|
108686
|
+
}
|
|
108687
|
+
|
|
108433
108688
|
// apps/agent/src/agent/wire/spawn/default.ts
|
|
108434
108689
|
import { join as join30 } from "path";
|
|
108435
108690
|
function defaultSpawn(changeName, cmd, cwd2, logsDir, onWorkerOutput, note) {
|
|
@@ -108517,7 +108772,7 @@ function dispositionFromExitCode(code) {
|
|
|
108517
108772
|
return "done";
|
|
108518
108773
|
case NO_CHANGES_EXIT2:
|
|
108519
108774
|
return "no-changes";
|
|
108520
|
-
case
|
|
108775
|
+
case CI_FAILED_EXIT:
|
|
108521
108776
|
return "ci-failed";
|
|
108522
108777
|
case PR_FAILED_EXIT2:
|
|
108523
108778
|
return "pr-failed";
|
|
@@ -108525,7 +108780,7 @@ function dispositionFromExitCode(code) {
|
|
|
108525
108780
|
return "error";
|
|
108526
108781
|
}
|
|
108527
108782
|
}
|
|
108528
|
-
var
|
|
108783
|
+
var CI_FAILED_EXIT = 70, PR_FAILED_EXIT2 = 71, NO_CHANGES_EXIT2 = 72;
|
|
108529
108784
|
|
|
108530
108785
|
// packages/retro/src/paths.ts
|
|
108531
108786
|
import { homedir as homedir7 } from "os";
|
|
@@ -108752,17 +109007,13 @@ function buildPostTaskInput(input) {
|
|
|
108752
109007
|
exitCode: input.exitCode,
|
|
108753
109008
|
useWorktree: input.useWorktree,
|
|
108754
109009
|
wantPr: input.wantPr,
|
|
108755
|
-
wantFixCi: input.wantFixCi,
|
|
108756
109010
|
wantAutoMerge: input.wantAutoMerge,
|
|
108757
109011
|
wantValidateOnly: input.wantValidateOnly,
|
|
108758
109012
|
cfg: {
|
|
108759
109013
|
teardownScript: cfg.teardownScript ?? null,
|
|
108760
109014
|
prBaseBranch: cfg.prBaseBranch,
|
|
108761
109015
|
autoMergeStrategy: cfg.autoMergeStrategy,
|
|
108762
|
-
maxCiFixAttempts: cfg.maxCiFixAttempts,
|
|
108763
|
-
ciPollIntervalSeconds: cfg.ciPollIntervalSeconds,
|
|
108764
109016
|
cleanupWorktreeOnSuccess: cfg.cleanupWorktreeOnSuccess,
|
|
108765
|
-
ignoreCiChecks: cfg.ignoreCiChecks,
|
|
108766
109017
|
stackPrsOnDependencies: args.stackPrs || cfg.stackPrsOnDependencies,
|
|
108767
109018
|
neverTouch: cfg.boundaries.never_touch,
|
|
108768
109019
|
metaOnlyFiles: cfg.boundaries.meta_only_files,
|
|
@@ -108858,6 +109109,8 @@ function createSpawnWorker(input) {
|
|
|
108858
109109
|
const f2 = Bun.file(missionTasksPath);
|
|
108859
109110
|
return await f2.exists() ? await f2.text() : "";
|
|
108860
109111
|
})();
|
|
109112
|
+
const guardOn = useWorktree && cwd2 !== projectRoot;
|
|
109113
|
+
const beforeSnapshotPromise = guardOn ? snapshotCheckout(projectRoot, gitRunner) : Promise.resolve(null);
|
|
108861
109114
|
let logFilePath;
|
|
108862
109115
|
let handle;
|
|
108863
109116
|
if (injected) {
|
|
@@ -108878,10 +109131,29 @@ function createSpawnWorker(input) {
|
|
|
108878
109131
|
onWorkerPhase?.(changeName, "working");
|
|
108879
109132
|
const tracedCmd = onWorkerCmd ? traceCmdRunner(cmdRunner, (cmd) => onWorkerCmd(changeName, cmd, "start"), (cmd, ms, ok) => onWorkerCmd(changeName, cmd, "end", ms, ok)) : cmdRunner;
|
|
108880
109133
|
const wantPrBase = args.createPr || cfg.createPrOnSuccess;
|
|
108881
|
-
const wantFixCi = args.fixCi || cfg.fixCiOnFailure;
|
|
108882
109134
|
const issueForChange = issueByChange.get(changeName);
|
|
108883
109135
|
const wantAutoMerge = issueForChange ? issueMatchesGetIndicator(issueForChange, indicators.getAutoMerge) : false;
|
|
108884
109136
|
const wrapped = handle.exited.then(async (code) => {
|
|
109137
|
+
const before2 = await beforeSnapshotPromise;
|
|
109138
|
+
if (before2) {
|
|
109139
|
+
const after2 = await snapshotCheckout(projectRoot, gitRunner);
|
|
109140
|
+
const leak = detectCheckoutLeak(before2, after2);
|
|
109141
|
+
if (leak.leaked) {
|
|
109142
|
+
const detail = [
|
|
109143
|
+
leak.headMoved ? "HEAD moved" : null,
|
|
109144
|
+
leak.newEntries.length > 0 ? leak.newEntries.join(", ") : null
|
|
109145
|
+
].filter(Boolean).join("; ");
|
|
109146
|
+
const msg = `main checkout leak in ${projectRoot}: ${detail}`;
|
|
109147
|
+
onLog(msg, "red");
|
|
109148
|
+
diag("sentinel", msg, "red");
|
|
109149
|
+
emitCapture(bus, "agent_main_checkout_leak", {
|
|
109150
|
+
change_name: changeName,
|
|
109151
|
+
head_moved: leak.headMoved,
|
|
109152
|
+
leaked_paths: leak.newEntries,
|
|
109153
|
+
...issueForChange ? { issue_identifier: issueForChange.identifier } : {}
|
|
109154
|
+
});
|
|
109155
|
+
}
|
|
109156
|
+
}
|
|
108885
109157
|
const workerLayout = projectLayout(cwd2);
|
|
108886
109158
|
const validateSpecPath = join33(workerLayout.changeDir(changeName), "specs", "validate.md");
|
|
108887
109159
|
const hasValidateSpec = await Bun.file(validateSpecPath).exists();
|
|
@@ -108943,7 +109215,6 @@ function createSpawnWorker(input) {
|
|
|
108943
109215
|
exitCode: code,
|
|
108944
109216
|
useWorktree,
|
|
108945
109217
|
wantPr,
|
|
108946
|
-
wantFixCi,
|
|
108947
109218
|
wantAutoMerge,
|
|
108948
109219
|
wantValidateOnly,
|
|
108949
109220
|
...trigger ? { trigger } : {},
|
|
@@ -108976,16 +109247,6 @@ function createSpawnWorker(input) {
|
|
|
108976
109247
|
...onWorkerPhase && {
|
|
108977
109248
|
onPhase: (phase2, detail) => onWorkerPhase(changeName, phase2, detail)
|
|
108978
109249
|
},
|
|
108979
|
-
checkPrConflict: async (prUrl) => {
|
|
108980
|
-
const outcome = await waitForMergeability({
|
|
108981
|
-
bailOnError: true,
|
|
108982
|
-
probe: async () => {
|
|
108983
|
-
const res = await tracedCmd.run(["gh", "pr", "view", prUrl, "--json", "state,mergeable,mergeStateStatus"], cwd2);
|
|
108984
|
-
return JSON.parse(res.stdout || "{}");
|
|
108985
|
-
}
|
|
108986
|
-
});
|
|
108987
|
-
return outcome.kind === "conflicting";
|
|
108988
|
-
},
|
|
108989
109250
|
resolveDependencyBaseBranch: (issue2) => resolveDependencyBaseBranchImpl(issue2, tracedCmd, cwd2, { apiKey, onLog })
|
|
108990
109251
|
});
|
|
108991
109252
|
releaseWorkerMaps({ cwdByChange, statesDirByChange, branchByChange, issueByChange }, changeName);
|
|
@@ -109003,7 +109264,6 @@ var init_worker = __esm(() => {
|
|
|
109003
109264
|
init_default2();
|
|
109004
109265
|
init_runners();
|
|
109005
109266
|
init_pr_helpers();
|
|
109006
|
-
init_wait_for_mergeability();
|
|
109007
109267
|
init_agent_run_state();
|
|
109008
109268
|
init_retro();
|
|
109009
109269
|
init_engine();
|
|
@@ -261431,6 +261691,7 @@ function renderListItem(doc2, item, indent, marker) {
|
|
|
261431
261691
|
const tokens = item.tokens ?? [];
|
|
261432
261692
|
let inlineRun = [];
|
|
261433
261693
|
let placedInline = false;
|
|
261694
|
+
let renderedBlock = false;
|
|
261434
261695
|
const placeInline = () => {
|
|
261435
261696
|
if (inlineRun.length === 0)
|
|
261436
261697
|
return;
|
|
@@ -261454,10 +261715,13 @@ function renderListItem(doc2, item, indent, marker) {
|
|
|
261454
261715
|
continue;
|
|
261455
261716
|
}
|
|
261456
261717
|
placeInline();
|
|
261718
|
+
if (!placedInline && !renderedBlock)
|
|
261719
|
+
doc2.y = startY;
|
|
261457
261720
|
renderBlock(doc2, tok, indent + LIST_INDENT);
|
|
261721
|
+
renderedBlock = true;
|
|
261458
261722
|
}
|
|
261459
261723
|
placeInline();
|
|
261460
|
-
if (!placedInline) {
|
|
261724
|
+
if (!placedInline && !renderedBlock) {
|
|
261461
261725
|
doc2.y = startY;
|
|
261462
261726
|
doc2.text(" ", bodyX, startY, { width: bodyWidth });
|
|
261463
261727
|
}
|
|
@@ -262432,8 +262696,7 @@ function buildAgentCoordinator(input) {
|
|
|
262432
262696
|
const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
|
|
262433
262697
|
const indicators = mergeIndicators(cfg.linear.indicators, args.indicators);
|
|
262434
262698
|
const team = args.linearTeam || cfg.linear.team;
|
|
262435
|
-
const
|
|
262436
|
-
const { assignee, anyAssignee } = parseLinearFilter(effectiveFilter);
|
|
262699
|
+
const { assignee, anyAssignee, requireAllLabels } = resolveLinearFilter(applyAssigneeOverride(cfg.linear.filter, args.linearAssignee));
|
|
262437
262700
|
const ticketNumbers = resolveTicketNumbers(args.ticketTokens, team);
|
|
262438
262701
|
const excludeFromTodo = unionMarkers(indicators.setDone, indicators.setError);
|
|
262439
262702
|
const gitRunner = input.runners?.git ?? bunGitRunner;
|
|
@@ -262475,6 +262738,7 @@ function buildAgentCoordinator(input) {
|
|
|
262475
262738
|
team,
|
|
262476
262739
|
assignee,
|
|
262477
262740
|
anyAssignee,
|
|
262741
|
+
requireAllLabels,
|
|
262478
262742
|
diag,
|
|
262479
262743
|
...ticketNumbers.length > 0 ? { ticketNumbers } : {}
|
|
262480
262744
|
});
|
|
@@ -262491,7 +262755,8 @@ function buildAgentCoordinator(input) {
|
|
|
262491
262755
|
onLog,
|
|
262492
262756
|
diag,
|
|
262493
262757
|
prByChange,
|
|
262494
|
-
getPollContext: () => pollContext
|
|
262758
|
+
getPollContext: () => pollContext,
|
|
262759
|
+
ignoreCiChecks: cfg.prRecovery.ignoreChecks
|
|
262495
262760
|
});
|
|
262496
262761
|
const prep = createPrepareHelpers({
|
|
262497
262762
|
args,
|
|
@@ -262504,7 +262769,8 @@ function buildAgentCoordinator(input) {
|
|
|
262504
262769
|
gitRunner,
|
|
262505
262770
|
diag,
|
|
262506
262771
|
maps: { cwdByChange, statesDirByChange, issueByChange, branchByChange, prByChange },
|
|
262507
|
-
scriptRunner
|
|
262772
|
+
scriptRunner,
|
|
262773
|
+
...input.runners?.worktree ? { worktreeProvider: input.runners.worktree } : {}
|
|
262508
262774
|
});
|
|
262509
262775
|
const fetchMentions = createMentionScanner({
|
|
262510
262776
|
apiKey,
|
|
@@ -262513,6 +262779,7 @@ function buildAgentCoordinator(input) {
|
|
|
262513
262779
|
team,
|
|
262514
262780
|
assignee,
|
|
262515
262781
|
anyAssignee,
|
|
262782
|
+
requireAllLabels,
|
|
262516
262783
|
indicators,
|
|
262517
262784
|
projectRoot,
|
|
262518
262785
|
useWorktree,
|
|
@@ -262606,10 +262873,10 @@ function buildAgentCoordinator(input) {
|
|
|
262606
262873
|
now: () => new Date
|
|
262607
262874
|
};
|
|
262608
262875
|
}
|
|
262609
|
-
const
|
|
262610
|
-
const prTracker =
|
|
262876
|
+
const prRecoveryEnabled = args.prRecoveryEnabled === undefined ? cfg.prRecovery.enabled : args.prRecoveryEnabled;
|
|
262877
|
+
const prTracker = prRecoveryEnabled ? new PrTracker({
|
|
262611
262878
|
projectRoot,
|
|
262612
|
-
maxRecoveryAttempts: cfg.
|
|
262879
|
+
maxRecoveryAttempts: cfg.prRecovery.maxRecoverySessions
|
|
262613
262880
|
}) : null;
|
|
262614
262881
|
const commentSync = createCommentSyncHooks({
|
|
262615
262882
|
apiKey,
|
|
@@ -262627,7 +262894,7 @@ function buildAgentCoordinator(input) {
|
|
|
262627
262894
|
fetchTodo: () => resolvers.fetchByGet(indicators.getTodo, excludeFromTodo),
|
|
262628
262895
|
fetchInProgress: () => resolvers.fetchByGet(indicators.getInProgress, unionMarkers(indicators.setError)),
|
|
262629
262896
|
fetchMentions,
|
|
262630
|
-
fetchDoneCandidates: () => fetchDoneCandidatesWith(apiKey, team, assignee, indicators, ticketNumbers.length > 0 ? ticketNumbers : undefined),
|
|
262897
|
+
fetchDoneCandidates: () => fetchDoneCandidatesWith(apiKey, team, assignee, anyAssignee, requireAllLabels, indicators, ticketNumbers.length > 0 ? ticketNumbers : undefined),
|
|
262631
262898
|
prepare: prep.prepare,
|
|
262632
262899
|
prepareTaskForTrigger: prep.prepareTaskForTrigger,
|
|
262633
262900
|
spawnWorker,
|
|
@@ -262639,6 +262906,7 @@ function buildAgentCoordinator(input) {
|
|
|
262639
262906
|
return c.map((x2) => ({ body: x2.body }));
|
|
262640
262907
|
},
|
|
262641
262908
|
checkPrStatus: prDiscovery.checkPrStatus,
|
|
262909
|
+
hasPrForChange: (changeName) => prByChange.has(changeName),
|
|
262642
262910
|
isChangeArchivedForIssue: (issue2) => isChangeArchivedForIssue(issue2, cwdByChange, projectRoot),
|
|
262643
262911
|
onLog,
|
|
262644
262912
|
...onFileLog ? { onFileLog } : {},
|
|
@@ -262675,7 +262943,13 @@ function buildAgentCoordinator(input) {
|
|
|
262675
262943
|
postComments: cfg.linear.postComments,
|
|
262676
262944
|
commentEveryIterations: cfg.linear.updateEveryIterations,
|
|
262677
262945
|
...args.maxTickets > 0 ? { maxTickets: args.maxTickets } : {},
|
|
262678
|
-
|
|
262946
|
+
createsPrs: args.createPr || cfg.createPrOnSuccess,
|
|
262947
|
+
...prTracker ? { prTracker } : {},
|
|
262948
|
+
prRecovery: {
|
|
262949
|
+
enabled: prRecoveryEnabled,
|
|
262950
|
+
fixCi: cfg.prRecovery.fixCi,
|
|
262951
|
+
fixConflicts: cfg.prRecovery.fixConflicts
|
|
262952
|
+
}
|
|
262679
262953
|
});
|
|
262680
262954
|
coordRef.current = coord;
|
|
262681
262955
|
const filterDesc = describeIndicators(indicators, team, assignee, anyAssignee);
|
|
@@ -262836,23 +263110,23 @@ function SteeringField({
|
|
|
262836
263110
|
initialFocused = false,
|
|
262837
263111
|
onStateChange
|
|
262838
263112
|
}) {
|
|
262839
|
-
const [state, dispatch] =
|
|
263113
|
+
const [state, dispatch] = import_react62.useReducer(reducer2, { initialBuffer, initialCursor, initialFocused }, (init2) => ({
|
|
262840
263114
|
buffer: init2.initialBuffer,
|
|
262841
263115
|
cursor: init2.initialCursor ?? init2.initialBuffer.length,
|
|
262842
263116
|
focused: init2.initialFocused,
|
|
262843
263117
|
status: "idle"
|
|
262844
263118
|
}));
|
|
262845
263119
|
const { buffer, cursor: cursor4, focused, status } = state;
|
|
262846
|
-
const stateRef =
|
|
263120
|
+
const stateRef = import_react62.useRef(state);
|
|
262847
263121
|
stateRef.current = state;
|
|
262848
|
-
const hintTimerRef =
|
|
262849
|
-
|
|
263122
|
+
const hintTimerRef = import_react62.useRef(null);
|
|
263123
|
+
import_react62.useEffect(() => {
|
|
262850
263124
|
onFocusChange?.(focused);
|
|
262851
263125
|
}, [focused, onFocusChange]);
|
|
262852
|
-
|
|
263126
|
+
import_react62.useEffect(() => {
|
|
262853
263127
|
onStateChange?.({ buffer, cursor: cursor4, focused });
|
|
262854
263128
|
}, [buffer, cursor4, focused, onStateChange]);
|
|
262855
|
-
|
|
263129
|
+
import_react62.useEffect(() => {
|
|
262856
263130
|
return () => {
|
|
262857
263131
|
if (hintTimerRef.current)
|
|
262858
263132
|
clearTimeout(hintTimerRef.current);
|
|
@@ -262961,10 +263235,10 @@ function SteeringField({
|
|
|
262961
263235
|
]
|
|
262962
263236
|
}, undefined, true, undefined, this);
|
|
262963
263237
|
}
|
|
262964
|
-
var
|
|
263238
|
+
var import_react62, jsx_dev_runtime10, STATUS_HINT_MS = 2000, PLACEHOLDER_IDLE = "CTRL+S to steer", PLACEHOLDER_SENT = "steered \u2192 next iteration", PLACEHOLDER_FAILED = "send failed";
|
|
262965
263239
|
var init_SteeringField = __esm(async () => {
|
|
262966
263240
|
await init_build2();
|
|
262967
|
-
|
|
263241
|
+
import_react62 = __toESM(require_react(), 1);
|
|
262968
263242
|
jsx_dev_runtime10 = __toESM(require_jsx_dev_runtime(), 1);
|
|
262969
263243
|
});
|
|
262970
263244
|
|
|
@@ -263333,21 +263607,35 @@ function AgentMode({
|
|
|
263333
263607
|
const { exit } = use_app_default();
|
|
263334
263608
|
const { isRawModeSupported } = use_stdin_default();
|
|
263335
263609
|
const { columns, rows, resizeKey } = useTerminalSize();
|
|
263336
|
-
const [logs, setLogs] =
|
|
263337
|
-
const [preflightError, setPreflightError] =
|
|
263338
|
-
const [,
|
|
263339
|
-
const
|
|
263340
|
-
const
|
|
263341
|
-
|
|
263342
|
-
|
|
263343
|
-
|
|
263344
|
-
|
|
263345
|
-
|
|
263346
|
-
|
|
263347
|
-
|
|
263348
|
-
|
|
263349
|
-
|
|
263350
|
-
|
|
263610
|
+
const [logs, setLogs] = import_react63.useState([]);
|
|
263611
|
+
const [preflightError, setPreflightError] = import_react63.useState(null);
|
|
263612
|
+
const [fatalExit, setFatalExit] = import_react63.useState(null);
|
|
263613
|
+
const heldRef = import_react63.useRef(false);
|
|
263614
|
+
const { awaitingClose } = useHoldToClose({
|
|
263615
|
+
finished: fatalExit !== null,
|
|
263616
|
+
hold: true,
|
|
263617
|
+
onClose: () => {
|
|
263618
|
+
const code = heldRef.current ? 0 : fatalExit ?? 0;
|
|
263619
|
+
setTimeout(() => process.exit(code), 200);
|
|
263620
|
+
}
|
|
263621
|
+
});
|
|
263622
|
+
import_react63.useEffect(() => {
|
|
263623
|
+
if (awaitingClose)
|
|
263624
|
+
heldRef.current = true;
|
|
263625
|
+
}, [awaitingClose]);
|
|
263626
|
+
const [, setTick] = import_react63.useState(0);
|
|
263627
|
+
const [clock, setClock] = import_react63.useState(0);
|
|
263628
|
+
const [focusedIdx, setFocusedIdx] = import_react63.useState(0);
|
|
263629
|
+
const [showPendingTasks, setShowPendingTasks] = import_react63.useState(false);
|
|
263630
|
+
const [showAllSubtasks, setShowAllSubtasks] = import_react63.useState(false);
|
|
263631
|
+
const [gaveUpCount, setGaveUpCount] = import_react63.useState(0);
|
|
263632
|
+
const coordRef = import_react63.useRef(null);
|
|
263633
|
+
const workerMetaRef = import_react63.useRef(new Map);
|
|
263634
|
+
const gatedTicketsRef = import_react63.useRef(new Map);
|
|
263635
|
+
const nextPollAtRef = import_react63.useRef(0);
|
|
263636
|
+
const cfgRef = import_react63.useRef(null);
|
|
263637
|
+
const [effective, setEffective] = import_react63.useState(null);
|
|
263638
|
+
const [pollStatus, setPollStatus] = import_react63.useState({
|
|
263351
263639
|
state: "idle",
|
|
263352
263640
|
lastFound: null,
|
|
263353
263641
|
lastAdded: null,
|
|
@@ -263360,14 +263648,14 @@ function AgentMode({
|
|
|
263360
263648
|
setLogs((prev) => [...prev, { id: nextId(), text, color }]);
|
|
263361
263649
|
logCoord(text, workerLogFile);
|
|
263362
263650
|
}
|
|
263363
|
-
const fileSinkRef =
|
|
263651
|
+
const fileSinkRef = import_react63.useRef(null);
|
|
263364
263652
|
if (fileSinkRef.current === null) {
|
|
263365
263653
|
fileSinkRef.current = createJsonLogFileSink(args.jsonLogFile);
|
|
263366
263654
|
}
|
|
263367
263655
|
const fileEmit = (event) => {
|
|
263368
263656
|
fileSinkRef.current?.emit(event);
|
|
263369
263657
|
};
|
|
263370
|
-
|
|
263658
|
+
import_react63.useEffect(() => {
|
|
263371
263659
|
let pollTimer = null;
|
|
263372
263660
|
let cancelled = false;
|
|
263373
263661
|
async function init2() {
|
|
@@ -263380,15 +263668,14 @@ function AgentMode({
|
|
|
263380
263668
|
if (!apiKey) {
|
|
263381
263669
|
throw new Error("LINEAR_API_KEY not set \u2014 cannot poll Linear");
|
|
263382
263670
|
}
|
|
263383
|
-
const pf = await runPreflight2(
|
|
263671
|
+
const pf = await runPreflight2({
|
|
263672
|
+
requireRepoWrite: args.createPr || cfg2.createPrOnSuccess,
|
|
263673
|
+
repoCwd: projectRoot
|
|
263674
|
+
});
|
|
263384
263675
|
if (!pf.ok) {
|
|
263385
263676
|
fileEmit({ type: "error", code: "auth_failure", tool: pf.tool, text: pf.message });
|
|
263386
263677
|
setPreflightError({ tool: pf.tool, message: pf.message });
|
|
263387
|
-
|
|
263388
|
-
setTimeout(() => {
|
|
263389
|
-
exit();
|
|
263390
|
-
setTimeout(() => process.exit(2), 200);
|
|
263391
|
-
}, 100);
|
|
263678
|
+
setFatalExit(2);
|
|
263392
263679
|
return;
|
|
263393
263680
|
}
|
|
263394
263681
|
const { coord: coord2, filterDesc, concurrency, pollInterval, runBaselineGate: runBaselineGate2, getGaveUpTotal } = buildCoordinator({
|
|
@@ -263564,10 +263851,7 @@ function AgentMode({
|
|
|
263564
263851
|
const message = err instanceof Error ? err.message : String(err);
|
|
263565
263852
|
fileEmit({ type: "error", code: "init_failure", text: message });
|
|
263566
263853
|
appendLog(`! ${message}`, "red");
|
|
263567
|
-
|
|
263568
|
-
exit();
|
|
263569
|
-
setTimeout(() => process.exit(1), 200);
|
|
263570
|
-
}, 100);
|
|
263854
|
+
setFatalExit(1);
|
|
263571
263855
|
});
|
|
263572
263856
|
let shuttingDown = false;
|
|
263573
263857
|
const onSig = () => {
|
|
@@ -263610,8 +263894,8 @@ function AgentMode({
|
|
|
263610
263894
|
process.off("SIGTERM", onSig);
|
|
263611
263895
|
};
|
|
263612
263896
|
}, []);
|
|
263613
|
-
const lastPauseRef =
|
|
263614
|
-
|
|
263897
|
+
const lastPauseRef = import_react63.useRef(null);
|
|
263898
|
+
import_react63.useEffect(() => {
|
|
263615
263899
|
let cancelled = false;
|
|
263616
263900
|
const interval = setInterval(() => {
|
|
263617
263901
|
if (cancelled)
|
|
@@ -263675,10 +263959,10 @@ function AgentMode({
|
|
|
263675
263959
|
const termWidth = columns - 2;
|
|
263676
263960
|
const termHeight = rows;
|
|
263677
263961
|
const safeFocusedIdx = activeCount > 0 ? Math.min(focusedIdx, activeCount - 1) : 0;
|
|
263678
|
-
const steeringFocusedRef =
|
|
263679
|
-
const steeringBufferRef =
|
|
263680
|
-
const steeringCursorRef =
|
|
263681
|
-
const steeringFocusedInitRef =
|
|
263962
|
+
const steeringFocusedRef = import_react63.useRef(false);
|
|
263963
|
+
const steeringBufferRef = import_react63.useRef("");
|
|
263964
|
+
const steeringCursorRef = import_react63.useRef(0);
|
|
263965
|
+
const steeringFocusedInitRef = import_react63.useRef(false);
|
|
263682
263966
|
use_input_default((input, key) => {
|
|
263683
263967
|
if (steeringFocusedRef.current)
|
|
263684
263968
|
return;
|
|
@@ -263730,7 +264014,15 @@ function AgentMode({
|
|
|
263730
264014
|
/* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
|
|
263731
264015
|
color: "red",
|
|
263732
264016
|
children: preflightError.message
|
|
263733
|
-
}, undefined, false, undefined, this)
|
|
264017
|
+
}, undefined, false, undefined, this),
|
|
264018
|
+
awaitingClose && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
|
|
264019
|
+
color: "cyan",
|
|
264020
|
+
children: [
|
|
264021
|
+
`
|
|
264022
|
+
`,
|
|
264023
|
+
"Press Enter to close\u2026"
|
|
264024
|
+
]
|
|
264025
|
+
}, undefined, true, undefined, this)
|
|
263734
264026
|
]
|
|
263735
264027
|
}, undefined, true, undefined, this);
|
|
263736
264028
|
}
|
|
@@ -263847,10 +264139,13 @@ function AgentMode({
|
|
|
263847
264139
|
color: "green",
|
|
263848
264140
|
children: " \u25CF PR"
|
|
263849
264141
|
}, undefined, false, undefined, this),
|
|
263850
|
-
cfg.
|
|
264142
|
+
cfg.prRecovery.enabled && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
|
|
263851
264143
|
color: "green",
|
|
263852
|
-
children:
|
|
263853
|
-
|
|
264144
|
+
children: [
|
|
264145
|
+
" \u25CF recover",
|
|
264146
|
+
cfg.prRecovery.fixCi ? "+CI" : ""
|
|
264147
|
+
]
|
|
264148
|
+
}, undefined, true, undefined, this),
|
|
263854
264149
|
cfg.useWorktree && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
|
|
263855
264150
|
color: "green",
|
|
263856
264151
|
children: " \u25CF worktree"
|
|
@@ -264750,11 +265045,15 @@ function AgentMode({
|
|
|
264750
265045
|
}, w2.changeName, true, undefined, this);
|
|
264751
265046
|
})
|
|
264752
265047
|
]
|
|
264753
|
-
}, undefined, true, undefined, this)
|
|
265048
|
+
}, undefined, true, undefined, this),
|
|
265049
|
+
awaitingClose && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
|
|
265050
|
+
color: "cyan",
|
|
265051
|
+
children: "Stopped \u2014 press Enter to close\u2026"
|
|
265052
|
+
}, undefined, false, undefined, this)
|
|
264754
265053
|
]
|
|
264755
265054
|
}, resizeKey, true, undefined, this);
|
|
264756
265055
|
}
|
|
264757
|
-
var
|
|
265056
|
+
var import_react63, jsx_dev_runtime11, lineCounter = 0, TAIL_BUFFER_SIZE = 30, CMD_DISPLAY_MAX = 80, MAX_PENDING_DISPLAY = 15, SPINNER_FRAMES, HYPERLINKS_SUPPORTED, SESSION_START;
|
|
264758
265057
|
var init_AgentMode = __esm(async () => {
|
|
264759
265058
|
init_cli2();
|
|
264760
265059
|
init_config();
|
|
@@ -264770,9 +265069,10 @@ var init_AgentMode = __esm(async () => {
|
|
|
264770
265069
|
init_worker_state_poll();
|
|
264771
265070
|
await __promiseAll([
|
|
264772
265071
|
init_build2(),
|
|
265072
|
+
init_useHoldToClose(),
|
|
264773
265073
|
init_SteeringField()
|
|
264774
265074
|
]);
|
|
264775
|
-
|
|
265075
|
+
import_react63 = __toESM(require_react(), 1);
|
|
264776
265076
|
jsx_dev_runtime11 = __toESM(require_jsx_dev_runtime(), 1);
|
|
264777
265077
|
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
264778
265078
|
HYPERLINKS_SUPPORTED = !process.env["TMUX"];
|
|
@@ -264834,7 +265134,7 @@ function createSession(name, command, env3) {
|
|
|
264834
265134
|
envArgs.push("-e", `${key}=${value}`);
|
|
264835
265135
|
}
|
|
264836
265136
|
const quoted = command.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
|
|
264837
|
-
const shellCmd = `${quoted}; printf '\\n[ralphy
|
|
265137
|
+
const shellCmd = `${quoted}; code=$?; ` + `if [ "$code" -ne 0 ]; then ` + `printf '\\n[ralphy crashed (exit %s) \u2014 press Enter to close]\\n' "$code"; read _; fi`;
|
|
264838
265138
|
const result2 = Bun.spawnSync({
|
|
264839
265139
|
cmd: ["tmux", "new-session", "-d", "-s", name, ...envArgs, "sh", "-c", shellCmd],
|
|
264840
265140
|
stderr: "pipe"
|
|
@@ -265075,23 +265375,20 @@ function buildBuckets(indicators) {
|
|
|
265075
265375
|
{ label: "auto-merge", indicator: indicators.getAutoMerge, exclude: [] }
|
|
265076
265376
|
];
|
|
265077
265377
|
}
|
|
265078
|
-
async function fetchBucketIssues(apiKey, bucket, team, assignee, anyAssignee, ticketNumbers) {
|
|
265378
|
+
async function fetchBucketIssues(apiKey, bucket, team, assignee, anyAssignee, requireAllLabels, ticketNumbers) {
|
|
265079
265379
|
if (!bucket.indicator || bucket.indicator.filter.length === 0)
|
|
265080
265380
|
return [];
|
|
265081
265381
|
const spec = {
|
|
265082
265382
|
team,
|
|
265083
265383
|
assignee,
|
|
265084
265384
|
anyAssignee,
|
|
265385
|
+
requireAllLabels,
|
|
265085
265386
|
include: bucket.indicator.filter,
|
|
265086
265387
|
exclude: bucket.exclude,
|
|
265087
265388
|
...ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
|
|
265088
265389
|
};
|
|
265089
265390
|
return fetchOpenIssues(apiKey, spec);
|
|
265090
265391
|
}
|
|
265091
|
-
function resolveLinearFilter(filterOverride, assigneeOverride, configFilter) {
|
|
265092
|
-
const effective = filterOverride || (assigneeOverride ? `assignee = ${assigneeOverride}` : "") || configFilter;
|
|
265093
|
-
return parseLinearFilter(effective);
|
|
265094
|
-
}
|
|
265095
265392
|
function formatReviewCell(prUrl, count) {
|
|
265096
265393
|
if (!prUrl)
|
|
265097
265394
|
return "-";
|
|
@@ -265138,13 +265435,13 @@ function backlogRankByIssueId(issues) {
|
|
|
265138
265435
|
ordered.forEach((o, i) => rankById.set(o.id, i));
|
|
265139
265436
|
return rankById;
|
|
265140
265437
|
}
|
|
265141
|
-
async function fetchAndPrintLinear(apiKey, buckets, team, assignee, anyAssignee, cwd2, runner, ignoreCiChecks = [], checks3 = false, review = false, ticketNumbers = []) {
|
|
265438
|
+
async function fetchAndPrintLinear(apiKey, buckets, team, assignee, anyAssignee, requireAllLabels, cwd2, runner, ignoreCiChecks = [], checks3 = false, review = false, ticketNumbers = []) {
|
|
265142
265439
|
const bucketResults = await Promise.all(buckets.map(async (bucket) => {
|
|
265143
265440
|
if (!bucket.indicator || bucket.indicator.filter.length === 0) {
|
|
265144
265441
|
return { bucket, issues: [], error: null };
|
|
265145
265442
|
}
|
|
265146
265443
|
try {
|
|
265147
|
-
const issues = await fetchBucketIssues(apiKey, bucket, team, assignee, anyAssignee, ticketNumbers);
|
|
265444
|
+
const issues = await fetchBucketIssues(apiKey, bucket, team, assignee, anyAssignee, requireAllLabels, ticketNumbers);
|
|
265148
265445
|
return { bucket, issues, error: null };
|
|
265149
265446
|
} catch (err) {
|
|
265150
265447
|
return {
|
|
@@ -265270,7 +265567,6 @@ async function runList(input) {
|
|
|
265270
265567
|
identifier: name,
|
|
265271
265568
|
projectRoot,
|
|
265272
265569
|
linearTeamOverride: input.linearTeamOverride,
|
|
265273
|
-
linearFilterOverride: input.linearFilterOverride,
|
|
265274
265570
|
linearAssigneeOverride: input.linearAssigneeOverride
|
|
265275
265571
|
});
|
|
265276
265572
|
return;
|
|
@@ -265281,7 +265577,7 @@ async function runList(input) {
|
|
|
265281
265577
|
const apiKey = process.env["LINEAR_API_KEY"];
|
|
265282
265578
|
const indicators = cfg.linear.indicators;
|
|
265283
265579
|
const team = input.linearTeamOverride || cfg.linear.team;
|
|
265284
|
-
const { assignee, anyAssignee } = resolveLinearFilter(
|
|
265580
|
+
const { assignee, anyAssignee, requireAllLabels } = resolveLinearFilter(applyAssigneeOverride(cfg.linear.filter, input.linearAssigneeOverride));
|
|
265285
265581
|
const buckets = buildBuckets(indicators);
|
|
265286
265582
|
const anyConfigured = buckets.some((b2) => b2.indicator && b2.indicator.filter.length > 0);
|
|
265287
265583
|
if (!anyConfigured) {
|
|
@@ -265321,7 +265617,7 @@ team: ${team}
|
|
|
265321
265617
|
if (ticketNumbers.length > 0)
|
|
265322
265618
|
process.stdout.write(`ticket: ${ticketNumbers.join(", ")}
|
|
265323
265619
|
`);
|
|
265324
|
-
await fetchAndPrintLinear(apiKey, buckets, team, assignee, anyAssignee, projectRoot, localCmdRunner, cfg.
|
|
265620
|
+
await fetchAndPrintLinear(apiKey, buckets, team, assignee, anyAssignee, requireAllLabels, projectRoot, localCmdRunner, cfg.prRecovery.ignoreChecks, input.checks, input.review, ticketNumbers);
|
|
265325
265621
|
}
|
|
265326
265622
|
function normalizeIdentifier(input) {
|
|
265327
265623
|
let parsed;
|
|
@@ -265404,7 +265700,7 @@ async function runListDebug(input) {
|
|
|
265404
265700
|
const cfg = await loadRalphyConfig(projectRoot, getArgs().workflowFile);
|
|
265405
265701
|
const indicators = cfg.linear.indicators;
|
|
265406
265702
|
const team = input.linearTeamOverride || cfg.linear.team;
|
|
265407
|
-
const { assignee, anyAssignee } = resolveLinearFilter(
|
|
265703
|
+
const { assignee, anyAssignee, requireAllLabels } = resolveLinearFilter(applyAssigneeOverride(cfg.linear.filter, input.linearAssigneeOverride));
|
|
265408
265704
|
const assigneeLabel = anyAssignee ? "any" : assignee ?? "*";
|
|
265409
265705
|
const normalized = normalizeIdentifier(identifier);
|
|
265410
265706
|
if (!normalized) {
|
|
@@ -265448,6 +265744,13 @@ Per-bucket diagnostics:
|
|
|
265448
265744
|
if (!assigneeMatches(issue2, assignee, anyAssignee)) {
|
|
265449
265745
|
reasons.push(`assignee mismatch: issue=${issue2.assignee ? issue2.assignee.email ?? issue2.assignee.id : "unassigned"}, config=${assigneeLabel}`);
|
|
265450
265746
|
}
|
|
265747
|
+
if (requireAllLabels && requireAllLabels.length > 0) {
|
|
265748
|
+
const issueLabels = new Set(issue2.labels.nodes.map((l3) => l3.name));
|
|
265749
|
+
const missing = requireAllLabels.filter((label) => !issueLabels.has(label));
|
|
265750
|
+
if (missing.length > 0) {
|
|
265751
|
+
reasons.push(`missing required linear.filter label(s): ${missing.join(", ")}`);
|
|
265752
|
+
}
|
|
265753
|
+
}
|
|
265451
265754
|
const includeMatches = bucket.indicator.filter.some((m2) => markerMatches(issue2, m2));
|
|
265452
265755
|
if (!includeMatches) {
|
|
265453
265756
|
const want = bucket.indicator.filter.map((m2) => `${m2.type}:${m2.value}`).join(" OR ");
|
|
@@ -265555,7 +265858,10 @@ async function runAgentJson({
|
|
|
265555
265858
|
process.exitCode = 1;
|
|
265556
265859
|
return;
|
|
265557
265860
|
}
|
|
265558
|
-
const pf = await runPreflight2(
|
|
265861
|
+
const pf = await runPreflight2({
|
|
265862
|
+
requireRepoWrite: args.createPr || cfg.createPrOnSuccess,
|
|
265863
|
+
repoCwd: projectRoot
|
|
265864
|
+
});
|
|
265559
265865
|
if (!pf.ok) {
|
|
265560
265866
|
emit3({ type: "error", code: "auth_failure", tool: pf.tool, text: pf.message });
|
|
265561
265867
|
process.exitCode = 2;
|
|
@@ -265776,7 +266082,6 @@ async function main3(argv) {
|
|
|
265776
266082
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
265777
266083
|
await runList2({
|
|
265778
266084
|
linearTeamOverride: args.linearTeam,
|
|
265779
|
-
linearFilterOverride: args.linearFilter,
|
|
265780
266085
|
linearAssigneeOverride: args.linearAssignee,
|
|
265781
266086
|
debug: args.debug,
|
|
265782
266087
|
name: args.name,
|
|
@@ -265852,12 +266157,12 @@ async function main3(argv) {
|
|
|
265852
266157
|
return 0;
|
|
265853
266158
|
}
|
|
265854
266159
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
265855
|
-
const { waitUntilExit } = render_default(
|
|
266160
|
+
const { waitUntilExit } = render_default(import_react64.createElement(AgentMode, { args, projectRoot, statesDir, tasksDir }));
|
|
265856
266161
|
await waitUntilExit();
|
|
265857
266162
|
});
|
|
265858
266163
|
return typeof process.exitCode === "number" ? process.exitCode : 0;
|
|
265859
266164
|
}
|
|
265860
|
-
var
|
|
266165
|
+
var import_react64;
|
|
265861
266166
|
var init_src8 = __esm(async () => {
|
|
265862
266167
|
init_context();
|
|
265863
266168
|
init_layout();
|
|
@@ -265868,7 +266173,7 @@ var init_src8 = __esm(async () => {
|
|
|
265868
266173
|
init_build2(),
|
|
265869
266174
|
init_AgentMode()
|
|
265870
266175
|
]);
|
|
265871
|
-
|
|
266176
|
+
import_react64 = __toESM(require_react(), 1);
|
|
265872
266177
|
});
|
|
265873
266178
|
|
|
265874
266179
|
// apps/shell/src/index.ts
|
|
@@ -265957,11 +266262,21 @@ ${HELP}
|
|
|
265957
266262
|
capture("command_run", { subcommand });
|
|
265958
266263
|
bus.emit({ type: "command_run", subcommand });
|
|
265959
266264
|
try {
|
|
265960
|
-
if (
|
|
266265
|
+
if (CONFIG_SUBCOMMANDS.has(subcommand)) {
|
|
265961
266266
|
try {
|
|
265962
|
-
const { maybeRunSetupWizard: maybeRunSetupWizard2 } = await init_src4().then(() => exports_src);
|
|
266267
|
+
const { maybeRunSetupWizard: maybeRunSetupWizard2, maybeUpgradeWorkflow: maybeUpgradeWorkflow2 } = await init_src4().then(() => exports_src);
|
|
265963
266268
|
const { projectRoot, workflowFile } = parseWorkflowPathArgs(argv.slice(1));
|
|
265964
|
-
|
|
266269
|
+
if (shouldOfferSetup(subcommand, argv.slice(1))) {
|
|
266270
|
+
await maybeRunSetupWizard2(projectRoot, workflowFile);
|
|
266271
|
+
}
|
|
266272
|
+
if (await maybeUpgradeWorkflow2(projectRoot, workflowFile)) {
|
|
266273
|
+
process.stdout.write(`
|
|
266274
|
+
WORKFLOW.md updated \u2014 re-run your command.
|
|
266275
|
+
`);
|
|
266276
|
+
capture("command_exit", { subcommand, exit_code: 0 });
|
|
266277
|
+
bus.emit({ type: "command_exit", subcommand, exit_code: 0 });
|
|
266278
|
+
return 0;
|
|
266279
|
+
}
|
|
265965
266280
|
} catch (setupErr) {
|
|
265966
266281
|
captureError("setup_wizard_error", setupErr, { subcommand });
|
|
265967
266282
|
}
|