@neriros/ralphy 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -3
- package/dist/mcp/index.js +69 -2
- package/dist/shell/index.js +745 -209
- 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.
|
|
18932
|
-
return "3.
|
|
18931
|
+
if ("3.3.0")
|
|
18932
|
+
return "3.3.0";
|
|
18933
18933
|
} catch {}
|
|
18934
18934
|
const dirsToTry = [];
|
|
18935
18935
|
try {
|
|
@@ -59219,11 +59219,10 @@ var init_use_app = __esm(() => {
|
|
|
59219
59219
|
});
|
|
59220
59220
|
|
|
59221
59221
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/hooks/use-stdout.js
|
|
59222
|
-
var import_react18
|
|
59222
|
+
var import_react18;
|
|
59223
59223
|
var init_use_stdout = __esm(() => {
|
|
59224
59224
|
init_StdoutContext();
|
|
59225
59225
|
import_react18 = __toESM(require_react(), 1);
|
|
59226
|
-
use_stdout_default = useStdout;
|
|
59227
59226
|
});
|
|
59228
59227
|
|
|
59229
59228
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/hooks/use-stderr.js
|
|
@@ -59448,6 +59447,50 @@ function runOpenspec(args, options = {}) {
|
|
|
59448
59447
|
stderr: proc.stderr ? decoder.decode(proc.stderr) : ""
|
|
59449
59448
|
};
|
|
59450
59449
|
}
|
|
59450
|
+
function appendSteeringTaskToTasksMd(existing, taskLine) {
|
|
59451
|
+
const SECTION = "## Steering";
|
|
59452
|
+
const trimmed = existing.replace(/\s+$/, "");
|
|
59453
|
+
if (trimmed.length === 0) {
|
|
59454
|
+
return `${SECTION}
|
|
59455
|
+
|
|
59456
|
+
${taskLine}
|
|
59457
|
+
`;
|
|
59458
|
+
}
|
|
59459
|
+
const lines = trimmed.split(/\r?\n/);
|
|
59460
|
+
let sectionStart = -1;
|
|
59461
|
+
for (let i = 0;i < lines.length; i += 1) {
|
|
59462
|
+
if (/^##\s+Steering\s*$/i.test(lines[i])) {
|
|
59463
|
+
sectionStart = i;
|
|
59464
|
+
break;
|
|
59465
|
+
}
|
|
59466
|
+
}
|
|
59467
|
+
if (sectionStart === -1) {
|
|
59468
|
+
return `${trimmed}
|
|
59469
|
+
|
|
59470
|
+
${SECTION}
|
|
59471
|
+
|
|
59472
|
+
${taskLine}
|
|
59473
|
+
`;
|
|
59474
|
+
}
|
|
59475
|
+
let sectionEnd = lines.length;
|
|
59476
|
+
for (let i = sectionStart + 1;i < lines.length; i += 1) {
|
|
59477
|
+
if (/^##\s+/.test(lines[i])) {
|
|
59478
|
+
sectionEnd = i;
|
|
59479
|
+
break;
|
|
59480
|
+
}
|
|
59481
|
+
}
|
|
59482
|
+
let insertAt = sectionEnd;
|
|
59483
|
+
while (insertAt - 1 > sectionStart && (lines[insertAt - 1] ?? "").trim() === "") {
|
|
59484
|
+
insertAt -= 1;
|
|
59485
|
+
}
|
|
59486
|
+
const before2 = lines.slice(0, insertAt);
|
|
59487
|
+
const after2 = lines.slice(insertAt);
|
|
59488
|
+
const out = [...before2, taskLine, ...after2.length ? [""] : [], ...after2].join(`
|
|
59489
|
+
`);
|
|
59490
|
+
return out.endsWith(`
|
|
59491
|
+
`) ? out : `${out}
|
|
59492
|
+
`;
|
|
59493
|
+
}
|
|
59451
59494
|
|
|
59452
59495
|
class OpenSpecChangeStore {
|
|
59453
59496
|
async createChange(name, description) {
|
|
@@ -59504,6 +59547,16 @@ ${existing.trimStart()}` : `${message}
|
|
|
59504
59547
|
`;
|
|
59505
59548
|
await mkdir(dirname4(path), { recursive: true });
|
|
59506
59549
|
await Bun.write(path, updated);
|
|
59550
|
+
const firstLine = message.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0) ?? message.trim();
|
|
59551
|
+
if (firstLine.length === 0)
|
|
59552
|
+
return;
|
|
59553
|
+
const tasksPath = join6("openspec", "changes", name, "tasks.md");
|
|
59554
|
+
const tasksFile = Bun.file(tasksPath);
|
|
59555
|
+
const existingTasks = await tasksFile.exists() ? await tasksFile.text() : "";
|
|
59556
|
+
const taskLine = `- [ ] Address steering: ${firstLine}`;
|
|
59557
|
+
const next = appendSteeringTaskToTasksMd(existingTasks, taskLine);
|
|
59558
|
+
await mkdir(dirname4(tasksPath), { recursive: true });
|
|
59559
|
+
await Bun.write(tasksPath, next);
|
|
59507
59560
|
}
|
|
59508
59561
|
async readSection(name, artifact, heading) {
|
|
59509
59562
|
const file = Bun.file(join6("openspec", "changes", name, artifact));
|
|
@@ -63850,7 +63903,12 @@ var init_types2 = __esm(() => {
|
|
|
63850
63903
|
createPr: exports_external.boolean().default(false),
|
|
63851
63904
|
usage: UsageSchema.default({}),
|
|
63852
63905
|
history: exports_external.array(HistoryEntrySchema).default([]),
|
|
63853
|
-
metadata: exports_external.object({ branch: exports_external.string().optional() }).default({})
|
|
63906
|
+
metadata: exports_external.object({ branch: exports_external.string().optional() }).default({}),
|
|
63907
|
+
linearComments: exports_external.object({
|
|
63908
|
+
planCommentId: exports_external.string().nullable().default(null),
|
|
63909
|
+
tasksCommentId: exports_external.string().nullable().default(null),
|
|
63910
|
+
planPostedAt: exports_external.string().nullable().default(null)
|
|
63911
|
+
}).default({ planCommentId: null, tasksCommentId: null, planPostedAt: null })
|
|
63854
63912
|
});
|
|
63855
63913
|
PhaseFrontmatterSchema = exports_external.object({
|
|
63856
63914
|
name: exports_external.string(),
|
|
@@ -68802,21 +68860,26 @@ function readSize() {
|
|
|
68802
68860
|
rows: process.stdout.rows ?? 24
|
|
68803
68861
|
};
|
|
68804
68862
|
}
|
|
68863
|
+
function clearScreenAndScrollback() {
|
|
68864
|
+
if (process.stdout.isTTY)
|
|
68865
|
+
process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
68866
|
+
}
|
|
68805
68867
|
function useTerminalSize() {
|
|
68806
|
-
const
|
|
68807
|
-
|
|
68808
|
-
|
|
68809
|
-
}));
|
|
68868
|
+
const initial2 = import_react53.useRef({ ...readSize(), resizeKey: 0 });
|
|
68869
|
+
const [size2, setSize] = import_react53.useState(initial2.current);
|
|
68870
|
+
const sizeRef = import_react53.useRef(initial2.current);
|
|
68810
68871
|
import_react53.useEffect(() => {
|
|
68811
68872
|
if (!process.stdout.isTTY)
|
|
68812
68873
|
return;
|
|
68813
68874
|
const onResize = () => {
|
|
68814
68875
|
const { columns, rows } = readSize();
|
|
68815
|
-
|
|
68816
|
-
|
|
68817
|
-
|
|
68818
|
-
|
|
68819
|
-
}
|
|
68876
|
+
const prev = sizeRef.current;
|
|
68877
|
+
if (prev.columns === columns && prev.rows === rows)
|
|
68878
|
+
return;
|
|
68879
|
+
clearScreenAndScrollback();
|
|
68880
|
+
const next = { columns, rows, resizeKey: prev.resizeKey + 1 };
|
|
68881
|
+
sizeRef.current = next;
|
|
68882
|
+
setSize(next);
|
|
68820
68883
|
};
|
|
68821
68884
|
process.stdout.on("resize", onResize);
|
|
68822
68885
|
return () => {
|
|
@@ -70863,14 +70926,8 @@ function TaskLoop({ opts }) {
|
|
|
70863
70926
|
const { exit } = use_app_default();
|
|
70864
70927
|
const loop = useLoop(opts);
|
|
70865
70928
|
const { isRawModeSupported } = use_stdin_default();
|
|
70866
|
-
const { stdout } = use_stdout_default();
|
|
70867
70929
|
const { resizeKey } = useTerminalSize();
|
|
70868
70930
|
const bannerItem = import_react56.useRef({ id: "__banner__", kind: "banner" });
|
|
70869
|
-
import_react56.useEffect(() => {
|
|
70870
|
-
if (resizeKey === 0)
|
|
70871
|
-
return;
|
|
70872
|
-
stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
70873
|
-
}, [resizeKey, stdout]);
|
|
70874
70931
|
const feedItems = import_react56.useMemo(() => [
|
|
70875
70932
|
bannerItem.current,
|
|
70876
70933
|
...loop.logLines.map((e) => ({ id: e.id, kind: "entry", entry: e }))
|
|
@@ -92410,7 +92467,7 @@ var init_zod2 = __esm(() => {
|
|
|
92410
92467
|
});
|
|
92411
92468
|
|
|
92412
92469
|
// packages/workflow/src/schema.ts
|
|
92413
|
-
var MarkerSchema, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, ProjectSchema, CommandsSchema, BoundariesSchema, WorkflowConfigSchema;
|
|
92470
|
+
var MarkerSchema, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, ProjectSchema, CommandsSchema, DEFAULT_META_ONLY_FILES, BoundariesSchema, WorkflowConfigSchema;
|
|
92414
92471
|
var init_schema = __esm(() => {
|
|
92415
92472
|
init_zod2();
|
|
92416
92473
|
MarkerSchema = exports_external2.object({
|
|
@@ -92421,7 +92478,7 @@ var init_schema = __esm(() => {
|
|
|
92421
92478
|
filter: exports_external2.array(MarkerSchema).default([])
|
|
92422
92479
|
});
|
|
92423
92480
|
SetIndicatorSchema = exports_external2.union([exports_external2.array(MarkerSchema).min(1), MarkerSchema]);
|
|
92424
|
-
IndicatorsSchema = exports_external2.object({
|
|
92481
|
+
IndicatorsSchema = exports_external2.preprocess((v) => v == null ? {} : v, exports_external2.object({
|
|
92425
92482
|
getTodo: GetIndicatorSchema.optional(),
|
|
92426
92483
|
getInProgress: GetIndicatorSchema.optional(),
|
|
92427
92484
|
getConflicted: GetIndicatorSchema.optional(),
|
|
@@ -92450,7 +92507,7 @@ var init_schema = __esm(() => {
|
|
|
92450
92507
|
}
|
|
92451
92508
|
}
|
|
92452
92509
|
}
|
|
92453
|
-
});
|
|
92510
|
+
}));
|
|
92454
92511
|
ProjectSchema = exports_external2.object({
|
|
92455
92512
|
name: exports_external2.string().optional(),
|
|
92456
92513
|
language: exports_external2.string().optional(),
|
|
@@ -92462,9 +92519,17 @@ var init_schema = __esm(() => {
|
|
|
92462
92519
|
build: exports_external2.string().optional(),
|
|
92463
92520
|
typecheck: exports_external2.string().optional()
|
|
92464
92521
|
}).catchall(exports_external2.string()).default({});
|
|
92522
|
+
DEFAULT_META_ONLY_FILES = [
|
|
92523
|
+
"openspec/**",
|
|
92524
|
+
".ralph/**",
|
|
92525
|
+
"**/agent-tasks.md",
|
|
92526
|
+
"**/tasks.md",
|
|
92527
|
+
"**/MANUAL_TESTING*.md"
|
|
92528
|
+
];
|
|
92465
92529
|
BoundariesSchema = exports_external2.object({
|
|
92466
|
-
never_touch: exports_external2.array(exports_external2.string()).default([])
|
|
92467
|
-
|
|
92530
|
+
never_touch: exports_external2.array(exports_external2.string()).default([]),
|
|
92531
|
+
meta_only_files: exports_external2.array(exports_external2.string()).default(DEFAULT_META_ONLY_FILES)
|
|
92532
|
+
}).strict().default({ never_touch: [], meta_only_files: DEFAULT_META_ONLY_FILES });
|
|
92468
92533
|
WorkflowConfigSchema = exports_external2.object({
|
|
92469
92534
|
project: ProjectSchema,
|
|
92470
92535
|
commands: CommandsSchema,
|
|
@@ -92501,20 +92566,20 @@ var init_schema = __esm(() => {
|
|
|
92501
92566
|
assignee: exports_external2.string().optional(),
|
|
92502
92567
|
postComments: exports_external2.boolean().default(true),
|
|
92503
92568
|
updateEveryIterations: exports_external2.number().int().nonnegative().default(10),
|
|
92504
|
-
mentionTrigger: exports_external2.boolean().default(
|
|
92569
|
+
mentionTrigger: exports_external2.boolean().default(true),
|
|
92505
92570
|
mentionHandle: exports_external2.string().default("@ralphy"),
|
|
92506
|
-
codeReviewTrigger: exports_external2.boolean().default(
|
|
92571
|
+
codeReviewTrigger: exports_external2.boolean().default(true),
|
|
92507
92572
|
codeReviewStaleHours: exports_external2.number().nonnegative().default(24),
|
|
92508
|
-
|
|
92573
|
+
syncTasksToComment: exports_external2.boolean().default(true),
|
|
92509
92574
|
indicators: IndicatorsSchema.default({})
|
|
92510
92575
|
}).strict().default({
|
|
92511
92576
|
postComments: true,
|
|
92512
92577
|
updateEveryIterations: 10,
|
|
92513
|
-
mentionTrigger:
|
|
92578
|
+
mentionTrigger: true,
|
|
92514
92579
|
mentionHandle: "@ralphy",
|
|
92515
|
-
codeReviewTrigger:
|
|
92580
|
+
codeReviewTrigger: true,
|
|
92516
92581
|
codeReviewStaleHours: 24,
|
|
92517
|
-
|
|
92582
|
+
syncTasksToComment: true,
|
|
92518
92583
|
indicators: {}
|
|
92519
92584
|
}),
|
|
92520
92585
|
github: exports_external2.object({
|
|
@@ -92573,70 +92638,71 @@ boundaries:
|
|
|
92573
92638
|
never_touch:
|
|
92574
92639
|
- "dist/**"
|
|
92575
92640
|
- ".claude/worktrees/**"
|
|
92576
|
-
|
|
92641
|
+
# Files that count as "meta only" for the pre-PR substantive-diff guard.
|
|
92642
|
+
# If every changed file matches one of these globs, the loop refuses to
|
|
92643
|
+
# open the PR and respawns the worker \u2014 the actual implementation was
|
|
92644
|
+
# lost (either deleted mid-loop or absorbed by a merge from base).
|
|
92645
|
+
meta_only_files:
|
|
92646
|
+
- "openspec/**"
|
|
92647
|
+
- ".ralph/**"
|
|
92648
|
+
- "**/agent-tasks.md"
|
|
92649
|
+
- "**/tasks.md"
|
|
92650
|
+
- "**/MANUAL_TESTING*.md"
|
|
92651
|
+
|
|
92652
|
+
# \u2500\u2500\u2500 Scheduling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92577
92653
|
# How many tasks to run in parallel.
|
|
92578
92654
|
concurrency: 1
|
|
92579
|
-
|
|
92580
92655
|
# Seconds between polls for new Linear issues (agent mode).
|
|
92581
92656
|
pollIntervalSeconds: 60
|
|
92657
|
+
# Seconds to wait between loop iterations (throttle).
|
|
92658
|
+
iterationDelaySeconds: 0
|
|
92582
92659
|
|
|
92583
|
-
#
|
|
92660
|
+
# \u2500\u2500\u2500 Per-task limits (0 = unlimited) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92584
92661
|
maxIterationsPerTask: 0
|
|
92585
|
-
|
|
92586
|
-
# Maximum cost in USD per task. 0 = unlimited.
|
|
92587
92662
|
maxCostUsdPerTask: 0
|
|
92588
|
-
|
|
92589
|
-
# Maximum wall-clock minutes per task. 0 = unlimited.
|
|
92590
92663
|
maxRuntimeMinutesPerTask: 0
|
|
92591
|
-
|
|
92592
92664
|
# Stop a task after this many consecutive identical failures.
|
|
92593
92665
|
maxConsecutiveFailuresPerTask: 5
|
|
92594
92666
|
|
|
92595
|
-
#
|
|
92596
|
-
|
|
92597
|
-
|
|
92667
|
+
# \u2500\u2500\u2500 Engine \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92668
|
+
# Underlying engine: "claude" or "codex".
|
|
92669
|
+
engine: claude
|
|
92670
|
+
# Model tier: "haiku", "sonnet", or "opus".
|
|
92671
|
+
model: opus
|
|
92598
92672
|
# Log the raw engine stream to stdout.
|
|
92599
92673
|
logRawStream: false
|
|
92600
|
-
|
|
92601
92674
|
# Pass --verbose to the ralph task sub-process.
|
|
92602
92675
|
taskVerbose: false
|
|
92603
92676
|
|
|
92677
|
+
# \u2500\u2500\u2500 Worktree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92604
92678
|
# Run each task in an isolated git worktree.
|
|
92605
92679
|
useWorktree: false
|
|
92606
|
-
|
|
92607
92680
|
# Delete the worktree after a successful task.
|
|
92608
92681
|
cleanupWorktreeOnSuccess: false
|
|
92609
92682
|
|
|
92683
|
+
# \u2500\u2500\u2500 Pull requests \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92610
92684
|
# Open a pull request after a task succeeds.
|
|
92611
92685
|
createPrOnSuccess: false
|
|
92612
|
-
|
|
92613
92686
|
# Base branch for pull requests.
|
|
92614
92687
|
prBaseBranch: main
|
|
92615
|
-
|
|
92616
92688
|
# When true, stack dependent issues' PRs onto their blocker's open PR.
|
|
92617
92689
|
stackPrsOnDependencies: false
|
|
92618
|
-
|
|
92619
92690
|
# Strategy used when GitHub auto-merge is enabled.
|
|
92620
92691
|
autoMergeStrategy: squash
|
|
92621
92692
|
|
|
92693
|
+
# \u2500\u2500\u2500 CI auto-fix \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92622
92694
|
# Let the agent attempt to fix CI failures after a PR is created.
|
|
92623
92695
|
fixCiOnFailure: false
|
|
92624
|
-
|
|
92625
92696
|
# Maximum number of CI-fix attempts per task.
|
|
92626
92697
|
maxCiFixAttempts: 5
|
|
92627
|
-
|
|
92628
92698
|
# Seconds between CI status polls.
|
|
92629
92699
|
ciPollIntervalSeconds: 30
|
|
92630
92700
|
|
|
92631
|
-
#
|
|
92632
|
-
|
|
92633
|
-
|
|
92634
|
-
#
|
|
92635
|
-
|
|
92636
|
-
|
|
92637
|
-
# Pre-existing error check: gate the agent when the base branch is already broken.
|
|
92638
|
-
# When enabled, the agent runs these commands against the base branch HEAD before
|
|
92639
|
-
# scheduling new work; failures open a Linear ticket and pause new pickups.
|
|
92701
|
+
# \u2500\u2500\u2500 Base-branch health gate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92702
|
+
# Pre-existing error check: gate the agent when the base branch is already
|
|
92703
|
+
# broken. When enabled, the agent runs these commands against the base
|
|
92704
|
+
# branch HEAD before scheduling new work; failures open a Linear ticket
|
|
92705
|
+
# and pause new pickups.
|
|
92640
92706
|
preExistingErrorCheck:
|
|
92641
92707
|
enabled: false
|
|
92642
92708
|
# Commands to run against the base branch. When empty, falls back to commands.lint / commands.test.
|
|
@@ -92645,38 +92711,49 @@ preExistingErrorCheck:
|
|
|
92645
92711
|
label: "ralph:pre-existing-error"
|
|
92646
92712
|
outputCharLimit: 4000
|
|
92647
92713
|
|
|
92714
|
+
# \u2500\u2500\u2500 Linear integration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92648
92715
|
linear:
|
|
92649
92716
|
# Linear team key (e.g. "ENG"). Omit to match all teams.
|
|
92650
92717
|
# team: ENG
|
|
92651
92718
|
|
|
92652
92719
|
# Post progress comments on the Linear issue while a task is running.
|
|
92653
92720
|
postComments: true
|
|
92654
|
-
|
|
92655
92721
|
# Post a progress update every N iterations. 0 disables.
|
|
92656
92722
|
updateEveryIterations: 10
|
|
92657
92723
|
|
|
92658
92724
|
# Watch done-issue comments + linked GitHub PR comments for @ralphy mentions.
|
|
92659
|
-
mentionTrigger:
|
|
92725
|
+
mentionTrigger: true
|
|
92660
92726
|
mentionHandle: "@ralphy"
|
|
92661
92727
|
|
|
92662
92728
|
# Watch open tracked PRs for unresolved review-thread comments.
|
|
92663
|
-
codeReviewTrigger:
|
|
92729
|
+
codeReviewTrigger: true
|
|
92664
92730
|
codeReviewStaleHours: 24
|
|
92665
92731
|
|
|
92666
|
-
# Mirror the loop's tasks.md into
|
|
92667
|
-
#
|
|
92668
|
-
#
|
|
92669
|
-
|
|
92732
|
+
# Mirror the loop's tasks.md into a sticky Linear comment (always the
|
|
92733
|
+
# last comment on the issue). Updates on worker launch, on the same
|
|
92734
|
+
# cadence as updateEveryIterations, and on done-transition.
|
|
92735
|
+
syncTasksToComment: true
|
|
92670
92736
|
|
|
92671
92737
|
# Indicators map Ralph lifecycle events to Linear labels/statuses.
|
|
92672
|
-
#
|
|
92673
|
-
#
|
|
92674
|
-
|
|
92675
|
-
|
|
92738
|
+
#
|
|
92739
|
+
# Filter semantics (per indicator's \`filter:\` list):
|
|
92740
|
+
# \u2022 Entries of the SAME type (e.g. two \`status\` entries) are ORed
|
|
92741
|
+
# \u2014 the issue matches if any value matches.
|
|
92742
|
+
# \u2022 Entries of DIFFERENT types (one \`status\` + one \`label\`) are
|
|
92743
|
+
# ANDed \u2014 the issue must satisfy every type.
|
|
92744
|
+
# Example: a filter with two statuses + one label matches issues
|
|
92745
|
+
# where status \u2208 {A, B} AND label = L.
|
|
92746
|
+
#
|
|
92747
|
+
# Sections below group one state at a time; its get/set/clear sit
|
|
92748
|
+
# adjacent so the lifecycle reads top-to-bottom.
|
|
92749
|
+
indicators:
|
|
92750
|
+
# \u2500\u2500 Todo (pickup trigger) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92676
92751
|
# getTodo:
|
|
92677
92752
|
# filter:
|
|
92678
92753
|
# - type: status
|
|
92679
92754
|
# value: Todo
|
|
92755
|
+
#
|
|
92756
|
+
# \u2500\u2500 In Progress \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92680
92757
|
# getInProgress:
|
|
92681
92758
|
# filter:
|
|
92682
92759
|
# - type: status
|
|
@@ -92685,7 +92762,7 @@ linear:
|
|
|
92685
92762
|
# type: status
|
|
92686
92763
|
# value: In Progress
|
|
92687
92764
|
#
|
|
92688
|
-
#
|
|
92765
|
+
# \u2500\u2500 Done \u2192 Review hand-off \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92689
92766
|
# setDone:
|
|
92690
92767
|
# type: status
|
|
92691
92768
|
# value: In Review
|
|
@@ -92697,7 +92774,7 @@ linear:
|
|
|
92697
92774
|
# type: label
|
|
92698
92775
|
# value: "ralph:review"
|
|
92699
92776
|
#
|
|
92700
|
-
#
|
|
92777
|
+
# \u2500\u2500 Conflicted \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92701
92778
|
# getConflicted:
|
|
92702
92779
|
# filter:
|
|
92703
92780
|
# - type: label
|
|
@@ -92709,13 +92786,13 @@ linear:
|
|
|
92709
92786
|
# type: label
|
|
92710
92787
|
# value: "ralph:conflict"
|
|
92711
92788
|
#
|
|
92712
|
-
#
|
|
92789
|
+
# \u2500\u2500 Auto-merge (opt-in) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92713
92790
|
# getAutoMerge:
|
|
92714
92791
|
# filter:
|
|
92715
92792
|
# - type: label
|
|
92716
92793
|
# value: "ralph:auto-merge"
|
|
92717
92794
|
#
|
|
92718
|
-
#
|
|
92795
|
+
# \u2500\u2500 Error quarantine \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
92719
92796
|
# setError:
|
|
92720
92797
|
# type: label
|
|
92721
92798
|
# value: "ralph:error"
|
|
@@ -93693,28 +93770,102 @@ async function fetchOpenIssues(apiKey, spec) {
|
|
|
93693
93770
|
blockedByIds: (n.relations?.nodes ?? []).filter((r) => r.type === "blocked_by" && !DONE_STATE_TYPES.has(r.relatedIssue.state.type)).map((r) => r.relatedIssue.id)
|
|
93694
93771
|
}));
|
|
93695
93772
|
}
|
|
93773
|
+
function isRetryableStatus(status) {
|
|
93774
|
+
return status >= 500 && status <= 599;
|
|
93775
|
+
}
|
|
93776
|
+
function parseRetryAfter(header) {
|
|
93777
|
+
if (!header)
|
|
93778
|
+
return;
|
|
93779
|
+
const trimmed = header.trim();
|
|
93780
|
+
if (!trimmed)
|
|
93781
|
+
return;
|
|
93782
|
+
const asNum = Number(trimmed);
|
|
93783
|
+
if (Number.isFinite(asNum))
|
|
93784
|
+
return Math.max(0, asNum * 1000);
|
|
93785
|
+
const asDate = Date.parse(trimmed);
|
|
93786
|
+
if (Number.isFinite(asDate))
|
|
93787
|
+
return Math.max(0, asDate - Date.now());
|
|
93788
|
+
return;
|
|
93789
|
+
}
|
|
93790
|
+
function backoffMs(attempt2) {
|
|
93791
|
+
const base2 = 250 * 2 ** (attempt2 - 1);
|
|
93792
|
+
const jitter = Math.floor(Math.random() * 100);
|
|
93793
|
+
return base2 + jitter;
|
|
93794
|
+
}
|
|
93795
|
+
function isRateLimitedBody(body) {
|
|
93796
|
+
if (typeof body !== "string" || body.length === 0)
|
|
93797
|
+
return false;
|
|
93798
|
+
return body.toLowerCase().includes("rate limit exceeded");
|
|
93799
|
+
}
|
|
93800
|
+
function isRateLimitedError(err) {
|
|
93801
|
+
if (err === null || typeof err !== "object")
|
|
93802
|
+
return false;
|
|
93803
|
+
return err.rateLimited === true;
|
|
93804
|
+
}
|
|
93805
|
+
function formatLinearError(err) {
|
|
93806
|
+
if (err === null || err === undefined)
|
|
93807
|
+
return String(err);
|
|
93808
|
+
if (typeof err !== "object")
|
|
93809
|
+
return String(err);
|
|
93810
|
+
const e = err;
|
|
93811
|
+
const parts = [];
|
|
93812
|
+
if (e.rateLimited)
|
|
93813
|
+
parts.push("rate limited");
|
|
93814
|
+
if (typeof e.status === "number")
|
|
93815
|
+
parts.push(`HTTP ${e.status}`);
|
|
93816
|
+
if (Array.isArray(e.messages) && e.messages.length > 0) {
|
|
93817
|
+
parts.push(`graphql: ${e.messages.join("; ")}`);
|
|
93818
|
+
}
|
|
93819
|
+
if (typeof e.body === "string" && e.body.length > 0 && !e.rateLimited) {
|
|
93820
|
+
const truncated = e.body.length > 200 ? `${e.body.slice(0, 200)}\u2026` : e.body;
|
|
93821
|
+
parts.push(`body: ${truncated}`);
|
|
93822
|
+
}
|
|
93823
|
+
if (parts.length === 0) {
|
|
93824
|
+
if (typeof e.message === "string" && e.message)
|
|
93825
|
+
return e.message;
|
|
93826
|
+
return String(err);
|
|
93827
|
+
}
|
|
93828
|
+
if (typeof e.message === "string" && e.message && !e.rateLimited)
|
|
93829
|
+
parts.unshift(e.message);
|
|
93830
|
+
return parts.join(" \u2014 ");
|
|
93831
|
+
}
|
|
93696
93832
|
async function linearRequest(apiKey, query, variables) {
|
|
93697
|
-
|
|
93698
|
-
|
|
93699
|
-
|
|
93700
|
-
|
|
93701
|
-
|
|
93702
|
-
|
|
93703
|
-
|
|
93704
|
-
|
|
93705
|
-
|
|
93706
|
-
|
|
93707
|
-
|
|
93708
|
-
|
|
93709
|
-
|
|
93710
|
-
|
|
93711
|
-
|
|
93712
|
-
|
|
93713
|
-
|
|
93714
|
-
|
|
93715
|
-
|
|
93833
|
+
let lastHttpError;
|
|
93834
|
+
for (let attempt2 = 1;attempt2 <= MAX_LINEAR_ATTEMPTS; attempt2++) {
|
|
93835
|
+
const res = await fetch(LINEAR_API, {
|
|
93836
|
+
method: "POST",
|
|
93837
|
+
headers: { "Content-Type": "application/json", Authorization: apiKey },
|
|
93838
|
+
body: JSON.stringify({ query, variables })
|
|
93839
|
+
});
|
|
93840
|
+
if (!res.ok) {
|
|
93841
|
+
const err = new Error("Linear API request failed");
|
|
93842
|
+
err.status = res.status;
|
|
93843
|
+
err.body = await res.text();
|
|
93844
|
+
if (res.status === 429 || isRateLimitedBody(err.body)) {
|
|
93845
|
+
err.rateLimited = true;
|
|
93846
|
+
throw err;
|
|
93847
|
+
}
|
|
93848
|
+
lastHttpError = err;
|
|
93849
|
+
if (isRetryableStatus(res.status) && attempt2 < MAX_LINEAR_ATTEMPTS) {
|
|
93850
|
+
const ra = parseRetryAfter(res.headers.get("Retry-After"));
|
|
93851
|
+
const waitMs = Math.min(ra ?? backoffMs(attempt2), MAX_RETRY_AFTER_MS);
|
|
93852
|
+
await linearRequestInternals.sleep(waitMs);
|
|
93853
|
+
continue;
|
|
93854
|
+
}
|
|
93855
|
+
throw err;
|
|
93856
|
+
}
|
|
93857
|
+
const json2 = await res.json();
|
|
93858
|
+
if (json2.errors?.length) {
|
|
93859
|
+
const err = new Error("Linear API returned errors");
|
|
93860
|
+
err.messages = json2.errors.map((e) => e.message);
|
|
93861
|
+
throw err;
|
|
93862
|
+
}
|
|
93863
|
+
if (!json2.data) {
|
|
93864
|
+
throw new Error("Linear API returned no data");
|
|
93865
|
+
}
|
|
93866
|
+
return json2.data;
|
|
93716
93867
|
}
|
|
93717
|
-
|
|
93868
|
+
throw lastHttpError ?? new Error("Linear API request failed");
|
|
93718
93869
|
}
|
|
93719
93870
|
async function addReactionToComment(apiKey, commentId, emoji3) {
|
|
93720
93871
|
const mutation = `mutation Reaction($commentId: String!, $emoji: String!) {
|
|
@@ -93734,6 +93885,36 @@ async function addIssueComment(apiKey, issueId, body) {
|
|
|
93734
93885
|
body
|
|
93735
93886
|
});
|
|
93736
93887
|
}
|
|
93888
|
+
async function createIssueComment(apiKey, issueId, body) {
|
|
93889
|
+
const mutation = `mutation Comment($issueId: String!, $body: String!) {
|
|
93890
|
+
commentCreate(input: { issueId: $issueId, body: $body }) {
|
|
93891
|
+
success
|
|
93892
|
+
comment { id }
|
|
93893
|
+
}
|
|
93894
|
+
}`;
|
|
93895
|
+
const data = await linearRequest(apiKey, mutation, { issueId, body });
|
|
93896
|
+
const id = data.commentCreate.comment?.id;
|
|
93897
|
+
if (!id)
|
|
93898
|
+
throw new Error("commentCreate returned no comment id");
|
|
93899
|
+
return id;
|
|
93900
|
+
}
|
|
93901
|
+
async function updateIssueComment(apiKey, commentId, body) {
|
|
93902
|
+
const mutation = `mutation UpdateComment($id: String!, $body: String!) {
|
|
93903
|
+
commentUpdate(id: $id, input: { body: $body }) { success }
|
|
93904
|
+
}`;
|
|
93905
|
+
await linearRequest(apiKey, mutation, {
|
|
93906
|
+
id: commentId,
|
|
93907
|
+
body
|
|
93908
|
+
});
|
|
93909
|
+
}
|
|
93910
|
+
async function deleteIssueComment(apiKey, commentId) {
|
|
93911
|
+
const mutation = `mutation DeleteComment($id: String!) {
|
|
93912
|
+
commentDelete(id: $id) { success }
|
|
93913
|
+
}`;
|
|
93914
|
+
await linearRequest(apiKey, mutation, {
|
|
93915
|
+
id: commentId
|
|
93916
|
+
});
|
|
93917
|
+
}
|
|
93737
93918
|
async function fetchIssueComments(apiKey, issueId) {
|
|
93738
93919
|
const query = `query Comments($id: String!) {
|
|
93739
93920
|
issue(id: $id) {
|
|
@@ -93956,7 +94137,12 @@ async function removeLabelFromIssue(apiKey, issueId, labelId) {
|
|
|
93956
94137
|
labelId
|
|
93957
94138
|
});
|
|
93958
94139
|
}
|
|
93959
|
-
var LINEAR_API = "https://api.linear.app/graphql", RALPHY_ATTACHMENT_TITLE_FILTER = "Ralphy", RALPHY_ATTACHMENT_TITLE = "Ralphy", BRANCH_LABEL_PREFIX = "ralph:branch:";
|
|
94140
|
+
var LINEAR_API = "https://api.linear.app/graphql", RALPHY_ATTACHMENT_TITLE_FILTER = "Ralphy", linearRequestInternals, MAX_LINEAR_ATTEMPTS = 3, MAX_RETRY_AFTER_MS = 2000, RALPHY_ATTACHMENT_TITLE = "Ralphy", BRANCH_LABEL_PREFIX = "ralph:branch:";
|
|
94141
|
+
var init_linear = __esm(() => {
|
|
94142
|
+
linearRequestInternals = {
|
|
94143
|
+
sleep: (ms) => Bun.sleep(ms)
|
|
94144
|
+
};
|
|
94145
|
+
});
|
|
93960
94146
|
|
|
93961
94147
|
// apps/agent/src/sort/compare.ts
|
|
93962
94148
|
function chain(...comparators) {
|
|
@@ -93987,6 +94173,7 @@ function compareQueueEntries(getAutoMerge) {
|
|
|
93987
94173
|
}
|
|
93988
94174
|
var MODE_RANK;
|
|
93989
94175
|
var init_queue_order = __esm(() => {
|
|
94176
|
+
init_linear();
|
|
93990
94177
|
MODE_RANK = {
|
|
93991
94178
|
resume: 0,
|
|
93992
94179
|
"conflict-fix": 1,
|
|
@@ -94428,6 +94615,15 @@ class AgentCoordinator {
|
|
|
94428
94615
|
} catch {}
|
|
94429
94616
|
return true;
|
|
94430
94617
|
}
|
|
94618
|
+
async notifySteeringAppended(changeName, message) {
|
|
94619
|
+
if (!this.deps.onSteeringAppended)
|
|
94620
|
+
return;
|
|
94621
|
+
try {
|
|
94622
|
+
await this.deps.onSteeringAppended(changeName, message);
|
|
94623
|
+
} catch (err) {
|
|
94624
|
+
this.deps.onLog(`! onSteeringAppended failed for ${changeName}: ${err.message}`, "yellow");
|
|
94625
|
+
}
|
|
94626
|
+
}
|
|
94431
94627
|
async notifyExited(issue2, changeName, code, mode) {
|
|
94432
94628
|
const ok = code === 0;
|
|
94433
94629
|
if (this.deps.syncTasks && ok) {
|
|
@@ -94537,6 +94733,7 @@ var emptyPrStatus = () => ({ mergeable: 0, conflicted: 0, ciFailed: 0 }), emptyP
|
|
|
94537
94733
|
prStatus: emptyPrStatus()
|
|
94538
94734
|
});
|
|
94539
94735
|
var init_coordinator = __esm(() => {
|
|
94736
|
+
init_linear();
|
|
94540
94737
|
init_queue_order();
|
|
94541
94738
|
init_src();
|
|
94542
94739
|
});
|
|
@@ -94905,11 +95102,49 @@ ${issue2.description.trim()}` : ""
|
|
|
94905
95102
|
].filter(Boolean).join(`
|
|
94906
95103
|
`);
|
|
94907
95104
|
}
|
|
95105
|
+
async function diffFilesAgainstBase(runner, cwd2, base2) {
|
|
95106
|
+
let raw = "";
|
|
95107
|
+
try {
|
|
95108
|
+
const r = await runner.run(["git", "diff", "--name-only", `origin/${base2}...HEAD`], cwd2);
|
|
95109
|
+
raw = r.stdout;
|
|
95110
|
+
} catch {
|
|
95111
|
+
try {
|
|
95112
|
+
const r = await runner.run(["git", "diff", "--name-only", `${base2}...HEAD`], cwd2);
|
|
95113
|
+
raw = r.stdout;
|
|
95114
|
+
} catch {
|
|
95115
|
+
return [];
|
|
95116
|
+
}
|
|
95117
|
+
}
|
|
95118
|
+
return raw.split(`
|
|
95119
|
+
`).map((s) => s.trim()).filter(Boolean);
|
|
95120
|
+
}
|
|
95121
|
+
async function classifyDiffAgainstMeta(runner, cwd2, base2, metaOnlyFiles) {
|
|
95122
|
+
const files = await diffFilesAgainstBase(runner, cwd2, base2);
|
|
95123
|
+
if (files.length === 0 || metaOnlyFiles.length === 0) {
|
|
95124
|
+
return { files, onlyMeta: false };
|
|
95125
|
+
}
|
|
95126
|
+
const violations = findBoundaryViolations(files, metaOnlyFiles);
|
|
95127
|
+
const metaSet = new Set(violations.map((v) => v.file));
|
|
95128
|
+
const onlyMeta = files.every((f2) => metaSet.has(f2.replace(/\\/g, "/")));
|
|
95129
|
+
return { files, onlyMeta };
|
|
95130
|
+
}
|
|
94908
95131
|
async function createPullRequest(input, runner) {
|
|
94909
95132
|
const base2 = input.base ?? "main";
|
|
94910
95133
|
const log2 = await runner.run(["git", "log", "--oneline", `${base2}..HEAD`, "--no-merges"], input.cwd);
|
|
94911
95134
|
if (log2.stdout.trim() === "")
|
|
94912
95135
|
return null;
|
|
95136
|
+
const metaOnlyFiles = input.metaOnlyFiles ?? [];
|
|
95137
|
+
if (metaOnlyFiles.length > 0) {
|
|
95138
|
+
const classification = await classifyDiffAgainstMeta(runner, input.cwd, base2, metaOnlyFiles);
|
|
95139
|
+
if (classification.onlyMeta && classification.files.length > 0) {
|
|
95140
|
+
return {
|
|
95141
|
+
url: null,
|
|
95142
|
+
created: false,
|
|
95143
|
+
blocked: "only-meta",
|
|
95144
|
+
blockedFiles: classification.files
|
|
95145
|
+
};
|
|
95146
|
+
}
|
|
95147
|
+
}
|
|
94913
95148
|
await runner.run(["git", "push", "-u", "origin", input.branch], input.cwd);
|
|
94914
95149
|
const existing = await runner.run([
|
|
94915
95150
|
"gh",
|
|
@@ -94934,6 +95169,7 @@ async function createPullRequest(input, runner) {
|
|
|
94934
95169
|
`).pop() ?? "";
|
|
94935
95170
|
return { url: url2, created: true };
|
|
94936
95171
|
}
|
|
95172
|
+
var init_pr = () => {};
|
|
94937
95173
|
|
|
94938
95174
|
// apps/agent/src/agent/post-task.ts
|
|
94939
95175
|
import { join as join20 } from "path";
|
|
@@ -95046,7 +95282,13 @@ async function createPrWithRetry(ctx, issue2) {
|
|
|
95046
95282
|
while (true) {
|
|
95047
95283
|
try {
|
|
95048
95284
|
ctx.emit("pr-create", "git push + gh pr create");
|
|
95049
|
-
pr = await createPullRequest({
|
|
95285
|
+
pr = await createPullRequest({
|
|
95286
|
+
cwd: ctx.cwd,
|
|
95287
|
+
branch: ctx.branch,
|
|
95288
|
+
issue: issue2,
|
|
95289
|
+
base: base2,
|
|
95290
|
+
metaOnlyFiles: ctx.cfg.metaOnlyFiles ?? []
|
|
95291
|
+
}, ctx.cmd);
|
|
95050
95292
|
return { pr, gaveUp: false };
|
|
95051
95293
|
} catch (err) {
|
|
95052
95294
|
const e = err;
|
|
@@ -95312,49 +95554,97 @@ ${indented}${suffix}`, "yellow");
|
|
|
95312
95554
|
}
|
|
95313
95555
|
return PR_FAILED_EXIT;
|
|
95314
95556
|
}
|
|
95315
|
-
const
|
|
95316
|
-
|
|
95317
|
-
|
|
95557
|
+
const maxOuterAttempts = cfg.maxCiFixAttempts;
|
|
95558
|
+
let onlyMetaAttempts = 0;
|
|
95559
|
+
let pr = null;
|
|
95560
|
+
while (true) {
|
|
95561
|
+
const attempt2 = await createPrWithRetry(ctx, issue2);
|
|
95562
|
+
if (attempt2.gaveUp)
|
|
95563
|
+
return PR_FAILED_EXIT;
|
|
95564
|
+
if (attempt2.pr?.blocked === "only-meta") {
|
|
95565
|
+
onlyMetaAttempts += 1;
|
|
95566
|
+
const files = attempt2.pr.blockedFiles ?? [];
|
|
95567
|
+
emit("pr-only-meta", `${files.length} meta file(s)`);
|
|
95568
|
+
log2(`! ${changeName}: branch diff against ${base2} contains only meta files \u2014 implementation appears lost. Refusing to open PR.`, "red");
|
|
95569
|
+
for (const f2 of files)
|
|
95570
|
+
log2(` ${f2}`, "red");
|
|
95571
|
+
if (onlyMetaAttempts > maxOuterAttempts) {
|
|
95572
|
+
log2(`! exceeded ${maxOuterAttempts} only-meta recovery attempts for ${changeName} \u2014 giving up`, "red");
|
|
95573
|
+
return PR_FAILED_EXIT;
|
|
95574
|
+
}
|
|
95575
|
+
const fileList = files.length > 0 ? files.map((f2) => `- ${f2}`).join(`
|
|
95576
|
+
`) : "(empty diff)";
|
|
95577
|
+
const retryCode = await runWorkerWithFixTask(ctx, "Reapply lost implementation files", [
|
|
95578
|
+
`The diff against \`${base2}\` contains only meta files`,
|
|
95579
|
+
`(openspec/tasks.md and similar). The substantive implementation`,
|
|
95580
|
+
`is missing from the branch \u2014 likely deleted by an earlier commit`,
|
|
95581
|
+
`or absorbed by a merge from origin/${base2}.`,
|
|
95582
|
+
"",
|
|
95583
|
+
`Files currently in the diff:`,
|
|
95584
|
+
fileList,
|
|
95585
|
+
"",
|
|
95586
|
+
`Re-apply the actual implementation work the change is supposed`,
|
|
95587
|
+
`to ship. Inspect git history (\`git log ${base2}..HEAD\`) to see`,
|
|
95588
|
+
`what was created earlier and lost, then restore those files`,
|
|
95589
|
+
`(or reproduce the work). Commit the restored files so the next`,
|
|
95590
|
+
`iteration's diff against \`${base2}\` contains real code, not`,
|
|
95591
|
+
`just meta files.`
|
|
95592
|
+
].join(`
|
|
95593
|
+
`));
|
|
95594
|
+
if (retryCode !== 0) {
|
|
95595
|
+
log2(`! worker re-run after only-meta block exited code ${retryCode} \u2014 giving up`, "red");
|
|
95596
|
+
return PR_FAILED_EXIT;
|
|
95597
|
+
}
|
|
95598
|
+
continue;
|
|
95599
|
+
}
|
|
95600
|
+
pr = attempt2.pr;
|
|
95601
|
+
break;
|
|
95602
|
+
}
|
|
95318
95603
|
if (!pr) {
|
|
95319
95604
|
log2(` no commits ahead of ${base2} \u2014 skipping PR`, "gray");
|
|
95320
95605
|
return 0;
|
|
95321
95606
|
}
|
|
95322
|
-
|
|
95323
|
-
|
|
95607
|
+
const prUrl = pr.url;
|
|
95608
|
+
if (!prUrl) {
|
|
95609
|
+
log2(`! PR creation returned a null URL for ${changeName} \u2014 giving up`, "red");
|
|
95610
|
+
return PR_FAILED_EXIT;
|
|
95611
|
+
}
|
|
95612
|
+
log2(` ${pr.created ? "opened" : "found existing"} PR: ${prUrl}`, "green");
|
|
95613
|
+
registerPr?.(changeName, prUrl);
|
|
95324
95614
|
let manualMergePending = false;
|
|
95325
95615
|
if (wantAutoMerge) {
|
|
95326
95616
|
const fallbackEnabled = cfg.manualMergeWhenAutoMergeDisabled !== false;
|
|
95327
|
-
const repoAllowsAutoMerge = await detectRepoAutoMergeAllowed(
|
|
95617
|
+
const repoAllowsAutoMerge = await detectRepoAutoMergeAllowed(prUrl, cmd, cwd2, log2);
|
|
95328
95618
|
if (repoAllowsAutoMerge === false && fallbackEnabled) {
|
|
95329
|
-
log2(` repo has auto-merge disabled \u2014 will poll ${
|
|
95619
|
+
log2(` repo has auto-merge disabled \u2014 will poll ${prUrl} and merge via gh pr merge once checks pass`, "yellow");
|
|
95330
95620
|
manualMergePending = true;
|
|
95331
95621
|
} else {
|
|
95332
95622
|
try {
|
|
95333
|
-
await cmd.run(["gh", "pr", "merge",
|
|
95334
|
-
log2(` enabled auto-merge (${cfg.autoMergeStrategy}) on ${
|
|
95623
|
+
await cmd.run(["gh", "pr", "merge", prUrl, "--auto", `--${cfg.autoMergeStrategy}`], cwd2);
|
|
95624
|
+
log2(` enabled auto-merge (${cfg.autoMergeStrategy}) on ${prUrl}`, "green");
|
|
95335
95625
|
emit("auto-merge-enabled", cfg.autoMergeStrategy);
|
|
95336
95626
|
} catch (err) {
|
|
95337
95627
|
const e = err;
|
|
95338
95628
|
const detail = e.stderr?.trim() || e.message;
|
|
95339
|
-
log2(`! failed to enable auto-merge on ${
|
|
95629
|
+
log2(`! failed to enable auto-merge on ${prUrl}: ${detail}`, "yellow");
|
|
95340
95630
|
if (fallbackEnabled && /auto[- ]merge/i.test(detail)) {
|
|
95341
|
-
log2(` falling back to manual merge after CI passes for ${
|
|
95631
|
+
log2(` falling back to manual merge after CI passes for ${prUrl}`, "yellow");
|
|
95342
95632
|
manualMergePending = true;
|
|
95343
95633
|
}
|
|
95344
95634
|
}
|
|
95345
95635
|
}
|
|
95346
95636
|
}
|
|
95347
|
-
const ciResult = await fixConflictsAndCiLoop(ctx,
|
|
95637
|
+
const ciResult = await fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict);
|
|
95348
95638
|
if (ciResult !== 0)
|
|
95349
95639
|
return ciResult;
|
|
95350
95640
|
if (manualMergePending) {
|
|
95351
95641
|
try {
|
|
95352
|
-
await cmd.run(["gh", "pr", "merge",
|
|
95353
|
-
log2(` manually merged (${cfg.autoMergeStrategy}) ${
|
|
95642
|
+
await cmd.run(["gh", "pr", "merge", prUrl, `--${cfg.autoMergeStrategy}`], cwd2);
|
|
95643
|
+
log2(` manually merged (${cfg.autoMergeStrategy}) ${prUrl}`, "green");
|
|
95354
95644
|
emit("auto-merge-enabled", `manual:${cfg.autoMergeStrategy}`);
|
|
95355
95645
|
} catch (err) {
|
|
95356
95646
|
const e = err;
|
|
95357
|
-
log2(`! manual merge failed for ${
|
|
95647
|
+
log2(`! manual merge failed for ${prUrl}: ${e.stderr?.trim() || e.message}`, "yellow");
|
|
95358
95648
|
}
|
|
95359
95649
|
}
|
|
95360
95650
|
return 0;
|
|
@@ -95455,6 +95745,8 @@ async function runPostTask(input, deps) {
|
|
|
95455
95745
|
var CI_FAILED_EXIT = 70, PR_FAILED_EXIT = 71, repoAutoMergeCache;
|
|
95456
95746
|
var init_post_task = __esm(() => {
|
|
95457
95747
|
init_tasks_md();
|
|
95748
|
+
init_linear();
|
|
95749
|
+
init_pr();
|
|
95458
95750
|
init_ci();
|
|
95459
95751
|
init_worktree();
|
|
95460
95752
|
repoAutoMergeCache = new Map;
|
|
@@ -95721,64 +96013,235 @@ function renderTasksBlock(tasksMd, meta3) {
|
|
|
95721
96013
|
return out.join(`
|
|
95722
96014
|
`);
|
|
95723
96015
|
}
|
|
95724
|
-
|
|
95725
|
-
|
|
95726
|
-
|
|
95727
|
-
|
|
95728
|
-
if (startIdx >= 0 && endIdx >= 0) {
|
|
95729
|
-
const before2 = existing.slice(0, startIdx);
|
|
95730
|
-
const after2 = existing.slice(endIdx + RALPHY_TASKS_END.length);
|
|
95731
|
-
return `${before2}${block}${after2}`;
|
|
95732
|
-
}
|
|
95733
|
-
if (existing.length === 0)
|
|
95734
|
-
return block;
|
|
95735
|
-
const trimmed = existing.replace(/\s+$/, "");
|
|
95736
|
-
return `${trimmed}
|
|
96016
|
+
var RALPHY_TASKS_START = "<!-- ralphy:tasks:start -->", RALPHY_TASKS_END = "<!-- ralphy:tasks:end -->", MAX_CODE_BLOCK_BYTES;
|
|
96017
|
+
var init_linear_sync = __esm(() => {
|
|
96018
|
+
MAX_CODE_BLOCK_BYTES = 2 * 1024;
|
|
96019
|
+
});
|
|
95737
96020
|
|
|
95738
|
-
|
|
96021
|
+
// apps/agent/src/agent/linear-sync/comment-sync.ts
|
|
96022
|
+
import { dirname as dirname7, join as join21 } from "path";
|
|
96023
|
+
import { mkdir as mkdir6 } from "fs/promises";
|
|
96024
|
+
async function readStateJson(statePath) {
|
|
96025
|
+
const file2 = Bun.file(statePath);
|
|
96026
|
+
if (!await file2.exists())
|
|
96027
|
+
return null;
|
|
96028
|
+
try {
|
|
96029
|
+
return await file2.json();
|
|
96030
|
+
} catch {
|
|
96031
|
+
return null;
|
|
96032
|
+
}
|
|
96033
|
+
}
|
|
96034
|
+
async function writeStateJson(statePath, state) {
|
|
96035
|
+
await mkdir6(dirname7(statePath), { recursive: true });
|
|
96036
|
+
await Bun.write(statePath, JSON.stringify(state, null, 2) + `
|
|
96037
|
+
`);
|
|
96038
|
+
}
|
|
96039
|
+
function readComments(state) {
|
|
96040
|
+
const raw = state?.linearComments ?? {};
|
|
96041
|
+
return {
|
|
96042
|
+
planCommentId: raw?.planCommentId ?? null,
|
|
96043
|
+
tasksCommentId: raw?.tasksCommentId ?? null,
|
|
96044
|
+
planPostedAt: raw?.planPostedAt ?? null
|
|
96045
|
+
};
|
|
95739
96046
|
}
|
|
95740
|
-
async function
|
|
95741
|
-
const
|
|
96047
|
+
async function patchComments(statePath, patch) {
|
|
96048
|
+
const existing = await readStateJson(statePath) ?? {};
|
|
96049
|
+
const current = readComments(existing);
|
|
96050
|
+
const next = { ...current, ...patch };
|
|
96051
|
+
await writeStateJson(statePath, { ...existing, linearComments: next });
|
|
96052
|
+
}
|
|
96053
|
+
function isCommentNotFoundError(err) {
|
|
96054
|
+
if (!err)
|
|
96055
|
+
return false;
|
|
96056
|
+
const candidates = [];
|
|
96057
|
+
const e = err;
|
|
96058
|
+
if (Array.isArray(e.messages))
|
|
96059
|
+
candidates.push(...e.messages);
|
|
96060
|
+
if (typeof e.message === "string")
|
|
96061
|
+
candidates.push(e.message);
|
|
96062
|
+
const text = candidates.join(" ").toLowerCase();
|
|
96063
|
+
return text.includes("not found") || text.includes("could not find") || text.includes("entity not found");
|
|
96064
|
+
}
|
|
96065
|
+
async function readTasksMd(changeDir, log2) {
|
|
96066
|
+
const file2 = Bun.file(join21(changeDir, "tasks.md"));
|
|
95742
96067
|
if (!await file2.exists()) {
|
|
95743
|
-
|
|
96068
|
+
log2(` comment-sync: tasks.md missing in ${changeDir}, skipping`, "gray");
|
|
95744
96069
|
return null;
|
|
95745
96070
|
}
|
|
95746
|
-
let tasksMd;
|
|
95747
96071
|
try {
|
|
95748
|
-
|
|
96072
|
+
return await file2.text();
|
|
95749
96073
|
} catch (err) {
|
|
95750
|
-
|
|
96074
|
+
log2(`! comment-sync: read tasks.md failed: ${err.message}`, "yellow");
|
|
95751
96075
|
return null;
|
|
95752
96076
|
}
|
|
95753
|
-
|
|
95754
|
-
|
|
95755
|
-
|
|
95756
|
-
|
|
95757
|
-
|
|
95758
|
-
|
|
96077
|
+
}
|
|
96078
|
+
function renderTasksCommentBody(tasksMd, changeName, iteration) {
|
|
96079
|
+
return renderTasksBlock(tasksMd, { changeName, iteration });
|
|
96080
|
+
}
|
|
96081
|
+
async function postOrUpdateTasksComment(deps) {
|
|
96082
|
+
const tasksMd = await readTasksMd(deps.changeDir, deps.log);
|
|
96083
|
+
if (!tasksMd)
|
|
96084
|
+
return null;
|
|
96085
|
+
const body = renderTasksCommentBody(tasksMd, deps.changeName, deps.iteration);
|
|
96086
|
+
const state = await readStateJson(deps.statePath);
|
|
96087
|
+
const comments = readComments(state);
|
|
96088
|
+
if (comments.tasksCommentId) {
|
|
96089
|
+
try {
|
|
96090
|
+
await deps.mutations.updateIssueComment(deps.apiKey, comments.tasksCommentId, body);
|
|
96091
|
+
deps.log(` comment-sync: updated tasks comment for ${deps.changeName}`, "gray");
|
|
96092
|
+
return comments.tasksCommentId;
|
|
96093
|
+
} catch (err) {
|
|
96094
|
+
if (!isCommentNotFoundError(err)) {
|
|
96095
|
+
deps.log(`! comment-sync: updateIssueComment failed: ${err.message}`, "yellow");
|
|
96096
|
+
return null;
|
|
96097
|
+
}
|
|
96098
|
+
deps.log(` comment-sync: tasks comment ${comments.tasksCommentId} not found \u2014 recreating`, "gray");
|
|
96099
|
+
}
|
|
96100
|
+
}
|
|
96101
|
+
let newId;
|
|
96102
|
+
try {
|
|
96103
|
+
newId = await deps.mutations.createIssueComment(deps.apiKey, deps.issueId, body);
|
|
96104
|
+
} catch (err) {
|
|
96105
|
+
deps.log(`! comment-sync: createIssueComment failed: ${err.message}`, "yellow");
|
|
95759
96106
|
return null;
|
|
95760
96107
|
}
|
|
95761
|
-
|
|
95762
|
-
|
|
96108
|
+
await patchComments(deps.statePath, { tasksCommentId: newId });
|
|
96109
|
+
deps.log(` comment-sync: created tasks comment for ${deps.changeName}`, "gray");
|
|
96110
|
+
return newId;
|
|
96111
|
+
}
|
|
96112
|
+
function planningComplete(tasksMd) {
|
|
96113
|
+
const lines = tasksMd.split(/\r?\n/);
|
|
96114
|
+
let inPlanning = false;
|
|
96115
|
+
let total = 0;
|
|
96116
|
+
let unchecked = 0;
|
|
96117
|
+
for (const line of lines) {
|
|
96118
|
+
const h = /^##\s+(.+?)\s*$/.exec(line);
|
|
96119
|
+
if (h) {
|
|
96120
|
+
inPlanning = h[1].trim().toLowerCase() === "planning";
|
|
96121
|
+
continue;
|
|
96122
|
+
}
|
|
96123
|
+
if (!inPlanning)
|
|
96124
|
+
continue;
|
|
96125
|
+
const m = /^\s*-\s+\[( |x|X)\]/.exec(line);
|
|
96126
|
+
if (!m)
|
|
96127
|
+
continue;
|
|
96128
|
+
total += 1;
|
|
96129
|
+
if (m[1] === " ")
|
|
96130
|
+
unchecked += 1;
|
|
96131
|
+
}
|
|
96132
|
+
return { allChecked: total > 0 && unchecked === 0, total };
|
|
96133
|
+
}
|
|
96134
|
+
async function readFirstParagraph(path) {
|
|
96135
|
+
const file2 = Bun.file(path);
|
|
96136
|
+
if (!await file2.exists())
|
|
95763
96137
|
return null;
|
|
96138
|
+
const text = await file2.text();
|
|
96139
|
+
const blocks = text.split(/\r?\n\s*\r?\n/).map((b) => b.trim()).filter((b) => b.length > 0 && !/^#\s/.test(b));
|
|
96140
|
+
return blocks[0] ?? null;
|
|
96141
|
+
}
|
|
96142
|
+
async function readSection(path, heading) {
|
|
96143
|
+
const file2 = Bun.file(path);
|
|
96144
|
+
if (!await file2.exists())
|
|
96145
|
+
return null;
|
|
96146
|
+
const text = await file2.text();
|
|
96147
|
+
const headingRe = new RegExp(`(^|\\n)##\\s+${heading}\\s*\\n`);
|
|
96148
|
+
const m = headingRe.exec(text);
|
|
96149
|
+
if (!m)
|
|
96150
|
+
return null;
|
|
96151
|
+
const start = m.index + m[0].length;
|
|
96152
|
+
const rest2 = text.slice(start);
|
|
96153
|
+
const next = /\n##\s+/.exec(rest2);
|
|
96154
|
+
const body = next ? rest2.slice(0, next.index) : rest2;
|
|
96155
|
+
return body.trim() || null;
|
|
96156
|
+
}
|
|
96157
|
+
async function postPlanCommentOnce(deps) {
|
|
96158
|
+
const state = await readStateJson(deps.statePath);
|
|
96159
|
+
const comments = readComments(state);
|
|
96160
|
+
if (comments.planCommentId)
|
|
96161
|
+
return null;
|
|
96162
|
+
const tasksMd = await readTasksMd(deps.changeDir, deps.log);
|
|
96163
|
+
if (!tasksMd)
|
|
96164
|
+
return null;
|
|
96165
|
+
const check2 = planningComplete(tasksMd);
|
|
96166
|
+
if (!check2.allChecked)
|
|
96167
|
+
return null;
|
|
96168
|
+
const proposalPath = join21(deps.changeDir, "proposal.md");
|
|
96169
|
+
const why = await readSection(proposalPath, "Why");
|
|
96170
|
+
const whatChanges = await readSection(proposalPath, "What Changes");
|
|
96171
|
+
if (!why && !whatChanges) {
|
|
96172
|
+
deps.log(` comment-sync: proposal.md has no Why/What Changes, skipping plan comment`, "gray");
|
|
96173
|
+
return null;
|
|
96174
|
+
}
|
|
96175
|
+
const designSummary = await readFirstParagraph(join21(deps.changeDir, "design.md"));
|
|
96176
|
+
const parts = [`### ${PLAN_COMMENT_TITLE} \u2014 \`${deps.changeName}\``];
|
|
96177
|
+
if (why) {
|
|
96178
|
+
parts.push("", "**Why**", "", why);
|
|
96179
|
+
}
|
|
96180
|
+
if (whatChanges) {
|
|
96181
|
+
parts.push("", "**What Changes**", "", whatChanges);
|
|
96182
|
+
}
|
|
96183
|
+
if (designSummary) {
|
|
96184
|
+
parts.push("", "**Design**", "", designSummary);
|
|
96185
|
+
}
|
|
96186
|
+
const body = parts.join(`
|
|
96187
|
+
`);
|
|
96188
|
+
let id;
|
|
95764
96189
|
try {
|
|
95765
|
-
await deps.
|
|
95766
|
-
deps.log(` sync-tasks: updated Linear description for ${deps.changeName}`, "gray");
|
|
95767
|
-
return next;
|
|
96190
|
+
id = await deps.mutations.createIssueComment(deps.apiKey, deps.issueId, body);
|
|
95768
96191
|
} catch (err) {
|
|
95769
|
-
deps.log(`! sync
|
|
96192
|
+
deps.log(`! comment-sync: plan comment create failed: ${err.message}`, "yellow");
|
|
95770
96193
|
return null;
|
|
95771
96194
|
}
|
|
96195
|
+
await patchComments(deps.statePath, {
|
|
96196
|
+
planCommentId: id,
|
|
96197
|
+
planPostedAt: new Date().toISOString()
|
|
96198
|
+
});
|
|
96199
|
+
deps.log(` comment-sync: posted plan comment for ${deps.changeName}`, "gray");
|
|
96200
|
+
return id;
|
|
95772
96201
|
}
|
|
95773
|
-
|
|
95774
|
-
|
|
95775
|
-
|
|
95776
|
-
|
|
96202
|
+
async function postSteeringAndRefreshTasks(deps) {
|
|
96203
|
+
const firstLine = deps.message.split(/\r?\n/, 1)[0].trim() || deps.message.trim();
|
|
96204
|
+
const steeringBody = `### ${STEERING_COMMENT_TITLE}
|
|
96205
|
+
|
|
96206
|
+
${deps.message.trim()}`;
|
|
96207
|
+
try {
|
|
96208
|
+
await deps.mutations.createIssueComment(deps.apiKey, deps.issueId, steeringBody);
|
|
96209
|
+
deps.log(` comment-sync: posted steering comment (${firstLine})`, "gray");
|
|
96210
|
+
} catch (err) {
|
|
96211
|
+
deps.log(`! comment-sync: steering comment create failed: ${err.message}`, "yellow");
|
|
96212
|
+
}
|
|
96213
|
+
const state = await readStateJson(deps.statePath);
|
|
96214
|
+
const comments = readComments(state);
|
|
96215
|
+
if (comments.tasksCommentId) {
|
|
96216
|
+
try {
|
|
96217
|
+
await deps.mutations.deleteIssueComment(deps.apiKey, comments.tasksCommentId);
|
|
96218
|
+
deps.log(` comment-sync: deleted old tasks comment`, "gray");
|
|
96219
|
+
} catch (err) {
|
|
96220
|
+
if (!isCommentNotFoundError(err)) {
|
|
96221
|
+
deps.log(`! comment-sync: deleteIssueComment failed: ${err.message}`, "yellow");
|
|
96222
|
+
}
|
|
96223
|
+
}
|
|
96224
|
+
await patchComments(deps.statePath, { tasksCommentId: null });
|
|
96225
|
+
}
|
|
96226
|
+
await postOrUpdateTasksComment({
|
|
96227
|
+
apiKey: deps.apiKey,
|
|
96228
|
+
issueId: deps.issueId,
|
|
96229
|
+
statePath: deps.statePath,
|
|
96230
|
+
changeDir: deps.changeDir,
|
|
96231
|
+
changeName: deps.changeName,
|
|
96232
|
+
log: deps.log,
|
|
96233
|
+
mutations: deps.mutations,
|
|
96234
|
+
iteration: deps.iteration
|
|
96235
|
+
});
|
|
96236
|
+
}
|
|
96237
|
+
var PLAN_COMMENT_TITLE = "\uD83D\uDCCB Ralph plan", STEERING_COMMENT_TITLE = "\uD83E\uDDED Ralph steering";
|
|
96238
|
+
var init_comment_sync = __esm(() => {
|
|
96239
|
+
init_linear_sync();
|
|
95777
96240
|
});
|
|
95778
96241
|
|
|
95779
96242
|
// apps/agent/src/agent/wire.ts
|
|
95780
|
-
import { join as
|
|
95781
|
-
import { mkdir as
|
|
96243
|
+
import { join as join22 } from "path";
|
|
96244
|
+
import { mkdir as mkdir7 } from "fs/promises";
|
|
95782
96245
|
async function pickOpenPrUrlFromAttachments(urls, issueIdent, cmd, cwd2, onLog) {
|
|
95783
96246
|
const candidates = urls.filter((url2) => GITHUB_PR_URL_RE.test(url2));
|
|
95784
96247
|
let sawNonOpenPr = false;
|
|
@@ -95942,7 +96405,7 @@ function buildAgentCoordinator(input) {
|
|
|
95942
96405
|
onWorkerOutput,
|
|
95943
96406
|
onWorkerCmd
|
|
95944
96407
|
} = input;
|
|
95945
|
-
const logsDir =
|
|
96408
|
+
const logsDir = join22(projectRoot, ".ralph", "logs");
|
|
95946
96409
|
const concurrency = args.concurrency || cfg.concurrency;
|
|
95947
96410
|
const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
|
|
95948
96411
|
const indicators = mergeIndicators(cfg.linear.indicators, args.indicators);
|
|
@@ -96065,6 +96528,7 @@ function buildAgentCoordinator(input) {
|
|
|
96065
96528
|
const prUnavailable = new Map;
|
|
96066
96529
|
const PR_UNAVAILABLE_TTL_MS = 10 * 60 * 1000;
|
|
96067
96530
|
const stalePingedAt = new Map;
|
|
96531
|
+
const lastHandledReviewActivity = new Map;
|
|
96068
96532
|
const useWorktree = args.worktree || cfg.useWorktree;
|
|
96069
96533
|
const scriptRunner = input.runners?.runScript ?? (async (cmd, cwd2) => {
|
|
96070
96534
|
const proc = Bun.spawn({
|
|
@@ -96154,8 +96618,8 @@ function buildAgentCoordinator(input) {
|
|
|
96154
96618
|
} else {
|
|
96155
96619
|
changeName = changeNameForIssue(issue2);
|
|
96156
96620
|
const wtLayout = projectLayout(workerCwd);
|
|
96157
|
-
await
|
|
96158
|
-
await
|
|
96621
|
+
await mkdir7(wtLayout.changeDir(changeName), { recursive: true });
|
|
96622
|
+
await mkdir7(wtLayout.taskStateDir(changeName), { recursive: true });
|
|
96159
96623
|
}
|
|
96160
96624
|
cwdByChange.set(changeName, workerCwd);
|
|
96161
96625
|
statesDirByChange.set(changeName, scaffoldStatesDir);
|
|
@@ -96164,7 +96628,7 @@ function buildAgentCoordinator(input) {
|
|
|
96164
96628
|
branchByChange.set(changeName, branch);
|
|
96165
96629
|
if (mode === "review") {
|
|
96166
96630
|
const wtLayout = projectLayout(workerCwd);
|
|
96167
|
-
const tasksFile =
|
|
96631
|
+
const tasksFile = join22(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
|
|
96168
96632
|
let body;
|
|
96169
96633
|
let heading;
|
|
96170
96634
|
if (trigger) {
|
|
@@ -96189,7 +96653,7 @@ function buildAgentCoordinator(input) {
|
|
|
96189
96653
|
await reactivateState2(wtLayout.stateFile(changeName), changeName);
|
|
96190
96654
|
} else if (mode === "conflict-fix") {
|
|
96191
96655
|
const wtLayout = projectLayout(workerCwd);
|
|
96192
|
-
const tasksFile =
|
|
96656
|
+
const tasksFile = join22(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
|
|
96193
96657
|
const prUrl = prByChange.get(changeName);
|
|
96194
96658
|
const body = [
|
|
96195
96659
|
`The PR for this change has merge conflicts with \`${cfg.prBaseBranch}\`.`,
|
|
@@ -96269,7 +96733,7 @@ PR: ${prUrl}` : ""
|
|
|
96269
96733
|
return c;
|
|
96270
96734
|
}
|
|
96271
96735
|
function defaultSpawn(changeName, cmd, cwd2, note) {
|
|
96272
|
-
const logFilePath =
|
|
96736
|
+
const logFilePath = join22(logsDir, `${changeName}.log`);
|
|
96273
96737
|
const ANSI_RE2 = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
96274
96738
|
const BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
|
|
96275
96739
|
const STATUS_BAR_LINE_RE = /^[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u2713\u2717]\s+iter\s+\d+/;
|
|
@@ -96328,7 +96792,7 @@ PR: ${prUrl}` : ""
|
|
|
96328
96792
|
function spawnWorker(changeName) {
|
|
96329
96793
|
const cwd2 = cwdByChange.get(changeName) ?? projectRoot;
|
|
96330
96794
|
const injected = input.runners?.spawnWorker;
|
|
96331
|
-
const missionTasksPath =
|
|
96795
|
+
const missionTasksPath = join22(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
|
|
96332
96796
|
const prevTasksPromise = (async () => {
|
|
96333
96797
|
const f2 = Bun.file(missionTasksPath);
|
|
96334
96798
|
return await f2.exists() ? await f2.text() : "";
|
|
@@ -96336,7 +96800,7 @@ PR: ${prUrl}` : ""
|
|
|
96336
96800
|
let logFilePath;
|
|
96337
96801
|
let handle;
|
|
96338
96802
|
if (injected) {
|
|
96339
|
-
logFilePath =
|
|
96803
|
+
logFilePath = join22(logsDir, `${changeName}.log`);
|
|
96340
96804
|
handle = injected(buildTaskCmdFor(changeName), cwd2);
|
|
96341
96805
|
} else {
|
|
96342
96806
|
const r = defaultSpawn(changeName, buildTaskCmdFor(changeName), cwd2, `spawn at ${new Date().toISOString()}`);
|
|
@@ -96396,6 +96860,7 @@ PR: ${prUrl}` : ""
|
|
|
96396
96860
|
ignoreCiChecks: cfg.ignoreCiChecks,
|
|
96397
96861
|
stackPrsOnDependencies: args.stackPrs || cfg.stackPrsOnDependencies,
|
|
96398
96862
|
neverTouch: cfg.boundaries.never_touch,
|
|
96863
|
+
metaOnlyFiles: cfg.boundaries.meta_only_files,
|
|
96399
96864
|
manualMergeWhenAutoMergeDisabled: cfg.manualMergeWhenAutoMergeDisabled
|
|
96400
96865
|
},
|
|
96401
96866
|
respawnWorker: respawn
|
|
@@ -96608,17 +97073,32 @@ PR: ${prUrl}` : ""
|
|
|
96608
97073
|
try {
|
|
96609
97074
|
candidates = await fetchMentionScanIssues(apiKey, { team, assignee });
|
|
96610
97075
|
} catch (err) {
|
|
96611
|
-
|
|
97076
|
+
if (isRateLimitedError(err)) {
|
|
97077
|
+
onLog(`! mention scan: rate limited, deferring rest of scan to next poll`, "yellow");
|
|
97078
|
+
return [];
|
|
97079
|
+
}
|
|
97080
|
+
onLog(`! mention scan: fetchMentionScanIssues failed: ${formatLinearError(err)}`, "yellow");
|
|
96612
97081
|
return [];
|
|
96613
97082
|
}
|
|
96614
97083
|
const out = [];
|
|
96615
97084
|
const queued = new Set;
|
|
97085
|
+
let rateLimitedLogged = false;
|
|
97086
|
+
const logRateLimited = () => {
|
|
97087
|
+
if (rateLimitedLogged)
|
|
97088
|
+
return;
|
|
97089
|
+
rateLimitedLogged = true;
|
|
97090
|
+
onLog(`! mention scan: rate limited, deferring rest of scan to next poll`, "yellow");
|
|
97091
|
+
};
|
|
96616
97092
|
for (const issue2 of candidates) {
|
|
96617
97093
|
let comments = [];
|
|
96618
97094
|
try {
|
|
96619
97095
|
comments = await fetchIssueComments(apiKey, issue2.id);
|
|
96620
97096
|
} catch (err) {
|
|
96621
|
-
|
|
97097
|
+
if (isRateLimitedError(err)) {
|
|
97098
|
+
logRateLimited();
|
|
97099
|
+
break;
|
|
97100
|
+
}
|
|
97101
|
+
onLog(`! mention scan: Linear comments failed for ${issue2.identifier}: ${formatLinearError(err)}`, "yellow");
|
|
96622
97102
|
continue;
|
|
96623
97103
|
}
|
|
96624
97104
|
const lastRalphPickup = findLastRalphPickupISO(comments);
|
|
@@ -96643,11 +97123,18 @@ PR: ${prUrl}` : ""
|
|
|
96643
97123
|
try {
|
|
96644
97124
|
await addReactionToComment(apiKey, c.id, "\uD83D\uDC40");
|
|
96645
97125
|
} catch (err) {
|
|
96646
|
-
|
|
97126
|
+
if (isRateLimitedError(err)) {
|
|
97127
|
+
logRateLimited();
|
|
97128
|
+
queued.add(issue2.id);
|
|
97129
|
+
break;
|
|
97130
|
+
}
|
|
97131
|
+
onLog(`! mention scan: Linear reaction failed for ${issue2.identifier}: ${formatLinearError(err)}`, "yellow");
|
|
96647
97132
|
}
|
|
96648
97133
|
queued.add(issue2.id);
|
|
96649
97134
|
break;
|
|
96650
97135
|
}
|
|
97136
|
+
if (rateLimitedLogged)
|
|
97137
|
+
break;
|
|
96651
97138
|
if (queued.has(issue2.id))
|
|
96652
97139
|
continue;
|
|
96653
97140
|
}
|
|
@@ -96677,7 +97164,7 @@ PR: ${prUrl}` : ""
|
|
|
96677
97164
|
try {
|
|
96678
97165
|
await addGithubReactionToComment({ owner, repo, kind: "issue" }, c.id, "\uD83D\uDC40");
|
|
96679
97166
|
} catch (err) {
|
|
96680
|
-
onLog(`! mention scan: GitHub reaction failed for ${prUrl}: ${err
|
|
97167
|
+
onLog(`! mention scan: GitHub reaction failed for ${prUrl}: ${formatLinearError(err)}`, "yellow");
|
|
96681
97168
|
}
|
|
96682
97169
|
}
|
|
96683
97170
|
queued.add(issue2.id);
|
|
@@ -96707,7 +97194,9 @@ PR: ${prUrl}` : ""
|
|
|
96707
97194
|
const last2 = t.comments[t.comments.length - 1].createdAt;
|
|
96708
97195
|
return last2 > acc ? last2 : acc;
|
|
96709
97196
|
}, "");
|
|
96710
|
-
|
|
97197
|
+
const lastHandled = lastHandledReviewActivity.get(prUrl) ?? null;
|
|
97198
|
+
const effectiveLastHandled = lastRalphPickup && lastHandled ? lastRalphPickup > lastHandled ? lastRalphPickup : lastHandled : lastRalphPickup ?? lastHandled;
|
|
97199
|
+
if (!effectiveLastHandled || newestReviewerActivity > effectiveLastHandled) {
|
|
96711
97200
|
const body = unresolved.map((t) => {
|
|
96712
97201
|
const head3 = t.path ? `_${t.path}${t.line ? `:${t.line}` : ""}_` : "_(general)_";
|
|
96713
97202
|
const lines = t.comments.map((c) => `> **${c.author ?? "reviewer"}** (${c.createdAt})
|
|
@@ -96721,6 +97210,7 @@ PR: ${prUrl}` : ""
|
|
|
96721
97210
|
---
|
|
96722
97211
|
|
|
96723
97212
|
`);
|
|
97213
|
+
lastHandledReviewActivity.set(prUrl, newestReviewerActivity);
|
|
96724
97214
|
return {
|
|
96725
97215
|
source: "github-review",
|
|
96726
97216
|
body,
|
|
@@ -96868,10 +97358,16 @@ PR: ${prUrl}` : ""
|
|
|
96868
97358
|
const parsed = JSON.parse(res.stdout || "[]");
|
|
96869
97359
|
return parsed;
|
|
96870
97360
|
} catch (err) {
|
|
96871
|
-
onLog(`! mention scan: gh comments failed for ${prUrl}: ${err
|
|
97361
|
+
onLog(`! mention scan: gh comments failed for ${prUrl}: ${formatLinearError(err)}`, "yellow");
|
|
96872
97362
|
return [];
|
|
96873
97363
|
}
|
|
96874
97364
|
}
|
|
97365
|
+
const commentSyncEnabled = Boolean(cfg.linear.syncTasksToComment && apiKey);
|
|
97366
|
+
const commentMutations = {
|
|
97367
|
+
createIssueComment,
|
|
97368
|
+
updateIssueComment,
|
|
97369
|
+
deleteIssueComment
|
|
97370
|
+
};
|
|
96875
97371
|
const coord = new AgentCoordinator({
|
|
96876
97372
|
fetchTodo: () => fetchByGet(indicators.getTodo, excludeFromTodo),
|
|
96877
97373
|
fetchInProgress: () => fetchByGet(indicators.getInProgress, []),
|
|
@@ -96900,26 +97396,62 @@ PR: ${prUrl}` : ""
|
|
|
96900
97396
|
const json2 = await file2.json();
|
|
96901
97397
|
return json2.iteration ?? 0;
|
|
96902
97398
|
},
|
|
96903
|
-
...
|
|
97399
|
+
...commentSyncEnabled ? {
|
|
96904
97400
|
syncTasks: async (worker, iteration) => {
|
|
96905
97401
|
const root = cwdByChange.get(worker.changeName) ?? projectRoot;
|
|
96906
|
-
const
|
|
96907
|
-
const
|
|
96908
|
-
const
|
|
97402
|
+
const layout = projectLayout(root);
|
|
97403
|
+
const changeDir = layout.changeDir(worker.changeName);
|
|
97404
|
+
const statePath = layout.stateFile(worker.changeName);
|
|
97405
|
+
await postPlanCommentOnce({
|
|
96909
97406
|
apiKey,
|
|
96910
97407
|
issueId: worker.issueId,
|
|
96911
|
-
|
|
96912
|
-
|
|
97408
|
+
statePath,
|
|
97409
|
+
changeDir,
|
|
97410
|
+
changeName: worker.changeName,
|
|
97411
|
+
log: onLog,
|
|
97412
|
+
mutations: commentMutations
|
|
97413
|
+
});
|
|
97414
|
+
await postOrUpdateTasksComment({
|
|
97415
|
+
apiKey,
|
|
97416
|
+
issueId: worker.issueId,
|
|
97417
|
+
statePath,
|
|
97418
|
+
changeDir,
|
|
96913
97419
|
changeName: worker.changeName,
|
|
96914
97420
|
iteration,
|
|
96915
97421
|
log: onLog,
|
|
96916
|
-
|
|
97422
|
+
mutations: commentMutations
|
|
96917
97423
|
});
|
|
96918
|
-
|
|
96919
|
-
|
|
96920
|
-
|
|
96921
|
-
|
|
97424
|
+
},
|
|
97425
|
+
onSteeringAppended: async (changeName, message) => {
|
|
97426
|
+
const root = cwdByChange.get(changeName) ?? projectRoot;
|
|
97427
|
+
const layout = projectLayout(root);
|
|
97428
|
+
const changeDir = layout.changeDir(changeName);
|
|
97429
|
+
const statePath = layout.stateFile(changeName);
|
|
97430
|
+
const issue2 = issueByChange.get(changeName) ?? null;
|
|
97431
|
+
const issueId = issue2?.id ?? null;
|
|
97432
|
+
if (!issueId) {
|
|
97433
|
+
onLog(` comment-sync: no Linear issue cached for ${changeName}; skipping steering refresh`, "gray");
|
|
97434
|
+
return;
|
|
96922
97435
|
}
|
|
97436
|
+
let iteration = 0;
|
|
97437
|
+
try {
|
|
97438
|
+
const f2 = Bun.file(statePath);
|
|
97439
|
+
if (await f2.exists()) {
|
|
97440
|
+
const json2 = await f2.json();
|
|
97441
|
+
iteration = json2.iteration ?? 0;
|
|
97442
|
+
}
|
|
97443
|
+
} catch {}
|
|
97444
|
+
await postSteeringAndRefreshTasks({
|
|
97445
|
+
apiKey,
|
|
97446
|
+
issueId,
|
|
97447
|
+
statePath,
|
|
97448
|
+
changeDir,
|
|
97449
|
+
changeName,
|
|
97450
|
+
iteration,
|
|
97451
|
+
message,
|
|
97452
|
+
log: onLog,
|
|
97453
|
+
mutations: commentMutations
|
|
97454
|
+
});
|
|
96923
97455
|
}
|
|
96924
97456
|
} : {}
|
|
96925
97457
|
}, {
|
|
@@ -96988,7 +97520,7 @@ PR: ${prUrl}` : ""
|
|
|
96988
97520
|
concurrency,
|
|
96989
97521
|
pollInterval,
|
|
96990
97522
|
getWorkerCwd: (changeName) => cwdByChange.get(changeName),
|
|
96991
|
-
syncTasksEnabled:
|
|
97523
|
+
syncTasksEnabled: commentSyncEnabled,
|
|
96992
97524
|
runBaselineGate: runBaselineGateOnce
|
|
96993
97525
|
};
|
|
96994
97526
|
}
|
|
@@ -97017,6 +97549,7 @@ var init_wire = __esm(() => {
|
|
|
97017
97549
|
init_tasks_md();
|
|
97018
97550
|
init_workflow();
|
|
97019
97551
|
init_types2();
|
|
97552
|
+
init_linear();
|
|
97020
97553
|
init_coordinator();
|
|
97021
97554
|
init_scaffold();
|
|
97022
97555
|
init_worktree();
|
|
@@ -97024,7 +97557,7 @@ var init_wire = __esm(() => {
|
|
|
97024
97557
|
init_post_task();
|
|
97025
97558
|
init_gate();
|
|
97026
97559
|
init_workflow();
|
|
97027
|
-
|
|
97560
|
+
init_comment_sync();
|
|
97028
97561
|
GITHUB_PR_URL_RE = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/;
|
|
97029
97562
|
bunGitRunner = {
|
|
97030
97563
|
run: async (args, cwd2) => {
|
|
@@ -97143,21 +97676,26 @@ function readSize2() {
|
|
|
97143
97676
|
rows: process.stdout.rows ?? 24
|
|
97144
97677
|
};
|
|
97145
97678
|
}
|
|
97679
|
+
function clearScreenAndScrollback2() {
|
|
97680
|
+
if (process.stdout.isTTY)
|
|
97681
|
+
process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
97682
|
+
}
|
|
97146
97683
|
function useTerminalSize2() {
|
|
97147
|
-
const
|
|
97148
|
-
|
|
97149
|
-
|
|
97150
|
-
}));
|
|
97684
|
+
const initial2 = import_react59.useRef({ ...readSize2(), resizeKey: 0 });
|
|
97685
|
+
const [size2, setSize] = import_react59.useState(initial2.current);
|
|
97686
|
+
const sizeRef = import_react59.useRef(initial2.current);
|
|
97151
97687
|
import_react59.useEffect(() => {
|
|
97152
97688
|
if (!process.stdout.isTTY)
|
|
97153
97689
|
return;
|
|
97154
97690
|
const onResize = () => {
|
|
97155
97691
|
const { columns, rows } = readSize2();
|
|
97156
|
-
|
|
97157
|
-
|
|
97158
|
-
|
|
97159
|
-
|
|
97160
|
-
}
|
|
97692
|
+
const prev = sizeRef.current;
|
|
97693
|
+
if (prev.columns === columns && prev.rows === rows)
|
|
97694
|
+
return;
|
|
97695
|
+
clearScreenAndScrollback2();
|
|
97696
|
+
const next = { columns, rows, resizeKey: prev.resizeKey + 1 };
|
|
97697
|
+
sizeRef.current = next;
|
|
97698
|
+
setSize(next);
|
|
97161
97699
|
};
|
|
97162
97700
|
process.stdout.on("resize", onResize);
|
|
97163
97701
|
return () => {
|
|
@@ -97347,7 +97885,7 @@ var init_SteeringField = __esm(async () => {
|
|
|
97347
97885
|
});
|
|
97348
97886
|
|
|
97349
97887
|
// apps/agent/src/components/AgentMode.tsx
|
|
97350
|
-
import { join as
|
|
97888
|
+
import { join as join23 } from "path";
|
|
97351
97889
|
async function appendSteeringImpl(changeDir, message) {
|
|
97352
97890
|
await runWithContext(createDefaultContext(), async () => {
|
|
97353
97891
|
appendSteeringMessage(changeDir, message);
|
|
@@ -97598,19 +98136,13 @@ function AgentMode({
|
|
|
97598
98136
|
loadConfig = loadRalphyConfig
|
|
97599
98137
|
}) {
|
|
97600
98138
|
const { exit } = use_app_default();
|
|
97601
|
-
const { stdout } = use_stdout_default();
|
|
97602
98139
|
const { isRawModeSupported } = use_stdin_default();
|
|
97603
98140
|
const { columns, rows, resizeKey } = useTerminalSize2();
|
|
97604
|
-
import_react61.useEffect(() => {
|
|
97605
|
-
if (resizeKey === 0)
|
|
97606
|
-
return;
|
|
97607
|
-
stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
97608
|
-
}, [resizeKey, stdout]);
|
|
97609
98141
|
const [logs, setLogs] = import_react61.useState([]);
|
|
97610
98142
|
const [, setTick] = import_react61.useState(0);
|
|
97611
98143
|
const [clock, setClock] = import_react61.useState(0);
|
|
97612
98144
|
const [focusedIdx, setFocusedIdx] = import_react61.useState(0);
|
|
97613
|
-
const [showPendingTasks, setShowPendingTasks] = import_react61.useState(
|
|
98145
|
+
const [showPendingTasks, setShowPendingTasks] = import_react61.useState(false);
|
|
97614
98146
|
const [showAllSubtasks, setShowAllSubtasks] = import_react61.useState(false);
|
|
97615
98147
|
const coordRef = import_react61.useRef(null);
|
|
97616
98148
|
const workerMetaRef = import_react61.useRef(new Map);
|
|
@@ -97808,7 +98340,7 @@ function AgentMode({
|
|
|
97808
98340
|
(async () => {
|
|
97809
98341
|
for (const [changeName, meta3] of workerMetaRef.current) {
|
|
97810
98342
|
try {
|
|
97811
|
-
const file2 = Bun.file(
|
|
98343
|
+
const file2 = Bun.file(join23(meta3.statesDir, changeName, ".ralph-state.json"));
|
|
97812
98344
|
if (await file2.exists()) {
|
|
97813
98345
|
const json2 = await file2.json();
|
|
97814
98346
|
meta3.iter = json2.iteration ?? meta3.iter;
|
|
@@ -97818,9 +98350,9 @@ function AgentMode({
|
|
|
97818
98350
|
}
|
|
97819
98351
|
if (meta3.changeDir) {
|
|
97820
98352
|
try {
|
|
97821
|
-
const tasksFile = Bun.file(
|
|
97822
|
-
const proposalFile = Bun.file(
|
|
97823
|
-
const designFile = Bun.file(
|
|
98353
|
+
const tasksFile = Bun.file(join23(meta3.changeDir, "tasks.md"));
|
|
98354
|
+
const proposalFile = Bun.file(join23(meta3.changeDir, "proposal.md"));
|
|
98355
|
+
const designFile = Bun.file(join23(meta3.changeDir, "design.md"));
|
|
97824
98356
|
const [tasksText, proposalText, designText] = await Promise.all([
|
|
97825
98357
|
tasksFile.exists().then((ok) => ok ? tasksFile.text() : null),
|
|
97826
98358
|
proposalFile.exists().then((ok) => ok ? proposalFile.text() : null),
|
|
@@ -98651,11 +99183,14 @@ function AgentMode({
|
|
|
98651
99183
|
},
|
|
98652
99184
|
onSubmit: async (message) => {
|
|
98653
99185
|
try {
|
|
98654
|
-
await appendSteering(
|
|
99186
|
+
await appendSteering(join23(tasksDir, w.changeName), message);
|
|
98655
99187
|
} catch (err) {
|
|
98656
99188
|
appendLog(`! steering append failed for ${w.changeName}: ${err.message}`, "red");
|
|
98657
99189
|
throw err;
|
|
98658
99190
|
}
|
|
99191
|
+
try {
|
|
99192
|
+
await coordRef.current?.notifySteeringAppended?.(w.changeName, message);
|
|
99193
|
+
} catch {}
|
|
98659
99194
|
const restarted = await coordRef.current?.restartWorker(w.changeName);
|
|
98660
99195
|
if (restarted) {
|
|
98661
99196
|
appendLog(` ${w.changeName}: steering applied, restarting worker`, "cyan");
|
|
@@ -98855,7 +99390,7 @@ var exports_list = {};
|
|
|
98855
99390
|
__export(exports_list, {
|
|
98856
99391
|
runList: () => runList
|
|
98857
99392
|
});
|
|
98858
|
-
import { join as
|
|
99393
|
+
import { join as join24 } from "path";
|
|
98859
99394
|
function countTaskItems(content) {
|
|
98860
99395
|
const checked = (content.match(/^- \[x\]/gm) ?? []).length;
|
|
98861
99396
|
const unchecked = (content.match(/^- \[ \]/gm) ?? []).length;
|
|
@@ -98868,13 +99403,13 @@ function buildLocalRows(statesDir, projectRoot) {
|
|
|
98868
99403
|
const sources = [{ dir: statesDir, label: "main" }];
|
|
98869
99404
|
const worktreesRoot = worktreesDir2(projectRoot);
|
|
98870
99405
|
for (const wt of storage.list(worktreesRoot)) {
|
|
98871
|
-
sources.push({ dir:
|
|
99406
|
+
sources.push({ dir: join24(worktreesRoot, wt, ".ralph", "tasks"), label: `wt:${wt}` });
|
|
98872
99407
|
}
|
|
98873
99408
|
for (const { dir, label } of sources) {
|
|
98874
99409
|
for (const entry of storage.list(dir)) {
|
|
98875
99410
|
if (seen.has(entry))
|
|
98876
99411
|
continue;
|
|
98877
|
-
const raw = storage.read(
|
|
99412
|
+
const raw = storage.read(join24(dir, entry, ".ralph-state.json"));
|
|
98878
99413
|
if (raw === null)
|
|
98879
99414
|
continue;
|
|
98880
99415
|
let state;
|
|
@@ -98889,7 +99424,7 @@ function buildLocalRows(statesDir, projectRoot) {
|
|
|
98889
99424
|
const firstLine = promptRaw.split(`
|
|
98890
99425
|
`).find((l) => l.trim() !== "") ?? "";
|
|
98891
99426
|
let progress = "\u2014";
|
|
98892
|
-
const tasksContent = storage.read(
|
|
99427
|
+
const tasksContent = storage.read(join24(dir, entry, "tasks.md"));
|
|
98893
99428
|
if (tasksContent !== null) {
|
|
98894
99429
|
const { checked, unchecked } = countTaskItems(tasksContent);
|
|
98895
99430
|
const total = checked + unchecked;
|
|
@@ -99283,6 +99818,7 @@ var init_list = __esm(() => {
|
|
|
99283
99818
|
init_types2();
|
|
99284
99819
|
init_worktree();
|
|
99285
99820
|
init_config();
|
|
99821
|
+
init_linear();
|
|
99286
99822
|
init_list_sort();
|
|
99287
99823
|
localCmdRunner = {
|
|
99288
99824
|
run: async (cmd, cwd2) => {
|
|
@@ -99305,8 +99841,8 @@ var exports_json_runner = {};
|
|
|
99305
99841
|
__export(exports_json_runner, {
|
|
99306
99842
|
runAgentJson: () => runAgentJson
|
|
99307
99843
|
});
|
|
99308
|
-
import { join as
|
|
99309
|
-
import { mkdir as
|
|
99844
|
+
import { join as join25 } from "path";
|
|
99845
|
+
import { mkdir as mkdir8 } from "fs/promises";
|
|
99310
99846
|
import { homedir as homedir5 } from "os";
|
|
99311
99847
|
function cleanOutputLine2(raw) {
|
|
99312
99848
|
const clean = raw.replace(ANSI_STRIP_RE2, "").trim();
|
|
@@ -99330,7 +99866,7 @@ async function runAgentJson({
|
|
|
99330
99866
|
statesDir,
|
|
99331
99867
|
tasksDir
|
|
99332
99868
|
}) {
|
|
99333
|
-
await
|
|
99869
|
+
await mkdir8(join25(homedir5(), ".ralph"), { recursive: true }).catch(() => {
|
|
99334
99870
|
return;
|
|
99335
99871
|
});
|
|
99336
99872
|
const cfgPath = await ensureRalphyConfig(projectRoot);
|
|
@@ -99484,8 +100020,8 @@ var exports_src2 = {};
|
|
|
99484
100020
|
__export(exports_src2, {
|
|
99485
100021
|
main: () => main2
|
|
99486
100022
|
});
|
|
99487
|
-
import { mkdir as
|
|
99488
|
-
import { join as
|
|
100023
|
+
import { mkdir as mkdir9 } from "fs/promises";
|
|
100024
|
+
import { join as join26 } from "path";
|
|
99489
100025
|
async function main2(argv) {
|
|
99490
100026
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
99491
100027
|
printHelp2();
|
|
@@ -99519,9 +100055,9 @@ async function main2(argv) {
|
|
|
99519
100055
|
});
|
|
99520
100056
|
return typeof process.exitCode === "number" ? process.exitCode : 0;
|
|
99521
100057
|
}
|
|
99522
|
-
await
|
|
99523
|
-
await
|
|
99524
|
-
await
|
|
100058
|
+
await mkdir9(statesDir, { recursive: true });
|
|
100059
|
+
await mkdir9(tasksDir, { recursive: true });
|
|
100060
|
+
await mkdir9(join26(projectRoot, ".ralph"), { recursive: true });
|
|
99525
100061
|
if (args.jsonOutput) {
|
|
99526
100062
|
const { runAgentJson: runAgentJson2 } = await Promise.resolve().then(() => (init_json_runner(), exports_json_runner));
|
|
99527
100063
|
await runAgentJson2({ args, projectRoot, statesDir, tasksDir });
|