@neriros/ralphy 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/shell/index.js +315 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -187,6 +187,7 @@ Example `ralphy.config.json`:
|
|
|
187
187
|
"mentionHandle": "@ralphy",
|
|
188
188
|
"codeReviewTrigger": true,
|
|
189
189
|
"codeReviewStaleHours": 24,
|
|
190
|
+
"syncTasksToDescription": false,
|
|
190
191
|
"indicators": {
|
|
191
192
|
"getTodo": { "filter": [{ "type": "status", "value": "Todo" }] },
|
|
192
193
|
"getInProgress": {
|
|
@@ -219,7 +220,7 @@ When a Linear issue is in a done state and a reviewer adds the `getReview` marke
|
|
|
219
220
|
|
|
220
221
|
#### `@ralphy` mention trigger
|
|
221
222
|
|
|
222
|
-
Set `linear.mentionTrigger: true` to scan
|
|
223
|
+
Set `linear.mentionTrigger: true` to scan Linear issue comments on every non-cancelled issue (Todo, In Progress, Backlog, Triage, Done) _and_ on the linked GitHub PR for a configurable handle (`linear.mentionHandle`, default `@ralphy`). Each unprocessed mention queues the issue as a review run, with the mention text used **verbatim** as the prepended task. Idempotency: a mention is processed when its `createdAt` is older than Ralph's latest `🔁 picked up` Linear comment, so the same comment never re-fires. Requires `gh` for the GitHub side.
|
|
223
224
|
|
|
224
225
|
#### Code-review iteration
|
|
225
226
|
|
|
@@ -230,6 +231,10 @@ Set `linear.codeReviewTrigger: true` (or pass `--code-review`) to watch open, un
|
|
|
230
231
|
|
|
231
232
|
The loop exits; the next poll re-checks the PR. The cycle continues until the PR is **approved** or **merged**. If the reviewer is silent for more than `linear.codeReviewStaleHours` (default `24`, `0` disables) while Ralph is the last actor, one `@`-mention ping comment is posted on the GitHub PR.
|
|
232
233
|
|
|
234
|
+
#### Sync tasks into Linear description
|
|
235
|
+
|
|
236
|
+
Set `linear.syncTasksToDescription: true` to mirror the active change's `tasks.md` into the linked Linear issue description. Ralph writes a checklist between sentinel HTML comments (`<!-- ralphy:tasks:start -->` / `<!-- ralphy:tasks:end -->`); any content outside the markers is preserved verbatim. The block is refreshed when the worker launches, on the same cadence as `updateEveryIterations`, and on done-transition. Sync failures are logged but never abort the loop.
|
|
237
|
+
|
|
233
238
|
#### Conflict re-fix
|
|
234
239
|
|
|
235
240
|
Done issues whose PR `gh pr view --json mergeable` reports as `CONFLICTING` get `setConflicted` applied and a conflict-fix task prepended. The scanner is resilient to:
|
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.2.0")
|
|
18932
|
+
return "3.2.0";
|
|
18933
18933
|
} catch {}
|
|
18934
18934
|
const dirsToTry = [];
|
|
18935
18935
|
try {
|
|
@@ -68864,7 +68864,7 @@ function StatusBar({
|
|
|
68864
68864
|
return () => clearInterval(id);
|
|
68865
68865
|
}, [isRunning, startedAt]);
|
|
68866
68866
|
const { columns } = useTerminalSize();
|
|
68867
|
-
const barWidth = Math.max(8,
|
|
68867
|
+
const barWidth = Math.max(8, columns);
|
|
68868
68868
|
const bar = "\u2500".repeat(barWidth);
|
|
68869
68869
|
return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
|
|
68870
68870
|
flexDirection: "column",
|
|
@@ -70223,6 +70223,37 @@ ${failureOutput.trim()}
|
|
|
70223
70223
|
${fence}`;
|
|
70224
70224
|
await Bun.write(tasksPath, prependSection(existing, stamped, body));
|
|
70225
70225
|
}
|
|
70226
|
+
function normalizeNewlyAppendedSectionWithReport(previous, current) {
|
|
70227
|
+
const prevHeadings = new Set;
|
|
70228
|
+
for (const line of previous.split(`
|
|
70229
|
+
`)) {
|
|
70230
|
+
if (line.startsWith("## "))
|
|
70231
|
+
prevHeadings.add(line);
|
|
70232
|
+
}
|
|
70233
|
+
const sections = current.split(/(?=^## )/m);
|
|
70234
|
+
const headings = [];
|
|
70235
|
+
let count = 0;
|
|
70236
|
+
const out = sections.map((section) => {
|
|
70237
|
+
const nlIdx = section.indexOf(`
|
|
70238
|
+
`);
|
|
70239
|
+
const headingLine = nlIdx === -1 ? section.replace(/\n$/, "") : section.slice(0, nlIdx);
|
|
70240
|
+
if (!headingLine.startsWith("## "))
|
|
70241
|
+
return section;
|
|
70242
|
+
if (prevHeadings.has(headingLine))
|
|
70243
|
+
return section;
|
|
70244
|
+
let localCount = 0;
|
|
70245
|
+
const rewritten = section.replace(/^(\s*)- \[[xX]\] (.+)$/gm, (_m, indent, rest2) => {
|
|
70246
|
+
localCount += 1;
|
|
70247
|
+
return `${indent}- [ ] ${rest2}`;
|
|
70248
|
+
});
|
|
70249
|
+
if (localCount > 0) {
|
|
70250
|
+
headings.push(headingLine.slice(3));
|
|
70251
|
+
count += localCount;
|
|
70252
|
+
}
|
|
70253
|
+
return rewritten;
|
|
70254
|
+
});
|
|
70255
|
+
return { text: count > 0 ? out.join("") : current, headings, count };
|
|
70256
|
+
}
|
|
70226
70257
|
var MISSION_TASKS_FILENAME = "tasks.md", AGENT_TASKS_FILENAME = "agent-tasks.md", FLOW_TASK_HEADING_PREFIXES;
|
|
70227
70258
|
var init_tasks_md = __esm(() => {
|
|
70228
70259
|
FLOW_TASK_HEADING_PREFIXES = [
|
|
@@ -92474,6 +92505,7 @@ var init_schema = __esm(() => {
|
|
|
92474
92505
|
mentionHandle: exports_external2.string().default("@ralphy"),
|
|
92475
92506
|
codeReviewTrigger: exports_external2.boolean().default(false),
|
|
92476
92507
|
codeReviewStaleHours: exports_external2.number().nonnegative().default(24),
|
|
92508
|
+
syncTasksToDescription: exports_external2.boolean().default(false),
|
|
92477
92509
|
indicators: IndicatorsSchema.default({})
|
|
92478
92510
|
}).strict().default({
|
|
92479
92511
|
postComments: true,
|
|
@@ -92482,6 +92514,7 @@ var init_schema = __esm(() => {
|
|
|
92482
92514
|
mentionHandle: "@ralphy",
|
|
92483
92515
|
codeReviewTrigger: false,
|
|
92484
92516
|
codeReviewStaleHours: 24,
|
|
92517
|
+
syncTasksToDescription: false,
|
|
92485
92518
|
indicators: {}
|
|
92486
92519
|
}),
|
|
92487
92520
|
github: exports_external2.object({
|
|
@@ -92630,6 +92663,11 @@ linear:
|
|
|
92630
92663
|
codeReviewTrigger: false
|
|
92631
92664
|
codeReviewStaleHours: 24
|
|
92632
92665
|
|
|
92666
|
+
# Mirror the loop's tasks.md into the Linear issue description as a
|
|
92667
|
+
# checklist between sentinel markers. Updates on worker launch, on the
|
|
92668
|
+
# same cadence as updateEveryIterations, and on done-transition.
|
|
92669
|
+
syncTasksToDescription: false
|
|
92670
|
+
|
|
92633
92671
|
# Indicators map Ralph lifecycle events to Linear labels/statuses.
|
|
92634
92672
|
# Grouped by lifecycle: each get* is followed by the set*/clear* that
|
|
92635
92673
|
# mutates the same state, so the lifecycle reads top-to-bottom.
|
|
@@ -93574,6 +93612,51 @@ function buildIssueFilter(spec) {
|
|
|
93574
93612
|
}
|
|
93575
93613
|
return where;
|
|
93576
93614
|
}
|
|
93615
|
+
async function fetchMentionScanIssues(apiKey, spec) {
|
|
93616
|
+
const where = {
|
|
93617
|
+
state: { type: { in: ["unstarted", "started", "backlog", "triage", "completed"] } }
|
|
93618
|
+
};
|
|
93619
|
+
if (spec.team)
|
|
93620
|
+
where.team = { key: { eq: spec.team } };
|
|
93621
|
+
if (spec.assignee) {
|
|
93622
|
+
if (spec.assignee === "me")
|
|
93623
|
+
where.assignee = { isMe: { eq: true } };
|
|
93624
|
+
else if (spec.assignee.includes("@"))
|
|
93625
|
+
where.assignee = { email: { eq: spec.assignee } };
|
|
93626
|
+
else
|
|
93627
|
+
where.assignee = { id: { eq: spec.assignee } };
|
|
93628
|
+
}
|
|
93629
|
+
const query = `query MentionScanIssues($filter: IssueFilter) {
|
|
93630
|
+
issues(filter: $filter, first: 50) {
|
|
93631
|
+
nodes {
|
|
93632
|
+
id identifier title description url priority createdAt
|
|
93633
|
+
state { name type }
|
|
93634
|
+
assignee { id email name }
|
|
93635
|
+
labels { nodes { name } }
|
|
93636
|
+
relations(first: 50) {
|
|
93637
|
+
nodes { type relatedIssue { id state { type } } }
|
|
93638
|
+
}
|
|
93639
|
+
}
|
|
93640
|
+
}
|
|
93641
|
+
}`;
|
|
93642
|
+
const data = await linearRequest(apiKey, query, {
|
|
93643
|
+
filter: where
|
|
93644
|
+
});
|
|
93645
|
+
const DONE_STATE_TYPES = new Set(["completed", "cancelled"]);
|
|
93646
|
+
return data.issues.nodes.map((n) => ({
|
|
93647
|
+
id: n.id,
|
|
93648
|
+
identifier: n.identifier,
|
|
93649
|
+
title: n.title,
|
|
93650
|
+
description: n.description,
|
|
93651
|
+
url: n.url,
|
|
93652
|
+
state: n.state,
|
|
93653
|
+
assignee: n.assignee,
|
|
93654
|
+
labels: n.labels.nodes.map((l) => l.name),
|
|
93655
|
+
priority: n.priority,
|
|
93656
|
+
createdAt: n.createdAt ?? "",
|
|
93657
|
+
blockedByIds: (n.relations?.nodes ?? []).filter((r) => r.type === "blocked_by" && !DONE_STATE_TYPES.has(r.relatedIssue.state.type)).map((r) => r.relatedIssue.id)
|
|
93658
|
+
}));
|
|
93659
|
+
}
|
|
93577
93660
|
async function fetchOpenIssues(apiKey, spec) {
|
|
93578
93661
|
const where = buildIssueFilter(spec);
|
|
93579
93662
|
const query = `query Issues($filter: IssueFilter) {
|
|
@@ -94117,6 +94200,13 @@ class AgentCoordinator {
|
|
|
94117
94200
|
} catch (err) {
|
|
94118
94201
|
this.deps.onLog(`! Linear progress comment failed for ${w.issueIdentifier}: ${err.message}`, "red");
|
|
94119
94202
|
}
|
|
94203
|
+
if (this.deps.syncTasks) {
|
|
94204
|
+
try {
|
|
94205
|
+
await this.deps.syncTasks(w, count);
|
|
94206
|
+
} catch (err) {
|
|
94207
|
+
this.deps.onLog(`! sync-tasks (progress) failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
|
|
94208
|
+
}
|
|
94209
|
+
}
|
|
94120
94210
|
}
|
|
94121
94211
|
}
|
|
94122
94212
|
async scanDoneForConflicts() {
|
|
@@ -94289,6 +94379,13 @@ class AgentCoordinator {
|
|
|
94289
94379
|
issue_identifier: issue2.identifier
|
|
94290
94380
|
});
|
|
94291
94381
|
this.deps.onWorkersChanged();
|
|
94382
|
+
if (this.deps.syncTasks) {
|
|
94383
|
+
try {
|
|
94384
|
+
await this.deps.syncTasks(worker, 0);
|
|
94385
|
+
} catch (err) {
|
|
94386
|
+
this.deps.onLog(`! sync-tasks (launch) failed for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
94387
|
+
}
|
|
94388
|
+
}
|
|
94292
94389
|
handle.exited.then(async (code) => {
|
|
94293
94390
|
const idx = this.workers.indexOf(worker);
|
|
94294
94391
|
if (idx >= 0)
|
|
@@ -94333,6 +94430,31 @@ class AgentCoordinator {
|
|
|
94333
94430
|
}
|
|
94334
94431
|
async notifyExited(issue2, changeName, code, mode) {
|
|
94335
94432
|
const ok = code === 0;
|
|
94433
|
+
if (this.deps.syncTasks && ok) {
|
|
94434
|
+
const synthetic = {
|
|
94435
|
+
changeName,
|
|
94436
|
+
issueId: issue2.id,
|
|
94437
|
+
issueIdentifier: issue2.identifier,
|
|
94438
|
+
issue: issue2,
|
|
94439
|
+
mode,
|
|
94440
|
+
kill: () => {},
|
|
94441
|
+
lastReportedIteration: 0,
|
|
94442
|
+
restarting: false
|
|
94443
|
+
};
|
|
94444
|
+
try {
|
|
94445
|
+
let iteration = 0;
|
|
94446
|
+
if (this.deps.getIterationCount) {
|
|
94447
|
+
try {
|
|
94448
|
+
iteration = await this.deps.getIterationCount(changeName);
|
|
94449
|
+
} catch {
|
|
94450
|
+
iteration = 0;
|
|
94451
|
+
}
|
|
94452
|
+
}
|
|
94453
|
+
await this.deps.syncTasks(synthetic, iteration);
|
|
94454
|
+
} catch (err) {
|
|
94455
|
+
this.deps.onLog(`! sync-tasks (done) failed for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
94456
|
+
}
|
|
94457
|
+
}
|
|
94336
94458
|
if (this.opts.postComments !== false) {
|
|
94337
94459
|
const body = ok ? mode === "conflict-fix" ? `\u2705 Ralph resolved merge conflicts on this issue. Change: \`${changeName}\`` : `\u2705 Ralph completed work on this issue. Change: \`${changeName}\`` : `\u2717 Ralph exited with code ${code} on this issue. Change: \`${changeName}\`
|
|
94338
94460
|
|
|
@@ -94483,7 +94605,7 @@ async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = []
|
|
|
94483
94605
|
`- [ ] Fill in \`## Why\` and \`## What Changes\` in proposal.md so \`openspec validate\` passes (these sections are required by the validator)`,
|
|
94484
94606
|
`- [ ] Add at least one spec delta under \`specs/<capability>/spec.md\` describing the behavior added/modified/removed by this change`,
|
|
94485
94607
|
`- [ ] Fill in design.md with the technical design (files to touch, data flow, edge cases)`,
|
|
94486
|
-
`- [ ] Append an \`## Implementation\` section below with concrete mission-specific tasks derived from the plan
|
|
94608
|
+
`- [ ] Append an \`## Implementation\` section below with concrete mission-specific tasks derived from the plan, including tests and \`bun run lint\` / \`bun run test\`. Every item in the new section MUST start as \`- [ ]\` (unchecked) \u2014 do not pre-check items even if you already did the work during planning. The loop ticks them off in later iterations after each one is verified.`,
|
|
94487
94609
|
""
|
|
94488
94610
|
].join(`
|
|
94489
94611
|
`);
|
|
@@ -95517,6 +95639,143 @@ var init_gate = __esm(() => {
|
|
|
95517
95639
|
FINGERPRINT_MARKER_RE = /<!--\s*ralphy:baseline:([a-f0-9]+)\s*-->/i;
|
|
95518
95640
|
});
|
|
95519
95641
|
|
|
95642
|
+
// apps/agent/src/agent/linear-sync/index.ts
|
|
95643
|
+
function parseTasksMd(md) {
|
|
95644
|
+
const lines = md.split(/\r?\n/);
|
|
95645
|
+
const sections = [];
|
|
95646
|
+
let current = null;
|
|
95647
|
+
let i = 0;
|
|
95648
|
+
while (i < lines.length) {
|
|
95649
|
+
const line = lines[i];
|
|
95650
|
+
const headingMatch = /^##\s+(.+?)\s*$/.exec(line);
|
|
95651
|
+
if (headingMatch) {
|
|
95652
|
+
current = { heading: headingMatch[1], items: [] };
|
|
95653
|
+
sections.push(current);
|
|
95654
|
+
i += 1;
|
|
95655
|
+
continue;
|
|
95656
|
+
}
|
|
95657
|
+
const bulletMatch = /^(\s*)-\s+\[( |x|X)\]\s+(.+?)\s*$/.exec(line);
|
|
95658
|
+
if (bulletMatch && current) {
|
|
95659
|
+
const indent = bulletMatch[1] ?? "";
|
|
95660
|
+
const checked = bulletMatch[2]?.toLowerCase() === "x";
|
|
95661
|
+
const text = bulletMatch[3] ?? "";
|
|
95662
|
+
const bullet = `${indent}- [${checked ? "x" : " "}] ${text}`;
|
|
95663
|
+
i += 1;
|
|
95664
|
+
let j = i;
|
|
95665
|
+
while (j < lines.length && lines[j].trim() === "")
|
|
95666
|
+
j += 1;
|
|
95667
|
+
let code;
|
|
95668
|
+
if (j < lines.length && /^\s*```/.test(lines[j])) {
|
|
95669
|
+
const fenceOpen = lines[j];
|
|
95670
|
+
const fenceMatch = /^(\s*)```/.exec(fenceOpen);
|
|
95671
|
+
const fenceIndent = fenceMatch?.[1] ?? "";
|
|
95672
|
+
const buf = [];
|
|
95673
|
+
j += 1;
|
|
95674
|
+
while (j < lines.length) {
|
|
95675
|
+
if (new RegExp(`^${fenceIndent}\`\`\`\\s*$`).test(lines[j])) {
|
|
95676
|
+
j += 1;
|
|
95677
|
+
break;
|
|
95678
|
+
}
|
|
95679
|
+
buf.push(lines[j]);
|
|
95680
|
+
j += 1;
|
|
95681
|
+
}
|
|
95682
|
+
code = buf.join(`
|
|
95683
|
+
`);
|
|
95684
|
+
i = j;
|
|
95685
|
+
}
|
|
95686
|
+
current.items.push(code !== undefined ? { bullet, code } : { bullet });
|
|
95687
|
+
continue;
|
|
95688
|
+
}
|
|
95689
|
+
i += 1;
|
|
95690
|
+
}
|
|
95691
|
+
return sections;
|
|
95692
|
+
}
|
|
95693
|
+
function truncate4(s, max2) {
|
|
95694
|
+
if (s.length <= max2)
|
|
95695
|
+
return s;
|
|
95696
|
+
return `${s.slice(0, max2)}
|
|
95697
|
+
\u2026(truncated)`;
|
|
95698
|
+
}
|
|
95699
|
+
function renderTasksBlock(tasksMd, meta3) {
|
|
95700
|
+
const sections = parseTasksMd(tasksMd);
|
|
95701
|
+
const out = [];
|
|
95702
|
+
out.push(RALPHY_TASKS_START);
|
|
95703
|
+
out.push("### Ralph progress");
|
|
95704
|
+
out.push("");
|
|
95705
|
+
for (const section of sections) {
|
|
95706
|
+
if (section.items.length === 0)
|
|
95707
|
+
continue;
|
|
95708
|
+
out.push(`**${section.heading}**`);
|
|
95709
|
+
out.push("");
|
|
95710
|
+
for (const item of section.items) {
|
|
95711
|
+
out.push(item.bullet);
|
|
95712
|
+
if (item.code !== undefined) {
|
|
95713
|
+
const inner = truncate4(item.code, MAX_CODE_BLOCK_BYTES);
|
|
95714
|
+
out.push(` <details><summary>output</summary><pre>${inner}</pre></details>`);
|
|
95715
|
+
}
|
|
95716
|
+
}
|
|
95717
|
+
out.push("");
|
|
95718
|
+
}
|
|
95719
|
+
out.push(`<sub>\`${meta3.changeName}\` \xB7 iteration ${meta3.iteration}</sub>`);
|
|
95720
|
+
out.push(RALPHY_TASKS_END);
|
|
95721
|
+
return out.join(`
|
|
95722
|
+
`);
|
|
95723
|
+
}
|
|
95724
|
+
function applyTasksBlock(existingDescription, block) {
|
|
95725
|
+
const existing = existingDescription ?? "";
|
|
95726
|
+
const startIdx = existing.indexOf(RALPHY_TASKS_START);
|
|
95727
|
+
const endIdx = startIdx >= 0 ? existing.indexOf(RALPHY_TASKS_END, startIdx + RALPHY_TASKS_START.length) : -1;
|
|
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}
|
|
95737
|
+
|
|
95738
|
+
${block}`;
|
|
95739
|
+
}
|
|
95740
|
+
async function syncTasksToLinearDescription(deps) {
|
|
95741
|
+
const file2 = Bun.file(deps.tasksPath);
|
|
95742
|
+
if (!await file2.exists()) {
|
|
95743
|
+
deps.log(` sync-tasks: tasks.md missing at ${deps.tasksPath}, skipping`, "gray");
|
|
95744
|
+
return null;
|
|
95745
|
+
}
|
|
95746
|
+
let tasksMd;
|
|
95747
|
+
try {
|
|
95748
|
+
tasksMd = await file2.text();
|
|
95749
|
+
} catch (err) {
|
|
95750
|
+
deps.log(`! sync-tasks: read failed for ${deps.tasksPath}: ${err.message}`, "yellow");
|
|
95751
|
+
return null;
|
|
95752
|
+
}
|
|
95753
|
+
const block = renderTasksBlock(tasksMd, {
|
|
95754
|
+
changeName: deps.changeName,
|
|
95755
|
+
iteration: deps.iteration
|
|
95756
|
+
});
|
|
95757
|
+
if (block.length > MAX_BLOCK_BYTES) {
|
|
95758
|
+
deps.log(`! sync-tasks: rendered block exceeds ${MAX_BLOCK_BYTES} bytes (${block.length}), skipping update`, "yellow");
|
|
95759
|
+
return null;
|
|
95760
|
+
}
|
|
95761
|
+
const next = applyTasksBlock(deps.currentDescription, block);
|
|
95762
|
+
if (next === (deps.currentDescription ?? ""))
|
|
95763
|
+
return null;
|
|
95764
|
+
try {
|
|
95765
|
+
await deps.updateIssueDescription(deps.apiKey, deps.issueId, next);
|
|
95766
|
+
deps.log(` sync-tasks: updated Linear description for ${deps.changeName}`, "gray");
|
|
95767
|
+
return next;
|
|
95768
|
+
} catch (err) {
|
|
95769
|
+
deps.log(`! sync-tasks: updateIssueDescription failed: ${err.message}`, "yellow");
|
|
95770
|
+
return null;
|
|
95771
|
+
}
|
|
95772
|
+
}
|
|
95773
|
+
var RALPHY_TASKS_START = "<!-- ralphy:tasks:start -->", RALPHY_TASKS_END = "<!-- ralphy:tasks:end -->", MAX_BLOCK_BYTES, MAX_CODE_BLOCK_BYTES;
|
|
95774
|
+
var init_linear_sync = __esm(() => {
|
|
95775
|
+
MAX_BLOCK_BYTES = 60 * 1024;
|
|
95776
|
+
MAX_CODE_BLOCK_BYTES = 2 * 1024;
|
|
95777
|
+
});
|
|
95778
|
+
|
|
95520
95779
|
// apps/agent/src/agent/wire.ts
|
|
95521
95780
|
import { join as join21 } from "path";
|
|
95522
95781
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
@@ -96069,6 +96328,11 @@ PR: ${prUrl}` : ""
|
|
|
96069
96328
|
function spawnWorker(changeName) {
|
|
96070
96329
|
const cwd2 = cwdByChange.get(changeName) ?? projectRoot;
|
|
96071
96330
|
const injected = input.runners?.spawnWorker;
|
|
96331
|
+
const missionTasksPath = join21(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
|
|
96332
|
+
const prevTasksPromise = (async () => {
|
|
96333
|
+
const f2 = Bun.file(missionTasksPath);
|
|
96334
|
+
return await f2.exists() ? await f2.text() : "";
|
|
96335
|
+
})();
|
|
96072
96336
|
let logFilePath;
|
|
96073
96337
|
let handle;
|
|
96074
96338
|
if (injected) {
|
|
@@ -96094,6 +96358,21 @@ PR: ${prUrl}` : ""
|
|
|
96094
96358
|
const wantAutoMerge = issueForChange ? issueMatchesGetIndicator(issueForChange, indicators.getAutoMerge) : false;
|
|
96095
96359
|
const wrapped = handle.exited.then(async (code) => {
|
|
96096
96360
|
const workerLayout = projectLayout(cwd2);
|
|
96361
|
+
try {
|
|
96362
|
+
const prevTasks = await prevTasksPromise;
|
|
96363
|
+
const nextFile = Bun.file(missionTasksPath);
|
|
96364
|
+
if (await nextFile.exists()) {
|
|
96365
|
+
const nextTasks = await nextFile.text();
|
|
96366
|
+
const report = normalizeNewlyAppendedSectionWithReport(prevTasks, nextTasks);
|
|
96367
|
+
if (report.text !== nextTasks) {
|
|
96368
|
+
await Bun.write(missionTasksPath, report.text);
|
|
96369
|
+
const sections = report.headings.map((h) => `## ${h}`).join(", ");
|
|
96370
|
+
onLog(`! normalized ${report.count} pre-checked item(s) in newly added section(s) ${sections}`, "yellow");
|
|
96371
|
+
}
|
|
96372
|
+
}
|
|
96373
|
+
} catch (err) {
|
|
96374
|
+
onLog(`! tasks.md normalization failed: ${err.message}`, "yellow");
|
|
96375
|
+
}
|
|
96097
96376
|
const effectiveCode = await runPostTask({
|
|
96098
96377
|
changeName,
|
|
96099
96378
|
cwd: cwd2,
|
|
@@ -96327,9 +96606,9 @@ PR: ${prUrl}` : ""
|
|
|
96327
96606
|
const handle = cfg.linear.mentionHandle;
|
|
96328
96607
|
let candidates = [];
|
|
96329
96608
|
try {
|
|
96330
|
-
candidates = await
|
|
96609
|
+
candidates = await fetchMentionScanIssues(apiKey, { team, assignee });
|
|
96331
96610
|
} catch (err) {
|
|
96332
|
-
onLog(`! mention scan:
|
|
96611
|
+
onLog(`! mention scan: fetchMentionScanIssues failed: ${err.message}`, "yellow");
|
|
96333
96612
|
return [];
|
|
96334
96613
|
}
|
|
96335
96614
|
const out = [];
|
|
@@ -96620,7 +96899,29 @@ PR: ${prUrl}` : ""
|
|
|
96620
96899
|
return 0;
|
|
96621
96900
|
const json2 = await file2.json();
|
|
96622
96901
|
return json2.iteration ?? 0;
|
|
96623
|
-
}
|
|
96902
|
+
},
|
|
96903
|
+
...cfg.linear.syncTasksToDescription && apiKey ? {
|
|
96904
|
+
syncTasks: async (worker, iteration) => {
|
|
96905
|
+
const root = cwdByChange.get(worker.changeName) ?? projectRoot;
|
|
96906
|
+
const tasksPath = join21(projectLayout(root).changeDir(worker.changeName), "tasks.md");
|
|
96907
|
+
const cachedIssue = issueByChange.get(worker.changeName) ?? worker.issue;
|
|
96908
|
+
const next = await syncTasksToLinearDescription({
|
|
96909
|
+
apiKey,
|
|
96910
|
+
issueId: worker.issueId,
|
|
96911
|
+
currentDescription: cachedIssue.description,
|
|
96912
|
+
tasksPath,
|
|
96913
|
+
changeName: worker.changeName,
|
|
96914
|
+
iteration,
|
|
96915
|
+
log: onLog,
|
|
96916
|
+
updateIssueDescription
|
|
96917
|
+
});
|
|
96918
|
+
if (next !== null) {
|
|
96919
|
+
const updated = { ...cachedIssue, description: next };
|
|
96920
|
+
issueByChange.set(worker.changeName, updated);
|
|
96921
|
+
worker.issue = updated;
|
|
96922
|
+
}
|
|
96923
|
+
}
|
|
96924
|
+
} : {}
|
|
96624
96925
|
}, {
|
|
96625
96926
|
concurrency,
|
|
96626
96927
|
...indicators.setInProgress !== undefined ? { setInProgress: indicators.setInProgress } : {},
|
|
@@ -96687,6 +96988,7 @@ PR: ${prUrl}` : ""
|
|
|
96687
96988
|
concurrency,
|
|
96688
96989
|
pollInterval,
|
|
96689
96990
|
getWorkerCwd: (changeName) => cwdByChange.get(changeName),
|
|
96991
|
+
syncTasksEnabled: Boolean(cfg.linear.syncTasksToDescription && apiKey),
|
|
96690
96992
|
runBaselineGate: runBaselineGateOnce
|
|
96691
96993
|
};
|
|
96692
96994
|
}
|
|
@@ -96722,6 +97024,7 @@ var init_wire = __esm(() => {
|
|
|
96722
97024
|
init_post_task();
|
|
96723
97025
|
init_gate();
|
|
96724
97026
|
init_workflow();
|
|
97027
|
+
init_linear_sync();
|
|
96725
97028
|
GITHUB_PR_URL_RE = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/;
|
|
96726
97029
|
bunGitRunner = {
|
|
96727
97030
|
run: async (args, cwd2) => {
|
|
@@ -97313,6 +97616,7 @@ function AgentMode({
|
|
|
97313
97616
|
const workerMetaRef = import_react61.useRef(new Map);
|
|
97314
97617
|
const nextPollAtRef = import_react61.useRef(0);
|
|
97315
97618
|
const cfgRef = import_react61.useRef(null);
|
|
97619
|
+
const [effective, setEffective] = import_react61.useState(null);
|
|
97316
97620
|
const [pollStatus, setPollStatus] = import_react61.useState({
|
|
97317
97621
|
state: "idle",
|
|
97318
97622
|
lastFound: null,
|
|
@@ -97411,6 +97715,7 @@ function AgentMode({
|
|
|
97411
97715
|
m.prUrl = prUrl;
|
|
97412
97716
|
}
|
|
97413
97717
|
});
|
|
97718
|
+
setEffective({ concurrency, pollInterval });
|
|
97414
97719
|
coordRef.current = coord2;
|
|
97415
97720
|
await coord2.init();
|
|
97416
97721
|
const tick = async () => {
|
|
@@ -97670,14 +97975,14 @@ function AgentMode({
|
|
|
97670
97975
|
dimColor: true,
|
|
97671
97976
|
children: [
|
|
97672
97977
|
" \u2502 \xD7",
|
|
97673
|
-
cfg.concurrency
|
|
97978
|
+
effective?.concurrency ?? cfg.concurrency
|
|
97674
97979
|
]
|
|
97675
97980
|
}, undefined, true, undefined, this),
|
|
97676
97981
|
/* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
|
|
97677
97982
|
dimColor: true,
|
|
97678
97983
|
children: [
|
|
97679
97984
|
" \u2502 poll ",
|
|
97680
|
-
cfg.pollIntervalSeconds,
|
|
97985
|
+
effective?.pollInterval ?? cfg.pollIntervalSeconds,
|
|
97681
97986
|
"s"
|
|
97682
97987
|
]
|
|
97683
97988
|
}, undefined, true, undefined, this),
|
|
@@ -97794,18 +98099,6 @@ function AgentMode({
|
|
|
97794
98099
|
dimColor: true,
|
|
97795
98100
|
children: "\xB7"
|
|
97796
98101
|
}, undefined, false, undefined, this),
|
|
97797
|
-
/* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
|
|
97798
|
-
dimColor: true,
|
|
97799
|
-
children: "conflict"
|
|
97800
|
-
}, undefined, false, undefined, this),
|
|
97801
|
-
/* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
|
|
97802
|
-
color: pollStatus.lastBuckets.conflicted > 0 ? "red" : "white",
|
|
97803
|
-
children: pollStatus.lastBuckets.conflicted
|
|
97804
|
-
}, undefined, false, undefined, this),
|
|
97805
|
-
/* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
|
|
97806
|
-
dimColor: true,
|
|
97807
|
-
children: "\xB7"
|
|
97808
|
-
}, undefined, false, undefined, this),
|
|
97809
98102
|
/* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
|
|
97810
98103
|
dimColor: true,
|
|
97811
98104
|
children: "review"
|
|
@@ -97836,6 +98129,7 @@ function AgentMode({
|
|
|
97836
98129
|
children: [
|
|
97837
98130
|
secsToNextPoll !== null ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
|
|
97838
98131
|
gap: 1,
|
|
98132
|
+
width: 7,
|
|
97839
98133
|
children: [
|
|
97840
98134
|
/* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
|
|
97841
98135
|
dimColor: true,
|