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