@neriros/ralphy 3.10.14 → 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 +1216 -911
- 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 {
|
|
@@ -103356,6 +103308,28 @@ async function fetchAttachmentsForIssues(apiKey, issueIds) {
|
|
|
103356
103308
|
}
|
|
103357
103309
|
return out;
|
|
103358
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
|
+
}
|
|
103359
103333
|
async function findIssueAttachmentByTitle(apiKey, issueId, title) {
|
|
103360
103334
|
const query = `query IssueAttachmentByTitle($id: String!) {
|
|
103361
103335
|
issue(id: $id) {
|
|
@@ -103576,6 +103550,326 @@ var init_linear_client = __esm(() => {
|
|
|
103576
103550
|
};
|
|
103577
103551
|
});
|
|
103578
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
|
+
|
|
103579
103873
|
// apps/agent/src/agent/linear.ts
|
|
103580
103874
|
var init_linear = __esm(() => {
|
|
103581
103875
|
init_linear_client();
|
|
@@ -103770,131 +104064,14 @@ function classifyGhBucket(bucket) {
|
|
|
103770
104064
|
return "pending";
|
|
103771
104065
|
return "pass";
|
|
103772
104066
|
}
|
|
103773
|
-
var TRANSIENT_GH_RE, NO_CHECKS_RE, GH_RETRY_DELAYS;
|
|
104067
|
+
var TRANSIENT_GH_RE, NO_CHECKS_RE, PARTIAL_ACCESS_RE, GH_RETRY_DELAYS;
|
|
103774
104068
|
var init_ci_classify = __esm(() => {
|
|
103775
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;
|
|
103776
104070
|
NO_CHECKS_RE = /no checks reported/i;
|
|
104071
|
+
PARTIAL_ACCESS_RE = /Resource not accessible by personal access token/i;
|
|
103777
104072
|
GH_RETRY_DELAYS = [5000, 15000, 45000];
|
|
103778
104073
|
});
|
|
103779
104074
|
|
|
103780
|
-
// apps/agent/src/agent/ci.ts
|
|
103781
|
-
async function getPrChecksStatus(prRef, runner, cwd2, onTransientRetry, ignoreCiChecks = []) {
|
|
103782
|
-
let out;
|
|
103783
|
-
try {
|
|
103784
|
-
out = await runGhWithRetry(["gh", "pr", "checks", prRef, "--json", PR_CHECKS_FIELDS], runner, cwd2, onTransientRetry);
|
|
103785
|
-
} catch (err) {
|
|
103786
|
-
const e = err;
|
|
103787
|
-
const blob = `${e.message}
|
|
103788
|
-
${e.stderr ?? ""}
|
|
103789
|
-
${e.stdout ?? ""}`;
|
|
103790
|
-
if (NO_CHECKS_RE.test(blob))
|
|
103791
|
-
return { bucket: "pass", failedRunIds: [], failedCheckNames: [] };
|
|
103792
|
-
throw err;
|
|
103793
|
-
}
|
|
103794
|
-
const ignoredLower = ignoreCiChecks.map((n) => n.toLowerCase());
|
|
103795
|
-
const checks3 = JSON.parse(out.stdout || "[]").filter((c) => !ignoredLower.includes(c.name.toLowerCase())).filter((c) => classifyGhBucket(c.bucket) !== "skip");
|
|
103796
|
-
if (checks3.some((c) => classifyGhBucket(c.bucket) === "pending")) {
|
|
103797
|
-
return { bucket: "pending", failedRunIds: [], failedCheckNames: [] };
|
|
103798
|
-
}
|
|
103799
|
-
const failed = checks3.filter((c) => classifyGhBucket(c.bucket) === "fail");
|
|
103800
|
-
if (failed.length === 0)
|
|
103801
|
-
return { bucket: "pass", failedRunIds: [], failedCheckNames: [] };
|
|
103802
|
-
const ids = new Set;
|
|
103803
|
-
for (const c of failed) {
|
|
103804
|
-
const m = c.link?.match(/\/actions\/runs\/(\d+)/);
|
|
103805
|
-
if (m)
|
|
103806
|
-
ids.add(m[1]);
|
|
103807
|
-
}
|
|
103808
|
-
return { bucket: "fail", failedRunIds: [...ids], failedCheckNames: failed.map((c) => c.name) };
|
|
103809
|
-
}
|
|
103810
|
-
async function fetchFailedRunLogs(runIds, runner, cwd2, maxCharsPerRun = 4000) {
|
|
103811
|
-
const chunks = [];
|
|
103812
|
-
for (const id of runIds) {
|
|
103813
|
-
try {
|
|
103814
|
-
const r = await runner.run(["gh", "run", "view", id, "--log-failed"], cwd2);
|
|
103815
|
-
const text = r.stdout.trim();
|
|
103816
|
-
const truncated = text.length > maxCharsPerRun ? text.slice(0, maxCharsPerRun) + `
|
|
103817
|
-
\u2026[truncated ${text.length - maxCharsPerRun} chars]` : text;
|
|
103818
|
-
chunks.push(`--- run ${id} ---
|
|
103819
|
-
${truncated}`);
|
|
103820
|
-
} catch (err) {
|
|
103821
|
-
chunks.push(`--- run ${id} ---
|
|
103822
|
-
(failed to fetch logs: ${err.message})`);
|
|
103823
|
-
}
|
|
103824
|
-
}
|
|
103825
|
-
return chunks.join(`
|
|
103826
|
-
|
|
103827
|
-
`);
|
|
103828
|
-
}
|
|
103829
|
-
async function safeSha(getHeadSha) {
|
|
103830
|
-
try {
|
|
103831
|
-
const sha = (await getHeadSha()).trim();
|
|
103832
|
-
return sha || null;
|
|
103833
|
-
} catch {
|
|
103834
|
-
return null;
|
|
103835
|
-
}
|
|
103836
|
-
}
|
|
103837
|
-
async function fixCiUntilGreen(deps, opts) {
|
|
103838
|
-
for (let attempt2 = 1;attempt2 <= opts.maxAttempts; attempt2++) {
|
|
103839
|
-
let pollN = 0;
|
|
103840
|
-
while (true) {
|
|
103841
|
-
if (deps.cancelled?.())
|
|
103842
|
-
return { success: false, attempts: attempt2 - 1, reason: "cancelled" };
|
|
103843
|
-
pollN += 1;
|
|
103844
|
-
deps.onPhase?.("ci-poll", `attempt ${attempt2}/${opts.maxAttempts} \xB7 poll ${pollN}`);
|
|
103845
|
-
let s;
|
|
103846
|
-
try {
|
|
103847
|
-
s = await deps.getStatus();
|
|
103848
|
-
} catch (err) {
|
|
103849
|
-
deps.log(`! gh pr checks failed permanently: ${err.message} \u2014 giving up CI watch`, "red");
|
|
103850
|
-
return { success: false, attempts: attempt2 - 1, reason: "gh-failed" };
|
|
103851
|
-
}
|
|
103852
|
-
if (s.bucket === "pass") {
|
|
103853
|
-
deps.log(`\u2713 CI green for PR (after ${attempt2 - 1} fix attempts)`, "green");
|
|
103854
|
-
return { success: true, attempts: attempt2 - 1 };
|
|
103855
|
-
}
|
|
103856
|
-
if (s.bucket === "fail") {
|
|
103857
|
-
deps.log(`\u2717 CI failing (attempt ${attempt2}/${opts.maxAttempts}) \u2014 fetching logs and re-running task`, "yellow");
|
|
103858
|
-
deps.onPhase?.("ci-fix", `attempt ${attempt2}/${opts.maxAttempts} \xB7 fetching logs`);
|
|
103859
|
-
const logs = await deps.getFailedLogs(s.failedRunIds);
|
|
103860
|
-
deps.onPhase?.("ci-fix", `attempt ${attempt2}/${opts.maxAttempts} \xB7 re-running worker`);
|
|
103861
|
-
const steering = `CI is failing on this PR. Investigate and fix:
|
|
103862
|
-
|
|
103863
|
-
\`\`\`
|
|
103864
|
-
${logs}
|
|
103865
|
-
\`\`\``;
|
|
103866
|
-
const shaBefore = deps.getHeadSha ? await safeSha(deps.getHeadSha) : null;
|
|
103867
|
-
const code = await deps.runTaskWithSteering(steering);
|
|
103868
|
-
if (code !== 0) {
|
|
103869
|
-
deps.log(`! task loop exited code ${code} during CI fix attempt ${attempt2}`, "red");
|
|
103870
|
-
}
|
|
103871
|
-
if (shaBefore !== null) {
|
|
103872
|
-
const shaAfter = await safeSha(deps.getHeadSha);
|
|
103873
|
-
if (shaAfter !== null && shaAfter === shaBefore) {
|
|
103874
|
-
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");
|
|
103875
|
-
return { success: false, attempts: attempt2, reason: "no-progress" };
|
|
103876
|
-
}
|
|
103877
|
-
}
|
|
103878
|
-
try {
|
|
103879
|
-
deps.onPhase?.("ci-fix", `attempt ${attempt2}/${opts.maxAttempts} \xB7 pushing fix`);
|
|
103880
|
-
await deps.pushBranch();
|
|
103881
|
-
} catch (err) {
|
|
103882
|
-
deps.log(`! push failed during CI fix: ${err.message}`, "red");
|
|
103883
|
-
return { success: false, attempts: attempt2, reason: "push-failed" };
|
|
103884
|
-
}
|
|
103885
|
-
break;
|
|
103886
|
-
}
|
|
103887
|
-
deps.onPhase?.("ci-poll", `attempt ${attempt2}/${opts.maxAttempts} \xB7 pending, waiting`);
|
|
103888
|
-
await deps.sleep(opts.pollIntervalSeconds * 1000);
|
|
103889
|
-
}
|
|
103890
|
-
}
|
|
103891
|
-
return { success: false, attempts: opts.maxAttempts, reason: "max-attempts" };
|
|
103892
|
-
}
|
|
103893
|
-
var PR_CHECKS_FIELDS = "name,bucket,link,workflow,event";
|
|
103894
|
-
var init_ci = __esm(() => {
|
|
103895
|
-
init_ci_classify();
|
|
103896
|
-
});
|
|
103897
|
-
|
|
103898
104075
|
// apps/agent/src/pr-status.ts
|
|
103899
104076
|
function bucketChecks(rollup, prState, ignoreCiChecks = []) {
|
|
103900
104077
|
if (rollup === null || rollup === undefined) {
|
|
@@ -104498,33 +104675,9 @@ async function runWorkerWithFixTask(ctx, heading, body) {
|
|
|
104498
104675
|
}
|
|
104499
104676
|
return code;
|
|
104500
104677
|
}
|
|
104501
|
-
async function pushBranchSafely(ctx) {
|
|
104502
|
-
try {
|
|
104503
|
-
ctx.emit("pushing", "after conflict resolution");
|
|
104504
|
-
await ctx.cmd.run(["git", "push", "origin", ctx.branch], ctx.cwd);
|
|
104505
|
-
return true;
|
|
104506
|
-
} catch (pushErr) {
|
|
104507
|
-
const pe = pushErr;
|
|
104508
|
-
const blob = `${pe.message}
|
|
104509
|
-
${pe.stderr ?? ""}`;
|
|
104510
|
-
if (!/non-fast-forward|Updates were rejected/i.test(blob)) {
|
|
104511
|
-
ctx.log(`! push after conflict fix failed: ${pe.message}`, "red");
|
|
104512
|
-
return false;
|
|
104513
|
-
}
|
|
104514
|
-
try {
|
|
104515
|
-
await ctx.cmd.run(["git", "fetch", "origin", ctx.branch], ctx.cwd);
|
|
104516
|
-
await ctx.cmd.run(["git", "merge", "--no-edit", `origin/${ctx.branch}`], ctx.cwd);
|
|
104517
|
-
await ctx.cmd.run(["git", "push", "origin", ctx.branch], ctx.cwd);
|
|
104518
|
-
return true;
|
|
104519
|
-
} catch (retryErr) {
|
|
104520
|
-
ctx.log(`! push after merging origin/${ctx.branch} failed: ${retryErr.message}`, "red");
|
|
104521
|
-
return false;
|
|
104522
|
-
}
|
|
104523
|
-
}
|
|
104524
|
-
}
|
|
104525
104678
|
async function createPrWithRetry(ctx, issue2) {
|
|
104526
104679
|
const base2 = ctx.base;
|
|
104527
|
-
const maxAttempts =
|
|
104680
|
+
const maxAttempts = MAX_PR_CREATE_ATTEMPTS;
|
|
104528
104681
|
let hookFixAttempt = 0;
|
|
104529
104682
|
let nonFfRebaseAttempted = false;
|
|
104530
104683
|
let pr = null;
|
|
@@ -104637,86 +104790,6 @@ ${reBlob.trim()}`);
|
|
|
104637
104790
|
}
|
|
104638
104791
|
}
|
|
104639
104792
|
}
|
|
104640
|
-
async function fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict) {
|
|
104641
|
-
const wantConflictLoop = !!checkPrConflict;
|
|
104642
|
-
const maxOuterAttempts = ctx.cfg.maxCiFixAttempts;
|
|
104643
|
-
let outerAttempt = 0;
|
|
104644
|
-
let ciConfirmedGreen = false;
|
|
104645
|
-
while (outerAttempt < maxOuterAttempts) {
|
|
104646
|
-
if (wantConflictLoop) {
|
|
104647
|
-
ctx.emit("conflict-check");
|
|
104648
|
-
let conflicting = false;
|
|
104649
|
-
try {
|
|
104650
|
-
conflicting = await checkPrConflict(prUrl);
|
|
104651
|
-
} catch (err) {
|
|
104652
|
-
ctx.log(`! conflict check failed: ${err.message}`, "yellow");
|
|
104653
|
-
}
|
|
104654
|
-
if (!conflicting && ciConfirmedGreen)
|
|
104655
|
-
return 0;
|
|
104656
|
-
if (conflicting) {
|
|
104657
|
-
outerAttempt++;
|
|
104658
|
-
ciConfirmedGreen = false;
|
|
104659
|
-
ctx.emit("conflict-fix-inner", `attempt ${outerAttempt}/${maxOuterAttempts}`);
|
|
104660
|
-
ctx.log(` merge conflicts on PR (attempt ${outerAttempt}/${maxOuterAttempts}) \u2014 spawning resolution task`, "yellow");
|
|
104661
|
-
const conflictCode = await runWorkerWithFixTask(ctx, "Resolve PR merge conflicts", [
|
|
104662
|
-
`The PR ${prUrl} has merge conflicts with \`${ctx.base}\`.`,
|
|
104663
|
-
"",
|
|
104664
|
-
"Steps:",
|
|
104665
|
-
`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.`,
|
|
104666
|
-
"2. Resolve conflicts in the files git lists.",
|
|
104667
|
-
"3. Stage and commit the resolution as a new merge commit."
|
|
104668
|
-
].join(`
|
|
104669
|
-
`));
|
|
104670
|
-
if (conflictCode !== 0) {
|
|
104671
|
-
ctx.log(`! conflict resolution worker exited code ${conflictCode} \u2014 giving up`, "red");
|
|
104672
|
-
return PR_FAILED_EXIT;
|
|
104673
|
-
}
|
|
104674
|
-
const pushed = await pushBranchSafely(ctx);
|
|
104675
|
-
if (!pushed)
|
|
104676
|
-
return PR_FAILED_EXIT;
|
|
104677
|
-
continue;
|
|
104678
|
-
}
|
|
104679
|
-
}
|
|
104680
|
-
if (!wantFixCi)
|
|
104681
|
-
break;
|
|
104682
|
-
if (!ciConfirmedGreen) {
|
|
104683
|
-
ctx.log(` watching CI for ${prUrl} (max ${ctx.cfg.maxCiFixAttempts} fix attempts)`, "gray");
|
|
104684
|
-
ctx.emit("ci-poll", "starting");
|
|
104685
|
-
const result2 = await fixCiUntilGreen({
|
|
104686
|
-
onPhase: (p, d) => ctx.emit(p, d),
|
|
104687
|
-
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),
|
|
104688
|
-
getFailedLogs: (ids) => fetchFailedRunLogs(ids, ctx.cmd, ctx.cwd),
|
|
104689
|
-
runTaskWithSteering: (steering) => runWorkerWithFixTask(ctx, "Fix failing CI checks", steering),
|
|
104690
|
-
pushBranch: async () => {
|
|
104691
|
-
await ctx.cmd.run(["git", "push", "origin", ctx.branch], ctx.cwd);
|
|
104692
|
-
},
|
|
104693
|
-
getHeadSha: async () => {
|
|
104694
|
-
const r = await ctx.cmd.run(["git", "rev-parse", "HEAD"], ctx.cwd);
|
|
104695
|
-
return r.stdout.trim();
|
|
104696
|
-
},
|
|
104697
|
-
log: ctx.log,
|
|
104698
|
-
sleep: (ms) => new Promise((r) => setTimeout(r, ms))
|
|
104699
|
-
}, {
|
|
104700
|
-
maxAttempts: ctx.cfg.maxCiFixAttempts,
|
|
104701
|
-
pollIntervalSeconds: ctx.cfg.ciPollIntervalSeconds
|
|
104702
|
-
});
|
|
104703
|
-
if (!result2.success) {
|
|
104704
|
-
ctx.log(`! CI fix loop gave up after ${result2.attempts} attempts (${result2.reason ?? "unknown"}) \u2014 withholding done-status until CI passes`, "red");
|
|
104705
|
-
return CI_FAILED_EXIT;
|
|
104706
|
-
}
|
|
104707
|
-
ciConfirmedGreen = true;
|
|
104708
|
-
}
|
|
104709
|
-
if (wantConflictLoop) {
|
|
104710
|
-
continue;
|
|
104711
|
-
}
|
|
104712
|
-
return 0;
|
|
104713
|
-
}
|
|
104714
|
-
if (outerAttempt >= maxOuterAttempts) {
|
|
104715
|
-
ctx.log(`! outer fix loop exhausted ${maxOuterAttempts} attempts \u2014 giving up`, "red");
|
|
104716
|
-
return CI_FAILED_EXIT;
|
|
104717
|
-
}
|
|
104718
|
-
return 0;
|
|
104719
|
-
}
|
|
104720
104793
|
async function findNeverTouchViolations(cmd, cwd2, base2, neverTouch) {
|
|
104721
104794
|
if (neverTouch.length === 0)
|
|
104722
104795
|
return [];
|
|
@@ -104737,27 +104810,8 @@ async function findNeverTouchViolations(cmd, cwd2, base2, neverTouch) {
|
|
|
104737
104810
|
return findBoundaryViolations(files, neverTouch);
|
|
104738
104811
|
}
|
|
104739
104812
|
async function runPrPhase(input, deps) {
|
|
104740
|
-
const {
|
|
104741
|
-
|
|
104742
|
-
cwd: cwd2,
|
|
104743
|
-
branch,
|
|
104744
|
-
changeDir,
|
|
104745
|
-
stateFilePath,
|
|
104746
|
-
issue: issue2,
|
|
104747
|
-
wantFixCi,
|
|
104748
|
-
wantAutoMerge,
|
|
104749
|
-
cfg
|
|
104750
|
-
} = input;
|
|
104751
|
-
const {
|
|
104752
|
-
cmd,
|
|
104753
|
-
log: log3,
|
|
104754
|
-
emit: emit3,
|
|
104755
|
-
respawnWorker,
|
|
104756
|
-
registerPr,
|
|
104757
|
-
onPrReady,
|
|
104758
|
-
checkPrConflict,
|
|
104759
|
-
resolveDependencyBaseBranch
|
|
104760
|
-
} = 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;
|
|
104761
104815
|
if (!branch || !issue2) {
|
|
104762
104816
|
log3(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
|
|
104763
104817
|
return PR_FAILED_EXIT;
|
|
@@ -104824,7 +104878,7 @@ ${indented}${suffix}`, "yellow");
|
|
|
104824
104878
|
}
|
|
104825
104879
|
return PR_FAILED_EXIT;
|
|
104826
104880
|
}
|
|
104827
|
-
const maxOuterAttempts =
|
|
104881
|
+
const maxOuterAttempts = MAX_PR_CREATE_ATTEMPTS;
|
|
104828
104882
|
let onlyMetaAttempts = 0;
|
|
104829
104883
|
let pr = null;
|
|
104830
104884
|
const finalizeNoOpAsDone = cfg.finalizeNoOpAsDone !== false;
|
|
@@ -104899,36 +104953,8 @@ ${indented}${suffix}`, "yellow");
|
|
|
104899
104953
|
}
|
|
104900
104954
|
log3(` ${pr.created ? "opened" : "found existing"} PR: ${prUrl}`, "green");
|
|
104901
104955
|
registerPr?.(changeName, prUrl);
|
|
104902
|
-
let
|
|
104903
|
-
|
|
104904
|
-
if (!prReadyNeeded && wantAutoMerge) {
|
|
104905
|
-
const fallbackEnabled = cfg.manualMergeWhenAutoMergeDisabled !== false;
|
|
104906
|
-
const repoAllowsAutoMerge = await detectRepoAutoMergeAllowed(prUrl, cmd, cwd2, log3);
|
|
104907
|
-
if (repoAllowsAutoMerge === false && fallbackEnabled) {
|
|
104908
|
-
log3(` repo has auto-merge disabled \u2014 will poll ${prUrl} and merge via gh pr merge once checks pass`, "yellow");
|
|
104909
|
-
manualMergePending = true;
|
|
104910
|
-
} else {
|
|
104911
|
-
try {
|
|
104912
|
-
await cmd.run(["gh", "pr", "merge", prUrl, "--auto", `--${cfg.autoMergeStrategy}`], cwd2);
|
|
104913
|
-
log3(` enabled auto-merge (${cfg.autoMergeStrategy}) on ${prUrl}`, "green");
|
|
104914
|
-
emit3("auto-merge-enabled", cfg.autoMergeStrategy);
|
|
104915
|
-
} catch (err) {
|
|
104916
|
-
const e = err;
|
|
104917
|
-
const detail = e.stderr?.trim() || e.message;
|
|
104918
|
-
log3(`! failed to enable auto-merge on ${prUrl}: ${detail}`, "yellow");
|
|
104919
|
-
if (fallbackEnabled && /auto[- ]merge/i.test(detail)) {
|
|
104920
|
-
log3(` falling back to manual merge after CI passes for ${prUrl}`, "yellow");
|
|
104921
|
-
manualMergePending = true;
|
|
104922
|
-
}
|
|
104923
|
-
}
|
|
104924
|
-
}
|
|
104925
|
-
} else if (prReadyNeeded && wantAutoMerge) {
|
|
104926
|
-
manualMergePending = true;
|
|
104927
|
-
}
|
|
104928
|
-
const ciResult = await fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict);
|
|
104929
|
-
if (ciResult !== 0)
|
|
104930
|
-
return ciResult;
|
|
104931
|
-
if (prReadyNeeded) {
|
|
104956
|
+
let readyOk = true;
|
|
104957
|
+
if (cfg.prDraft === true) {
|
|
104932
104958
|
emit3("pr-ready");
|
|
104933
104959
|
try {
|
|
104934
104960
|
await cmd.run(["gh", "pr", "ready", prUrl], cwd2);
|
|
@@ -104936,22 +104962,25 @@ ${indented}${suffix}`, "yellow");
|
|
|
104936
104962
|
} catch (err) {
|
|
104937
104963
|
const e = err;
|
|
104938
104964
|
log3(`! gh pr ready failed for ${prUrl}: ${e.stderr?.trim() || e.message}`, "yellow");
|
|
104939
|
-
|
|
104965
|
+
readyOk = false;
|
|
104940
104966
|
}
|
|
104941
104967
|
}
|
|
104942
|
-
if (
|
|
104943
|
-
|
|
104944
|
-
|
|
104945
|
-
log3(`
|
|
104946
|
-
|
|
104947
|
-
|
|
104948
|
-
|
|
104949
|
-
|
|
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
|
+
}
|
|
104950
104981
|
}
|
|
104951
104982
|
}
|
|
104952
|
-
|
|
104953
|
-
await onPrReady?.(prUrl);
|
|
104954
|
-
}
|
|
104983
|
+
await onPrReady?.(prUrl);
|
|
104955
104984
|
return 0;
|
|
104956
104985
|
}
|
|
104957
104986
|
async function runWorktreeCleanupPhase(input, deps) {
|
|
@@ -105058,7 +105087,6 @@ async function runPostTask(input, deps) {
|
|
|
105058
105087
|
exitCode,
|
|
105059
105088
|
useWorktree,
|
|
105060
105089
|
wantPr,
|
|
105061
|
-
wantFixCi,
|
|
105062
105090
|
wantAutoMerge,
|
|
105063
105091
|
wantValidateOnly,
|
|
105064
105092
|
cfg,
|
|
@@ -105148,7 +105176,6 @@ async function runPostTask(input, deps) {
|
|
|
105148
105176
|
changeDir,
|
|
105149
105177
|
stateFilePath,
|
|
105150
105178
|
issue: issue2,
|
|
105151
|
-
wantFixCi,
|
|
105152
105179
|
wantAutoMerge,
|
|
105153
105180
|
cfg
|
|
105154
105181
|
}, {
|
|
@@ -105158,7 +105185,6 @@ async function runPostTask(input, deps) {
|
|
|
105158
105185
|
respawnWorker,
|
|
105159
105186
|
...deps.registerPr !== undefined ? { registerPr: deps.registerPr } : {},
|
|
105160
105187
|
...deps.onPrReady !== undefined ? { onPrReady: deps.onPrReady } : {},
|
|
105161
|
-
...deps.checkPrConflict !== undefined ? { checkPrConflict: deps.checkPrConflict } : {},
|
|
105162
105188
|
...deps.resolveDependencyBaseBranch !== undefined ? { resolveDependencyBaseBranch: deps.resolveDependencyBaseBranch } : {}
|
|
105163
105189
|
});
|
|
105164
105190
|
}
|
|
@@ -105179,7 +105205,7 @@ async function runPostTask(input, deps) {
|
|
|
105179
105205
|
await runTeardownPhase({ cwd: cwd2, teardownScript: cfg.teardownScript }, { runScript, log: log3, emit: emit3 });
|
|
105180
105206
|
return effectiveCode;
|
|
105181
105207
|
}
|
|
105182
|
-
var
|
|
105208
|
+
var PR_FAILED_EXIT = 71, MAX_PR_CREATE_ATTEMPTS = 5, NO_CHANGES_EXIT = 72, repoAutoMergeCache, defaultRunCommand = async (cmd, cwd2) => {
|
|
105183
105209
|
const proc = Bun.spawnSync({
|
|
105184
105210
|
cmd: ["sh", "-c", cmd],
|
|
105185
105211
|
cwd: cwd2,
|
|
@@ -105198,7 +105224,6 @@ var init_post_task = __esm(() => {
|
|
|
105198
105224
|
init_git2();
|
|
105199
105225
|
init_linear();
|
|
105200
105226
|
init_pr();
|
|
105201
|
-
init_ci();
|
|
105202
105227
|
init_pr_status();
|
|
105203
105228
|
init_wait_for_mergeability();
|
|
105204
105229
|
init_worktree();
|
|
@@ -105603,10 +105628,12 @@ class AgentCoordinator {
|
|
|
105603
105628
|
continue;
|
|
105604
105629
|
if (!this.dependenciesResolved(issue2))
|
|
105605
105630
|
continue;
|
|
105606
|
-
if (await this.maybePromoteFinishedConflicted(issue2))
|
|
105607
|
-
continue;
|
|
105608
105631
|
const changeDir = this.deps.getChangeDir?.(issue2) ?? undefined;
|
|
105609
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;
|
|
105610
105637
|
actor.send({ type: "RESUME_DETECTED" });
|
|
105611
105638
|
if (changeDir) {
|
|
105612
105639
|
await this.flowStore.persistActor(issue2.id, changeDir).catch(() => {});
|
|
@@ -105751,6 +105778,12 @@ class AgentCoordinator {
|
|
|
105751
105778
|
return false;
|
|
105752
105779
|
if (pr.status !== "conflicted" && pr.status !== "ci_failed")
|
|
105753
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;
|
|
105754
105787
|
const stateLabel = pr.status === "conflicted" ? "conflicting with main" : "failing CI";
|
|
105755
105788
|
if (this.conflictPromoted.has(issue2.id))
|
|
105756
105789
|
return true;
|
|
@@ -105888,6 +105921,8 @@ class AgentCoordinator {
|
|
|
105888
105921
|
}
|
|
105889
105922
|
async scanPrMergeStates() {
|
|
105890
105923
|
const counts = emptyPrStatus();
|
|
105924
|
+
if (!this.opts.prRecovery?.enabled)
|
|
105925
|
+
return counts;
|
|
105891
105926
|
let candidates = [];
|
|
105892
105927
|
try {
|
|
105893
105928
|
candidates = await this.deps.fetchDoneCandidates();
|
|
@@ -105932,7 +105967,26 @@ class AgentCoordinator {
|
|
|
105932
105967
|
this.deps.onLog(`! pr-tracker clear failed for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
105933
105968
|
}
|
|
105934
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
|
+
}
|
|
105935
105987
|
if (pr.status === "conflicted") {
|
|
105988
|
+
if (!this.opts.prRecovery?.fixConflicts)
|
|
105989
|
+
continue;
|
|
105936
105990
|
if (tracker?.isBailed(issue2.identifier)) {
|
|
105937
105991
|
counts.quarantined += 1;
|
|
105938
105992
|
continue;
|
|
@@ -105966,6 +106020,8 @@ class AgentCoordinator {
|
|
|
105966
106020
|
continue;
|
|
105967
106021
|
}
|
|
105968
106022
|
if (pr.status === "ci_failed") {
|
|
106023
|
+
if (!this.opts.prRecovery?.fixCi)
|
|
106024
|
+
continue;
|
|
105969
106025
|
if (tracker?.isBailed(issue2.identifier)) {
|
|
105970
106026
|
counts.quarantined += 1;
|
|
105971
106027
|
continue;
|
|
@@ -106012,6 +106068,49 @@ class AgentCoordinator {
|
|
|
106012
106068
|
}
|
|
106013
106069
|
return counts;
|
|
106014
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
|
+
}
|
|
106015
106114
|
errorMarkerCleared(issue2) {
|
|
106016
106115
|
const se = this.opts.setError;
|
|
106017
106116
|
if (!se)
|
|
@@ -106320,9 +106419,12 @@ class AgentCoordinator {
|
|
|
106320
106419
|
async notifyExited(issue2, changeName, code, trigger, workerCwd) {
|
|
106321
106420
|
const noChanges = code === NO_CHANGES_EXIT;
|
|
106322
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;
|
|
106323
106425
|
const changeDir = this.deps.getChangeDir?.(issue2) ?? undefined;
|
|
106324
106426
|
const exitActor = await this.flowStore.getActor(issue2.id, changeDir);
|
|
106325
|
-
exitActor.send({ type: ok ? "
|
|
106427
|
+
exitActor.send({ type: !ok ? "WORKER_FAILED" : deferDone ? "PR_OPENED" : "WORKER_SUCCEEDED" });
|
|
106326
106428
|
if (changeDir) {
|
|
106327
106429
|
await this.flowStore.persistActor(issue2.id, changeDir).catch(() => {});
|
|
106328
106430
|
}
|
|
@@ -106375,6 +106477,8 @@ class AgentCoordinator {
|
|
|
106375
106477
|
} else if (trigger === "ci-fix") {
|
|
106376
106478
|
this.ciFailedNotified.delete(issue2.id);
|
|
106377
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");
|
|
106378
106482
|
} else if (this.opts.setDone) {
|
|
106379
106483
|
try {
|
|
106380
106484
|
await this.deps.applyIndicator(issue2, this.opts.setDone);
|
|
@@ -106456,6 +106560,7 @@ var emptyPrStatus = () => ({
|
|
|
106456
106560
|
});
|
|
106457
106561
|
var init_coordinator = __esm(() => {
|
|
106458
106562
|
init_types2();
|
|
106563
|
+
init_linear_client();
|
|
106459
106564
|
init_post_task();
|
|
106460
106565
|
init_queue_order();
|
|
106461
106566
|
init_src();
|
|
@@ -106993,7 +107098,7 @@ function issueInAwaitingStatus(issue2, indicators) {
|
|
|
106993
107098
|
async function releaseAwaitingMarker(issue2, statePath, deps) {
|
|
106994
107099
|
const { stateObj, confirmation } = await readConfirmationState(statePath);
|
|
106995
107100
|
if (!confirmation.awaitingMarkerAppliedAt && !issueInAwaitingStatus(issue2, deps.indicators)) {
|
|
106996
|
-
return;
|
|
107101
|
+
return false;
|
|
106997
107102
|
}
|
|
106998
107103
|
if (deps.indicators.clearAwaitingConfirmation) {
|
|
106999
107104
|
try {
|
|
@@ -107015,6 +107120,7 @@ async function releaseAwaitingMarker(issue2, statePath, deps) {
|
|
|
107015
107120
|
} catch (err) {
|
|
107016
107121
|
deps.onLog(`! persist cleared awaitingMarkerAppliedAt for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
107017
107122
|
}
|
|
107123
|
+
return true;
|
|
107018
107124
|
}
|
|
107019
107125
|
function confirmationUsesCommentIndicator(cfg) {
|
|
107020
107126
|
const { getApproved, getAutoApprove, getConfirmGate } = cfg.linear.indicators;
|
|
@@ -107076,13 +107182,15 @@ async function processAwaitingForIssue(issue2, deps) {
|
|
|
107076
107182
|
persistedConfirmation: confirmation
|
|
107077
107183
|
});
|
|
107078
107184
|
if (!active) {
|
|
107079
|
-
deps.awaitingChangeSet.delete(changeName);
|
|
107080
|
-
await releaseAwaitingMarker(issue2, statePath, {
|
|
107185
|
+
const wasTracked = deps.awaitingChangeSet.delete(changeName);
|
|
107186
|
+
const released = await releaseAwaitingMarker(issue2, statePath, {
|
|
107081
107187
|
indicators,
|
|
107082
107188
|
applyIndicator: deps.applyIndicator,
|
|
107083
107189
|
onLog: deps.onLog
|
|
107084
107190
|
});
|
|
107085
|
-
|
|
107191
|
+
if (wasTracked || released) {
|
|
107192
|
+
deps.onLog(` ${issue2.identifier}: confirmation detect released \u2014 gate-cleared`);
|
|
107193
|
+
}
|
|
107086
107194
|
return false;
|
|
107087
107195
|
}
|
|
107088
107196
|
if (!hasUnchecked(tasks2 ?? "")) {
|
|
@@ -107226,8 +107334,28 @@ function parsePrNumber(url2) {
|
|
|
107226
107334
|
const m = PR_NUMBER_RE.exec(url2);
|
|
107227
107335
|
return m ? Number(m[1]) : null;
|
|
107228
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
|
+
}
|
|
107229
107350
|
async function resolveDependencyBaseBranchImpl(issue2, runner, runnerCwd, deps) {
|
|
107230
|
-
|
|
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
|
+
}
|
|
107231
107359
|
if (blockerIds.length === 0)
|
|
107232
107360
|
return null;
|
|
107233
107361
|
let attachmentsByBlocker;
|
|
@@ -107261,17 +107389,30 @@ async function resolveDependencyBaseBranchImpl(issue2, runner, runnerCwd, deps)
|
|
|
107261
107389
|
}
|
|
107262
107390
|
}
|
|
107263
107391
|
if (openPrs.length === 1) {
|
|
107264
|
-
candidates.push(openPrs[0]);
|
|
107392
|
+
candidates.push({ blockerId, base: openPrs[0] });
|
|
107265
107393
|
} else if (openPrs.length > 1) {
|
|
107266
107394
|
deps.onLog(` ${issue2.identifier}: blocker ${blockerId} has ${openPrs.length} open PRs \u2014 skipping dependency base resolution`, "gray");
|
|
107267
107395
|
}
|
|
107268
107396
|
}
|
|
107269
107397
|
if (candidates.length === 1)
|
|
107270
|
-
return candidates[0];
|
|
107271
|
-
if (candidates.length
|
|
107272
|
-
|
|
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;
|
|
107273
107408
|
}
|
|
107274
|
-
|
|
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;
|
|
107275
107416
|
}
|
|
107276
107417
|
function createOpenDraftPr(deps) {
|
|
107277
107418
|
const create3 = deps.createPr ?? createPullRequest;
|
|
@@ -107315,11 +107456,18 @@ function traceCmdRunner(base2, onStart, onEnd) {
|
|
|
107315
107456
|
}
|
|
107316
107457
|
};
|
|
107317
107458
|
}
|
|
107318
|
-
var bunGitRunner, bunCmdRunner;
|
|
107459
|
+
var ghAuthEnv = () => scrubGithubAppTokenEnv(), bunGitRunner, bunCmdRunner;
|
|
107319
107460
|
var init_runners = __esm(() => {
|
|
107461
|
+
init_preflight();
|
|
107320
107462
|
bunGitRunner = {
|
|
107321
107463
|
run: async (args, cwd2) => {
|
|
107322
|
-
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
|
+
});
|
|
107323
107471
|
const stdout = await new Response(proc.stdout).text();
|
|
107324
107472
|
const stderr = await new Response(proc.stderr).text();
|
|
107325
107473
|
const code = await proc.exited;
|
|
@@ -107337,7 +107485,7 @@ var init_runners = __esm(() => {
|
|
|
107337
107485
|
};
|
|
107338
107486
|
bunCmdRunner = {
|
|
107339
107487
|
run: async (cmd, cwd2) => {
|
|
107340
|
-
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" });
|
|
107341
107489
|
const stdout = await new Response(proc.stdout).text();
|
|
107342
107490
|
const stderr = await new Response(proc.stderr).text();
|
|
107343
107491
|
const code = await proc.exited;
|
|
@@ -107402,7 +107550,7 @@ var init_indicators = __esm(() => {
|
|
|
107402
107550
|
|
|
107403
107551
|
// apps/agent/src/agent/wire/linear-resolvers.ts
|
|
107404
107552
|
function createLinearResolvers(input) {
|
|
107405
|
-
const { apiKey, team, assignee, anyAssignee, diag } = input;
|
|
107553
|
+
const { apiKey, team, assignee, anyAssignee, requireAllLabels, diag } = input;
|
|
107406
107554
|
const ticketNumbers = input.ticketNumbers ?? [];
|
|
107407
107555
|
const stateCache = new Map;
|
|
107408
107556
|
const labelCache = new Map;
|
|
@@ -107519,6 +107667,7 @@ function createLinearResolvers(input) {
|
|
|
107519
107667
|
team,
|
|
107520
107668
|
assignee,
|
|
107521
107669
|
anyAssignee,
|
|
107670
|
+
requireAllLabels,
|
|
107522
107671
|
include,
|
|
107523
107672
|
exclude: excl,
|
|
107524
107673
|
...ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
|
|
@@ -107541,7 +107690,18 @@ function createLinearResolvers(input) {
|
|
|
107541
107690
|
resolveLabelIdForTeam
|
|
107542
107691
|
};
|
|
107543
107692
|
}
|
|
107544
|
-
|
|
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) {
|
|
107545
107705
|
const getIndicators = [
|
|
107546
107706
|
indicators.getTodo,
|
|
107547
107707
|
indicators.getInProgress,
|
|
@@ -107560,13 +107720,7 @@ async function fetchDoneCandidatesWith(apiKey, team, _assignee, indicators, tick
|
|
|
107560
107720
|
const include = ind.filter ?? [];
|
|
107561
107721
|
if (include.length === 0)
|
|
107562
107722
|
return;
|
|
107563
|
-
const issues = await fetchOpenIssues(apiKey,
|
|
107564
|
-
team,
|
|
107565
|
-
anyAssignee: true,
|
|
107566
|
-
include,
|
|
107567
|
-
exclude: [],
|
|
107568
|
-
...ticketNumbers && ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
|
|
107569
|
-
});
|
|
107723
|
+
const issues = await fetchOpenIssues(apiKey, doneCandidateSpec(team, assignee, anyAssignee, requireAllLabels, include, ticketNumbers));
|
|
107570
107724
|
for (const issue2 of issues) {
|
|
107571
107725
|
if (!seen.has(issue2.id)) {
|
|
107572
107726
|
seen.add(issue2.id);
|
|
@@ -107603,6 +107757,7 @@ function createPrepareHelpers(input) {
|
|
|
107603
107757
|
maps,
|
|
107604
107758
|
scriptRunner
|
|
107605
107759
|
} = input;
|
|
107760
|
+
const worktreeProvider = input.worktreeProvider ?? defaultWorktreeProvider;
|
|
107606
107761
|
async function runScript(label, cmd, cwd2) {
|
|
107607
107762
|
diag("script", ` ${label}: ${cmd}`, "gray");
|
|
107608
107763
|
const code = await scriptRunner(cmd, cwd2);
|
|
@@ -107621,7 +107776,7 @@ function createPrepareHelpers(input) {
|
|
|
107621
107776
|
const baseBranch = baseBranchFromLabels(issue2.labels) ?? cfg.prBaseBranch;
|
|
107622
107777
|
let wt;
|
|
107623
107778
|
try {
|
|
107624
|
-
wt = await
|
|
107779
|
+
wt = await worktreeProvider.create({
|
|
107625
107780
|
projectRoot,
|
|
107626
107781
|
changeName: probeName,
|
|
107627
107782
|
baseBranch,
|
|
@@ -107638,7 +107793,7 @@ function createPrepareHelpers(input) {
|
|
|
107638
107793
|
scaffoldStatesDir = wtLayout.statesDir;
|
|
107639
107794
|
diag("worktree", ` ${issue2.identifier} worktree: ${wt.cwd} (${wt.branch})`, "gray");
|
|
107640
107795
|
try {
|
|
107641
|
-
await
|
|
107796
|
+
await worktreeProvider.seedMcpConfig({
|
|
107642
107797
|
projectRoot,
|
|
107643
107798
|
worktreeCwd: wt.cwd
|
|
107644
107799
|
});
|
|
@@ -107834,6 +107989,7 @@ PR: ${ciPrUrl}` : ""
|
|
|
107834
107989
|
}
|
|
107835
107990
|
return { prepare, prepareTaskForTrigger, runScript, reactivateState: reactivateState2 };
|
|
107836
107991
|
}
|
|
107992
|
+
var defaultWorktreeProvider;
|
|
107837
107993
|
var init_prepare = __esm(() => {
|
|
107838
107994
|
init_layout();
|
|
107839
107995
|
init_tasks_md();
|
|
@@ -107844,6 +108000,10 @@ var init_prepare = __esm(() => {
|
|
|
107844
108000
|
init_scaffold();
|
|
107845
108001
|
init_worktree();
|
|
107846
108002
|
init_task_bodies();
|
|
108003
|
+
defaultWorktreeProvider = {
|
|
108004
|
+
create: (args) => runCapability(git.createWorktree, args),
|
|
108005
|
+
seedMcpConfig: (args) => runCapability(git.seedWorktreeMcpConfig, args)
|
|
108006
|
+
};
|
|
107847
108007
|
});
|
|
107848
108008
|
|
|
107849
108009
|
// apps/agent/src/agent/pr-url/index.ts
|
|
@@ -107903,9 +108063,65 @@ var init_pr_url = __esm(() => {
|
|
|
107903
108063
|
init_task_bodies();
|
|
107904
108064
|
});
|
|
107905
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
|
+
|
|
107906
108113
|
// apps/agent/src/agent/wire/pr-discovery.ts
|
|
107907
108114
|
function createPrDiscovery(input) {
|
|
107908
|
-
const {
|
|
108115
|
+
const {
|
|
108116
|
+
apiKey,
|
|
108117
|
+
projectRoot,
|
|
108118
|
+
cmdRunner,
|
|
108119
|
+
onLog,
|
|
108120
|
+
diag,
|
|
108121
|
+
prByChange,
|
|
108122
|
+
getPollContext,
|
|
108123
|
+
ignoreCiChecks
|
|
108124
|
+
} = input;
|
|
107909
108125
|
const prUnavailable = new Map;
|
|
107910
108126
|
const prUrlByIssue = createPrUrlCache(5 * 60 * 1000);
|
|
107911
108127
|
function isPrUnavailable(changeName) {
|
|
@@ -107977,9 +108193,11 @@ function createPrDiscovery(input) {
|
|
|
107977
108193
|
if (outcome.kind === "conflicting")
|
|
107978
108194
|
return { url: prUrl, status: "conflicted" };
|
|
107979
108195
|
try {
|
|
107980
|
-
const ci = await getPrChecksStatus(prUrl, cmdRunner, projectRoot);
|
|
108196
|
+
const ci = await getPrChecksStatus(prUrl, cmdRunner, projectRoot, undefined, ignoreCiChecks);
|
|
107981
108197
|
if (ci.bucket === "fail")
|
|
107982
108198
|
return { url: prUrl, status: "ci_failed" };
|
|
108199
|
+
if (ci.bucket === "pending")
|
|
108200
|
+
return { url: prUrl, status: "unknown" };
|
|
107983
108201
|
} catch (err) {
|
|
107984
108202
|
diag("ci", `! gh pr checks ${prUrl} failed (PR scan): ${err.message}`, "yellow");
|
|
107985
108203
|
}
|
|
@@ -108260,6 +108478,7 @@ function createMentionScanner(input) {
|
|
|
108260
108478
|
team,
|
|
108261
108479
|
assignee,
|
|
108262
108480
|
anyAssignee,
|
|
108481
|
+
requireAllLabels,
|
|
108263
108482
|
indicators,
|
|
108264
108483
|
projectRoot,
|
|
108265
108484
|
useWorktree,
|
|
@@ -108284,6 +108503,7 @@ function createMentionScanner(input) {
|
|
|
108284
108503
|
team,
|
|
108285
108504
|
assignee,
|
|
108286
108505
|
anyAssignee,
|
|
108506
|
+
...requireAllLabels && requireAllLabels.length > 0 ? { requireAllLabels } : {},
|
|
108287
108507
|
...ticketNumbers && ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {},
|
|
108288
108508
|
indicators: {
|
|
108289
108509
|
...indicators.getTodo !== undefined ? { getTodo: indicators.getTodo } : {},
|
|
@@ -108440,6 +108660,31 @@ var init_mention_scan = __esm(() => {
|
|
|
108440
108660
|
init_task_bodies();
|
|
108441
108661
|
});
|
|
108442
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
|
+
|
|
108443
108688
|
// apps/agent/src/agent/wire/spawn/default.ts
|
|
108444
108689
|
import { join as join30 } from "path";
|
|
108445
108690
|
function defaultSpawn(changeName, cmd, cwd2, logsDir, onWorkerOutput, note) {
|
|
@@ -108527,7 +108772,7 @@ function dispositionFromExitCode(code) {
|
|
|
108527
108772
|
return "done";
|
|
108528
108773
|
case NO_CHANGES_EXIT2:
|
|
108529
108774
|
return "no-changes";
|
|
108530
|
-
case
|
|
108775
|
+
case CI_FAILED_EXIT:
|
|
108531
108776
|
return "ci-failed";
|
|
108532
108777
|
case PR_FAILED_EXIT2:
|
|
108533
108778
|
return "pr-failed";
|
|
@@ -108535,7 +108780,7 @@ function dispositionFromExitCode(code) {
|
|
|
108535
108780
|
return "error";
|
|
108536
108781
|
}
|
|
108537
108782
|
}
|
|
108538
|
-
var
|
|
108783
|
+
var CI_FAILED_EXIT = 70, PR_FAILED_EXIT2 = 71, NO_CHANGES_EXIT2 = 72;
|
|
108539
108784
|
|
|
108540
108785
|
// packages/retro/src/paths.ts
|
|
108541
108786
|
import { homedir as homedir7 } from "os";
|
|
@@ -108762,17 +109007,13 @@ function buildPostTaskInput(input) {
|
|
|
108762
109007
|
exitCode: input.exitCode,
|
|
108763
109008
|
useWorktree: input.useWorktree,
|
|
108764
109009
|
wantPr: input.wantPr,
|
|
108765
|
-
wantFixCi: input.wantFixCi,
|
|
108766
109010
|
wantAutoMerge: input.wantAutoMerge,
|
|
108767
109011
|
wantValidateOnly: input.wantValidateOnly,
|
|
108768
109012
|
cfg: {
|
|
108769
109013
|
teardownScript: cfg.teardownScript ?? null,
|
|
108770
109014
|
prBaseBranch: cfg.prBaseBranch,
|
|
108771
109015
|
autoMergeStrategy: cfg.autoMergeStrategy,
|
|
108772
|
-
maxCiFixAttempts: cfg.maxCiFixAttempts,
|
|
108773
|
-
ciPollIntervalSeconds: cfg.ciPollIntervalSeconds,
|
|
108774
109016
|
cleanupWorktreeOnSuccess: cfg.cleanupWorktreeOnSuccess,
|
|
108775
|
-
ignoreCiChecks: cfg.ignoreCiChecks,
|
|
108776
109017
|
stackPrsOnDependencies: args.stackPrs || cfg.stackPrsOnDependencies,
|
|
108777
109018
|
neverTouch: cfg.boundaries.never_touch,
|
|
108778
109019
|
metaOnlyFiles: cfg.boundaries.meta_only_files,
|
|
@@ -108868,6 +109109,8 @@ function createSpawnWorker(input) {
|
|
|
108868
109109
|
const f2 = Bun.file(missionTasksPath);
|
|
108869
109110
|
return await f2.exists() ? await f2.text() : "";
|
|
108870
109111
|
})();
|
|
109112
|
+
const guardOn = useWorktree && cwd2 !== projectRoot;
|
|
109113
|
+
const beforeSnapshotPromise = guardOn ? snapshotCheckout(projectRoot, gitRunner) : Promise.resolve(null);
|
|
108871
109114
|
let logFilePath;
|
|
108872
109115
|
let handle;
|
|
108873
109116
|
if (injected) {
|
|
@@ -108888,10 +109131,29 @@ function createSpawnWorker(input) {
|
|
|
108888
109131
|
onWorkerPhase?.(changeName, "working");
|
|
108889
109132
|
const tracedCmd = onWorkerCmd ? traceCmdRunner(cmdRunner, (cmd) => onWorkerCmd(changeName, cmd, "start"), (cmd, ms, ok) => onWorkerCmd(changeName, cmd, "end", ms, ok)) : cmdRunner;
|
|
108890
109133
|
const wantPrBase = args.createPr || cfg.createPrOnSuccess;
|
|
108891
|
-
const wantFixCi = args.fixCi || cfg.fixCiOnFailure;
|
|
108892
109134
|
const issueForChange = issueByChange.get(changeName);
|
|
108893
109135
|
const wantAutoMerge = issueForChange ? issueMatchesGetIndicator(issueForChange, indicators.getAutoMerge) : false;
|
|
108894
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
|
+
}
|
|
108895
109157
|
const workerLayout = projectLayout(cwd2);
|
|
108896
109158
|
const validateSpecPath = join33(workerLayout.changeDir(changeName), "specs", "validate.md");
|
|
108897
109159
|
const hasValidateSpec = await Bun.file(validateSpecPath).exists();
|
|
@@ -108953,7 +109215,6 @@ function createSpawnWorker(input) {
|
|
|
108953
109215
|
exitCode: code,
|
|
108954
109216
|
useWorktree,
|
|
108955
109217
|
wantPr,
|
|
108956
|
-
wantFixCi,
|
|
108957
109218
|
wantAutoMerge,
|
|
108958
109219
|
wantValidateOnly,
|
|
108959
109220
|
...trigger ? { trigger } : {},
|
|
@@ -108986,16 +109247,6 @@ function createSpawnWorker(input) {
|
|
|
108986
109247
|
...onWorkerPhase && {
|
|
108987
109248
|
onPhase: (phase2, detail) => onWorkerPhase(changeName, phase2, detail)
|
|
108988
109249
|
},
|
|
108989
|
-
checkPrConflict: async (prUrl) => {
|
|
108990
|
-
const outcome = await waitForMergeability({
|
|
108991
|
-
bailOnError: true,
|
|
108992
|
-
probe: async () => {
|
|
108993
|
-
const res = await tracedCmd.run(["gh", "pr", "view", prUrl, "--json", "state,mergeable,mergeStateStatus"], cwd2);
|
|
108994
|
-
return JSON.parse(res.stdout || "{}");
|
|
108995
|
-
}
|
|
108996
|
-
});
|
|
108997
|
-
return outcome.kind === "conflicting";
|
|
108998
|
-
},
|
|
108999
109250
|
resolveDependencyBaseBranch: (issue2) => resolveDependencyBaseBranchImpl(issue2, tracedCmd, cwd2, { apiKey, onLog })
|
|
109000
109251
|
});
|
|
109001
109252
|
releaseWorkerMaps({ cwdByChange, statesDirByChange, branchByChange, issueByChange }, changeName);
|
|
@@ -109013,7 +109264,6 @@ var init_worker = __esm(() => {
|
|
|
109013
109264
|
init_default2();
|
|
109014
109265
|
init_runners();
|
|
109015
109266
|
init_pr_helpers();
|
|
109016
|
-
init_wait_for_mergeability();
|
|
109017
109267
|
init_agent_run_state();
|
|
109018
109268
|
init_retro();
|
|
109019
109269
|
init_engine();
|
|
@@ -261441,6 +261691,7 @@ function renderListItem(doc2, item, indent, marker) {
|
|
|
261441
261691
|
const tokens = item.tokens ?? [];
|
|
261442
261692
|
let inlineRun = [];
|
|
261443
261693
|
let placedInline = false;
|
|
261694
|
+
let renderedBlock = false;
|
|
261444
261695
|
const placeInline = () => {
|
|
261445
261696
|
if (inlineRun.length === 0)
|
|
261446
261697
|
return;
|
|
@@ -261464,10 +261715,13 @@ function renderListItem(doc2, item, indent, marker) {
|
|
|
261464
261715
|
continue;
|
|
261465
261716
|
}
|
|
261466
261717
|
placeInline();
|
|
261718
|
+
if (!placedInline && !renderedBlock)
|
|
261719
|
+
doc2.y = startY;
|
|
261467
261720
|
renderBlock(doc2, tok, indent + LIST_INDENT);
|
|
261721
|
+
renderedBlock = true;
|
|
261468
261722
|
}
|
|
261469
261723
|
placeInline();
|
|
261470
|
-
if (!placedInline) {
|
|
261724
|
+
if (!placedInline && !renderedBlock) {
|
|
261471
261725
|
doc2.y = startY;
|
|
261472
261726
|
doc2.text(" ", bodyX, startY, { width: bodyWidth });
|
|
261473
261727
|
}
|
|
@@ -262442,8 +262696,7 @@ function buildAgentCoordinator(input) {
|
|
|
262442
262696
|
const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
|
|
262443
262697
|
const indicators = mergeIndicators(cfg.linear.indicators, args.indicators);
|
|
262444
262698
|
const team = args.linearTeam || cfg.linear.team;
|
|
262445
|
-
const
|
|
262446
|
-
const { assignee, anyAssignee } = parseLinearFilter(effectiveFilter);
|
|
262699
|
+
const { assignee, anyAssignee, requireAllLabels } = resolveLinearFilter(applyAssigneeOverride(cfg.linear.filter, args.linearAssignee));
|
|
262447
262700
|
const ticketNumbers = resolveTicketNumbers(args.ticketTokens, team);
|
|
262448
262701
|
const excludeFromTodo = unionMarkers(indicators.setDone, indicators.setError);
|
|
262449
262702
|
const gitRunner = input.runners?.git ?? bunGitRunner;
|
|
@@ -262485,6 +262738,7 @@ function buildAgentCoordinator(input) {
|
|
|
262485
262738
|
team,
|
|
262486
262739
|
assignee,
|
|
262487
262740
|
anyAssignee,
|
|
262741
|
+
requireAllLabels,
|
|
262488
262742
|
diag,
|
|
262489
262743
|
...ticketNumbers.length > 0 ? { ticketNumbers } : {}
|
|
262490
262744
|
});
|
|
@@ -262501,7 +262755,8 @@ function buildAgentCoordinator(input) {
|
|
|
262501
262755
|
onLog,
|
|
262502
262756
|
diag,
|
|
262503
262757
|
prByChange,
|
|
262504
|
-
getPollContext: () => pollContext
|
|
262758
|
+
getPollContext: () => pollContext,
|
|
262759
|
+
ignoreCiChecks: cfg.prRecovery.ignoreChecks
|
|
262505
262760
|
});
|
|
262506
262761
|
const prep = createPrepareHelpers({
|
|
262507
262762
|
args,
|
|
@@ -262514,7 +262769,8 @@ function buildAgentCoordinator(input) {
|
|
|
262514
262769
|
gitRunner,
|
|
262515
262770
|
diag,
|
|
262516
262771
|
maps: { cwdByChange, statesDirByChange, issueByChange, branchByChange, prByChange },
|
|
262517
|
-
scriptRunner
|
|
262772
|
+
scriptRunner,
|
|
262773
|
+
...input.runners?.worktree ? { worktreeProvider: input.runners.worktree } : {}
|
|
262518
262774
|
});
|
|
262519
262775
|
const fetchMentions = createMentionScanner({
|
|
262520
262776
|
apiKey,
|
|
@@ -262523,6 +262779,7 @@ function buildAgentCoordinator(input) {
|
|
|
262523
262779
|
team,
|
|
262524
262780
|
assignee,
|
|
262525
262781
|
anyAssignee,
|
|
262782
|
+
requireAllLabels,
|
|
262526
262783
|
indicators,
|
|
262527
262784
|
projectRoot,
|
|
262528
262785
|
useWorktree,
|
|
@@ -262616,10 +262873,10 @@ function buildAgentCoordinator(input) {
|
|
|
262616
262873
|
now: () => new Date
|
|
262617
262874
|
};
|
|
262618
262875
|
}
|
|
262619
|
-
const
|
|
262620
|
-
const prTracker =
|
|
262876
|
+
const prRecoveryEnabled = args.prRecoveryEnabled === undefined ? cfg.prRecovery.enabled : args.prRecoveryEnabled;
|
|
262877
|
+
const prTracker = prRecoveryEnabled ? new PrTracker({
|
|
262621
262878
|
projectRoot,
|
|
262622
|
-
maxRecoveryAttempts: cfg.
|
|
262879
|
+
maxRecoveryAttempts: cfg.prRecovery.maxRecoverySessions
|
|
262623
262880
|
}) : null;
|
|
262624
262881
|
const commentSync = createCommentSyncHooks({
|
|
262625
262882
|
apiKey,
|
|
@@ -262637,7 +262894,7 @@ function buildAgentCoordinator(input) {
|
|
|
262637
262894
|
fetchTodo: () => resolvers.fetchByGet(indicators.getTodo, excludeFromTodo),
|
|
262638
262895
|
fetchInProgress: () => resolvers.fetchByGet(indicators.getInProgress, unionMarkers(indicators.setError)),
|
|
262639
262896
|
fetchMentions,
|
|
262640
|
-
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),
|
|
262641
262898
|
prepare: prep.prepare,
|
|
262642
262899
|
prepareTaskForTrigger: prep.prepareTaskForTrigger,
|
|
262643
262900
|
spawnWorker,
|
|
@@ -262649,6 +262906,7 @@ function buildAgentCoordinator(input) {
|
|
|
262649
262906
|
return c.map((x2) => ({ body: x2.body }));
|
|
262650
262907
|
},
|
|
262651
262908
|
checkPrStatus: prDiscovery.checkPrStatus,
|
|
262909
|
+
hasPrForChange: (changeName) => prByChange.has(changeName),
|
|
262652
262910
|
isChangeArchivedForIssue: (issue2) => isChangeArchivedForIssue(issue2, cwdByChange, projectRoot),
|
|
262653
262911
|
onLog,
|
|
262654
262912
|
...onFileLog ? { onFileLog } : {},
|
|
@@ -262685,7 +262943,13 @@ function buildAgentCoordinator(input) {
|
|
|
262685
262943
|
postComments: cfg.linear.postComments,
|
|
262686
262944
|
commentEveryIterations: cfg.linear.updateEveryIterations,
|
|
262687
262945
|
...args.maxTickets > 0 ? { maxTickets: args.maxTickets } : {},
|
|
262688
|
-
|
|
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
|
+
}
|
|
262689
262953
|
});
|
|
262690
262954
|
coordRef.current = coord;
|
|
262691
262955
|
const filterDesc = describeIndicators(indicators, team, assignee, anyAssignee);
|
|
@@ -262846,23 +263110,23 @@ function SteeringField({
|
|
|
262846
263110
|
initialFocused = false,
|
|
262847
263111
|
onStateChange
|
|
262848
263112
|
}) {
|
|
262849
|
-
const [state, dispatch] =
|
|
263113
|
+
const [state, dispatch] = import_react62.useReducer(reducer2, { initialBuffer, initialCursor, initialFocused }, (init2) => ({
|
|
262850
263114
|
buffer: init2.initialBuffer,
|
|
262851
263115
|
cursor: init2.initialCursor ?? init2.initialBuffer.length,
|
|
262852
263116
|
focused: init2.initialFocused,
|
|
262853
263117
|
status: "idle"
|
|
262854
263118
|
}));
|
|
262855
263119
|
const { buffer, cursor: cursor4, focused, status } = state;
|
|
262856
|
-
const stateRef =
|
|
263120
|
+
const stateRef = import_react62.useRef(state);
|
|
262857
263121
|
stateRef.current = state;
|
|
262858
|
-
const hintTimerRef =
|
|
262859
|
-
|
|
263122
|
+
const hintTimerRef = import_react62.useRef(null);
|
|
263123
|
+
import_react62.useEffect(() => {
|
|
262860
263124
|
onFocusChange?.(focused);
|
|
262861
263125
|
}, [focused, onFocusChange]);
|
|
262862
|
-
|
|
263126
|
+
import_react62.useEffect(() => {
|
|
262863
263127
|
onStateChange?.({ buffer, cursor: cursor4, focused });
|
|
262864
263128
|
}, [buffer, cursor4, focused, onStateChange]);
|
|
262865
|
-
|
|
263129
|
+
import_react62.useEffect(() => {
|
|
262866
263130
|
return () => {
|
|
262867
263131
|
if (hintTimerRef.current)
|
|
262868
263132
|
clearTimeout(hintTimerRef.current);
|
|
@@ -262971,10 +263235,10 @@ function SteeringField({
|
|
|
262971
263235
|
]
|
|
262972
263236
|
}, undefined, true, undefined, this);
|
|
262973
263237
|
}
|
|
262974
|
-
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";
|
|
262975
263239
|
var init_SteeringField = __esm(async () => {
|
|
262976
263240
|
await init_build2();
|
|
262977
|
-
|
|
263241
|
+
import_react62 = __toESM(require_react(), 1);
|
|
262978
263242
|
jsx_dev_runtime10 = __toESM(require_jsx_dev_runtime(), 1);
|
|
262979
263243
|
});
|
|
262980
263244
|
|
|
@@ -263343,21 +263607,35 @@ function AgentMode({
|
|
|
263343
263607
|
const { exit } = use_app_default();
|
|
263344
263608
|
const { isRawModeSupported } = use_stdin_default();
|
|
263345
263609
|
const { columns, rows, resizeKey } = useTerminalSize();
|
|
263346
|
-
const [logs, setLogs] =
|
|
263347
|
-
const [preflightError, setPreflightError] =
|
|
263348
|
-
const [,
|
|
263349
|
-
const
|
|
263350
|
-
const
|
|
263351
|
-
|
|
263352
|
-
|
|
263353
|
-
|
|
263354
|
-
|
|
263355
|
-
|
|
263356
|
-
|
|
263357
|
-
|
|
263358
|
-
|
|
263359
|
-
|
|
263360
|
-
|
|
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({
|
|
263361
263639
|
state: "idle",
|
|
263362
263640
|
lastFound: null,
|
|
263363
263641
|
lastAdded: null,
|
|
@@ -263370,14 +263648,14 @@ function AgentMode({
|
|
|
263370
263648
|
setLogs((prev) => [...prev, { id: nextId(), text, color }]);
|
|
263371
263649
|
logCoord(text, workerLogFile);
|
|
263372
263650
|
}
|
|
263373
|
-
const fileSinkRef =
|
|
263651
|
+
const fileSinkRef = import_react63.useRef(null);
|
|
263374
263652
|
if (fileSinkRef.current === null) {
|
|
263375
263653
|
fileSinkRef.current = createJsonLogFileSink(args.jsonLogFile);
|
|
263376
263654
|
}
|
|
263377
263655
|
const fileEmit = (event) => {
|
|
263378
263656
|
fileSinkRef.current?.emit(event);
|
|
263379
263657
|
};
|
|
263380
|
-
|
|
263658
|
+
import_react63.useEffect(() => {
|
|
263381
263659
|
let pollTimer = null;
|
|
263382
263660
|
let cancelled = false;
|
|
263383
263661
|
async function init2() {
|
|
@@ -263390,15 +263668,14 @@ function AgentMode({
|
|
|
263390
263668
|
if (!apiKey) {
|
|
263391
263669
|
throw new Error("LINEAR_API_KEY not set \u2014 cannot poll Linear");
|
|
263392
263670
|
}
|
|
263393
|
-
const pf = await runPreflight2(
|
|
263671
|
+
const pf = await runPreflight2({
|
|
263672
|
+
requireRepoWrite: args.createPr || cfg2.createPrOnSuccess,
|
|
263673
|
+
repoCwd: projectRoot
|
|
263674
|
+
});
|
|
263394
263675
|
if (!pf.ok) {
|
|
263395
263676
|
fileEmit({ type: "error", code: "auth_failure", tool: pf.tool, text: pf.message });
|
|
263396
263677
|
setPreflightError({ tool: pf.tool, message: pf.message });
|
|
263397
|
-
|
|
263398
|
-
setTimeout(() => {
|
|
263399
|
-
exit();
|
|
263400
|
-
setTimeout(() => process.exit(2), 200);
|
|
263401
|
-
}, 100);
|
|
263678
|
+
setFatalExit(2);
|
|
263402
263679
|
return;
|
|
263403
263680
|
}
|
|
263404
263681
|
const { coord: coord2, filterDesc, concurrency, pollInterval, runBaselineGate: runBaselineGate2, getGaveUpTotal } = buildCoordinator({
|
|
@@ -263574,10 +263851,7 @@ function AgentMode({
|
|
|
263574
263851
|
const message = err instanceof Error ? err.message : String(err);
|
|
263575
263852
|
fileEmit({ type: "error", code: "init_failure", text: message });
|
|
263576
263853
|
appendLog(`! ${message}`, "red");
|
|
263577
|
-
|
|
263578
|
-
exit();
|
|
263579
|
-
setTimeout(() => process.exit(1), 200);
|
|
263580
|
-
}, 100);
|
|
263854
|
+
setFatalExit(1);
|
|
263581
263855
|
});
|
|
263582
263856
|
let shuttingDown = false;
|
|
263583
263857
|
const onSig = () => {
|
|
@@ -263620,8 +263894,8 @@ function AgentMode({
|
|
|
263620
263894
|
process.off("SIGTERM", onSig);
|
|
263621
263895
|
};
|
|
263622
263896
|
}, []);
|
|
263623
|
-
const lastPauseRef =
|
|
263624
|
-
|
|
263897
|
+
const lastPauseRef = import_react63.useRef(null);
|
|
263898
|
+
import_react63.useEffect(() => {
|
|
263625
263899
|
let cancelled = false;
|
|
263626
263900
|
const interval = setInterval(() => {
|
|
263627
263901
|
if (cancelled)
|
|
@@ -263685,10 +263959,10 @@ function AgentMode({
|
|
|
263685
263959
|
const termWidth = columns - 2;
|
|
263686
263960
|
const termHeight = rows;
|
|
263687
263961
|
const safeFocusedIdx = activeCount > 0 ? Math.min(focusedIdx, activeCount - 1) : 0;
|
|
263688
|
-
const steeringFocusedRef =
|
|
263689
|
-
const steeringBufferRef =
|
|
263690
|
-
const steeringCursorRef =
|
|
263691
|
-
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);
|
|
263692
263966
|
use_input_default((input, key) => {
|
|
263693
263967
|
if (steeringFocusedRef.current)
|
|
263694
263968
|
return;
|
|
@@ -263740,7 +264014,15 @@ function AgentMode({
|
|
|
263740
264014
|
/* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
|
|
263741
264015
|
color: "red",
|
|
263742
264016
|
children: preflightError.message
|
|
263743
|
-
}, 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)
|
|
263744
264026
|
]
|
|
263745
264027
|
}, undefined, true, undefined, this);
|
|
263746
264028
|
}
|
|
@@ -263857,10 +264139,13 @@ function AgentMode({
|
|
|
263857
264139
|
color: "green",
|
|
263858
264140
|
children: " \u25CF PR"
|
|
263859
264141
|
}, undefined, false, undefined, this),
|
|
263860
|
-
cfg.
|
|
264142
|
+
cfg.prRecovery.enabled && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
|
|
263861
264143
|
color: "green",
|
|
263862
|
-
children:
|
|
263863
|
-
|
|
264144
|
+
children: [
|
|
264145
|
+
" \u25CF recover",
|
|
264146
|
+
cfg.prRecovery.fixCi ? "+CI" : ""
|
|
264147
|
+
]
|
|
264148
|
+
}, undefined, true, undefined, this),
|
|
263864
264149
|
cfg.useWorktree && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
|
|
263865
264150
|
color: "green",
|
|
263866
264151
|
children: " \u25CF worktree"
|
|
@@ -264760,11 +265045,15 @@ function AgentMode({
|
|
|
264760
265045
|
}, w2.changeName, true, undefined, this);
|
|
264761
265046
|
})
|
|
264762
265047
|
]
|
|
264763
|
-
}, 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)
|
|
264764
265053
|
]
|
|
264765
265054
|
}, resizeKey, true, undefined, this);
|
|
264766
265055
|
}
|
|
264767
|
-
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;
|
|
264768
265057
|
var init_AgentMode = __esm(async () => {
|
|
264769
265058
|
init_cli2();
|
|
264770
265059
|
init_config();
|
|
@@ -264780,9 +265069,10 @@ var init_AgentMode = __esm(async () => {
|
|
|
264780
265069
|
init_worker_state_poll();
|
|
264781
265070
|
await __promiseAll([
|
|
264782
265071
|
init_build2(),
|
|
265072
|
+
init_useHoldToClose(),
|
|
264783
265073
|
init_SteeringField()
|
|
264784
265074
|
]);
|
|
264785
|
-
|
|
265075
|
+
import_react63 = __toESM(require_react(), 1);
|
|
264786
265076
|
jsx_dev_runtime11 = __toESM(require_jsx_dev_runtime(), 1);
|
|
264787
265077
|
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
264788
265078
|
HYPERLINKS_SUPPORTED = !process.env["TMUX"];
|
|
@@ -264844,7 +265134,7 @@ function createSession(name, command, env3) {
|
|
|
264844
265134
|
envArgs.push("-e", `${key}=${value}`);
|
|
264845
265135
|
}
|
|
264846
265136
|
const quoted = command.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
|
|
264847
|
-
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`;
|
|
264848
265138
|
const result2 = Bun.spawnSync({
|
|
264849
265139
|
cmd: ["tmux", "new-session", "-d", "-s", name, ...envArgs, "sh", "-c", shellCmd],
|
|
264850
265140
|
stderr: "pipe"
|
|
@@ -265085,23 +265375,20 @@ function buildBuckets(indicators) {
|
|
|
265085
265375
|
{ label: "auto-merge", indicator: indicators.getAutoMerge, exclude: [] }
|
|
265086
265376
|
];
|
|
265087
265377
|
}
|
|
265088
|
-
async function fetchBucketIssues(apiKey, bucket, team, assignee, anyAssignee, ticketNumbers) {
|
|
265378
|
+
async function fetchBucketIssues(apiKey, bucket, team, assignee, anyAssignee, requireAllLabels, ticketNumbers) {
|
|
265089
265379
|
if (!bucket.indicator || bucket.indicator.filter.length === 0)
|
|
265090
265380
|
return [];
|
|
265091
265381
|
const spec = {
|
|
265092
265382
|
team,
|
|
265093
265383
|
assignee,
|
|
265094
265384
|
anyAssignee,
|
|
265385
|
+
requireAllLabels,
|
|
265095
265386
|
include: bucket.indicator.filter,
|
|
265096
265387
|
exclude: bucket.exclude,
|
|
265097
265388
|
...ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
|
|
265098
265389
|
};
|
|
265099
265390
|
return fetchOpenIssues(apiKey, spec);
|
|
265100
265391
|
}
|
|
265101
|
-
function resolveLinearFilter(filterOverride, assigneeOverride, configFilter) {
|
|
265102
|
-
const effective = filterOverride || (assigneeOverride ? `assignee = ${assigneeOverride}` : "") || configFilter;
|
|
265103
|
-
return parseLinearFilter(effective);
|
|
265104
|
-
}
|
|
265105
265392
|
function formatReviewCell(prUrl, count) {
|
|
265106
265393
|
if (!prUrl)
|
|
265107
265394
|
return "-";
|
|
@@ -265148,13 +265435,13 @@ function backlogRankByIssueId(issues) {
|
|
|
265148
265435
|
ordered.forEach((o, i) => rankById.set(o.id, i));
|
|
265149
265436
|
return rankById;
|
|
265150
265437
|
}
|
|
265151
|
-
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 = []) {
|
|
265152
265439
|
const bucketResults = await Promise.all(buckets.map(async (bucket) => {
|
|
265153
265440
|
if (!bucket.indicator || bucket.indicator.filter.length === 0) {
|
|
265154
265441
|
return { bucket, issues: [], error: null };
|
|
265155
265442
|
}
|
|
265156
265443
|
try {
|
|
265157
|
-
const issues = await fetchBucketIssues(apiKey, bucket, team, assignee, anyAssignee, ticketNumbers);
|
|
265444
|
+
const issues = await fetchBucketIssues(apiKey, bucket, team, assignee, anyAssignee, requireAllLabels, ticketNumbers);
|
|
265158
265445
|
return { bucket, issues, error: null };
|
|
265159
265446
|
} catch (err) {
|
|
265160
265447
|
return {
|
|
@@ -265280,7 +265567,6 @@ async function runList(input) {
|
|
|
265280
265567
|
identifier: name,
|
|
265281
265568
|
projectRoot,
|
|
265282
265569
|
linearTeamOverride: input.linearTeamOverride,
|
|
265283
|
-
linearFilterOverride: input.linearFilterOverride,
|
|
265284
265570
|
linearAssigneeOverride: input.linearAssigneeOverride
|
|
265285
265571
|
});
|
|
265286
265572
|
return;
|
|
@@ -265291,7 +265577,7 @@ async function runList(input) {
|
|
|
265291
265577
|
const apiKey = process.env["LINEAR_API_KEY"];
|
|
265292
265578
|
const indicators = cfg.linear.indicators;
|
|
265293
265579
|
const team = input.linearTeamOverride || cfg.linear.team;
|
|
265294
|
-
const { assignee, anyAssignee } = resolveLinearFilter(
|
|
265580
|
+
const { assignee, anyAssignee, requireAllLabels } = resolveLinearFilter(applyAssigneeOverride(cfg.linear.filter, input.linearAssigneeOverride));
|
|
265295
265581
|
const buckets = buildBuckets(indicators);
|
|
265296
265582
|
const anyConfigured = buckets.some((b2) => b2.indicator && b2.indicator.filter.length > 0);
|
|
265297
265583
|
if (!anyConfigured) {
|
|
@@ -265331,7 +265617,7 @@ team: ${team}
|
|
|
265331
265617
|
if (ticketNumbers.length > 0)
|
|
265332
265618
|
process.stdout.write(`ticket: ${ticketNumbers.join(", ")}
|
|
265333
265619
|
`);
|
|
265334
|
-
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);
|
|
265335
265621
|
}
|
|
265336
265622
|
function normalizeIdentifier(input) {
|
|
265337
265623
|
let parsed;
|
|
@@ -265414,7 +265700,7 @@ async function runListDebug(input) {
|
|
|
265414
265700
|
const cfg = await loadRalphyConfig(projectRoot, getArgs().workflowFile);
|
|
265415
265701
|
const indicators = cfg.linear.indicators;
|
|
265416
265702
|
const team = input.linearTeamOverride || cfg.linear.team;
|
|
265417
|
-
const { assignee, anyAssignee } = resolveLinearFilter(
|
|
265703
|
+
const { assignee, anyAssignee, requireAllLabels } = resolveLinearFilter(applyAssigneeOverride(cfg.linear.filter, input.linearAssigneeOverride));
|
|
265418
265704
|
const assigneeLabel = anyAssignee ? "any" : assignee ?? "*";
|
|
265419
265705
|
const normalized = normalizeIdentifier(identifier);
|
|
265420
265706
|
if (!normalized) {
|
|
@@ -265458,6 +265744,13 @@ Per-bucket diagnostics:
|
|
|
265458
265744
|
if (!assigneeMatches(issue2, assignee, anyAssignee)) {
|
|
265459
265745
|
reasons.push(`assignee mismatch: issue=${issue2.assignee ? issue2.assignee.email ?? issue2.assignee.id : "unassigned"}, config=${assigneeLabel}`);
|
|
265460
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
|
+
}
|
|
265461
265754
|
const includeMatches = bucket.indicator.filter.some((m2) => markerMatches(issue2, m2));
|
|
265462
265755
|
if (!includeMatches) {
|
|
265463
265756
|
const want = bucket.indicator.filter.map((m2) => `${m2.type}:${m2.value}`).join(" OR ");
|
|
@@ -265565,7 +265858,10 @@ async function runAgentJson({
|
|
|
265565
265858
|
process.exitCode = 1;
|
|
265566
265859
|
return;
|
|
265567
265860
|
}
|
|
265568
|
-
const pf = await runPreflight2(
|
|
265861
|
+
const pf = await runPreflight2({
|
|
265862
|
+
requireRepoWrite: args.createPr || cfg.createPrOnSuccess,
|
|
265863
|
+
repoCwd: projectRoot
|
|
265864
|
+
});
|
|
265569
265865
|
if (!pf.ok) {
|
|
265570
265866
|
emit3({ type: "error", code: "auth_failure", tool: pf.tool, text: pf.message });
|
|
265571
265867
|
process.exitCode = 2;
|
|
@@ -265786,7 +266082,6 @@ async function main3(argv) {
|
|
|
265786
266082
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
265787
266083
|
await runList2({
|
|
265788
266084
|
linearTeamOverride: args.linearTeam,
|
|
265789
|
-
linearFilterOverride: args.linearFilter,
|
|
265790
266085
|
linearAssigneeOverride: args.linearAssignee,
|
|
265791
266086
|
debug: args.debug,
|
|
265792
266087
|
name: args.name,
|
|
@@ -265862,12 +266157,12 @@ async function main3(argv) {
|
|
|
265862
266157
|
return 0;
|
|
265863
266158
|
}
|
|
265864
266159
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
265865
|
-
const { waitUntilExit } = render_default(
|
|
266160
|
+
const { waitUntilExit } = render_default(import_react64.createElement(AgentMode, { args, projectRoot, statesDir, tasksDir }));
|
|
265866
266161
|
await waitUntilExit();
|
|
265867
266162
|
});
|
|
265868
266163
|
return typeof process.exitCode === "number" ? process.exitCode : 0;
|
|
265869
266164
|
}
|
|
265870
|
-
var
|
|
266165
|
+
var import_react64;
|
|
265871
266166
|
var init_src8 = __esm(async () => {
|
|
265872
266167
|
init_context();
|
|
265873
266168
|
init_layout();
|
|
@@ -265878,7 +266173,7 @@ var init_src8 = __esm(async () => {
|
|
|
265878
266173
|
init_build2(),
|
|
265879
266174
|
init_AgentMode()
|
|
265880
266175
|
]);
|
|
265881
|
-
|
|
266176
|
+
import_react64 = __toESM(require_react(), 1);
|
|
265882
266177
|
});
|
|
265883
266178
|
|
|
265884
266179
|
// apps/shell/src/index.ts
|
|
@@ -265967,11 +266262,21 @@ ${HELP}
|
|
|
265967
266262
|
capture("command_run", { subcommand });
|
|
265968
266263
|
bus.emit({ type: "command_run", subcommand });
|
|
265969
266264
|
try {
|
|
265970
|
-
if (
|
|
266265
|
+
if (CONFIG_SUBCOMMANDS.has(subcommand)) {
|
|
265971
266266
|
try {
|
|
265972
|
-
const { maybeRunSetupWizard: maybeRunSetupWizard2 } = await init_src4().then(() => exports_src);
|
|
266267
|
+
const { maybeRunSetupWizard: maybeRunSetupWizard2, maybeUpgradeWorkflow: maybeUpgradeWorkflow2 } = await init_src4().then(() => exports_src);
|
|
265973
266268
|
const { projectRoot, workflowFile } = parseWorkflowPathArgs(argv.slice(1));
|
|
265974
|
-
|
|
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
|
+
}
|
|
265975
266280
|
} catch (setupErr) {
|
|
265976
266281
|
captureError("setup_wizard_error", setupErr, { subcommand });
|
|
265977
266282
|
}
|