runcastle 1.2.0 → 1.2.2
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/bin/runcastle.js +233 -22
- package/drizzle/0018_nice_lorna_dane.sql +10 -0
- package/drizzle/meta/0018_snapshot.json +846 -0
- package/drizzle/meta/_journal.json +7 -0
- package/index.js +231 -20
- package/package.json +1 -1
- package/web/assets/index-Co7aViQi.js +142 -0
- package/web/assets/{index-D0dcZNJp.css → index-o2brqt2b.css} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-g3KG__Hv.js +0 -142
|
@@ -127,6 +127,13 @@
|
|
|
127
127
|
"when": 1785745839500,
|
|
128
128
|
"tag": "0017_certain_ezekiel_stane",
|
|
129
129
|
"breakpoints": true
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"idx": 18,
|
|
133
|
+
"version": "6",
|
|
134
|
+
"when": 1786021663891,
|
|
135
|
+
"tag": "0018_nice_lorna_dane",
|
|
136
|
+
"breakpoints": true
|
|
130
137
|
}
|
|
131
138
|
]
|
|
132
139
|
}
|
package/index.js
CHANGED
|
@@ -245,7 +245,7 @@ var init_ids = () => {};
|
|
|
245
245
|
|
|
246
246
|
// ../core/src/schemas.ts
|
|
247
247
|
import * as z2 from "zod";
|
|
248
|
-
var Phase, TicketStatus, SessionKind, RunStatus, FeatureStatus, SessionStatus, TicketInput, Ticket, WaypointType, WaypointStatus, WaypointDisposition, WaypointInput, Waypoint, PREPARED_KEYS, PreparedKey, DRIVE_LOOP_KEYS, FindingSource, PROJECT_NAME_MAX = 80, ProjectName, Project, ProjectFinding, Feature, SessionRow, Run, SettingSource, SettingScope, SettingField, SettingsView, SettingsUpdateInput, EventRow;
|
|
248
|
+
var Phase, TicketStatus, SessionKind, RunStatus, FeatureStatus, SessionStatus, TicketInput, Ticket, WaypointType, WaypointStatus, WaypointDisposition, WaypointInput, Waypoint, TestNoteStatus, TestNote, PREPARED_KEYS, PreparedKey, DRIVE_LOOP_KEYS, FindingSource, PROJECT_NAME_MAX = 80, ProjectName, Project, ProjectFinding, Feature, SessionRow, Run, SettingSource, SettingScope, SettingField, SettingsView, SettingsUpdateInput, EventRow;
|
|
249
249
|
var init_schemas = __esm(() => {
|
|
250
250
|
Phase = z2.enum([
|
|
251
251
|
"ideation",
|
|
@@ -307,6 +307,17 @@ var init_schemas = __esm(() => {
|
|
|
307
307
|
lastSessionId: z2.string().optional(),
|
|
308
308
|
summary: z2.string().optional()
|
|
309
309
|
});
|
|
310
|
+
TestNoteStatus = z2.enum(["open", "done", "promoted"]);
|
|
311
|
+
TestNote = z2.object({
|
|
312
|
+
id: z2.string(),
|
|
313
|
+
featureId: z2.string(),
|
|
314
|
+
lap: z2.number(),
|
|
315
|
+
text: z2.string(),
|
|
316
|
+
status: TestNoteStatus,
|
|
317
|
+
ticketId: z2.string().optional(),
|
|
318
|
+
createdAt: z2.number(),
|
|
319
|
+
updatedAt: z2.number()
|
|
320
|
+
});
|
|
310
321
|
PREPARED_KEYS = [
|
|
311
322
|
"setupCommand",
|
|
312
323
|
"verifyCommands",
|
|
@@ -559,7 +570,7 @@ var init_pipeline = __esm(() => {
|
|
|
559
570
|
|
|
560
571
|
// ../core/src/db-schema.ts
|
|
561
572
|
import { integer, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
562
|
-
var projects, projectFindings, features, sessions, tickets, waypoints, runs, events, gateOverrides;
|
|
573
|
+
var projects, projectFindings, features, sessions, tickets, testNotes, waypoints, runs, events, gateOverrides;
|
|
563
574
|
var init_db_schema = __esm(() => {
|
|
564
575
|
projects = sqliteTable("projects", {
|
|
565
576
|
id: text("id").primaryKey(),
|
|
@@ -631,6 +642,16 @@ var init_db_schema = __esm(() => {
|
|
|
631
642
|
attemptBranch: text("attempt_branch"),
|
|
632
643
|
conflictFiles: text("conflict_files", { mode: "json" }).$type()
|
|
633
644
|
});
|
|
645
|
+
testNotes = sqliteTable("test_notes", {
|
|
646
|
+
id: text("id").primaryKey(),
|
|
647
|
+
featureId: text("feature_id").notNull(),
|
|
648
|
+
lap: integer("lap").notNull(),
|
|
649
|
+
text: text("text").notNull(),
|
|
650
|
+
status: text("status").notNull().$type(),
|
|
651
|
+
ticketId: text("ticket_id"),
|
|
652
|
+
createdAt: integer("created_at").notNull(),
|
|
653
|
+
updatedAt: integer("updated_at").notNull()
|
|
654
|
+
});
|
|
634
655
|
waypoints = sqliteTable("waypoints", {
|
|
635
656
|
id: text("id").primaryKey(),
|
|
636
657
|
featureId: text("feature_id").notNull(),
|
|
@@ -693,6 +714,7 @@ var init_schema = __esm(() => {
|
|
|
693
714
|
features,
|
|
694
715
|
sessions,
|
|
695
716
|
tickets,
|
|
717
|
+
testNotes,
|
|
696
718
|
waypoints,
|
|
697
719
|
runs,
|
|
698
720
|
events,
|
|
@@ -1773,6 +1795,7 @@ var init_waypoints = __esm(() => {
|
|
|
1773
1795
|
});
|
|
1774
1796
|
|
|
1775
1797
|
// src/pty/dev-pane.ts
|
|
1798
|
+
import { execFileSync } from "child_process";
|
|
1776
1799
|
function drivePaneId(ownerId) {
|
|
1777
1800
|
return `${DRIVE_PREFIX}${ownerId}`;
|
|
1778
1801
|
}
|
|
@@ -1832,14 +1855,20 @@ function devPaneLive(paneId) {
|
|
|
1832
1855
|
const entry = ptyRegistry().get(paneId);
|
|
1833
1856
|
return !!entry && !entry.exited;
|
|
1834
1857
|
}
|
|
1858
|
+
function killProcessTree(pid) {
|
|
1859
|
+
try {
|
|
1860
|
+
if (process.platform === "win32") {
|
|
1861
|
+
execFileSync("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
|
|
1862
|
+
} else {
|
|
1863
|
+
process.kill(-pid, "SIGTERM");
|
|
1864
|
+
}
|
|
1865
|
+
} catch {}
|
|
1866
|
+
}
|
|
1835
1867
|
function stopDevPane(paneId) {
|
|
1836
1868
|
const reg = ptyRegistry();
|
|
1837
1869
|
const entry = reg.get(paneId);
|
|
1838
|
-
if (entry && !entry.exited
|
|
1839
|
-
|
|
1840
|
-
process.kill(-entry.pty.pid, "SIGTERM");
|
|
1841
|
-
} catch {}
|
|
1842
|
-
}
|
|
1870
|
+
if (entry && !entry.exited)
|
|
1871
|
+
killProcessTree(entry.pty.pid);
|
|
1843
1872
|
reg.kill(paneId);
|
|
1844
1873
|
reg.remove(paneId);
|
|
1845
1874
|
}
|
|
@@ -9345,8 +9374,188 @@ var init_system = __esm(() => {
|
|
|
9345
9374
|
});
|
|
9346
9375
|
});
|
|
9347
9376
|
|
|
9348
|
-
// src/
|
|
9377
|
+
// src/services/test-notes.ts
|
|
9378
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
9379
|
+
import { dirname as dirname10 } from "path";
|
|
9380
|
+
import { asc as asc4, eq as eq14 } from "drizzle-orm";
|
|
9381
|
+
function rowToNote(row) {
|
|
9382
|
+
return {
|
|
9383
|
+
id: row.id,
|
|
9384
|
+
featureId: row.featureId,
|
|
9385
|
+
lap: row.lap,
|
|
9386
|
+
text: row.text,
|
|
9387
|
+
status: row.status,
|
|
9388
|
+
ticketId: row.ticketId ?? undefined,
|
|
9389
|
+
createdAt: row.createdAt,
|
|
9390
|
+
updatedAt: row.updatedAt
|
|
9391
|
+
};
|
|
9392
|
+
}
|
|
9393
|
+
function getNote(ctx, id) {
|
|
9394
|
+
const row = ctx.db.select().from(testNotes).where(eq14(testNotes.id, id)).get();
|
|
9395
|
+
if (!row)
|
|
9396
|
+
throw new NotFoundError(`test note ${id} not found`);
|
|
9397
|
+
return rowToNote(row);
|
|
9398
|
+
}
|
|
9399
|
+
function cleanText(text2) {
|
|
9400
|
+
const trimmed = text2.trim();
|
|
9401
|
+
if (!trimmed)
|
|
9402
|
+
throw new InvalidInputError("note text cannot be empty");
|
|
9403
|
+
return trimmed;
|
|
9404
|
+
}
|
|
9405
|
+
function assertOpen(note, action) {
|
|
9406
|
+
if (note.status !== "open") {
|
|
9407
|
+
throw new InvalidInputError(`cannot ${action} note ${note.id} \u2014 it is ${note.status}; only open notes can be changed`);
|
|
9408
|
+
}
|
|
9409
|
+
}
|
|
9410
|
+
function listByFeature3(ctx, featureId) {
|
|
9411
|
+
return ctx.db.select().from(testNotes).where(eq14(testNotes.featureId, featureId)).orderBy(asc4(testNotes.createdAt), asc4(testNotes.id)).all().map(rowToNote);
|
|
9412
|
+
}
|
|
9413
|
+
function addNote(ctx, featureId, text2) {
|
|
9414
|
+
const body = cleanText(text2);
|
|
9415
|
+
const feature = getFeatureRow(ctx, featureId);
|
|
9416
|
+
const now = Date.now();
|
|
9417
|
+
const row = ctx.db.insert(testNotes).values({
|
|
9418
|
+
id: newId("note"),
|
|
9419
|
+
featureId,
|
|
9420
|
+
lap: feature.lap,
|
|
9421
|
+
text: body,
|
|
9422
|
+
status: "open",
|
|
9423
|
+
ticketId: null,
|
|
9424
|
+
createdAt: now,
|
|
9425
|
+
updatedAt: now
|
|
9426
|
+
}).returning().get();
|
|
9427
|
+
const note = rowToNote(row);
|
|
9428
|
+
emit(ctx, featureId, {
|
|
9429
|
+
type: "note.added",
|
|
9430
|
+
message: `note captured on lap ${note.lap}`,
|
|
9431
|
+
data: { noteId: note.id, lap: note.lap }
|
|
9432
|
+
});
|
|
9433
|
+
renderTestNotes(ctx, feature);
|
|
9434
|
+
return note;
|
|
9435
|
+
}
|
|
9436
|
+
function editNote(ctx, noteId, text2) {
|
|
9437
|
+
const current2 = getNote(ctx, noteId);
|
|
9438
|
+
assertOpen(current2, "edit");
|
|
9439
|
+
const body = cleanText(text2);
|
|
9440
|
+
ctx.db.update(testNotes).set({ text: body, updatedAt: Date.now() }).where(eq14(testNotes.id, noteId)).run();
|
|
9441
|
+
emit(ctx, current2.featureId, {
|
|
9442
|
+
type: "note.edited",
|
|
9443
|
+
message: "note edited",
|
|
9444
|
+
data: { noteId }
|
|
9445
|
+
});
|
|
9446
|
+
renderTestNotes(ctx, getFeatureRow(ctx, current2.featureId));
|
|
9447
|
+
return getNote(ctx, noteId);
|
|
9448
|
+
}
|
|
9449
|
+
function deleteNote(ctx, noteId) {
|
|
9450
|
+
const current2 = getNote(ctx, noteId);
|
|
9451
|
+
assertOpen(current2, "delete");
|
|
9452
|
+
ctx.db.delete(testNotes).where(eq14(testNotes.id, noteId)).run();
|
|
9453
|
+
emit(ctx, current2.featureId, {
|
|
9454
|
+
type: "note.deleted",
|
|
9455
|
+
message: "note deleted",
|
|
9456
|
+
data: { noteId }
|
|
9457
|
+
});
|
|
9458
|
+
renderTestNotes(ctx, getFeatureRow(ctx, current2.featureId));
|
|
9459
|
+
}
|
|
9460
|
+
function toggleNote(ctx, noteId) {
|
|
9461
|
+
const current2 = getNote(ctx, noteId);
|
|
9462
|
+
if (current2.status === "promoted") {
|
|
9463
|
+
throw new InvalidInputError(`cannot toggle note ${noteId} \u2014 it is promoted, and promoted notes are frozen`);
|
|
9464
|
+
}
|
|
9465
|
+
const status = current2.status === "open" ? "done" : "open";
|
|
9466
|
+
ctx.db.update(testNotes).set({ status, updatedAt: Date.now() }).where(eq14(testNotes.id, noteId)).run();
|
|
9467
|
+
emit(ctx, current2.featureId, {
|
|
9468
|
+
type: "note.toggled",
|
|
9469
|
+
message: `note ${current2.status} \u2192 ${status}`,
|
|
9470
|
+
data: { noteId, from: current2.status, to: status }
|
|
9471
|
+
});
|
|
9472
|
+
renderTestNotes(ctx, getFeatureRow(ctx, current2.featureId));
|
|
9473
|
+
return getNote(ctx, noteId);
|
|
9474
|
+
}
|
|
9475
|
+
function promotionTicket(feature, note) {
|
|
9476
|
+
const firstLine = note.text.split(`
|
|
9477
|
+
`)[0].trim();
|
|
9478
|
+
const docs = featureDocsRel(feature.slug);
|
|
9479
|
+
return {
|
|
9480
|
+
title: firstLine.length <= TITLE_MAX ? firstLine : `${firstLine.slice(0, TITLE_MAX - 1).trimEnd()}\u2026`,
|
|
9481
|
+
goal: note.text,
|
|
9482
|
+
context: [
|
|
9483
|
+
`Found during lap ${note.lap} test drive of ${feature.slug}.`,
|
|
9484
|
+
`Read ${docs}/spec.md and ${docs}/decisions.md for what this feature is meant to do.`
|
|
9485
|
+
].join(`
|
|
9486
|
+
|
|
9487
|
+
`),
|
|
9488
|
+
acceptanceCriteria: [`The noted behavior no longer reproduces: ${note.text}`],
|
|
9489
|
+
seams: [],
|
|
9490
|
+
blockedBy: []
|
|
9491
|
+
};
|
|
9492
|
+
}
|
|
9493
|
+
function promoteNote(ctx, noteId) {
|
|
9494
|
+
const current2 = getNote(ctx, noteId);
|
|
9495
|
+
assertOpen(current2, "promote");
|
|
9496
|
+
const feature = getFeatureRow(ctx, current2.featureId);
|
|
9497
|
+
const [ticket] = storeTickets(ctx, current2.featureId, [promotionTicket(feature, current2)]);
|
|
9498
|
+
ctx.db.update(testNotes).set({ status: "promoted", ticketId: ticket.id, updatedAt: Date.now() }).where(eq14(testNotes.id, noteId)).run();
|
|
9499
|
+
emit(ctx, current2.featureId, {
|
|
9500
|
+
type: "note.promoted",
|
|
9501
|
+
message: `note promoted to ticket ${ticket.seq}`,
|
|
9502
|
+
ticketId: ticket.id,
|
|
9503
|
+
data: { noteId, seq: ticket.seq }
|
|
9504
|
+
});
|
|
9505
|
+
renderTestNotes(ctx, feature);
|
|
9506
|
+
return { note: getNote(ctx, noteId), ticket };
|
|
9507
|
+
}
|
|
9508
|
+
function noteLine(ctx, note) {
|
|
9509
|
+
if (note.status === "open")
|
|
9510
|
+
return `- [ ] ${note.text}`;
|
|
9511
|
+
const ticket = note.ticketId ? ` (\u2192 ticket ${getTicket(ctx, note.ticketId).seq})` : "";
|
|
9512
|
+
return `- [x] ${note.text}${ticket}`;
|
|
9513
|
+
}
|
|
9514
|
+
function renderTestNotes(ctx, feature) {
|
|
9515
|
+
const notes = listByFeature3(ctx, feature.id);
|
|
9516
|
+
const laps = [...new Set(notes.map((n) => n.lap))].sort((a, b) => a - b);
|
|
9517
|
+
const lines = ["# Test notes"];
|
|
9518
|
+
for (const lap of laps) {
|
|
9519
|
+
lines.push("", `## Lap ${lap}`, "");
|
|
9520
|
+
for (const note of notes.filter((n) => n.lap === lap))
|
|
9521
|
+
lines.push(noteLine(ctx, note));
|
|
9522
|
+
}
|
|
9523
|
+
const path = featureDocPath(projectForFeature(ctx, feature), feature, "test-notes.md");
|
|
9524
|
+
mkdirSync9(dirname10(path), { recursive: true });
|
|
9525
|
+
writeFileSync5(path, `${lines.join(`
|
|
9526
|
+
`)}
|
|
9527
|
+
`, "utf8");
|
|
9528
|
+
}
|
|
9529
|
+
var TITLE_MAX = 60;
|
|
9530
|
+
var init_test_notes = __esm(() => {
|
|
9531
|
+
init_src();
|
|
9532
|
+
init_paths();
|
|
9533
|
+
init_schema();
|
|
9534
|
+
init_errors();
|
|
9535
|
+
init_events();
|
|
9536
|
+
init_feature_docs();
|
|
9537
|
+
init_repo();
|
|
9538
|
+
init_tickets();
|
|
9539
|
+
});
|
|
9540
|
+
|
|
9541
|
+
// src/trpc/routers/test-notes.ts
|
|
9349
9542
|
import * as z12 from "zod";
|
|
9543
|
+
var testNotesRouter;
|
|
9544
|
+
var init_test_notes2 = __esm(() => {
|
|
9545
|
+
init_test_notes();
|
|
9546
|
+
init_context();
|
|
9547
|
+
testNotesRouter = router({
|
|
9548
|
+
list: publicProcedure.input(z12.object({ featureId: z12.string() })).query(({ ctx, input }) => listByFeature3(ctx, input.featureId)),
|
|
9549
|
+
add: publicProcedure.input(z12.object({ featureId: z12.string(), text: z12.string().min(1) })).mutation(({ ctx, input }) => addNote(ctx, input.featureId, input.text)),
|
|
9550
|
+
edit: publicProcedure.input(z12.object({ noteId: z12.string(), text: z12.string().min(1) })).mutation(({ ctx, input }) => editNote(ctx, input.noteId, input.text)),
|
|
9551
|
+
remove: publicProcedure.input(z12.object({ noteId: z12.string() })).mutation(({ ctx, input }) => deleteNote(ctx, input.noteId)),
|
|
9552
|
+
toggle: publicProcedure.input(z12.object({ noteId: z12.string() })).mutation(({ ctx, input }) => toggleNote(ctx, input.noteId)),
|
|
9553
|
+
promote: publicProcedure.input(z12.object({ noteId: z12.string() })).mutation(({ ctx, input }) => promoteNote(ctx, input.noteId))
|
|
9554
|
+
});
|
|
9555
|
+
});
|
|
9556
|
+
|
|
9557
|
+
// src/trpc/routers/ticket.ts
|
|
9558
|
+
import * as z13 from "zod";
|
|
9350
9559
|
var ticketRouter;
|
|
9351
9560
|
var init_ticket = __esm(() => {
|
|
9352
9561
|
init_features();
|
|
@@ -9355,8 +9564,8 @@ var init_ticket = __esm(() => {
|
|
|
9355
9564
|
init_ticket_burner();
|
|
9356
9565
|
init_context();
|
|
9357
9566
|
ticketRouter = router({
|
|
9358
|
-
retry: publicProcedure.input(
|
|
9359
|
-
stop: publicProcedure.input(
|
|
9567
|
+
retry: publicProcedure.input(z13.object({ ticketId: z13.string(), fresh: z13.boolean().optional() })).mutation(({ ctx, input }) => retryTicket(ctx, input.ticketId, { fresh: input.fresh })),
|
|
9568
|
+
stop: publicProcedure.input(z13.object({ ticketId: z13.string() })).mutation(({ ctx, input }) => {
|
|
9360
9569
|
if (stopTicketRun(input.ticketId))
|
|
9361
9570
|
return { stopped: true, swept: false };
|
|
9362
9571
|
const ticket = getTicket(ctx, input.ticketId);
|
|
@@ -9366,13 +9575,13 @@ var init_ticket = __esm(() => {
|
|
|
9366
9575
|
}
|
|
9367
9576
|
return { stopped: false, swept: false };
|
|
9368
9577
|
}),
|
|
9369
|
-
cancel: publicProcedure.input(
|
|
9370
|
-
edit: publicProcedure.input(
|
|
9371
|
-
ticketId:
|
|
9372
|
-
title:
|
|
9373
|
-
goal:
|
|
9374
|
-
context:
|
|
9375
|
-
acceptanceCriteria:
|
|
9578
|
+
cancel: publicProcedure.input(z13.object({ ticketId: z13.string(), reason: z13.string().optional() })).mutation(({ ctx, input }) => cancelTicket(ctx, input.ticketId, input.reason)),
|
|
9579
|
+
edit: publicProcedure.input(z13.object({
|
|
9580
|
+
ticketId: z13.string(),
|
|
9581
|
+
title: z13.string().min(1).optional(),
|
|
9582
|
+
goal: z13.string().min(1).optional(),
|
|
9583
|
+
context: z13.string().optional(),
|
|
9584
|
+
acceptanceCriteria: z13.array(z13.string().min(1)).optional()
|
|
9376
9585
|
})).mutation(({ ctx, input }) => {
|
|
9377
9586
|
const { ticketId, ...patch } = input;
|
|
9378
9587
|
return editTicket(ctx, ticketId, patch);
|
|
@@ -9392,12 +9601,14 @@ var init_router = __esm(() => {
|
|
|
9392
9601
|
init_settings2();
|
|
9393
9602
|
init_setup2();
|
|
9394
9603
|
init_system();
|
|
9604
|
+
init_test_notes2();
|
|
9395
9605
|
init_ticket();
|
|
9396
9606
|
appRouter = router({
|
|
9397
9607
|
project: projectRouter,
|
|
9398
9608
|
feature: featureRouter,
|
|
9399
9609
|
run: runRouter,
|
|
9400
9610
|
ticket: ticketRouter,
|
|
9611
|
+
notes: testNotesRouter,
|
|
9401
9612
|
events: eventsRouter,
|
|
9402
9613
|
docs: docsRouter,
|
|
9403
9614
|
settings: settingsRouter,
|
|
@@ -9407,14 +9618,14 @@ var init_router = __esm(() => {
|
|
|
9407
9618
|
});
|
|
9408
9619
|
|
|
9409
9620
|
// src/workflows/reconcile-runs.ts
|
|
9410
|
-
import { eq as
|
|
9621
|
+
import { eq as eq15 } from "drizzle-orm";
|
|
9411
9622
|
async function reconcileStaleRuns(ctx) {
|
|
9412
|
-
const stale = ctx.db.select().from(runs).where(
|
|
9623
|
+
const stale = ctx.db.select().from(runs).where(eq15(runs.status, "running")).all().map(rowToRun);
|
|
9413
9624
|
const reconciled = [];
|
|
9414
9625
|
for (const run3 of stale) {
|
|
9415
9626
|
if (isRunActive(run3.id))
|
|
9416
9627
|
continue;
|
|
9417
|
-
ctx.db.update(runs).set({ status: "failed", endedAt: Date.now(), summary: "orphaned by server restart" }).where(
|
|
9628
|
+
ctx.db.update(runs).set({ status: "failed", endedAt: Date.now(), summary: "orphaned by server restart" }).where(eq15(runs.id, run3.id)).run();
|
|
9418
9629
|
const released = releaseForSession(ctx, run3.id);
|
|
9419
9630
|
const swept = workflowClaimsFeatureBranch(run3.workflow) ? sweepOrphanedBurning(ctx, run3.featureId, "orphaned by server restart \u2014 retry to resume its commits") : [];
|
|
9420
9631
|
if (workflowClaimsFeatureBranch(run3.workflow)) {
|