runcastle 1.3.3 → 1.3.4
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 +975 -596
- package/drizzle/0035_feature_tickets_ready_lap.sql +1 -0
- package/drizzle/0036_project_drive_instructions.sql +1 -0
- package/drizzle/meta/0036_snapshot.json +1128 -0
- package/drizzle/meta/_journal.json +7 -0
- package/index.js +973 -594
- package/package.json +1 -1
- package/skills/burner/review-ticket.md +22 -8
- package/skills/burner/verify-fixes.md +9 -1
- package/skills/packs/runcastle/skills/prepare/SKILL.md +5 -1
- package/skills/packs/runcastle/skills/tickets/SKILL.md +2 -0
- package/web/assets/{index-C2XRT4ng.js → index-CgCA-f62.js} +22 -21
- package/web/assets/index-D697YlOb.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-5D6uCZUU.css +0 -1
package/index.js
CHANGED
|
@@ -24,6 +24,12 @@ function newId(prefix) {
|
|
|
24
24
|
var init_ids = () => {};
|
|
25
25
|
|
|
26
26
|
// ../core/src/docs.ts
|
|
27
|
+
function withheldFeatureDocs(state) {
|
|
28
|
+
if (!state.carriedNotesOpen)
|
|
29
|
+
return WITHHELD_FEATURE_DOCS;
|
|
30
|
+
const { "test-notes.md": _testNotes, ...rest } = WITHHELD_FEATURE_DOCS;
|
|
31
|
+
return rest;
|
|
32
|
+
}
|
|
27
33
|
function isAgentDigestDoc(relPath) {
|
|
28
34
|
if (relPath.includes("/") || relPath.includes("\\"))
|
|
29
35
|
return false;
|
|
@@ -322,7 +328,8 @@ var init_schemas = __esm(() => {
|
|
|
322
328
|
"devCommand",
|
|
323
329
|
"driveSetupCommand",
|
|
324
330
|
"driveStopCommand",
|
|
325
|
-
"dbResetCommand"
|
|
331
|
+
"dbResetCommand",
|
|
332
|
+
"driveInstructions"
|
|
326
333
|
];
|
|
327
334
|
PreparedKey = z2.enum(PREPARED_KEYS);
|
|
328
335
|
DRIVE_LOOP_KEYS = [
|
|
@@ -345,6 +352,7 @@ var init_schemas = __esm(() => {
|
|
|
345
352
|
dbResetCommand: z2.string().optional(),
|
|
346
353
|
driveSetupCommand: z2.string().optional(),
|
|
347
354
|
driveStopCommand: z2.string().optional(),
|
|
355
|
+
driveInstructions: z2.string().optional(),
|
|
348
356
|
closedAt: z2.number().optional()
|
|
349
357
|
});
|
|
350
358
|
ProjectFinding = z2.object({
|
|
@@ -366,6 +374,7 @@ var init_schemas = __esm(() => {
|
|
|
366
374
|
brief: z2.string().optional(),
|
|
367
375
|
mapped: z2.boolean(),
|
|
368
376
|
lap: z2.number(),
|
|
377
|
+
ticketsReadyLap: z2.number().nullable(),
|
|
369
378
|
phase: Phase,
|
|
370
379
|
branch: z2.string(),
|
|
371
380
|
baseBranch: z2.string().optional(),
|
|
@@ -550,6 +559,9 @@ function nextPhase(feature) {
|
|
|
550
559
|
return null;
|
|
551
560
|
return ORDER[i + 1];
|
|
552
561
|
}
|
|
562
|
+
function isPastPhase(feature, phase) {
|
|
563
|
+
return ORDER.indexOf(feature.phase) > ORDER.indexOf(phase);
|
|
564
|
+
}
|
|
553
565
|
function previousPhase(feature) {
|
|
554
566
|
const i = ORDER.indexOf(feature.phase);
|
|
555
567
|
if (i <= 0)
|
|
@@ -637,6 +649,7 @@ var init_db_schema = __esm(() => {
|
|
|
637
649
|
verifyCommands: text("verify_commands"),
|
|
638
650
|
knownFailures: text("known_failures"),
|
|
639
651
|
dbResetCommand: text("db_reset_command"),
|
|
652
|
+
driveInstructions: text("drive_instructions"),
|
|
640
653
|
driveSetupCommand: text("drive_setup_command"),
|
|
641
654
|
driveStopCommand: text("drive_stop_command")
|
|
642
655
|
});
|
|
@@ -659,6 +672,7 @@ var init_db_schema = __esm(() => {
|
|
|
659
672
|
brief: text("brief"),
|
|
660
673
|
mapped: integer("mapped", { mode: "boolean" }).notNull().default(false),
|
|
661
674
|
lap: integer("lap").notNull().default(1),
|
|
675
|
+
ticketsReadyLap: integer("tickets_ready_lap"),
|
|
662
676
|
phase: text("phase").notNull().$type(),
|
|
663
677
|
branch: text("branch").notNull(),
|
|
664
678
|
baseBranch: text("base_branch"),
|
|
@@ -2099,6 +2113,7 @@ function rowToFeature(row) {
|
|
|
2099
2113
|
brief: row.brief ?? undefined,
|
|
2100
2114
|
mapped: row.mapped,
|
|
2101
2115
|
lap: row.lap,
|
|
2116
|
+
ticketsReadyLap: row.ticketsReadyLap,
|
|
2102
2117
|
phase: row.phase,
|
|
2103
2118
|
branch: row.branch,
|
|
2104
2119
|
baseBranch: row.baseBranch ?? undefined,
|
|
@@ -2120,6 +2135,7 @@ function rowToProject(row) {
|
|
|
2120
2135
|
dbResetCommand: row.dbResetCommand ?? undefined,
|
|
2121
2136
|
driveSetupCommand: row.driveSetupCommand ?? undefined,
|
|
2122
2137
|
driveStopCommand: row.driveStopCommand ?? undefined,
|
|
2138
|
+
driveInstructions: row.driveInstructions ?? undefined,
|
|
2123
2139
|
closedAt: row.closedAt ?? undefined
|
|
2124
2140
|
});
|
|
2125
2141
|
}
|
|
@@ -2223,6 +2239,16 @@ function setPhase(ctx, featureId, toPhase, eventType = "phase.changed", message)
|
|
|
2223
2239
|
});
|
|
2224
2240
|
return { ...current, phase: toPhase };
|
|
2225
2241
|
}
|
|
2242
|
+
function markTicketsReady(ctx, featureId, nextPhase2) {
|
|
2243
|
+
const current = getFeatureRow(ctx, featureId);
|
|
2244
|
+
ctx.db.update(features).set({ ticketsReadyLap: current.lap }).where(eq2(features.id, featureId)).run();
|
|
2245
|
+
emit(ctx, featureId, {
|
|
2246
|
+
type: "tickets.awaiting_burn",
|
|
2247
|
+
message: "tickets complete \u2014 waiting on the human Burn click (gate G3)",
|
|
2248
|
+
data: { phase: current.phase, nextPhase: nextPhase2, waitingOn: "human burn" }
|
|
2249
|
+
});
|
|
2250
|
+
return { ...current, ticketsReadyLap: current.lap };
|
|
2251
|
+
}
|
|
2226
2252
|
function setFeatureStatus(ctx, featureId, status) {
|
|
2227
2253
|
const current = getFeatureRow(ctx, featureId);
|
|
2228
2254
|
ctx.db.update(features).set({ status }).where(eq2(features.id, featureId)).run();
|
|
@@ -2891,7 +2917,8 @@ var init_findings = __esm(() => {
|
|
|
2891
2917
|
devCommand: projects.devCommand,
|
|
2892
2918
|
driveSetupCommand: projects.driveSetupCommand,
|
|
2893
2919
|
driveStopCommand: projects.driveStopCommand,
|
|
2894
|
-
dbResetCommand: projects.dbResetCommand
|
|
2920
|
+
dbResetCommand: projects.dbResetCommand,
|
|
2921
|
+
driveInstructions: projects.driveInstructions
|
|
2895
2922
|
};
|
|
2896
2923
|
COLUMN_NAME = {
|
|
2897
2924
|
setupCommand: "setupCommand",
|
|
@@ -2900,7 +2927,8 @@ var init_findings = __esm(() => {
|
|
|
2900
2927
|
devCommand: "devCommand",
|
|
2901
2928
|
driveSetupCommand: "driveSetupCommand",
|
|
2902
2929
|
driveStopCommand: "driveStopCommand",
|
|
2903
|
-
dbResetCommand: "dbResetCommand"
|
|
2930
|
+
dbResetCommand: "dbResetCommand",
|
|
2931
|
+
driveInstructions: "driveInstructions"
|
|
2904
2932
|
};
|
|
2905
2933
|
PREPARED_SET = new Set(PREPARED_KEYS);
|
|
2906
2934
|
UNVERIFIED = { verifiedAt: null, verifiedSha: null };
|
|
@@ -4192,6 +4220,460 @@ var init_git = __esm(() => {
|
|
|
4192
4220
|
TEMP_BRANCH_PREFIXES = [RESEARCH_BRANCH_PREFIX, TICKET_BRANCH_PREFIX];
|
|
4193
4221
|
});
|
|
4194
4222
|
|
|
4223
|
+
// src/services/tickets.ts
|
|
4224
|
+
import { and as and5, asc as asc3, eq as eq5, inArray } from "drizzle-orm";
|
|
4225
|
+
function rowToTicket(row) {
|
|
4226
|
+
return Ticket.parse({
|
|
4227
|
+
id: row.id,
|
|
4228
|
+
featureId: row.featureId,
|
|
4229
|
+
seq: row.seq,
|
|
4230
|
+
title: row.title,
|
|
4231
|
+
goal: row.goal,
|
|
4232
|
+
context: row.context,
|
|
4233
|
+
acceptanceCriteria: row.acceptanceCriteria,
|
|
4234
|
+
seams: row.seams,
|
|
4235
|
+
blockedBy: row.blockedBy,
|
|
4236
|
+
kind: row.kind,
|
|
4237
|
+
passKind: row.passKind,
|
|
4238
|
+
reviewedCommit: row.reviewedCommit,
|
|
4239
|
+
completedAt: row.completedAt,
|
|
4240
|
+
model: row.model ?? undefined,
|
|
4241
|
+
lap: row.lap,
|
|
4242
|
+
status: row.status,
|
|
4243
|
+
commits: row.commits,
|
|
4244
|
+
error: row.error ?? undefined,
|
|
4245
|
+
attemptBranch: row.attemptBranch ?? undefined,
|
|
4246
|
+
conflictFiles: row.conflictFiles ?? undefined,
|
|
4247
|
+
digest: row.digest ?? undefined,
|
|
4248
|
+
originFindingId: row.originFindingId ?? undefined
|
|
4249
|
+
});
|
|
4250
|
+
}
|
|
4251
|
+
function resolveBlocking(inputs, startSeq) {
|
|
4252
|
+
try {
|
|
4253
|
+
return resolveBatchBlocking(inputs, { startSeq });
|
|
4254
|
+
} catch (e) {
|
|
4255
|
+
if (e instanceof BlockingEdgeError)
|
|
4256
|
+
throw new InvalidInputError(e.message);
|
|
4257
|
+
throw e;
|
|
4258
|
+
}
|
|
4259
|
+
}
|
|
4260
|
+
function normalizeModel(ctx, value) {
|
|
4261
|
+
const id = value?.trim();
|
|
4262
|
+
if (!id)
|
|
4263
|
+
return null;
|
|
4264
|
+
const roster = modelRoster(ctx.config);
|
|
4265
|
+
if (!roster.some((m) => m.id === id)) {
|
|
4266
|
+
throw new InvalidInputError(`unknown model "${id}" \u2014 assign one of the configured models: ${roster.map((m) => m.id).join(", ")}`);
|
|
4267
|
+
}
|
|
4268
|
+
return id;
|
|
4269
|
+
}
|
|
4270
|
+
function listByFeature2(ctx, featureId) {
|
|
4271
|
+
return ctx.db.select().from(tickets).where(eq5(tickets.featureId, featureId)).orderBy(asc3(tickets.seq)).all().map(rowToTicket);
|
|
4272
|
+
}
|
|
4273
|
+
function storeTickets(ctx, featureId, inputs, options = {}) {
|
|
4274
|
+
if (inputs.length === 0)
|
|
4275
|
+
return [];
|
|
4276
|
+
const { lap } = getFeatureRow(ctx, featureId);
|
|
4277
|
+
const existing = ctx.db.select({ seq: tickets.seq }).from(tickets).where(eq5(tickets.featureId, featureId)).all();
|
|
4278
|
+
const startSeq = existing.reduce((max2, r) => Math.max(max2, r.seq), 0) + 1;
|
|
4279
|
+
const resolved = options.blockedByAreGlobal ? inputs.map((input, index2) => ({ seq: startSeq + index2, blockedBy: input.blockedBy })) : resolveBlocking(inputs, startSeq);
|
|
4280
|
+
const rows = inputs.map((t, i) => ({
|
|
4281
|
+
id: newId("tkt"),
|
|
4282
|
+
featureId,
|
|
4283
|
+
seq: resolved[i].seq,
|
|
4284
|
+
title: t.title,
|
|
4285
|
+
goal: t.goal,
|
|
4286
|
+
context: t.context,
|
|
4287
|
+
acceptanceCriteria: t.acceptanceCriteria,
|
|
4288
|
+
seams: t.seams,
|
|
4289
|
+
blockedBy: resolved[i].blockedBy,
|
|
4290
|
+
kind: t.kind ?? "implementation",
|
|
4291
|
+
passKind: t.passKind ?? "review",
|
|
4292
|
+
reviewedCommit: null,
|
|
4293
|
+
completedAt: null,
|
|
4294
|
+
model: normalizeModel(ctx, t.model),
|
|
4295
|
+
lap,
|
|
4296
|
+
status: "pending",
|
|
4297
|
+
commits: [],
|
|
4298
|
+
error: null,
|
|
4299
|
+
attemptBranch: null,
|
|
4300
|
+
conflictFiles: null,
|
|
4301
|
+
digest: null,
|
|
4302
|
+
originFindingId: t.originFindingId ?? null
|
|
4303
|
+
}));
|
|
4304
|
+
ctx.db.insert(tickets).values(rows).run();
|
|
4305
|
+
emit(ctx, featureId, {
|
|
4306
|
+
type: "tickets.stored",
|
|
4307
|
+
message: `${rows.length} ticket(s) stored`,
|
|
4308
|
+
data: { count: rows.length, seqs: rows.map((r) => r.seq) }
|
|
4309
|
+
});
|
|
4310
|
+
return rows.map(rowToTicket);
|
|
4311
|
+
}
|
|
4312
|
+
function getTicket(ctx, id) {
|
|
4313
|
+
const row = ctx.db.select().from(tickets).where(eq5(tickets.id, id)).get();
|
|
4314
|
+
if (!row)
|
|
4315
|
+
throw new NotFoundError(`ticket ${id} not found`);
|
|
4316
|
+
return rowToTicket(row);
|
|
4317
|
+
}
|
|
4318
|
+
function assertMutable(t, verb) {
|
|
4319
|
+
if (!MUTABLE_STATUSES.has(t.status)) {
|
|
4320
|
+
throw new InvalidInputError(`cannot ${verb} ticket ${t.seq} \u2014 it is ${t.status}; only pending or failed tickets can be ${verb}${verb.endsWith("l") ? "led" : "ed"}`);
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
function editTicket(ctx, id, patch) {
|
|
4324
|
+
const current = getTicket(ctx, id);
|
|
4325
|
+
assertMutable(current, "edit");
|
|
4326
|
+
const set = {};
|
|
4327
|
+
if (patch.title !== undefined)
|
|
4328
|
+
set.title = patch.title;
|
|
4329
|
+
if (patch.goal !== undefined)
|
|
4330
|
+
set.goal = patch.goal;
|
|
4331
|
+
if (patch.context !== undefined)
|
|
4332
|
+
set.context = patch.context;
|
|
4333
|
+
if (patch.acceptanceCriteria !== undefined)
|
|
4334
|
+
set.acceptanceCriteria = patch.acceptanceCriteria;
|
|
4335
|
+
if (patch.seams !== undefined)
|
|
4336
|
+
set.seams = patch.seams;
|
|
4337
|
+
if (patch.model !== undefined)
|
|
4338
|
+
set.model = normalizeModel(ctx, patch.model);
|
|
4339
|
+
if (Object.keys(set).length === 0) {
|
|
4340
|
+
throw new InvalidInputError("nothing to edit \u2014 pass at least one field");
|
|
4341
|
+
}
|
|
4342
|
+
ctx.db.update(tickets).set(set).where(eq5(tickets.id, id)).run();
|
|
4343
|
+
emit(ctx, current.featureId, {
|
|
4344
|
+
type: "ticket.edited",
|
|
4345
|
+
message: `ticket ${current.seq} content edited (${Object.keys(set).join(", ")})`,
|
|
4346
|
+
ticketId: id,
|
|
4347
|
+
data: { fields: Object.keys(set) }
|
|
4348
|
+
});
|
|
4349
|
+
return getTicket(ctx, id);
|
|
4350
|
+
}
|
|
4351
|
+
function cancelTicket(ctx, id, reason, opts) {
|
|
4352
|
+
const current = getTicket(ctx, id);
|
|
4353
|
+
if (!(opts?.agentStopped && current.status === "burning"))
|
|
4354
|
+
assertMutable(current, "cancel");
|
|
4355
|
+
ctx.db.update(tickets).set({
|
|
4356
|
+
status: "cancelled",
|
|
4357
|
+
error: reason?.trim() ? reason.trim() : null,
|
|
4358
|
+
completedAt: Date.now()
|
|
4359
|
+
}).where(eq5(tickets.id, id)).run();
|
|
4360
|
+
emit(ctx, current.featureId, {
|
|
4361
|
+
type: "ticket.cancelled",
|
|
4362
|
+
message: `ticket ${current.seq} cancelled${reason?.trim() ? `: ${reason.trim()}` : ""}`,
|
|
4363
|
+
ticketId: id,
|
|
4364
|
+
data: { reason: reason ?? null }
|
|
4365
|
+
});
|
|
4366
|
+
return getTicket(ctx, id);
|
|
4367
|
+
}
|
|
4368
|
+
function sweepOrphanedBurning(ctx, featureId, reason) {
|
|
4369
|
+
const orphaned = ctx.db.select().from(tickets).where(and5(eq5(tickets.featureId, featureId), eq5(tickets.status, "burning"))).all().map(rowToTicket);
|
|
4370
|
+
const swept = [];
|
|
4371
|
+
for (const t of orphaned) {
|
|
4372
|
+
swept.push(updateTicket(ctx, t.id, { status: "failed", error: reason }));
|
|
4373
|
+
if (t.originFindingId)
|
|
4374
|
+
markFailed(ctx, t.originFindingId, reason);
|
|
4375
|
+
emit(ctx, featureId, {
|
|
4376
|
+
type: "ticket.failed",
|
|
4377
|
+
message: `ticket ${t.seq} failed: ${reason}`,
|
|
4378
|
+
ticketId: t.id,
|
|
4379
|
+
data: { error: reason, orphaned: true }
|
|
4380
|
+
});
|
|
4381
|
+
}
|
|
4382
|
+
return swept;
|
|
4383
|
+
}
|
|
4384
|
+
function updateTicket(ctx, id, patch) {
|
|
4385
|
+
const current = ctx.db.select().from(tickets).where(eq5(tickets.id, id)).get();
|
|
4386
|
+
if (!current)
|
|
4387
|
+
throw new NotFoundError(`ticket ${id} not found`);
|
|
4388
|
+
const set = {};
|
|
4389
|
+
if (patch.status !== undefined)
|
|
4390
|
+
set.status = patch.status;
|
|
4391
|
+
if (patch.status !== undefined) {
|
|
4392
|
+
set.completedAt = ["done", "failed", "cancelled"].includes(patch.status) ? Date.now() : null;
|
|
4393
|
+
}
|
|
4394
|
+
if (patch.commits !== undefined)
|
|
4395
|
+
set.commits = patch.commits;
|
|
4396
|
+
if (patch.error !== undefined)
|
|
4397
|
+
set.error = patch.error;
|
|
4398
|
+
if (patch.attemptBranch !== undefined)
|
|
4399
|
+
set.attemptBranch = patch.attemptBranch;
|
|
4400
|
+
if (patch.conflictFiles !== undefined)
|
|
4401
|
+
set.conflictFiles = patch.conflictFiles;
|
|
4402
|
+
if (patch.digest !== undefined)
|
|
4403
|
+
set.digest = patch.digest;
|
|
4404
|
+
if (patch.reviewedCommit !== undefined)
|
|
4405
|
+
set.reviewedCommit = patch.reviewedCommit;
|
|
4406
|
+
ctx.db.update(tickets).set(set).where(eq5(tickets.id, id)).run();
|
|
4407
|
+
emit(ctx, current.featureId, {
|
|
4408
|
+
type: "ticket.updated",
|
|
4409
|
+
message: `ticket ${current.seq} \u2192 ${patch.status ?? current.status}`,
|
|
4410
|
+
ticketId: id,
|
|
4411
|
+
data: patch
|
|
4412
|
+
});
|
|
4413
|
+
const updated = ctx.db.select().from(tickets).where(eq5(tickets.id, id)).get();
|
|
4414
|
+
return rowToTicket(updated);
|
|
4415
|
+
}
|
|
4416
|
+
function listByIds(ctx, ids2) {
|
|
4417
|
+
if (ids2.length === 0)
|
|
4418
|
+
return [];
|
|
4419
|
+
return ctx.db.select().from(tickets).where(inArray(tickets.id, [...ids2])).orderBy(asc3(tickets.seq)).all().map(rowToTicket);
|
|
4420
|
+
}
|
|
4421
|
+
function timingWallMs(data) {
|
|
4422
|
+
if (typeof data !== "object" || data === null)
|
|
4423
|
+
return;
|
|
4424
|
+
const ms = data.wallMs;
|
|
4425
|
+
return typeof ms === "number" && Number.isFinite(ms) && ms > 0 ? ms : undefined;
|
|
4426
|
+
}
|
|
4427
|
+
function ticketDurationStats(ctx, projectId) {
|
|
4428
|
+
const rows = ctx.db.select({ ticketId: events.ticketId, data: events.data }).from(events).innerJoin(tickets, eq5(events.ticketId, tickets.id)).where(and5(eq5(events.projectId, projectId), eq5(events.type, "ticket.timing"), eq5(tickets.status, "done"), eq5(tickets.kind, "implementation"))).orderBy(asc3(events.id)).all();
|
|
4429
|
+
const perTicket = new Map;
|
|
4430
|
+
for (const row of rows) {
|
|
4431
|
+
const ms = timingWallMs(row.data);
|
|
4432
|
+
if (row.ticketId && ms !== undefined)
|
|
4433
|
+
perTicket.set(row.ticketId, ms);
|
|
4434
|
+
}
|
|
4435
|
+
const samples = [...perTicket.values()].sort((a, b) => a - b);
|
|
4436
|
+
if (samples.length === 0)
|
|
4437
|
+
return { medianMs: 0, sampleSize: 0 };
|
|
4438
|
+
const mid = samples.length >> 1;
|
|
4439
|
+
const medianMs = samples.length % 2 === 1 ? samples[mid] : Math.round((samples[mid - 1] + samples[mid]) / 2);
|
|
4440
|
+
return { medianMs, sampleSize: samples.length };
|
|
4441
|
+
}
|
|
4442
|
+
var MUTABLE_STATUSES;
|
|
4443
|
+
var init_tickets = __esm(() => {
|
|
4444
|
+
init_src();
|
|
4445
|
+
init_schema();
|
|
4446
|
+
init_errors();
|
|
4447
|
+
init_events();
|
|
4448
|
+
init_repo();
|
|
4449
|
+
init_review_findings();
|
|
4450
|
+
MUTABLE_STATUSES = new Set(["pending", "failed"]);
|
|
4451
|
+
});
|
|
4452
|
+
|
|
4453
|
+
// src/services/review-findings.ts
|
|
4454
|
+
import { and as and6, asc as asc4, eq as eq6, isNotNull } from "drizzle-orm";
|
|
4455
|
+
function rowToFinding(row) {
|
|
4456
|
+
return ReviewFinding.parse(row);
|
|
4457
|
+
}
|
|
4458
|
+
function getFinding(ctx, id) {
|
|
4459
|
+
const row = ctx.db.select().from(reviewFindings).where(eq6(reviewFindings.id, id)).get();
|
|
4460
|
+
if (!row)
|
|
4461
|
+
throw new NotFoundError(`review finding ${id} not found`);
|
|
4462
|
+
return rowToFinding(row);
|
|
4463
|
+
}
|
|
4464
|
+
function listByFeature3(ctx, featureId) {
|
|
4465
|
+
return ctx.db.select().from(reviewFindings).where(eq6(reviewFindings.featureId, featureId)).orderBy(asc4(reviewFindings.createdAt), asc4(reviewFindings.id)).all().map(rowToFinding);
|
|
4466
|
+
}
|
|
4467
|
+
function buildFixTicket(finding) {
|
|
4468
|
+
return {
|
|
4469
|
+
title: finding.title,
|
|
4470
|
+
goal: `Fix: ${finding.title}`,
|
|
4471
|
+
context: [
|
|
4472
|
+
`Location: ${finding.location}`,
|
|
4473
|
+
`Citation: ${finding.citation}`,
|
|
4474
|
+
`Detail: ${finding.detail}`,
|
|
4475
|
+
`Repro step: ${finding.reproStep}`
|
|
4476
|
+
].join(`
|
|
4477
|
+
|
|
4478
|
+
`),
|
|
4479
|
+
acceptanceCriteria: [
|
|
4480
|
+
`The repro step no longer reproduces / the cited criterion holds: ${finding.reproStep}`
|
|
4481
|
+
],
|
|
4482
|
+
seams: [],
|
|
4483
|
+
blockedBy: [],
|
|
4484
|
+
kind: "implementation",
|
|
4485
|
+
originFindingId: finding.id
|
|
4486
|
+
};
|
|
4487
|
+
}
|
|
4488
|
+
function reportFinding(ctx, args) {
|
|
4489
|
+
const { featureId, reviewTicket, input } = args;
|
|
4490
|
+
const feature = getFeatureRow(ctx, featureId);
|
|
4491
|
+
const id = newId("finding");
|
|
4492
|
+
const ticketCount = ctx.db.select({ id: reviewFindings.id }).from(reviewFindings).where(and6(eq6(reviewFindings.reviewTicketId, reviewTicket.id), isNotNull(reviewFindings.fixTicketId))).all().length;
|
|
4493
|
+
const verification = reviewTicket.passKind === "verification";
|
|
4494
|
+
const overCap = input.kind === "defect" && !verification && ticketCount >= AUTO_FIX_CAP;
|
|
4495
|
+
ctx.db.insert(reviewFindings).values({
|
|
4496
|
+
id,
|
|
4497
|
+
featureId,
|
|
4498
|
+
lap: feature.lap,
|
|
4499
|
+
reviewTicketId: reviewTicket.id,
|
|
4500
|
+
...input,
|
|
4501
|
+
reproStep: input.reproStep ?? "",
|
|
4502
|
+
status: "open",
|
|
4503
|
+
openReason: verification && input.kind === "defect" ? "verification" : overCap ? "over-cap" : null,
|
|
4504
|
+
failureReason: null,
|
|
4505
|
+
fixTicketId: null,
|
|
4506
|
+
createdAt: Date.now()
|
|
4507
|
+
}).run();
|
|
4508
|
+
let fixTicket = null;
|
|
4509
|
+
if (input.kind === "defect" && !overCap && !verification) {
|
|
4510
|
+
const finding2 = getFinding(ctx, id);
|
|
4511
|
+
fixTicket = storeTickets(ctx, featureId, [
|
|
4512
|
+
{ ...buildFixTicket(finding2), blockedBy: [reviewTicket.seq] }
|
|
4513
|
+
], { blockedByAreGlobal: true })[0];
|
|
4514
|
+
ctx.db.update(reviewFindings).set({ fixTicketId: fixTicket.id }).where(eq6(reviewFindings.id, id)).run();
|
|
4515
|
+
}
|
|
4516
|
+
const finding = getFinding(ctx, id);
|
|
4517
|
+
emit(ctx, featureId, {
|
|
4518
|
+
type: "finding.reported",
|
|
4519
|
+
message: `${input.kind} reported: ${input.title}`,
|
|
4520
|
+
ticketId: fixTicket?.id,
|
|
4521
|
+
data: { findingId: id, fixTicketId: fixTicket?.id, overCap }
|
|
4522
|
+
});
|
|
4523
|
+
return { finding, fixTicket, overCap };
|
|
4524
|
+
}
|
|
4525
|
+
function updateStatus(ctx, findingId, patch) {
|
|
4526
|
+
const current = getFinding(ctx, findingId);
|
|
4527
|
+
ctx.db.update(reviewFindings).set(patch).where(eq6(reviewFindings.id, findingId)).run();
|
|
4528
|
+
emit(ctx, current.featureId, {
|
|
4529
|
+
type: "finding.updated",
|
|
4530
|
+
message: `finding ${findingId} marked ${patch.status}`,
|
|
4531
|
+
data: { findingId, status: patch.status, reason: patch.failureReason }
|
|
4532
|
+
});
|
|
4533
|
+
return getFinding(ctx, findingId);
|
|
4534
|
+
}
|
|
4535
|
+
function markFixing(ctx, findingId) {
|
|
4536
|
+
return updateStatus(ctx, findingId, {
|
|
4537
|
+
status: "fixing",
|
|
4538
|
+
openReason: null,
|
|
4539
|
+
failureReason: null
|
|
4540
|
+
});
|
|
4541
|
+
}
|
|
4542
|
+
function markFixed(ctx, findingId) {
|
|
4543
|
+
return updateStatus(ctx, findingId, {
|
|
4544
|
+
status: "fixed",
|
|
4545
|
+
openReason: null,
|
|
4546
|
+
failureReason: null
|
|
4547
|
+
});
|
|
4548
|
+
}
|
|
4549
|
+
function markFailed(ctx, findingId, reason) {
|
|
4550
|
+
return updateStatus(ctx, findingId, {
|
|
4551
|
+
status: "failed",
|
|
4552
|
+
openReason: "fix-failed",
|
|
4553
|
+
failureReason: reason ?? null
|
|
4554
|
+
});
|
|
4555
|
+
}
|
|
4556
|
+
function markFixProgress(ctx, findingId, progress, reason) {
|
|
4557
|
+
if (progress === "fixing")
|
|
4558
|
+
return markFixing(ctx, findingId);
|
|
4559
|
+
if (progress === "fixed")
|
|
4560
|
+
return markFixed(ctx, findingId);
|
|
4561
|
+
return markFailed(ctx, findingId, reason);
|
|
4562
|
+
}
|
|
4563
|
+
function dismiss(ctx, findingId) {
|
|
4564
|
+
return updateStatus(ctx, findingId, {
|
|
4565
|
+
status: "dismissed",
|
|
4566
|
+
openReason: null,
|
|
4567
|
+
failureReason: null
|
|
4568
|
+
});
|
|
4569
|
+
}
|
|
4570
|
+
function defectState(finding, fixTicket) {
|
|
4571
|
+
if (finding.status === "dismissed")
|
|
4572
|
+
return "dismissed";
|
|
4573
|
+
if (finding.status === "fixed" || fixTicket?.status === "done")
|
|
4574
|
+
return "fixed";
|
|
4575
|
+
if (fixTicket && fixTicket.status !== "failed" && fixTicket.status !== "cancelled") {
|
|
4576
|
+
return "fixing";
|
|
4577
|
+
}
|
|
4578
|
+
return finding.status === "open" || finding.status === "failed" || fixTicket?.status === "failed" || fixTicket?.status === "cancelled" ? "open" : "fixing";
|
|
4579
|
+
}
|
|
4580
|
+
function viewByFeature(ctx, featureId) {
|
|
4581
|
+
const findings = listByFeature3(ctx, featureId);
|
|
4582
|
+
const tickets2 = listByFeature2(ctx, featureId);
|
|
4583
|
+
const summary = { found: 0, fixed: 0, open: 0, observations: 0 };
|
|
4584
|
+
const openDefects = [];
|
|
4585
|
+
for (const finding of findings) {
|
|
4586
|
+
if (finding.kind === "observation") {
|
|
4587
|
+
summary.observations += 1;
|
|
4588
|
+
continue;
|
|
4589
|
+
}
|
|
4590
|
+
summary.found += 1;
|
|
4591
|
+
const state = defectState(finding, fixTicketOf(finding, tickets2));
|
|
4592
|
+
if (state === "fixed")
|
|
4593
|
+
summary.fixed += 1;
|
|
4594
|
+
else if (state === "open") {
|
|
4595
|
+
summary.open += 1;
|
|
4596
|
+
openDefects.push(finding);
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
return { findings, openDefects, summary };
|
|
4600
|
+
}
|
|
4601
|
+
function fixTicketOf(finding, tickets2) {
|
|
4602
|
+
return finding.fixTicketId ? tickets2.find((t) => t.id === finding.fixTicketId) : undefined;
|
|
4603
|
+
}
|
|
4604
|
+
function promoteOpenDefects(ctx, featureId, findingIds) {
|
|
4605
|
+
const open = viewByFeature(ctx, featureId).openDefects;
|
|
4606
|
+
const defects = findingIds ? findingIds.map((id) => {
|
|
4607
|
+
const finding = open.find((candidate) => candidate.id === id);
|
|
4608
|
+
if (!finding)
|
|
4609
|
+
throw new InvalidInputError(`finding ${id} is not an open defect of this feature`);
|
|
4610
|
+
return finding;
|
|
4611
|
+
}) : open;
|
|
4612
|
+
if (findingIds && new Set(findingIds).size !== findingIds.length)
|
|
4613
|
+
throw new InvalidInputError("duplicate finding selected");
|
|
4614
|
+
if (defects.length === 0)
|
|
4615
|
+
throw new InvalidInputError("no open defects to fix");
|
|
4616
|
+
const tickets2 = storeTickets(ctx, featureId, defects.map(buildFixTicket));
|
|
4617
|
+
const findings = defects.map((defect, index2) => {
|
|
4618
|
+
ctx.db.update(reviewFindings).set({
|
|
4619
|
+
status: "fixing",
|
|
4620
|
+
openReason: null,
|
|
4621
|
+
failureReason: null,
|
|
4622
|
+
fixTicketId: tickets2[index2].id
|
|
4623
|
+
}).where(eq6(reviewFindings.id, defect.id)).run();
|
|
4624
|
+
return getFinding(ctx, defect.id);
|
|
4625
|
+
});
|
|
4626
|
+
emit(ctx, featureId, {
|
|
4627
|
+
type: "finding.fixing",
|
|
4628
|
+
message: `${findings.length} open defect${findings.length === 1 ? "" : "s"} promoted to fix ticket${tickets2.length === 1 ? ` ${tickets2[0].seq}` : `s ${tickets2.map((t) => t.seq).join(", ")}`}`,
|
|
4629
|
+
data: { findingIds: findings.map((f) => f.id), seqs: tickets2.map((t) => t.seq) }
|
|
4630
|
+
});
|
|
4631
|
+
return { findings, tickets: tickets2 };
|
|
4632
|
+
}
|
|
4633
|
+
var AUTO_FIX_CAP = 8;
|
|
4634
|
+
var init_review_findings = __esm(() => {
|
|
4635
|
+
init_src();
|
|
4636
|
+
init_schema();
|
|
4637
|
+
init_errors();
|
|
4638
|
+
init_events();
|
|
4639
|
+
init_repo();
|
|
4640
|
+
init_tickets();
|
|
4641
|
+
});
|
|
4642
|
+
|
|
4643
|
+
// src/services/carried-work.ts
|
|
4644
|
+
import { and as and7, eq as eq7 } from "drizzle-orm";
|
|
4645
|
+
function carriedWork(ctx, featureId) {
|
|
4646
|
+
const carried = ctx.db.select({ id: testNotes.id }).from(testNotes).where(and7(eq7(testNotes.featureId, featureId), eq7(testNotes.status, "carried"))).all();
|
|
4647
|
+
return {
|
|
4648
|
+
carriedNotes: carried.length,
|
|
4649
|
+
openDefects: viewByFeature(ctx, featureId).openDefects.map((defect) => ({
|
|
4650
|
+
title: defect.title,
|
|
4651
|
+
location: defect.location,
|
|
4652
|
+
detail: defect.detail,
|
|
4653
|
+
reproStep: defect.reproStep
|
|
4654
|
+
}))
|
|
4655
|
+
};
|
|
4656
|
+
}
|
|
4657
|
+
function hasCarriedWork(carried) {
|
|
4658
|
+
return carried !== undefined && (carried.carriedNotes > 0 || carried.openDefects.length > 0);
|
|
4659
|
+
}
|
|
4660
|
+
function carriedWorkSummary(carried) {
|
|
4661
|
+
if (!hasCarriedWork(carried))
|
|
4662
|
+
return;
|
|
4663
|
+
const parts = [];
|
|
4664
|
+
if (carried.carriedNotes > 0) {
|
|
4665
|
+
parts.push(`${carried.carriedNotes} note${carried.carriedNotes === 1 ? "" : "s"} carried`);
|
|
4666
|
+
}
|
|
4667
|
+
const defects = carried.openDefects.length;
|
|
4668
|
+
if (defects > 0)
|
|
4669
|
+
parts.push(`${defects} defect${defects === 1 ? "" : "s"} open`);
|
|
4670
|
+
return `${parts.join(" and ")} from earlier laps`;
|
|
4671
|
+
}
|
|
4672
|
+
var init_carried_work = __esm(() => {
|
|
4673
|
+
init_schema();
|
|
4674
|
+
init_review_findings();
|
|
4675
|
+
});
|
|
4676
|
+
|
|
4195
4677
|
// src/launcher/edit-guard.ts
|
|
4196
4678
|
import { isAbsolute, relative, resolve as resolve5 } from "path";
|
|
4197
4679
|
function guardsEdits(kind) {
|
|
@@ -4304,15 +4786,15 @@ function noCodeRule(docs2) {
|
|
|
4304
4786
|
function conflictResolutionRule() {
|
|
4305
4787
|
return "- **This session resolves a merge conflict, so it DOES write code here.** Edit the " + "conflicted files in this checkout, resolving them from the feature docs\u2019 intent, and " + "commit the merge. That is the exception and its whole extent: the edit guard exempts " + "writes only while the merge is in progress, so work the merge revealed but did not " + "cause still rides a ticket.";
|
|
4306
4788
|
}
|
|
4307
|
-
function renderSystemPrompt(feature, kind, waypoint, lap, purpose, runtime = DEFAULT_RUNTIME) {
|
|
4789
|
+
function renderSystemPrompt(feature, kind, waypoint, lap, purpose, runtime = DEFAULT_RUNTIME, carried) {
|
|
4308
4790
|
if (lap !== undefined)
|
|
4309
|
-
return renderRevisitPrompt(feature, lap, purpose, runtime);
|
|
4791
|
+
return renderRevisitPrompt(feature, lap, purpose, runtime, carried);
|
|
4310
4792
|
if (kind === "waypoint")
|
|
4311
4793
|
return renderWaypointPrompt(feature, waypoint, runtime);
|
|
4312
4794
|
if (kind === "converge")
|
|
4313
4795
|
return renderConvergePrompt(feature, runtime);
|
|
4314
4796
|
if (kind === "revisit")
|
|
4315
|
-
return renderRevisitPrompt(feature, lap, purpose, runtime);
|
|
4797
|
+
return renderRevisitPrompt(feature, lap, purpose, runtime, carried);
|
|
4316
4798
|
if (kind === "qa")
|
|
4317
4799
|
return renderQaPrompt(feature, runtime);
|
|
4318
4800
|
const docs2 = featureDocsRel(feature.slug);
|
|
@@ -4460,18 +4942,30 @@ function renderConvergePrompt(feature, runtime = DEFAULT_RUNTIME) {
|
|
|
4460
4942
|
].join(`
|
|
4461
4943
|
`);
|
|
4462
4944
|
}
|
|
4463
|
-
function renderRevisitPrompt(feature, lap, purpose, runtime = DEFAULT_RUNTIME) {
|
|
4945
|
+
function renderRevisitPrompt(feature, lap, purpose, runtime = DEFAULT_RUNTIME, carried) {
|
|
4464
4946
|
if (lap !== undefined && purpose === "resolve-conflict") {
|
|
4465
4947
|
throw new Error(`cannot brief one revisit session as both lap ${lap} and a conflict resolution \u2014 ` + "a lap advances the pipeline and writes no code; resolving a conflict writes code " + "and advances nothing. Launch them as separate sessions.");
|
|
4466
4948
|
}
|
|
4467
4949
|
const docs2 = featureDocsRel(feature.slug);
|
|
4950
|
+
const carriedSummary = carriedWorkSummary(carried);
|
|
4468
4951
|
const lapIteration = lap ? [
|
|
4469
4952
|
`## This is lap ${lap}`,
|
|
4470
4953
|
`You are running **lap ${lap}** of this feature (ADR-0010): the human test-drove what`,
|
|
4471
|
-
`lap ${lap - 1} burned and came back with what it taught them
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4954
|
+
`lap ${lap - 1} burned and came back with what it taught them.`,
|
|
4955
|
+
...carriedSummary ? [
|
|
4956
|
+
`**${carriedSummary} \u2014 address them.** They are this lap's opening agenda:`,
|
|
4957
|
+
`- \`${docs2}/test-notes.md\`, section \`## Carried, still open\` \u2014 every note carried`,
|
|
4958
|
+
" and not yet done, whatever lap captured it. It exists; read it.",
|
|
4959
|
+
"- `get_feature_context` \u2192 `openDefects` \u2014 each open defect with its title,",
|
|
4960
|
+
" location, detail and repro step.",
|
|
4961
|
+
`- \`${docs2}/spec.md\`, section \`## Later laps\` \u2014 scope parked by earlier laps,`,
|
|
4962
|
+
" OPTIONAL; a missing one is normal, not an error."
|
|
4963
|
+
] : [
|
|
4964
|
+
"Its inputs, both OPTIONAL \u2014 a missing one is normal, not an error:",
|
|
4965
|
+
`- \`${docs2}/test-notes.md\`, section \`## Carried, still open\` \u2014 every note carried`,
|
|
4966
|
+
" and not yet done, whatever lap captured it.",
|
|
4967
|
+
`- \`${docs2}/spec.md\`, section \`## Later laps\` \u2014 scope parked by earlier laps.`
|
|
4968
|
+
],
|
|
4475
4969
|
`New decisions go under a \`## Lap ${lap}\` heading in \`${docs2}/decisions.md\`.`,
|
|
4476
4970
|
"",
|
|
4477
4971
|
"Unlike an ordinary revisit a lap MOVES the pipeline, and only this session will:",
|
|
@@ -4813,11 +5307,12 @@ function renderRunMcpConfig(runId, config2) {
|
|
|
4813
5307
|
};
|
|
4814
5308
|
}
|
|
4815
5309
|
function renderSessionPrompt(input) {
|
|
4816
|
-
const { session, feature, waypoint, prepare, projectBrief, driveFix, lap, purpose, runtime } = input;
|
|
5310
|
+
const { session, feature, waypoint, prepare, projectBrief, driveFix, lap, purpose, runtime, carried } = input;
|
|
4817
5311
|
if (driveFix)
|
|
4818
5312
|
return renderDriveFixPrompt(driveFix);
|
|
4819
|
-
if (feature)
|
|
4820
|
-
return renderSystemPrompt(feature, session.kind, waypoint, lap, purpose, runtime);
|
|
5313
|
+
if (feature) {
|
|
5314
|
+
return renderSystemPrompt(feature, session.kind, waypoint, lap, purpose, runtime, carried);
|
|
5315
|
+
}
|
|
4821
5316
|
if (prepare)
|
|
4822
5317
|
return renderPreparePrompt(prepare);
|
|
4823
5318
|
if (projectBrief)
|
|
@@ -4840,6 +5335,7 @@ var RUNCASTLE_MCP_ALLOW_RULES, SESSION_BASH_READ_RULES, SESSION_BASH_WRITE_RULES
|
|
|
4840
5335
|
var init_artifacts = __esm(() => {
|
|
4841
5336
|
init_src();
|
|
4842
5337
|
init_paths();
|
|
5338
|
+
init_carried_work();
|
|
4843
5339
|
init_asset_paths();
|
|
4844
5340
|
init_edit_guard();
|
|
4845
5341
|
init_skills();
|
|
@@ -5604,7 +6100,7 @@ var init_runtimes = __esm(() => {
|
|
|
5604
6100
|
|
|
5605
6101
|
// src/launcher/sessions.ts
|
|
5606
6102
|
import { statSync as statSync3 } from "fs";
|
|
5607
|
-
import { and as
|
|
6103
|
+
import { and as and8, desc as desc3, eq as eq8, inArray as inArray2, isNotNull as isNotNull2, sql as sql2 } from "drizzle-orm";
|
|
5608
6104
|
function createSessionRow(ctx, input) {
|
|
5609
6105
|
const row = ctx.db.insert(sessions).values({
|
|
5610
6106
|
id: newId("sess"),
|
|
@@ -5626,11 +6122,11 @@ function createSessionRow(ctx, input) {
|
|
|
5626
6122
|
return rowToSession(row);
|
|
5627
6123
|
}
|
|
5628
6124
|
function getSessionRow(ctx, id) {
|
|
5629
|
-
const row = ctx.db.select().from(sessions).where(
|
|
6125
|
+
const row = ctx.db.select().from(sessions).where(eq8(sessions.id, id)).get();
|
|
5630
6126
|
return row ? rowToSession(row) : null;
|
|
5631
6127
|
}
|
|
5632
6128
|
function activeSessionsForFeature(ctx, featureId) {
|
|
5633
|
-
return ctx.db.select().from(sessions).where(
|
|
6129
|
+
return ctx.db.select().from(sessions).where(and8(eq8(sessions.featureId, featureId), inArray2(sessions.status, ["launching", "live"]))).all().map(rowToSession);
|
|
5634
6130
|
}
|
|
5635
6131
|
function markAgentWorking(ctx, id) {
|
|
5636
6132
|
setAwaitingInput(ctx, id, false);
|
|
@@ -5639,7 +6135,7 @@ function markAwaitingInput(ctx, id) {
|
|
|
5639
6135
|
setAwaitingInput(ctx, id, true);
|
|
5640
6136
|
}
|
|
5641
6137
|
function setAwaitingInput(ctx, id, awaitingInput) {
|
|
5642
|
-
ctx.db.update(sessions).set({ awaitingInput }).where(
|
|
6138
|
+
ctx.db.update(sessions).set({ awaitingInput }).where(eq8(sessions.id, id)).run();
|
|
5643
6139
|
}
|
|
5644
6140
|
function markSessionLive(ctx, id, input = {}) {
|
|
5645
6141
|
const existing = getSessionRow(ctx, id);
|
|
@@ -5649,7 +6145,7 @@ function markSessionLive(ctx, id, input = {}) {
|
|
|
5649
6145
|
status: "live",
|
|
5650
6146
|
ccSessionId: input.ccSessionId ?? existing.ccSessionId ?? null,
|
|
5651
6147
|
transcriptPath: input.transcriptPath ?? existing.transcriptPath ?? null
|
|
5652
|
-
}).where(
|
|
6148
|
+
}).where(eq8(sessions.id, id)).run();
|
|
5653
6149
|
const firstTimeLive = existing.status !== "live";
|
|
5654
6150
|
if (firstTimeLive) {
|
|
5655
6151
|
promoteLastSession(ctx, id);
|
|
@@ -5657,8 +6153,9 @@ function markSessionLive(ctx, id, input = {}) {
|
|
|
5657
6153
|
}
|
|
5658
6154
|
return getSessionRow(ctx, id);
|
|
5659
6155
|
}
|
|
5660
|
-
function lapKickoff(lap) {
|
|
5661
|
-
|
|
6156
|
+
function lapKickoff(lap, carried) {
|
|
6157
|
+
const summary = carriedWorkSummary(carried);
|
|
6158
|
+
return `Proceed with your task: invoke the /runcastle:revisit skill for LAP ${lap} REVIEW ITERATION. ` + (summary ? `${summary} \u2014 address them. ` : "") + `Call get_feature_context, then read this feature's test-notes.md (its "## Carried, still ` + 'open" section \u2014 every note carried and not yet done, whatever lap captured it), the open ' + `defects in that payload's openDefects, and the "## Later laps" section of its spec.md. ` + (summary ? 'THE "## Later laps" SECTION MAY NOT EXIST; that is normal, not an error. ' : "ANY OF THOSE MAY NOT EXIST YET; that is normal, not an error \u2014 say so and carry on from " + "what I tell you. ") + "Interview me about what the test drive taught: what was wrong, what was missing, " + "what I want next. Write what we settle on into decisions.md and amend spec.md for this lap " + '(pruning anything you promote out of "## Later laps"), then call emit_tickets for this ' + `lap's work. Finish in THIS session: complete_phase through ideation \u2192 spec \u2192 tickets, then ` + "tell me to review the cards and click Burn.";
|
|
5662
6159
|
}
|
|
5663
6160
|
function kickoffLineFor(kind, override, runtime = DEFAULT_RUNTIME) {
|
|
5664
6161
|
return override ?? runtimeAdapterFor(runtime).kickoffLine(kind);
|
|
@@ -5668,7 +6165,7 @@ function lapInFlight(input) {
|
|
|
5668
6165
|
}
|
|
5669
6166
|
function planKickoff(input) {
|
|
5670
6167
|
const running = input.lapInFlight === true;
|
|
5671
|
-
const lapBriefing = input.lap > 1 ? lapKickoff(input.lap) : undefined;
|
|
6168
|
+
const lapBriefing = input.lap > 1 ? lapKickoff(input.lap, input.carried) : undefined;
|
|
5672
6169
|
const line = input.kickoffLine ?? (running ? lapBriefing : undefined);
|
|
5673
6170
|
const lap = running ? input.lap : undefined;
|
|
5674
6171
|
if (!line)
|
|
@@ -5900,31 +6397,31 @@ function markSessionEnded(ctx, id) {
|
|
|
5900
6397
|
forgetKickoff(id);
|
|
5901
6398
|
if (existing.featureId)
|
|
5902
6399
|
stopDocsWatch(existing.featureId);
|
|
5903
|
-
ctx.db.update(sessions).set({ status: "ended", ...existing.endedAt === undefined ? { endedAt: Date.now() } : {} }).where(
|
|
6400
|
+
ctx.db.update(sessions).set({ status: "ended", ...existing.endedAt === undefined ? { endedAt: Date.now() } : {} }).where(eq8(sessions.id, id)).run();
|
|
5904
6401
|
return getSessionRow(ctx, id);
|
|
5905
6402
|
}
|
|
5906
6403
|
function mostRecentResumableSession(ctx, featureId, kind) {
|
|
5907
|
-
const row = ctx.db.select().from(sessions).where(
|
|
6404
|
+
const row = ctx.db.select().from(sessions).where(and8(eq8(sessions.featureId, featureId), eq8(sessions.status, "ended"), isNotNull2(sessions.ccSessionId), ...kind ? [eq8(sessions.kind, kind)] : [])).orderBy(desc3(sql2`rowid`)).limit(1).get();
|
|
5908
6405
|
return row ? rowToSession(row) : null;
|
|
5909
6406
|
}
|
|
5910
6407
|
function activeProjectSession(ctx, projectId, kind) {
|
|
5911
|
-
const row = ctx.db.select().from(sessions).where(
|
|
6408
|
+
const row = ctx.db.select().from(sessions).where(and8(eq8(sessions.projectId, projectId), eq8(sessions.kind, kind), inArray2(sessions.status, ["launching", "live"]))).orderBy(desc3(sql2`rowid`)).limit(1).get();
|
|
5912
6409
|
return row ? rowToSession(row) : null;
|
|
5913
6410
|
}
|
|
5914
6411
|
function projectSessions(ctx, projectId, kind) {
|
|
5915
|
-
return ctx.db.select().from(sessions).where(
|
|
6412
|
+
return ctx.db.select().from(sessions).where(and8(eq8(sessions.projectId, projectId), eq8(sessions.kind, kind))).orderBy(desc3(sql2`rowid`)).all().map(rowToSession);
|
|
5916
6413
|
}
|
|
5917
6414
|
function setSessionTitle(ctx, id, title) {
|
|
5918
|
-
ctx.db.update(sessions).set({ title }).where(
|
|
6415
|
+
ctx.db.update(sessions).set({ title }).where(eq8(sessions.id, id)).run();
|
|
5919
6416
|
}
|
|
5920
6417
|
function clearSessionTitle(ctx, id) {
|
|
5921
|
-
ctx.db.update(sessions).set({ title: null }).where(
|
|
6418
|
+
ctx.db.update(sessions).set({ title: null }).where(eq8(sessions.id, id)).run();
|
|
5922
6419
|
}
|
|
5923
6420
|
function hasCompletedProjectSession(ctx, projectId, kind) {
|
|
5924
|
-
return ctx.db.select({ id: sessions.id }).from(sessions).where(
|
|
6421
|
+
return ctx.db.select({ id: sessions.id }).from(sessions).where(and8(eq8(sessions.projectId, projectId), eq8(sessions.kind, kind), eq8(sessions.status, "ended"))).limit(1).get() !== undefined;
|
|
5925
6422
|
}
|
|
5926
6423
|
function mostRecentResumableProjectSession(ctx, projectId, kind) {
|
|
5927
|
-
const row = ctx.db.select().from(sessions).where(
|
|
6424
|
+
const row = ctx.db.select().from(sessions).where(and8(eq8(sessions.projectId, projectId), eq8(sessions.kind, kind), eq8(sessions.status, "ended"), isNotNull2(sessions.ccSessionId))).orderBy(desc3(sql2`rowid`)).limit(1).get();
|
|
5928
6425
|
return row ? rowToSession(row) : null;
|
|
5929
6426
|
}
|
|
5930
6427
|
function envInt(name, fallback) {
|
|
@@ -5964,17 +6461,17 @@ function transcriptBytes(session) {
|
|
|
5964
6461
|
}
|
|
5965
6462
|
function reentryCount(ctx, scope) {
|
|
5966
6463
|
const where = [
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
...scope.featureId ? [
|
|
5970
|
-
...scope.projectId ? [
|
|
5971
|
-
...scope.kind ? [
|
|
6464
|
+
eq8(sessions.status, "ended"),
|
|
6465
|
+
isNotNull2(sessions.ccSessionId),
|
|
6466
|
+
...scope.featureId ? [eq8(sessions.featureId, scope.featureId)] : [],
|
|
6467
|
+
...scope.projectId ? [eq8(sessions.projectId, scope.projectId)] : [],
|
|
6468
|
+
...scope.kind ? [eq8(sessions.kind, scope.kind)] : []
|
|
5972
6469
|
];
|
|
5973
|
-
const row = ctx.db.select({ n: sql2`count(*)` }).from(sessions).where(
|
|
6470
|
+
const row = ctx.db.select({ n: sql2`count(*)` }).from(sessions).where(and8(...where)).get();
|
|
5974
6471
|
return row?.n ?? 0;
|
|
5975
6472
|
}
|
|
5976
6473
|
function mostRecentLiveSession(ctx) {
|
|
5977
|
-
const row = ctx.db.select().from(sessions).where(
|
|
6474
|
+
const row = ctx.db.select().from(sessions).where(eq8(sessions.status, "live")).orderBy(desc3(sql2`rowid`)).limit(1).get();
|
|
5978
6475
|
return row ? rowToSession(row) : null;
|
|
5979
6476
|
}
|
|
5980
6477
|
var KICKOFF_DELAY_MS = 1500, KICKOFF_SUBMIT_DELAY_MS = 350, KICKOFF_CONFIRM_MS = 12000, KICKOFF_MAX_ATTEMPTS = 3, CLEAR_INPUT = "\x15", SESSION_READY_TIMEOUT_MS = 25000, PREPARE_CONFIRM_KICKOFF, RESUME_KICKOFF_PREFIX, pendingKickoffOverrides, deliveries, MATCH_PREFIX = 40, landedProjectSessions, inFlightLandings, LANDING_KEPT, RESUME_MAX_TRANSCRIPT_BYTES, RESUME_MAX_REENTRIES;
|
|
@@ -5985,6 +6482,7 @@ var init_sessions = __esm(() => {
|
|
|
5985
6482
|
init_docs_watch();
|
|
5986
6483
|
init_events();
|
|
5987
6484
|
init_git();
|
|
6485
|
+
init_carried_work();
|
|
5988
6486
|
init_waypoints();
|
|
5989
6487
|
init_repo();
|
|
5990
6488
|
init_runtimes();
|
|
@@ -6001,9 +6499,9 @@ var init_sessions = __esm(() => {
|
|
|
6001
6499
|
});
|
|
6002
6500
|
|
|
6003
6501
|
// src/launcher/reconcile.ts
|
|
6004
|
-
import { inArray as
|
|
6502
|
+
import { inArray as inArray3 } from "drizzle-orm";
|
|
6005
6503
|
function reconcileStaleSessions(ctx) {
|
|
6006
|
-
const stale = ctx.db.select().from(sessions).where(
|
|
6504
|
+
const stale = ctx.db.select().from(sessions).where(inArray3(sessions.status, ["launching", "live"])).all().map(rowToSession);
|
|
6007
6505
|
const reconciled = [];
|
|
6008
6506
|
for (const session of stale) {
|
|
6009
6507
|
const entry = ptyRegistry().get(session.id);
|
|
@@ -6051,428 +6549,9 @@ async function getRuntimeCtx() {
|
|
|
6051
6549
|
}
|
|
6052
6550
|
var current = null;
|
|
6053
6551
|
|
|
6054
|
-
// src/services/review-findings.ts
|
|
6055
|
-
import { and as and6, asc as asc3, eq as eq6, isNotNull as isNotNull2 } from "drizzle-orm";
|
|
6056
|
-
function rowToFinding(row) {
|
|
6057
|
-
return ReviewFinding.parse(row);
|
|
6058
|
-
}
|
|
6059
|
-
function getFinding(ctx, id) {
|
|
6060
|
-
const row = ctx.db.select().from(reviewFindings).where(eq6(reviewFindings.id, id)).get();
|
|
6061
|
-
if (!row)
|
|
6062
|
-
throw new NotFoundError(`review finding ${id} not found`);
|
|
6063
|
-
return rowToFinding(row);
|
|
6064
|
-
}
|
|
6065
|
-
function listByFeature3(ctx, featureId) {
|
|
6066
|
-
return ctx.db.select().from(reviewFindings).where(eq6(reviewFindings.featureId, featureId)).orderBy(asc3(reviewFindings.createdAt), asc3(reviewFindings.id)).all().map(rowToFinding);
|
|
6067
|
-
}
|
|
6068
|
-
function buildFixTicket(finding) {
|
|
6069
|
-
return {
|
|
6070
|
-
title: finding.title,
|
|
6071
|
-
goal: `Fix: ${finding.title}`,
|
|
6072
|
-
context: [
|
|
6073
|
-
`Location: ${finding.location}`,
|
|
6074
|
-
`Citation: ${finding.citation}`,
|
|
6075
|
-
`Detail: ${finding.detail}`,
|
|
6076
|
-
`Repro step: ${finding.reproStep}`
|
|
6077
|
-
].join(`
|
|
6078
|
-
|
|
6079
|
-
`),
|
|
6080
|
-
acceptanceCriteria: [
|
|
6081
|
-
`The repro step no longer reproduces / the cited criterion holds: ${finding.reproStep}`
|
|
6082
|
-
],
|
|
6083
|
-
seams: [],
|
|
6084
|
-
blockedBy: [],
|
|
6085
|
-
kind: "implementation",
|
|
6086
|
-
originFindingId: finding.id
|
|
6087
|
-
};
|
|
6088
|
-
}
|
|
6089
|
-
function reportFinding(ctx, args) {
|
|
6090
|
-
const { featureId, reviewTicket, input } = args;
|
|
6091
|
-
const feature = getFeatureRow(ctx, featureId);
|
|
6092
|
-
const id = newId("finding");
|
|
6093
|
-
const ticketCount = ctx.db.select({ id: reviewFindings.id }).from(reviewFindings).where(and6(eq6(reviewFindings.reviewTicketId, reviewTicket.id), isNotNull2(reviewFindings.fixTicketId))).all().length;
|
|
6094
|
-
const verification = reviewTicket.passKind === "verification";
|
|
6095
|
-
const overCap = input.kind === "defect" && !verification && ticketCount >= AUTO_FIX_CAP;
|
|
6096
|
-
ctx.db.insert(reviewFindings).values({
|
|
6097
|
-
id,
|
|
6098
|
-
featureId,
|
|
6099
|
-
lap: feature.lap,
|
|
6100
|
-
reviewTicketId: reviewTicket.id,
|
|
6101
|
-
...input,
|
|
6102
|
-
reproStep: input.reproStep ?? "",
|
|
6103
|
-
status: "open",
|
|
6104
|
-
openReason: verification && input.kind === "defect" ? "verification" : overCap ? "over-cap" : null,
|
|
6105
|
-
failureReason: null,
|
|
6106
|
-
fixTicketId: null,
|
|
6107
|
-
createdAt: Date.now()
|
|
6108
|
-
}).run();
|
|
6109
|
-
let fixTicket = null;
|
|
6110
|
-
if (input.kind === "defect" && !overCap && !verification) {
|
|
6111
|
-
const finding2 = getFinding(ctx, id);
|
|
6112
|
-
fixTicket = storeTickets(ctx, featureId, [
|
|
6113
|
-
{ ...buildFixTicket(finding2), blockedBy: [reviewTicket.seq] }
|
|
6114
|
-
], { blockedByAreGlobal: true })[0];
|
|
6115
|
-
ctx.db.update(reviewFindings).set({ fixTicketId: fixTicket.id }).where(eq6(reviewFindings.id, id)).run();
|
|
6116
|
-
}
|
|
6117
|
-
const finding = getFinding(ctx, id);
|
|
6118
|
-
emit(ctx, featureId, {
|
|
6119
|
-
type: "finding.reported",
|
|
6120
|
-
message: `${input.kind} reported: ${input.title}`,
|
|
6121
|
-
ticketId: fixTicket?.id,
|
|
6122
|
-
data: { findingId: id, fixTicketId: fixTicket?.id, overCap }
|
|
6123
|
-
});
|
|
6124
|
-
return { finding, fixTicket, overCap };
|
|
6125
|
-
}
|
|
6126
|
-
function updateStatus(ctx, findingId, patch) {
|
|
6127
|
-
const current2 = getFinding(ctx, findingId);
|
|
6128
|
-
ctx.db.update(reviewFindings).set(patch).where(eq6(reviewFindings.id, findingId)).run();
|
|
6129
|
-
emit(ctx, current2.featureId, {
|
|
6130
|
-
type: "finding.updated",
|
|
6131
|
-
message: `finding ${findingId} marked ${patch.status}`,
|
|
6132
|
-
data: { findingId, status: patch.status, reason: patch.failureReason }
|
|
6133
|
-
});
|
|
6134
|
-
return getFinding(ctx, findingId);
|
|
6135
|
-
}
|
|
6136
|
-
function markFixing(ctx, findingId) {
|
|
6137
|
-
return updateStatus(ctx, findingId, {
|
|
6138
|
-
status: "fixing",
|
|
6139
|
-
openReason: null,
|
|
6140
|
-
failureReason: null
|
|
6141
|
-
});
|
|
6142
|
-
}
|
|
6143
|
-
function markFixed(ctx, findingId) {
|
|
6144
|
-
return updateStatus(ctx, findingId, {
|
|
6145
|
-
status: "fixed",
|
|
6146
|
-
openReason: null,
|
|
6147
|
-
failureReason: null
|
|
6148
|
-
});
|
|
6149
|
-
}
|
|
6150
|
-
function markFailed(ctx, findingId, reason) {
|
|
6151
|
-
return updateStatus(ctx, findingId, {
|
|
6152
|
-
status: "failed",
|
|
6153
|
-
openReason: "fix-failed",
|
|
6154
|
-
failureReason: reason ?? null
|
|
6155
|
-
});
|
|
6156
|
-
}
|
|
6157
|
-
function markFixProgress(ctx, findingId, progress, reason) {
|
|
6158
|
-
if (progress === "fixing")
|
|
6159
|
-
return markFixing(ctx, findingId);
|
|
6160
|
-
if (progress === "fixed")
|
|
6161
|
-
return markFixed(ctx, findingId);
|
|
6162
|
-
return markFailed(ctx, findingId, reason);
|
|
6163
|
-
}
|
|
6164
|
-
function dismiss(ctx, findingId) {
|
|
6165
|
-
return updateStatus(ctx, findingId, {
|
|
6166
|
-
status: "dismissed",
|
|
6167
|
-
openReason: null,
|
|
6168
|
-
failureReason: null
|
|
6169
|
-
});
|
|
6170
|
-
}
|
|
6171
|
-
function defectState(finding, fixTicket) {
|
|
6172
|
-
if (finding.status === "dismissed")
|
|
6173
|
-
return "dismissed";
|
|
6174
|
-
if (finding.status === "fixed" || fixTicket?.status === "done")
|
|
6175
|
-
return "fixed";
|
|
6176
|
-
if (fixTicket && fixTicket.status !== "failed" && fixTicket.status !== "cancelled") {
|
|
6177
|
-
return "fixing";
|
|
6178
|
-
}
|
|
6179
|
-
return finding.status === "open" || finding.status === "failed" || fixTicket?.status === "failed" || fixTicket?.status === "cancelled" ? "open" : "fixing";
|
|
6180
|
-
}
|
|
6181
|
-
function viewByFeature(ctx, featureId) {
|
|
6182
|
-
const findings = listByFeature3(ctx, featureId);
|
|
6183
|
-
const tickets2 = listByFeature2(ctx, featureId);
|
|
6184
|
-
const summary = { found: 0, fixed: 0, open: 0, observations: 0 };
|
|
6185
|
-
const openDefects = [];
|
|
6186
|
-
for (const finding of findings) {
|
|
6187
|
-
if (finding.kind === "observation") {
|
|
6188
|
-
summary.observations += 1;
|
|
6189
|
-
continue;
|
|
6190
|
-
}
|
|
6191
|
-
summary.found += 1;
|
|
6192
|
-
const state = defectState(finding, fixTicketOf(finding, tickets2));
|
|
6193
|
-
if (state === "fixed")
|
|
6194
|
-
summary.fixed += 1;
|
|
6195
|
-
else if (state === "open") {
|
|
6196
|
-
summary.open += 1;
|
|
6197
|
-
openDefects.push(finding);
|
|
6198
|
-
}
|
|
6199
|
-
}
|
|
6200
|
-
return { findings, openDefects, summary };
|
|
6201
|
-
}
|
|
6202
|
-
function fixTicketOf(finding, tickets2) {
|
|
6203
|
-
return finding.fixTicketId ? tickets2.find((t) => t.id === finding.fixTicketId) : undefined;
|
|
6204
|
-
}
|
|
6205
|
-
function promoteOpenDefects(ctx, featureId, findingIds) {
|
|
6206
|
-
const open = viewByFeature(ctx, featureId).openDefects;
|
|
6207
|
-
const defects = findingIds ? findingIds.map((id) => {
|
|
6208
|
-
const finding = open.find((candidate) => candidate.id === id);
|
|
6209
|
-
if (!finding)
|
|
6210
|
-
throw new InvalidInputError(`finding ${id} is not an open defect of this feature`);
|
|
6211
|
-
return finding;
|
|
6212
|
-
}) : open;
|
|
6213
|
-
if (findingIds && new Set(findingIds).size !== findingIds.length)
|
|
6214
|
-
throw new InvalidInputError("duplicate finding selected");
|
|
6215
|
-
if (defects.length === 0)
|
|
6216
|
-
throw new InvalidInputError("no open defects to fix");
|
|
6217
|
-
const tickets2 = storeTickets(ctx, featureId, defects.map(buildFixTicket));
|
|
6218
|
-
const findings = defects.map((defect, index2) => {
|
|
6219
|
-
ctx.db.update(reviewFindings).set({
|
|
6220
|
-
status: "fixing",
|
|
6221
|
-
openReason: null,
|
|
6222
|
-
failureReason: null,
|
|
6223
|
-
fixTicketId: tickets2[index2].id
|
|
6224
|
-
}).where(eq6(reviewFindings.id, defect.id)).run();
|
|
6225
|
-
return getFinding(ctx, defect.id);
|
|
6226
|
-
});
|
|
6227
|
-
emit(ctx, featureId, {
|
|
6228
|
-
type: "finding.fixing",
|
|
6229
|
-
message: `${findings.length} open defect${findings.length === 1 ? "" : "s"} promoted to fix ticket${tickets2.length === 1 ? ` ${tickets2[0].seq}` : `s ${tickets2.map((t) => t.seq).join(", ")}`}`,
|
|
6230
|
-
data: { findingIds: findings.map((f) => f.id), seqs: tickets2.map((t) => t.seq) }
|
|
6231
|
-
});
|
|
6232
|
-
return { findings, tickets: tickets2 };
|
|
6233
|
-
}
|
|
6234
|
-
var AUTO_FIX_CAP = 8;
|
|
6235
|
-
var init_review_findings = __esm(() => {
|
|
6236
|
-
init_src();
|
|
6237
|
-
init_schema();
|
|
6238
|
-
init_errors();
|
|
6239
|
-
init_events();
|
|
6240
|
-
init_repo();
|
|
6241
|
-
init_tickets();
|
|
6242
|
-
});
|
|
6243
|
-
|
|
6244
|
-
// src/services/tickets.ts
|
|
6245
|
-
import { and as and7, asc as asc4, eq as eq7, inArray as inArray3 } from "drizzle-orm";
|
|
6246
|
-
function rowToTicket(row) {
|
|
6247
|
-
return Ticket.parse({
|
|
6248
|
-
id: row.id,
|
|
6249
|
-
featureId: row.featureId,
|
|
6250
|
-
seq: row.seq,
|
|
6251
|
-
title: row.title,
|
|
6252
|
-
goal: row.goal,
|
|
6253
|
-
context: row.context,
|
|
6254
|
-
acceptanceCriteria: row.acceptanceCriteria,
|
|
6255
|
-
seams: row.seams,
|
|
6256
|
-
blockedBy: row.blockedBy,
|
|
6257
|
-
kind: row.kind,
|
|
6258
|
-
passKind: row.passKind,
|
|
6259
|
-
reviewedCommit: row.reviewedCommit,
|
|
6260
|
-
completedAt: row.completedAt,
|
|
6261
|
-
model: row.model ?? undefined,
|
|
6262
|
-
lap: row.lap,
|
|
6263
|
-
status: row.status,
|
|
6264
|
-
commits: row.commits,
|
|
6265
|
-
error: row.error ?? undefined,
|
|
6266
|
-
attemptBranch: row.attemptBranch ?? undefined,
|
|
6267
|
-
conflictFiles: row.conflictFiles ?? undefined,
|
|
6268
|
-
digest: row.digest ?? undefined,
|
|
6269
|
-
originFindingId: row.originFindingId ?? undefined
|
|
6270
|
-
});
|
|
6271
|
-
}
|
|
6272
|
-
function resolveBlocking(inputs, startSeq) {
|
|
6273
|
-
try {
|
|
6274
|
-
return resolveBatchBlocking(inputs, { startSeq });
|
|
6275
|
-
} catch (e) {
|
|
6276
|
-
if (e instanceof BlockingEdgeError)
|
|
6277
|
-
throw new InvalidInputError(e.message);
|
|
6278
|
-
throw e;
|
|
6279
|
-
}
|
|
6280
|
-
}
|
|
6281
|
-
function normalizeModel(ctx, value) {
|
|
6282
|
-
const id = value?.trim();
|
|
6283
|
-
if (!id)
|
|
6284
|
-
return null;
|
|
6285
|
-
const roster = modelRoster(ctx.config);
|
|
6286
|
-
if (!roster.some((m) => m.id === id)) {
|
|
6287
|
-
throw new InvalidInputError(`unknown model "${id}" \u2014 assign one of the configured models: ${roster.map((m) => m.id).join(", ")}`);
|
|
6288
|
-
}
|
|
6289
|
-
return id;
|
|
6290
|
-
}
|
|
6291
|
-
function listByFeature2(ctx, featureId) {
|
|
6292
|
-
return ctx.db.select().from(tickets).where(eq7(tickets.featureId, featureId)).orderBy(asc4(tickets.seq)).all().map(rowToTicket);
|
|
6293
|
-
}
|
|
6294
|
-
function storeTickets(ctx, featureId, inputs, options = {}) {
|
|
6295
|
-
if (inputs.length === 0)
|
|
6296
|
-
return [];
|
|
6297
|
-
const { lap } = getFeatureRow(ctx, featureId);
|
|
6298
|
-
const existing = ctx.db.select({ seq: tickets.seq }).from(tickets).where(eq7(tickets.featureId, featureId)).all();
|
|
6299
|
-
const startSeq = existing.reduce((max2, r) => Math.max(max2, r.seq), 0) + 1;
|
|
6300
|
-
const resolved = options.blockedByAreGlobal ? inputs.map((input, index2) => ({ seq: startSeq + index2, blockedBy: input.blockedBy })) : resolveBlocking(inputs, startSeq);
|
|
6301
|
-
const rows = inputs.map((t, i) => ({
|
|
6302
|
-
id: newId("tkt"),
|
|
6303
|
-
featureId,
|
|
6304
|
-
seq: resolved[i].seq,
|
|
6305
|
-
title: t.title,
|
|
6306
|
-
goal: t.goal,
|
|
6307
|
-
context: t.context,
|
|
6308
|
-
acceptanceCriteria: t.acceptanceCriteria,
|
|
6309
|
-
seams: t.seams,
|
|
6310
|
-
blockedBy: resolved[i].blockedBy,
|
|
6311
|
-
kind: t.kind ?? "implementation",
|
|
6312
|
-
passKind: t.passKind ?? "review",
|
|
6313
|
-
reviewedCommit: null,
|
|
6314
|
-
completedAt: null,
|
|
6315
|
-
model: normalizeModel(ctx, t.model),
|
|
6316
|
-
lap,
|
|
6317
|
-
status: "pending",
|
|
6318
|
-
commits: [],
|
|
6319
|
-
error: null,
|
|
6320
|
-
attemptBranch: null,
|
|
6321
|
-
conflictFiles: null,
|
|
6322
|
-
digest: null,
|
|
6323
|
-
originFindingId: t.originFindingId ?? null
|
|
6324
|
-
}));
|
|
6325
|
-
ctx.db.insert(tickets).values(rows).run();
|
|
6326
|
-
emit(ctx, featureId, {
|
|
6327
|
-
type: "tickets.stored",
|
|
6328
|
-
message: `${rows.length} ticket(s) stored`,
|
|
6329
|
-
data: { count: rows.length, seqs: rows.map((r) => r.seq) }
|
|
6330
|
-
});
|
|
6331
|
-
return rows.map(rowToTicket);
|
|
6332
|
-
}
|
|
6333
|
-
function getTicket(ctx, id) {
|
|
6334
|
-
const row = ctx.db.select().from(tickets).where(eq7(tickets.id, id)).get();
|
|
6335
|
-
if (!row)
|
|
6336
|
-
throw new NotFoundError(`ticket ${id} not found`);
|
|
6337
|
-
return rowToTicket(row);
|
|
6338
|
-
}
|
|
6339
|
-
function assertMutable(t, verb) {
|
|
6340
|
-
if (!MUTABLE_STATUSES.has(t.status)) {
|
|
6341
|
-
throw new InvalidInputError(`cannot ${verb} ticket ${t.seq} \u2014 it is ${t.status}; only pending or failed tickets can be ${verb}${verb.endsWith("l") ? "led" : "ed"}`);
|
|
6342
|
-
}
|
|
6343
|
-
}
|
|
6344
|
-
function editTicket(ctx, id, patch) {
|
|
6345
|
-
const current2 = getTicket(ctx, id);
|
|
6346
|
-
assertMutable(current2, "edit");
|
|
6347
|
-
const set = {};
|
|
6348
|
-
if (patch.title !== undefined)
|
|
6349
|
-
set.title = patch.title;
|
|
6350
|
-
if (patch.goal !== undefined)
|
|
6351
|
-
set.goal = patch.goal;
|
|
6352
|
-
if (patch.context !== undefined)
|
|
6353
|
-
set.context = patch.context;
|
|
6354
|
-
if (patch.acceptanceCriteria !== undefined)
|
|
6355
|
-
set.acceptanceCriteria = patch.acceptanceCriteria;
|
|
6356
|
-
if (patch.seams !== undefined)
|
|
6357
|
-
set.seams = patch.seams;
|
|
6358
|
-
if (patch.model !== undefined)
|
|
6359
|
-
set.model = normalizeModel(ctx, patch.model);
|
|
6360
|
-
if (Object.keys(set).length === 0) {
|
|
6361
|
-
throw new InvalidInputError("nothing to edit \u2014 pass at least one field");
|
|
6362
|
-
}
|
|
6363
|
-
ctx.db.update(tickets).set(set).where(eq7(tickets.id, id)).run();
|
|
6364
|
-
emit(ctx, current2.featureId, {
|
|
6365
|
-
type: "ticket.edited",
|
|
6366
|
-
message: `ticket ${current2.seq} content edited (${Object.keys(set).join(", ")})`,
|
|
6367
|
-
ticketId: id,
|
|
6368
|
-
data: { fields: Object.keys(set) }
|
|
6369
|
-
});
|
|
6370
|
-
return getTicket(ctx, id);
|
|
6371
|
-
}
|
|
6372
|
-
function cancelTicket(ctx, id, reason) {
|
|
6373
|
-
const current2 = getTicket(ctx, id);
|
|
6374
|
-
assertMutable(current2, "cancel");
|
|
6375
|
-
ctx.db.update(tickets).set({
|
|
6376
|
-
status: "cancelled",
|
|
6377
|
-
error: reason?.trim() ? reason.trim() : null,
|
|
6378
|
-
completedAt: Date.now()
|
|
6379
|
-
}).where(eq7(tickets.id, id)).run();
|
|
6380
|
-
emit(ctx, current2.featureId, {
|
|
6381
|
-
type: "ticket.cancelled",
|
|
6382
|
-
message: `ticket ${current2.seq} cancelled${reason?.trim() ? `: ${reason.trim()}` : ""}`,
|
|
6383
|
-
ticketId: id,
|
|
6384
|
-
data: { reason: reason ?? null }
|
|
6385
|
-
});
|
|
6386
|
-
return getTicket(ctx, id);
|
|
6387
|
-
}
|
|
6388
|
-
function sweepOrphanedBurning(ctx, featureId, reason) {
|
|
6389
|
-
const orphaned = ctx.db.select().from(tickets).where(and7(eq7(tickets.featureId, featureId), eq7(tickets.status, "burning"))).all().map(rowToTicket);
|
|
6390
|
-
const swept = [];
|
|
6391
|
-
for (const t of orphaned) {
|
|
6392
|
-
swept.push(updateTicket(ctx, t.id, { status: "failed", error: reason }));
|
|
6393
|
-
if (t.originFindingId)
|
|
6394
|
-
markFailed(ctx, t.originFindingId, reason);
|
|
6395
|
-
emit(ctx, featureId, {
|
|
6396
|
-
type: "ticket.failed",
|
|
6397
|
-
message: `ticket ${t.seq} failed: ${reason}`,
|
|
6398
|
-
ticketId: t.id,
|
|
6399
|
-
data: { error: reason, orphaned: true }
|
|
6400
|
-
});
|
|
6401
|
-
}
|
|
6402
|
-
return swept;
|
|
6403
|
-
}
|
|
6404
|
-
function updateTicket(ctx, id, patch) {
|
|
6405
|
-
const current2 = ctx.db.select().from(tickets).where(eq7(tickets.id, id)).get();
|
|
6406
|
-
if (!current2)
|
|
6407
|
-
throw new NotFoundError(`ticket ${id} not found`);
|
|
6408
|
-
const set = {};
|
|
6409
|
-
if (patch.status !== undefined)
|
|
6410
|
-
set.status = patch.status;
|
|
6411
|
-
if (patch.status !== undefined) {
|
|
6412
|
-
set.completedAt = ["done", "failed", "cancelled"].includes(patch.status) ? Date.now() : null;
|
|
6413
|
-
}
|
|
6414
|
-
if (patch.commits !== undefined)
|
|
6415
|
-
set.commits = patch.commits;
|
|
6416
|
-
if (patch.error !== undefined)
|
|
6417
|
-
set.error = patch.error;
|
|
6418
|
-
if (patch.attemptBranch !== undefined)
|
|
6419
|
-
set.attemptBranch = patch.attemptBranch;
|
|
6420
|
-
if (patch.conflictFiles !== undefined)
|
|
6421
|
-
set.conflictFiles = patch.conflictFiles;
|
|
6422
|
-
if (patch.digest !== undefined)
|
|
6423
|
-
set.digest = patch.digest;
|
|
6424
|
-
if (patch.reviewedCommit !== undefined)
|
|
6425
|
-
set.reviewedCommit = patch.reviewedCommit;
|
|
6426
|
-
ctx.db.update(tickets).set(set).where(eq7(tickets.id, id)).run();
|
|
6427
|
-
emit(ctx, current2.featureId, {
|
|
6428
|
-
type: "ticket.updated",
|
|
6429
|
-
message: `ticket ${current2.seq} \u2192 ${patch.status ?? current2.status}`,
|
|
6430
|
-
ticketId: id,
|
|
6431
|
-
data: patch
|
|
6432
|
-
});
|
|
6433
|
-
const updated = ctx.db.select().from(tickets).where(eq7(tickets.id, id)).get();
|
|
6434
|
-
return rowToTicket(updated);
|
|
6435
|
-
}
|
|
6436
|
-
function listByIds(ctx, ids2) {
|
|
6437
|
-
if (ids2.length === 0)
|
|
6438
|
-
return [];
|
|
6439
|
-
return ctx.db.select().from(tickets).where(inArray3(tickets.id, [...ids2])).orderBy(asc4(tickets.seq)).all().map(rowToTicket);
|
|
6440
|
-
}
|
|
6441
|
-
function timingWallMs(data) {
|
|
6442
|
-
if (typeof data !== "object" || data === null)
|
|
6443
|
-
return;
|
|
6444
|
-
const ms = data.wallMs;
|
|
6445
|
-
return typeof ms === "number" && Number.isFinite(ms) && ms > 0 ? ms : undefined;
|
|
6446
|
-
}
|
|
6447
|
-
function ticketDurationStats(ctx, projectId) {
|
|
6448
|
-
const rows = ctx.db.select({ ticketId: events.ticketId, data: events.data }).from(events).innerJoin(tickets, eq7(events.ticketId, tickets.id)).where(and7(eq7(events.projectId, projectId), eq7(events.type, "ticket.timing"), eq7(tickets.status, "done"), eq7(tickets.kind, "implementation"))).orderBy(asc4(events.id)).all();
|
|
6449
|
-
const perTicket = new Map;
|
|
6450
|
-
for (const row of rows) {
|
|
6451
|
-
const ms = timingWallMs(row.data);
|
|
6452
|
-
if (row.ticketId && ms !== undefined)
|
|
6453
|
-
perTicket.set(row.ticketId, ms);
|
|
6454
|
-
}
|
|
6455
|
-
const samples = [...perTicket.values()].sort((a, b) => a - b);
|
|
6456
|
-
if (samples.length === 0)
|
|
6457
|
-
return { medianMs: 0, sampleSize: 0 };
|
|
6458
|
-
const mid = samples.length >> 1;
|
|
6459
|
-
const medianMs = samples.length % 2 === 1 ? samples[mid] : Math.round((samples[mid - 1] + samples[mid]) / 2);
|
|
6460
|
-
return { medianMs, sampleSize: samples.length };
|
|
6461
|
-
}
|
|
6462
|
-
var MUTABLE_STATUSES;
|
|
6463
|
-
var init_tickets = __esm(() => {
|
|
6464
|
-
init_src();
|
|
6465
|
-
init_schema();
|
|
6466
|
-
init_errors();
|
|
6467
|
-
init_events();
|
|
6468
|
-
init_repo();
|
|
6469
|
-
init_review_findings();
|
|
6470
|
-
MUTABLE_STATUSES = new Set(["pending", "failed"]);
|
|
6471
|
-
});
|
|
6472
|
-
|
|
6473
6552
|
// src/services/gates.ts
|
|
6474
6553
|
import { existsSync as existsSync11 } from "fs";
|
|
6475
|
-
import { and as
|
|
6554
|
+
import { and as and9, desc as desc4, eq as eq9, inArray as inArray4 } from "drizzle-orm";
|
|
6476
6555
|
function checkGate(ctx, check, feature) {
|
|
6477
6556
|
switch (check) {
|
|
6478
6557
|
case "decisions-file-exists":
|
|
@@ -6498,8 +6577,16 @@ function checkGate(ctx, check, feature) {
|
|
|
6498
6577
|
case "spec-file-exists":
|
|
6499
6578
|
return docGate(ctx, feature, "spec.md", "write the spec (spec.md) before breaking into tickets");
|
|
6500
6579
|
case "tickets-approved": {
|
|
6501
|
-
const
|
|
6502
|
-
|
|
6580
|
+
const burnable = listByFeature2(ctx, feature.id).filter((t) => t.lap === feature.lap && t.status !== "cancelled");
|
|
6581
|
+
if (burnable.length === 0)
|
|
6582
|
+
return { satisfied: false, reason: "no tickets to burn" };
|
|
6583
|
+
if (!burnable.some((t) => t.kind === "review")) {
|
|
6584
|
+
return {
|
|
6585
|
+
satisfied: false,
|
|
6586
|
+
reason: 'no review ticket on this lap \u2014 every batch closes with one (kind: "review"); emit it, ' + "fix the kind on a review-shaped ticket, or override this gate with a reason"
|
|
6587
|
+
};
|
|
6588
|
+
}
|
|
6589
|
+
return { satisfied: true };
|
|
6503
6590
|
}
|
|
6504
6591
|
case "all-tickets-terminal": {
|
|
6505
6592
|
const tickets2 = listByFeature2(ctx, feature.id);
|
|
@@ -6531,7 +6618,7 @@ function fileGate(ctx, feature, fileName, reason) {
|
|
|
6531
6618
|
return existsSync11(featureDocPath(project, feature, fileName)) ? { satisfied: true } : { satisfied: false, reason };
|
|
6532
6619
|
}
|
|
6533
6620
|
function lapWorked(ctx, feature) {
|
|
6534
|
-
return ctx.db.select({ id: sessions.id }).from(sessions).where(
|
|
6621
|
+
return ctx.db.select({ id: sessions.id }).from(sessions).where(and9(eq9(sessions.featureId, feature.id), eq9(sessions.lap, feature.lap), inArray4(sessions.kind, [...DOC_WRITING_KINDS]))).all().length > 0;
|
|
6535
6622
|
}
|
|
6536
6623
|
function docGate(ctx, feature, fileName, reason) {
|
|
6537
6624
|
const file = fileGate(ctx, feature, fileName, reason);
|
|
@@ -6563,10 +6650,10 @@ function overrideGate(ctx, featureId, gate, reason) {
|
|
|
6563
6650
|
return feature;
|
|
6564
6651
|
}
|
|
6565
6652
|
function undoLastGateOverride(ctx, featureId, gate) {
|
|
6566
|
-
const last = ctx.db.select({ id: gateOverrides.id }).from(gateOverrides).where(
|
|
6653
|
+
const last = ctx.db.select({ id: gateOverrides.id }).from(gateOverrides).where(and9(eq9(gateOverrides.featureId, featureId), eq9(gateOverrides.gate, gate))).orderBy(desc4(gateOverrides.id)).get();
|
|
6567
6654
|
if (!last)
|
|
6568
6655
|
return false;
|
|
6569
|
-
ctx.db.delete(gateOverrides).where(
|
|
6656
|
+
ctx.db.delete(gateOverrides).where(eq9(gateOverrides.id, last.id)).run();
|
|
6570
6657
|
return true;
|
|
6571
6658
|
}
|
|
6572
6659
|
function undoGateOverride(ctx, featureId, gate) {
|
|
@@ -6760,6 +6847,133 @@ var init_end_session = __esm(() => {
|
|
|
6760
6847
|
init_registry();
|
|
6761
6848
|
});
|
|
6762
6849
|
|
|
6850
|
+
// src/workflows/kill-registry.ts
|
|
6851
|
+
import { spawn as spawn4 } from "child_process";
|
|
6852
|
+
function log2(msg) {
|
|
6853
|
+
console.error(`[kill-registry] ${msg}`);
|
|
6854
|
+
}
|
|
6855
|
+
function runDockerCommand(args) {
|
|
6856
|
+
return new Promise((resolve9) => {
|
|
6857
|
+
let settled = false;
|
|
6858
|
+
const settle = (ok) => {
|
|
6859
|
+
if (settled)
|
|
6860
|
+
return;
|
|
6861
|
+
settled = true;
|
|
6862
|
+
clearTimeout(timer);
|
|
6863
|
+
resolve9(ok);
|
|
6864
|
+
};
|
|
6865
|
+
const timer = setTimeout(() => settle(false), DOCKER_COMMAND_TIMEOUT_MS);
|
|
6866
|
+
timer.unref?.();
|
|
6867
|
+
try {
|
|
6868
|
+
const child = spawn4("docker", args, { windowsHide: true, stdio: "ignore" });
|
|
6869
|
+
child.on("exit", (code) => settle(code === 0));
|
|
6870
|
+
child.on("close", (code) => settle(code === 0));
|
|
6871
|
+
child.on("error", () => settle(false));
|
|
6872
|
+
} catch {
|
|
6873
|
+
settle(false);
|
|
6874
|
+
}
|
|
6875
|
+
});
|
|
6876
|
+
}
|
|
6877
|
+
function withDeadline2(body, ms) {
|
|
6878
|
+
return new Promise((resolve9) => {
|
|
6879
|
+
const timer = setTimeout(() => resolve9(false), ms);
|
|
6880
|
+
body.catch(() => false).then((confirmed) => {
|
|
6881
|
+
clearTimeout(timer);
|
|
6882
|
+
resolve9(confirmed);
|
|
6883
|
+
});
|
|
6884
|
+
});
|
|
6885
|
+
}
|
|
6886
|
+
function forget() {}
|
|
6887
|
+
function delay(ms) {
|
|
6888
|
+
return new Promise((resolve9) => {
|
|
6889
|
+
const timer = setTimeout(resolve9, ms);
|
|
6890
|
+
timer.unref?.();
|
|
6891
|
+
});
|
|
6892
|
+
}
|
|
6893
|
+
|
|
6894
|
+
class KillRegistry {
|
|
6895
|
+
deps;
|
|
6896
|
+
handles = new Map;
|
|
6897
|
+
killsInFlight = new Map;
|
|
6898
|
+
constructor(deps = REAL_DEPS) {
|
|
6899
|
+
this.deps = deps;
|
|
6900
|
+
}
|
|
6901
|
+
registerContainer(laneKey, containerName, owner = {}) {
|
|
6902
|
+
this.handles.set(laneKey, { kind: "container", containerName, ...owner });
|
|
6903
|
+
}
|
|
6904
|
+
registerHostPid(laneKey, pid, owner = {}) {
|
|
6905
|
+
this.handles.set(laneKey, { kind: "host", pid, ...owner });
|
|
6906
|
+
}
|
|
6907
|
+
release(laneKey) {
|
|
6908
|
+
this.handles.delete(laneKey);
|
|
6909
|
+
}
|
|
6910
|
+
killAndWait(laneKey, opts) {
|
|
6911
|
+
const handle = this.handles.get(laneKey);
|
|
6912
|
+
if (!handle)
|
|
6913
|
+
return Promise.resolve({ confirmed: true });
|
|
6914
|
+
const kill = this.killLane(laneKey, handle, opts);
|
|
6915
|
+
const entry = { runId: handle.runId, settled: kill.then(forget, forget) };
|
|
6916
|
+
this.killsInFlight.set(laneKey, entry);
|
|
6917
|
+
entry.settled.then(() => {
|
|
6918
|
+
if (this.killsInFlight.get(laneKey) === entry)
|
|
6919
|
+
this.killsInFlight.delete(laneKey);
|
|
6920
|
+
});
|
|
6921
|
+
return kill;
|
|
6922
|
+
}
|
|
6923
|
+
async whenKillSettled(laneKey) {
|
|
6924
|
+
await this.killsInFlight.get(laneKey)?.settled;
|
|
6925
|
+
}
|
|
6926
|
+
async whenRunKillsSettled(runId) {
|
|
6927
|
+
await Promise.all([...this.killsInFlight.values()].filter((kill) => kill.runId === runId).map((kill) => kill.settled));
|
|
6928
|
+
}
|
|
6929
|
+
async killLane(laneKey, handle, opts) {
|
|
6930
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
6931
|
+
const started = Date.now();
|
|
6932
|
+
const confirmed = await withDeadline2(this.kill(handle, started + timeoutMs), timeoutMs);
|
|
6933
|
+
if (confirmed)
|
|
6934
|
+
this.handles.delete(laneKey);
|
|
6935
|
+
const target = handle.kind === "container" ? handle.containerName : `pid=${handle.pid}`;
|
|
6936
|
+
log2(`killAndWait: lane=${laneKey} ${handle.kind}=${target} ${confirmed ? "confirmed dead" : `NOT confirmed dead (${timeoutMs}ms deadline)`} after ${Date.now() - started}ms`);
|
|
6937
|
+
return { confirmed };
|
|
6938
|
+
}
|
|
6939
|
+
async killAllForRun(runId, opts) {
|
|
6940
|
+
const lanes = [...this.handles].filter(([, handle]) => handle.runId === runId).map(([laneKey]) => laneKey);
|
|
6941
|
+
const outcomes = await Promise.all(lanes.map((laneKey) => this.killAndWait(laneKey, opts)));
|
|
6942
|
+
return { confirmed: outcomes.every((outcome) => outcome.confirmed) };
|
|
6943
|
+
}
|
|
6944
|
+
keys() {
|
|
6945
|
+
return [...this.handles.keys()];
|
|
6946
|
+
}
|
|
6947
|
+
async kill(handle, deadline) {
|
|
6948
|
+
if (handle.kind === "host") {
|
|
6949
|
+
await this.deps.killTree(handle.pid);
|
|
6950
|
+
return true;
|
|
6951
|
+
}
|
|
6952
|
+
await this.deps.runDocker(["rm", "-f", handle.containerName]);
|
|
6953
|
+
do {
|
|
6954
|
+
if (!await this.deps.runDocker(["inspect", handle.containerName]))
|
|
6955
|
+
return true;
|
|
6956
|
+
await delay(DEATH_POLL_INTERVAL_MS);
|
|
6957
|
+
} while (Date.now() < deadline);
|
|
6958
|
+
return false;
|
|
6959
|
+
}
|
|
6960
|
+
}
|
|
6961
|
+
function killRegistry() {
|
|
6962
|
+
const g = globalThis;
|
|
6963
|
+
if (!g[REGISTRY_KEY2])
|
|
6964
|
+
g[REGISTRY_KEY2] = new KillRegistry;
|
|
6965
|
+
return g[REGISTRY_KEY2];
|
|
6966
|
+
}
|
|
6967
|
+
function registerHostChildren(laneKey, owner) {
|
|
6968
|
+
return (pid) => killRegistry().registerHostPid(laneKey, pid, owner);
|
|
6969
|
+
}
|
|
6970
|
+
var DEFAULT_TIMEOUT_MS = 1e4, DEATH_POLL_INTERVAL_MS = 100, DOCKER_COMMAND_TIMEOUT_MS = 5000, REAL_DEPS, REGISTRY_KEY2;
|
|
6971
|
+
var init_kill_registry = __esm(() => {
|
|
6972
|
+
init_kill_tree();
|
|
6973
|
+
REAL_DEPS = { runDocker: runDockerCommand, killTree: killProcessTree };
|
|
6974
|
+
REGISTRY_KEY2 = Symbol.for("runcastle.kill.registry");
|
|
6975
|
+
});
|
|
6976
|
+
|
|
6763
6977
|
// ../../node_modules/.bun/@ai-hero+sandcastle@0.12.0/node_modules/@ai-hero/sandcastle/dist/chunk-NGBM7T3E.js
|
|
6764
6978
|
import { createRequire as createRequire3 } from "module";
|
|
6765
6979
|
var require$1, __create, __defProp2, __getOwnPropDesc, __getOwnPropNames, __getProtoOf, __hasOwnProp, __require2, __commonJS = (cb, mod) => function __require22() {
|
|
@@ -10285,7 +10499,7 @@ ${prefix}}`;
|
|
|
10285
10499
|
}, _catch, catchAllDefect, catchSomeCause, catchSomeDefect, catchTag, catchTags, cause = (self) => matchCause(self, {
|
|
10286
10500
|
onFailure: identity,
|
|
10287
10501
|
onSuccess: () => empty7
|
|
10288
|
-
}), clockWith3, clock,
|
|
10502
|
+
}), clockWith3, clock, delay2, descriptorWith = (f) => withFiberRuntime((state, status3) => f({
|
|
10289
10503
|
id: state.id(),
|
|
10290
10504
|
status: status3,
|
|
10291
10505
|
interruptors: interruptors(state.getFiberRef(currentInterruptedCause))
|
|
@@ -10356,7 +10570,7 @@ ${prefix}}`;
|
|
|
10356
10570
|
fiberState.log(message, cause3, levelOption);
|
|
10357
10571
|
return void_2;
|
|
10358
10572
|
});
|
|
10359
|
-
},
|
|
10573
|
+
}, log4, logTrace, logDebug, logInfo, logWarning, logError, logFatal, withLogSpan, logAnnotations, loop = (initial, options) => options.discard ? loopDiscard(initial, options.while, options.step, options.body) : map10(loopInternal(initial, options.while, options.step, options.body), fromIterable2), loopInternal = (initial, cont, inc, body) => suspend(() => cont(initial) ? flatMap7(body(initial), (a) => map10(loopInternal(inc(initial), cont, inc, body), prepend3(a))) : sync(() => empty11())), loopDiscard = (initial, cont, inc, body) => suspend(() => cont(initial) ? flatMap7(body(initial), () => loopDiscard(inc(initial), cont, inc, body)) : void_2), mapAccum2, mapErrorCause, memoize = (self) => pipe(deferredMake(), flatMap7((deferred) => pipe(diffFiberRefsAndRuntimeFlags(self), intoDeferred(deferred), once, map10((complete4) => zipRight2(complete4, pipe(deferredAwait(deferred), flatMap7(([patch9, a]) => as2(zip2(patchFiberRefs(patch9[0]), updateRuntimeFlags(patch9[1])), a)))))))), merge5 = (self) => matchEffect(self, {
|
|
10360
10574
|
onFailure: (e) => succeed(e),
|
|
10361
10575
|
onSuccess: succeed
|
|
10362
10576
|
}), negate = (self) => map10(self, (b) => !b), none6 = (self) => flatMap7(self, (option5) => {
|
|
@@ -11970,11 +12184,11 @@ ${prefix}}`;
|
|
|
11970
12184
|
return end5 - time2 <= 0 ? some2(computeCachedValue(self, timeToLive, time2)) : none2();
|
|
11971
12185
|
}
|
|
11972
12186
|
}
|
|
11973
|
-
})), flatMap7((option5) => isNone2(option5) ? dieMessage("BUG: Effect.cachedInvalidate - please report an issue at https://github.com/Effect-TS/effect/issues") : restore(deferredAwait(option5.value[1]))))), invalidateCache = (cache) => set4(cache, none2()), ensuringChild, ensuringChildren, forkAll, forkIn, forkScoped = (self) => scopeWith((scope5) => forkIn(self, scope5)), fromFiber = (fiber) => join22(fiber), fromFiberEffect = (fiber) => suspend(() => flatMap7(fiber, join22)), memoKeySymbol, Key, cachedFunction = (f,
|
|
12187
|
+
})), flatMap7((option5) => isNone2(option5) ? dieMessage("BUG: Effect.cachedInvalidate - please report an issue at https://github.com/Effect-TS/effect/issues") : restore(deferredAwait(option5.value[1]))))), invalidateCache = (cache) => set4(cache, none2()), ensuringChild, ensuringChildren, forkAll, forkIn, forkScoped = (self) => scopeWith((scope5) => forkIn(self, scope5)), fromFiber = (fiber) => join22(fiber), fromFiberEffect = (fiber) => suspend(() => flatMap7(fiber, join22)), memoKeySymbol, Key, cachedFunction = (f, eq10) => {
|
|
11974
12188
|
return pipe(sync(() => empty22()), flatMap7(makeSynchronized), map10((ref) => (a) => pipe(ref.modifyEffect((map27) => {
|
|
11975
|
-
const result = pipe(map27, get12(new Key(a,
|
|
12189
|
+
const result = pipe(map27, get12(new Key(a, eq10)));
|
|
11976
12190
|
if (isNone2(result)) {
|
|
11977
|
-
return pipe(deferredMake(), tap2((deferred) => pipe(diffFiberRefs(f(a)), intoDeferred(deferred), fork)), map10((deferred) => [deferred, pipe(map27, set6(new Key(a,
|
|
12191
|
+
return pipe(deferredMake(), tap2((deferred) => pipe(diffFiberRefs(f(a)), intoDeferred(deferred), fork)), map10((deferred) => [deferred, pipe(map27, set6(new Key(a, eq10), deferred))]));
|
|
11978
12192
|
}
|
|
11979
12193
|
return succeed([result.value, map27]);
|
|
11980
12194
|
}), flatMap7(deferredAwait), flatMap7(([patch9, b]) => pipe(patchFiberRefs(patch9), as2(b))))));
|
|
@@ -13238,7 +13452,7 @@ ${prefix}}`;
|
|
|
13238
13452
|
}
|
|
13239
13453
|
return void_2;
|
|
13240
13454
|
});
|
|
13241
|
-
}, withRequestCaching, withRequestCache, isRequest2, EffectTypeId3, isEffect2, cachedWithTTL, cachedInvalidateWithTTL2, cached3, cachedFunction2, once3, all7, allWith2, allSuccesses2, dropUntil2, dropWhile2, takeUntil2, takeWhile2, every6, exists4, filter8, filterMap5, findFirst6, forEach8, head4, mergeAll5, partition4, reduce12, reduceWhile2, reduceRight3, reduceEffect3, replicate2, replicateEffect2, validateAll2, validateFirst2, async2, asyncEffect2, custom2, withFiberRuntime2, fail10, failSync4, failCause9, failCauseSync4, die7, dieMessage2, dieSync4, gen3, never4, none9, promise2, succeed10, succeedNone2, succeedSome2, suspend4, sync6, _void, yieldNow4, _catch2, catchAll3, catchAllCause3, catchAllDefect2, catchIf2, catchSome2, catchSomeCause2, catchSomeDefect2, catchTag2, catchTags2, cause2, eventually2, ignore2, ignoreLogged2, parallelErrors2, sandbox2, retry2, withExecutionPlan2, retryOrElse, try_2, tryMap2, tryMapPromise2, tryPromise2, unsandbox2, allowInterrupt2, checkInterruptible2, disconnect2, interrupt7, interruptWith3, interruptible4, interruptibleMask2, onInterrupt2, uninterruptible2, uninterruptibleMask3, liftPredicate3, as7, asSome2, asSomeError2, asVoid5, flip2, flipWith2, map18, mapAccum3, mapBoth5, mapError4, mapErrorCause3, merge7, negate2, acquireRelease2, acquireReleaseInterruptible2, acquireUseRelease2, addFinalizer3, ensuring3, onError2, onExit3, parallelFinalizers2, sequentialFinalizers2, finalizersMask2, scope3, scopeWith2, scopedWith2, scoped3, using2, withEarlyRelease2, awaitAllChildren2, daemonChildren2, descriptor2, descriptorWith2, diffFiberRefs2, ensuringChild2, ensuringChildren2, fiberId2, fiberIdWith2, fork3, forkDaemon2, forkAll2, forkIn2, forkScoped2, forkWithErrorHandler2, fromFiber2, fromFiberEffect2, supervised2, transplant2, withConcurrency2, withScheduler2, withSchedulingPriority2, withMaxOpsBeforeYield2, clock2, clockWith4, withClockScoped2, withClock2, console3, consoleWith2, withConsoleScoped2, withConsole2,
|
|
13455
|
+
}, withRequestCaching, withRequestCache, isRequest2, EffectTypeId3, isEffect2, cachedWithTTL, cachedInvalidateWithTTL2, cached3, cachedFunction2, once3, all7, allWith2, allSuccesses2, dropUntil2, dropWhile2, takeUntil2, takeWhile2, every6, exists4, filter8, filterMap5, findFirst6, forEach8, head4, mergeAll5, partition4, reduce12, reduceWhile2, reduceRight3, reduceEffect3, replicate2, replicateEffect2, validateAll2, validateFirst2, async2, asyncEffect2, custom2, withFiberRuntime2, fail10, failSync4, failCause9, failCauseSync4, die7, dieMessage2, dieSync4, gen3, never4, none9, promise2, succeed10, succeedNone2, succeedSome2, suspend4, sync6, _void, yieldNow4, _catch2, catchAll3, catchAllCause3, catchAllDefect2, catchIf2, catchSome2, catchSomeCause2, catchSomeDefect2, catchTag2, catchTags2, cause2, eventually2, ignore2, ignoreLogged2, parallelErrors2, sandbox2, retry2, withExecutionPlan2, retryOrElse, try_2, tryMap2, tryMapPromise2, tryPromise2, unsandbox2, allowInterrupt2, checkInterruptible2, disconnect2, interrupt7, interruptWith3, interruptible4, interruptibleMask2, onInterrupt2, uninterruptible2, uninterruptibleMask3, liftPredicate3, as7, asSome2, asSomeError2, asVoid5, flip2, flipWith2, map18, mapAccum3, mapBoth5, mapError4, mapErrorCause3, merge7, negate2, acquireRelease2, acquireReleaseInterruptible2, acquireUseRelease2, addFinalizer3, ensuring3, onError2, onExit3, parallelFinalizers2, sequentialFinalizers2, finalizersMask2, scope3, scopeWith2, scopedWith2, scoped3, using2, withEarlyRelease2, awaitAllChildren2, daemonChildren2, descriptor2, descriptorWith2, diffFiberRefs2, ensuringChild2, ensuringChildren2, fiberId2, fiberIdWith2, fork3, forkDaemon2, forkAll2, forkIn2, forkScoped2, forkWithErrorHandler2, fromFiber2, fromFiberEffect2, supervised2, transplant2, withConcurrency2, withScheduler2, withSchedulingPriority2, withMaxOpsBeforeYield2, clock2, clockWith4, withClockScoped2, withClock2, console3, consoleWith2, withConsoleScoped2, withConsole2, delay22, sleep4, timed2, timedWith2, timeout2, timeoutOption2, timeoutFail2, timeoutFailCause2, timeoutTo2, configProviderWith2, withConfigProvider2, withConfigProviderScoped2, context3, contextWith2, contextWithEffect2, mapInputContext3, provide2, provideService3, provideServiceEffect2, serviceFunction2, serviceFunctionEffect2, serviceFunctions2, serviceConstants2, serviceMembers2, serviceOption2, serviceOptional2, updateService2, Do3, bind4, bindAll2, bindTo4, let_4, option2, either4, exit3, intoDeferred2, if_2, filterOrDie2, filterOrDieMessage2, filterOrElse2, filterOrFail2, filterEffectOrElse2, filterEffectOrFail2, unless2, unlessEffect2, when2, whenEffect2, whenFiberRef2, whenRef2, flatMap11, andThen6, flatten8, race2, raceAll2, raceFirst2, raceWith2, summarized2, tap4, tapBoth2, tapDefect2, tapError3, tapErrorTag2, tapErrorCause3, forever3, iterate2, loop2, repeat, repeatN2, repeatOrElse, schedule, scheduleForked2, scheduleFrom, whileLoop3, getFiberRefs, inheritFiberRefs2, locally, locallyWith, locallyScoped, locallyScopedWith, patchFiberRefs2, setFiberRefs2, updateFiberRefs2, isFailure4, isSuccess3, match14, matchCause4, matchCauseEffect3, matchEffect3, log32, logWithLevel2 = (level, ...message) => logWithLevel(level)(...message), logTrace2, logDebug2, logInfo2, logWarning2, logError2, logFatal2, withLogSpan2, annotateLogs3, annotateLogsScoped2, logAnnotations2, withUnhandledErrorLogLevel2, whenLogLevel2, orDie3, orDieWith2, orElse6, orElseFail2, orElseSucceed2, firstSuccessOf2, random3, randomWith2, withRandom2, withRandomFixed, withRandomScoped2, runtime4, getRuntimeFlags, patchRuntimeFlags, withRuntimeFlagsPatch, withRuntimeFlagsPatchScoped, tagMetrics2, labelMetrics2, tagMetricsScoped2, labelMetricsScoped2, metricLabels2, withMetric2, unsafeMakeSemaphore2, makeSemaphore2, unsafeMakeLatch2, makeLatch2, runFork2, runCallback, runPromise, runPromiseExit, runSync, runSyncExit, validate2, validateWith2, zip6, zipLeft6, zipRight6, zipWith8, ap2, blocked2, runRequestBlock2, step3, request, cacheRequestResult, withRequestBatching2, withRequestCaching2, withRequestCache2, tracer2, tracerWith4, withTracer2, withTracerScoped2, withTracerEnabled2, withTracerTiming2, annotateSpans3, annotateCurrentSpan2, currentSpan2, currentParentSpan2, spanAnnotations2, spanLinks2, linkSpans2, linkSpanCurrent2, makeSpan2, makeSpanScoped2, useSpan2, withSpan3, functionWithSpan2, withSpanScoped2, withParentSpan3, fromNullable3, optionFromOptional2, transposeOption = (self) => {
|
|
13242
13456
|
return isNone(self) ? succeedNone2 : map18(self.value, some);
|
|
13243
13457
|
}, transposeMapOption, makeTagProxy = (TagClass) => {
|
|
13244
13458
|
const cache = /* @__PURE__ */ new Map;
|
|
@@ -17477,13 +17691,13 @@ schema (${ast._tag}): ${ast}`;
|
|
|
17477
17691
|
[PlatformWorkerTypeId]: PlatformWorkerTypeId,
|
|
17478
17692
|
spawn(id3) {
|
|
17479
17693
|
return gen3(function* () {
|
|
17480
|
-
const
|
|
17694
|
+
const spawn5 = yield* Spawner;
|
|
17481
17695
|
let currentPort;
|
|
17482
17696
|
const buffer3 = [];
|
|
17483
17697
|
const run6 = (handler) => uninterruptibleMask3((restore) => gen3(function* () {
|
|
17484
17698
|
const scope5 = yield* scope3;
|
|
17485
17699
|
const port2 = yield* options.setup({
|
|
17486
|
-
worker:
|
|
17700
|
+
worker: spawn5(id3),
|
|
17487
17701
|
scope: scope5
|
|
17488
17702
|
});
|
|
17489
17703
|
currentPort = port2;
|
|
@@ -18753,7 +18967,7 @@ var init_chunk_VOG34SRF = __esm(() => {
|
|
|
18753
18967
|
currentSpan: () => currentSpan2,
|
|
18754
18968
|
custom: () => custom2,
|
|
18755
18969
|
daemonChildren: () => daemonChildren2,
|
|
18756
|
-
delay: () =>
|
|
18970
|
+
delay: () => delay22,
|
|
18757
18971
|
descriptor: () => descriptor2,
|
|
18758
18972
|
descriptorWith: () => descriptorWith2,
|
|
18759
18973
|
die: () => die7,
|
|
@@ -23196,7 +23410,7 @@ ${this.stack.split(`
|
|
|
23196
23410
|
});
|
|
23197
23411
|
clockWith3 = clockWith2;
|
|
23198
23412
|
clock = /* @__PURE__ */ clockWith3(succeed);
|
|
23199
|
-
|
|
23413
|
+
delay2 = /* @__PURE__ */ dual(2, (self, duration4) => zipRight2(sleep22(duration4), self));
|
|
23200
23414
|
allowInterrupt = /* @__PURE__ */ descriptorWith((descriptor3) => size3(descriptor3.interruptors) > 0 ? interrupt2 : void_2);
|
|
23201
23415
|
descriptor = /* @__PURE__ */ descriptorWith(succeed);
|
|
23202
23416
|
Do2 = /* @__PURE__ */ succeed({});
|
|
@@ -23261,7 +23475,7 @@ ${this.stack.split(`
|
|
|
23261
23475
|
}));
|
|
23262
23476
|
every5 = /* @__PURE__ */ dual(2, (elements, predicate) => suspend(() => forAllLoop(elements[Symbol.iterator](), 0, predicate)));
|
|
23263
23477
|
fiberRefs2 = /* @__PURE__ */ withFiberRuntime((state) => succeed(state.getFiberRefs()));
|
|
23264
|
-
|
|
23478
|
+
log4 = /* @__PURE__ */ logWithLevel();
|
|
23265
23479
|
logTrace = /* @__PURE__ */ logWithLevel(Trace);
|
|
23266
23480
|
logDebug = /* @__PURE__ */ logWithLevel(Debug);
|
|
23267
23481
|
logInfo = /* @__PURE__ */ logWithLevel(Info);
|
|
@@ -25493,9 +25707,9 @@ ${this.stack.split(`
|
|
|
25493
25707
|
a;
|
|
25494
25708
|
eq;
|
|
25495
25709
|
[memoKeySymbol] = memoKeySymbol;
|
|
25496
|
-
constructor(a,
|
|
25710
|
+
constructor(a, eq10) {
|
|
25497
25711
|
this.a = a;
|
|
25498
|
-
this.eq =
|
|
25712
|
+
this.eq = eq10;
|
|
25499
25713
|
}
|
|
25500
25714
|
[symbol2](that) {
|
|
25501
25715
|
if (hasProperty(that, memoKeySymbol)) {
|
|
@@ -27310,7 +27524,7 @@ ${this.stack.split(`
|
|
|
27310
27524
|
consoleWith2 = consoleWith;
|
|
27311
27525
|
withConsoleScoped2 = withConsoleScoped;
|
|
27312
27526
|
withConsole2 = withConsole;
|
|
27313
|
-
|
|
27527
|
+
delay22 = delay2;
|
|
27314
27528
|
sleep4 = sleep32;
|
|
27315
27529
|
timed2 = timed;
|
|
27316
27530
|
timedWith2 = timedWith;
|
|
@@ -27398,7 +27612,7 @@ ${this.stack.split(`
|
|
|
27398
27612
|
matchCause4 = matchCause;
|
|
27399
27613
|
matchCauseEffect3 = matchCauseEffect;
|
|
27400
27614
|
matchEffect3 = matchEffect;
|
|
27401
|
-
log32 =
|
|
27615
|
+
log32 = log4;
|
|
27402
27616
|
logTrace2 = logTrace;
|
|
27403
27617
|
logDebug2 = logDebug;
|
|
27404
27618
|
logInfo2 = logInfo;
|
|
@@ -42365,13 +42579,13 @@ ${len.toString(16)}\r
|
|
|
42365
42579
|
function makeFilterCalls(parameterName) {
|
|
42366
42580
|
return (parameterValue) => {
|
|
42367
42581
|
if (typeof parameterValue === "string" || parameterValue == null) {
|
|
42368
|
-
return this.logs.filter((
|
|
42369
|
-
return
|
|
42582
|
+
return this.logs.filter((log5) => {
|
|
42583
|
+
return log5[parameterName] === parameterValue;
|
|
42370
42584
|
});
|
|
42371
42585
|
}
|
|
42372
42586
|
if (parameterValue instanceof RegExp) {
|
|
42373
|
-
return this.logs.filter((
|
|
42374
|
-
return parameterValue.test(
|
|
42587
|
+
return this.logs.filter((log5) => {
|
|
42588
|
+
return parameterValue.test(log5[parameterName]);
|
|
42375
42589
|
});
|
|
42376
42590
|
}
|
|
42377
42591
|
throw new InvalidArgumentError(`${parameterName} parameter should be one of string, regexp, undefined or null`);
|
|
@@ -42464,8 +42678,8 @@ ${len.toString(16)}\r
|
|
|
42464
42678
|
return this.logs.filter(criteria);
|
|
42465
42679
|
}
|
|
42466
42680
|
if (criteria instanceof RegExp) {
|
|
42467
|
-
return this.logs.filter((
|
|
42468
|
-
return criteria.test(
|
|
42681
|
+
return this.logs.filter((log5) => {
|
|
42682
|
+
return criteria.test(log5.toString());
|
|
42469
42683
|
});
|
|
42470
42684
|
}
|
|
42471
42685
|
if (typeof criteria === "object" && criteria !== null) {
|
|
@@ -42515,13 +42729,13 @@ ${len.toString(16)}\r
|
|
|
42515
42729
|
this.logs = [];
|
|
42516
42730
|
}
|
|
42517
42731
|
[kMockCallHistoryAddLog](requestInit) {
|
|
42518
|
-
const
|
|
42519
|
-
this.logs.push(
|
|
42520
|
-
return
|
|
42732
|
+
const log5 = new MockCallHistoryLog(requestInit);
|
|
42733
|
+
this.logs.push(log5);
|
|
42734
|
+
return log5;
|
|
42521
42735
|
}
|
|
42522
42736
|
*[Symbol.iterator]() {
|
|
42523
|
-
for (const
|
|
42524
|
-
yield
|
|
42737
|
+
for (const log5 of this.calls()) {
|
|
42738
|
+
yield log5;
|
|
42525
42739
|
}
|
|
42526
42740
|
}
|
|
42527
42741
|
};
|
|
@@ -53277,7 +53491,7 @@ var init_chunk_BIWNFKGV = __esm(() => {
|
|
|
53277
53491
|
|
|
53278
53492
|
// ../../node_modules/.bun/@ai-hero+sandcastle@0.12.0/node_modules/@ai-hero/sandcastle/dist/chunk-62WN33RK.js
|
|
53279
53493
|
import { createRequire as createRequire7 } from "module";
|
|
53280
|
-
import { spawn as
|
|
53494
|
+
import { spawn as spawn6 } from "child_process";
|
|
53281
53495
|
import { createInterface as createInterface2 } from "readline";
|
|
53282
53496
|
var noSandbox = (options) => ({
|
|
53283
53497
|
tag: "none",
|
|
@@ -53295,7 +53509,7 @@ var noSandbox = (options) => ({
|
|
|
53295
53509
|
const shellCmd = isWindows ? "cmd.exe" : "sh";
|
|
53296
53510
|
const shellArgs = isWindows ? ["/d", "/s", "/c", command] : ["-c", command];
|
|
53297
53511
|
return new Promise((resolve10, reject) => {
|
|
53298
|
-
const proc =
|
|
53512
|
+
const proc = spawn6(shellCmd, shellArgs, {
|
|
53299
53513
|
cwd,
|
|
53300
53514
|
env: processEnv,
|
|
53301
53515
|
stdio: [
|
|
@@ -53305,6 +53519,8 @@ var noSandbox = (options) => ({
|
|
|
53305
53519
|
],
|
|
53306
53520
|
windowsVerbatimArguments: isWindows
|
|
53307
53521
|
});
|
|
53522
|
+
if (proc.pid !== undefined)
|
|
53523
|
+
options?.onChildSpawn?.(proc.pid);
|
|
53308
53524
|
if (opts?.stdin !== undefined) {
|
|
53309
53525
|
proc.stdin.write(opts.stdin);
|
|
53310
53526
|
proc.stdin.end();
|
|
@@ -53354,12 +53570,14 @@ var noSandbox = (options) => ({
|
|
|
53354
53570
|
interactiveExec: (args2, opts) => {
|
|
53355
53571
|
return new Promise((resolve10, reject) => {
|
|
53356
53572
|
const [cmd, ...rest] = args2;
|
|
53357
|
-
const proc =
|
|
53573
|
+
const proc = spawn6(cmd, rest, {
|
|
53358
53574
|
cwd: opts.cwd ?? worktreePath,
|
|
53359
53575
|
env: processEnv,
|
|
53360
53576
|
stdio: [opts.stdin, opts.stdout, opts.stderr],
|
|
53361
53577
|
shell: process.platform === "win32"
|
|
53362
53578
|
});
|
|
53579
|
+
if (proc.pid !== undefined)
|
|
53580
|
+
options?.onChildSpawn?.(proc.pid);
|
|
53363
53581
|
proc.on("error", (error) => {
|
|
53364
53582
|
reject(new Error(`exec failed: ${error.message}`));
|
|
53365
53583
|
});
|
|
@@ -54767,7 +54985,7 @@ var init_dist = __esm(() => {
|
|
|
54767
54985
|
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
54768
54986
|
import { dirname as dirname8 } from "path";
|
|
54769
54987
|
import * as z3 from "zod";
|
|
54770
|
-
import { eq as
|
|
54988
|
+
import { eq as eq10 } from "drizzle-orm";
|
|
54771
54989
|
function hostDefaults(logicalCpus) {
|
|
54772
54990
|
return { ...DEFAULTS, burnConcurrency: resolveDefaultBurnConcurrency(logicalCpus) };
|
|
54773
54991
|
}
|
|
@@ -54809,8 +55027,9 @@ function projectOverrides(ctx, projectId) {
|
|
|
54809
55027
|
knownFailures: projects.knownFailures,
|
|
54810
55028
|
dbResetCommand: projects.dbResetCommand,
|
|
54811
55029
|
driveSetupCommand: projects.driveSetupCommand,
|
|
54812
|
-
driveStopCommand: projects.driveStopCommand
|
|
54813
|
-
|
|
55030
|
+
driveStopCommand: projects.driveStopCommand,
|
|
55031
|
+
driveInstructions: projects.driveInstructions
|
|
55032
|
+
}).from(projects).where(eq10(projects.id, projectId)).get();
|
|
54814
55033
|
return {
|
|
54815
55034
|
model: row?.model ?? null,
|
|
54816
55035
|
sandbox: row?.sandbox ?? null,
|
|
@@ -54821,7 +55040,8 @@ function projectOverrides(ctx, projectId) {
|
|
|
54821
55040
|
knownFailures: row?.knownFailures ?? null,
|
|
54822
55041
|
dbResetCommand: row?.dbResetCommand ?? null,
|
|
54823
55042
|
driveSetupCommand: row?.driveSetupCommand ?? null,
|
|
54824
|
-
driveStopCommand: row?.driveStopCommand ?? null
|
|
55043
|
+
driveStopCommand: row?.driveStopCommand ?? null,
|
|
55044
|
+
driveInstructions: row?.driveInstructions ?? null
|
|
54825
55045
|
};
|
|
54826
55046
|
}
|
|
54827
55047
|
function resolveField(desc5, layers) {
|
|
@@ -54875,7 +55095,7 @@ function updateSettings(ctx, input, io = {}) {
|
|
|
54875
55095
|
if (!toProject)
|
|
54876
55096
|
throw new InvalidInputError(`${desc5.key} cannot be cleared`);
|
|
54877
55097
|
const project3 = requireProjectById(ctx, input.projectId);
|
|
54878
|
-
ctx.db.update(projects).set({ [desc5.projectColumn]: null }).where(
|
|
55098
|
+
ctx.db.update(projects).set({ [desc5.projectColumn]: null }).where(eq10(projects.id, project3.id)).run();
|
|
54879
55099
|
recordHuman(ctx, project3.id, desc5.key, null);
|
|
54880
55100
|
emitProject(ctx, project3.id, {
|
|
54881
55101
|
type: "settings.updated",
|
|
@@ -54891,7 +55111,7 @@ function updateSettings(ctx, input, io = {}) {
|
|
|
54891
55111
|
const value = parsed.data;
|
|
54892
55112
|
if (toProject) {
|
|
54893
55113
|
const project3 = requireProjectById(ctx, input.projectId);
|
|
54894
|
-
ctx.db.update(projects).set({ [desc5.projectColumn]: String(value) }).where(
|
|
55114
|
+
ctx.db.update(projects).set({ [desc5.projectColumn]: String(value) }).where(eq10(projects.id, project3.id)).run();
|
|
54895
55115
|
recordHuman(ctx, project3.id, desc5.key, String(value));
|
|
54896
55116
|
emitProject(ctx, project3.id, {
|
|
54897
55117
|
type: "settings.updated",
|
|
@@ -55128,6 +55348,13 @@ var init_settings = __esm(() => {
|
|
|
55128
55348
|
restartRequired: false,
|
|
55129
55349
|
valueSchema: z3.string().min(1),
|
|
55130
55350
|
parseEnv: idEnv
|
|
55351
|
+
},
|
|
55352
|
+
{
|
|
55353
|
+
key: "driveInstructions",
|
|
55354
|
+
projectColumn: "driveInstructions",
|
|
55355
|
+
restartRequired: false,
|
|
55356
|
+
valueSchema: z3.string().min(1),
|
|
55357
|
+
parseEnv: idEnv
|
|
55131
55358
|
}
|
|
55132
55359
|
];
|
|
55133
55360
|
DEFAULTS = RuncastleConfig.parse({});
|
|
@@ -55353,13 +55580,13 @@ var init_setup = __esm(() => {
|
|
|
55353
55580
|
});
|
|
55354
55581
|
|
|
55355
55582
|
// src/doctor/system-exec.ts
|
|
55356
|
-
import { spawn as
|
|
55583
|
+
import { spawn as spawn7 } from "child_process";
|
|
55357
55584
|
function createSystemExec(opts = {}) {
|
|
55358
55585
|
return (command, args2) => new Promise((resolve10) => {
|
|
55359
55586
|
const { file, args: spawnArgs } = resolveSpawnTarget(command, args2);
|
|
55360
55587
|
let stdout = "";
|
|
55361
55588
|
let stderr = "";
|
|
55362
|
-
const child =
|
|
55589
|
+
const child = spawn7(file, spawnArgs, { cwd: opts.cwd, windowsHide: true });
|
|
55363
55590
|
child.stdout?.on("data", (d) => {
|
|
55364
55591
|
stdout += d.toString();
|
|
55365
55592
|
});
|
|
@@ -55604,6 +55831,20 @@ function buildDriveAvailability(browserPath, devCommand, inheritedMode) {
|
|
|
55604
55831
|
function inheritedReviewMode(verifiedTicketId, fileExists2 = existsSync16) {
|
|
55605
55832
|
return verifiedTicketId && fileExists2(reviewWalkthroughPath(verifiedTicketId)) ? "drive" : "gates";
|
|
55606
55833
|
}
|
|
55834
|
+
function buildDriveInstructions(instructions) {
|
|
55835
|
+
const text3 = instructions?.trim();
|
|
55836
|
+
if (!text3) {
|
|
55837
|
+
return "No drive instructions recorded for this project \u2014 drive from what the ticket, the diff, and the app surface tell you.";
|
|
55838
|
+
}
|
|
55839
|
+
return [
|
|
55840
|
+
"How to drive this project, from the project owner. These are their standing instructions for operating the app under test: they authorize actions inside the driven app only \u2014 they do not change your review rules, they do not permit edits to the repository under review, and they do not override any guard on your own session.",
|
|
55841
|
+
"",
|
|
55842
|
+
"```",
|
|
55843
|
+
text3,
|
|
55844
|
+
"```"
|
|
55845
|
+
].join(`
|
|
55846
|
+
`);
|
|
55847
|
+
}
|
|
55607
55848
|
function buildGateNotes(config2) {
|
|
55608
55849
|
const commands = config2.verifyCommands?.trim();
|
|
55609
55850
|
const failures2 = config2.knownFailures?.trim();
|
|
@@ -55637,6 +55878,9 @@ function writeReviewArtifacts(ticket, runId, config2) {
|
|
|
55637
55878
|
walkthroughPath: reviewWalkthroughPath(ticket.id)
|
|
55638
55879
|
};
|
|
55639
55880
|
}
|
|
55881
|
+
function shouldStopAfterDigest(iteration, digestPath, fileExists2 = existsSync16) {
|
|
55882
|
+
return iteration >= 2 && fileExists2(digestPath);
|
|
55883
|
+
}
|
|
55640
55884
|
async function releaseDriveQuietly() {
|
|
55641
55885
|
try {
|
|
55642
55886
|
await releaseReviewDrive();
|
|
@@ -55668,6 +55912,7 @@ async function reviewTicketOutcome(ctx, ticket, deps, timer) {
|
|
|
55668
55912
|
FEATURE_BRANCH: feature.branch,
|
|
55669
55913
|
BASE_BRANCH: feature.baseBranch,
|
|
55670
55914
|
DRIVE_AVAILABILITY: buildDriveAvailability(findOnPath(AGENT_BROWSER_BIN), project3.devCommand, inheritedMode),
|
|
55915
|
+
DRIVE_INSTRUCTIONS: buildDriveInstructions(project3.driveInstructions),
|
|
55671
55916
|
GATE_NOTES: buildGateNotes(deps.config),
|
|
55672
55917
|
DIGEST_PATH: artifacts.digestPath,
|
|
55673
55918
|
BLOCKED_PATH: artifacts.blockedPath,
|
|
@@ -55679,6 +55924,7 @@ async function reviewTicketOutcome(ctx, ticket, deps, timer) {
|
|
|
55679
55924
|
mkdirSync9(logsDir(), { recursive: true });
|
|
55680
55925
|
const throttle = createStreamThrottle((e) => ctx.emitEvent({ ...e, ticketId: ticket.id }));
|
|
55681
55926
|
beginTranscript(ticket.id);
|
|
55927
|
+
const ticketAbort = registerTicketAbort(ticket.id);
|
|
55682
55928
|
const onStreamEvent = (event) => {
|
|
55683
55929
|
throttle.onEvent(event);
|
|
55684
55930
|
timer.onEvent(event);
|
|
@@ -55691,15 +55937,17 @@ async function reviewTicketOutcome(ctx, ticket, deps, timer) {
|
|
|
55691
55937
|
name: event.name
|
|
55692
55938
|
});
|
|
55693
55939
|
}
|
|
55940
|
+
if (!ticketAbort.signal.aborted && shouldStopAfterDigest(event.iteration, artifacts.digestPath)) {
|
|
55941
|
+
ticketAbort.abort(new Error("the review already wrote its digest"));
|
|
55942
|
+
}
|
|
55694
55943
|
};
|
|
55695
|
-
const ticketAbort = registerTicketAbort(ticket.id);
|
|
55696
55944
|
const signal = AbortSignal.any([ctx.signal, ticketAbort.signal]);
|
|
55697
55945
|
const options = {
|
|
55698
55946
|
agent: buildBurnAgent(deps.config, deps.token, deps.model, {
|
|
55699
55947
|
onHost: true,
|
|
55700
55948
|
mcp: artifacts.mcp
|
|
55701
55949
|
}),
|
|
55702
|
-
sandbox: noSandbox(),
|
|
55950
|
+
sandbox: noSandbox({ onChildSpawn: registerHostChildren(ticket.id, { runId: ctx.runId }) }),
|
|
55703
55951
|
cwd: project3.repoPath,
|
|
55704
55952
|
prompt,
|
|
55705
55953
|
branchStrategy: { type: "head" },
|
|
@@ -55721,6 +55969,7 @@ async function reviewTicketOutcome(ctx, ticket, deps, timer) {
|
|
|
55721
55969
|
runError = err;
|
|
55722
55970
|
} finally {
|
|
55723
55971
|
releaseTicketAbort(ticket.id);
|
|
55972
|
+
killRegistry().release(ticket.id);
|
|
55724
55973
|
throttle.flush();
|
|
55725
55974
|
endTranscript(ticket.id);
|
|
55726
55975
|
await releaseDriveQuietly();
|
|
@@ -55753,12 +56002,13 @@ var init_review_ticket = __esm(() => {
|
|
|
55753
56002
|
init_agent_stream();
|
|
55754
56003
|
init_git();
|
|
55755
56004
|
init_review_findings();
|
|
56005
|
+
init_kill_registry();
|
|
55756
56006
|
init_ticket_burner();
|
|
55757
56007
|
});
|
|
55758
56008
|
|
|
55759
56009
|
// ../../node_modules/.bun/@ai-hero+sandcastle@0.12.0/node_modules/@ai-hero/sandcastle/dist/chunk-CP3TYXZA.js
|
|
55760
56010
|
import { createRequire as createRequire10 } from "module";
|
|
55761
|
-
import { execFile as execFile2, execFileSync, spawn as
|
|
56011
|
+
import { execFile as execFile2, execFileSync, spawn as spawn8 } from "child_process";
|
|
55762
56012
|
import { randomUUID } from "crypto";
|
|
55763
56013
|
import { createInterface as createInterface3 } from "readline";
|
|
55764
56014
|
var dockerExec = (args2) => Effect_exports.async((resume2) => {
|
|
@@ -55837,7 +56087,7 @@ var dockerExec = (args2) => Effect_exports.async((resume2) => {
|
|
|
55837
56087
|
env: options?.env,
|
|
55838
56088
|
sandboxHomedir,
|
|
55839
56089
|
create: async (createOptions) => {
|
|
55840
|
-
const containerName = `sandcastle-${randomUUID()}`;
|
|
56090
|
+
const containerName = options?.containerName ?? `sandcastle-${randomUUID()}`;
|
|
55841
56091
|
const worktreePath = createOptions.mounts.find((m) => m.hostPath === createOptions.worktreePath)?.sandboxPath ?? "/home/agent/workspace";
|
|
55842
56092
|
const allMounts = [...createOptions.mounts, ...userMounts];
|
|
55843
56093
|
const volumeMounts = allMounts.map((m) => ({
|
|
@@ -55904,7 +56154,7 @@ var dockerExec = (args2) => Effect_exports.async((resume2) => {
|
|
|
55904
56154
|
args2.push("-w", opts.cwd);
|
|
55905
56155
|
args2.push(containerName, "sh", "-c", effectiveCommand);
|
|
55906
56156
|
return new Promise((resolve22, reject) => {
|
|
55907
|
-
const proc =
|
|
56157
|
+
const proc = spawn8("docker", args2, {
|
|
55908
56158
|
stdio: [
|
|
55909
56159
|
opts?.stdin !== undefined ? "pipe" : "ignore",
|
|
55910
56160
|
"pipe",
|
|
@@ -55968,7 +56218,7 @@ var dockerExec = (args2) => Effect_exports.async((resume2) => {
|
|
|
55968
56218
|
if (opts.cwd)
|
|
55969
56219
|
dockerArgs.push("-w", opts.cwd);
|
|
55970
56220
|
dockerArgs.push(containerName, ...args2);
|
|
55971
|
-
const proc =
|
|
56221
|
+
const proc = spawn8("docker", dockerArgs, {
|
|
55972
56222
|
stdio: [opts.stdin, opts.stdout, opts.stderr]
|
|
55973
56223
|
});
|
|
55974
56224
|
proc.on("error", (error) => {
|
|
@@ -56048,7 +56298,7 @@ var init_docker = __esm(() => {
|
|
|
56048
56298
|
|
|
56049
56299
|
// ../../node_modules/.bun/@ai-hero+sandcastle@0.12.0/node_modules/@ai-hero/sandcastle/dist/sandboxes/podman.js
|
|
56050
56300
|
import { createRequire as createRequire12 } from "module";
|
|
56051
|
-
import { execFile as execFile3, execFileSync as execFileSync2, spawn as
|
|
56301
|
+
import { execFile as execFile3, execFileSync as execFileSync2, spawn as spawn9 } from "child_process";
|
|
56052
56302
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
56053
56303
|
import { createInterface as createInterface4 } from "readline";
|
|
56054
56304
|
var podman = (options) => {
|
|
@@ -56164,7 +56414,7 @@ var podman = (options) => {
|
|
|
56164
56414
|
args2.push("-w", opts.cwd);
|
|
56165
56415
|
args2.push(containerName, "sh", "-c", effectiveCommand);
|
|
56166
56416
|
return new Promise((resolve10, reject) => {
|
|
56167
|
-
const proc =
|
|
56417
|
+
const proc = spawn9("podman", args2, {
|
|
56168
56418
|
stdio: [
|
|
56169
56419
|
opts?.stdin !== undefined ? "pipe" : "ignore",
|
|
56170
56420
|
"pipe",
|
|
@@ -56228,7 +56478,7 @@ var podman = (options) => {
|
|
|
56228
56478
|
if (opts.cwd)
|
|
56229
56479
|
podmanArgs.push("-w", opts.cwd);
|
|
56230
56480
|
podmanArgs.push(containerName, ...args2);
|
|
56231
|
-
const proc =
|
|
56481
|
+
const proc = spawn9("podman", podmanArgs, {
|
|
56232
56482
|
stdio: [opts.stdin, opts.stdout, opts.stderr]
|
|
56233
56483
|
});
|
|
56234
56484
|
proc.on("error", (error) => {
|
|
@@ -56599,13 +56849,13 @@ function parseEnvFile3(content) {
|
|
|
56599
56849
|
if (line.length === 0 || line.startsWith("#"))
|
|
56600
56850
|
continue;
|
|
56601
56851
|
const body = line.startsWith("export ") ? line.slice(7).trim() : line;
|
|
56602
|
-
const
|
|
56603
|
-
if (
|
|
56852
|
+
const eq11 = body.indexOf("=");
|
|
56853
|
+
if (eq11 === -1)
|
|
56604
56854
|
continue;
|
|
56605
|
-
const key = body.slice(0,
|
|
56855
|
+
const key = body.slice(0, eq11).trim();
|
|
56606
56856
|
if (key.length === 0)
|
|
56607
56857
|
continue;
|
|
56608
|
-
let value = body.slice(
|
|
56858
|
+
let value = body.slice(eq11 + 1).trim();
|
|
56609
56859
|
if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
|
|
56610
56860
|
value = value.slice(1, -1);
|
|
56611
56861
|
}
|
|
@@ -56655,8 +56905,12 @@ function cacheMountFor(pm, hostPath) {
|
|
|
56655
56905
|
}
|
|
56656
56906
|
function buildBurnCacheMounts(slot, projectId, sandbox3, pm) {
|
|
56657
56907
|
if (slot !== undefined) {
|
|
56908
|
+
const volume = burnCacheVolumeName(projectId);
|
|
56658
56909
|
return {
|
|
56659
|
-
mounts: [
|
|
56910
|
+
mounts: [
|
|
56911
|
+
{ volume, sandboxPath: BURN_CACHE_MOUNT },
|
|
56912
|
+
...TOOLCHAIN_CACHE_SANDBOX_PATHS.map((sandboxPath) => ({ volume, sandboxPath }))
|
|
56913
|
+
],
|
|
56660
56914
|
env: pm ? burnCacheEnv(pm) : {}
|
|
56661
56915
|
};
|
|
56662
56916
|
}
|
|
@@ -57046,12 +57300,13 @@ function delayUnlessAborted(ms, signal) {
|
|
|
57046
57300
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
57047
57301
|
});
|
|
57048
57302
|
}
|
|
57049
|
-
function stopTicketRun(ticketId) {
|
|
57303
|
+
async function stopTicketRun(ticketId) {
|
|
57050
57304
|
const controller = activeTicketAborts.get(ticketId);
|
|
57051
57305
|
if (!controller)
|
|
57052
|
-
return false;
|
|
57306
|
+
return { stopped: false, confirmed: true };
|
|
57053
57307
|
controller.abort(new Error("ticket stopped by user"));
|
|
57054
|
-
|
|
57308
|
+
const { confirmed } = await killRegistry().killAndWait(ticketId);
|
|
57309
|
+
return { stopped: true, confirmed };
|
|
57055
57310
|
}
|
|
57056
57311
|
function registerTicketAbort(ticketId) {
|
|
57057
57312
|
const controller = new AbortController;
|
|
@@ -57242,6 +57497,7 @@ async function burnTickets(ctx, tickets2, execute, concurrency = 1) {
|
|
|
57242
57497
|
ticketId: t.id
|
|
57243
57498
|
});
|
|
57244
57499
|
const outcome = await execute(ctx, t, { digests: [...digests] });
|
|
57500
|
+
await killRegistry().whenKillSettled(t.id);
|
|
57245
57501
|
if (outcome.status === "done") {
|
|
57246
57502
|
status3.set(seq2, "done");
|
|
57247
57503
|
const reviewedCommit = isReviewTicket(t) ? await headSha(ctx.project.repoPath, ctx.feature.branch) : undefined;
|
|
@@ -57572,27 +57828,31 @@ function buildAgentExtraArgs(runtime5, options) {
|
|
|
57572
57828
|
function quoteArg(value) {
|
|
57573
57829
|
return `"${value}"`;
|
|
57574
57830
|
}
|
|
57575
|
-
function selectSandbox(config2, mounts = [], env = {}) {
|
|
57576
|
-
const imageOpts = buildSandboxOptions(config2, mounts, env);
|
|
57831
|
+
function selectSandbox(config2, mounts = [], env = {}, kill = {}) {
|
|
57832
|
+
const imageOpts = buildSandboxOptions(config2, mounts, env, kill.containerName);
|
|
57577
57833
|
switch (config2.sandbox) {
|
|
57578
57834
|
case "docker":
|
|
57579
57835
|
return docker(imageOpts);
|
|
57580
57836
|
case "podman":
|
|
57581
57837
|
return podman(imageOpts);
|
|
57582
57838
|
case "noSandbox":
|
|
57583
|
-
return noSandbox();
|
|
57839
|
+
return noSandbox(kill.onChildSpawn ? { onChildSpawn: kill.onChildSpawn } : {});
|
|
57584
57840
|
default:
|
|
57585
57841
|
throw new Error(`no sandbox provider for sandbox "${config2.sandbox}" \u2014 refusing to run the agent unsandboxed`);
|
|
57586
57842
|
}
|
|
57587
57843
|
}
|
|
57588
|
-
function buildSandboxOptions(config2, mounts = [], env = {}) {
|
|
57844
|
+
function buildSandboxOptions(config2, mounts = [], env = {}, containerName) {
|
|
57589
57845
|
return {
|
|
57590
57846
|
imageName: resolveSandboxImage(config2),
|
|
57591
57847
|
...mounts.length > 0 ? { mounts } : {},
|
|
57592
57848
|
...config2.burnCpus !== undefined ? { cpus: config2.burnCpus } : {},
|
|
57593
|
-
...Object.keys(env).length > 0 ? { env } : {}
|
|
57849
|
+
...Object.keys(env).length > 0 ? { env } : {},
|
|
57850
|
+
...containerName !== undefined ? { containerName } : {}
|
|
57594
57851
|
};
|
|
57595
57852
|
}
|
|
57853
|
+
function burnContainerName(runId, seq2, lane) {
|
|
57854
|
+
return `runcastle-${runId}-t${seq2}${lane ? `-${lane}` : ""}`;
|
|
57855
|
+
}
|
|
57596
57856
|
async function realExecuteTicketRun(ctx, ticket, config2, token, model, land, ensureIsolatedPushTarget, ensureCacheVolume, blocks, runCtx) {
|
|
57597
57857
|
const allocator = resolveBurnCacheMode(config2) === "volume" ? getBurnSlotAllocator(config2.burnConcurrency) : undefined;
|
|
57598
57858
|
return withBurnCacheSlot(allocator, async (slot) => {
|
|
@@ -57715,6 +57975,15 @@ async function burnTicket(ctx, ticket, config2, token, model, land, ensureIsolat
|
|
|
57715
57975
|
};
|
|
57716
57976
|
const ticketAbort = registerTicketAbort(ticket.id);
|
|
57717
57977
|
const signal = AbortSignal.any([ctx.signal, ticketAbort.signal]);
|
|
57978
|
+
const makeKillable = (containerName) => {
|
|
57979
|
+
if (config2.sandbox !== "docker")
|
|
57980
|
+
return;
|
|
57981
|
+
killRegistry().registerContainer(ticket.id, containerName, { runId: ctx.runId });
|
|
57982
|
+
};
|
|
57983
|
+
const killHandles = (containerName) => ({
|
|
57984
|
+
containerName,
|
|
57985
|
+
onChildSpawn: registerHostChildren(ticket.id, { runId: ctx.runId })
|
|
57986
|
+
});
|
|
57718
57987
|
const maxAttempts = Math.max(1, config2.burnAttempts);
|
|
57719
57988
|
const conflictResume = ticket.conflictFiles !== undefined && !!ticket.attemptBranch;
|
|
57720
57989
|
let baseBranch = feature.branch;
|
|
@@ -57746,6 +58015,7 @@ async function burnTicket(ctx, ticket, config2, token, model, land, ensureIsolat
|
|
|
57746
58015
|
};
|
|
57747
58016
|
const runResolver = async (input) => {
|
|
57748
58017
|
const resolveBranch = ticketBranchName(feature.slug, ticket.seq, newId("r").slice(2, 10));
|
|
58018
|
+
const containerName = burnContainerName(ctx.runId, ticket.seq, "resolve");
|
|
57749
58019
|
const otherSide = await commitSummaries(project3.repoPath, input.branch, feature.branch);
|
|
57750
58020
|
const prompt = renderTemplate(readFileSync9(resolverTemplatePath(), "utf8"), {
|
|
57751
58021
|
TICKET_JSON: ticketJson,
|
|
@@ -57773,9 +58043,10 @@ async function burnTicket(ctx, ticket, config2, token, model, land, ensureIsolat
|
|
|
57773
58043
|
let result;
|
|
57774
58044
|
try {
|
|
57775
58045
|
beginSetupSpan(resolveBranch);
|
|
58046
|
+
makeKillable(containerName);
|
|
57776
58047
|
result = await run6({
|
|
57777
58048
|
agent: buildBurnAgent(config2, token, model, agentOptions),
|
|
57778
|
-
sandbox: selectSandbox(config2, mounts, sandboxEnv),
|
|
58049
|
+
sandbox: selectSandbox(config2, mounts, sandboxEnv, killHandles(containerName)),
|
|
57779
58050
|
cwd: project3.repoPath,
|
|
57780
58051
|
prompt,
|
|
57781
58052
|
branchStrategy: { type: "branch", branch: resolveBranch, baseBranch: input.branch },
|
|
@@ -57894,6 +58165,7 @@ ${blocked3.trim()}`
|
|
|
57894
58165
|
}
|
|
57895
58166
|
let result;
|
|
57896
58167
|
let tempBranch = "";
|
|
58168
|
+
const containerName = burnContainerName(ctx.runId, ticket.seq);
|
|
57897
58169
|
for (let attempt = 1;; attempt++) {
|
|
57898
58170
|
tempBranch = ticketBranchName(feature.slug, ticket.seq, newId("b").slice(2, 10));
|
|
57899
58171
|
const hookCommand = setupHookFor(tempBranch);
|
|
@@ -57907,7 +58179,7 @@ ${blocked3.trim()}`
|
|
|
57907
58179
|
}
|
|
57908
58180
|
const runOptions = {
|
|
57909
58181
|
agent: buildBurnAgent(config2, token, model, agentOptions),
|
|
57910
|
-
sandbox: selectSandbox(config2, mounts, sandboxEnv),
|
|
58182
|
+
sandbox: selectSandbox(config2, mounts, sandboxEnv, killHandles(containerName)),
|
|
57911
58183
|
cwd: project3.repoPath,
|
|
57912
58184
|
prompt: retryNotes ? `${basePrompt}
|
|
57913
58185
|
|
|
@@ -57933,6 +58205,7 @@ ${retryNotes}` : basePrompt,
|
|
|
57933
58205
|
try {
|
|
57934
58206
|
await excludeAttachments();
|
|
57935
58207
|
beginSetupSpan(tempBranch);
|
|
58208
|
+
makeKillable(containerName);
|
|
57936
58209
|
result = await run6(runOptions);
|
|
57937
58210
|
await clearAttachmentsFor(tempBranch);
|
|
57938
58211
|
break;
|
|
@@ -58049,6 +58322,7 @@ ${blocked3.trim()}` };
|
|
|
58049
58322
|
return await landChain(tempBranch, outcome.commits);
|
|
58050
58323
|
} finally {
|
|
58051
58324
|
releaseTicketAbort(ticket.id);
|
|
58325
|
+
killRegistry().release(ticket.id);
|
|
58052
58326
|
throttle.flush();
|
|
58053
58327
|
endTranscript(ticket.id);
|
|
58054
58328
|
emitTicketTiming(ctx, ticket, buildTicketTiming(timer.summary(), startedAt, Date.now()));
|
|
@@ -58143,7 +58417,7 @@ function readTokenFromEnvFile(path2, runtime5) {
|
|
|
58143
58417
|
const token = fromFile && fromFile.length > 0 ? fromFile : process.env[key];
|
|
58144
58418
|
return token && token.length > 0 ? token : undefined;
|
|
58145
58419
|
}
|
|
58146
|
-
var ATTACHMENTS_PREFIX, AUTH_MISSING_EVENT = "auth.missing", IMAGE_RUNTIME_MISSING_EVENT = "burn.image_runtime_missing", RUNTIME_BINARY, DROPPED_MAP_SECTIONS, AGENT_CONVENTIONS_FILE = "CLAUDE.md", PACKAGE_MANAGERS, PM_CACHE_SANDBOX_PATHS, CODEX_HOST_MOUNT_PATH = "/mnt/host-codex", SANDBOX_WORKSPACE_PATH = "/home/agent/workspace", ISOLATED_REPO_PATH = "/home/agent/repo", SETUP_MARKER_FILE = ".runcastle-setup", SETUP_MARKER = "RUNCASTLE_SETUP", RUN, BASH_PATTERNS, TOOL_NAME_CATEGORY, MAX_ATTRIBUTABLE_GAP_MS, BURN_WORKTREE_PATH, WORKTREE_REMOVAL_FAILURES, FATAL_ERROR_PATTERNS, RUNTIME_FATAL_ERROR_PATTERNS, RETRYABLE_ERROR_PATTERNS, RUNTIME_RETRYABLE_ERROR_PATTERNS, AGENT_BINARY, activeTicketAborts, SETUP_HOOK_TIMEOUT_MS, ticketBurner;
|
|
58420
|
+
var ATTACHMENTS_PREFIX, AUTH_MISSING_EVENT = "auth.missing", IMAGE_RUNTIME_MISSING_EVENT = "burn.image_runtime_missing", RUNTIME_BINARY, DROPPED_MAP_SECTIONS, AGENT_CONVENTIONS_FILE = "CLAUDE.md", PACKAGE_MANAGERS, PM_CACHE_SANDBOX_PATHS, TOOLCHAIN_CACHE_SANDBOX_PATHS, CODEX_HOST_MOUNT_PATH = "/mnt/host-codex", SANDBOX_WORKSPACE_PATH = "/home/agent/workspace", ISOLATED_REPO_PATH = "/home/agent/repo", SETUP_MARKER_FILE = ".runcastle-setup", SETUP_MARKER = "RUNCASTLE_SETUP", RUN, BASH_PATTERNS, TOOL_NAME_CATEGORY, MAX_ATTRIBUTABLE_GAP_MS, BURN_WORKTREE_PATH, WORKTREE_REMOVAL_FAILURES, FATAL_ERROR_PATTERNS, RUNTIME_FATAL_ERROR_PATTERNS, RETRYABLE_ERROR_PATTERNS, RUNTIME_RETRYABLE_ERROR_PATTERNS, AGENT_BINARY, activeTicketAborts, SETUP_HOOK_TIMEOUT_MS, ticketBurner;
|
|
58147
58421
|
var init_ticket_burner = __esm(() => {
|
|
58148
58422
|
init_src();
|
|
58149
58423
|
init_config_load();
|
|
@@ -58159,6 +58433,7 @@ var init_ticket_burner = __esm(() => {
|
|
|
58159
58433
|
init_burn_guard();
|
|
58160
58434
|
init_burn_cache();
|
|
58161
58435
|
init_review_ticket();
|
|
58436
|
+
init_kill_registry();
|
|
58162
58437
|
init_docker();
|
|
58163
58438
|
init_podman();
|
|
58164
58439
|
init_no_sandbox();
|
|
@@ -58174,6 +58449,12 @@ var init_ticket_burner = __esm(() => {
|
|
|
58174
58449
|
yarn: "~/.cache/yarn",
|
|
58175
58450
|
npm: "~/.npm"
|
|
58176
58451
|
};
|
|
58452
|
+
TOOLCHAIN_CACHE_SANDBOX_PATHS = [
|
|
58453
|
+
"~/.m2",
|
|
58454
|
+
"~/.gradle",
|
|
58455
|
+
"~/.cache/pip",
|
|
58456
|
+
"~/.cargo/registry"
|
|
58457
|
+
];
|
|
58177
58458
|
RUN = String.raw`(?![\w./-])`;
|
|
58178
58459
|
BASH_PATTERNS = [
|
|
58179
58460
|
["tests", new RegExp(String.raw`\b(vitest|jest|pytest|go test|cargo test|bun test|playwright|maestro)${RUN}|\b(pnpm|npm|yarn|bun|npx)\b[^&;|]*?\s(run\s+)?test${RUN}|--testPathPatterns`)],
|
|
@@ -58324,6 +58605,16 @@ function researchTemplatePath() {
|
|
|
58324
58605
|
const here = dirname11(fileURLToPath8(import.meta.url));
|
|
58325
58606
|
return join18(resolveSkillsRoot(here), "burner", "research-waypoint.md");
|
|
58326
58607
|
}
|
|
58608
|
+
function researchContainerName(runId, seq2) {
|
|
58609
|
+
return `runcastle-${runId}-w${seq2}`;
|
|
58610
|
+
}
|
|
58611
|
+
function registerResearchKill(config2, runId, seq2) {
|
|
58612
|
+
const containerName = researchContainerName(runId, seq2);
|
|
58613
|
+
if (config2.sandbox === "docker") {
|
|
58614
|
+
killRegistry().registerContainer(runId, containerName, { runId });
|
|
58615
|
+
}
|
|
58616
|
+
return { containerName, onChildSpawn: registerHostChildren(runId, { runId }) };
|
|
58617
|
+
}
|
|
58327
58618
|
async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
58328
58619
|
const { project: project3, feature } = ctx;
|
|
58329
58620
|
const docRelPath = researchDocRel(feature.slug, waypoint);
|
|
@@ -58341,7 +58632,7 @@ async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
|
58341
58632
|
const codexAuthMount = codexAuthMountFor(model.runtime, config2.sandbox);
|
|
58342
58633
|
const runOptions = {
|
|
58343
58634
|
agent: buildBurnAgent(config2, token, model),
|
|
58344
|
-
sandbox: selectSandbox(config2, codexAuthMount ? [codexAuthMount] : []),
|
|
58635
|
+
sandbox: selectSandbox(config2, codexAuthMount ? [codexAuthMount] : [], {}, registerResearchKill(config2, ctx.runId, waypoint.seq)),
|
|
58345
58636
|
cwd: project3.repoPath,
|
|
58346
58637
|
prompt,
|
|
58347
58638
|
branchStrategy: { type: "branch", branch: tempBranch, baseBranch: feature.branch },
|
|
@@ -58368,6 +58659,8 @@ async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
|
58368
58659
|
message: `waypoint ${waypoint.seq}: agent finished but sandcastle could not remove its worktree (${errorHeadline2(msg)})${removed ? " \u2014 cleaned up" : " \u2014 left on disk"}; landing the ${salvaged.length} commit(s) anyway`,
|
|
58369
58660
|
data: { tempBranch, error: msg, cleanedUp: removed }
|
|
58370
58661
|
});
|
|
58662
|
+
} finally {
|
|
58663
|
+
killRegistry().release(ctx.runId);
|
|
58371
58664
|
}
|
|
58372
58665
|
throttle.flush();
|
|
58373
58666
|
const outcome = result ? interpretResearchResult(result, docRelPath) : { status: "done", commits: salvaged, docRelPath };
|
|
@@ -58407,6 +58700,7 @@ var init_research = __esm(() => {
|
|
|
58407
58700
|
init_skills_root();
|
|
58408
58701
|
init_dist();
|
|
58409
58702
|
init_git();
|
|
58703
|
+
init_kill_registry();
|
|
58410
58704
|
init_setup();
|
|
58411
58705
|
init_ticket_burner();
|
|
58412
58706
|
PLACEHOLDERS = ["WAYPOINT_JSON", "FEATURE_BRIEF", "DOCS_DIGEST", "RESEARCH_DOC_PATH"];
|
|
@@ -58431,7 +58725,7 @@ var init_registry2 = __esm(() => {
|
|
|
58431
58725
|
});
|
|
58432
58726
|
|
|
58433
58727
|
// src/workflows/runner.ts
|
|
58434
|
-
import { eq as
|
|
58728
|
+
import { eq as eq11 } from "drizzle-orm";
|
|
58435
58729
|
function workflowClaimsFeatureBranch(workflowId) {
|
|
58436
58730
|
return BRANCH_CLAIMING.has(workflowId);
|
|
58437
58731
|
}
|
|
@@ -58460,7 +58754,7 @@ async function startRun(ctx, featureId, workflowId, opts = {}) {
|
|
|
58460
58754
|
claim(ctx, opts.claimWaypointId, runId);
|
|
58461
58755
|
} catch (e) {
|
|
58462
58756
|
const summary = e instanceof Error ? e.message : "claim failed";
|
|
58463
|
-
ctx.db.update(runs).set({ status: "failed", endedAt: Date.now(), summary }).where(
|
|
58757
|
+
ctx.db.update(runs).set({ status: "failed", endedAt: Date.now(), summary }).where(eq11(runs.id, runId)).run();
|
|
58464
58758
|
throw e;
|
|
58465
58759
|
}
|
|
58466
58760
|
}
|
|
@@ -58510,13 +58804,15 @@ async function startRun(ctx, featureId, workflowId, opts = {}) {
|
|
|
58510
58804
|
});
|
|
58511
58805
|
return { runId, done: done10 };
|
|
58512
58806
|
}
|
|
58513
|
-
function cancelRun(runId) {
|
|
58807
|
+
async function cancelRun(runId) {
|
|
58514
58808
|
controllers.get(runId)?.abort();
|
|
58809
|
+
return await killRegistry().killAllForRun(runId);
|
|
58515
58810
|
}
|
|
58516
58811
|
async function executeRun(ctx, runId, featureId, workflow2, runPromise2, controller, cleanup) {
|
|
58517
58812
|
let status3 = "failed";
|
|
58518
58813
|
let summary = "run failed";
|
|
58519
58814
|
let digest;
|
|
58815
|
+
let threw = false;
|
|
58520
58816
|
try {
|
|
58521
58817
|
const result = await runPromise2;
|
|
58522
58818
|
status3 = result.status;
|
|
@@ -58530,11 +58826,14 @@ async function executeRun(ctx, runId, featureId, workflow2, runPromise2, control
|
|
|
58530
58826
|
status3 = "failed";
|
|
58531
58827
|
summary = e instanceof Error ? e.message : "run failed";
|
|
58532
58828
|
}
|
|
58533
|
-
|
|
58829
|
+
threw = true;
|
|
58534
58830
|
} finally {
|
|
58535
58831
|
controllers.delete(runId);
|
|
58536
58832
|
}
|
|
58537
|
-
|
|
58833
|
+
await killRegistry().whenRunKillsSettled(runId);
|
|
58834
|
+
if (threw)
|
|
58835
|
+
emit(ctx, featureId, { type: "run.error", message: summary, runId });
|
|
58836
|
+
ctx.db.update(runs).set({ status: status3, endedAt: Date.now(), summary, ...digest ? { digest } : {} }).where(eq11(runs.id, runId)).run();
|
|
58538
58837
|
releaseForSession(ctx, runId);
|
|
58539
58838
|
if (workflowClaimsFeatureBranch(workflow2)) {
|
|
58540
58839
|
sweepOrphanedBurning(ctx, featureId, `orphaned \u2014 the run ended (${status3}) while it was burning`);
|
|
@@ -58577,13 +58876,14 @@ var init_runner = __esm(() => {
|
|
|
58577
58876
|
init_review_findings();
|
|
58578
58877
|
init_tickets();
|
|
58579
58878
|
init_waypoints();
|
|
58879
|
+
init_kill_registry();
|
|
58580
58880
|
init_registry2();
|
|
58581
58881
|
controllers = new Map;
|
|
58582
58882
|
BRANCH_CLAIMING = new Set(["ticket-burner"]);
|
|
58583
58883
|
});
|
|
58584
58884
|
|
|
58585
58885
|
// src/services/features.ts
|
|
58586
|
-
import { desc as desc5, eq as
|
|
58886
|
+
import { desc as desc5, eq as eq12 } from "drizzle-orm";
|
|
58587
58887
|
import { rmSync as rmSync4 } from "fs";
|
|
58588
58888
|
async function createFeature(ctx, input) {
|
|
58589
58889
|
const project3 = requireProjectById(ctx, input.projectId);
|
|
@@ -58636,7 +58936,7 @@ async function startDraft(ctx, featureId, opts = {}) {
|
|
|
58636
58936
|
const project3 = projectForFeature(ctx, feature);
|
|
58637
58937
|
const base = await requestedBase(project3, opts.baseBranch);
|
|
58638
58938
|
const { branchReady, baseBranch } = await ensureFeatureBranch(project3, feature.slug, base);
|
|
58639
|
-
ctx.db.update(features).set({ status: "active", baseBranch }).where(
|
|
58939
|
+
ctx.db.update(features).set({ status: "active", baseBranch }).where(eq12(features.id, featureId)).run();
|
|
58640
58940
|
const started = { ...feature, status: "active", baseBranch };
|
|
58641
58941
|
emit(ctx, featureId, {
|
|
58642
58942
|
type: "feature.started",
|
|
@@ -58785,7 +59085,7 @@ function getFeatureFull(ctx, id3) {
|
|
|
58785
59085
|
};
|
|
58786
59086
|
}
|
|
58787
59087
|
function list(ctx, projectId) {
|
|
58788
|
-
const rows = ctx.db.select().from(features).where(
|
|
59088
|
+
const rows = ctx.db.select().from(features).where(eq12(features.projectId, projectId)).orderBy(desc5(features.createdAt)).all();
|
|
58789
59089
|
const lastActivity = latestTsByFeature(ctx, projectId);
|
|
58790
59090
|
return rows.map((row) => {
|
|
58791
59091
|
const feature = rowToFeature(row);
|
|
@@ -58832,6 +59132,13 @@ function advance(ctx, featureId) {
|
|
|
58832
59132
|
throw new GateError("feature is already at the final phase");
|
|
58833
59133
|
return setPhase(ctx, featureId, next4, "phase.advanced");
|
|
58834
59134
|
}
|
|
59135
|
+
function assertTicketsReady(ctx, feature) {
|
|
59136
|
+
if (feature.ticketsReadyLap === feature.lap)
|
|
59137
|
+
return;
|
|
59138
|
+
if (activeSessionsForFeature(ctx, feature.id).length === 0)
|
|
59139
|
+
return;
|
|
59140
|
+
throw new GateError("the session is still finishing the tickets \u2014 it completes the tickets phase when the lap " + "is done, and the burn arms then");
|
|
59141
|
+
}
|
|
58835
59142
|
async function burn(ctx, featureId, opts = {}) {
|
|
58836
59143
|
const feature = getFeatureRow(ctx, featureId);
|
|
58837
59144
|
requireNotDraft(feature);
|
|
@@ -58854,6 +59161,12 @@ async function burn(ctx, featureId, opts = {}) {
|
|
|
58854
59161
|
if (lapTickets.filter((t) => t.status !== "cancelled").length < 1) {
|
|
58855
59162
|
throw new GateError(lapTickets.length > 0 ? "no burnable tickets \u2014 every ticket is cancelled" : "no tickets to burn");
|
|
58856
59163
|
}
|
|
59164
|
+
if (feature.phase === "tickets") {
|
|
59165
|
+
const gate = checkGate(ctx, "tickets-approved", feature);
|
|
59166
|
+
if (!gate.satisfied)
|
|
59167
|
+
throw new GateError(gate.reason ?? "gate G3 not satisfied");
|
|
59168
|
+
assertTicketsReady(ctx, feature);
|
|
59169
|
+
}
|
|
58857
59170
|
if (restarting) {
|
|
58858
59171
|
sweepOrphanedBurning(ctx, featureId, "orphaned \u2014 the previous run died while it was burning");
|
|
58859
59172
|
tickets2 = listByFeature2(ctx, featureId);
|
|
@@ -58903,7 +59216,7 @@ function rethink(ctx, featureId) {
|
|
|
58903
59216
|
throw new GateError(`a ${live[0].kind} session is already live for ${feature.slug} \u2014 only one terminal per feature; end or resume it first`);
|
|
58904
59217
|
}
|
|
58905
59218
|
const lap = feature.lap + 1;
|
|
58906
|
-
ctx.db.update(features).set({ lap }).where(
|
|
59219
|
+
ctx.db.update(features).set({ lap }).where(eq12(features.id, featureId)).run();
|
|
58907
59220
|
return setPhase(ctx, featureId, RETHINK_LOOP_BACK.to, "lap.started", `rethink \u2014 lap ${lap}`);
|
|
58908
59221
|
}
|
|
58909
59222
|
async function rethinkAndLaunch(ctx, featureId, launch3) {
|
|
@@ -58912,7 +59225,7 @@ async function rethinkAndLaunch(ctx, featureId, launch3) {
|
|
|
58912
59225
|
try {
|
|
58913
59226
|
return await launch3(feature);
|
|
58914
59227
|
} catch (e) {
|
|
58915
|
-
ctx.db.update(features).set({ lap: before.lap }).where(
|
|
59228
|
+
ctx.db.update(features).set({ lap: before.lap }).where(eq12(features.id, featureId)).run();
|
|
58916
59229
|
setPhase(ctx, featureId, before.phase, "lap.aborted", `lap ${feature.lap} aborted \u2014 its terminal could not be opened (${errMsg2(e)}); back at ${before.phase} on lap ${before.lap}`);
|
|
58917
59230
|
throw e;
|
|
58918
59231
|
}
|
|
@@ -58991,7 +59304,7 @@ function escalateToMap(ctx, featureId, input) {
|
|
|
58991
59304
|
return { ok: true, warning: `feature ${feature.slug} is already mapped \u2014 no changes made` };
|
|
58992
59305
|
}
|
|
58993
59306
|
const project3 = projectForFeature(ctx, feature);
|
|
58994
|
-
ctx.db.update(features).set({ mapped: true }).where(
|
|
59307
|
+
ctx.db.update(features).set({ mapped: true }).where(eq12(features.id, featureId)).run();
|
|
58995
59308
|
scaffoldMapDoc(project3, { ...feature, mapped: true }, input);
|
|
58996
59309
|
emit(ctx, featureId, {
|
|
58997
59310
|
type: "feature.escalated",
|
|
@@ -59009,7 +59322,7 @@ function archiveFeature(ctx, featureId) {
|
|
|
59009
59322
|
const live = listSessionsByFeature(ctx, featureId).find((s) => s.status === "live");
|
|
59010
59323
|
if (live)
|
|
59011
59324
|
endSession(ctx, live.id);
|
|
59012
|
-
ctx.db.update(features).set({ status: "archived" }).where(
|
|
59325
|
+
ctx.db.update(features).set({ status: "archived" }).where(eq12(features.id, featureId)).run();
|
|
59013
59326
|
emit(ctx, featureId, {
|
|
59014
59327
|
type: "feature.archived",
|
|
59015
59328
|
message: `feature ${feature.slug} archived`,
|
|
@@ -59023,7 +59336,7 @@ function unarchiveFeature(ctx, featureId) {
|
|
|
59023
59336
|
throw new GateError(`feature ${feature.slug} is not archived`);
|
|
59024
59337
|
}
|
|
59025
59338
|
const restored = feature.phase === "shipped" ? "shipped" : "active";
|
|
59026
|
-
ctx.db.update(features).set({ status: restored }).where(
|
|
59339
|
+
ctx.db.update(features).set({ status: restored }).where(eq12(features.id, featureId)).run();
|
|
59027
59340
|
emit(ctx, featureId, {
|
|
59028
59341
|
type: "feature.unarchived",
|
|
59029
59342
|
message: `feature ${feature.slug} unarchived (${restored})`,
|
|
@@ -59039,7 +59352,7 @@ async function deleteFeature(ctx, featureId) {
|
|
|
59039
59352
|
const project3 = projectForFeature(ctx, feature);
|
|
59040
59353
|
for (const run7 of listRunsByFeature(ctx, featureId)) {
|
|
59041
59354
|
if (run7.status === "running")
|
|
59042
|
-
cancelRun(run7.id);
|
|
59355
|
+
await cancelRun(run7.id);
|
|
59043
59356
|
}
|
|
59044
59357
|
const live = listSessionsByFeature(ctx, featureId).find((s) => s.status === "live");
|
|
59045
59358
|
if (live)
|
|
@@ -59063,13 +59376,13 @@ async function deleteFeature(ctx, featureId) {
|
|
|
59063
59376
|
return { ok: true, slug: feature.slug };
|
|
59064
59377
|
}
|
|
59065
59378
|
function deleteFeatureRows(ctx, featureId) {
|
|
59066
|
-
ctx.db.delete(tickets).where(
|
|
59067
|
-
ctx.db.delete(sessions).where(
|
|
59068
|
-
ctx.db.delete(runs).where(
|
|
59069
|
-
ctx.db.delete(events).where(
|
|
59070
|
-
ctx.db.delete(gateOverrides).where(
|
|
59071
|
-
ctx.db.delete(waypoints).where(
|
|
59072
|
-
ctx.db.delete(features).where(
|
|
59379
|
+
ctx.db.delete(tickets).where(eq12(tickets.featureId, featureId)).run();
|
|
59380
|
+
ctx.db.delete(sessions).where(eq12(sessions.featureId, featureId)).run();
|
|
59381
|
+
ctx.db.delete(runs).where(eq12(runs.featureId, featureId)).run();
|
|
59382
|
+
ctx.db.delete(events).where(eq12(events.featureId, featureId)).run();
|
|
59383
|
+
ctx.db.delete(gateOverrides).where(eq12(gateOverrides.featureId, featureId)).run();
|
|
59384
|
+
ctx.db.delete(waypoints).where(eq12(waypoints.featureId, featureId)).run();
|
|
59385
|
+
ctx.db.delete(features).where(eq12(features.id, featureId)).run();
|
|
59073
59386
|
}
|
|
59074
59387
|
function gateState(ctx, feature) {
|
|
59075
59388
|
const gate = nextGate(feature);
|
|
@@ -59080,7 +59393,7 @@ function gateState(ctx, feature) {
|
|
|
59080
59393
|
}
|
|
59081
59394
|
function uniqueSlug(ctx, projectId, title) {
|
|
59082
59395
|
const base = slugify(title);
|
|
59083
|
-
const existing = new Set(ctx.db.select({ slug: features.slug }).from(features).where(
|
|
59396
|
+
const existing = new Set(ctx.db.select({ slug: features.slug }).from(features).where(eq12(features.projectId, projectId)).all().map((r) => r.slug));
|
|
59084
59397
|
if (!existing.has(base))
|
|
59085
59398
|
return base;
|
|
59086
59399
|
let n = 2;
|
|
@@ -59113,7 +59426,7 @@ var init_features = __esm(() => {
|
|
|
59113
59426
|
// src/services/test-notes.ts
|
|
59114
59427
|
import { existsSync as existsSync19, mkdirSync as mkdirSync12, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
59115
59428
|
import { dirname as dirname12 } from "path";
|
|
59116
|
-
import { and as
|
|
59429
|
+
import { and as and10, asc as asc5, eq as eq13, lt } from "drizzle-orm";
|
|
59117
59430
|
function rowToNote(row) {
|
|
59118
59431
|
return TestNote.parse({
|
|
59119
59432
|
id: row.id,
|
|
@@ -59132,7 +59445,7 @@ function rowToNote(row) {
|
|
|
59132
59445
|
});
|
|
59133
59446
|
}
|
|
59134
59447
|
function getNote(ctx, id3) {
|
|
59135
|
-
const row = ctx.db.select().from(testNotes).where(
|
|
59448
|
+
const row = ctx.db.select().from(testNotes).where(eq13(testNotes.id, id3)).get();
|
|
59136
59449
|
if (!row)
|
|
59137
59450
|
throw new NotFoundError(`test note ${id3} not found`);
|
|
59138
59451
|
return rowToNote(row);
|
|
@@ -59149,7 +59462,7 @@ function assertOpen(note2, action) {
|
|
|
59149
59462
|
}
|
|
59150
59463
|
}
|
|
59151
59464
|
function listByFeature4(ctx, featureId) {
|
|
59152
|
-
return ctx.db.select().from(testNotes).where(
|
|
59465
|
+
return ctx.db.select().from(testNotes).where(eq13(testNotes.featureId, featureId)).orderBy(asc5(testNotes.createdAt), asc5(testNotes.id)).all().map(rowToNote);
|
|
59153
59466
|
}
|
|
59154
59467
|
function addNote(ctx, featureId, text3, author = "human", videoTimestamp, reviewTicketId) {
|
|
59155
59468
|
const body = cleanText(text3);
|
|
@@ -59187,7 +59500,7 @@ function editNote(ctx, noteId, text3) {
|
|
|
59187
59500
|
const current2 = getNote(ctx, noteId);
|
|
59188
59501
|
assertOpen(current2, "edit");
|
|
59189
59502
|
const body = cleanText(text3);
|
|
59190
|
-
ctx.db.update(testNotes).set({ text: body, updatedAt: Date.now() }).where(
|
|
59503
|
+
ctx.db.update(testNotes).set({ text: body, updatedAt: Date.now() }).where(eq13(testNotes.id, noteId)).run();
|
|
59191
59504
|
emit(ctx, current2.featureId, {
|
|
59192
59505
|
type: "note.edited",
|
|
59193
59506
|
message: "note edited",
|
|
@@ -59199,7 +59512,7 @@ function editNote(ctx, noteId, text3) {
|
|
|
59199
59512
|
function deleteNote(ctx, noteId) {
|
|
59200
59513
|
const current2 = getNote(ctx, noteId);
|
|
59201
59514
|
assertOpen(current2, "delete");
|
|
59202
|
-
ctx.db.delete(testNotes).where(
|
|
59515
|
+
ctx.db.delete(testNotes).where(eq13(testNotes.id, noteId)).run();
|
|
59203
59516
|
rmSync5(annotationPath(noteId), { force: true });
|
|
59204
59517
|
emit(ctx, current2.featureId, {
|
|
59205
59518
|
type: "note.deleted",
|
|
@@ -59226,7 +59539,7 @@ function toggleNote(ctx, noteId) {
|
|
|
59226
59539
|
throw new InvalidInputError(`cannot toggle note ${noteId} \u2014 it is promoted, and promoted notes are frozen`);
|
|
59227
59540
|
}
|
|
59228
59541
|
const status3 = current2.status === "open" ? "done" : "open";
|
|
59229
|
-
ctx.db.update(testNotes).set({ status: status3, updatedAt: Date.now() }).where(
|
|
59542
|
+
ctx.db.update(testNotes).set({ status: status3, updatedAt: Date.now() }).where(eq13(testNotes.id, noteId)).run();
|
|
59230
59543
|
emit(ctx, current2.featureId, {
|
|
59231
59544
|
type: "note.toggled",
|
|
59232
59545
|
message: `note ${current2.status} \u2192 ${status3}`,
|
|
@@ -59274,7 +59587,7 @@ function freezeAsTickets(ctx, noteIds) {
|
|
|
59274
59587
|
const tickets2 = storeTickets(ctx, featureId, selected.map((note2) => promotionTicket(feature, note2)));
|
|
59275
59588
|
const now = Date.now();
|
|
59276
59589
|
selected.forEach((note2, i) => {
|
|
59277
|
-
ctx.db.update(testNotes).set({ status: "promoted", ticketId: tickets2[i].id, updatedAt: now }).where(
|
|
59590
|
+
ctx.db.update(testNotes).set({ status: "promoted", ticketId: tickets2[i].id, updatedAt: now }).where(eq13(testNotes.id, note2.id)).run();
|
|
59278
59591
|
});
|
|
59279
59592
|
return { feature, notes: selected.map((n) => getNote(ctx, n.id)), tickets: tickets2 };
|
|
59280
59593
|
}
|
|
@@ -59319,7 +59632,7 @@ function carryNotes(ctx, featureId, noteIds, lap) {
|
|
|
59319
59632
|
if (note2.featureId !== featureId)
|
|
59320
59633
|
throw new InvalidInputError("every carried note must belong to the feature");
|
|
59321
59634
|
assertOpen(note2, "carry");
|
|
59322
|
-
ctx.db.update(testNotes).set({ status: "carried", carriedLap: lap, updatedAt: Date.now() }).where(
|
|
59635
|
+
ctx.db.update(testNotes).set({ status: "carried", carriedLap: lap, updatedAt: Date.now() }).where(eq13(testNotes.id, note2.id)).run();
|
|
59323
59636
|
emit(ctx, featureId, { type: "note.carried", message: `note carried into lap ${lap}`, data: { noteId: note2.id, lap } });
|
|
59324
59637
|
}
|
|
59325
59638
|
renderTestNotes(ctx, getFeatureRow(ctx, featureId));
|
|
@@ -59329,7 +59642,7 @@ function reopenNote(ctx, noteId) {
|
|
|
59329
59642
|
const note2 = getNote(ctx, noteId);
|
|
59330
59643
|
if (note2.status !== "carried")
|
|
59331
59644
|
throw new InvalidInputError("only a carried note can be reopened");
|
|
59332
|
-
ctx.db.update(testNotes).set({ status: "open", carriedLap: null, updatedAt: Date.now() }).where(
|
|
59645
|
+
ctx.db.update(testNotes).set({ status: "open", carriedLap: null, updatedAt: Date.now() }).where(eq13(testNotes.id, noteId)).run();
|
|
59333
59646
|
emit(ctx, note2.featureId, { type: "note.reopened", message: "note reopened", data: { noteId } });
|
|
59334
59647
|
renderTestNotes(ctx, getFeatureRow(ctx, note2.featureId));
|
|
59335
59648
|
return getNote(ctx, noteId);
|
|
@@ -59348,7 +59661,7 @@ function triageNotes(ctx, featureId, input) {
|
|
|
59348
59661
|
}
|
|
59349
59662
|
function triagePreview(ctx, featureId) {
|
|
59350
59663
|
const feature = getFeatureRow(ctx, featureId);
|
|
59351
|
-
const standing = ctx.db.select().from(tickets).where(
|
|
59664
|
+
const standing = ctx.db.select().from(tickets).where(and10(eq13(tickets.featureId, featureId), eq13(tickets.kind, "implementation"), eq13(tickets.status, "pending"), lt(tickets.lap, feature.lap))).all();
|
|
59352
59665
|
const grouped = new Map;
|
|
59353
59666
|
for (const ticket of standing)
|
|
59354
59667
|
grouped.set(ticket.lap, (grouped.get(ticket.lap) ?? 0) + 1);
|
|
@@ -59358,10 +59671,19 @@ function triagePreview(ctx, featureId) {
|
|
|
59358
59671
|
standingFixTickets: [...grouped].sort(([a], [b]) => a - b).map(([lap, count3]) => ({ count: count3, lap }))
|
|
59359
59672
|
};
|
|
59360
59673
|
}
|
|
59674
|
+
function carriedLine(note2) {
|
|
59675
|
+
return `- [\u2192] ${note2.text} (captured lap ${note2.lap}, carried into lap ${note2.carriedLap})${screenshotSuffix(note2)}`;
|
|
59676
|
+
}
|
|
59361
59677
|
function renderTestNotes(ctx, feature) {
|
|
59362
59678
|
const notes = listByFeature4(ctx, feature.id);
|
|
59363
59679
|
const laps = [...new Set(notes.map((n) => n.lap))].sort((a, b) => a - b);
|
|
59364
59680
|
const lines = ["# Test notes"];
|
|
59681
|
+
const carried = notes.filter((note2) => note2.status === "carried");
|
|
59682
|
+
if (carried.length) {
|
|
59683
|
+
lines.push("", "## Carried, still open", "");
|
|
59684
|
+
for (const note2 of carried)
|
|
59685
|
+
lines.push(carriedLine(note2));
|
|
59686
|
+
}
|
|
59365
59687
|
for (const lap of laps) {
|
|
59366
59688
|
lines.push("", `## Lap ${lap}`, "");
|
|
59367
59689
|
for (const note2 of notes.filter((n) => n.lap === lap))
|
|
@@ -59441,6 +59763,8 @@ function resolveFeatureReader(ctx, caller) {
|
|
|
59441
59763
|
}
|
|
59442
59764
|
function featureContext(ctx, reader) {
|
|
59443
59765
|
const feature = getFeatureRow(ctx, reader.featureId);
|
|
59766
|
+
const carried = carriedWork(ctx, feature.id);
|
|
59767
|
+
const withheldDocs = withheldFeatureDocs({ carriedNotesOpen: carried.carriedNotes > 0 });
|
|
59444
59768
|
const docs2 = [];
|
|
59445
59769
|
const moreDocs = [];
|
|
59446
59770
|
for (const summary of listDocs(ctx, feature)) {
|
|
@@ -59454,7 +59778,7 @@ function featureContext(ctx, reader) {
|
|
|
59454
59778
|
docs2.push({ relPath: summary.relPath, content });
|
|
59455
59779
|
continue;
|
|
59456
59780
|
}
|
|
59457
|
-
const withheld =
|
|
59781
|
+
const withheld = withheldDocs[summary.relPath.toLowerCase()];
|
|
59458
59782
|
moreDocs.push({
|
|
59459
59783
|
relPath: summary.relPath,
|
|
59460
59784
|
title: summary.title,
|
|
@@ -59470,6 +59794,7 @@ function featureContext(ctx, reader) {
|
|
|
59470
59794
|
docs: docs2,
|
|
59471
59795
|
moreDocs,
|
|
59472
59796
|
docsNote: DOCS_NOTE,
|
|
59797
|
+
openDefects: carried.openDefects,
|
|
59473
59798
|
tickets: listByFeature2(ctx, feature.id).map(stripDigest),
|
|
59474
59799
|
annotatedModels: annotatedModels(ctx)
|
|
59475
59800
|
};
|
|
@@ -59516,9 +59841,16 @@ function toolResolveWaypoint(ctx, session, input) {
|
|
|
59516
59841
|
frontierIds: after3.map((w) => w.id)
|
|
59517
59842
|
};
|
|
59518
59843
|
}
|
|
59844
|
+
function refuseMisKindedReview(inputs) {
|
|
59845
|
+
const misKinded = inputs.find((t) => REVIEW_TITLE.test(t.title) && t.kind !== "review");
|
|
59846
|
+
if (!misKinded)
|
|
59847
|
+
return;
|
|
59848
|
+
throw new GateError(`ticket "${misKinded.title}" is titled like a review ticket but its kind is ` + `"${misKinded.kind ?? "implementation"}" \u2014 set kind: "review" if it is the batch-closing ` + "review (almost certainly), or retitle it if it genuinely is an implementation ticket. " + "Nothing was stored; fix it and re-emit the batch.");
|
|
59849
|
+
}
|
|
59519
59850
|
function toolEmitTickets(ctx, session, input) {
|
|
59520
59851
|
const feature = getFeatureRow(ctx, requireFeatureId(session));
|
|
59521
59852
|
refuseIfReadOnly(session, "emitting tickets");
|
|
59853
|
+
refuseMisKindedReview(input.tickets);
|
|
59522
59854
|
const stored = storeTickets(ctx, feature.id, input.tickets);
|
|
59523
59855
|
return {
|
|
59524
59856
|
stored: stored.length,
|
|
@@ -59580,14 +59912,26 @@ function toolCompletePhase(ctx, session, input) {
|
|
|
59580
59912
|
message: `session marked phase '${input.phase}' complete`,
|
|
59581
59913
|
data: { phase: input.phase, currentPhase: feature.phase }
|
|
59582
59914
|
});
|
|
59915
|
+
if (input.phase === "tickets" && isPastPhase(feature, "tickets")) {
|
|
59916
|
+
return {
|
|
59917
|
+
ok: true,
|
|
59918
|
+
nextPhase: feature.phase,
|
|
59919
|
+
note: "tickets phase already crossed \u2014 the burn has started; nothing left to complete"
|
|
59920
|
+
};
|
|
59921
|
+
}
|
|
59583
59922
|
const gate = nextGate(feature);
|
|
59584
59923
|
if (gate?.id === "G3") {
|
|
59924
|
+
const result = checkGate(ctx, gate.check, feature);
|
|
59925
|
+
if (!result.satisfied) {
|
|
59926
|
+
const failed = requirement(gate);
|
|
59927
|
+
return {
|
|
59928
|
+
ok: false,
|
|
59929
|
+
reason: result.reason ?? `gate ${gate.id} not satisfied`,
|
|
59930
|
+
...failed ? { gate: failed } : {}
|
|
59931
|
+
};
|
|
59932
|
+
}
|
|
59585
59933
|
const next4 = nextPhase(feature) ?? "implementation";
|
|
59586
|
-
|
|
59587
|
-
type: "tickets.awaiting_burn",
|
|
59588
|
-
message: "tickets complete \u2014 waiting on the human Burn click (gate G3)",
|
|
59589
|
-
data: { phase: feature.phase, nextPhase: next4, waitingOn: "human burn" }
|
|
59590
|
-
});
|
|
59934
|
+
markTicketsReady(ctx, feature.id, next4);
|
|
59591
59935
|
const after3 = requirement(nextGate({ ...feature, phase: next4 }));
|
|
59592
59936
|
return {
|
|
59593
59937
|
ok: true,
|
|
@@ -60235,7 +60579,7 @@ function buildMcpServer(audience) {
|
|
|
60235
60579
|
if (wants("complete_phase")) {
|
|
60236
60580
|
server.registerTool("complete_phase", {
|
|
60237
60581
|
title: "Complete phase",
|
|
60238
|
-
description: "Mark the named phase complete. Runs the gate check server-side and advances the " + "feature, or returns { ok: false, reason, gate } naming what the gate wanted. On " + "success it also returns `nextGate` \u2014 what the FOLLOWING gate requires \u2014 so the next " + 'step is not a guess. The tickets \u2192 implementation gate (G3) is the human "Burn" ' + "approval: completing the tickets phase records the work done and returns " + '{ ok: true, nextPhase: "implementation", waitingOn: "human burn" } WITHOUT advancing.',
|
|
60582
|
+
description: "Mark the named phase complete. Runs the gate check server-side and advances the " + "feature, or returns { ok: false, reason, gate } naming what the gate wanted. On " + "success it also returns `nextGate` \u2014 what the FOLLOWING gate requires \u2014 so the next " + 'step is not a guess. The tickets \u2192 implementation gate (G3) is the human "Burn" ' + "approval: completing the tickets phase records the work done and returns " + '{ ok: true, nextPhase: "implementation", waitingOn: "human burn" } WITHOUT advancing. ' + "That call is also what ARMS the Burn click \u2014 until it lands, the button waits on you " + "rather than burning tickets you are still writing, so make it the last thing you do " + "in the tickets phase. If the human burned while you were closing out, it comes back " + "{ ok: true, note } saying the phase was already crossed: nothing left to do.",
|
|
60239
60583
|
inputSchema: {
|
|
60240
60584
|
phase: CompletablePhase.describe("The phase you are finishing \u2014 normally the feature\u2019s current one. `shipped` is not " + "completable by any session: a feature ships when the human merges it.")
|
|
60241
60585
|
}
|
|
@@ -60262,7 +60606,7 @@ async function resolveAudience(sessionId, runId) {
|
|
|
60262
60606
|
return;
|
|
60263
60607
|
}
|
|
60264
60608
|
}
|
|
60265
|
-
var DOCS_NOTE, DRAFTING_KINDS, ADRS_NOTE, FEATURE_KINDS, FEATURE_WRITE_KINDS, PROJECT_KINDS, ALL_AUDIENCES, TOOL_AUDIENCES, CompletablePhase, EventType, mcp, server_default;
|
|
60609
|
+
var DOCS_NOTE, REVIEW_TITLE, DRAFTING_KINDS, ADRS_NOTE, FEATURE_KINDS, FEATURE_WRITE_KINDS, PROJECT_KINDS, ALL_AUDIENCES, TOOL_AUDIENCES, CompletablePhase, EventType, mcp, server_default;
|
|
60266
60610
|
var init_server = __esm(() => {
|
|
60267
60611
|
init_src();
|
|
60268
60612
|
init_paths();
|
|
@@ -60270,8 +60614,10 @@ var init_server = __esm(() => {
|
|
|
60270
60614
|
init_artifacts();
|
|
60271
60615
|
init_sessions();
|
|
60272
60616
|
init_features();
|
|
60617
|
+
init_carried_work();
|
|
60273
60618
|
init_events();
|
|
60274
60619
|
init_findings();
|
|
60620
|
+
init_gates();
|
|
60275
60621
|
init_review_findings();
|
|
60276
60622
|
init_git();
|
|
60277
60623
|
init_knowledge();
|
|
@@ -60280,6 +60626,7 @@ var init_server = __esm(() => {
|
|
|
60280
60626
|
init_tickets();
|
|
60281
60627
|
init_waypoints();
|
|
60282
60628
|
DOCS_NOTE = "docs[] holds this feature\u2019s canonical docs in full. moreDocs[] is everything else that " + "exists in docs/features/<slug>/ \u2014 not inlined, not gone: read any of them with " + "read_feature_doc({ relPath }). A `withheld` reason means it was left out on purpose; fetch " + "it anyway if a ticket points at it.";
|
|
60629
|
+
REVIEW_TITLE = /^review[: ]/i;
|
|
60283
60630
|
DRAFTING_KINDS = ["ideation", "revisit", "waypoint", "converge"];
|
|
60284
60631
|
ADRS_NOTE = "adrs[] is an index of the project\u2019s LIVE decisions (superseded ones are already omitted), not " + "their text. Read the ones your work touches with read_adr({ relPath }) \u2014 they bind you the " + "same either way. They are also plain files in this worktree at docs/adr/.";
|
|
60285
60632
|
FEATURE_KINDS = [
|
|
@@ -60403,7 +60750,7 @@ var init_ws = __esm(() => {
|
|
|
60403
60750
|
});
|
|
60404
60751
|
|
|
60405
60752
|
// src/services/prep.ts
|
|
60406
|
-
import { and as
|
|
60753
|
+
import { and as and11, desc as desc6, eq as eq14, inArray as inArray5 } from "drizzle-orm";
|
|
60407
60754
|
function keysToPrepare(ctx, project3) {
|
|
60408
60755
|
return unsetPreparedKeys(project3).filter((key) => isOverwritable(ctx, project3.id, key));
|
|
60409
60756
|
}
|
|
@@ -60413,10 +60760,10 @@ function isPrepared(ctx, project3) {
|
|
|
60413
60760
|
return hasCompletedProjectSession(ctx, project3.id, "prepare");
|
|
60414
60761
|
}
|
|
60415
60762
|
function preparedAt(ctx, projectId) {
|
|
60416
|
-
const prepareSessions = new Set(ctx.db.select({ id: sessions.id }).from(sessions).where(
|
|
60763
|
+
const prepareSessions = new Set(ctx.db.select({ id: sessions.id }).from(sessions).where(and11(eq14(sessions.projectId, projectId), eq14(sessions.kind, "prepare"))).all().map((r) => r.id));
|
|
60417
60764
|
if (prepareSessions.size === 0)
|
|
60418
60765
|
return null;
|
|
60419
|
-
const ends = ctx.db.select({ ts: events.ts, data: events.data }).from(events).where(
|
|
60766
|
+
const ends = ctx.db.select({ ts: events.ts, data: events.data }).from(events).where(and11(eq14(events.projectId, projectId), inArray5(events.type, ["session.ended", "session.reconciled"]))).orderBy(desc6(events.ts)).all();
|
|
60420
60767
|
for (const end5 of ends) {
|
|
60421
60768
|
const sessionId = end5.data?.sessionId;
|
|
60422
60769
|
if (sessionId && prepareSessions.has(sessionId))
|
|
@@ -61174,7 +61521,7 @@ var init_events2 = __esm(() => {
|
|
|
61174
61521
|
// src/launcher/launcher.ts
|
|
61175
61522
|
import { existsSync as existsSync23 } from "fs";
|
|
61176
61523
|
import { join as join23 } from "path";
|
|
61177
|
-
import { and as
|
|
61524
|
+
import { and as and12, eq as eq15 } from "drizzle-orm";
|
|
61178
61525
|
function ptyExitMessage(exitCode) {
|
|
61179
61526
|
const label = typeof exitCode === "number" ? String(exitCode) : "unknown";
|
|
61180
61527
|
return `terminal exited (code ${label})`;
|
|
@@ -61194,7 +61541,7 @@ function renderCommand(runtime5, spec) {
|
|
|
61194
61541
|
return [...env, runtime5.binary, ...spec.argv].join(" ");
|
|
61195
61542
|
}
|
|
61196
61543
|
function activeRunFor(ctx, featureId) {
|
|
61197
|
-
const row = ctx.db.select().from(runs).where(
|
|
61544
|
+
const row = ctx.db.select().from(runs).where(and12(eq15(runs.featureId, featureId), eq15(runs.status, "running"))).limit(1).get();
|
|
61198
61545
|
return row ? rowToRun(row) : null;
|
|
61199
61546
|
}
|
|
61200
61547
|
function assertSpawnable(ctx, feature, excludeSessionId) {
|
|
@@ -61275,6 +61622,7 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
61275
61622
|
worktreePath,
|
|
61276
61623
|
model
|
|
61277
61624
|
});
|
|
61625
|
+
const carried = carriedWork(ctx, feature.id);
|
|
61278
61626
|
const plan = planKickoff({
|
|
61279
61627
|
kind: input.kind,
|
|
61280
61628
|
lap: feature.lap,
|
|
@@ -61283,7 +61631,8 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
61283
61631
|
lap: feature.lap,
|
|
61284
61632
|
phase: feature.phase,
|
|
61285
61633
|
ticketLaps: listByFeature2(ctx, feature.id).map((t2) => t2.lap)
|
|
61286
|
-
})
|
|
61634
|
+
}),
|
|
61635
|
+
carried
|
|
61287
61636
|
});
|
|
61288
61637
|
let waypoint;
|
|
61289
61638
|
let resumeSessionId;
|
|
@@ -61380,6 +61729,7 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
61380
61729
|
config: ctx.config,
|
|
61381
61730
|
waypoint,
|
|
61382
61731
|
lap: plan.lap,
|
|
61732
|
+
carried: plan.lap === undefined ? undefined : carried,
|
|
61383
61733
|
purpose: input.purpose,
|
|
61384
61734
|
worktreePath,
|
|
61385
61735
|
serverUrl: serverUrlFor(ctx.config),
|
|
@@ -61801,6 +62151,7 @@ var init_launcher = __esm(() => {
|
|
|
61801
62151
|
init_errors();
|
|
61802
62152
|
init_end_session();
|
|
61803
62153
|
init_registry();
|
|
62154
|
+
init_carried_work();
|
|
61804
62155
|
init_docs_watch();
|
|
61805
62156
|
init_events();
|
|
61806
62157
|
init_gates();
|
|
@@ -61967,6 +62318,7 @@ var init_feature = __esm(() => {
|
|
|
61967
62318
|
init_src();
|
|
61968
62319
|
init_launcher();
|
|
61969
62320
|
init_sessions();
|
|
62321
|
+
init_carried_work();
|
|
61970
62322
|
init_events();
|
|
61971
62323
|
init_features();
|
|
61972
62324
|
init_gates();
|
|
@@ -62006,7 +62358,7 @@ var init_feature = __esm(() => {
|
|
|
62006
62358
|
rethink: publicProcedure.input(z7.object({ featureId: z7.string() })).mutation(({ ctx, input }) => rethinkAndLaunch(ctx, input.featureId, (feature) => launchSession(ctx, {
|
|
62007
62359
|
featureId: input.featureId,
|
|
62008
62360
|
kind: "revisit",
|
|
62009
|
-
kickoffLine: lapKickoff(feature.lap)
|
|
62361
|
+
kickoffLine: lapKickoff(feature.lap, carriedWork(ctx, input.featureId))
|
|
62010
62362
|
}))),
|
|
62011
62363
|
resendKickoff: publicProcedure.input(z7.object({ sessionId: z7.string() })).mutation(({ ctx, input }) => resendKickoff(ctx, input.sessionId)),
|
|
62012
62364
|
endSession: publicProcedure.input(z7.object({ sessionId: z7.string() })).mutation(({ ctx, input }) => endSession(ctx, input.sessionId)),
|
|
@@ -62397,7 +62749,7 @@ var init_conversations = __esm(() => {
|
|
|
62397
62749
|
// src/services/projects.ts
|
|
62398
62750
|
import { existsSync as existsSync25 } from "fs";
|
|
62399
62751
|
import { basename as basename4, join as join27 } from "path";
|
|
62400
|
-
import { eq as
|
|
62752
|
+
import { eq as eq16, isNull } from "drizzle-orm";
|
|
62401
62753
|
function listProjects(ctx) {
|
|
62402
62754
|
return ctx.db.select().from(projects).where(isNull(projects.closedAt)).all().map(rowToProject);
|
|
62403
62755
|
}
|
|
@@ -62418,7 +62770,7 @@ async function openProject(ctx, rawPath) {
|
|
|
62418
62770
|
const name = basename4(repoPath) || repoPath;
|
|
62419
62771
|
const existing = getProjectByRepoPath(ctx, repoPath) ?? findProjectByCanonPath(ctx, repoPath);
|
|
62420
62772
|
if (existing) {
|
|
62421
|
-
ctx.db.update(projects).set({ repoPath, closedAt: null }).where(
|
|
62773
|
+
ctx.db.update(projects).set({ repoPath, closedAt: null }).where(eq16(projects.id, existing.id)).run();
|
|
62422
62774
|
const reopened = { ...existing, repoPath, closedAt: undefined };
|
|
62423
62775
|
emitProject(ctx, existing.id, {
|
|
62424
62776
|
type: "project.opened",
|
|
@@ -62448,7 +62800,7 @@ function closeProject(ctx, projectId) {
|
|
|
62448
62800
|
if (projectHasActiveRun(ctx, projectId)) {
|
|
62449
62801
|
throw new InvalidInputError(`cannot close ${project3.name}: a run is in flight \u2014 wait for it to finish or cancel it first`);
|
|
62450
62802
|
}
|
|
62451
|
-
ctx.db.update(projects).set({ closedAt: Date.now() }).where(
|
|
62803
|
+
ctx.db.update(projects).set({ closedAt: Date.now() }).where(eq16(projects.id, projectId)).run();
|
|
62452
62804
|
emitProject(ctx, projectId, {
|
|
62453
62805
|
type: "project.closed",
|
|
62454
62806
|
message: `project ${project3.name} closed`
|
|
@@ -62457,7 +62809,7 @@ function closeProject(ctx, projectId) {
|
|
|
62457
62809
|
}
|
|
62458
62810
|
function renameProject(ctx, projectId, name) {
|
|
62459
62811
|
const project3 = requireProjectById(ctx, projectId);
|
|
62460
|
-
ctx.db.update(projects).set({ name }).where(
|
|
62812
|
+
ctx.db.update(projects).set({ name }).where(eq16(projects.id, projectId)).run();
|
|
62461
62813
|
emitProject(ctx, projectId, {
|
|
62462
62814
|
type: "project.renamed",
|
|
62463
62815
|
message: `project renamed ${project3.name} \u2192 ${name}`,
|
|
@@ -62549,9 +62901,9 @@ var init_review_findings2 = __esm(() => {
|
|
|
62549
62901
|
});
|
|
62550
62902
|
|
|
62551
62903
|
// src/services/runs.ts
|
|
62552
|
-
import { and as
|
|
62904
|
+
import { and as and13, asc as asc6, eq as eq17, isNotNull as isNotNull3 } from "drizzle-orm";
|
|
62553
62905
|
function runTicketIds(ctx, runId) {
|
|
62554
|
-
const rows = ctx.db.select({ ticketId: events.ticketId }).from(events).where(
|
|
62906
|
+
const rows = ctx.db.select({ ticketId: events.ticketId }).from(events).where(and13(eq17(events.runId, runId), isNotNull3(events.ticketId))).orderBy(asc6(events.id)).all();
|
|
62555
62907
|
const seen = new Set;
|
|
62556
62908
|
for (const row of rows)
|
|
62557
62909
|
if (row.ticketId)
|
|
@@ -62559,7 +62911,7 @@ function runTicketIds(ctx, runId) {
|
|
|
62559
62911
|
return [...seen];
|
|
62560
62912
|
}
|
|
62561
62913
|
function runLap(ctx, runId) {
|
|
62562
|
-
const row = ctx.db.select({ lap: events.lap }).from(events).where(
|
|
62914
|
+
const row = ctx.db.select({ lap: events.lap }).from(events).where(eq17(events.runId, runId)).orderBy(asc6(events.id)).limit(1).get();
|
|
62563
62915
|
return row?.lap ?? 1;
|
|
62564
62916
|
}
|
|
62565
62917
|
function listRunSummaries(ctx, featureId) {
|
|
@@ -62587,6 +62939,7 @@ import * as z10 from "zod";
|
|
|
62587
62939
|
var runRouter;
|
|
62588
62940
|
var init_run = __esm(() => {
|
|
62589
62941
|
init_agent_stream();
|
|
62942
|
+
init_events();
|
|
62590
62943
|
init_repo();
|
|
62591
62944
|
init_runs();
|
|
62592
62945
|
init_runner();
|
|
@@ -62595,10 +62948,17 @@ var init_run = __esm(() => {
|
|
|
62595
62948
|
get: publicProcedure.input(z10.object({ runId: z10.string() })).query(({ ctx, input }) => getRunWithTickets(ctx, input.runId)),
|
|
62596
62949
|
listByFeature: publicProcedure.input(z10.object({ featureId: z10.string() })).query(({ ctx, input }) => listRunSummaries(ctx, input.featureId)),
|
|
62597
62950
|
agentTranscript: publicProcedure.input(z10.object({ ticketId: z10.string(), after: z10.number().int().optional() })).query(({ input }) => readTranscript(input.ticketId, input.after ?? -1)),
|
|
62598
|
-
cancel: publicProcedure.input(z10.object({ runId: z10.string() })).mutation(({ ctx, input }) => {
|
|
62599
|
-
getRunRow(ctx, input.runId);
|
|
62600
|
-
cancelRun(input.runId);
|
|
62601
|
-
|
|
62951
|
+
cancel: publicProcedure.input(z10.object({ runId: z10.string() })).mutation(async ({ ctx, input }) => {
|
|
62952
|
+
const run7 = getRunRow(ctx, input.runId);
|
|
62953
|
+
const { confirmed } = await cancelRun(input.runId);
|
|
62954
|
+
if (!confirmed) {
|
|
62955
|
+
emit(ctx, run7.featureId, {
|
|
62956
|
+
type: "run.cancel_timeout",
|
|
62957
|
+
message: "cancel timed out \u2014 an agent of this run may still be running",
|
|
62958
|
+
runId: run7.id
|
|
62959
|
+
});
|
|
62960
|
+
}
|
|
62961
|
+
return { ok: true, confirmed };
|
|
62602
62962
|
})
|
|
62603
62963
|
});
|
|
62604
62964
|
});
|
|
@@ -62776,8 +63136,17 @@ var init_test_notes2 = __esm(() => {
|
|
|
62776
63136
|
|
|
62777
63137
|
// src/trpc/routers/ticket.ts
|
|
62778
63138
|
import * as z15 from "zod";
|
|
63139
|
+
function noteStopTimeout(ctx, ticketId) {
|
|
63140
|
+
const ticket = getTicket(ctx, ticketId);
|
|
63141
|
+
emit(ctx, ticket.featureId, {
|
|
63142
|
+
type: "ticket.stop_timeout",
|
|
63143
|
+
message: `ticket #${ticket.seq}: stop timed out \u2014 the process may still be running`,
|
|
63144
|
+
ticketId: ticket.id
|
|
63145
|
+
});
|
|
63146
|
+
}
|
|
62779
63147
|
var ticketRouter;
|
|
62780
63148
|
var init_ticket = __esm(() => {
|
|
63149
|
+
init_events();
|
|
62781
63150
|
init_features();
|
|
62782
63151
|
init_repo();
|
|
62783
63152
|
init_tickets();
|
|
@@ -62785,17 +63154,27 @@ var init_ticket = __esm(() => {
|
|
|
62785
63154
|
init_context();
|
|
62786
63155
|
ticketRouter = router({
|
|
62787
63156
|
retry: publicProcedure.input(z15.object({ ticketId: z15.string(), fresh: z15.boolean().optional() })).mutation(({ ctx, input }) => retryTicket(ctx, input.ticketId, { fresh: input.fresh })),
|
|
62788
|
-
stop: publicProcedure.input(z15.object({ ticketId: z15.string() })).mutation(({ ctx, input }) => {
|
|
62789
|
-
|
|
62790
|
-
|
|
63157
|
+
stop: publicProcedure.input(z15.object({ ticketId: z15.string() })).mutation(async ({ ctx, input }) => {
|
|
63158
|
+
const { stopped, confirmed } = await stopTicketRun(input.ticketId);
|
|
63159
|
+
if (stopped) {
|
|
63160
|
+
if (!confirmed)
|
|
63161
|
+
noteStopTimeout(ctx, input.ticketId);
|
|
63162
|
+
return { stopped: true, swept: false, confirmed };
|
|
63163
|
+
}
|
|
62791
63164
|
const ticket = getTicket(ctx, input.ticketId);
|
|
62792
63165
|
if (ticket.status === "burning" && !hasActiveRun(ctx, ticket.featureId)) {
|
|
62793
63166
|
sweepOrphanedBurning(ctx, ticket.featureId, "orphaned \u2014 the run that was burning it is gone");
|
|
62794
|
-
return { stopped: false, swept: true };
|
|
63167
|
+
return { stopped: false, swept: true, confirmed };
|
|
62795
63168
|
}
|
|
62796
|
-
return { stopped: false, swept: false };
|
|
63169
|
+
return { stopped: false, swept: false, confirmed };
|
|
63170
|
+
}),
|
|
63171
|
+
cancel: publicProcedure.input(z15.object({ ticketId: z15.string(), reason: z15.string().optional() })).mutation(async ({ ctx, input }) => {
|
|
63172
|
+
const { stopped, confirmed } = await stopTicketRun(input.ticketId);
|
|
63173
|
+
if (stopped && !confirmed)
|
|
63174
|
+
noteStopTimeout(ctx, input.ticketId);
|
|
63175
|
+
const ticket = cancelTicket(ctx, input.ticketId, input.reason, { agentStopped: stopped });
|
|
63176
|
+
return { ticket, stopped, confirmed };
|
|
62797
63177
|
}),
|
|
62798
|
-
cancel: publicProcedure.input(z15.object({ ticketId: z15.string(), reason: z15.string().optional() })).mutation(({ ctx, input }) => cancelTicket(ctx, input.ticketId, input.reason)),
|
|
62799
63178
|
durationStats: publicProcedure.input(z15.object({ projectId: z15.string() })).query(({ ctx, input }) => ticketDurationStats(ctx, input.projectId)),
|
|
62800
63179
|
edit: publicProcedure.input(z15.object({
|
|
62801
63180
|
ticketId: z15.string(),
|
|
@@ -62842,14 +63221,14 @@ var init_router = __esm(() => {
|
|
|
62842
63221
|
});
|
|
62843
63222
|
|
|
62844
63223
|
// src/workflows/reconcile-runs.ts
|
|
62845
|
-
import { eq as
|
|
63224
|
+
import { eq as eq18 } from "drizzle-orm";
|
|
62846
63225
|
async function reconcileStaleRuns(ctx) {
|
|
62847
|
-
const stale = ctx.db.select().from(runs).where(
|
|
63226
|
+
const stale = ctx.db.select().from(runs).where(eq18(runs.status, "running")).all().map(rowToRun);
|
|
62848
63227
|
const reconciled = [];
|
|
62849
63228
|
for (const run7 of stale) {
|
|
62850
63229
|
if (isRunActive(run7.id))
|
|
62851
63230
|
continue;
|
|
62852
|
-
ctx.db.update(runs).set({ status: "failed", endedAt: Date.now(), summary: "orphaned by server restart" }).where(
|
|
63231
|
+
ctx.db.update(runs).set({ status: "failed", endedAt: Date.now(), summary: "orphaned by server restart" }).where(eq18(runs.id, run7.id)).run();
|
|
62853
63232
|
const released = releaseForSession(ctx, run7.id);
|
|
62854
63233
|
const swept = workflowClaimsFeatureBranch(run7.workflow) ? sweepOrphanedBurning(ctx, run7.featureId, "orphaned by server restart \u2014 retry to resume its commits") : [];
|
|
62855
63234
|
if (workflowClaimsFeatureBranch(run7.workflow)) {
|