@neriros/ralphy 3.4.0 → 3.5.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/dist/shell/index.js +329 -46
- 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.5.0")
|
|
18932
|
+
return "3.5.0";
|
|
18933
18933
|
} catch {}
|
|
18934
18934
|
const dirsToTry = [];
|
|
18935
18935
|
try {
|
|
@@ -92668,6 +92668,7 @@ var init_schema = __esm(() => {
|
|
|
92668
92668
|
codeReviewTrigger: exports_external2.boolean().default(true),
|
|
92669
92669
|
codeReviewStaleHours: exports_external2.number().nonnegative().default(24),
|
|
92670
92670
|
syncTasksToComment: exports_external2.boolean().default(true),
|
|
92671
|
+
syncSpecsAsAttachments: exports_external2.boolean().default(true),
|
|
92671
92672
|
indicators: IndicatorsSchema.default({})
|
|
92672
92673
|
}).strict().default({
|
|
92673
92674
|
postComments: true,
|
|
@@ -92677,6 +92678,7 @@ var init_schema = __esm(() => {
|
|
|
92677
92678
|
codeReviewTrigger: true,
|
|
92678
92679
|
codeReviewStaleHours: 24,
|
|
92679
92680
|
syncTasksToComment: true,
|
|
92681
|
+
syncSpecsAsAttachments: true,
|
|
92680
92682
|
indicators: {}
|
|
92681
92683
|
}),
|
|
92682
92684
|
github: exports_external2.object({
|
|
@@ -92831,6 +92833,11 @@ linear:
|
|
|
92831
92833
|
# cadence as updateEveryIterations, and on done-transition.
|
|
92832
92834
|
syncTasksToComment: true
|
|
92833
92835
|
|
|
92836
|
+
# Upload openspec proposal.md and design.md as Linear attachments on the
|
|
92837
|
+
# parent issue. Refreshed when file contents change, no-op otherwise.
|
|
92838
|
+
# Requires syncTasksToComment.
|
|
92839
|
+
syncSpecsAsAttachments: true
|
|
92840
|
+
|
|
92834
92841
|
# Indicators map Ralph lifecycle events to Linear labels/statuses.
|
|
92835
92842
|
#
|
|
92836
92843
|
# Filter semantics (per indicator's \`filter:\` list):
|
|
@@ -93818,10 +93825,45 @@ function buildIssueFilter(spec) {
|
|
|
93818
93825
|
}
|
|
93819
93826
|
return where;
|
|
93820
93827
|
}
|
|
93828
|
+
function clauseFromMarkers(markers) {
|
|
93829
|
+
if (markers.length === 0)
|
|
93830
|
+
return null;
|
|
93831
|
+
const { statuses, labels, attachmentSubtitles, projects } = partition2(markers);
|
|
93832
|
+
const parts = {};
|
|
93833
|
+
if (statuses.length > 0)
|
|
93834
|
+
parts.state = { name: { in: statuses } };
|
|
93835
|
+
if (labels.length > 0)
|
|
93836
|
+
parts.labels = { some: { name: { in: labels } } };
|
|
93837
|
+
if (attachmentSubtitles.length > 0) {
|
|
93838
|
+
parts.attachments = {
|
|
93839
|
+
some: {
|
|
93840
|
+
title: { eq: RALPHY_ATTACHMENT_TITLE_FILTER },
|
|
93841
|
+
subtitle: { in: attachmentSubtitles }
|
|
93842
|
+
}
|
|
93843
|
+
};
|
|
93844
|
+
}
|
|
93845
|
+
if (projects.length > 0)
|
|
93846
|
+
parts.project = { name: { in: projects } };
|
|
93847
|
+
return Object.keys(parts).length > 0 ? parts : null;
|
|
93848
|
+
}
|
|
93821
93849
|
async function fetchMentionScanIssues(apiKey, spec) {
|
|
93822
|
-
const
|
|
93823
|
-
|
|
93824
|
-
|
|
93850
|
+
const branches = [];
|
|
93851
|
+
const { getTodo, getInProgress, setDone } = spec.indicators;
|
|
93852
|
+
for (const ind of [getTodo, getInProgress]) {
|
|
93853
|
+
if (!ind)
|
|
93854
|
+
continue;
|
|
93855
|
+
const c = clauseFromMarkers(ind.filter);
|
|
93856
|
+
if (c)
|
|
93857
|
+
branches.push(c);
|
|
93858
|
+
}
|
|
93859
|
+
if (setDone) {
|
|
93860
|
+
const c = clauseFromMarkers(markersOf(setDone));
|
|
93861
|
+
if (c)
|
|
93862
|
+
branches.push(c);
|
|
93863
|
+
}
|
|
93864
|
+
if (branches.length === 0)
|
|
93865
|
+
return [];
|
|
93866
|
+
const where = branches.length === 1 ? { ...branches[0] } : { or: branches };
|
|
93825
93867
|
if (spec.team)
|
|
93826
93868
|
where.team = { key: { eq: spec.team } };
|
|
93827
93869
|
if (spec.assignee) {
|
|
@@ -94004,6 +94046,66 @@ async function linearRequest(apiKey, query, variables) {
|
|
|
94004
94046
|
}
|
|
94005
94047
|
throw lastHttpError ?? new Error("Linear API request failed");
|
|
94006
94048
|
}
|
|
94049
|
+
async function uploadFileToLinear(apiKey, input) {
|
|
94050
|
+
const mutation = `mutation FileUpload($filename: String!, $contentType: String!, $size: Int!) {
|
|
94051
|
+
fileUpload(filename: $filename, contentType: $contentType, size: $size) {
|
|
94052
|
+
uploadFile { uploadUrl assetUrl headers { key value } }
|
|
94053
|
+
}
|
|
94054
|
+
}`;
|
|
94055
|
+
const data = await linearRequest(apiKey, mutation, {
|
|
94056
|
+
filename: input.filename,
|
|
94057
|
+
contentType: input.contentType,
|
|
94058
|
+
size: input.bytes.byteLength
|
|
94059
|
+
});
|
|
94060
|
+
const up = data.uploadFile;
|
|
94061
|
+
if (!up)
|
|
94062
|
+
throw new Error("fileUpload returned no uploadFile payload");
|
|
94063
|
+
const headers = { "Content-Type": input.contentType };
|
|
94064
|
+
for (const h of up.headers)
|
|
94065
|
+
headers[h.key] = h.value;
|
|
94066
|
+
const res = await fetch(up.uploadUrl, {
|
|
94067
|
+
method: "PUT",
|
|
94068
|
+
headers,
|
|
94069
|
+
body: input.bytes
|
|
94070
|
+
});
|
|
94071
|
+
if (!res.ok) {
|
|
94072
|
+
const body = await res.text().catch(() => "");
|
|
94073
|
+
const err = new Error("Linear file upload PUT failed");
|
|
94074
|
+
err.status = res.status;
|
|
94075
|
+
err.body = body;
|
|
94076
|
+
throw err;
|
|
94077
|
+
}
|
|
94078
|
+
return { assetUrl: up.assetUrl };
|
|
94079
|
+
}
|
|
94080
|
+
async function createAttachmentForUrl(apiKey, input) {
|
|
94081
|
+
const mutation = `mutation CreateAttachment(
|
|
94082
|
+
$issueId: String!, $url: String!, $title: String!, $subtitle: String
|
|
94083
|
+
) {
|
|
94084
|
+
attachmentCreate(input: { issueId: $issueId, url: $url, title: $title, subtitle: $subtitle }) {
|
|
94085
|
+
success
|
|
94086
|
+
attachment { id }
|
|
94087
|
+
}
|
|
94088
|
+
}`;
|
|
94089
|
+
const data = await linearRequest(apiKey, mutation, {
|
|
94090
|
+
issueId: input.issueId,
|
|
94091
|
+
url: input.url,
|
|
94092
|
+
title: input.title,
|
|
94093
|
+
subtitle: input.subtitle ?? null
|
|
94094
|
+
});
|
|
94095
|
+
const id = data.attachmentCreate.attachment?.id;
|
|
94096
|
+
if (!id)
|
|
94097
|
+
throw new Error("attachmentCreate returned no attachment id");
|
|
94098
|
+
return id;
|
|
94099
|
+
}
|
|
94100
|
+
async function updateAttachmentUrl(apiKey, attachmentId, url2, subtitle) {
|
|
94101
|
+
const mutation = subtitle === undefined ? `mutation UpdateAttachmentUrl($id: String!, $url: String!) {
|
|
94102
|
+
attachmentUpdate(id: $id, input: { url: $url }) { success }
|
|
94103
|
+
}` : `mutation UpdateAttachmentUrl($id: String!, $url: String!, $subtitle: String!) {
|
|
94104
|
+
attachmentUpdate(id: $id, input: { url: $url, subtitle: $subtitle }) { success }
|
|
94105
|
+
}`;
|
|
94106
|
+
const variables = subtitle === undefined ? { id: attachmentId, url: url2 } : { id: attachmentId, url: url2, subtitle };
|
|
94107
|
+
await linearRequest(apiKey, mutation, variables);
|
|
94108
|
+
}
|
|
94007
94109
|
async function addReactionToComment(apiKey, commentId, emoji3) {
|
|
94008
94110
|
const mutation = `mutation Reaction($commentId: String!, $emoji: String!) {
|
|
94009
94111
|
reactionCreate(input: { commentId: $commentId, emoji: $emoji }) { success }
|
|
@@ -94332,6 +94434,7 @@ async function removeLabelFromIssue(apiKey, issueId, labelId) {
|
|
|
94332
94434
|
}
|
|
94333
94435
|
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:";
|
|
94334
94436
|
var init_linear = __esm(() => {
|
|
94437
|
+
init_types2();
|
|
94335
94438
|
linearRequestInternals = {
|
|
94336
94439
|
sleep: (ms) => Bun.sleep(ms)
|
|
94337
94440
|
};
|
|
@@ -94526,6 +94629,7 @@ class AgentCoordinator {
|
|
|
94526
94629
|
this.spawnNext();
|
|
94527
94630
|
const prStatus = await this.scanDoneForConflicts();
|
|
94528
94631
|
await this.reportProgress();
|
|
94632
|
+
await this.syncWorkerTasks();
|
|
94529
94633
|
const buckets = {
|
|
94530
94634
|
todo: todo.length,
|
|
94531
94635
|
inProgress: inProgress.length,
|
|
@@ -94580,12 +94684,26 @@ class AgentCoordinator {
|
|
|
94580
94684
|
} catch (err) {
|
|
94581
94685
|
this.deps.onLog(`! Linear progress comment failed for ${w.issueIdentifier}: ${err.message}`, "red");
|
|
94582
94686
|
}
|
|
94583
|
-
|
|
94584
|
-
|
|
94585
|
-
|
|
94586
|
-
|
|
94587
|
-
|
|
94588
|
-
|
|
94687
|
+
}
|
|
94688
|
+
}
|
|
94689
|
+
async syncWorkerTasks() {
|
|
94690
|
+
if (!this.deps.syncTasks || !this.deps.getIterationCount)
|
|
94691
|
+
return;
|
|
94692
|
+
for (const w of this.workers) {
|
|
94693
|
+
let count;
|
|
94694
|
+
try {
|
|
94695
|
+
count = await this.deps.getIterationCount(w.changeName);
|
|
94696
|
+
} catch (err) {
|
|
94697
|
+
this.deps.onLog(`! iteration count read failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
|
|
94698
|
+
continue;
|
|
94699
|
+
}
|
|
94700
|
+
if (count === w.lastSyncedIteration)
|
|
94701
|
+
continue;
|
|
94702
|
+
try {
|
|
94703
|
+
await this.deps.syncTasks(w, count);
|
|
94704
|
+
w.lastSyncedIteration = count;
|
|
94705
|
+
} catch (err) {
|
|
94706
|
+
this.deps.onLog(`! sync-tasks (poll) failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
|
|
94589
94707
|
}
|
|
94590
94708
|
}
|
|
94591
94709
|
}
|
|
@@ -94745,6 +94863,7 @@ class AgentCoordinator {
|
|
|
94745
94863
|
mode,
|
|
94746
94864
|
kill: handle.kill,
|
|
94747
94865
|
lastReportedIteration: 0,
|
|
94866
|
+
lastSyncedIteration: 0,
|
|
94748
94867
|
restarting: false
|
|
94749
94868
|
};
|
|
94750
94869
|
this.workers.push(worker);
|
|
@@ -94828,6 +94947,7 @@ class AgentCoordinator {
|
|
|
94828
94947
|
mode,
|
|
94829
94948
|
kill: () => {},
|
|
94830
94949
|
lastReportedIteration: 0,
|
|
94950
|
+
lastSyncedIteration: 0,
|
|
94831
94951
|
restarting: false
|
|
94832
94952
|
};
|
|
94833
94953
|
try {
|
|
@@ -94935,7 +95055,7 @@ var init_coordinator = __esm(() => {
|
|
|
94935
95055
|
import { join as join18 } from "path";
|
|
94936
95056
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
94937
95057
|
function changeNameForIssue(issue2) {
|
|
94938
|
-
const slug = issue2.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")
|
|
95058
|
+
const slug = issue2.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40).replace(/^-+|-+$/g, "");
|
|
94939
95059
|
return slug ? `${issue2.identifier.toLowerCase()}-${slug}` : issue2.identifier.toLowerCase();
|
|
94940
95060
|
}
|
|
94941
95061
|
async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = [], appendPrompt = "") {
|
|
@@ -96544,9 +96664,139 @@ var init_comment_sync = __esm(() => {
|
|
|
96544
96664
|
init_linear_sync();
|
|
96545
96665
|
});
|
|
96546
96666
|
|
|
96547
|
-
// apps/agent/src/agent/
|
|
96548
|
-
import { join as join22 } from "path";
|
|
96667
|
+
// apps/agent/src/agent/linear-sync/spec-attachments.ts
|
|
96668
|
+
import { dirname as dirname8, join as join22 } from "path";
|
|
96549
96669
|
import { mkdir as mkdir7 } from "fs/promises";
|
|
96670
|
+
async function readStateJson2(statePath) {
|
|
96671
|
+
const file2 = Bun.file(statePath);
|
|
96672
|
+
if (!await file2.exists())
|
|
96673
|
+
return null;
|
|
96674
|
+
try {
|
|
96675
|
+
return await file2.json();
|
|
96676
|
+
} catch {
|
|
96677
|
+
return null;
|
|
96678
|
+
}
|
|
96679
|
+
}
|
|
96680
|
+
async function writeStateJson2(statePath, state) {
|
|
96681
|
+
await mkdir7(dirname8(statePath), { recursive: true });
|
|
96682
|
+
await Bun.write(statePath, JSON.stringify(state, null, 2) + `
|
|
96683
|
+
`);
|
|
96684
|
+
}
|
|
96685
|
+
function readSpecState(state) {
|
|
96686
|
+
const raw = state?.specAttachments ?? {};
|
|
96687
|
+
return {
|
|
96688
|
+
proposal: {
|
|
96689
|
+
attachmentId: raw?.proposal?.attachmentId ?? null,
|
|
96690
|
+
sha256: raw?.proposal?.sha256 ?? null
|
|
96691
|
+
},
|
|
96692
|
+
design: {
|
|
96693
|
+
attachmentId: raw?.design?.attachmentId ?? null,
|
|
96694
|
+
sha256: raw?.design?.sha256 ?? null
|
|
96695
|
+
}
|
|
96696
|
+
};
|
|
96697
|
+
}
|
|
96698
|
+
async function patchSpecState(statePath, patch) {
|
|
96699
|
+
const existing = await readStateJson2(statePath) ?? {};
|
|
96700
|
+
const current = readSpecState(existing);
|
|
96701
|
+
const next = { ...current, [patch.slot]: patch.value };
|
|
96702
|
+
await writeStateJson2(statePath, { ...existing, specAttachments: next });
|
|
96703
|
+
}
|
|
96704
|
+
function sha256Hex(bytes) {
|
|
96705
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
96706
|
+
hasher.update(bytes);
|
|
96707
|
+
return hasher.digest("hex");
|
|
96708
|
+
}
|
|
96709
|
+
async function syncSlot(deps, slot) {
|
|
96710
|
+
const filename = SLOT_FILES[slot];
|
|
96711
|
+
const path = join22(deps.changeDir, filename);
|
|
96712
|
+
const file2 = Bun.file(path);
|
|
96713
|
+
if (!await file2.exists()) {
|
|
96714
|
+
deps.log(` spec-attachments: ${filename} missing, skipping`, "gray");
|
|
96715
|
+
return;
|
|
96716
|
+
}
|
|
96717
|
+
let bytes;
|
|
96718
|
+
try {
|
|
96719
|
+
bytes = await file2.bytes();
|
|
96720
|
+
} catch (err) {
|
|
96721
|
+
deps.log(`! spec-attachments: read ${filename} failed: ${err.message}`, "yellow");
|
|
96722
|
+
return;
|
|
96723
|
+
}
|
|
96724
|
+
const hash2 = sha256Hex(bytes);
|
|
96725
|
+
const state = await readStateJson2(deps.statePath);
|
|
96726
|
+
const current = readSpecState(state)[slot] ?? EMPTY_SLOT;
|
|
96727
|
+
if (current.attachmentId && current.sha256 === hash2) {
|
|
96728
|
+
deps.log(` spec-attachments: ${filename} unchanged, skipping`, "gray");
|
|
96729
|
+
return;
|
|
96730
|
+
}
|
|
96731
|
+
const subtitle = `iteration ${deps.iteration}`;
|
|
96732
|
+
let assetUrl;
|
|
96733
|
+
try {
|
|
96734
|
+
const uploaded = await deps.mutations.uploadFileToLinear(deps.apiKey, {
|
|
96735
|
+
filename,
|
|
96736
|
+
contentType: "text/markdown",
|
|
96737
|
+
bytes
|
|
96738
|
+
});
|
|
96739
|
+
assetUrl = uploaded.assetUrl;
|
|
96740
|
+
} catch (err) {
|
|
96741
|
+
deps.log(`! spec-attachments: upload ${filename} failed: ${err.message}`, "yellow");
|
|
96742
|
+
return;
|
|
96743
|
+
}
|
|
96744
|
+
if (current.attachmentId) {
|
|
96745
|
+
try {
|
|
96746
|
+
await deps.mutations.updateAttachmentUrl(deps.apiKey, current.attachmentId, assetUrl, subtitle);
|
|
96747
|
+
await patchSpecState(deps.statePath, {
|
|
96748
|
+
slot,
|
|
96749
|
+
value: { attachmentId: current.attachmentId, sha256: hash2 }
|
|
96750
|
+
});
|
|
96751
|
+
deps.log(` spec-attachments: refreshed ${filename}`, "gray");
|
|
96752
|
+
return;
|
|
96753
|
+
} catch (err) {
|
|
96754
|
+
if (!isCommentNotFoundError(err)) {
|
|
96755
|
+
deps.log(`! spec-attachments: updateAttachmentUrl ${filename} failed: ${err.message}`, "yellow");
|
|
96756
|
+
return;
|
|
96757
|
+
}
|
|
96758
|
+
deps.log(` spec-attachments: attachment ${current.attachmentId} not found \u2014 recreating`, "gray");
|
|
96759
|
+
}
|
|
96760
|
+
}
|
|
96761
|
+
let newId;
|
|
96762
|
+
try {
|
|
96763
|
+
newId = await deps.mutations.createAttachmentForUrl(deps.apiKey, {
|
|
96764
|
+
issueId: deps.issueId,
|
|
96765
|
+
url: assetUrl,
|
|
96766
|
+
title: ATTACHMENT_TITLES[slot],
|
|
96767
|
+
subtitle
|
|
96768
|
+
});
|
|
96769
|
+
} catch (err) {
|
|
96770
|
+
deps.log(`! spec-attachments: createAttachmentForUrl ${filename} failed: ${err.message}`, "yellow");
|
|
96771
|
+
return;
|
|
96772
|
+
}
|
|
96773
|
+
await patchSpecState(deps.statePath, {
|
|
96774
|
+
slot,
|
|
96775
|
+
value: { attachmentId: newId, sha256: hash2 }
|
|
96776
|
+
});
|
|
96777
|
+
deps.log(` spec-attachments: created ${filename} attachment`, "gray");
|
|
96778
|
+
}
|
|
96779
|
+
async function syncSpecAttachments(deps) {
|
|
96780
|
+
await syncSlot(deps, "proposal");
|
|
96781
|
+
await syncSlot(deps, "design");
|
|
96782
|
+
}
|
|
96783
|
+
var ATTACHMENT_TITLES, SLOT_FILES, EMPTY_SLOT;
|
|
96784
|
+
var init_spec_attachments = __esm(() => {
|
|
96785
|
+
init_comment_sync();
|
|
96786
|
+
ATTACHMENT_TITLES = {
|
|
96787
|
+
proposal: "Ralph proposal",
|
|
96788
|
+
design: "Ralph design"
|
|
96789
|
+
};
|
|
96790
|
+
SLOT_FILES = {
|
|
96791
|
+
proposal: "proposal.md",
|
|
96792
|
+
design: "design.md"
|
|
96793
|
+
};
|
|
96794
|
+
EMPTY_SLOT = { attachmentId: null, sha256: null };
|
|
96795
|
+
});
|
|
96796
|
+
|
|
96797
|
+
// apps/agent/src/agent/wire.ts
|
|
96798
|
+
import { join as join23 } from "path";
|
|
96799
|
+
import { mkdir as mkdir8 } from "fs/promises";
|
|
96550
96800
|
async function pickOpenPrUrlFromAttachments(urls, issueIdent, cmd, cwd2, onLog) {
|
|
96551
96801
|
const candidates = urls.filter((url2) => GITHUB_PR_URL_RE.test(url2));
|
|
96552
96802
|
let sawNonOpenPr = false;
|
|
@@ -96750,7 +97000,7 @@ function buildAgentCoordinator(input) {
|
|
|
96750
97000
|
onWorkerOutput,
|
|
96751
97001
|
onWorkerCmd
|
|
96752
97002
|
} = input;
|
|
96753
|
-
const logsDir =
|
|
97003
|
+
const logsDir = join23(projectRoot, ".ralph", "logs");
|
|
96754
97004
|
const concurrency = args.concurrency || cfg.concurrency;
|
|
96755
97005
|
const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
|
|
96756
97006
|
const indicators = mergeIndicators(cfg.linear.indicators, args.indicators);
|
|
@@ -96942,7 +97192,15 @@ function buildAgentCoordinator(input) {
|
|
|
96942
97192
|
async function prepare(issue2, mode, trigger) {
|
|
96943
97193
|
const { workerCwd, scaffoldTasksDir, scaffoldStatesDir, branch } = await setupWorktree(issue2);
|
|
96944
97194
|
let changeName;
|
|
96945
|
-
const
|
|
97195
|
+
const wtLayoutPre = projectLayout(workerCwd);
|
|
97196
|
+
const derivedName = changeNameForIssue(issue2);
|
|
97197
|
+
const tasksMdPath = join23(wtLayoutPre.changeDir(derivedName), "tasks.md");
|
|
97198
|
+
const tasksMdExists = await Bun.file(tasksMdPath).exists();
|
|
97199
|
+
const needsScaffold = !tasksMdExists;
|
|
97200
|
+
if (mode !== "fresh" && needsScaffold) {
|
|
97201
|
+
onLog(` ${issue2.identifier}: tasks.md missing at ${tasksMdPath} \u2014 rescaffolding`, "yellow");
|
|
97202
|
+
}
|
|
97203
|
+
const isFresh = mode === "fresh" || needsScaffold;
|
|
96946
97204
|
if (isFresh) {
|
|
96947
97205
|
let comments = [];
|
|
96948
97206
|
try {
|
|
@@ -96972,10 +97230,9 @@ function buildAgentCoordinator(input) {
|
|
|
96972
97230
|
`);
|
|
96973
97231
|
changeName = await scaffoldChangeForIssue(scaffoldTasksDir, scaffoldStatesDir, issue2, comments, appendPrompt);
|
|
96974
97232
|
} else {
|
|
96975
|
-
changeName =
|
|
96976
|
-
|
|
96977
|
-
await
|
|
96978
|
-
await mkdir7(wtLayout.taskStateDir(changeName), { recursive: true });
|
|
97233
|
+
changeName = derivedName;
|
|
97234
|
+
await mkdir8(wtLayoutPre.changeDir(changeName), { recursive: true });
|
|
97235
|
+
await mkdir8(wtLayoutPre.taskStateDir(changeName), { recursive: true });
|
|
96979
97236
|
}
|
|
96980
97237
|
cwdByChange.set(changeName, workerCwd);
|
|
96981
97238
|
statesDirByChange.set(changeName, scaffoldStatesDir);
|
|
@@ -96984,7 +97241,7 @@ function buildAgentCoordinator(input) {
|
|
|
96984
97241
|
branchByChange.set(changeName, branch);
|
|
96985
97242
|
if (mode === "review") {
|
|
96986
97243
|
const wtLayout = projectLayout(workerCwd);
|
|
96987
|
-
const tasksFile =
|
|
97244
|
+
const tasksFile = join23(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
|
|
96988
97245
|
let body;
|
|
96989
97246
|
let heading;
|
|
96990
97247
|
if (trigger) {
|
|
@@ -97009,7 +97266,7 @@ function buildAgentCoordinator(input) {
|
|
|
97009
97266
|
await reactivateState2(wtLayout.stateFile(changeName), changeName);
|
|
97010
97267
|
} else if (mode === "conflict-fix") {
|
|
97011
97268
|
const wtLayout = projectLayout(workerCwd);
|
|
97012
|
-
const tasksFile =
|
|
97269
|
+
const tasksFile = join23(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
|
|
97013
97270
|
const prUrl = prByChange.get(changeName);
|
|
97014
97271
|
const body = [
|
|
97015
97272
|
`The PR for this change has merge conflicts with \`${cfg.prBaseBranch}\`.`,
|
|
@@ -97089,7 +97346,7 @@ PR: ${prUrl}` : ""
|
|
|
97089
97346
|
return c;
|
|
97090
97347
|
}
|
|
97091
97348
|
function defaultSpawn(changeName, cmd, cwd2, note) {
|
|
97092
|
-
const logFilePath =
|
|
97349
|
+
const logFilePath = join23(logsDir, `${changeName}.log`);
|
|
97093
97350
|
const ANSI_RE2 = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
97094
97351
|
const BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
|
|
97095
97352
|
const STATUS_BAR_LINE_RE = /^[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u2713\u2717]\s+iter\s+\d+/;
|
|
@@ -97148,7 +97405,7 @@ PR: ${prUrl}` : ""
|
|
|
97148
97405
|
function spawnWorker(changeName) {
|
|
97149
97406
|
const cwd2 = cwdByChange.get(changeName) ?? projectRoot;
|
|
97150
97407
|
const injected = input.runners?.spawnWorker;
|
|
97151
|
-
const missionTasksPath =
|
|
97408
|
+
const missionTasksPath = join23(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
|
|
97152
97409
|
const prevTasksPromise = (async () => {
|
|
97153
97410
|
const f2 = Bun.file(missionTasksPath);
|
|
97154
97411
|
return await f2.exists() ? await f2.text() : "";
|
|
@@ -97156,7 +97413,7 @@ PR: ${prUrl}` : ""
|
|
|
97156
97413
|
let logFilePath;
|
|
97157
97414
|
let handle;
|
|
97158
97415
|
if (injected) {
|
|
97159
|
-
logFilePath =
|
|
97416
|
+
logFilePath = join23(logsDir, `${changeName}.log`);
|
|
97160
97417
|
handle = injected(buildTaskCmdFor(changeName), cwd2);
|
|
97161
97418
|
} else {
|
|
97162
97419
|
const r = defaultSpawn(changeName, buildTaskCmdFor(changeName), cwd2, `spawn at ${new Date().toISOString()}`);
|
|
@@ -97372,7 +97629,15 @@ PR: ${prUrl}` : ""
|
|
|
97372
97629
|
const handle = cfg.linear.mentionHandle;
|
|
97373
97630
|
let candidates = [];
|
|
97374
97631
|
try {
|
|
97375
|
-
candidates = await fetchMentionScanIssues(apiKey, {
|
|
97632
|
+
candidates = await fetchMentionScanIssues(apiKey, {
|
|
97633
|
+
team,
|
|
97634
|
+
assignee,
|
|
97635
|
+
indicators: {
|
|
97636
|
+
...indicators.getTodo !== undefined ? { getTodo: indicators.getTodo } : {},
|
|
97637
|
+
...indicators.getInProgress !== undefined ? { getInProgress: indicators.getInProgress } : {},
|
|
97638
|
+
...indicators.setDone !== undefined ? { setDone: indicators.setDone } : {}
|
|
97639
|
+
}
|
|
97640
|
+
});
|
|
97376
97641
|
} catch (err) {
|
|
97377
97642
|
if (isRateLimitedError(err)) {
|
|
97378
97643
|
onLog(`! mention scan: rate limited, deferring rest of scan to next poll`, "yellow");
|
|
@@ -97663,6 +97928,12 @@ PR: ${prUrl}` : ""
|
|
|
97663
97928
|
updateIssueComment,
|
|
97664
97929
|
deleteIssueComment
|
|
97665
97930
|
};
|
|
97931
|
+
const specAttachmentsEnabled = Boolean(commentSyncEnabled && cfg.linear.syncSpecsAsAttachments);
|
|
97932
|
+
const specAttachmentMutations = {
|
|
97933
|
+
uploadFileToLinear,
|
|
97934
|
+
createAttachmentForUrl,
|
|
97935
|
+
updateAttachmentUrl
|
|
97936
|
+
};
|
|
97666
97937
|
const coord = new AgentCoordinator({
|
|
97667
97938
|
fetchTodo: () => fetchByGet(indicators.getTodo, excludeFromTodo),
|
|
97668
97939
|
fetchInProgress: () => fetchByGet(indicators.getInProgress, []),
|
|
@@ -97716,6 +97987,17 @@ PR: ${prUrl}` : ""
|
|
|
97716
97987
|
log: onLog,
|
|
97717
97988
|
mutations: commentMutations
|
|
97718
97989
|
});
|
|
97990
|
+
if (specAttachmentsEnabled) {
|
|
97991
|
+
await syncSpecAttachments({
|
|
97992
|
+
apiKey,
|
|
97993
|
+
issueId: worker.issueId,
|
|
97994
|
+
statePath,
|
|
97995
|
+
changeDir,
|
|
97996
|
+
iteration,
|
|
97997
|
+
log: onLog,
|
|
97998
|
+
mutations: specAttachmentMutations
|
|
97999
|
+
});
|
|
98000
|
+
}
|
|
97719
98001
|
},
|
|
97720
98002
|
onSteeringAppended: async (changeName, message) => {
|
|
97721
98003
|
const root = cwdByChange.get(changeName) ?? projectRoot;
|
|
@@ -97853,6 +98135,7 @@ var init_wire = __esm(() => {
|
|
|
97853
98135
|
init_gate();
|
|
97854
98136
|
init_workflow();
|
|
97855
98137
|
init_comment_sync();
|
|
98138
|
+
init_spec_attachments();
|
|
97856
98139
|
GITHUB_PR_URL_RE = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/;
|
|
97857
98140
|
bunGitRunner = {
|
|
97858
98141
|
run: async (args, cwd2) => {
|
|
@@ -98180,7 +98463,7 @@ var init_SteeringField = __esm(async () => {
|
|
|
98180
98463
|
});
|
|
98181
98464
|
|
|
98182
98465
|
// apps/agent/src/components/AgentMode.tsx
|
|
98183
|
-
import { join as
|
|
98466
|
+
import { join as join24 } from "path";
|
|
98184
98467
|
async function appendSteeringImpl(changeDir, message) {
|
|
98185
98468
|
await runWithContext(createDefaultContext(), async () => {
|
|
98186
98469
|
appendSteeringMessage(changeDir, message);
|
|
@@ -98635,7 +98918,7 @@ function AgentMode({
|
|
|
98635
98918
|
(async () => {
|
|
98636
98919
|
for (const [changeName, meta3] of workerMetaRef.current) {
|
|
98637
98920
|
try {
|
|
98638
|
-
const file2 = Bun.file(
|
|
98921
|
+
const file2 = Bun.file(join24(meta3.statesDir, changeName, ".ralph-state.json"));
|
|
98639
98922
|
if (await file2.exists()) {
|
|
98640
98923
|
const json2 = await file2.json();
|
|
98641
98924
|
meta3.iter = json2.iteration ?? meta3.iter;
|
|
@@ -98645,9 +98928,9 @@ function AgentMode({
|
|
|
98645
98928
|
}
|
|
98646
98929
|
if (meta3.changeDir) {
|
|
98647
98930
|
try {
|
|
98648
|
-
const tasksFile = Bun.file(
|
|
98649
|
-
const proposalFile = Bun.file(
|
|
98650
|
-
const designFile = Bun.file(
|
|
98931
|
+
const tasksFile = Bun.file(join24(meta3.changeDir, "tasks.md"));
|
|
98932
|
+
const proposalFile = Bun.file(join24(meta3.changeDir, "proposal.md"));
|
|
98933
|
+
const designFile = Bun.file(join24(meta3.changeDir, "design.md"));
|
|
98651
98934
|
const [tasksText, proposalText, designText] = await Promise.all([
|
|
98652
98935
|
tasksFile.exists().then((ok) => ok ? tasksFile.text() : null),
|
|
98653
98936
|
proposalFile.exists().then((ok) => ok ? proposalFile.text() : null),
|
|
@@ -98695,7 +98978,7 @@ function AgentMode({
|
|
|
98695
98978
|
use_input_default((input, key) => {
|
|
98696
98979
|
if (steeringFocusedRef.current)
|
|
98697
98980
|
return;
|
|
98698
|
-
if (key.ctrl &&
|
|
98981
|
+
if (key.ctrl && (input === "l" || input === "L")) {
|
|
98699
98982
|
if (activeCount > 0)
|
|
98700
98983
|
setShowAllSubtasks((v) => !v);
|
|
98701
98984
|
return;
|
|
@@ -99456,7 +99739,7 @@ function AgentMode({
|
|
|
99456
99739
|
}),
|
|
99457
99740
|
!showAllSubtasks && subtasks.length > MAX_PENDING_DISPLAY && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
|
|
99458
99741
|
dimColor: true,
|
|
99459
|
-
children: ` \u2026 +${subtasks.length - MAX_PENDING_DISPLAY} more (CTRL+
|
|
99742
|
+
children: ` \u2026 +${subtasks.length - MAX_PENDING_DISPLAY} more (CTRL+L to expand)`
|
|
99460
99743
|
}, undefined, false, undefined, this)
|
|
99461
99744
|
]
|
|
99462
99745
|
}, undefined, true, undefined, this),
|
|
@@ -99478,7 +99761,7 @@ function AgentMode({
|
|
|
99478
99761
|
},
|
|
99479
99762
|
onSubmit: async (message) => {
|
|
99480
99763
|
try {
|
|
99481
|
-
await appendSteering(
|
|
99764
|
+
await appendSteering(join24(tasksDir, w.changeName), message);
|
|
99482
99765
|
} catch (err) {
|
|
99483
99766
|
appendLog(`! steering append failed for ${w.changeName}: ${err.message}`, "red");
|
|
99484
99767
|
throw err;
|
|
@@ -99688,7 +99971,7 @@ var exports_list = {};
|
|
|
99688
99971
|
__export(exports_list, {
|
|
99689
99972
|
runList: () => runList
|
|
99690
99973
|
});
|
|
99691
|
-
import { join as
|
|
99974
|
+
import { join as join25 } from "path";
|
|
99692
99975
|
function countTaskItems(content) {
|
|
99693
99976
|
const checked = (content.match(/^- \[x\]/gm) ?? []).length;
|
|
99694
99977
|
const unchecked = (content.match(/^- \[ \]/gm) ?? []).length;
|
|
@@ -99701,13 +99984,13 @@ function buildLocalRows(statesDir, projectRoot) {
|
|
|
99701
99984
|
const sources = [{ dir: statesDir, label: "main" }];
|
|
99702
99985
|
const worktreesRoot = worktreesDir2(projectRoot);
|
|
99703
99986
|
for (const wt of storage.list(worktreesRoot)) {
|
|
99704
|
-
sources.push({ dir:
|
|
99987
|
+
sources.push({ dir: join25(worktreesRoot, wt, ".ralph", "tasks"), label: `wt:${wt}` });
|
|
99705
99988
|
}
|
|
99706
99989
|
for (const { dir, label } of sources) {
|
|
99707
99990
|
for (const entry of storage.list(dir)) {
|
|
99708
99991
|
if (seen.has(entry))
|
|
99709
99992
|
continue;
|
|
99710
|
-
const raw = storage.read(
|
|
99993
|
+
const raw = storage.read(join25(dir, entry, ".ralph-state.json"));
|
|
99711
99994
|
if (raw === null)
|
|
99712
99995
|
continue;
|
|
99713
99996
|
let state;
|
|
@@ -99722,7 +100005,7 @@ function buildLocalRows(statesDir, projectRoot) {
|
|
|
99722
100005
|
const firstLine = promptRaw.split(`
|
|
99723
100006
|
`).find((l) => l.trim() !== "") ?? "";
|
|
99724
100007
|
let progress = "\u2014";
|
|
99725
|
-
const tasksContent = storage.read(
|
|
100008
|
+
const tasksContent = storage.read(join25(dir, entry, "tasks.md"));
|
|
99726
100009
|
if (tasksContent !== null) {
|
|
99727
100010
|
const { checked, unchecked } = countTaskItems(tasksContent);
|
|
99728
100011
|
const total = checked + unchecked;
|
|
@@ -100149,8 +100432,8 @@ var exports_json_runner = {};
|
|
|
100149
100432
|
__export(exports_json_runner, {
|
|
100150
100433
|
runAgentJson: () => runAgentJson
|
|
100151
100434
|
});
|
|
100152
|
-
import { join as
|
|
100153
|
-
import { mkdir as
|
|
100435
|
+
import { join as join26 } from "path";
|
|
100436
|
+
import { mkdir as mkdir9 } from "fs/promises";
|
|
100154
100437
|
import { homedir as homedir5 } from "os";
|
|
100155
100438
|
function cleanOutputLine2(raw) {
|
|
100156
100439
|
const clean = raw.replace(ANSI_STRIP_RE2, "").trim();
|
|
@@ -100174,7 +100457,7 @@ async function runAgentJson({
|
|
|
100174
100457
|
statesDir,
|
|
100175
100458
|
tasksDir
|
|
100176
100459
|
}) {
|
|
100177
|
-
await
|
|
100460
|
+
await mkdir9(join26(homedir5(), ".ralph"), { recursive: true }).catch(() => {
|
|
100178
100461
|
return;
|
|
100179
100462
|
});
|
|
100180
100463
|
const cfgPath = await ensureRalphyConfig(projectRoot);
|
|
@@ -100328,8 +100611,8 @@ var exports_src2 = {};
|
|
|
100328
100611
|
__export(exports_src2, {
|
|
100329
100612
|
main: () => main2
|
|
100330
100613
|
});
|
|
100331
|
-
import { mkdir as
|
|
100332
|
-
import { join as
|
|
100614
|
+
import { mkdir as mkdir10 } from "fs/promises";
|
|
100615
|
+
import { join as join27 } from "path";
|
|
100333
100616
|
async function main2(argv) {
|
|
100334
100617
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
100335
100618
|
printHelp2();
|
|
@@ -100363,9 +100646,9 @@ async function main2(argv) {
|
|
|
100363
100646
|
});
|
|
100364
100647
|
return typeof process.exitCode === "number" ? process.exitCode : 0;
|
|
100365
100648
|
}
|
|
100366
|
-
await
|
|
100367
|
-
await
|
|
100368
|
-
await
|
|
100649
|
+
await mkdir10(statesDir, { recursive: true });
|
|
100650
|
+
await mkdir10(tasksDir, { recursive: true });
|
|
100651
|
+
await mkdir10(join27(projectRoot, ".ralph"), { recursive: true });
|
|
100369
100652
|
if (args.jsonOutput) {
|
|
100370
100653
|
const { runAgentJson: runAgentJson2 } = await Promise.resolve().then(() => (init_json_runner(), exports_json_runner));
|
|
100371
100654
|
await runAgentJson2({ args, projectRoot, statesDir, tasksDir });
|