runcastle 1.3.2 → 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 +1069 -668
- 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 +1062 -645
- 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-CgCA-f62.js +186 -0
- package/web/assets/index-D697YlOb.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-CqM7LtBd.css +0 -1
- package/web/assets/index-Dr6YNyo1.js +0 -145
package/index.js
CHANGED
|
@@ -24,6 +24,12 @@ function newId(prefix) {
|
|
|
24
24
|
var init_ids = () => {};
|
|
25
25
|
|
|
26
26
|
// ../core/src/docs.ts
|
|
27
|
+
function withheldFeatureDocs(state) {
|
|
28
|
+
if (!state.carriedNotesOpen)
|
|
29
|
+
return WITHHELD_FEATURE_DOCS;
|
|
30
|
+
const { "test-notes.md": _testNotes, ...rest } = WITHHELD_FEATURE_DOCS;
|
|
31
|
+
return rest;
|
|
32
|
+
}
|
|
27
33
|
function isAgentDigestDoc(relPath) {
|
|
28
34
|
if (relPath.includes("/") || relPath.includes("\\"))
|
|
29
35
|
return false;
|
|
@@ -322,7 +328,8 @@ var init_schemas = __esm(() => {
|
|
|
322
328
|
"devCommand",
|
|
323
329
|
"driveSetupCommand",
|
|
324
330
|
"driveStopCommand",
|
|
325
|
-
"dbResetCommand"
|
|
331
|
+
"dbResetCommand",
|
|
332
|
+
"driveInstructions"
|
|
326
333
|
];
|
|
327
334
|
PreparedKey = z2.enum(PREPARED_KEYS);
|
|
328
335
|
DRIVE_LOOP_KEYS = [
|
|
@@ -345,6 +352,7 @@ var init_schemas = __esm(() => {
|
|
|
345
352
|
dbResetCommand: z2.string().optional(),
|
|
346
353
|
driveSetupCommand: z2.string().optional(),
|
|
347
354
|
driveStopCommand: z2.string().optional(),
|
|
355
|
+
driveInstructions: z2.string().optional(),
|
|
348
356
|
closedAt: z2.number().optional()
|
|
349
357
|
});
|
|
350
358
|
ProjectFinding = z2.object({
|
|
@@ -366,6 +374,7 @@ var init_schemas = __esm(() => {
|
|
|
366
374
|
brief: z2.string().optional(),
|
|
367
375
|
mapped: z2.boolean(),
|
|
368
376
|
lap: z2.number(),
|
|
377
|
+
ticketsReadyLap: z2.number().nullable(),
|
|
369
378
|
phase: Phase,
|
|
370
379
|
branch: z2.string(),
|
|
371
380
|
baseBranch: z2.string().optional(),
|
|
@@ -550,6 +559,9 @@ function nextPhase(feature) {
|
|
|
550
559
|
return null;
|
|
551
560
|
return ORDER[i + 1];
|
|
552
561
|
}
|
|
562
|
+
function isPastPhase(feature, phase) {
|
|
563
|
+
return ORDER.indexOf(feature.phase) > ORDER.indexOf(phase);
|
|
564
|
+
}
|
|
553
565
|
function previousPhase(feature) {
|
|
554
566
|
const i = ORDER.indexOf(feature.phase);
|
|
555
567
|
if (i <= 0)
|
|
@@ -637,6 +649,7 @@ var init_db_schema = __esm(() => {
|
|
|
637
649
|
verifyCommands: text("verify_commands"),
|
|
638
650
|
knownFailures: text("known_failures"),
|
|
639
651
|
dbResetCommand: text("db_reset_command"),
|
|
652
|
+
driveInstructions: text("drive_instructions"),
|
|
640
653
|
driveSetupCommand: text("drive_setup_command"),
|
|
641
654
|
driveStopCommand: text("drive_stop_command")
|
|
642
655
|
});
|
|
@@ -659,6 +672,7 @@ var init_db_schema = __esm(() => {
|
|
|
659
672
|
brief: text("brief"),
|
|
660
673
|
mapped: integer("mapped", { mode: "boolean" }).notNull().default(false),
|
|
661
674
|
lap: integer("lap").notNull().default(1),
|
|
675
|
+
ticketsReadyLap: integer("tickets_ready_lap"),
|
|
662
676
|
phase: text("phase").notNull().$type(),
|
|
663
677
|
branch: text("branch").notNull(),
|
|
664
678
|
baseBranch: text("base_branch"),
|
|
@@ -1017,7 +1031,8 @@ var init_client = __esm(() => {
|
|
|
1017
1031
|
|
|
1018
1032
|
// src/launcher/asset-paths.ts
|
|
1019
1033
|
import { existsSync as existsSync2 } from "fs";
|
|
1020
|
-
import { join as join3, resolve as resolve2 } from "path";
|
|
1034
|
+
import { dirname, join as join3, resolve as resolve2 } from "path";
|
|
1035
|
+
import { fileURLToPath } from "url";
|
|
1021
1036
|
function resolveAsset(envVar, fallback) {
|
|
1022
1037
|
const override = process.env[envVar];
|
|
1023
1038
|
if (override) {
|
|
@@ -1038,6 +1053,17 @@ function vendoredAssetPaths(pkgRoot) {
|
|
|
1038
1053
|
[ASSET_ENV.sandcastleTemplate]: join3(pkgRoot, "sandcastle-template")
|
|
1039
1054
|
};
|
|
1040
1055
|
}
|
|
1056
|
+
function sandcastleTemplateFallback(moduleDir) {
|
|
1057
|
+
const inSource = join3(moduleDir, "..", "assets", "sandcastle");
|
|
1058
|
+
const installed = [moduleDir, dirname(moduleDir)].map((pkgRoot) => vendoredAssetPaths(pkgRoot)[ASSET_ENV.sandcastleTemplate]);
|
|
1059
|
+
return [inSource, ...installed].find((dir) => existsSync2(dir)) ?? inSource;
|
|
1060
|
+
}
|
|
1061
|
+
function sandcastleTemplateDir() {
|
|
1062
|
+
return resolveAsset(ASSET_ENV.sandcastleTemplate, sandcastleTemplateFallback(dirname(fileURLToPath(import.meta.url))));
|
|
1063
|
+
}
|
|
1064
|
+
function burnerDockerfilePath() {
|
|
1065
|
+
return join3(sandcastleTemplateDir(), "Dockerfile");
|
|
1066
|
+
}
|
|
1041
1067
|
function applyInstalledAssetEnv(pkgRoot) {
|
|
1042
1068
|
for (const [envVar, path] of Object.entries(vendoredAssetPaths(pkgRoot))) {
|
|
1043
1069
|
if (process.env[envVar] === undefined && existsSync2(path))
|
|
@@ -1235,7 +1261,7 @@ var init_kill_tree = () => {};
|
|
|
1235
1261
|
import { spawn as spawn2 } from "child_process";
|
|
1236
1262
|
import { createRequire } from "module";
|
|
1237
1263
|
import { existsSync as existsSync4 } from "fs";
|
|
1238
|
-
import { fileURLToPath } from "url";
|
|
1264
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1239
1265
|
function isBun() {
|
|
1240
1266
|
return typeof globalThis.Bun !== "undefined";
|
|
1241
1267
|
}
|
|
@@ -1428,7 +1454,7 @@ var init_pty_sidecar = __esm(() => {
|
|
|
1428
1454
|
init_asset_paths();
|
|
1429
1455
|
init_resolve_executable();
|
|
1430
1456
|
init_kill_tree();
|
|
1431
|
-
HOST_PATH = resolveAsset(ASSET_ENV.ptyHost,
|
|
1457
|
+
HOST_PATH = resolveAsset(ASSET_ENV.ptyHost, fileURLToPath2(new URL("./pty-host.cjs", import.meta.url)));
|
|
1432
1458
|
});
|
|
1433
1459
|
|
|
1434
1460
|
// src/pty/pty.ts
|
|
@@ -2087,6 +2113,7 @@ function rowToFeature(row) {
|
|
|
2087
2113
|
brief: row.brief ?? undefined,
|
|
2088
2114
|
mapped: row.mapped,
|
|
2089
2115
|
lap: row.lap,
|
|
2116
|
+
ticketsReadyLap: row.ticketsReadyLap,
|
|
2090
2117
|
phase: row.phase,
|
|
2091
2118
|
branch: row.branch,
|
|
2092
2119
|
baseBranch: row.baseBranch ?? undefined,
|
|
@@ -2108,6 +2135,7 @@ function rowToProject(row) {
|
|
|
2108
2135
|
dbResetCommand: row.dbResetCommand ?? undefined,
|
|
2109
2136
|
driveSetupCommand: row.driveSetupCommand ?? undefined,
|
|
2110
2137
|
driveStopCommand: row.driveStopCommand ?? undefined,
|
|
2138
|
+
driveInstructions: row.driveInstructions ?? undefined,
|
|
2111
2139
|
closedAt: row.closedAt ?? undefined
|
|
2112
2140
|
});
|
|
2113
2141
|
}
|
|
@@ -2211,6 +2239,16 @@ function setPhase(ctx, featureId, toPhase, eventType = "phase.changed", message)
|
|
|
2211
2239
|
});
|
|
2212
2240
|
return { ...current, phase: toPhase };
|
|
2213
2241
|
}
|
|
2242
|
+
function markTicketsReady(ctx, featureId, nextPhase2) {
|
|
2243
|
+
const current = getFeatureRow(ctx, featureId);
|
|
2244
|
+
ctx.db.update(features).set({ ticketsReadyLap: current.lap }).where(eq2(features.id, featureId)).run();
|
|
2245
|
+
emit(ctx, featureId, {
|
|
2246
|
+
type: "tickets.awaiting_burn",
|
|
2247
|
+
message: "tickets complete \u2014 waiting on the human Burn click (gate G3)",
|
|
2248
|
+
data: { phase: current.phase, nextPhase: nextPhase2, waitingOn: "human burn" }
|
|
2249
|
+
});
|
|
2250
|
+
return { ...current, ticketsReadyLap: current.lap };
|
|
2251
|
+
}
|
|
2214
2252
|
function setFeatureStatus(ctx, featureId, status) {
|
|
2215
2253
|
const current = getFeatureRow(ctx, featureId);
|
|
2216
2254
|
ctx.db.update(features).set({ status }).where(eq2(features.id, featureId)).run();
|
|
@@ -2879,7 +2917,8 @@ var init_findings = __esm(() => {
|
|
|
2879
2917
|
devCommand: projects.devCommand,
|
|
2880
2918
|
driveSetupCommand: projects.driveSetupCommand,
|
|
2881
2919
|
driveStopCommand: projects.driveStopCommand,
|
|
2882
|
-
dbResetCommand: projects.dbResetCommand
|
|
2920
|
+
dbResetCommand: projects.dbResetCommand,
|
|
2921
|
+
driveInstructions: projects.driveInstructions
|
|
2883
2922
|
};
|
|
2884
2923
|
COLUMN_NAME = {
|
|
2885
2924
|
setupCommand: "setupCommand",
|
|
@@ -2888,7 +2927,8 @@ var init_findings = __esm(() => {
|
|
|
2888
2927
|
devCommand: "devCommand",
|
|
2889
2928
|
driveSetupCommand: "driveSetupCommand",
|
|
2890
2929
|
driveStopCommand: "driveStopCommand",
|
|
2891
|
-
dbResetCommand: "dbResetCommand"
|
|
2930
|
+
dbResetCommand: "dbResetCommand",
|
|
2931
|
+
driveInstructions: "driveInstructions"
|
|
2892
2932
|
};
|
|
2893
2933
|
PREPARED_SET = new Set(PREPARED_KEYS);
|
|
2894
2934
|
UNVERIFIED = { verifiedAt: null, verifiedSha: null };
|
|
@@ -2905,7 +2945,7 @@ import {
|
|
|
2905
2945
|
writeFileSync
|
|
2906
2946
|
} from "fs";
|
|
2907
2947
|
import { tmpdir } from "os";
|
|
2908
|
-
import { dirname, join as join6, resolve as resolve4 } from "path";
|
|
2948
|
+
import { dirname as dirname2, join as join6, resolve as resolve4 } from "path";
|
|
2909
2949
|
import { simpleGit } from "simple-git";
|
|
2910
2950
|
function deriveDriveState(info) {
|
|
2911
2951
|
if (!info)
|
|
@@ -3067,7 +3107,7 @@ async function ensureTalkWorktree(project, feature) {
|
|
|
3067
3107
|
return addWorktree(g, worktreePath, branch, "talk worktree");
|
|
3068
3108
|
}
|
|
3069
3109
|
async function addWorktree(g, worktreePath, branch, label) {
|
|
3070
|
-
mkdirSync2(
|
|
3110
|
+
mkdirSync2(dirname2(worktreePath), { recursive: true });
|
|
3071
3111
|
try {
|
|
3072
3112
|
await g.raw(["worktree", "add", worktreePath, branch]);
|
|
3073
3113
|
return worktreePath;
|
|
@@ -3274,7 +3314,7 @@ async function excludeFilePath(repoPath) {
|
|
|
3274
3314
|
}
|
|
3275
3315
|
async function excludePath(repoPath, pattern) {
|
|
3276
3316
|
const file = await excludeFilePath(repoPath);
|
|
3277
|
-
mkdirSync2(
|
|
3317
|
+
mkdirSync2(dirname2(file), { recursive: true });
|
|
3278
3318
|
const current = existsSync6(file) ? readFileSync3(file, "utf8") : "";
|
|
3279
3319
|
if (current.split(/\r?\n/).includes(pattern))
|
|
3280
3320
|
return;
|
|
@@ -4180,6 +4220,460 @@ var init_git = __esm(() => {
|
|
|
4180
4220
|
TEMP_BRANCH_PREFIXES = [RESEARCH_BRANCH_PREFIX, TICKET_BRANCH_PREFIX];
|
|
4181
4221
|
});
|
|
4182
4222
|
|
|
4223
|
+
// src/services/tickets.ts
|
|
4224
|
+
import { and as and5, asc as asc3, eq as eq5, inArray } from "drizzle-orm";
|
|
4225
|
+
function rowToTicket(row) {
|
|
4226
|
+
return Ticket.parse({
|
|
4227
|
+
id: row.id,
|
|
4228
|
+
featureId: row.featureId,
|
|
4229
|
+
seq: row.seq,
|
|
4230
|
+
title: row.title,
|
|
4231
|
+
goal: row.goal,
|
|
4232
|
+
context: row.context,
|
|
4233
|
+
acceptanceCriteria: row.acceptanceCriteria,
|
|
4234
|
+
seams: row.seams,
|
|
4235
|
+
blockedBy: row.blockedBy,
|
|
4236
|
+
kind: row.kind,
|
|
4237
|
+
passKind: row.passKind,
|
|
4238
|
+
reviewedCommit: row.reviewedCommit,
|
|
4239
|
+
completedAt: row.completedAt,
|
|
4240
|
+
model: row.model ?? undefined,
|
|
4241
|
+
lap: row.lap,
|
|
4242
|
+
status: row.status,
|
|
4243
|
+
commits: row.commits,
|
|
4244
|
+
error: row.error ?? undefined,
|
|
4245
|
+
attemptBranch: row.attemptBranch ?? undefined,
|
|
4246
|
+
conflictFiles: row.conflictFiles ?? undefined,
|
|
4247
|
+
digest: row.digest ?? undefined,
|
|
4248
|
+
originFindingId: row.originFindingId ?? undefined
|
|
4249
|
+
});
|
|
4250
|
+
}
|
|
4251
|
+
function resolveBlocking(inputs, startSeq) {
|
|
4252
|
+
try {
|
|
4253
|
+
return resolveBatchBlocking(inputs, { startSeq });
|
|
4254
|
+
} catch (e) {
|
|
4255
|
+
if (e instanceof BlockingEdgeError)
|
|
4256
|
+
throw new InvalidInputError(e.message);
|
|
4257
|
+
throw e;
|
|
4258
|
+
}
|
|
4259
|
+
}
|
|
4260
|
+
function normalizeModel(ctx, value) {
|
|
4261
|
+
const id = value?.trim();
|
|
4262
|
+
if (!id)
|
|
4263
|
+
return null;
|
|
4264
|
+
const roster = modelRoster(ctx.config);
|
|
4265
|
+
if (!roster.some((m) => m.id === id)) {
|
|
4266
|
+
throw new InvalidInputError(`unknown model "${id}" \u2014 assign one of the configured models: ${roster.map((m) => m.id).join(", ")}`);
|
|
4267
|
+
}
|
|
4268
|
+
return id;
|
|
4269
|
+
}
|
|
4270
|
+
function listByFeature2(ctx, featureId) {
|
|
4271
|
+
return ctx.db.select().from(tickets).where(eq5(tickets.featureId, featureId)).orderBy(asc3(tickets.seq)).all().map(rowToTicket);
|
|
4272
|
+
}
|
|
4273
|
+
function storeTickets(ctx, featureId, inputs, options = {}) {
|
|
4274
|
+
if (inputs.length === 0)
|
|
4275
|
+
return [];
|
|
4276
|
+
const { lap } = getFeatureRow(ctx, featureId);
|
|
4277
|
+
const existing = ctx.db.select({ seq: tickets.seq }).from(tickets).where(eq5(tickets.featureId, featureId)).all();
|
|
4278
|
+
const startSeq = existing.reduce((max2, r) => Math.max(max2, r.seq), 0) + 1;
|
|
4279
|
+
const resolved = options.blockedByAreGlobal ? inputs.map((input, index2) => ({ seq: startSeq + index2, blockedBy: input.blockedBy })) : resolveBlocking(inputs, startSeq);
|
|
4280
|
+
const rows = inputs.map((t, i) => ({
|
|
4281
|
+
id: newId("tkt"),
|
|
4282
|
+
featureId,
|
|
4283
|
+
seq: resolved[i].seq,
|
|
4284
|
+
title: t.title,
|
|
4285
|
+
goal: t.goal,
|
|
4286
|
+
context: t.context,
|
|
4287
|
+
acceptanceCriteria: t.acceptanceCriteria,
|
|
4288
|
+
seams: t.seams,
|
|
4289
|
+
blockedBy: resolved[i].blockedBy,
|
|
4290
|
+
kind: t.kind ?? "implementation",
|
|
4291
|
+
passKind: t.passKind ?? "review",
|
|
4292
|
+
reviewedCommit: null,
|
|
4293
|
+
completedAt: null,
|
|
4294
|
+
model: normalizeModel(ctx, t.model),
|
|
4295
|
+
lap,
|
|
4296
|
+
status: "pending",
|
|
4297
|
+
commits: [],
|
|
4298
|
+
error: null,
|
|
4299
|
+
attemptBranch: null,
|
|
4300
|
+
conflictFiles: null,
|
|
4301
|
+
digest: null,
|
|
4302
|
+
originFindingId: t.originFindingId ?? null
|
|
4303
|
+
}));
|
|
4304
|
+
ctx.db.insert(tickets).values(rows).run();
|
|
4305
|
+
emit(ctx, featureId, {
|
|
4306
|
+
type: "tickets.stored",
|
|
4307
|
+
message: `${rows.length} ticket(s) stored`,
|
|
4308
|
+
data: { count: rows.length, seqs: rows.map((r) => r.seq) }
|
|
4309
|
+
});
|
|
4310
|
+
return rows.map(rowToTicket);
|
|
4311
|
+
}
|
|
4312
|
+
function getTicket(ctx, id) {
|
|
4313
|
+
const row = ctx.db.select().from(tickets).where(eq5(tickets.id, id)).get();
|
|
4314
|
+
if (!row)
|
|
4315
|
+
throw new NotFoundError(`ticket ${id} not found`);
|
|
4316
|
+
return rowToTicket(row);
|
|
4317
|
+
}
|
|
4318
|
+
function assertMutable(t, verb) {
|
|
4319
|
+
if (!MUTABLE_STATUSES.has(t.status)) {
|
|
4320
|
+
throw new InvalidInputError(`cannot ${verb} ticket ${t.seq} \u2014 it is ${t.status}; only pending or failed tickets can be ${verb}${verb.endsWith("l") ? "led" : "ed"}`);
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
function editTicket(ctx, id, patch) {
|
|
4324
|
+
const current = getTicket(ctx, id);
|
|
4325
|
+
assertMutable(current, "edit");
|
|
4326
|
+
const set = {};
|
|
4327
|
+
if (patch.title !== undefined)
|
|
4328
|
+
set.title = patch.title;
|
|
4329
|
+
if (patch.goal !== undefined)
|
|
4330
|
+
set.goal = patch.goal;
|
|
4331
|
+
if (patch.context !== undefined)
|
|
4332
|
+
set.context = patch.context;
|
|
4333
|
+
if (patch.acceptanceCriteria !== undefined)
|
|
4334
|
+
set.acceptanceCriteria = patch.acceptanceCriteria;
|
|
4335
|
+
if (patch.seams !== undefined)
|
|
4336
|
+
set.seams = patch.seams;
|
|
4337
|
+
if (patch.model !== undefined)
|
|
4338
|
+
set.model = normalizeModel(ctx, patch.model);
|
|
4339
|
+
if (Object.keys(set).length === 0) {
|
|
4340
|
+
throw new InvalidInputError("nothing to edit \u2014 pass at least one field");
|
|
4341
|
+
}
|
|
4342
|
+
ctx.db.update(tickets).set(set).where(eq5(tickets.id, id)).run();
|
|
4343
|
+
emit(ctx, current.featureId, {
|
|
4344
|
+
type: "ticket.edited",
|
|
4345
|
+
message: `ticket ${current.seq} content edited (${Object.keys(set).join(", ")})`,
|
|
4346
|
+
ticketId: id,
|
|
4347
|
+
data: { fields: Object.keys(set) }
|
|
4348
|
+
});
|
|
4349
|
+
return getTicket(ctx, id);
|
|
4350
|
+
}
|
|
4351
|
+
function cancelTicket(ctx, id, reason, opts) {
|
|
4352
|
+
const current = getTicket(ctx, id);
|
|
4353
|
+
if (!(opts?.agentStopped && current.status === "burning"))
|
|
4354
|
+
assertMutable(current, "cancel");
|
|
4355
|
+
ctx.db.update(tickets).set({
|
|
4356
|
+
status: "cancelled",
|
|
4357
|
+
error: reason?.trim() ? reason.trim() : null,
|
|
4358
|
+
completedAt: Date.now()
|
|
4359
|
+
}).where(eq5(tickets.id, id)).run();
|
|
4360
|
+
emit(ctx, current.featureId, {
|
|
4361
|
+
type: "ticket.cancelled",
|
|
4362
|
+
message: `ticket ${current.seq} cancelled${reason?.trim() ? `: ${reason.trim()}` : ""}`,
|
|
4363
|
+
ticketId: id,
|
|
4364
|
+
data: { reason: reason ?? null }
|
|
4365
|
+
});
|
|
4366
|
+
return getTicket(ctx, id);
|
|
4367
|
+
}
|
|
4368
|
+
function sweepOrphanedBurning(ctx, featureId, reason) {
|
|
4369
|
+
const orphaned = ctx.db.select().from(tickets).where(and5(eq5(tickets.featureId, featureId), eq5(tickets.status, "burning"))).all().map(rowToTicket);
|
|
4370
|
+
const swept = [];
|
|
4371
|
+
for (const t of orphaned) {
|
|
4372
|
+
swept.push(updateTicket(ctx, t.id, { status: "failed", error: reason }));
|
|
4373
|
+
if (t.originFindingId)
|
|
4374
|
+
markFailed(ctx, t.originFindingId, reason);
|
|
4375
|
+
emit(ctx, featureId, {
|
|
4376
|
+
type: "ticket.failed",
|
|
4377
|
+
message: `ticket ${t.seq} failed: ${reason}`,
|
|
4378
|
+
ticketId: t.id,
|
|
4379
|
+
data: { error: reason, orphaned: true }
|
|
4380
|
+
});
|
|
4381
|
+
}
|
|
4382
|
+
return swept;
|
|
4383
|
+
}
|
|
4384
|
+
function updateTicket(ctx, id, patch) {
|
|
4385
|
+
const current = ctx.db.select().from(tickets).where(eq5(tickets.id, id)).get();
|
|
4386
|
+
if (!current)
|
|
4387
|
+
throw new NotFoundError(`ticket ${id} not found`);
|
|
4388
|
+
const set = {};
|
|
4389
|
+
if (patch.status !== undefined)
|
|
4390
|
+
set.status = patch.status;
|
|
4391
|
+
if (patch.status !== undefined) {
|
|
4392
|
+
set.completedAt = ["done", "failed", "cancelled"].includes(patch.status) ? Date.now() : null;
|
|
4393
|
+
}
|
|
4394
|
+
if (patch.commits !== undefined)
|
|
4395
|
+
set.commits = patch.commits;
|
|
4396
|
+
if (patch.error !== undefined)
|
|
4397
|
+
set.error = patch.error;
|
|
4398
|
+
if (patch.attemptBranch !== undefined)
|
|
4399
|
+
set.attemptBranch = patch.attemptBranch;
|
|
4400
|
+
if (patch.conflictFiles !== undefined)
|
|
4401
|
+
set.conflictFiles = patch.conflictFiles;
|
|
4402
|
+
if (patch.digest !== undefined)
|
|
4403
|
+
set.digest = patch.digest;
|
|
4404
|
+
if (patch.reviewedCommit !== undefined)
|
|
4405
|
+
set.reviewedCommit = patch.reviewedCommit;
|
|
4406
|
+
ctx.db.update(tickets).set(set).where(eq5(tickets.id, id)).run();
|
|
4407
|
+
emit(ctx, current.featureId, {
|
|
4408
|
+
type: "ticket.updated",
|
|
4409
|
+
message: `ticket ${current.seq} \u2192 ${patch.status ?? current.status}`,
|
|
4410
|
+
ticketId: id,
|
|
4411
|
+
data: patch
|
|
4412
|
+
});
|
|
4413
|
+
const updated = ctx.db.select().from(tickets).where(eq5(tickets.id, id)).get();
|
|
4414
|
+
return rowToTicket(updated);
|
|
4415
|
+
}
|
|
4416
|
+
function listByIds(ctx, ids2) {
|
|
4417
|
+
if (ids2.length === 0)
|
|
4418
|
+
return [];
|
|
4419
|
+
return ctx.db.select().from(tickets).where(inArray(tickets.id, [...ids2])).orderBy(asc3(tickets.seq)).all().map(rowToTicket);
|
|
4420
|
+
}
|
|
4421
|
+
function timingWallMs(data) {
|
|
4422
|
+
if (typeof data !== "object" || data === null)
|
|
4423
|
+
return;
|
|
4424
|
+
const ms = data.wallMs;
|
|
4425
|
+
return typeof ms === "number" && Number.isFinite(ms) && ms > 0 ? ms : undefined;
|
|
4426
|
+
}
|
|
4427
|
+
function ticketDurationStats(ctx, projectId) {
|
|
4428
|
+
const rows = ctx.db.select({ ticketId: events.ticketId, data: events.data }).from(events).innerJoin(tickets, eq5(events.ticketId, tickets.id)).where(and5(eq5(events.projectId, projectId), eq5(events.type, "ticket.timing"), eq5(tickets.status, "done"), eq5(tickets.kind, "implementation"))).orderBy(asc3(events.id)).all();
|
|
4429
|
+
const perTicket = new Map;
|
|
4430
|
+
for (const row of rows) {
|
|
4431
|
+
const ms = timingWallMs(row.data);
|
|
4432
|
+
if (row.ticketId && ms !== undefined)
|
|
4433
|
+
perTicket.set(row.ticketId, ms);
|
|
4434
|
+
}
|
|
4435
|
+
const samples = [...perTicket.values()].sort((a, b) => a - b);
|
|
4436
|
+
if (samples.length === 0)
|
|
4437
|
+
return { medianMs: 0, sampleSize: 0 };
|
|
4438
|
+
const mid = samples.length >> 1;
|
|
4439
|
+
const medianMs = samples.length % 2 === 1 ? samples[mid] : Math.round((samples[mid - 1] + samples[mid]) / 2);
|
|
4440
|
+
return { medianMs, sampleSize: samples.length };
|
|
4441
|
+
}
|
|
4442
|
+
var MUTABLE_STATUSES;
|
|
4443
|
+
var init_tickets = __esm(() => {
|
|
4444
|
+
init_src();
|
|
4445
|
+
init_schema();
|
|
4446
|
+
init_errors();
|
|
4447
|
+
init_events();
|
|
4448
|
+
init_repo();
|
|
4449
|
+
init_review_findings();
|
|
4450
|
+
MUTABLE_STATUSES = new Set(["pending", "failed"]);
|
|
4451
|
+
});
|
|
4452
|
+
|
|
4453
|
+
// src/services/review-findings.ts
|
|
4454
|
+
import { and as and6, asc as asc4, eq as eq6, isNotNull } from "drizzle-orm";
|
|
4455
|
+
function rowToFinding(row) {
|
|
4456
|
+
return ReviewFinding.parse(row);
|
|
4457
|
+
}
|
|
4458
|
+
function getFinding(ctx, id) {
|
|
4459
|
+
const row = ctx.db.select().from(reviewFindings).where(eq6(reviewFindings.id, id)).get();
|
|
4460
|
+
if (!row)
|
|
4461
|
+
throw new NotFoundError(`review finding ${id} not found`);
|
|
4462
|
+
return rowToFinding(row);
|
|
4463
|
+
}
|
|
4464
|
+
function listByFeature3(ctx, featureId) {
|
|
4465
|
+
return ctx.db.select().from(reviewFindings).where(eq6(reviewFindings.featureId, featureId)).orderBy(asc4(reviewFindings.createdAt), asc4(reviewFindings.id)).all().map(rowToFinding);
|
|
4466
|
+
}
|
|
4467
|
+
function buildFixTicket(finding) {
|
|
4468
|
+
return {
|
|
4469
|
+
title: finding.title,
|
|
4470
|
+
goal: `Fix: ${finding.title}`,
|
|
4471
|
+
context: [
|
|
4472
|
+
`Location: ${finding.location}`,
|
|
4473
|
+
`Citation: ${finding.citation}`,
|
|
4474
|
+
`Detail: ${finding.detail}`,
|
|
4475
|
+
`Repro step: ${finding.reproStep}`
|
|
4476
|
+
].join(`
|
|
4477
|
+
|
|
4478
|
+
`),
|
|
4479
|
+
acceptanceCriteria: [
|
|
4480
|
+
`The repro step no longer reproduces / the cited criterion holds: ${finding.reproStep}`
|
|
4481
|
+
],
|
|
4482
|
+
seams: [],
|
|
4483
|
+
blockedBy: [],
|
|
4484
|
+
kind: "implementation",
|
|
4485
|
+
originFindingId: finding.id
|
|
4486
|
+
};
|
|
4487
|
+
}
|
|
4488
|
+
function reportFinding(ctx, args) {
|
|
4489
|
+
const { featureId, reviewTicket, input } = args;
|
|
4490
|
+
const feature = getFeatureRow(ctx, featureId);
|
|
4491
|
+
const id = newId("finding");
|
|
4492
|
+
const ticketCount = ctx.db.select({ id: reviewFindings.id }).from(reviewFindings).where(and6(eq6(reviewFindings.reviewTicketId, reviewTicket.id), isNotNull(reviewFindings.fixTicketId))).all().length;
|
|
4493
|
+
const verification = reviewTicket.passKind === "verification";
|
|
4494
|
+
const overCap = input.kind === "defect" && !verification && ticketCount >= AUTO_FIX_CAP;
|
|
4495
|
+
ctx.db.insert(reviewFindings).values({
|
|
4496
|
+
id,
|
|
4497
|
+
featureId,
|
|
4498
|
+
lap: feature.lap,
|
|
4499
|
+
reviewTicketId: reviewTicket.id,
|
|
4500
|
+
...input,
|
|
4501
|
+
reproStep: input.reproStep ?? "",
|
|
4502
|
+
status: "open",
|
|
4503
|
+
openReason: verification && input.kind === "defect" ? "verification" : overCap ? "over-cap" : null,
|
|
4504
|
+
failureReason: null,
|
|
4505
|
+
fixTicketId: null,
|
|
4506
|
+
createdAt: Date.now()
|
|
4507
|
+
}).run();
|
|
4508
|
+
let fixTicket = null;
|
|
4509
|
+
if (input.kind === "defect" && !overCap && !verification) {
|
|
4510
|
+
const finding2 = getFinding(ctx, id);
|
|
4511
|
+
fixTicket = storeTickets(ctx, featureId, [
|
|
4512
|
+
{ ...buildFixTicket(finding2), blockedBy: [reviewTicket.seq] }
|
|
4513
|
+
], { blockedByAreGlobal: true })[0];
|
|
4514
|
+
ctx.db.update(reviewFindings).set({ fixTicketId: fixTicket.id }).where(eq6(reviewFindings.id, id)).run();
|
|
4515
|
+
}
|
|
4516
|
+
const finding = getFinding(ctx, id);
|
|
4517
|
+
emit(ctx, featureId, {
|
|
4518
|
+
type: "finding.reported",
|
|
4519
|
+
message: `${input.kind} reported: ${input.title}`,
|
|
4520
|
+
ticketId: fixTicket?.id,
|
|
4521
|
+
data: { findingId: id, fixTicketId: fixTicket?.id, overCap }
|
|
4522
|
+
});
|
|
4523
|
+
return { finding, fixTicket, overCap };
|
|
4524
|
+
}
|
|
4525
|
+
function updateStatus(ctx, findingId, patch) {
|
|
4526
|
+
const current = getFinding(ctx, findingId);
|
|
4527
|
+
ctx.db.update(reviewFindings).set(patch).where(eq6(reviewFindings.id, findingId)).run();
|
|
4528
|
+
emit(ctx, current.featureId, {
|
|
4529
|
+
type: "finding.updated",
|
|
4530
|
+
message: `finding ${findingId} marked ${patch.status}`,
|
|
4531
|
+
data: { findingId, status: patch.status, reason: patch.failureReason }
|
|
4532
|
+
});
|
|
4533
|
+
return getFinding(ctx, findingId);
|
|
4534
|
+
}
|
|
4535
|
+
function markFixing(ctx, findingId) {
|
|
4536
|
+
return updateStatus(ctx, findingId, {
|
|
4537
|
+
status: "fixing",
|
|
4538
|
+
openReason: null,
|
|
4539
|
+
failureReason: null
|
|
4540
|
+
});
|
|
4541
|
+
}
|
|
4542
|
+
function markFixed(ctx, findingId) {
|
|
4543
|
+
return updateStatus(ctx, findingId, {
|
|
4544
|
+
status: "fixed",
|
|
4545
|
+
openReason: null,
|
|
4546
|
+
failureReason: null
|
|
4547
|
+
});
|
|
4548
|
+
}
|
|
4549
|
+
function markFailed(ctx, findingId, reason) {
|
|
4550
|
+
return updateStatus(ctx, findingId, {
|
|
4551
|
+
status: "failed",
|
|
4552
|
+
openReason: "fix-failed",
|
|
4553
|
+
failureReason: reason ?? null
|
|
4554
|
+
});
|
|
4555
|
+
}
|
|
4556
|
+
function markFixProgress(ctx, findingId, progress, reason) {
|
|
4557
|
+
if (progress === "fixing")
|
|
4558
|
+
return markFixing(ctx, findingId);
|
|
4559
|
+
if (progress === "fixed")
|
|
4560
|
+
return markFixed(ctx, findingId);
|
|
4561
|
+
return markFailed(ctx, findingId, reason);
|
|
4562
|
+
}
|
|
4563
|
+
function dismiss(ctx, findingId) {
|
|
4564
|
+
return updateStatus(ctx, findingId, {
|
|
4565
|
+
status: "dismissed",
|
|
4566
|
+
openReason: null,
|
|
4567
|
+
failureReason: null
|
|
4568
|
+
});
|
|
4569
|
+
}
|
|
4570
|
+
function defectState(finding, fixTicket) {
|
|
4571
|
+
if (finding.status === "dismissed")
|
|
4572
|
+
return "dismissed";
|
|
4573
|
+
if (finding.status === "fixed" || fixTicket?.status === "done")
|
|
4574
|
+
return "fixed";
|
|
4575
|
+
if (fixTicket && fixTicket.status !== "failed" && fixTicket.status !== "cancelled") {
|
|
4576
|
+
return "fixing";
|
|
4577
|
+
}
|
|
4578
|
+
return finding.status === "open" || finding.status === "failed" || fixTicket?.status === "failed" || fixTicket?.status === "cancelled" ? "open" : "fixing";
|
|
4579
|
+
}
|
|
4580
|
+
function viewByFeature(ctx, featureId) {
|
|
4581
|
+
const findings = listByFeature3(ctx, featureId);
|
|
4582
|
+
const tickets2 = listByFeature2(ctx, featureId);
|
|
4583
|
+
const summary = { found: 0, fixed: 0, open: 0, observations: 0 };
|
|
4584
|
+
const openDefects = [];
|
|
4585
|
+
for (const finding of findings) {
|
|
4586
|
+
if (finding.kind === "observation") {
|
|
4587
|
+
summary.observations += 1;
|
|
4588
|
+
continue;
|
|
4589
|
+
}
|
|
4590
|
+
summary.found += 1;
|
|
4591
|
+
const state = defectState(finding, fixTicketOf(finding, tickets2));
|
|
4592
|
+
if (state === "fixed")
|
|
4593
|
+
summary.fixed += 1;
|
|
4594
|
+
else if (state === "open") {
|
|
4595
|
+
summary.open += 1;
|
|
4596
|
+
openDefects.push(finding);
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
return { findings, openDefects, summary };
|
|
4600
|
+
}
|
|
4601
|
+
function fixTicketOf(finding, tickets2) {
|
|
4602
|
+
return finding.fixTicketId ? tickets2.find((t) => t.id === finding.fixTicketId) : undefined;
|
|
4603
|
+
}
|
|
4604
|
+
function promoteOpenDefects(ctx, featureId, findingIds) {
|
|
4605
|
+
const open = viewByFeature(ctx, featureId).openDefects;
|
|
4606
|
+
const defects = findingIds ? findingIds.map((id) => {
|
|
4607
|
+
const finding = open.find((candidate) => candidate.id === id);
|
|
4608
|
+
if (!finding)
|
|
4609
|
+
throw new InvalidInputError(`finding ${id} is not an open defect of this feature`);
|
|
4610
|
+
return finding;
|
|
4611
|
+
}) : open;
|
|
4612
|
+
if (findingIds && new Set(findingIds).size !== findingIds.length)
|
|
4613
|
+
throw new InvalidInputError("duplicate finding selected");
|
|
4614
|
+
if (defects.length === 0)
|
|
4615
|
+
throw new InvalidInputError("no open defects to fix");
|
|
4616
|
+
const tickets2 = storeTickets(ctx, featureId, defects.map(buildFixTicket));
|
|
4617
|
+
const findings = defects.map((defect, index2) => {
|
|
4618
|
+
ctx.db.update(reviewFindings).set({
|
|
4619
|
+
status: "fixing",
|
|
4620
|
+
openReason: null,
|
|
4621
|
+
failureReason: null,
|
|
4622
|
+
fixTicketId: tickets2[index2].id
|
|
4623
|
+
}).where(eq6(reviewFindings.id, defect.id)).run();
|
|
4624
|
+
return getFinding(ctx, defect.id);
|
|
4625
|
+
});
|
|
4626
|
+
emit(ctx, featureId, {
|
|
4627
|
+
type: "finding.fixing",
|
|
4628
|
+
message: `${findings.length} open defect${findings.length === 1 ? "" : "s"} promoted to fix ticket${tickets2.length === 1 ? ` ${tickets2[0].seq}` : `s ${tickets2.map((t) => t.seq).join(", ")}`}`,
|
|
4629
|
+
data: { findingIds: findings.map((f) => f.id), seqs: tickets2.map((t) => t.seq) }
|
|
4630
|
+
});
|
|
4631
|
+
return { findings, tickets: tickets2 };
|
|
4632
|
+
}
|
|
4633
|
+
var AUTO_FIX_CAP = 8;
|
|
4634
|
+
var init_review_findings = __esm(() => {
|
|
4635
|
+
init_src();
|
|
4636
|
+
init_schema();
|
|
4637
|
+
init_errors();
|
|
4638
|
+
init_events();
|
|
4639
|
+
init_repo();
|
|
4640
|
+
init_tickets();
|
|
4641
|
+
});
|
|
4642
|
+
|
|
4643
|
+
// src/services/carried-work.ts
|
|
4644
|
+
import { and as and7, eq as eq7 } from "drizzle-orm";
|
|
4645
|
+
function carriedWork(ctx, featureId) {
|
|
4646
|
+
const carried = ctx.db.select({ id: testNotes.id }).from(testNotes).where(and7(eq7(testNotes.featureId, featureId), eq7(testNotes.status, "carried"))).all();
|
|
4647
|
+
return {
|
|
4648
|
+
carriedNotes: carried.length,
|
|
4649
|
+
openDefects: viewByFeature(ctx, featureId).openDefects.map((defect) => ({
|
|
4650
|
+
title: defect.title,
|
|
4651
|
+
location: defect.location,
|
|
4652
|
+
detail: defect.detail,
|
|
4653
|
+
reproStep: defect.reproStep
|
|
4654
|
+
}))
|
|
4655
|
+
};
|
|
4656
|
+
}
|
|
4657
|
+
function hasCarriedWork(carried) {
|
|
4658
|
+
return carried !== undefined && (carried.carriedNotes > 0 || carried.openDefects.length > 0);
|
|
4659
|
+
}
|
|
4660
|
+
function carriedWorkSummary(carried) {
|
|
4661
|
+
if (!hasCarriedWork(carried))
|
|
4662
|
+
return;
|
|
4663
|
+
const parts = [];
|
|
4664
|
+
if (carried.carriedNotes > 0) {
|
|
4665
|
+
parts.push(`${carried.carriedNotes} note${carried.carriedNotes === 1 ? "" : "s"} carried`);
|
|
4666
|
+
}
|
|
4667
|
+
const defects = carried.openDefects.length;
|
|
4668
|
+
if (defects > 0)
|
|
4669
|
+
parts.push(`${defects} defect${defects === 1 ? "" : "s"} open`);
|
|
4670
|
+
return `${parts.join(" and ")} from earlier laps`;
|
|
4671
|
+
}
|
|
4672
|
+
var init_carried_work = __esm(() => {
|
|
4673
|
+
init_schema();
|
|
4674
|
+
init_review_findings();
|
|
4675
|
+
});
|
|
4676
|
+
|
|
4183
4677
|
// src/launcher/edit-guard.ts
|
|
4184
4678
|
import { isAbsolute, relative, resolve as resolve5 } from "path";
|
|
4185
4679
|
function guardsEdits(kind) {
|
|
@@ -4278,10 +4772,10 @@ var init_skills = __esm(() => {
|
|
|
4278
4772
|
|
|
4279
4773
|
// src/launcher/artifacts.ts
|
|
4280
4774
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
4281
|
-
import { fileURLToPath as
|
|
4775
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4282
4776
|
import { join as join7 } from "path";
|
|
4283
4777
|
function hookClientPath() {
|
|
4284
|
-
return resolveAsset(ASSET_ENV.hookClient,
|
|
4778
|
+
return resolveAsset(ASSET_ENV.hookClient, fileURLToPath3(new URL("./hook-client.ts", import.meta.url)));
|
|
4285
4779
|
}
|
|
4286
4780
|
function serverUrlFor(config2) {
|
|
4287
4781
|
return `http://localhost:${config2.serverPort}`;
|
|
@@ -4292,15 +4786,15 @@ function noCodeRule(docs2) {
|
|
|
4292
4786
|
function conflictResolutionRule() {
|
|
4293
4787
|
return "- **This session resolves a merge conflict, so it DOES write code here.** Edit the " + "conflicted files in this checkout, resolving them from the feature docs\u2019 intent, and " + "commit the merge. That is the exception and its whole extent: the edit guard exempts " + "writes only while the merge is in progress, so work the merge revealed but did not " + "cause still rides a ticket.";
|
|
4294
4788
|
}
|
|
4295
|
-
function renderSystemPrompt(feature, kind, waypoint, lap, purpose, runtime = DEFAULT_RUNTIME) {
|
|
4789
|
+
function renderSystemPrompt(feature, kind, waypoint, lap, purpose, runtime = DEFAULT_RUNTIME, carried) {
|
|
4296
4790
|
if (lap !== undefined)
|
|
4297
|
-
return renderRevisitPrompt(feature, lap, purpose, runtime);
|
|
4791
|
+
return renderRevisitPrompt(feature, lap, purpose, runtime, carried);
|
|
4298
4792
|
if (kind === "waypoint")
|
|
4299
4793
|
return renderWaypointPrompt(feature, waypoint, runtime);
|
|
4300
4794
|
if (kind === "converge")
|
|
4301
4795
|
return renderConvergePrompt(feature, runtime);
|
|
4302
4796
|
if (kind === "revisit")
|
|
4303
|
-
return renderRevisitPrompt(feature, lap, purpose, runtime);
|
|
4797
|
+
return renderRevisitPrompt(feature, lap, purpose, runtime, carried);
|
|
4304
4798
|
if (kind === "qa")
|
|
4305
4799
|
return renderQaPrompt(feature, runtime);
|
|
4306
4800
|
const docs2 = featureDocsRel(feature.slug);
|
|
@@ -4448,18 +4942,30 @@ function renderConvergePrompt(feature, runtime = DEFAULT_RUNTIME) {
|
|
|
4448
4942
|
].join(`
|
|
4449
4943
|
`);
|
|
4450
4944
|
}
|
|
4451
|
-
function renderRevisitPrompt(feature, lap, purpose, runtime = DEFAULT_RUNTIME) {
|
|
4945
|
+
function renderRevisitPrompt(feature, lap, purpose, runtime = DEFAULT_RUNTIME, carried) {
|
|
4452
4946
|
if (lap !== undefined && purpose === "resolve-conflict") {
|
|
4453
4947
|
throw new Error(`cannot brief one revisit session as both lap ${lap} and a conflict resolution \u2014 ` + "a lap advances the pipeline and writes no code; resolving a conflict writes code " + "and advances nothing. Launch them as separate sessions.");
|
|
4454
4948
|
}
|
|
4455
4949
|
const docs2 = featureDocsRel(feature.slug);
|
|
4950
|
+
const carriedSummary = carriedWorkSummary(carried);
|
|
4456
4951
|
const lapIteration = lap ? [
|
|
4457
4952
|
`## This is lap ${lap}`,
|
|
4458
4953
|
`You are running **lap ${lap}** of this feature (ADR-0010): the human test-drove what`,
|
|
4459
|
-
`lap ${lap - 1} burned and came back with what it taught them
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4954
|
+
`lap ${lap - 1} burned and came back with what it taught them.`,
|
|
4955
|
+
...carriedSummary ? [
|
|
4956
|
+
`**${carriedSummary} \u2014 address them.** They are this lap's opening agenda:`,
|
|
4957
|
+
`- \`${docs2}/test-notes.md\`, section \`## Carried, still open\` \u2014 every note carried`,
|
|
4958
|
+
" and not yet done, whatever lap captured it. It exists; read it.",
|
|
4959
|
+
"- `get_feature_context` \u2192 `openDefects` \u2014 each open defect with its title,",
|
|
4960
|
+
" location, detail and repro step.",
|
|
4961
|
+
`- \`${docs2}/spec.md\`, section \`## Later laps\` \u2014 scope parked by earlier laps,`,
|
|
4962
|
+
" OPTIONAL; a missing one is normal, not an error."
|
|
4963
|
+
] : [
|
|
4964
|
+
"Its inputs, both OPTIONAL \u2014 a missing one is normal, not an error:",
|
|
4965
|
+
`- \`${docs2}/test-notes.md\`, section \`## Carried, still open\` \u2014 every note carried`,
|
|
4966
|
+
" and not yet done, whatever lap captured it.",
|
|
4967
|
+
`- \`${docs2}/spec.md\`, section \`## Later laps\` \u2014 scope parked by earlier laps.`
|
|
4968
|
+
],
|
|
4463
4969
|
`New decisions go under a \`## Lap ${lap}\` heading in \`${docs2}/decisions.md\`.`,
|
|
4464
4970
|
"",
|
|
4465
4971
|
"Unlike an ordinary revisit a lap MOVES the pipeline, and only this session will:",
|
|
@@ -4801,11 +5307,12 @@ function renderRunMcpConfig(runId, config2) {
|
|
|
4801
5307
|
};
|
|
4802
5308
|
}
|
|
4803
5309
|
function renderSessionPrompt(input) {
|
|
4804
|
-
const { session, feature, waypoint, prepare, projectBrief, driveFix, lap, purpose, runtime } = input;
|
|
5310
|
+
const { session, feature, waypoint, prepare, projectBrief, driveFix, lap, purpose, runtime, carried } = input;
|
|
4805
5311
|
if (driveFix)
|
|
4806
5312
|
return renderDriveFixPrompt(driveFix);
|
|
4807
|
-
if (feature)
|
|
4808
|
-
return renderSystemPrompt(feature, session.kind, waypoint, lap, purpose, runtime);
|
|
5313
|
+
if (feature) {
|
|
5314
|
+
return renderSystemPrompt(feature, session.kind, waypoint, lap, purpose, runtime, carried);
|
|
5315
|
+
}
|
|
4809
5316
|
if (prepare)
|
|
4810
5317
|
return renderPreparePrompt(prepare);
|
|
4811
5318
|
if (projectBrief)
|
|
@@ -4828,6 +5335,7 @@ var RUNCASTLE_MCP_ALLOW_RULES, SESSION_BASH_READ_RULES, SESSION_BASH_WRITE_RULES
|
|
|
4828
5335
|
var init_artifacts = __esm(() => {
|
|
4829
5336
|
init_src();
|
|
4830
5337
|
init_paths();
|
|
5338
|
+
init_carried_work();
|
|
4831
5339
|
init_asset_paths();
|
|
4832
5340
|
init_edit_guard();
|
|
4833
5341
|
init_skills();
|
|
@@ -4877,8 +5385,8 @@ var init_artifacts = __esm(() => {
|
|
|
4877
5385
|
|
|
4878
5386
|
// src/launcher/skills-root.ts
|
|
4879
5387
|
import { existsSync as existsSync7 } from "fs";
|
|
4880
|
-
import { dirname as
|
|
4881
|
-
import { fileURLToPath as
|
|
5388
|
+
import { dirname as dirname3, join as join8, resolve as resolve6 } from "path";
|
|
5389
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4882
5390
|
function resolveSkillsRoot(fromDir) {
|
|
4883
5391
|
const override = process.env[SKILLS_DIR_ENV];
|
|
4884
5392
|
if (override) {
|
|
@@ -4894,7 +5402,7 @@ function resolveSkillsRoot(fromDir) {
|
|
|
4894
5402
|
searched.push(candidate);
|
|
4895
5403
|
if (existsSync7(join8(candidate, SKILLS_MARKER)))
|
|
4896
5404
|
return candidate;
|
|
4897
|
-
const parent =
|
|
5405
|
+
const parent = dirname3(dir);
|
|
4898
5406
|
if (parent === dir)
|
|
4899
5407
|
break;
|
|
4900
5408
|
dir = parent;
|
|
@@ -4903,7 +5411,7 @@ function resolveSkillsRoot(fromDir) {
|
|
|
4903
5411
|
${searched.join(`
|
|
4904
5412
|
`)}`);
|
|
4905
5413
|
}
|
|
4906
|
-
function resolvePluginDir(fromDir =
|
|
5414
|
+
function resolvePluginDir(fromDir = dirname3(fileURLToPath4(import.meta.url))) {
|
|
4907
5415
|
const rel = join8("packages", "skills", "packs", "runcastle");
|
|
4908
5416
|
const override = process.env[SKILLS_DIR_ENV];
|
|
4909
5417
|
if (override) {
|
|
@@ -4919,7 +5427,7 @@ function resolvePluginDir(fromDir = dirname2(fileURLToPath3(import.meta.url))) {
|
|
|
4919
5427
|
searched.push(candidate);
|
|
4920
5428
|
if (existsSync7(candidate))
|
|
4921
5429
|
return candidate;
|
|
4922
|
-
const parent =
|
|
5430
|
+
const parent = dirname3(dir);
|
|
4923
5431
|
if (parent === dir)
|
|
4924
5432
|
break;
|
|
4925
5433
|
dir = parent;
|
|
@@ -5031,7 +5539,6 @@ var init_codex_auth = () => {};
|
|
|
5031
5539
|
|
|
5032
5540
|
// src/doctor/doctor.ts
|
|
5033
5541
|
import { existsSync as existsSync9, statSync } from "fs";
|
|
5034
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5035
5542
|
async function versionProbe(exec, id, label, bin, fix) {
|
|
5036
5543
|
const out = await exec(bin, ["--version"]);
|
|
5037
5544
|
if (out.ok && out.code === 0) {
|
|
@@ -5287,7 +5794,7 @@ async function runDoctor(env) {
|
|
|
5287
5794
|
const { exec } = env;
|
|
5288
5795
|
const processEnv = env.env ?? process.env;
|
|
5289
5796
|
const imageName = env.imageName ?? DEFAULT_SANDBOX_IMAGE;
|
|
5290
|
-
const burnerDockerfile = env.burnerDockerfile ??
|
|
5797
|
+
const burnerDockerfile = env.burnerDockerfile ?? burnerDockerfilePath();
|
|
5291
5798
|
const fileMtime = env.fileMtime ?? ((path) => statSync(path).mtime);
|
|
5292
5799
|
const required = new Set(env.runtimes ?? [DEFAULT_RUNTIME]);
|
|
5293
5800
|
const results = [
|
|
@@ -5319,6 +5826,7 @@ function exitCodeFor(report, mode) {
|
|
|
5319
5826
|
var RUNTIME_SPECS;
|
|
5320
5827
|
var init_doctor = __esm(() => {
|
|
5321
5828
|
init_src();
|
|
5829
|
+
init_asset_paths();
|
|
5322
5830
|
init_codex_auth();
|
|
5323
5831
|
RUNTIME_SPECS = {
|
|
5324
5832
|
"claude-code": {
|
|
@@ -5363,7 +5871,7 @@ import {
|
|
|
5363
5871
|
statSync as statSync2,
|
|
5364
5872
|
writeFileSync as writeFileSync3
|
|
5365
5873
|
} from "fs";
|
|
5366
|
-
import { basename, dirname as
|
|
5874
|
+
import { basename, dirname as dirname4, join as join10, resolve as resolve7 } from "path";
|
|
5367
5875
|
function codexHomeDir2(sessionId) {
|
|
5368
5876
|
return join10(sessionDir(sessionId), "codex-home");
|
|
5369
5877
|
}
|
|
@@ -5413,7 +5921,7 @@ function mcpServerTables(userConfigToml) {
|
|
|
5413
5921
|
`).trim();
|
|
5414
5922
|
}
|
|
5415
5923
|
function readInheritedMcpServers(env = process.env) {
|
|
5416
|
-
const path = join10(
|
|
5924
|
+
const path = join10(dirname4(realCodexAuthFile(env)), "config.toml");
|
|
5417
5925
|
if (!existsSync10(path))
|
|
5418
5926
|
return "";
|
|
5419
5927
|
try {
|
|
@@ -5485,8 +5993,8 @@ function gitInfoDir(worktreePath) {
|
|
|
5485
5993
|
if (!named)
|
|
5486
5994
|
return null;
|
|
5487
5995
|
const gitDir = resolve7(worktreePath, named[1].trim());
|
|
5488
|
-
const parent =
|
|
5489
|
-
return basename(parent) === "worktrees" ? join10(
|
|
5996
|
+
const parent = dirname4(gitDir);
|
|
5997
|
+
return basename(parent) === "worktrees" ? join10(dirname4(parent), "info") : join10(gitDir, "info");
|
|
5490
5998
|
}
|
|
5491
5999
|
function buildCodexArgs(input) {
|
|
5492
6000
|
return [
|
|
@@ -5592,7 +6100,7 @@ var init_runtimes = __esm(() => {
|
|
|
5592
6100
|
|
|
5593
6101
|
// src/launcher/sessions.ts
|
|
5594
6102
|
import { statSync as statSync3 } from "fs";
|
|
5595
|
-
import { and as
|
|
6103
|
+
import { and as and8, desc as desc3, eq as eq8, inArray as inArray2, isNotNull as isNotNull2, sql as sql2 } from "drizzle-orm";
|
|
5596
6104
|
function createSessionRow(ctx, input) {
|
|
5597
6105
|
const row = ctx.db.insert(sessions).values({
|
|
5598
6106
|
id: newId("sess"),
|
|
@@ -5614,11 +6122,11 @@ function createSessionRow(ctx, input) {
|
|
|
5614
6122
|
return rowToSession(row);
|
|
5615
6123
|
}
|
|
5616
6124
|
function getSessionRow(ctx, id) {
|
|
5617
|
-
const row = ctx.db.select().from(sessions).where(
|
|
6125
|
+
const row = ctx.db.select().from(sessions).where(eq8(sessions.id, id)).get();
|
|
5618
6126
|
return row ? rowToSession(row) : null;
|
|
5619
6127
|
}
|
|
5620
6128
|
function activeSessionsForFeature(ctx, featureId) {
|
|
5621
|
-
return ctx.db.select().from(sessions).where(
|
|
6129
|
+
return ctx.db.select().from(sessions).where(and8(eq8(sessions.featureId, featureId), inArray2(sessions.status, ["launching", "live"]))).all().map(rowToSession);
|
|
5622
6130
|
}
|
|
5623
6131
|
function markAgentWorking(ctx, id) {
|
|
5624
6132
|
setAwaitingInput(ctx, id, false);
|
|
@@ -5627,7 +6135,7 @@ function markAwaitingInput(ctx, id) {
|
|
|
5627
6135
|
setAwaitingInput(ctx, id, true);
|
|
5628
6136
|
}
|
|
5629
6137
|
function setAwaitingInput(ctx, id, awaitingInput) {
|
|
5630
|
-
ctx.db.update(sessions).set({ awaitingInput }).where(
|
|
6138
|
+
ctx.db.update(sessions).set({ awaitingInput }).where(eq8(sessions.id, id)).run();
|
|
5631
6139
|
}
|
|
5632
6140
|
function markSessionLive(ctx, id, input = {}) {
|
|
5633
6141
|
const existing = getSessionRow(ctx, id);
|
|
@@ -5637,7 +6145,7 @@ function markSessionLive(ctx, id, input = {}) {
|
|
|
5637
6145
|
status: "live",
|
|
5638
6146
|
ccSessionId: input.ccSessionId ?? existing.ccSessionId ?? null,
|
|
5639
6147
|
transcriptPath: input.transcriptPath ?? existing.transcriptPath ?? null
|
|
5640
|
-
}).where(
|
|
6148
|
+
}).where(eq8(sessions.id, id)).run();
|
|
5641
6149
|
const firstTimeLive = existing.status !== "live";
|
|
5642
6150
|
if (firstTimeLive) {
|
|
5643
6151
|
promoteLastSession(ctx, id);
|
|
@@ -5645,8 +6153,9 @@ function markSessionLive(ctx, id, input = {}) {
|
|
|
5645
6153
|
}
|
|
5646
6154
|
return getSessionRow(ctx, id);
|
|
5647
6155
|
}
|
|
5648
|
-
function lapKickoff(lap) {
|
|
5649
|
-
|
|
6156
|
+
function lapKickoff(lap, carried) {
|
|
6157
|
+
const summary = carriedWorkSummary(carried);
|
|
6158
|
+
return `Proceed with your task: invoke the /runcastle:revisit skill for LAP ${lap} REVIEW ITERATION. ` + (summary ? `${summary} \u2014 address them. ` : "") + `Call get_feature_context, then read this feature's test-notes.md (its "## Carried, still ` + 'open" section \u2014 every note carried and not yet done, whatever lap captured it), the open ' + `defects in that payload's openDefects, and the "## Later laps" section of its spec.md. ` + (summary ? 'THE "## Later laps" SECTION MAY NOT EXIST; that is normal, not an error. ' : "ANY OF THOSE MAY NOT EXIST YET; that is normal, not an error \u2014 say so and carry on from " + "what I tell you. ") + "Interview me about what the test drive taught: what was wrong, what was missing, " + "what I want next. Write what we settle on into decisions.md and amend spec.md for this lap " + '(pruning anything you promote out of "## Later laps"), then call emit_tickets for this ' + `lap's work. Finish in THIS session: complete_phase through ideation \u2192 spec \u2192 tickets, then ` + "tell me to review the cards and click Burn.";
|
|
5650
6159
|
}
|
|
5651
6160
|
function kickoffLineFor(kind, override, runtime = DEFAULT_RUNTIME) {
|
|
5652
6161
|
return override ?? runtimeAdapterFor(runtime).kickoffLine(kind);
|
|
@@ -5656,7 +6165,7 @@ function lapInFlight(input) {
|
|
|
5656
6165
|
}
|
|
5657
6166
|
function planKickoff(input) {
|
|
5658
6167
|
const running = input.lapInFlight === true;
|
|
5659
|
-
const lapBriefing = input.lap > 1 ? lapKickoff(input.lap) : undefined;
|
|
6168
|
+
const lapBriefing = input.lap > 1 ? lapKickoff(input.lap, input.carried) : undefined;
|
|
5660
6169
|
const line = input.kickoffLine ?? (running ? lapBriefing : undefined);
|
|
5661
6170
|
const lap = running ? input.lap : undefined;
|
|
5662
6171
|
if (!line)
|
|
@@ -5888,31 +6397,31 @@ function markSessionEnded(ctx, id) {
|
|
|
5888
6397
|
forgetKickoff(id);
|
|
5889
6398
|
if (existing.featureId)
|
|
5890
6399
|
stopDocsWatch(existing.featureId);
|
|
5891
|
-
ctx.db.update(sessions).set({ status: "ended", ...existing.endedAt === undefined ? { endedAt: Date.now() } : {} }).where(
|
|
6400
|
+
ctx.db.update(sessions).set({ status: "ended", ...existing.endedAt === undefined ? { endedAt: Date.now() } : {} }).where(eq8(sessions.id, id)).run();
|
|
5892
6401
|
return getSessionRow(ctx, id);
|
|
5893
6402
|
}
|
|
5894
6403
|
function mostRecentResumableSession(ctx, featureId, kind) {
|
|
5895
|
-
const row = ctx.db.select().from(sessions).where(
|
|
6404
|
+
const row = ctx.db.select().from(sessions).where(and8(eq8(sessions.featureId, featureId), eq8(sessions.status, "ended"), isNotNull2(sessions.ccSessionId), ...kind ? [eq8(sessions.kind, kind)] : [])).orderBy(desc3(sql2`rowid`)).limit(1).get();
|
|
5896
6405
|
return row ? rowToSession(row) : null;
|
|
5897
6406
|
}
|
|
5898
6407
|
function activeProjectSession(ctx, projectId, kind) {
|
|
5899
|
-
const row = ctx.db.select().from(sessions).where(
|
|
6408
|
+
const row = ctx.db.select().from(sessions).where(and8(eq8(sessions.projectId, projectId), eq8(sessions.kind, kind), inArray2(sessions.status, ["launching", "live"]))).orderBy(desc3(sql2`rowid`)).limit(1).get();
|
|
5900
6409
|
return row ? rowToSession(row) : null;
|
|
5901
6410
|
}
|
|
5902
6411
|
function projectSessions(ctx, projectId, kind) {
|
|
5903
|
-
return ctx.db.select().from(sessions).where(
|
|
6412
|
+
return ctx.db.select().from(sessions).where(and8(eq8(sessions.projectId, projectId), eq8(sessions.kind, kind))).orderBy(desc3(sql2`rowid`)).all().map(rowToSession);
|
|
5904
6413
|
}
|
|
5905
6414
|
function setSessionTitle(ctx, id, title) {
|
|
5906
|
-
ctx.db.update(sessions).set({ title }).where(
|
|
6415
|
+
ctx.db.update(sessions).set({ title }).where(eq8(sessions.id, id)).run();
|
|
5907
6416
|
}
|
|
5908
6417
|
function clearSessionTitle(ctx, id) {
|
|
5909
|
-
ctx.db.update(sessions).set({ title: null }).where(
|
|
6418
|
+
ctx.db.update(sessions).set({ title: null }).where(eq8(sessions.id, id)).run();
|
|
5910
6419
|
}
|
|
5911
6420
|
function hasCompletedProjectSession(ctx, projectId, kind) {
|
|
5912
|
-
return ctx.db.select({ id: sessions.id }).from(sessions).where(
|
|
6421
|
+
return ctx.db.select({ id: sessions.id }).from(sessions).where(and8(eq8(sessions.projectId, projectId), eq8(sessions.kind, kind), eq8(sessions.status, "ended"))).limit(1).get() !== undefined;
|
|
5913
6422
|
}
|
|
5914
6423
|
function mostRecentResumableProjectSession(ctx, projectId, kind) {
|
|
5915
|
-
const row = ctx.db.select().from(sessions).where(
|
|
6424
|
+
const row = ctx.db.select().from(sessions).where(and8(eq8(sessions.projectId, projectId), eq8(sessions.kind, kind), eq8(sessions.status, "ended"), isNotNull2(sessions.ccSessionId))).orderBy(desc3(sql2`rowid`)).limit(1).get();
|
|
5916
6425
|
return row ? rowToSession(row) : null;
|
|
5917
6426
|
}
|
|
5918
6427
|
function envInt(name, fallback) {
|
|
@@ -5952,17 +6461,17 @@ function transcriptBytes(session) {
|
|
|
5952
6461
|
}
|
|
5953
6462
|
function reentryCount(ctx, scope) {
|
|
5954
6463
|
const where = [
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
...scope.featureId ? [
|
|
5958
|
-
...scope.projectId ? [
|
|
5959
|
-
...scope.kind ? [
|
|
6464
|
+
eq8(sessions.status, "ended"),
|
|
6465
|
+
isNotNull2(sessions.ccSessionId),
|
|
6466
|
+
...scope.featureId ? [eq8(sessions.featureId, scope.featureId)] : [],
|
|
6467
|
+
...scope.projectId ? [eq8(sessions.projectId, scope.projectId)] : [],
|
|
6468
|
+
...scope.kind ? [eq8(sessions.kind, scope.kind)] : []
|
|
5960
6469
|
];
|
|
5961
|
-
const row = ctx.db.select({ n: sql2`count(*)` }).from(sessions).where(
|
|
6470
|
+
const row = ctx.db.select({ n: sql2`count(*)` }).from(sessions).where(and8(...where)).get();
|
|
5962
6471
|
return row?.n ?? 0;
|
|
5963
6472
|
}
|
|
5964
6473
|
function mostRecentLiveSession(ctx) {
|
|
5965
|
-
const row = ctx.db.select().from(sessions).where(
|
|
6474
|
+
const row = ctx.db.select().from(sessions).where(eq8(sessions.status, "live")).orderBy(desc3(sql2`rowid`)).limit(1).get();
|
|
5966
6475
|
return row ? rowToSession(row) : null;
|
|
5967
6476
|
}
|
|
5968
6477
|
var KICKOFF_DELAY_MS = 1500, KICKOFF_SUBMIT_DELAY_MS = 350, KICKOFF_CONFIRM_MS = 12000, KICKOFF_MAX_ATTEMPTS = 3, CLEAR_INPUT = "\x15", SESSION_READY_TIMEOUT_MS = 25000, PREPARE_CONFIRM_KICKOFF, RESUME_KICKOFF_PREFIX, pendingKickoffOverrides, deliveries, MATCH_PREFIX = 40, landedProjectSessions, inFlightLandings, LANDING_KEPT, RESUME_MAX_TRANSCRIPT_BYTES, RESUME_MAX_REENTRIES;
|
|
@@ -5973,6 +6482,7 @@ var init_sessions = __esm(() => {
|
|
|
5973
6482
|
init_docs_watch();
|
|
5974
6483
|
init_events();
|
|
5975
6484
|
init_git();
|
|
6485
|
+
init_carried_work();
|
|
5976
6486
|
init_waypoints();
|
|
5977
6487
|
init_repo();
|
|
5978
6488
|
init_runtimes();
|
|
@@ -5989,9 +6499,9 @@ var init_sessions = __esm(() => {
|
|
|
5989
6499
|
});
|
|
5990
6500
|
|
|
5991
6501
|
// src/launcher/reconcile.ts
|
|
5992
|
-
import { inArray as
|
|
6502
|
+
import { inArray as inArray3 } from "drizzle-orm";
|
|
5993
6503
|
function reconcileStaleSessions(ctx) {
|
|
5994
|
-
const stale = ctx.db.select().from(sessions).where(
|
|
6504
|
+
const stale = ctx.db.select().from(sessions).where(inArray3(sessions.status, ["launching", "live"])).all().map(rowToSession);
|
|
5995
6505
|
const reconciled = [];
|
|
5996
6506
|
for (const session of stale) {
|
|
5997
6507
|
const entry = ptyRegistry().get(session.id);
|
|
@@ -6039,428 +6549,9 @@ async function getRuntimeCtx() {
|
|
|
6039
6549
|
}
|
|
6040
6550
|
var current = null;
|
|
6041
6551
|
|
|
6042
|
-
// src/services/review-findings.ts
|
|
6043
|
-
import { and as and6, asc as asc3, eq as eq6, isNotNull as isNotNull2 } from "drizzle-orm";
|
|
6044
|
-
function rowToFinding(row) {
|
|
6045
|
-
return ReviewFinding.parse(row);
|
|
6046
|
-
}
|
|
6047
|
-
function getFinding(ctx, id) {
|
|
6048
|
-
const row = ctx.db.select().from(reviewFindings).where(eq6(reviewFindings.id, id)).get();
|
|
6049
|
-
if (!row)
|
|
6050
|
-
throw new NotFoundError(`review finding ${id} not found`);
|
|
6051
|
-
return rowToFinding(row);
|
|
6052
|
-
}
|
|
6053
|
-
function listByFeature3(ctx, featureId) {
|
|
6054
|
-
return ctx.db.select().from(reviewFindings).where(eq6(reviewFindings.featureId, featureId)).orderBy(asc3(reviewFindings.createdAt), asc3(reviewFindings.id)).all().map(rowToFinding);
|
|
6055
|
-
}
|
|
6056
|
-
function buildFixTicket(finding) {
|
|
6057
|
-
return {
|
|
6058
|
-
title: finding.title,
|
|
6059
|
-
goal: `Fix: ${finding.title}`,
|
|
6060
|
-
context: [
|
|
6061
|
-
`Location: ${finding.location}`,
|
|
6062
|
-
`Citation: ${finding.citation}`,
|
|
6063
|
-
`Detail: ${finding.detail}`,
|
|
6064
|
-
`Repro step: ${finding.reproStep}`
|
|
6065
|
-
].join(`
|
|
6066
|
-
|
|
6067
|
-
`),
|
|
6068
|
-
acceptanceCriteria: [
|
|
6069
|
-
`The repro step no longer reproduces / the cited criterion holds: ${finding.reproStep}`
|
|
6070
|
-
],
|
|
6071
|
-
seams: [],
|
|
6072
|
-
blockedBy: [],
|
|
6073
|
-
kind: "implementation",
|
|
6074
|
-
originFindingId: finding.id
|
|
6075
|
-
};
|
|
6076
|
-
}
|
|
6077
|
-
function reportFinding(ctx, args) {
|
|
6078
|
-
const { featureId, reviewTicket, input } = args;
|
|
6079
|
-
const feature = getFeatureRow(ctx, featureId);
|
|
6080
|
-
const id = newId("finding");
|
|
6081
|
-
const ticketCount = ctx.db.select({ id: reviewFindings.id }).from(reviewFindings).where(and6(eq6(reviewFindings.reviewTicketId, reviewTicket.id), isNotNull2(reviewFindings.fixTicketId))).all().length;
|
|
6082
|
-
const verification = reviewTicket.passKind === "verification";
|
|
6083
|
-
const overCap = input.kind === "defect" && !verification && ticketCount >= AUTO_FIX_CAP;
|
|
6084
|
-
ctx.db.insert(reviewFindings).values({
|
|
6085
|
-
id,
|
|
6086
|
-
featureId,
|
|
6087
|
-
lap: feature.lap,
|
|
6088
|
-
reviewTicketId: reviewTicket.id,
|
|
6089
|
-
...input,
|
|
6090
|
-
reproStep: input.reproStep ?? "",
|
|
6091
|
-
status: "open",
|
|
6092
|
-
openReason: verification && input.kind === "defect" ? "verification" : overCap ? "over-cap" : null,
|
|
6093
|
-
failureReason: null,
|
|
6094
|
-
fixTicketId: null,
|
|
6095
|
-
createdAt: Date.now()
|
|
6096
|
-
}).run();
|
|
6097
|
-
let fixTicket = null;
|
|
6098
|
-
if (input.kind === "defect" && !overCap && !verification) {
|
|
6099
|
-
const finding2 = getFinding(ctx, id);
|
|
6100
|
-
fixTicket = storeTickets(ctx, featureId, [
|
|
6101
|
-
{ ...buildFixTicket(finding2), blockedBy: [reviewTicket.seq] }
|
|
6102
|
-
], { blockedByAreGlobal: true })[0];
|
|
6103
|
-
ctx.db.update(reviewFindings).set({ fixTicketId: fixTicket.id }).where(eq6(reviewFindings.id, id)).run();
|
|
6104
|
-
}
|
|
6105
|
-
const finding = getFinding(ctx, id);
|
|
6106
|
-
emit(ctx, featureId, {
|
|
6107
|
-
type: "finding.reported",
|
|
6108
|
-
message: `${input.kind} reported: ${input.title}`,
|
|
6109
|
-
ticketId: fixTicket?.id,
|
|
6110
|
-
data: { findingId: id, fixTicketId: fixTicket?.id, overCap }
|
|
6111
|
-
});
|
|
6112
|
-
return { finding, fixTicket, overCap };
|
|
6113
|
-
}
|
|
6114
|
-
function updateStatus(ctx, findingId, patch) {
|
|
6115
|
-
const current2 = getFinding(ctx, findingId);
|
|
6116
|
-
ctx.db.update(reviewFindings).set(patch).where(eq6(reviewFindings.id, findingId)).run();
|
|
6117
|
-
emit(ctx, current2.featureId, {
|
|
6118
|
-
type: "finding.updated",
|
|
6119
|
-
message: `finding ${findingId} marked ${patch.status}`,
|
|
6120
|
-
data: { findingId, status: patch.status, reason: patch.failureReason }
|
|
6121
|
-
});
|
|
6122
|
-
return getFinding(ctx, findingId);
|
|
6123
|
-
}
|
|
6124
|
-
function markFixing(ctx, findingId) {
|
|
6125
|
-
return updateStatus(ctx, findingId, {
|
|
6126
|
-
status: "fixing",
|
|
6127
|
-
openReason: null,
|
|
6128
|
-
failureReason: null
|
|
6129
|
-
});
|
|
6130
|
-
}
|
|
6131
|
-
function markFixed(ctx, findingId) {
|
|
6132
|
-
return updateStatus(ctx, findingId, {
|
|
6133
|
-
status: "fixed",
|
|
6134
|
-
openReason: null,
|
|
6135
|
-
failureReason: null
|
|
6136
|
-
});
|
|
6137
|
-
}
|
|
6138
|
-
function markFailed(ctx, findingId, reason) {
|
|
6139
|
-
return updateStatus(ctx, findingId, {
|
|
6140
|
-
status: "failed",
|
|
6141
|
-
openReason: "fix-failed",
|
|
6142
|
-
failureReason: reason ?? null
|
|
6143
|
-
});
|
|
6144
|
-
}
|
|
6145
|
-
function markFixProgress(ctx, findingId, progress, reason) {
|
|
6146
|
-
if (progress === "fixing")
|
|
6147
|
-
return markFixing(ctx, findingId);
|
|
6148
|
-
if (progress === "fixed")
|
|
6149
|
-
return markFixed(ctx, findingId);
|
|
6150
|
-
return markFailed(ctx, findingId, reason);
|
|
6151
|
-
}
|
|
6152
|
-
function dismiss(ctx, findingId) {
|
|
6153
|
-
return updateStatus(ctx, findingId, {
|
|
6154
|
-
status: "dismissed",
|
|
6155
|
-
openReason: null,
|
|
6156
|
-
failureReason: null
|
|
6157
|
-
});
|
|
6158
|
-
}
|
|
6159
|
-
function defectState(finding, fixTicket) {
|
|
6160
|
-
if (finding.status === "dismissed")
|
|
6161
|
-
return "dismissed";
|
|
6162
|
-
if (finding.status === "fixed" || fixTicket?.status === "done")
|
|
6163
|
-
return "fixed";
|
|
6164
|
-
if (fixTicket && fixTicket.status !== "failed" && fixTicket.status !== "cancelled") {
|
|
6165
|
-
return "fixing";
|
|
6166
|
-
}
|
|
6167
|
-
return finding.status === "open" || finding.status === "failed" || fixTicket?.status === "failed" || fixTicket?.status === "cancelled" ? "open" : "fixing";
|
|
6168
|
-
}
|
|
6169
|
-
function viewByFeature(ctx, featureId) {
|
|
6170
|
-
const findings = listByFeature3(ctx, featureId);
|
|
6171
|
-
const tickets2 = listByFeature2(ctx, featureId);
|
|
6172
|
-
const summary = { found: 0, fixed: 0, open: 0, observations: 0 };
|
|
6173
|
-
const openDefects = [];
|
|
6174
|
-
for (const finding of findings) {
|
|
6175
|
-
if (finding.kind === "observation") {
|
|
6176
|
-
summary.observations += 1;
|
|
6177
|
-
continue;
|
|
6178
|
-
}
|
|
6179
|
-
summary.found += 1;
|
|
6180
|
-
const state = defectState(finding, fixTicketOf(finding, tickets2));
|
|
6181
|
-
if (state === "fixed")
|
|
6182
|
-
summary.fixed += 1;
|
|
6183
|
-
else if (state === "open") {
|
|
6184
|
-
summary.open += 1;
|
|
6185
|
-
openDefects.push(finding);
|
|
6186
|
-
}
|
|
6187
|
-
}
|
|
6188
|
-
return { findings, openDefects, summary };
|
|
6189
|
-
}
|
|
6190
|
-
function fixTicketOf(finding, tickets2) {
|
|
6191
|
-
return finding.fixTicketId ? tickets2.find((t) => t.id === finding.fixTicketId) : undefined;
|
|
6192
|
-
}
|
|
6193
|
-
function promoteOpenDefects(ctx, featureId, findingIds) {
|
|
6194
|
-
const open = viewByFeature(ctx, featureId).openDefects;
|
|
6195
|
-
const defects = findingIds ? findingIds.map((id) => {
|
|
6196
|
-
const finding = open.find((candidate) => candidate.id === id);
|
|
6197
|
-
if (!finding)
|
|
6198
|
-
throw new InvalidInputError(`finding ${id} is not an open defect of this feature`);
|
|
6199
|
-
return finding;
|
|
6200
|
-
}) : open;
|
|
6201
|
-
if (findingIds && new Set(findingIds).size !== findingIds.length)
|
|
6202
|
-
throw new InvalidInputError("duplicate finding selected");
|
|
6203
|
-
if (defects.length === 0)
|
|
6204
|
-
throw new InvalidInputError("no open defects to fix");
|
|
6205
|
-
const tickets2 = storeTickets(ctx, featureId, defects.map(buildFixTicket));
|
|
6206
|
-
const findings = defects.map((defect, index2) => {
|
|
6207
|
-
ctx.db.update(reviewFindings).set({
|
|
6208
|
-
status: "fixing",
|
|
6209
|
-
openReason: null,
|
|
6210
|
-
failureReason: null,
|
|
6211
|
-
fixTicketId: tickets2[index2].id
|
|
6212
|
-
}).where(eq6(reviewFindings.id, defect.id)).run();
|
|
6213
|
-
return getFinding(ctx, defect.id);
|
|
6214
|
-
});
|
|
6215
|
-
emit(ctx, featureId, {
|
|
6216
|
-
type: "finding.fixing",
|
|
6217
|
-
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(", ")}`}`,
|
|
6218
|
-
data: { findingIds: findings.map((f) => f.id), seqs: tickets2.map((t) => t.seq) }
|
|
6219
|
-
});
|
|
6220
|
-
return { findings, tickets: tickets2 };
|
|
6221
|
-
}
|
|
6222
|
-
var AUTO_FIX_CAP = 8;
|
|
6223
|
-
var init_review_findings = __esm(() => {
|
|
6224
|
-
init_src();
|
|
6225
|
-
init_schema();
|
|
6226
|
-
init_errors();
|
|
6227
|
-
init_events();
|
|
6228
|
-
init_repo();
|
|
6229
|
-
init_tickets();
|
|
6230
|
-
});
|
|
6231
|
-
|
|
6232
|
-
// src/services/tickets.ts
|
|
6233
|
-
import { and as and7, asc as asc4, eq as eq7, inArray as inArray3 } from "drizzle-orm";
|
|
6234
|
-
function rowToTicket(row) {
|
|
6235
|
-
return Ticket.parse({
|
|
6236
|
-
id: row.id,
|
|
6237
|
-
featureId: row.featureId,
|
|
6238
|
-
seq: row.seq,
|
|
6239
|
-
title: row.title,
|
|
6240
|
-
goal: row.goal,
|
|
6241
|
-
context: row.context,
|
|
6242
|
-
acceptanceCriteria: row.acceptanceCriteria,
|
|
6243
|
-
seams: row.seams,
|
|
6244
|
-
blockedBy: row.blockedBy,
|
|
6245
|
-
kind: row.kind,
|
|
6246
|
-
passKind: row.passKind,
|
|
6247
|
-
reviewedCommit: row.reviewedCommit,
|
|
6248
|
-
completedAt: row.completedAt,
|
|
6249
|
-
model: row.model ?? undefined,
|
|
6250
|
-
lap: row.lap,
|
|
6251
|
-
status: row.status,
|
|
6252
|
-
commits: row.commits,
|
|
6253
|
-
error: row.error ?? undefined,
|
|
6254
|
-
attemptBranch: row.attemptBranch ?? undefined,
|
|
6255
|
-
conflictFiles: row.conflictFiles ?? undefined,
|
|
6256
|
-
digest: row.digest ?? undefined,
|
|
6257
|
-
originFindingId: row.originFindingId ?? undefined
|
|
6258
|
-
});
|
|
6259
|
-
}
|
|
6260
|
-
function resolveBlocking(inputs, startSeq) {
|
|
6261
|
-
try {
|
|
6262
|
-
return resolveBatchBlocking(inputs, { startSeq });
|
|
6263
|
-
} catch (e) {
|
|
6264
|
-
if (e instanceof BlockingEdgeError)
|
|
6265
|
-
throw new InvalidInputError(e.message);
|
|
6266
|
-
throw e;
|
|
6267
|
-
}
|
|
6268
|
-
}
|
|
6269
|
-
function normalizeModel(ctx, value) {
|
|
6270
|
-
const id = value?.trim();
|
|
6271
|
-
if (!id)
|
|
6272
|
-
return null;
|
|
6273
|
-
const roster = modelRoster(ctx.config);
|
|
6274
|
-
if (!roster.some((m) => m.id === id)) {
|
|
6275
|
-
throw new InvalidInputError(`unknown model "${id}" \u2014 assign one of the configured models: ${roster.map((m) => m.id).join(", ")}`);
|
|
6276
|
-
}
|
|
6277
|
-
return id;
|
|
6278
|
-
}
|
|
6279
|
-
function listByFeature2(ctx, featureId) {
|
|
6280
|
-
return ctx.db.select().from(tickets).where(eq7(tickets.featureId, featureId)).orderBy(asc4(tickets.seq)).all().map(rowToTicket);
|
|
6281
|
-
}
|
|
6282
|
-
function storeTickets(ctx, featureId, inputs, options = {}) {
|
|
6283
|
-
if (inputs.length === 0)
|
|
6284
|
-
return [];
|
|
6285
|
-
const { lap } = getFeatureRow(ctx, featureId);
|
|
6286
|
-
const existing = ctx.db.select({ seq: tickets.seq }).from(tickets).where(eq7(tickets.featureId, featureId)).all();
|
|
6287
|
-
const startSeq = existing.reduce((max2, r) => Math.max(max2, r.seq), 0) + 1;
|
|
6288
|
-
const resolved = options.blockedByAreGlobal ? inputs.map((input, index2) => ({ seq: startSeq + index2, blockedBy: input.blockedBy })) : resolveBlocking(inputs, startSeq);
|
|
6289
|
-
const rows = inputs.map((t, i) => ({
|
|
6290
|
-
id: newId("tkt"),
|
|
6291
|
-
featureId,
|
|
6292
|
-
seq: resolved[i].seq,
|
|
6293
|
-
title: t.title,
|
|
6294
|
-
goal: t.goal,
|
|
6295
|
-
context: t.context,
|
|
6296
|
-
acceptanceCriteria: t.acceptanceCriteria,
|
|
6297
|
-
seams: t.seams,
|
|
6298
|
-
blockedBy: resolved[i].blockedBy,
|
|
6299
|
-
kind: t.kind ?? "implementation",
|
|
6300
|
-
passKind: t.passKind ?? "review",
|
|
6301
|
-
reviewedCommit: null,
|
|
6302
|
-
completedAt: null,
|
|
6303
|
-
model: normalizeModel(ctx, t.model),
|
|
6304
|
-
lap,
|
|
6305
|
-
status: "pending",
|
|
6306
|
-
commits: [],
|
|
6307
|
-
error: null,
|
|
6308
|
-
attemptBranch: null,
|
|
6309
|
-
conflictFiles: null,
|
|
6310
|
-
digest: null,
|
|
6311
|
-
originFindingId: t.originFindingId ?? null
|
|
6312
|
-
}));
|
|
6313
|
-
ctx.db.insert(tickets).values(rows).run();
|
|
6314
|
-
emit(ctx, featureId, {
|
|
6315
|
-
type: "tickets.stored",
|
|
6316
|
-
message: `${rows.length} ticket(s) stored`,
|
|
6317
|
-
data: { count: rows.length, seqs: rows.map((r) => r.seq) }
|
|
6318
|
-
});
|
|
6319
|
-
return rows.map(rowToTicket);
|
|
6320
|
-
}
|
|
6321
|
-
function getTicket(ctx, id) {
|
|
6322
|
-
const row = ctx.db.select().from(tickets).where(eq7(tickets.id, id)).get();
|
|
6323
|
-
if (!row)
|
|
6324
|
-
throw new NotFoundError(`ticket ${id} not found`);
|
|
6325
|
-
return rowToTicket(row);
|
|
6326
|
-
}
|
|
6327
|
-
function assertMutable(t, verb) {
|
|
6328
|
-
if (!MUTABLE_STATUSES.has(t.status)) {
|
|
6329
|
-
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"}`);
|
|
6330
|
-
}
|
|
6331
|
-
}
|
|
6332
|
-
function editTicket(ctx, id, patch) {
|
|
6333
|
-
const current2 = getTicket(ctx, id);
|
|
6334
|
-
assertMutable(current2, "edit");
|
|
6335
|
-
const set = {};
|
|
6336
|
-
if (patch.title !== undefined)
|
|
6337
|
-
set.title = patch.title;
|
|
6338
|
-
if (patch.goal !== undefined)
|
|
6339
|
-
set.goal = patch.goal;
|
|
6340
|
-
if (patch.context !== undefined)
|
|
6341
|
-
set.context = patch.context;
|
|
6342
|
-
if (patch.acceptanceCriteria !== undefined)
|
|
6343
|
-
set.acceptanceCriteria = patch.acceptanceCriteria;
|
|
6344
|
-
if (patch.seams !== undefined)
|
|
6345
|
-
set.seams = patch.seams;
|
|
6346
|
-
if (patch.model !== undefined)
|
|
6347
|
-
set.model = normalizeModel(ctx, patch.model);
|
|
6348
|
-
if (Object.keys(set).length === 0) {
|
|
6349
|
-
throw new InvalidInputError("nothing to edit \u2014 pass at least one field");
|
|
6350
|
-
}
|
|
6351
|
-
ctx.db.update(tickets).set(set).where(eq7(tickets.id, id)).run();
|
|
6352
|
-
emit(ctx, current2.featureId, {
|
|
6353
|
-
type: "ticket.edited",
|
|
6354
|
-
message: `ticket ${current2.seq} content edited (${Object.keys(set).join(", ")})`,
|
|
6355
|
-
ticketId: id,
|
|
6356
|
-
data: { fields: Object.keys(set) }
|
|
6357
|
-
});
|
|
6358
|
-
return getTicket(ctx, id);
|
|
6359
|
-
}
|
|
6360
|
-
function cancelTicket(ctx, id, reason) {
|
|
6361
|
-
const current2 = getTicket(ctx, id);
|
|
6362
|
-
assertMutable(current2, "cancel");
|
|
6363
|
-
ctx.db.update(tickets).set({
|
|
6364
|
-
status: "cancelled",
|
|
6365
|
-
error: reason?.trim() ? reason.trim() : null,
|
|
6366
|
-
completedAt: Date.now()
|
|
6367
|
-
}).where(eq7(tickets.id, id)).run();
|
|
6368
|
-
emit(ctx, current2.featureId, {
|
|
6369
|
-
type: "ticket.cancelled",
|
|
6370
|
-
message: `ticket ${current2.seq} cancelled${reason?.trim() ? `: ${reason.trim()}` : ""}`,
|
|
6371
|
-
ticketId: id,
|
|
6372
|
-
data: { reason: reason ?? null }
|
|
6373
|
-
});
|
|
6374
|
-
return getTicket(ctx, id);
|
|
6375
|
-
}
|
|
6376
|
-
function sweepOrphanedBurning(ctx, featureId, reason) {
|
|
6377
|
-
const orphaned = ctx.db.select().from(tickets).where(and7(eq7(tickets.featureId, featureId), eq7(tickets.status, "burning"))).all().map(rowToTicket);
|
|
6378
|
-
const swept = [];
|
|
6379
|
-
for (const t of orphaned) {
|
|
6380
|
-
swept.push(updateTicket(ctx, t.id, { status: "failed", error: reason }));
|
|
6381
|
-
if (t.originFindingId)
|
|
6382
|
-
markFailed(ctx, t.originFindingId, reason);
|
|
6383
|
-
emit(ctx, featureId, {
|
|
6384
|
-
type: "ticket.failed",
|
|
6385
|
-
message: `ticket ${t.seq} failed: ${reason}`,
|
|
6386
|
-
ticketId: t.id,
|
|
6387
|
-
data: { error: reason, orphaned: true }
|
|
6388
|
-
});
|
|
6389
|
-
}
|
|
6390
|
-
return swept;
|
|
6391
|
-
}
|
|
6392
|
-
function updateTicket(ctx, id, patch) {
|
|
6393
|
-
const current2 = ctx.db.select().from(tickets).where(eq7(tickets.id, id)).get();
|
|
6394
|
-
if (!current2)
|
|
6395
|
-
throw new NotFoundError(`ticket ${id} not found`);
|
|
6396
|
-
const set = {};
|
|
6397
|
-
if (patch.status !== undefined)
|
|
6398
|
-
set.status = patch.status;
|
|
6399
|
-
if (patch.status !== undefined) {
|
|
6400
|
-
set.completedAt = ["done", "failed", "cancelled"].includes(patch.status) ? Date.now() : null;
|
|
6401
|
-
}
|
|
6402
|
-
if (patch.commits !== undefined)
|
|
6403
|
-
set.commits = patch.commits;
|
|
6404
|
-
if (patch.error !== undefined)
|
|
6405
|
-
set.error = patch.error;
|
|
6406
|
-
if (patch.attemptBranch !== undefined)
|
|
6407
|
-
set.attemptBranch = patch.attemptBranch;
|
|
6408
|
-
if (patch.conflictFiles !== undefined)
|
|
6409
|
-
set.conflictFiles = patch.conflictFiles;
|
|
6410
|
-
if (patch.digest !== undefined)
|
|
6411
|
-
set.digest = patch.digest;
|
|
6412
|
-
if (patch.reviewedCommit !== undefined)
|
|
6413
|
-
set.reviewedCommit = patch.reviewedCommit;
|
|
6414
|
-
ctx.db.update(tickets).set(set).where(eq7(tickets.id, id)).run();
|
|
6415
|
-
emit(ctx, current2.featureId, {
|
|
6416
|
-
type: "ticket.updated",
|
|
6417
|
-
message: `ticket ${current2.seq} \u2192 ${patch.status ?? current2.status}`,
|
|
6418
|
-
ticketId: id,
|
|
6419
|
-
data: patch
|
|
6420
|
-
});
|
|
6421
|
-
const updated = ctx.db.select().from(tickets).where(eq7(tickets.id, id)).get();
|
|
6422
|
-
return rowToTicket(updated);
|
|
6423
|
-
}
|
|
6424
|
-
function listByIds(ctx, ids2) {
|
|
6425
|
-
if (ids2.length === 0)
|
|
6426
|
-
return [];
|
|
6427
|
-
return ctx.db.select().from(tickets).where(inArray3(tickets.id, [...ids2])).orderBy(asc4(tickets.seq)).all().map(rowToTicket);
|
|
6428
|
-
}
|
|
6429
|
-
function timingWallMs(data) {
|
|
6430
|
-
if (typeof data !== "object" || data === null)
|
|
6431
|
-
return;
|
|
6432
|
-
const ms = data.wallMs;
|
|
6433
|
-
return typeof ms === "number" && Number.isFinite(ms) && ms > 0 ? ms : undefined;
|
|
6434
|
-
}
|
|
6435
|
-
function ticketDurationStats(ctx, projectId) {
|
|
6436
|
-
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();
|
|
6437
|
-
const perTicket = new Map;
|
|
6438
|
-
for (const row of rows) {
|
|
6439
|
-
const ms = timingWallMs(row.data);
|
|
6440
|
-
if (row.ticketId && ms !== undefined)
|
|
6441
|
-
perTicket.set(row.ticketId, ms);
|
|
6442
|
-
}
|
|
6443
|
-
const samples = [...perTicket.values()].sort((a, b) => a - b);
|
|
6444
|
-
if (samples.length === 0)
|
|
6445
|
-
return { medianMs: 0, sampleSize: 0 };
|
|
6446
|
-
const mid = samples.length >> 1;
|
|
6447
|
-
const medianMs = samples.length % 2 === 1 ? samples[mid] : Math.round((samples[mid - 1] + samples[mid]) / 2);
|
|
6448
|
-
return { medianMs, sampleSize: samples.length };
|
|
6449
|
-
}
|
|
6450
|
-
var MUTABLE_STATUSES;
|
|
6451
|
-
var init_tickets = __esm(() => {
|
|
6452
|
-
init_src();
|
|
6453
|
-
init_schema();
|
|
6454
|
-
init_errors();
|
|
6455
|
-
init_events();
|
|
6456
|
-
init_repo();
|
|
6457
|
-
init_review_findings();
|
|
6458
|
-
MUTABLE_STATUSES = new Set(["pending", "failed"]);
|
|
6459
|
-
});
|
|
6460
|
-
|
|
6461
6552
|
// src/services/gates.ts
|
|
6462
6553
|
import { existsSync as existsSync11 } from "fs";
|
|
6463
|
-
import { and as
|
|
6554
|
+
import { and as and9, desc as desc4, eq as eq9, inArray as inArray4 } from "drizzle-orm";
|
|
6464
6555
|
function checkGate(ctx, check, feature) {
|
|
6465
6556
|
switch (check) {
|
|
6466
6557
|
case "decisions-file-exists":
|
|
@@ -6486,8 +6577,16 @@ function checkGate(ctx, check, feature) {
|
|
|
6486
6577
|
case "spec-file-exists":
|
|
6487
6578
|
return docGate(ctx, feature, "spec.md", "write the spec (spec.md) before breaking into tickets");
|
|
6488
6579
|
case "tickets-approved": {
|
|
6489
|
-
const
|
|
6490
|
-
|
|
6580
|
+
const burnable = listByFeature2(ctx, feature.id).filter((t) => t.lap === feature.lap && t.status !== "cancelled");
|
|
6581
|
+
if (burnable.length === 0)
|
|
6582
|
+
return { satisfied: false, reason: "no tickets to burn" };
|
|
6583
|
+
if (!burnable.some((t) => t.kind === "review")) {
|
|
6584
|
+
return {
|
|
6585
|
+
satisfied: false,
|
|
6586
|
+
reason: 'no review ticket on this lap \u2014 every batch closes with one (kind: "review"); emit it, ' + "fix the kind on a review-shaped ticket, or override this gate with a reason"
|
|
6587
|
+
};
|
|
6588
|
+
}
|
|
6589
|
+
return { satisfied: true };
|
|
6491
6590
|
}
|
|
6492
6591
|
case "all-tickets-terminal": {
|
|
6493
6592
|
const tickets2 = listByFeature2(ctx, feature.id);
|
|
@@ -6519,7 +6618,7 @@ function fileGate(ctx, feature, fileName, reason) {
|
|
|
6519
6618
|
return existsSync11(featureDocPath(project, feature, fileName)) ? { satisfied: true } : { satisfied: false, reason };
|
|
6520
6619
|
}
|
|
6521
6620
|
function lapWorked(ctx, feature) {
|
|
6522
|
-
return ctx.db.select({ id: sessions.id }).from(sessions).where(
|
|
6621
|
+
return ctx.db.select({ id: sessions.id }).from(sessions).where(and9(eq9(sessions.featureId, feature.id), eq9(sessions.lap, feature.lap), inArray4(sessions.kind, [...DOC_WRITING_KINDS]))).all().length > 0;
|
|
6523
6622
|
}
|
|
6524
6623
|
function docGate(ctx, feature, fileName, reason) {
|
|
6525
6624
|
const file = fileGate(ctx, feature, fileName, reason);
|
|
@@ -6551,10 +6650,10 @@ function overrideGate(ctx, featureId, gate, reason) {
|
|
|
6551
6650
|
return feature;
|
|
6552
6651
|
}
|
|
6553
6652
|
function undoLastGateOverride(ctx, featureId, gate) {
|
|
6554
|
-
const last = ctx.db.select({ id: gateOverrides.id }).from(gateOverrides).where(
|
|
6653
|
+
const last = ctx.db.select({ id: gateOverrides.id }).from(gateOverrides).where(and9(eq9(gateOverrides.featureId, featureId), eq9(gateOverrides.gate, gate))).orderBy(desc4(gateOverrides.id)).get();
|
|
6555
6654
|
if (!last)
|
|
6556
6655
|
return false;
|
|
6557
|
-
ctx.db.delete(gateOverrides).where(
|
|
6656
|
+
ctx.db.delete(gateOverrides).where(eq9(gateOverrides.id, last.id)).run();
|
|
6558
6657
|
return true;
|
|
6559
6658
|
}
|
|
6560
6659
|
function undoGateOverride(ctx, featureId, gate) {
|
|
@@ -6748,6 +6847,133 @@ var init_end_session = __esm(() => {
|
|
|
6748
6847
|
init_registry();
|
|
6749
6848
|
});
|
|
6750
6849
|
|
|
6850
|
+
// src/workflows/kill-registry.ts
|
|
6851
|
+
import { spawn as spawn4 } from "child_process";
|
|
6852
|
+
function log2(msg) {
|
|
6853
|
+
console.error(`[kill-registry] ${msg}`);
|
|
6854
|
+
}
|
|
6855
|
+
function runDockerCommand(args) {
|
|
6856
|
+
return new Promise((resolve9) => {
|
|
6857
|
+
let settled = false;
|
|
6858
|
+
const settle = (ok) => {
|
|
6859
|
+
if (settled)
|
|
6860
|
+
return;
|
|
6861
|
+
settled = true;
|
|
6862
|
+
clearTimeout(timer);
|
|
6863
|
+
resolve9(ok);
|
|
6864
|
+
};
|
|
6865
|
+
const timer = setTimeout(() => settle(false), DOCKER_COMMAND_TIMEOUT_MS);
|
|
6866
|
+
timer.unref?.();
|
|
6867
|
+
try {
|
|
6868
|
+
const child = spawn4("docker", args, { windowsHide: true, stdio: "ignore" });
|
|
6869
|
+
child.on("exit", (code) => settle(code === 0));
|
|
6870
|
+
child.on("close", (code) => settle(code === 0));
|
|
6871
|
+
child.on("error", () => settle(false));
|
|
6872
|
+
} catch {
|
|
6873
|
+
settle(false);
|
|
6874
|
+
}
|
|
6875
|
+
});
|
|
6876
|
+
}
|
|
6877
|
+
function withDeadline2(body, ms) {
|
|
6878
|
+
return new Promise((resolve9) => {
|
|
6879
|
+
const timer = setTimeout(() => resolve9(false), ms);
|
|
6880
|
+
body.catch(() => false).then((confirmed) => {
|
|
6881
|
+
clearTimeout(timer);
|
|
6882
|
+
resolve9(confirmed);
|
|
6883
|
+
});
|
|
6884
|
+
});
|
|
6885
|
+
}
|
|
6886
|
+
function forget() {}
|
|
6887
|
+
function delay(ms) {
|
|
6888
|
+
return new Promise((resolve9) => {
|
|
6889
|
+
const timer = setTimeout(resolve9, ms);
|
|
6890
|
+
timer.unref?.();
|
|
6891
|
+
});
|
|
6892
|
+
}
|
|
6893
|
+
|
|
6894
|
+
class KillRegistry {
|
|
6895
|
+
deps;
|
|
6896
|
+
handles = new Map;
|
|
6897
|
+
killsInFlight = new Map;
|
|
6898
|
+
constructor(deps = REAL_DEPS) {
|
|
6899
|
+
this.deps = deps;
|
|
6900
|
+
}
|
|
6901
|
+
registerContainer(laneKey, containerName, owner = {}) {
|
|
6902
|
+
this.handles.set(laneKey, { kind: "container", containerName, ...owner });
|
|
6903
|
+
}
|
|
6904
|
+
registerHostPid(laneKey, pid, owner = {}) {
|
|
6905
|
+
this.handles.set(laneKey, { kind: "host", pid, ...owner });
|
|
6906
|
+
}
|
|
6907
|
+
release(laneKey) {
|
|
6908
|
+
this.handles.delete(laneKey);
|
|
6909
|
+
}
|
|
6910
|
+
killAndWait(laneKey, opts) {
|
|
6911
|
+
const handle = this.handles.get(laneKey);
|
|
6912
|
+
if (!handle)
|
|
6913
|
+
return Promise.resolve({ confirmed: true });
|
|
6914
|
+
const kill = this.killLane(laneKey, handle, opts);
|
|
6915
|
+
const entry = { runId: handle.runId, settled: kill.then(forget, forget) };
|
|
6916
|
+
this.killsInFlight.set(laneKey, entry);
|
|
6917
|
+
entry.settled.then(() => {
|
|
6918
|
+
if (this.killsInFlight.get(laneKey) === entry)
|
|
6919
|
+
this.killsInFlight.delete(laneKey);
|
|
6920
|
+
});
|
|
6921
|
+
return kill;
|
|
6922
|
+
}
|
|
6923
|
+
async whenKillSettled(laneKey) {
|
|
6924
|
+
await this.killsInFlight.get(laneKey)?.settled;
|
|
6925
|
+
}
|
|
6926
|
+
async whenRunKillsSettled(runId) {
|
|
6927
|
+
await Promise.all([...this.killsInFlight.values()].filter((kill) => kill.runId === runId).map((kill) => kill.settled));
|
|
6928
|
+
}
|
|
6929
|
+
async killLane(laneKey, handle, opts) {
|
|
6930
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
6931
|
+
const started = Date.now();
|
|
6932
|
+
const confirmed = await withDeadline2(this.kill(handle, started + timeoutMs), timeoutMs);
|
|
6933
|
+
if (confirmed)
|
|
6934
|
+
this.handles.delete(laneKey);
|
|
6935
|
+
const target = handle.kind === "container" ? handle.containerName : `pid=${handle.pid}`;
|
|
6936
|
+
log2(`killAndWait: lane=${laneKey} ${handle.kind}=${target} ${confirmed ? "confirmed dead" : `NOT confirmed dead (${timeoutMs}ms deadline)`} after ${Date.now() - started}ms`);
|
|
6937
|
+
return { confirmed };
|
|
6938
|
+
}
|
|
6939
|
+
async killAllForRun(runId, opts) {
|
|
6940
|
+
const lanes = [...this.handles].filter(([, handle]) => handle.runId === runId).map(([laneKey]) => laneKey);
|
|
6941
|
+
const outcomes = await Promise.all(lanes.map((laneKey) => this.killAndWait(laneKey, opts)));
|
|
6942
|
+
return { confirmed: outcomes.every((outcome) => outcome.confirmed) };
|
|
6943
|
+
}
|
|
6944
|
+
keys() {
|
|
6945
|
+
return [...this.handles.keys()];
|
|
6946
|
+
}
|
|
6947
|
+
async kill(handle, deadline) {
|
|
6948
|
+
if (handle.kind === "host") {
|
|
6949
|
+
await this.deps.killTree(handle.pid);
|
|
6950
|
+
return true;
|
|
6951
|
+
}
|
|
6952
|
+
await this.deps.runDocker(["rm", "-f", handle.containerName]);
|
|
6953
|
+
do {
|
|
6954
|
+
if (!await this.deps.runDocker(["inspect", handle.containerName]))
|
|
6955
|
+
return true;
|
|
6956
|
+
await delay(DEATH_POLL_INTERVAL_MS);
|
|
6957
|
+
} while (Date.now() < deadline);
|
|
6958
|
+
return false;
|
|
6959
|
+
}
|
|
6960
|
+
}
|
|
6961
|
+
function killRegistry() {
|
|
6962
|
+
const g = globalThis;
|
|
6963
|
+
if (!g[REGISTRY_KEY2])
|
|
6964
|
+
g[REGISTRY_KEY2] = new KillRegistry;
|
|
6965
|
+
return g[REGISTRY_KEY2];
|
|
6966
|
+
}
|
|
6967
|
+
function registerHostChildren(laneKey, owner) {
|
|
6968
|
+
return (pid) => killRegistry().registerHostPid(laneKey, pid, owner);
|
|
6969
|
+
}
|
|
6970
|
+
var DEFAULT_TIMEOUT_MS = 1e4, DEATH_POLL_INTERVAL_MS = 100, DOCKER_COMMAND_TIMEOUT_MS = 5000, REAL_DEPS, REGISTRY_KEY2;
|
|
6971
|
+
var init_kill_registry = __esm(() => {
|
|
6972
|
+
init_kill_tree();
|
|
6973
|
+
REAL_DEPS = { runDocker: runDockerCommand, killTree: killProcessTree };
|
|
6974
|
+
REGISTRY_KEY2 = Symbol.for("runcastle.kill.registry");
|
|
6975
|
+
});
|
|
6976
|
+
|
|
6751
6977
|
// ../../node_modules/.bun/@ai-hero+sandcastle@0.12.0/node_modules/@ai-hero/sandcastle/dist/chunk-NGBM7T3E.js
|
|
6752
6978
|
import { createRequire as createRequire3 } from "module";
|
|
6753
6979
|
var require$1, __create, __defProp2, __getOwnPropDesc, __getOwnPropNames, __getProtoOf, __hasOwnProp, __require2, __commonJS = (cb, mod) => function __require22() {
|
|
@@ -6805,7 +7031,7 @@ var init_chunk_NGBM7T3E = __esm(() => {
|
|
|
6805
7031
|
// ../../node_modules/.bun/@ai-hero+sandcastle@0.12.0/node_modules/@ai-hero/sandcastle/dist/chunk-VOG34SRF.js
|
|
6806
7032
|
import { createRequire as createRequire4 } from "module";
|
|
6807
7033
|
import * as clack from "@clack/prompts";
|
|
6808
|
-
import { join as join$1, basename as basename2, dirname as
|
|
7034
|
+
import { join as join$1, basename as basename2, dirname as dirname5, resolve as resolve9, posix as posix2, normalize, isAbsolute as isAbsolute2 } from "path";
|
|
6809
7035
|
import { styleText, promisify } from "util";
|
|
6810
7036
|
import { existsSync as existsSync13, statSync as statSync5 } from "fs";
|
|
6811
7037
|
import { tmpdir as tmpdir2, homedir as homedir3 } from "os";
|
|
@@ -10273,7 +10499,7 @@ ${prefix}}`;
|
|
|
10273
10499
|
}, _catch, catchAllDefect, catchSomeCause, catchSomeDefect, catchTag, catchTags, cause = (self) => matchCause(self, {
|
|
10274
10500
|
onFailure: identity,
|
|
10275
10501
|
onSuccess: () => empty7
|
|
10276
|
-
}), clockWith3, clock,
|
|
10502
|
+
}), clockWith3, clock, delay2, descriptorWith = (f) => withFiberRuntime((state, status3) => f({
|
|
10277
10503
|
id: state.id(),
|
|
10278
10504
|
status: status3,
|
|
10279
10505
|
interruptors: interruptors(state.getFiberRef(currentInterruptedCause))
|
|
@@ -10344,7 +10570,7 @@ ${prefix}}`;
|
|
|
10344
10570
|
fiberState.log(message, cause3, levelOption);
|
|
10345
10571
|
return void_2;
|
|
10346
10572
|
});
|
|
10347
|
-
},
|
|
10573
|
+
}, log4, logTrace, logDebug, logInfo, logWarning, logError, logFatal, withLogSpan, logAnnotations, loop = (initial, options) => options.discard ? loopDiscard(initial, options.while, options.step, options.body) : map10(loopInternal(initial, options.while, options.step, options.body), fromIterable2), loopInternal = (initial, cont, inc, body) => suspend(() => cont(initial) ? flatMap7(body(initial), (a) => map10(loopInternal(inc(initial), cont, inc, body), prepend3(a))) : sync(() => empty11())), loopDiscard = (initial, cont, inc, body) => suspend(() => cont(initial) ? flatMap7(body(initial), () => loopDiscard(inc(initial), cont, inc, body)) : void_2), mapAccum2, mapErrorCause, memoize = (self) => pipe(deferredMake(), flatMap7((deferred) => pipe(diffFiberRefsAndRuntimeFlags(self), intoDeferred(deferred), once, map10((complete4) => zipRight2(complete4, pipe(deferredAwait(deferred), flatMap7(([patch9, a]) => as2(zip2(patchFiberRefs(patch9[0]), updateRuntimeFlags(patch9[1])), a)))))))), merge5 = (self) => matchEffect(self, {
|
|
10348
10574
|
onFailure: (e) => succeed(e),
|
|
10349
10575
|
onSuccess: succeed
|
|
10350
10576
|
}), negate = (self) => map10(self, (b) => !b), none6 = (self) => flatMap7(self, (option5) => {
|
|
@@ -11958,11 +12184,11 @@ ${prefix}}`;
|
|
|
11958
12184
|
return end5 - time2 <= 0 ? some2(computeCachedValue(self, timeToLive, time2)) : none2();
|
|
11959
12185
|
}
|
|
11960
12186
|
}
|
|
11961
|
-
})), flatMap7((option5) => isNone2(option5) ? dieMessage("BUG: Effect.cachedInvalidate - please report an issue at https://github.com/Effect-TS/effect/issues") : restore(deferredAwait(option5.value[1]))))), invalidateCache = (cache) => set4(cache, none2()), ensuringChild, ensuringChildren, forkAll, forkIn, forkScoped = (self) => scopeWith((scope5) => forkIn(self, scope5)), fromFiber = (fiber) => join22(fiber), fromFiberEffect = (fiber) => suspend(() => flatMap7(fiber, join22)), memoKeySymbol, Key, cachedFunction = (f,
|
|
12187
|
+
})), flatMap7((option5) => isNone2(option5) ? dieMessage("BUG: Effect.cachedInvalidate - please report an issue at https://github.com/Effect-TS/effect/issues") : restore(deferredAwait(option5.value[1]))))), invalidateCache = (cache) => set4(cache, none2()), ensuringChild, ensuringChildren, forkAll, forkIn, forkScoped = (self) => scopeWith((scope5) => forkIn(self, scope5)), fromFiber = (fiber) => join22(fiber), fromFiberEffect = (fiber) => suspend(() => flatMap7(fiber, join22)), memoKeySymbol, Key, cachedFunction = (f, eq10) => {
|
|
11962
12188
|
return pipe(sync(() => empty22()), flatMap7(makeSynchronized), map10((ref) => (a) => pipe(ref.modifyEffect((map27) => {
|
|
11963
|
-
const result = pipe(map27, get12(new Key(a,
|
|
12189
|
+
const result = pipe(map27, get12(new Key(a, eq10)));
|
|
11964
12190
|
if (isNone2(result)) {
|
|
11965
|
-
return pipe(deferredMake(), tap2((deferred) => pipe(diffFiberRefs(f(a)), intoDeferred(deferred), fork)), map10((deferred) => [deferred, pipe(map27, set6(new Key(a,
|
|
12191
|
+
return pipe(deferredMake(), tap2((deferred) => pipe(diffFiberRefs(f(a)), intoDeferred(deferred), fork)), map10((deferred) => [deferred, pipe(map27, set6(new Key(a, eq10), deferred))]));
|
|
11966
12192
|
}
|
|
11967
12193
|
return succeed([result.value, map27]);
|
|
11968
12194
|
}), flatMap7(deferredAwait), flatMap7(([patch9, b]) => pipe(patchFiberRefs(patch9), as2(b))))));
|
|
@@ -13226,7 +13452,7 @@ ${prefix}}`;
|
|
|
13226
13452
|
}
|
|
13227
13453
|
return void_2;
|
|
13228
13454
|
});
|
|
13229
|
-
}, withRequestCaching, withRequestCache, isRequest2, EffectTypeId3, isEffect2, cachedWithTTL, cachedInvalidateWithTTL2, cached3, cachedFunction2, once3, all7, allWith2, allSuccesses2, dropUntil2, dropWhile2, takeUntil2, takeWhile2, every6, exists4, filter8, filterMap5, findFirst6, forEach8, head4, mergeAll5, partition4, reduce12, reduceWhile2, reduceRight3, reduceEffect3, replicate2, replicateEffect2, validateAll2, validateFirst2, async2, asyncEffect2, custom2, withFiberRuntime2, fail10, failSync4, failCause9, failCauseSync4, die7, dieMessage2, dieSync4, gen3, never4, none9, promise2, succeed10, succeedNone2, succeedSome2, suspend4, sync6, _void, yieldNow4, _catch2, catchAll3, catchAllCause3, catchAllDefect2, catchIf2, catchSome2, catchSomeCause2, catchSomeDefect2, catchTag2, catchTags2, cause2, eventually2, ignore2, ignoreLogged2, parallelErrors2, sandbox2, retry2, withExecutionPlan2, retryOrElse, try_2, tryMap2, tryMapPromise2, tryPromise2, unsandbox2, allowInterrupt2, checkInterruptible2, disconnect2, interrupt7, interruptWith3, interruptible4, interruptibleMask2, onInterrupt2, uninterruptible2, uninterruptibleMask3, liftPredicate3, as7, asSome2, asSomeError2, asVoid5, flip2, flipWith2, map18, mapAccum3, mapBoth5, mapError4, mapErrorCause3, merge7, negate2, acquireRelease2, acquireReleaseInterruptible2, acquireUseRelease2, addFinalizer3, ensuring3, onError2, onExit3, parallelFinalizers2, sequentialFinalizers2, finalizersMask2, scope3, scopeWith2, scopedWith2, scoped3, using2, withEarlyRelease2, awaitAllChildren2, daemonChildren2, descriptor2, descriptorWith2, diffFiberRefs2, ensuringChild2, ensuringChildren2, fiberId2, fiberIdWith2, fork3, forkDaemon2, forkAll2, forkIn2, forkScoped2, forkWithErrorHandler2, fromFiber2, fromFiberEffect2, supervised2, transplant2, withConcurrency2, withScheduler2, withSchedulingPriority2, withMaxOpsBeforeYield2, clock2, clockWith4, withClockScoped2, withClock2, console3, consoleWith2, withConsoleScoped2, withConsole2,
|
|
13455
|
+
}, withRequestCaching, withRequestCache, isRequest2, EffectTypeId3, isEffect2, cachedWithTTL, cachedInvalidateWithTTL2, cached3, cachedFunction2, once3, all7, allWith2, allSuccesses2, dropUntil2, dropWhile2, takeUntil2, takeWhile2, every6, exists4, filter8, filterMap5, findFirst6, forEach8, head4, mergeAll5, partition4, reduce12, reduceWhile2, reduceRight3, reduceEffect3, replicate2, replicateEffect2, validateAll2, validateFirst2, async2, asyncEffect2, custom2, withFiberRuntime2, fail10, failSync4, failCause9, failCauseSync4, die7, dieMessage2, dieSync4, gen3, never4, none9, promise2, succeed10, succeedNone2, succeedSome2, suspend4, sync6, _void, yieldNow4, _catch2, catchAll3, catchAllCause3, catchAllDefect2, catchIf2, catchSome2, catchSomeCause2, catchSomeDefect2, catchTag2, catchTags2, cause2, eventually2, ignore2, ignoreLogged2, parallelErrors2, sandbox2, retry2, withExecutionPlan2, retryOrElse, try_2, tryMap2, tryMapPromise2, tryPromise2, unsandbox2, allowInterrupt2, checkInterruptible2, disconnect2, interrupt7, interruptWith3, interruptible4, interruptibleMask2, onInterrupt2, uninterruptible2, uninterruptibleMask3, liftPredicate3, as7, asSome2, asSomeError2, asVoid5, flip2, flipWith2, map18, mapAccum3, mapBoth5, mapError4, mapErrorCause3, merge7, negate2, acquireRelease2, acquireReleaseInterruptible2, acquireUseRelease2, addFinalizer3, ensuring3, onError2, onExit3, parallelFinalizers2, sequentialFinalizers2, finalizersMask2, scope3, scopeWith2, scopedWith2, scoped3, using2, withEarlyRelease2, awaitAllChildren2, daemonChildren2, descriptor2, descriptorWith2, diffFiberRefs2, ensuringChild2, ensuringChildren2, fiberId2, fiberIdWith2, fork3, forkDaemon2, forkAll2, forkIn2, forkScoped2, forkWithErrorHandler2, fromFiber2, fromFiberEffect2, supervised2, transplant2, withConcurrency2, withScheduler2, withSchedulingPriority2, withMaxOpsBeforeYield2, clock2, clockWith4, withClockScoped2, withClock2, console3, consoleWith2, withConsoleScoped2, withConsole2, delay22, sleep4, timed2, timedWith2, timeout2, timeoutOption2, timeoutFail2, timeoutFailCause2, timeoutTo2, configProviderWith2, withConfigProvider2, withConfigProviderScoped2, context3, contextWith2, contextWithEffect2, mapInputContext3, provide2, provideService3, provideServiceEffect2, serviceFunction2, serviceFunctionEffect2, serviceFunctions2, serviceConstants2, serviceMembers2, serviceOption2, serviceOptional2, updateService2, Do3, bind4, bindAll2, bindTo4, let_4, option2, either4, exit3, intoDeferred2, if_2, filterOrDie2, filterOrDieMessage2, filterOrElse2, filterOrFail2, filterEffectOrElse2, filterEffectOrFail2, unless2, unlessEffect2, when2, whenEffect2, whenFiberRef2, whenRef2, flatMap11, andThen6, flatten8, race2, raceAll2, raceFirst2, raceWith2, summarized2, tap4, tapBoth2, tapDefect2, tapError3, tapErrorTag2, tapErrorCause3, forever3, iterate2, loop2, repeat, repeatN2, repeatOrElse, schedule, scheduleForked2, scheduleFrom, whileLoop3, getFiberRefs, inheritFiberRefs2, locally, locallyWith, locallyScoped, locallyScopedWith, patchFiberRefs2, setFiberRefs2, updateFiberRefs2, isFailure4, isSuccess3, match14, matchCause4, matchCauseEffect3, matchEffect3, log32, logWithLevel2 = (level, ...message) => logWithLevel(level)(...message), logTrace2, logDebug2, logInfo2, logWarning2, logError2, logFatal2, withLogSpan2, annotateLogs3, annotateLogsScoped2, logAnnotations2, withUnhandledErrorLogLevel2, whenLogLevel2, orDie3, orDieWith2, orElse6, orElseFail2, orElseSucceed2, firstSuccessOf2, random3, randomWith2, withRandom2, withRandomFixed, withRandomScoped2, runtime4, getRuntimeFlags, patchRuntimeFlags, withRuntimeFlagsPatch, withRuntimeFlagsPatchScoped, tagMetrics2, labelMetrics2, tagMetricsScoped2, labelMetricsScoped2, metricLabels2, withMetric2, unsafeMakeSemaphore2, makeSemaphore2, unsafeMakeLatch2, makeLatch2, runFork2, runCallback, runPromise, runPromiseExit, runSync, runSyncExit, validate2, validateWith2, zip6, zipLeft6, zipRight6, zipWith8, ap2, blocked2, runRequestBlock2, step3, request, cacheRequestResult, withRequestBatching2, withRequestCaching2, withRequestCache2, tracer2, tracerWith4, withTracer2, withTracerScoped2, withTracerEnabled2, withTracerTiming2, annotateSpans3, annotateCurrentSpan2, currentSpan2, currentParentSpan2, spanAnnotations2, spanLinks2, linkSpans2, linkSpanCurrent2, makeSpan2, makeSpanScoped2, useSpan2, withSpan3, functionWithSpan2, withSpanScoped2, withParentSpan3, fromNullable3, optionFromOptional2, transposeOption = (self) => {
|
|
13230
13456
|
return isNone(self) ? succeedNone2 : map18(self.value, some);
|
|
13231
13457
|
}, transposeMapOption, makeTagProxy = (TagClass) => {
|
|
13232
13458
|
const cache = /* @__PURE__ */ new Map;
|
|
@@ -17465,13 +17691,13 @@ schema (${ast._tag}): ${ast}`;
|
|
|
17465
17691
|
[PlatformWorkerTypeId]: PlatformWorkerTypeId,
|
|
17466
17692
|
spawn(id3) {
|
|
17467
17693
|
return gen3(function* () {
|
|
17468
|
-
const
|
|
17694
|
+
const spawn5 = yield* Spawner;
|
|
17469
17695
|
let currentPort;
|
|
17470
17696
|
const buffer3 = [];
|
|
17471
17697
|
const run6 = (handler) => uninterruptibleMask3((restore) => gen3(function* () {
|
|
17472
17698
|
const scope5 = yield* scope3;
|
|
17473
17699
|
const port2 = yield* options.setup({
|
|
17474
|
-
worker:
|
|
17700
|
+
worker: spawn5(id3),
|
|
17475
17701
|
scope: scope5
|
|
17476
17702
|
});
|
|
17477
17703
|
currentPort = port2;
|
|
@@ -17947,7 +18173,7 @@ ${e instanceof Error ? e.message : String(e)}`
|
|
|
17947
18173
|
const hostFilePath = join$1(untrackedDir, relPath);
|
|
17948
18174
|
yield* Effect_exports.tryPromise({
|
|
17949
18175
|
try: async () => {
|
|
17950
|
-
await mkdir(
|
|
18176
|
+
await mkdir(dirname5(hostFilePath), { recursive: true });
|
|
17951
18177
|
await handle.copyFileOut(sandboxFilePath, hostFilePath);
|
|
17952
18178
|
},
|
|
17953
18179
|
catch: (e) => new SyncError({
|
|
@@ -17979,7 +18205,7 @@ ${e instanceof Error ? e.message : String(e)}`
|
|
|
17979
18205
|
for (const relPath of untrackedFiles) {
|
|
17980
18206
|
const srcPath = join$1(untrackedDir, relPath);
|
|
17981
18207
|
const destPath = join$1(hostRepoDir, relPath);
|
|
17982
|
-
await mkdir(
|
|
18208
|
+
await mkdir(dirname5(destPath), { recursive: true });
|
|
17983
18209
|
const content = await readFile(srcPath);
|
|
17984
18210
|
await writeFile(destPath, content);
|
|
17985
18211
|
}
|
|
@@ -18630,7 +18856,7 @@ Worktree removed (no uncommitted changes)`);
|
|
|
18630
18856
|
}
|
|
18631
18857
|
if (!isFile2)
|
|
18632
18858
|
continue;
|
|
18633
|
-
const parentDir =
|
|
18859
|
+
const parentDir = dirname5(mount.sandboxPath);
|
|
18634
18860
|
if (parentDir === sandboxHomedir)
|
|
18635
18861
|
continue;
|
|
18636
18862
|
if (!parentDir.startsWith(sandboxHomedir + "/")) {
|
|
@@ -18741,7 +18967,7 @@ var init_chunk_VOG34SRF = __esm(() => {
|
|
|
18741
18967
|
currentSpan: () => currentSpan2,
|
|
18742
18968
|
custom: () => custom2,
|
|
18743
18969
|
daemonChildren: () => daemonChildren2,
|
|
18744
|
-
delay: () =>
|
|
18970
|
+
delay: () => delay22,
|
|
18745
18971
|
descriptor: () => descriptor2,
|
|
18746
18972
|
descriptorWith: () => descriptorWith2,
|
|
18747
18973
|
die: () => die7,
|
|
@@ -23184,7 +23410,7 @@ ${this.stack.split(`
|
|
|
23184
23410
|
});
|
|
23185
23411
|
clockWith3 = clockWith2;
|
|
23186
23412
|
clock = /* @__PURE__ */ clockWith3(succeed);
|
|
23187
|
-
|
|
23413
|
+
delay2 = /* @__PURE__ */ dual(2, (self, duration4) => zipRight2(sleep22(duration4), self));
|
|
23188
23414
|
allowInterrupt = /* @__PURE__ */ descriptorWith((descriptor3) => size3(descriptor3.interruptors) > 0 ? interrupt2 : void_2);
|
|
23189
23415
|
descriptor = /* @__PURE__ */ descriptorWith(succeed);
|
|
23190
23416
|
Do2 = /* @__PURE__ */ succeed({});
|
|
@@ -23249,7 +23475,7 @@ ${this.stack.split(`
|
|
|
23249
23475
|
}));
|
|
23250
23476
|
every5 = /* @__PURE__ */ dual(2, (elements, predicate) => suspend(() => forAllLoop(elements[Symbol.iterator](), 0, predicate)));
|
|
23251
23477
|
fiberRefs2 = /* @__PURE__ */ withFiberRuntime((state) => succeed(state.getFiberRefs()));
|
|
23252
|
-
|
|
23478
|
+
log4 = /* @__PURE__ */ logWithLevel();
|
|
23253
23479
|
logTrace = /* @__PURE__ */ logWithLevel(Trace);
|
|
23254
23480
|
logDebug = /* @__PURE__ */ logWithLevel(Debug);
|
|
23255
23481
|
logInfo = /* @__PURE__ */ logWithLevel(Info);
|
|
@@ -25481,9 +25707,9 @@ ${this.stack.split(`
|
|
|
25481
25707
|
a;
|
|
25482
25708
|
eq;
|
|
25483
25709
|
[memoKeySymbol] = memoKeySymbol;
|
|
25484
|
-
constructor(a,
|
|
25710
|
+
constructor(a, eq10) {
|
|
25485
25711
|
this.a = a;
|
|
25486
|
-
this.eq =
|
|
25712
|
+
this.eq = eq10;
|
|
25487
25713
|
}
|
|
25488
25714
|
[symbol2](that) {
|
|
25489
25715
|
if (hasProperty(that, memoKeySymbol)) {
|
|
@@ -27298,7 +27524,7 @@ ${this.stack.split(`
|
|
|
27298
27524
|
consoleWith2 = consoleWith;
|
|
27299
27525
|
withConsoleScoped2 = withConsoleScoped;
|
|
27300
27526
|
withConsole2 = withConsole;
|
|
27301
|
-
|
|
27527
|
+
delay22 = delay2;
|
|
27302
27528
|
sleep4 = sleep32;
|
|
27303
27529
|
timed2 = timed;
|
|
27304
27530
|
timedWith2 = timedWith;
|
|
@@ -27386,7 +27612,7 @@ ${this.stack.split(`
|
|
|
27386
27612
|
matchCause4 = matchCause;
|
|
27387
27613
|
matchCauseEffect3 = matchCauseEffect;
|
|
27388
27614
|
matchEffect3 = matchEffect;
|
|
27389
|
-
log32 =
|
|
27615
|
+
log32 = log4;
|
|
27390
27616
|
logTrace2 = logTrace;
|
|
27391
27617
|
logDebug2 = logDebug;
|
|
27392
27618
|
logInfo2 = logInfo;
|
|
@@ -29898,7 +30124,7 @@ ${this.stack.split(`
|
|
|
29898
30124
|
FileDisplay = {
|
|
29899
30125
|
layer: (filePath) => Layer_exports.effect(Display, Effect_exports.gen(function* () {
|
|
29900
30126
|
const fs = yield* FileSystem_exports.FileSystem;
|
|
29901
|
-
yield* fs.makeDirectory(
|
|
30127
|
+
yield* fs.makeDirectory(dirname5(filePath), { recursive: true }).pipe(Effect_exports.orDie);
|
|
29902
30128
|
const delimiter = `
|
|
29903
30129
|
--- Run started: ${(/* @__PURE__ */ new Date()).toISOString()} ---
|
|
29904
30130
|
`;
|
|
@@ -42353,13 +42579,13 @@ ${len.toString(16)}\r
|
|
|
42353
42579
|
function makeFilterCalls(parameterName) {
|
|
42354
42580
|
return (parameterValue) => {
|
|
42355
42581
|
if (typeof parameterValue === "string" || parameterValue == null) {
|
|
42356
|
-
return this.logs.filter((
|
|
42357
|
-
return
|
|
42582
|
+
return this.logs.filter((log5) => {
|
|
42583
|
+
return log5[parameterName] === parameterValue;
|
|
42358
42584
|
});
|
|
42359
42585
|
}
|
|
42360
42586
|
if (parameterValue instanceof RegExp) {
|
|
42361
|
-
return this.logs.filter((
|
|
42362
|
-
return parameterValue.test(
|
|
42587
|
+
return this.logs.filter((log5) => {
|
|
42588
|
+
return parameterValue.test(log5[parameterName]);
|
|
42363
42589
|
});
|
|
42364
42590
|
}
|
|
42365
42591
|
throw new InvalidArgumentError(`${parameterName} parameter should be one of string, regexp, undefined or null`);
|
|
@@ -42452,8 +42678,8 @@ ${len.toString(16)}\r
|
|
|
42452
42678
|
return this.logs.filter(criteria);
|
|
42453
42679
|
}
|
|
42454
42680
|
if (criteria instanceof RegExp) {
|
|
42455
|
-
return this.logs.filter((
|
|
42456
|
-
return criteria.test(
|
|
42681
|
+
return this.logs.filter((log5) => {
|
|
42682
|
+
return criteria.test(log5.toString());
|
|
42457
42683
|
});
|
|
42458
42684
|
}
|
|
42459
42685
|
if (typeof criteria === "object" && criteria !== null) {
|
|
@@ -42503,13 +42729,13 @@ ${len.toString(16)}\r
|
|
|
42503
42729
|
this.logs = [];
|
|
42504
42730
|
}
|
|
42505
42731
|
[kMockCallHistoryAddLog](requestInit) {
|
|
42506
|
-
const
|
|
42507
|
-
this.logs.push(
|
|
42508
|
-
return
|
|
42732
|
+
const log5 = new MockCallHistoryLog(requestInit);
|
|
42733
|
+
this.logs.push(log5);
|
|
42734
|
+
return log5;
|
|
42509
42735
|
}
|
|
42510
42736
|
*[Symbol.iterator]() {
|
|
42511
|
-
for (const
|
|
42512
|
-
yield
|
|
42737
|
+
for (const log5 of this.calls()) {
|
|
42738
|
+
yield log5;
|
|
42513
42739
|
}
|
|
42514
42740
|
}
|
|
42515
42741
|
};
|
|
@@ -53265,7 +53491,7 @@ var init_chunk_BIWNFKGV = __esm(() => {
|
|
|
53265
53491
|
|
|
53266
53492
|
// ../../node_modules/.bun/@ai-hero+sandcastle@0.12.0/node_modules/@ai-hero/sandcastle/dist/chunk-62WN33RK.js
|
|
53267
53493
|
import { createRequire as createRequire7 } from "module";
|
|
53268
|
-
import { spawn as
|
|
53494
|
+
import { spawn as spawn6 } from "child_process";
|
|
53269
53495
|
import { createInterface as createInterface2 } from "readline";
|
|
53270
53496
|
var noSandbox = (options) => ({
|
|
53271
53497
|
tag: "none",
|
|
@@ -53283,7 +53509,7 @@ var noSandbox = (options) => ({
|
|
|
53283
53509
|
const shellCmd = isWindows ? "cmd.exe" : "sh";
|
|
53284
53510
|
const shellArgs = isWindows ? ["/d", "/s", "/c", command] : ["-c", command];
|
|
53285
53511
|
return new Promise((resolve10, reject) => {
|
|
53286
|
-
const proc =
|
|
53512
|
+
const proc = spawn6(shellCmd, shellArgs, {
|
|
53287
53513
|
cwd,
|
|
53288
53514
|
env: processEnv,
|
|
53289
53515
|
stdio: [
|
|
@@ -53293,6 +53519,8 @@ var noSandbox = (options) => ({
|
|
|
53293
53519
|
],
|
|
53294
53520
|
windowsVerbatimArguments: isWindows
|
|
53295
53521
|
});
|
|
53522
|
+
if (proc.pid !== undefined)
|
|
53523
|
+
options?.onChildSpawn?.(proc.pid);
|
|
53296
53524
|
if (opts?.stdin !== undefined) {
|
|
53297
53525
|
proc.stdin.write(opts.stdin);
|
|
53298
53526
|
proc.stdin.end();
|
|
@@ -53342,12 +53570,14 @@ var noSandbox = (options) => ({
|
|
|
53342
53570
|
interactiveExec: (args2, opts) => {
|
|
53343
53571
|
return new Promise((resolve10, reject) => {
|
|
53344
53572
|
const [cmd, ...rest] = args2;
|
|
53345
|
-
const proc =
|
|
53573
|
+
const proc = spawn6(cmd, rest, {
|
|
53346
53574
|
cwd: opts.cwd ?? worktreePath,
|
|
53347
53575
|
env: processEnv,
|
|
53348
53576
|
stdio: [opts.stdin, opts.stdout, opts.stderr],
|
|
53349
53577
|
shell: process.platform === "win32"
|
|
53350
53578
|
});
|
|
53579
|
+
if (proc.pid !== undefined)
|
|
53580
|
+
options?.onChildSpawn?.(proc.pid);
|
|
53351
53581
|
proc.on("error", (error) => {
|
|
53352
53582
|
reject(new Error(`exec failed: ${error.message}`));
|
|
53353
53583
|
});
|
|
@@ -53369,7 +53599,7 @@ var init_chunk_62WN33RK = __esm(() => {
|
|
|
53369
53599
|
// ../../node_modules/.bun/@ai-hero+sandcastle@0.12.0/node_modules/@ai-hero/sandcastle/dist/index.js
|
|
53370
53600
|
import { createRequire as createRequire8 } from "module";
|
|
53371
53601
|
import { mkdirSync as mkdirSync6, appendFileSync } from "fs";
|
|
53372
|
-
import path, { join as join14, posix as posix4, dirname as
|
|
53602
|
+
import path, { join as join14, posix as posix4, dirname as dirname7, relative as relative3 } from "path";
|
|
53373
53603
|
import { styleText as styleText2 } from "util";
|
|
53374
53604
|
import * as clack2 from "@clack/prompts";
|
|
53375
53605
|
import { readdir as readdir2, access as access3, readFile as readFile3, mkdir as mkdir3, writeFile as writeFile3, rm as rm3 } from "fs/promises";
|
|
@@ -54476,7 +54706,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
54476
54706
|
}) => {
|
|
54477
54707
|
const jsonl = await readSandboxFile(handle, sourcePath, tag);
|
|
54478
54708
|
const rewritten = transferClaudeSession(jsonl, fromCwd, toCwd);
|
|
54479
|
-
await mkdir3(
|
|
54709
|
+
await mkdir3(dirname7(destPath), { recursive: true });
|
|
54480
54710
|
await writeFile3(destPath, rewritten);
|
|
54481
54711
|
}, makeClaudeSessionStorage = (options) => {
|
|
54482
54712
|
const hostProjectsDir = options?.sessionStorage?.hostProjectsDir;
|
|
@@ -54547,7 +54777,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
54547
54777
|
const rewritten = transferCodexSession(jsonl, sandboxCwd, hostCwd);
|
|
54548
54778
|
const root = hostSessionsDir ?? join14(process.env.HOME ?? "~", ".codex", "sessions");
|
|
54549
54779
|
const target = join14(root, located.relativePath);
|
|
54550
|
-
await mkdir3(
|
|
54780
|
+
await mkdir3(dirname7(target), { recursive: true });
|
|
54551
54781
|
await writeFile3(target, rewritten);
|
|
54552
54782
|
capturedPaths.set(sessionId, target);
|
|
54553
54783
|
},
|
|
@@ -54753,9 +54983,9 @@ var init_dist = __esm(() => {
|
|
|
54753
54983
|
|
|
54754
54984
|
// src/services/settings.ts
|
|
54755
54985
|
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
54756
|
-
import { dirname as
|
|
54986
|
+
import { dirname as dirname8 } from "path";
|
|
54757
54987
|
import * as z3 from "zod";
|
|
54758
|
-
import { eq as
|
|
54988
|
+
import { eq as eq10 } from "drizzle-orm";
|
|
54759
54989
|
function hostDefaults(logicalCpus) {
|
|
54760
54990
|
return { ...DEFAULTS, burnConcurrency: resolveDefaultBurnConcurrency(logicalCpus) };
|
|
54761
54991
|
}
|
|
@@ -54797,8 +55027,9 @@ function projectOverrides(ctx, projectId) {
|
|
|
54797
55027
|
knownFailures: projects.knownFailures,
|
|
54798
55028
|
dbResetCommand: projects.dbResetCommand,
|
|
54799
55029
|
driveSetupCommand: projects.driveSetupCommand,
|
|
54800
|
-
driveStopCommand: projects.driveStopCommand
|
|
54801
|
-
|
|
55030
|
+
driveStopCommand: projects.driveStopCommand,
|
|
55031
|
+
driveInstructions: projects.driveInstructions
|
|
55032
|
+
}).from(projects).where(eq10(projects.id, projectId)).get();
|
|
54802
55033
|
return {
|
|
54803
55034
|
model: row?.model ?? null,
|
|
54804
55035
|
sandbox: row?.sandbox ?? null,
|
|
@@ -54809,7 +55040,8 @@ function projectOverrides(ctx, projectId) {
|
|
|
54809
55040
|
knownFailures: row?.knownFailures ?? null,
|
|
54810
55041
|
dbResetCommand: row?.dbResetCommand ?? null,
|
|
54811
55042
|
driveSetupCommand: row?.driveSetupCommand ?? null,
|
|
54812
|
-
driveStopCommand: row?.driveStopCommand ?? null
|
|
55043
|
+
driveStopCommand: row?.driveStopCommand ?? null,
|
|
55044
|
+
driveInstructions: row?.driveInstructions ?? null
|
|
54813
55045
|
};
|
|
54814
55046
|
}
|
|
54815
55047
|
function resolveField(desc5, layers) {
|
|
@@ -54863,7 +55095,7 @@ function updateSettings(ctx, input, io = {}) {
|
|
|
54863
55095
|
if (!toProject)
|
|
54864
55096
|
throw new InvalidInputError(`${desc5.key} cannot be cleared`);
|
|
54865
55097
|
const project3 = requireProjectById(ctx, input.projectId);
|
|
54866
|
-
ctx.db.update(projects).set({ [desc5.projectColumn]: null }).where(
|
|
55098
|
+
ctx.db.update(projects).set({ [desc5.projectColumn]: null }).where(eq10(projects.id, project3.id)).run();
|
|
54867
55099
|
recordHuman(ctx, project3.id, desc5.key, null);
|
|
54868
55100
|
emitProject(ctx, project3.id, {
|
|
54869
55101
|
type: "settings.updated",
|
|
@@ -54879,7 +55111,7 @@ function updateSettings(ctx, input, io = {}) {
|
|
|
54879
55111
|
const value = parsed.data;
|
|
54880
55112
|
if (toProject) {
|
|
54881
55113
|
const project3 = requireProjectById(ctx, input.projectId);
|
|
54882
|
-
ctx.db.update(projects).set({ [desc5.projectColumn]: String(value) }).where(
|
|
55114
|
+
ctx.db.update(projects).set({ [desc5.projectColumn]: String(value) }).where(eq10(projects.id, project3.id)).run();
|
|
54883
55115
|
recordHuman(ctx, project3.id, desc5.key, String(value));
|
|
54884
55116
|
emitProject(ctx, project3.id, {
|
|
54885
55117
|
type: "settings.updated",
|
|
@@ -54906,7 +55138,7 @@ function describeValue(value) {
|
|
|
54906
55138
|
function writeGlobal(configFile, configKey, value) {
|
|
54907
55139
|
const raw = readRawConfig(configFile);
|
|
54908
55140
|
raw[configKey] = value;
|
|
54909
|
-
mkdirSync7(
|
|
55141
|
+
mkdirSync7(dirname8(configFile), { recursive: true });
|
|
54910
55142
|
writeFileSync5(configFile, `${JSON.stringify(raw, null, 2)}
|
|
54911
55143
|
`);
|
|
54912
55144
|
}
|
|
@@ -54945,7 +55177,7 @@ function writeStepModel(configFile, step4, value) {
|
|
|
54945
55177
|
else
|
|
54946
55178
|
stepModels[step4] = value;
|
|
54947
55179
|
raw.stepModels = stepModels;
|
|
54948
|
-
mkdirSync7(
|
|
55180
|
+
mkdirSync7(dirname8(configFile), { recursive: true });
|
|
54949
55181
|
writeFileSync5(configFile, `${JSON.stringify(raw, null, 2)}
|
|
54950
55182
|
`);
|
|
54951
55183
|
}
|
|
@@ -55116,6 +55348,13 @@ var init_settings = __esm(() => {
|
|
|
55116
55348
|
restartRequired: false,
|
|
55117
55349
|
valueSchema: z3.string().min(1),
|
|
55118
55350
|
parseEnv: idEnv
|
|
55351
|
+
},
|
|
55352
|
+
{
|
|
55353
|
+
key: "driveInstructions",
|
|
55354
|
+
projectColumn: "driveInstructions",
|
|
55355
|
+
restartRequired: false,
|
|
55356
|
+
valueSchema: z3.string().min(1),
|
|
55357
|
+
parseEnv: idEnv
|
|
55119
55358
|
}
|
|
55120
55359
|
];
|
|
55121
55360
|
DEFAULTS = RuncastleConfig.parse({});
|
|
@@ -55124,7 +55363,7 @@ var init_settings = __esm(() => {
|
|
|
55124
55363
|
|
|
55125
55364
|
// src/services/setup.ts
|
|
55126
55365
|
import { copyFileSync as copyFileSync2, existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync7, readdirSync as readdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
55127
|
-
import { dirname as
|
|
55366
|
+
import { dirname as dirname9, join as join15 } from "path";
|
|
55128
55367
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
55129
55368
|
async function writeGitIdentity(exec3, identity4) {
|
|
55130
55369
|
const name = identity4.name.trim();
|
|
@@ -55228,7 +55467,7 @@ async function resolveRuntime(exec3, preferred) {
|
|
|
55228
55467
|
function resolveSandcastleBin() {
|
|
55229
55468
|
try {
|
|
55230
55469
|
const mainUrl = import.meta.resolve("@ai-hero/sandcastle");
|
|
55231
|
-
let dir =
|
|
55470
|
+
let dir = dirname9(fileURLToPath6(mainUrl));
|
|
55232
55471
|
for (let hops = 0;hops < 6; hops++) {
|
|
55233
55472
|
const manifestPath = join15(dir, "package.json");
|
|
55234
55473
|
if (existsSync15(manifestPath)) {
|
|
@@ -55237,7 +55476,7 @@ function resolveSandcastleBin() {
|
|
|
55237
55476
|
return join15(dir, manifest.bin.sandcastle);
|
|
55238
55477
|
}
|
|
55239
55478
|
}
|
|
55240
|
-
const parent =
|
|
55479
|
+
const parent = dirname9(dir);
|
|
55241
55480
|
if (parent === dir)
|
|
55242
55481
|
break;
|
|
55243
55482
|
dir = parent;
|
|
@@ -55262,9 +55501,6 @@ function terminalSpec(kind, opts) {
|
|
|
55262
55501
|
args: [opts.sandcastleBin, opts.runtime, "build-image", "--image-name", opts.imageName]
|
|
55263
55502
|
};
|
|
55264
55503
|
}
|
|
55265
|
-
function sandcastleTemplateDir() {
|
|
55266
|
-
return resolveAsset(ASSET_ENV.sandcastleTemplate, fileURLToPath6(new URL("../assets/sandcastle", import.meta.url)));
|
|
55267
|
-
}
|
|
55268
55504
|
function refreshSandcastleConfig(templateDir, targetDir) {
|
|
55269
55505
|
const dir = join15(targetDir, ".sandcastle");
|
|
55270
55506
|
mkdirSync8(dir, { recursive: true });
|
|
@@ -55313,7 +55549,7 @@ function fileAfkTokenIo(exec3, runtime5 = DEFAULT_RUNTIME) {
|
|
|
55313
55549
|
return {
|
|
55314
55550
|
read: () => existsSync15(path2) ? readFileSync7(path2, "utf8") : "",
|
|
55315
55551
|
write: (content) => {
|
|
55316
|
-
mkdirSync8(
|
|
55552
|
+
mkdirSync8(dirname9(path2), { recursive: true });
|
|
55317
55553
|
writeFileSync6(path2, content, "utf8");
|
|
55318
55554
|
},
|
|
55319
55555
|
verify: createCredentialVerifier(exec3, runtime5)
|
|
@@ -55344,13 +55580,13 @@ var init_setup = __esm(() => {
|
|
|
55344
55580
|
});
|
|
55345
55581
|
|
|
55346
55582
|
// src/doctor/system-exec.ts
|
|
55347
|
-
import { spawn as
|
|
55583
|
+
import { spawn as spawn7 } from "child_process";
|
|
55348
55584
|
function createSystemExec(opts = {}) {
|
|
55349
55585
|
return (command, args2) => new Promise((resolve10) => {
|
|
55350
55586
|
const { file, args: spawnArgs } = resolveSpawnTarget(command, args2);
|
|
55351
55587
|
let stdout = "";
|
|
55352
55588
|
let stderr = "";
|
|
55353
|
-
const child =
|
|
55589
|
+
const child = spawn7(file, spawnArgs, { cwd: opts.cwd, windowsHide: true });
|
|
55354
55590
|
child.stdout?.on("data", (d) => {
|
|
55355
55591
|
stdout += d.toString();
|
|
55356
55592
|
});
|
|
@@ -55595,6 +55831,20 @@ function buildDriveAvailability(browserPath, devCommand, inheritedMode) {
|
|
|
55595
55831
|
function inheritedReviewMode(verifiedTicketId, fileExists2 = existsSync16) {
|
|
55596
55832
|
return verifiedTicketId && fileExists2(reviewWalkthroughPath(verifiedTicketId)) ? "drive" : "gates";
|
|
55597
55833
|
}
|
|
55834
|
+
function buildDriveInstructions(instructions) {
|
|
55835
|
+
const text3 = instructions?.trim();
|
|
55836
|
+
if (!text3) {
|
|
55837
|
+
return "No drive instructions recorded for this project \u2014 drive from what the ticket, the diff, and the app surface tell you.";
|
|
55838
|
+
}
|
|
55839
|
+
return [
|
|
55840
|
+
"How to drive this project, from the project owner. These are their standing instructions for operating the app under test: they authorize actions inside the driven app only \u2014 they do not change your review rules, they do not permit edits to the repository under review, and they do not override any guard on your own session.",
|
|
55841
|
+
"",
|
|
55842
|
+
"```",
|
|
55843
|
+
text3,
|
|
55844
|
+
"```"
|
|
55845
|
+
].join(`
|
|
55846
|
+
`);
|
|
55847
|
+
}
|
|
55598
55848
|
function buildGateNotes(config2) {
|
|
55599
55849
|
const commands = config2.verifyCommands?.trim();
|
|
55600
55850
|
const failures2 = config2.knownFailures?.trim();
|
|
@@ -55628,6 +55878,9 @@ function writeReviewArtifacts(ticket, runId, config2) {
|
|
|
55628
55878
|
walkthroughPath: reviewWalkthroughPath(ticket.id)
|
|
55629
55879
|
};
|
|
55630
55880
|
}
|
|
55881
|
+
function shouldStopAfterDigest(iteration, digestPath, fileExists2 = existsSync16) {
|
|
55882
|
+
return iteration >= 2 && fileExists2(digestPath);
|
|
55883
|
+
}
|
|
55631
55884
|
async function releaseDriveQuietly() {
|
|
55632
55885
|
try {
|
|
55633
55886
|
await releaseReviewDrive();
|
|
@@ -55659,6 +55912,7 @@ async function reviewTicketOutcome(ctx, ticket, deps, timer) {
|
|
|
55659
55912
|
FEATURE_BRANCH: feature.branch,
|
|
55660
55913
|
BASE_BRANCH: feature.baseBranch,
|
|
55661
55914
|
DRIVE_AVAILABILITY: buildDriveAvailability(findOnPath(AGENT_BROWSER_BIN), project3.devCommand, inheritedMode),
|
|
55915
|
+
DRIVE_INSTRUCTIONS: buildDriveInstructions(project3.driveInstructions),
|
|
55662
55916
|
GATE_NOTES: buildGateNotes(deps.config),
|
|
55663
55917
|
DIGEST_PATH: artifacts.digestPath,
|
|
55664
55918
|
BLOCKED_PATH: artifacts.blockedPath,
|
|
@@ -55670,6 +55924,7 @@ async function reviewTicketOutcome(ctx, ticket, deps, timer) {
|
|
|
55670
55924
|
mkdirSync9(logsDir(), { recursive: true });
|
|
55671
55925
|
const throttle = createStreamThrottle((e) => ctx.emitEvent({ ...e, ticketId: ticket.id }));
|
|
55672
55926
|
beginTranscript(ticket.id);
|
|
55927
|
+
const ticketAbort = registerTicketAbort(ticket.id);
|
|
55673
55928
|
const onStreamEvent = (event) => {
|
|
55674
55929
|
throttle.onEvent(event);
|
|
55675
55930
|
timer.onEvent(event);
|
|
@@ -55682,15 +55937,17 @@ async function reviewTicketOutcome(ctx, ticket, deps, timer) {
|
|
|
55682
55937
|
name: event.name
|
|
55683
55938
|
});
|
|
55684
55939
|
}
|
|
55940
|
+
if (!ticketAbort.signal.aborted && shouldStopAfterDigest(event.iteration, artifacts.digestPath)) {
|
|
55941
|
+
ticketAbort.abort(new Error("the review already wrote its digest"));
|
|
55942
|
+
}
|
|
55685
55943
|
};
|
|
55686
|
-
const ticketAbort = registerTicketAbort(ticket.id);
|
|
55687
55944
|
const signal = AbortSignal.any([ctx.signal, ticketAbort.signal]);
|
|
55688
55945
|
const options = {
|
|
55689
55946
|
agent: buildBurnAgent(deps.config, deps.token, deps.model, {
|
|
55690
55947
|
onHost: true,
|
|
55691
55948
|
mcp: artifacts.mcp
|
|
55692
55949
|
}),
|
|
55693
|
-
sandbox: noSandbox(),
|
|
55950
|
+
sandbox: noSandbox({ onChildSpawn: registerHostChildren(ticket.id, { runId: ctx.runId }) }),
|
|
55694
55951
|
cwd: project3.repoPath,
|
|
55695
55952
|
prompt,
|
|
55696
55953
|
branchStrategy: { type: "head" },
|
|
@@ -55712,6 +55969,7 @@ async function reviewTicketOutcome(ctx, ticket, deps, timer) {
|
|
|
55712
55969
|
runError = err;
|
|
55713
55970
|
} finally {
|
|
55714
55971
|
releaseTicketAbort(ticket.id);
|
|
55972
|
+
killRegistry().release(ticket.id);
|
|
55715
55973
|
throttle.flush();
|
|
55716
55974
|
endTranscript(ticket.id);
|
|
55717
55975
|
await releaseDriveQuietly();
|
|
@@ -55744,12 +56002,13 @@ var init_review_ticket = __esm(() => {
|
|
|
55744
56002
|
init_agent_stream();
|
|
55745
56003
|
init_git();
|
|
55746
56004
|
init_review_findings();
|
|
56005
|
+
init_kill_registry();
|
|
55747
56006
|
init_ticket_burner();
|
|
55748
56007
|
});
|
|
55749
56008
|
|
|
55750
56009
|
// ../../node_modules/.bun/@ai-hero+sandcastle@0.12.0/node_modules/@ai-hero/sandcastle/dist/chunk-CP3TYXZA.js
|
|
55751
56010
|
import { createRequire as createRequire10 } from "module";
|
|
55752
|
-
import { execFile as execFile2, execFileSync, spawn as
|
|
56011
|
+
import { execFile as execFile2, execFileSync, spawn as spawn8 } from "child_process";
|
|
55753
56012
|
import { randomUUID } from "crypto";
|
|
55754
56013
|
import { createInterface as createInterface3 } from "readline";
|
|
55755
56014
|
var dockerExec = (args2) => Effect_exports.async((resume2) => {
|
|
@@ -55828,7 +56087,7 @@ var dockerExec = (args2) => Effect_exports.async((resume2) => {
|
|
|
55828
56087
|
env: options?.env,
|
|
55829
56088
|
sandboxHomedir,
|
|
55830
56089
|
create: async (createOptions) => {
|
|
55831
|
-
const containerName = `sandcastle-${randomUUID()}`;
|
|
56090
|
+
const containerName = options?.containerName ?? `sandcastle-${randomUUID()}`;
|
|
55832
56091
|
const worktreePath = createOptions.mounts.find((m) => m.hostPath === createOptions.worktreePath)?.sandboxPath ?? "/home/agent/workspace";
|
|
55833
56092
|
const allMounts = [...createOptions.mounts, ...userMounts];
|
|
55834
56093
|
const volumeMounts = allMounts.map((m) => ({
|
|
@@ -55895,7 +56154,7 @@ var dockerExec = (args2) => Effect_exports.async((resume2) => {
|
|
|
55895
56154
|
args2.push("-w", opts.cwd);
|
|
55896
56155
|
args2.push(containerName, "sh", "-c", effectiveCommand);
|
|
55897
56156
|
return new Promise((resolve22, reject) => {
|
|
55898
|
-
const proc =
|
|
56157
|
+
const proc = spawn8("docker", args2, {
|
|
55899
56158
|
stdio: [
|
|
55900
56159
|
opts?.stdin !== undefined ? "pipe" : "ignore",
|
|
55901
56160
|
"pipe",
|
|
@@ -55959,7 +56218,7 @@ var dockerExec = (args2) => Effect_exports.async((resume2) => {
|
|
|
55959
56218
|
if (opts.cwd)
|
|
55960
56219
|
dockerArgs.push("-w", opts.cwd);
|
|
55961
56220
|
dockerArgs.push(containerName, ...args2);
|
|
55962
|
-
const proc =
|
|
56221
|
+
const proc = spawn8("docker", dockerArgs, {
|
|
55963
56222
|
stdio: [opts.stdin, opts.stdout, opts.stderr]
|
|
55964
56223
|
});
|
|
55965
56224
|
proc.on("error", (error) => {
|
|
@@ -56039,7 +56298,7 @@ var init_docker = __esm(() => {
|
|
|
56039
56298
|
|
|
56040
56299
|
// ../../node_modules/.bun/@ai-hero+sandcastle@0.12.0/node_modules/@ai-hero/sandcastle/dist/sandboxes/podman.js
|
|
56041
56300
|
import { createRequire as createRequire12 } from "module";
|
|
56042
|
-
import { execFile as execFile3, execFileSync as execFileSync2, spawn as
|
|
56301
|
+
import { execFile as execFile3, execFileSync as execFileSync2, spawn as spawn9 } from "child_process";
|
|
56043
56302
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
56044
56303
|
import { createInterface as createInterface4 } from "readline";
|
|
56045
56304
|
var podman = (options) => {
|
|
@@ -56155,7 +56414,7 @@ var podman = (options) => {
|
|
|
56155
56414
|
args2.push("-w", opts.cwd);
|
|
56156
56415
|
args2.push(containerName, "sh", "-c", effectiveCommand);
|
|
56157
56416
|
return new Promise((resolve10, reject) => {
|
|
56158
|
-
const proc =
|
|
56417
|
+
const proc = spawn9("podman", args2, {
|
|
56159
56418
|
stdio: [
|
|
56160
56419
|
opts?.stdin !== undefined ? "pipe" : "ignore",
|
|
56161
56420
|
"pipe",
|
|
@@ -56219,7 +56478,7 @@ var podman = (options) => {
|
|
|
56219
56478
|
if (opts.cwd)
|
|
56220
56479
|
podmanArgs.push("-w", opts.cwd);
|
|
56221
56480
|
podmanArgs.push(containerName, ...args2);
|
|
56222
|
-
const proc =
|
|
56481
|
+
const proc = spawn9("podman", podmanArgs, {
|
|
56223
56482
|
stdio: [opts.stdin, opts.stdout, opts.stderr]
|
|
56224
56483
|
});
|
|
56225
56484
|
proc.on("error", (error) => {
|
|
@@ -56300,7 +56559,7 @@ var init_podman = __esm(() => {
|
|
|
56300
56559
|
|
|
56301
56560
|
// src/workflows/ticket-burner.ts
|
|
56302
56561
|
import { existsSync as existsSync17, mkdirSync as mkdirSync10, readFileSync as readFileSync9, readdirSync as readdirSync5, rmSync as rmSync3 } from "fs";
|
|
56303
|
-
import { dirname as
|
|
56562
|
+
import { dirname as dirname10, join as join17, posix as posixPath, win32 as winPath } from "path";
|
|
56304
56563
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
56305
56564
|
function missingImageRuntimeMessage(runtime5, image) {
|
|
56306
56565
|
const binary = RUNTIME_BINARY[runtime5];
|
|
@@ -56590,13 +56849,13 @@ function parseEnvFile3(content) {
|
|
|
56590
56849
|
if (line.length === 0 || line.startsWith("#"))
|
|
56591
56850
|
continue;
|
|
56592
56851
|
const body = line.startsWith("export ") ? line.slice(7).trim() : line;
|
|
56593
|
-
const
|
|
56594
|
-
if (
|
|
56852
|
+
const eq11 = body.indexOf("=");
|
|
56853
|
+
if (eq11 === -1)
|
|
56595
56854
|
continue;
|
|
56596
|
-
const key = body.slice(0,
|
|
56855
|
+
const key = body.slice(0, eq11).trim();
|
|
56597
56856
|
if (key.length === 0)
|
|
56598
56857
|
continue;
|
|
56599
|
-
let value = body.slice(
|
|
56858
|
+
let value = body.slice(eq11 + 1).trim();
|
|
56600
56859
|
if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
|
|
56601
56860
|
value = value.slice(1, -1);
|
|
56602
56861
|
}
|
|
@@ -56646,8 +56905,12 @@ function cacheMountFor(pm, hostPath) {
|
|
|
56646
56905
|
}
|
|
56647
56906
|
function buildBurnCacheMounts(slot, projectId, sandbox3, pm) {
|
|
56648
56907
|
if (slot !== undefined) {
|
|
56908
|
+
const volume = burnCacheVolumeName(projectId);
|
|
56649
56909
|
return {
|
|
56650
|
-
mounts: [
|
|
56910
|
+
mounts: [
|
|
56911
|
+
{ volume, sandboxPath: BURN_CACHE_MOUNT },
|
|
56912
|
+
...TOOLCHAIN_CACHE_SANDBOX_PATHS.map((sandboxPath) => ({ volume, sandboxPath }))
|
|
56913
|
+
],
|
|
56651
56914
|
env: pm ? burnCacheEnv(pm) : {}
|
|
56652
56915
|
};
|
|
56653
56916
|
}
|
|
@@ -57037,12 +57300,13 @@ function delayUnlessAborted(ms, signal) {
|
|
|
57037
57300
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
57038
57301
|
});
|
|
57039
57302
|
}
|
|
57040
|
-
function stopTicketRun(ticketId) {
|
|
57303
|
+
async function stopTicketRun(ticketId) {
|
|
57041
57304
|
const controller = activeTicketAborts.get(ticketId);
|
|
57042
57305
|
if (!controller)
|
|
57043
|
-
return false;
|
|
57306
|
+
return { stopped: false, confirmed: true };
|
|
57044
57307
|
controller.abort(new Error("ticket stopped by user"));
|
|
57045
|
-
|
|
57308
|
+
const { confirmed } = await killRegistry().killAndWait(ticketId);
|
|
57309
|
+
return { stopped: true, confirmed };
|
|
57046
57310
|
}
|
|
57047
57311
|
function registerTicketAbort(ticketId) {
|
|
57048
57312
|
const controller = new AbortController;
|
|
@@ -57233,6 +57497,7 @@ async function burnTickets(ctx, tickets2, execute, concurrency = 1) {
|
|
|
57233
57497
|
ticketId: t.id
|
|
57234
57498
|
});
|
|
57235
57499
|
const outcome = await execute(ctx, t, { digests: [...digests] });
|
|
57500
|
+
await killRegistry().whenKillSettled(t.id);
|
|
57236
57501
|
if (outcome.status === "done") {
|
|
57237
57502
|
status3.set(seq2, "done");
|
|
57238
57503
|
const reviewedCommit = isReviewTicket(t) ? await headSha(ctx.project.repoPath, ctx.feature.branch) : undefined;
|
|
@@ -57402,7 +57667,7 @@ function resolverTemplatePath() {
|
|
|
57402
57667
|
return burnerAssetPath("resolve-conflict.md");
|
|
57403
57668
|
}
|
|
57404
57669
|
function burnerAssetPath(file) {
|
|
57405
|
-
const here =
|
|
57670
|
+
const here = dirname10(fileURLToPath7(import.meta.url));
|
|
57406
57671
|
return join17(resolveSkillsRoot(here), "burner", file);
|
|
57407
57672
|
}
|
|
57408
57673
|
function listFeatureDocs(docsDir, prefix = "") {
|
|
@@ -57563,27 +57828,31 @@ function buildAgentExtraArgs(runtime5, options) {
|
|
|
57563
57828
|
function quoteArg(value) {
|
|
57564
57829
|
return `"${value}"`;
|
|
57565
57830
|
}
|
|
57566
|
-
function selectSandbox(config2, mounts = [], env = {}) {
|
|
57567
|
-
const imageOpts = buildSandboxOptions(config2, mounts, env);
|
|
57831
|
+
function selectSandbox(config2, mounts = [], env = {}, kill = {}) {
|
|
57832
|
+
const imageOpts = buildSandboxOptions(config2, mounts, env, kill.containerName);
|
|
57568
57833
|
switch (config2.sandbox) {
|
|
57569
57834
|
case "docker":
|
|
57570
57835
|
return docker(imageOpts);
|
|
57571
57836
|
case "podman":
|
|
57572
57837
|
return podman(imageOpts);
|
|
57573
57838
|
case "noSandbox":
|
|
57574
|
-
return noSandbox();
|
|
57839
|
+
return noSandbox(kill.onChildSpawn ? { onChildSpawn: kill.onChildSpawn } : {});
|
|
57575
57840
|
default:
|
|
57576
57841
|
throw new Error(`no sandbox provider for sandbox "${config2.sandbox}" \u2014 refusing to run the agent unsandboxed`);
|
|
57577
57842
|
}
|
|
57578
57843
|
}
|
|
57579
|
-
function buildSandboxOptions(config2, mounts = [], env = {}) {
|
|
57844
|
+
function buildSandboxOptions(config2, mounts = [], env = {}, containerName) {
|
|
57580
57845
|
return {
|
|
57581
57846
|
imageName: resolveSandboxImage(config2),
|
|
57582
57847
|
...mounts.length > 0 ? { mounts } : {},
|
|
57583
57848
|
...config2.burnCpus !== undefined ? { cpus: config2.burnCpus } : {},
|
|
57584
|
-
...Object.keys(env).length > 0 ? { env } : {}
|
|
57849
|
+
...Object.keys(env).length > 0 ? { env } : {},
|
|
57850
|
+
...containerName !== undefined ? { containerName } : {}
|
|
57585
57851
|
};
|
|
57586
57852
|
}
|
|
57853
|
+
function burnContainerName(runId, seq2, lane) {
|
|
57854
|
+
return `runcastle-${runId}-t${seq2}${lane ? `-${lane}` : ""}`;
|
|
57855
|
+
}
|
|
57587
57856
|
async function realExecuteTicketRun(ctx, ticket, config2, token, model, land, ensureIsolatedPushTarget, ensureCacheVolume, blocks, runCtx) {
|
|
57588
57857
|
const allocator = resolveBurnCacheMode(config2) === "volume" ? getBurnSlotAllocator(config2.burnConcurrency) : undefined;
|
|
57589
57858
|
return withBurnCacheSlot(allocator, async (slot) => {
|
|
@@ -57706,6 +57975,15 @@ async function burnTicket(ctx, ticket, config2, token, model, land, ensureIsolat
|
|
|
57706
57975
|
};
|
|
57707
57976
|
const ticketAbort = registerTicketAbort(ticket.id);
|
|
57708
57977
|
const signal = AbortSignal.any([ctx.signal, ticketAbort.signal]);
|
|
57978
|
+
const makeKillable = (containerName) => {
|
|
57979
|
+
if (config2.sandbox !== "docker")
|
|
57980
|
+
return;
|
|
57981
|
+
killRegistry().registerContainer(ticket.id, containerName, { runId: ctx.runId });
|
|
57982
|
+
};
|
|
57983
|
+
const killHandles = (containerName) => ({
|
|
57984
|
+
containerName,
|
|
57985
|
+
onChildSpawn: registerHostChildren(ticket.id, { runId: ctx.runId })
|
|
57986
|
+
});
|
|
57709
57987
|
const maxAttempts = Math.max(1, config2.burnAttempts);
|
|
57710
57988
|
const conflictResume = ticket.conflictFiles !== undefined && !!ticket.attemptBranch;
|
|
57711
57989
|
let baseBranch = feature.branch;
|
|
@@ -57737,6 +58015,7 @@ async function burnTicket(ctx, ticket, config2, token, model, land, ensureIsolat
|
|
|
57737
58015
|
};
|
|
57738
58016
|
const runResolver = async (input) => {
|
|
57739
58017
|
const resolveBranch = ticketBranchName(feature.slug, ticket.seq, newId("r").slice(2, 10));
|
|
58018
|
+
const containerName = burnContainerName(ctx.runId, ticket.seq, "resolve");
|
|
57740
58019
|
const otherSide = await commitSummaries(project3.repoPath, input.branch, feature.branch);
|
|
57741
58020
|
const prompt = renderTemplate(readFileSync9(resolverTemplatePath(), "utf8"), {
|
|
57742
58021
|
TICKET_JSON: ticketJson,
|
|
@@ -57764,9 +58043,10 @@ async function burnTicket(ctx, ticket, config2, token, model, land, ensureIsolat
|
|
|
57764
58043
|
let result;
|
|
57765
58044
|
try {
|
|
57766
58045
|
beginSetupSpan(resolveBranch);
|
|
58046
|
+
makeKillable(containerName);
|
|
57767
58047
|
result = await run6({
|
|
57768
58048
|
agent: buildBurnAgent(config2, token, model, agentOptions),
|
|
57769
|
-
sandbox: selectSandbox(config2, mounts, sandboxEnv),
|
|
58049
|
+
sandbox: selectSandbox(config2, mounts, sandboxEnv, killHandles(containerName)),
|
|
57770
58050
|
cwd: project3.repoPath,
|
|
57771
58051
|
prompt,
|
|
57772
58052
|
branchStrategy: { type: "branch", branch: resolveBranch, baseBranch: input.branch },
|
|
@@ -57885,6 +58165,7 @@ ${blocked3.trim()}`
|
|
|
57885
58165
|
}
|
|
57886
58166
|
let result;
|
|
57887
58167
|
let tempBranch = "";
|
|
58168
|
+
const containerName = burnContainerName(ctx.runId, ticket.seq);
|
|
57888
58169
|
for (let attempt = 1;; attempt++) {
|
|
57889
58170
|
tempBranch = ticketBranchName(feature.slug, ticket.seq, newId("b").slice(2, 10));
|
|
57890
58171
|
const hookCommand = setupHookFor(tempBranch);
|
|
@@ -57898,7 +58179,7 @@ ${blocked3.trim()}`
|
|
|
57898
58179
|
}
|
|
57899
58180
|
const runOptions = {
|
|
57900
58181
|
agent: buildBurnAgent(config2, token, model, agentOptions),
|
|
57901
|
-
sandbox: selectSandbox(config2, mounts, sandboxEnv),
|
|
58182
|
+
sandbox: selectSandbox(config2, mounts, sandboxEnv, killHandles(containerName)),
|
|
57902
58183
|
cwd: project3.repoPath,
|
|
57903
58184
|
prompt: retryNotes ? `${basePrompt}
|
|
57904
58185
|
|
|
@@ -57924,6 +58205,7 @@ ${retryNotes}` : basePrompt,
|
|
|
57924
58205
|
try {
|
|
57925
58206
|
await excludeAttachments();
|
|
57926
58207
|
beginSetupSpan(tempBranch);
|
|
58208
|
+
makeKillable(containerName);
|
|
57927
58209
|
result = await run6(runOptions);
|
|
57928
58210
|
await clearAttachmentsFor(tempBranch);
|
|
57929
58211
|
break;
|
|
@@ -58040,6 +58322,7 @@ ${blocked3.trim()}` };
|
|
|
58040
58322
|
return await landChain(tempBranch, outcome.commits);
|
|
58041
58323
|
} finally {
|
|
58042
58324
|
releaseTicketAbort(ticket.id);
|
|
58325
|
+
killRegistry().release(ticket.id);
|
|
58043
58326
|
throttle.flush();
|
|
58044
58327
|
endTranscript(ticket.id);
|
|
58045
58328
|
emitTicketTiming(ctx, ticket, buildTicketTiming(timer.summary(), startedAt, Date.now()));
|
|
@@ -58134,7 +58417,7 @@ function readTokenFromEnvFile(path2, runtime5) {
|
|
|
58134
58417
|
const token = fromFile && fromFile.length > 0 ? fromFile : process.env[key];
|
|
58135
58418
|
return token && token.length > 0 ? token : undefined;
|
|
58136
58419
|
}
|
|
58137
|
-
var ATTACHMENTS_PREFIX, AUTH_MISSING_EVENT = "auth.missing", IMAGE_RUNTIME_MISSING_EVENT = "burn.image_runtime_missing", RUNTIME_BINARY, DROPPED_MAP_SECTIONS, AGENT_CONVENTIONS_FILE = "CLAUDE.md", PACKAGE_MANAGERS, PM_CACHE_SANDBOX_PATHS, CODEX_HOST_MOUNT_PATH = "/mnt/host-codex", SANDBOX_WORKSPACE_PATH = "/home/agent/workspace", ISOLATED_REPO_PATH = "/home/agent/repo", SETUP_MARKER_FILE = ".runcastle-setup", SETUP_MARKER = "RUNCASTLE_SETUP", RUN, BASH_PATTERNS, TOOL_NAME_CATEGORY, MAX_ATTRIBUTABLE_GAP_MS, BURN_WORKTREE_PATH, WORKTREE_REMOVAL_FAILURES, FATAL_ERROR_PATTERNS, RUNTIME_FATAL_ERROR_PATTERNS, RETRYABLE_ERROR_PATTERNS, RUNTIME_RETRYABLE_ERROR_PATTERNS, AGENT_BINARY, activeTicketAborts, SETUP_HOOK_TIMEOUT_MS, ticketBurner;
|
|
58420
|
+
var ATTACHMENTS_PREFIX, AUTH_MISSING_EVENT = "auth.missing", IMAGE_RUNTIME_MISSING_EVENT = "burn.image_runtime_missing", RUNTIME_BINARY, DROPPED_MAP_SECTIONS, AGENT_CONVENTIONS_FILE = "CLAUDE.md", PACKAGE_MANAGERS, PM_CACHE_SANDBOX_PATHS, TOOLCHAIN_CACHE_SANDBOX_PATHS, CODEX_HOST_MOUNT_PATH = "/mnt/host-codex", SANDBOX_WORKSPACE_PATH = "/home/agent/workspace", ISOLATED_REPO_PATH = "/home/agent/repo", SETUP_MARKER_FILE = ".runcastle-setup", SETUP_MARKER = "RUNCASTLE_SETUP", RUN, BASH_PATTERNS, TOOL_NAME_CATEGORY, MAX_ATTRIBUTABLE_GAP_MS, BURN_WORKTREE_PATH, WORKTREE_REMOVAL_FAILURES, FATAL_ERROR_PATTERNS, RUNTIME_FATAL_ERROR_PATTERNS, RETRYABLE_ERROR_PATTERNS, RUNTIME_RETRYABLE_ERROR_PATTERNS, AGENT_BINARY, activeTicketAborts, SETUP_HOOK_TIMEOUT_MS, ticketBurner;
|
|
58138
58421
|
var init_ticket_burner = __esm(() => {
|
|
58139
58422
|
init_src();
|
|
58140
58423
|
init_config_load();
|
|
@@ -58150,6 +58433,7 @@ var init_ticket_burner = __esm(() => {
|
|
|
58150
58433
|
init_burn_guard();
|
|
58151
58434
|
init_burn_cache();
|
|
58152
58435
|
init_review_ticket();
|
|
58436
|
+
init_kill_registry();
|
|
58153
58437
|
init_docker();
|
|
58154
58438
|
init_podman();
|
|
58155
58439
|
init_no_sandbox();
|
|
@@ -58165,6 +58449,12 @@ var init_ticket_burner = __esm(() => {
|
|
|
58165
58449
|
yarn: "~/.cache/yarn",
|
|
58166
58450
|
npm: "~/.npm"
|
|
58167
58451
|
};
|
|
58452
|
+
TOOLCHAIN_CACHE_SANDBOX_PATHS = [
|
|
58453
|
+
"~/.m2",
|
|
58454
|
+
"~/.gradle",
|
|
58455
|
+
"~/.cache/pip",
|
|
58456
|
+
"~/.cargo/registry"
|
|
58457
|
+
];
|
|
58168
58458
|
RUN = String.raw`(?![\w./-])`;
|
|
58169
58459
|
BASH_PATTERNS = [
|
|
58170
58460
|
["tests", new RegExp(String.raw`\b(vitest|jest|pytest|go test|cargo test|bun test|playwright|maestro)${RUN}|\b(pnpm|npm|yarn|bun|npx)\b[^&;|]*?\s(run\s+)?test${RUN}|--testPathPatterns`)],
|
|
@@ -58244,7 +58534,7 @@ var init_ticket_burner = __esm(() => {
|
|
|
58244
58534
|
|
|
58245
58535
|
// src/workflows/research.ts
|
|
58246
58536
|
import { mkdirSync as mkdirSync11, readFileSync as readFileSync10 } from "fs";
|
|
58247
|
-
import { dirname as
|
|
58537
|
+
import { dirname as dirname11, join as join18 } from "path";
|
|
58248
58538
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
58249
58539
|
function waypointSlug(waypoint) {
|
|
58250
58540
|
const base = waypoint.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -58312,9 +58602,19 @@ function interpretResearchResult(result, docRelPath) {
|
|
|
58312
58602
|
return { status: "failed", error: "agent produced no commits \u2014 no research summary landed" };
|
|
58313
58603
|
}
|
|
58314
58604
|
function researchTemplatePath() {
|
|
58315
|
-
const here =
|
|
58605
|
+
const here = dirname11(fileURLToPath8(import.meta.url));
|
|
58316
58606
|
return join18(resolveSkillsRoot(here), "burner", "research-waypoint.md");
|
|
58317
58607
|
}
|
|
58608
|
+
function researchContainerName(runId, seq2) {
|
|
58609
|
+
return `runcastle-${runId}-w${seq2}`;
|
|
58610
|
+
}
|
|
58611
|
+
function registerResearchKill(config2, runId, seq2) {
|
|
58612
|
+
const containerName = researchContainerName(runId, seq2);
|
|
58613
|
+
if (config2.sandbox === "docker") {
|
|
58614
|
+
killRegistry().registerContainer(runId, containerName, { runId });
|
|
58615
|
+
}
|
|
58616
|
+
return { containerName, onChildSpawn: registerHostChildren(runId, { runId }) };
|
|
58617
|
+
}
|
|
58318
58618
|
async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
58319
58619
|
const { project: project3, feature } = ctx;
|
|
58320
58620
|
const docRelPath = researchDocRel(feature.slug, waypoint);
|
|
@@ -58332,7 +58632,7 @@ async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
|
58332
58632
|
const codexAuthMount = codexAuthMountFor(model.runtime, config2.sandbox);
|
|
58333
58633
|
const runOptions = {
|
|
58334
58634
|
agent: buildBurnAgent(config2, token, model),
|
|
58335
|
-
sandbox: selectSandbox(config2, codexAuthMount ? [codexAuthMount] : []),
|
|
58635
|
+
sandbox: selectSandbox(config2, codexAuthMount ? [codexAuthMount] : [], {}, registerResearchKill(config2, ctx.runId, waypoint.seq)),
|
|
58336
58636
|
cwd: project3.repoPath,
|
|
58337
58637
|
prompt,
|
|
58338
58638
|
branchStrategy: { type: "branch", branch: tempBranch, baseBranch: feature.branch },
|
|
@@ -58359,6 +58659,8 @@ async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
|
58359
58659
|
message: `waypoint ${waypoint.seq}: agent finished but sandcastle could not remove its worktree (${errorHeadline2(msg)})${removed ? " \u2014 cleaned up" : " \u2014 left on disk"}; landing the ${salvaged.length} commit(s) anyway`,
|
|
58360
58660
|
data: { tempBranch, error: msg, cleanedUp: removed }
|
|
58361
58661
|
});
|
|
58662
|
+
} finally {
|
|
58663
|
+
killRegistry().release(ctx.runId);
|
|
58362
58664
|
}
|
|
58363
58665
|
throttle.flush();
|
|
58364
58666
|
const outcome = result ? interpretResearchResult(result, docRelPath) : { status: "done", commits: salvaged, docRelPath };
|
|
@@ -58398,6 +58700,7 @@ var init_research = __esm(() => {
|
|
|
58398
58700
|
init_skills_root();
|
|
58399
58701
|
init_dist();
|
|
58400
58702
|
init_git();
|
|
58703
|
+
init_kill_registry();
|
|
58401
58704
|
init_setup();
|
|
58402
58705
|
init_ticket_burner();
|
|
58403
58706
|
PLACEHOLDERS = ["WAYPOINT_JSON", "FEATURE_BRIEF", "DOCS_DIGEST", "RESEARCH_DOC_PATH"];
|
|
@@ -58422,7 +58725,7 @@ var init_registry2 = __esm(() => {
|
|
|
58422
58725
|
});
|
|
58423
58726
|
|
|
58424
58727
|
// src/workflows/runner.ts
|
|
58425
|
-
import { eq as
|
|
58728
|
+
import { eq as eq11 } from "drizzle-orm";
|
|
58426
58729
|
function workflowClaimsFeatureBranch(workflowId) {
|
|
58427
58730
|
return BRANCH_CLAIMING.has(workflowId);
|
|
58428
58731
|
}
|
|
@@ -58451,7 +58754,7 @@ async function startRun(ctx, featureId, workflowId, opts = {}) {
|
|
|
58451
58754
|
claim(ctx, opts.claimWaypointId, runId);
|
|
58452
58755
|
} catch (e) {
|
|
58453
58756
|
const summary = e instanceof Error ? e.message : "claim failed";
|
|
58454
|
-
ctx.db.update(runs).set({ status: "failed", endedAt: Date.now(), summary }).where(
|
|
58757
|
+
ctx.db.update(runs).set({ status: "failed", endedAt: Date.now(), summary }).where(eq11(runs.id, runId)).run();
|
|
58455
58758
|
throw e;
|
|
58456
58759
|
}
|
|
58457
58760
|
}
|
|
@@ -58501,13 +58804,15 @@ async function startRun(ctx, featureId, workflowId, opts = {}) {
|
|
|
58501
58804
|
});
|
|
58502
58805
|
return { runId, done: done10 };
|
|
58503
58806
|
}
|
|
58504
|
-
function cancelRun(runId) {
|
|
58807
|
+
async function cancelRun(runId) {
|
|
58505
58808
|
controllers.get(runId)?.abort();
|
|
58809
|
+
return await killRegistry().killAllForRun(runId);
|
|
58506
58810
|
}
|
|
58507
58811
|
async function executeRun(ctx, runId, featureId, workflow2, runPromise2, controller, cleanup) {
|
|
58508
58812
|
let status3 = "failed";
|
|
58509
58813
|
let summary = "run failed";
|
|
58510
58814
|
let digest;
|
|
58815
|
+
let threw = false;
|
|
58511
58816
|
try {
|
|
58512
58817
|
const result = await runPromise2;
|
|
58513
58818
|
status3 = result.status;
|
|
@@ -58521,11 +58826,14 @@ async function executeRun(ctx, runId, featureId, workflow2, runPromise2, control
|
|
|
58521
58826
|
status3 = "failed";
|
|
58522
58827
|
summary = e instanceof Error ? e.message : "run failed";
|
|
58523
58828
|
}
|
|
58524
|
-
|
|
58829
|
+
threw = true;
|
|
58525
58830
|
} finally {
|
|
58526
58831
|
controllers.delete(runId);
|
|
58527
58832
|
}
|
|
58528
|
-
|
|
58833
|
+
await killRegistry().whenRunKillsSettled(runId);
|
|
58834
|
+
if (threw)
|
|
58835
|
+
emit(ctx, featureId, { type: "run.error", message: summary, runId });
|
|
58836
|
+
ctx.db.update(runs).set({ status: status3, endedAt: Date.now(), summary, ...digest ? { digest } : {} }).where(eq11(runs.id, runId)).run();
|
|
58529
58837
|
releaseForSession(ctx, runId);
|
|
58530
58838
|
if (workflowClaimsFeatureBranch(workflow2)) {
|
|
58531
58839
|
sweepOrphanedBurning(ctx, featureId, `orphaned \u2014 the run ended (${status3}) while it was burning`);
|
|
@@ -58568,13 +58876,14 @@ var init_runner = __esm(() => {
|
|
|
58568
58876
|
init_review_findings();
|
|
58569
58877
|
init_tickets();
|
|
58570
58878
|
init_waypoints();
|
|
58879
|
+
init_kill_registry();
|
|
58571
58880
|
init_registry2();
|
|
58572
58881
|
controllers = new Map;
|
|
58573
58882
|
BRANCH_CLAIMING = new Set(["ticket-burner"]);
|
|
58574
58883
|
});
|
|
58575
58884
|
|
|
58576
58885
|
// src/services/features.ts
|
|
58577
|
-
import { desc as desc5, eq as
|
|
58886
|
+
import { desc as desc5, eq as eq12 } from "drizzle-orm";
|
|
58578
58887
|
import { rmSync as rmSync4 } from "fs";
|
|
58579
58888
|
async function createFeature(ctx, input) {
|
|
58580
58889
|
const project3 = requireProjectById(ctx, input.projectId);
|
|
@@ -58627,7 +58936,7 @@ async function startDraft(ctx, featureId, opts = {}) {
|
|
|
58627
58936
|
const project3 = projectForFeature(ctx, feature);
|
|
58628
58937
|
const base = await requestedBase(project3, opts.baseBranch);
|
|
58629
58938
|
const { branchReady, baseBranch } = await ensureFeatureBranch(project3, feature.slug, base);
|
|
58630
|
-
ctx.db.update(features).set({ status: "active", baseBranch }).where(
|
|
58939
|
+
ctx.db.update(features).set({ status: "active", baseBranch }).where(eq12(features.id, featureId)).run();
|
|
58631
58940
|
const started = { ...feature, status: "active", baseBranch };
|
|
58632
58941
|
emit(ctx, featureId, {
|
|
58633
58942
|
type: "feature.started",
|
|
@@ -58776,7 +59085,7 @@ function getFeatureFull(ctx, id3) {
|
|
|
58776
59085
|
};
|
|
58777
59086
|
}
|
|
58778
59087
|
function list(ctx, projectId) {
|
|
58779
|
-
const rows = ctx.db.select().from(features).where(
|
|
59088
|
+
const rows = ctx.db.select().from(features).where(eq12(features.projectId, projectId)).orderBy(desc5(features.createdAt)).all();
|
|
58780
59089
|
const lastActivity = latestTsByFeature(ctx, projectId);
|
|
58781
59090
|
return rows.map((row) => {
|
|
58782
59091
|
const feature = rowToFeature(row);
|
|
@@ -58823,6 +59132,13 @@ function advance(ctx, featureId) {
|
|
|
58823
59132
|
throw new GateError("feature is already at the final phase");
|
|
58824
59133
|
return setPhase(ctx, featureId, next4, "phase.advanced");
|
|
58825
59134
|
}
|
|
59135
|
+
function assertTicketsReady(ctx, feature) {
|
|
59136
|
+
if (feature.ticketsReadyLap === feature.lap)
|
|
59137
|
+
return;
|
|
59138
|
+
if (activeSessionsForFeature(ctx, feature.id).length === 0)
|
|
59139
|
+
return;
|
|
59140
|
+
throw new GateError("the session is still finishing the tickets \u2014 it completes the tickets phase when the lap " + "is done, and the burn arms then");
|
|
59141
|
+
}
|
|
58826
59142
|
async function burn(ctx, featureId, opts = {}) {
|
|
58827
59143
|
const feature = getFeatureRow(ctx, featureId);
|
|
58828
59144
|
requireNotDraft(feature);
|
|
@@ -58845,6 +59161,12 @@ async function burn(ctx, featureId, opts = {}) {
|
|
|
58845
59161
|
if (lapTickets.filter((t) => t.status !== "cancelled").length < 1) {
|
|
58846
59162
|
throw new GateError(lapTickets.length > 0 ? "no burnable tickets \u2014 every ticket is cancelled" : "no tickets to burn");
|
|
58847
59163
|
}
|
|
59164
|
+
if (feature.phase === "tickets") {
|
|
59165
|
+
const gate = checkGate(ctx, "tickets-approved", feature);
|
|
59166
|
+
if (!gate.satisfied)
|
|
59167
|
+
throw new GateError(gate.reason ?? "gate G3 not satisfied");
|
|
59168
|
+
assertTicketsReady(ctx, feature);
|
|
59169
|
+
}
|
|
58848
59170
|
if (restarting) {
|
|
58849
59171
|
sweepOrphanedBurning(ctx, featureId, "orphaned \u2014 the previous run died while it was burning");
|
|
58850
59172
|
tickets2 = listByFeature2(ctx, featureId);
|
|
@@ -58894,7 +59216,7 @@ function rethink(ctx, featureId) {
|
|
|
58894
59216
|
throw new GateError(`a ${live[0].kind} session is already live for ${feature.slug} \u2014 only one terminal per feature; end or resume it first`);
|
|
58895
59217
|
}
|
|
58896
59218
|
const lap = feature.lap + 1;
|
|
58897
|
-
ctx.db.update(features).set({ lap }).where(
|
|
59219
|
+
ctx.db.update(features).set({ lap }).where(eq12(features.id, featureId)).run();
|
|
58898
59220
|
return setPhase(ctx, featureId, RETHINK_LOOP_BACK.to, "lap.started", `rethink \u2014 lap ${lap}`);
|
|
58899
59221
|
}
|
|
58900
59222
|
async function rethinkAndLaunch(ctx, featureId, launch3) {
|
|
@@ -58903,7 +59225,7 @@ async function rethinkAndLaunch(ctx, featureId, launch3) {
|
|
|
58903
59225
|
try {
|
|
58904
59226
|
return await launch3(feature);
|
|
58905
59227
|
} catch (e) {
|
|
58906
|
-
ctx.db.update(features).set({ lap: before.lap }).where(
|
|
59228
|
+
ctx.db.update(features).set({ lap: before.lap }).where(eq12(features.id, featureId)).run();
|
|
58907
59229
|
setPhase(ctx, featureId, before.phase, "lap.aborted", `lap ${feature.lap} aborted \u2014 its terminal could not be opened (${errMsg2(e)}); back at ${before.phase} on lap ${before.lap}`);
|
|
58908
59230
|
throw e;
|
|
58909
59231
|
}
|
|
@@ -58982,7 +59304,7 @@ function escalateToMap(ctx, featureId, input) {
|
|
|
58982
59304
|
return { ok: true, warning: `feature ${feature.slug} is already mapped \u2014 no changes made` };
|
|
58983
59305
|
}
|
|
58984
59306
|
const project3 = projectForFeature(ctx, feature);
|
|
58985
|
-
ctx.db.update(features).set({ mapped: true }).where(
|
|
59307
|
+
ctx.db.update(features).set({ mapped: true }).where(eq12(features.id, featureId)).run();
|
|
58986
59308
|
scaffoldMapDoc(project3, { ...feature, mapped: true }, input);
|
|
58987
59309
|
emit(ctx, featureId, {
|
|
58988
59310
|
type: "feature.escalated",
|
|
@@ -59000,7 +59322,7 @@ function archiveFeature(ctx, featureId) {
|
|
|
59000
59322
|
const live = listSessionsByFeature(ctx, featureId).find((s) => s.status === "live");
|
|
59001
59323
|
if (live)
|
|
59002
59324
|
endSession(ctx, live.id);
|
|
59003
|
-
ctx.db.update(features).set({ status: "archived" }).where(
|
|
59325
|
+
ctx.db.update(features).set({ status: "archived" }).where(eq12(features.id, featureId)).run();
|
|
59004
59326
|
emit(ctx, featureId, {
|
|
59005
59327
|
type: "feature.archived",
|
|
59006
59328
|
message: `feature ${feature.slug} archived`,
|
|
@@ -59014,7 +59336,7 @@ function unarchiveFeature(ctx, featureId) {
|
|
|
59014
59336
|
throw new GateError(`feature ${feature.slug} is not archived`);
|
|
59015
59337
|
}
|
|
59016
59338
|
const restored = feature.phase === "shipped" ? "shipped" : "active";
|
|
59017
|
-
ctx.db.update(features).set({ status: restored }).where(
|
|
59339
|
+
ctx.db.update(features).set({ status: restored }).where(eq12(features.id, featureId)).run();
|
|
59018
59340
|
emit(ctx, featureId, {
|
|
59019
59341
|
type: "feature.unarchived",
|
|
59020
59342
|
message: `feature ${feature.slug} unarchived (${restored})`,
|
|
@@ -59030,7 +59352,7 @@ async function deleteFeature(ctx, featureId) {
|
|
|
59030
59352
|
const project3 = projectForFeature(ctx, feature);
|
|
59031
59353
|
for (const run7 of listRunsByFeature(ctx, featureId)) {
|
|
59032
59354
|
if (run7.status === "running")
|
|
59033
|
-
cancelRun(run7.id);
|
|
59355
|
+
await cancelRun(run7.id);
|
|
59034
59356
|
}
|
|
59035
59357
|
const live = listSessionsByFeature(ctx, featureId).find((s) => s.status === "live");
|
|
59036
59358
|
if (live)
|
|
@@ -59054,13 +59376,13 @@ async function deleteFeature(ctx, featureId) {
|
|
|
59054
59376
|
return { ok: true, slug: feature.slug };
|
|
59055
59377
|
}
|
|
59056
59378
|
function deleteFeatureRows(ctx, featureId) {
|
|
59057
|
-
ctx.db.delete(tickets).where(
|
|
59058
|
-
ctx.db.delete(sessions).where(
|
|
59059
|
-
ctx.db.delete(runs).where(
|
|
59060
|
-
ctx.db.delete(events).where(
|
|
59061
|
-
ctx.db.delete(gateOverrides).where(
|
|
59062
|
-
ctx.db.delete(waypoints).where(
|
|
59063
|
-
ctx.db.delete(features).where(
|
|
59379
|
+
ctx.db.delete(tickets).where(eq12(tickets.featureId, featureId)).run();
|
|
59380
|
+
ctx.db.delete(sessions).where(eq12(sessions.featureId, featureId)).run();
|
|
59381
|
+
ctx.db.delete(runs).where(eq12(runs.featureId, featureId)).run();
|
|
59382
|
+
ctx.db.delete(events).where(eq12(events.featureId, featureId)).run();
|
|
59383
|
+
ctx.db.delete(gateOverrides).where(eq12(gateOverrides.featureId, featureId)).run();
|
|
59384
|
+
ctx.db.delete(waypoints).where(eq12(waypoints.featureId, featureId)).run();
|
|
59385
|
+
ctx.db.delete(features).where(eq12(features.id, featureId)).run();
|
|
59064
59386
|
}
|
|
59065
59387
|
function gateState(ctx, feature) {
|
|
59066
59388
|
const gate = nextGate(feature);
|
|
@@ -59071,7 +59393,7 @@ function gateState(ctx, feature) {
|
|
|
59071
59393
|
}
|
|
59072
59394
|
function uniqueSlug(ctx, projectId, title) {
|
|
59073
59395
|
const base = slugify(title);
|
|
59074
|
-
const existing = new Set(ctx.db.select({ slug: features.slug }).from(features).where(
|
|
59396
|
+
const existing = new Set(ctx.db.select({ slug: features.slug }).from(features).where(eq12(features.projectId, projectId)).all().map((r) => r.slug));
|
|
59075
59397
|
if (!existing.has(base))
|
|
59076
59398
|
return base;
|
|
59077
59399
|
let n = 2;
|
|
@@ -59103,8 +59425,8 @@ var init_features = __esm(() => {
|
|
|
59103
59425
|
|
|
59104
59426
|
// src/services/test-notes.ts
|
|
59105
59427
|
import { existsSync as existsSync19, mkdirSync as mkdirSync12, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
59106
|
-
import { dirname as
|
|
59107
|
-
import { and as
|
|
59428
|
+
import { dirname as dirname12 } from "path";
|
|
59429
|
+
import { and as and10, asc as asc5, eq as eq13, lt } from "drizzle-orm";
|
|
59108
59430
|
function rowToNote(row) {
|
|
59109
59431
|
return TestNote.parse({
|
|
59110
59432
|
id: row.id,
|
|
@@ -59123,7 +59445,7 @@ function rowToNote(row) {
|
|
|
59123
59445
|
});
|
|
59124
59446
|
}
|
|
59125
59447
|
function getNote(ctx, id3) {
|
|
59126
|
-
const row = ctx.db.select().from(testNotes).where(
|
|
59448
|
+
const row = ctx.db.select().from(testNotes).where(eq13(testNotes.id, id3)).get();
|
|
59127
59449
|
if (!row)
|
|
59128
59450
|
throw new NotFoundError(`test note ${id3} not found`);
|
|
59129
59451
|
return rowToNote(row);
|
|
@@ -59140,7 +59462,7 @@ function assertOpen(note2, action) {
|
|
|
59140
59462
|
}
|
|
59141
59463
|
}
|
|
59142
59464
|
function listByFeature4(ctx, featureId) {
|
|
59143
|
-
return ctx.db.select().from(testNotes).where(
|
|
59465
|
+
return ctx.db.select().from(testNotes).where(eq13(testNotes.featureId, featureId)).orderBy(asc5(testNotes.createdAt), asc5(testNotes.id)).all().map(rowToNote);
|
|
59144
59466
|
}
|
|
59145
59467
|
function addNote(ctx, featureId, text3, author = "human", videoTimestamp, reviewTicketId) {
|
|
59146
59468
|
const body = cleanText(text3);
|
|
@@ -59178,7 +59500,7 @@ function editNote(ctx, noteId, text3) {
|
|
|
59178
59500
|
const current2 = getNote(ctx, noteId);
|
|
59179
59501
|
assertOpen(current2, "edit");
|
|
59180
59502
|
const body = cleanText(text3);
|
|
59181
|
-
ctx.db.update(testNotes).set({ text: body, updatedAt: Date.now() }).where(
|
|
59503
|
+
ctx.db.update(testNotes).set({ text: body, updatedAt: Date.now() }).where(eq13(testNotes.id, noteId)).run();
|
|
59182
59504
|
emit(ctx, current2.featureId, {
|
|
59183
59505
|
type: "note.edited",
|
|
59184
59506
|
message: "note edited",
|
|
@@ -59190,7 +59512,7 @@ function editNote(ctx, noteId, text3) {
|
|
|
59190
59512
|
function deleteNote(ctx, noteId) {
|
|
59191
59513
|
const current2 = getNote(ctx, noteId);
|
|
59192
59514
|
assertOpen(current2, "delete");
|
|
59193
|
-
ctx.db.delete(testNotes).where(
|
|
59515
|
+
ctx.db.delete(testNotes).where(eq13(testNotes.id, noteId)).run();
|
|
59194
59516
|
rmSync5(annotationPath(noteId), { force: true });
|
|
59195
59517
|
emit(ctx, current2.featureId, {
|
|
59196
59518
|
type: "note.deleted",
|
|
@@ -59217,7 +59539,7 @@ function toggleNote(ctx, noteId) {
|
|
|
59217
59539
|
throw new InvalidInputError(`cannot toggle note ${noteId} \u2014 it is promoted, and promoted notes are frozen`);
|
|
59218
59540
|
}
|
|
59219
59541
|
const status3 = current2.status === "open" ? "done" : "open";
|
|
59220
|
-
ctx.db.update(testNotes).set({ status: status3, updatedAt: Date.now() }).where(
|
|
59542
|
+
ctx.db.update(testNotes).set({ status: status3, updatedAt: Date.now() }).where(eq13(testNotes.id, noteId)).run();
|
|
59221
59543
|
emit(ctx, current2.featureId, {
|
|
59222
59544
|
type: "note.toggled",
|
|
59223
59545
|
message: `note ${current2.status} \u2192 ${status3}`,
|
|
@@ -59265,7 +59587,7 @@ function freezeAsTickets(ctx, noteIds) {
|
|
|
59265
59587
|
const tickets2 = storeTickets(ctx, featureId, selected.map((note2) => promotionTicket(feature, note2)));
|
|
59266
59588
|
const now = Date.now();
|
|
59267
59589
|
selected.forEach((note2, i) => {
|
|
59268
|
-
ctx.db.update(testNotes).set({ status: "promoted", ticketId: tickets2[i].id, updatedAt: now }).where(
|
|
59590
|
+
ctx.db.update(testNotes).set({ status: "promoted", ticketId: tickets2[i].id, updatedAt: now }).where(eq13(testNotes.id, note2.id)).run();
|
|
59269
59591
|
});
|
|
59270
59592
|
return { feature, notes: selected.map((n) => getNote(ctx, n.id)), tickets: tickets2 };
|
|
59271
59593
|
}
|
|
@@ -59310,7 +59632,7 @@ function carryNotes(ctx, featureId, noteIds, lap) {
|
|
|
59310
59632
|
if (note2.featureId !== featureId)
|
|
59311
59633
|
throw new InvalidInputError("every carried note must belong to the feature");
|
|
59312
59634
|
assertOpen(note2, "carry");
|
|
59313
|
-
ctx.db.update(testNotes).set({ status: "carried", carriedLap: lap, updatedAt: Date.now() }).where(
|
|
59635
|
+
ctx.db.update(testNotes).set({ status: "carried", carriedLap: lap, updatedAt: Date.now() }).where(eq13(testNotes.id, note2.id)).run();
|
|
59314
59636
|
emit(ctx, featureId, { type: "note.carried", message: `note carried into lap ${lap}`, data: { noteId: note2.id, lap } });
|
|
59315
59637
|
}
|
|
59316
59638
|
renderTestNotes(ctx, getFeatureRow(ctx, featureId));
|
|
@@ -59320,7 +59642,7 @@ function reopenNote(ctx, noteId) {
|
|
|
59320
59642
|
const note2 = getNote(ctx, noteId);
|
|
59321
59643
|
if (note2.status !== "carried")
|
|
59322
59644
|
throw new InvalidInputError("only a carried note can be reopened");
|
|
59323
|
-
ctx.db.update(testNotes).set({ status: "open", carriedLap: null, updatedAt: Date.now() }).where(
|
|
59645
|
+
ctx.db.update(testNotes).set({ status: "open", carriedLap: null, updatedAt: Date.now() }).where(eq13(testNotes.id, noteId)).run();
|
|
59324
59646
|
emit(ctx, note2.featureId, { type: "note.reopened", message: "note reopened", data: { noteId } });
|
|
59325
59647
|
renderTestNotes(ctx, getFeatureRow(ctx, note2.featureId));
|
|
59326
59648
|
return getNote(ctx, noteId);
|
|
@@ -59339,7 +59661,7 @@ function triageNotes(ctx, featureId, input) {
|
|
|
59339
59661
|
}
|
|
59340
59662
|
function triagePreview(ctx, featureId) {
|
|
59341
59663
|
const feature = getFeatureRow(ctx, featureId);
|
|
59342
|
-
const standing = ctx.db.select().from(tickets).where(
|
|
59664
|
+
const standing = ctx.db.select().from(tickets).where(and10(eq13(tickets.featureId, featureId), eq13(tickets.kind, "implementation"), eq13(tickets.status, "pending"), lt(tickets.lap, feature.lap))).all();
|
|
59343
59665
|
const grouped = new Map;
|
|
59344
59666
|
for (const ticket of standing)
|
|
59345
59667
|
grouped.set(ticket.lap, (grouped.get(ticket.lap) ?? 0) + 1);
|
|
@@ -59349,17 +59671,26 @@ function triagePreview(ctx, featureId) {
|
|
|
59349
59671
|
standingFixTickets: [...grouped].sort(([a], [b]) => a - b).map(([lap, count3]) => ({ count: count3, lap }))
|
|
59350
59672
|
};
|
|
59351
59673
|
}
|
|
59674
|
+
function carriedLine(note2) {
|
|
59675
|
+
return `- [\u2192] ${note2.text} (captured lap ${note2.lap}, carried into lap ${note2.carriedLap})${screenshotSuffix(note2)}`;
|
|
59676
|
+
}
|
|
59352
59677
|
function renderTestNotes(ctx, feature) {
|
|
59353
59678
|
const notes = listByFeature4(ctx, feature.id);
|
|
59354
59679
|
const laps = [...new Set(notes.map((n) => n.lap))].sort((a, b) => a - b);
|
|
59355
59680
|
const lines = ["# Test notes"];
|
|
59681
|
+
const carried = notes.filter((note2) => note2.status === "carried");
|
|
59682
|
+
if (carried.length) {
|
|
59683
|
+
lines.push("", "## Carried, still open", "");
|
|
59684
|
+
for (const note2 of carried)
|
|
59685
|
+
lines.push(carriedLine(note2));
|
|
59686
|
+
}
|
|
59356
59687
|
for (const lap of laps) {
|
|
59357
59688
|
lines.push("", `## Lap ${lap}`, "");
|
|
59358
59689
|
for (const note2 of notes.filter((n) => n.lap === lap))
|
|
59359
59690
|
lines.push(noteLine(ctx, note2));
|
|
59360
59691
|
}
|
|
59361
59692
|
const path2 = featureDocPath(projectForFeature(ctx, feature), feature, "test-notes.md");
|
|
59362
|
-
mkdirSync12(
|
|
59693
|
+
mkdirSync12(dirname12(path2), { recursive: true });
|
|
59363
59694
|
writeFileSync8(path2, `${lines.join(`
|
|
59364
59695
|
`)}
|
|
59365
59696
|
`, "utf8");
|
|
@@ -59432,6 +59763,8 @@ function resolveFeatureReader(ctx, caller) {
|
|
|
59432
59763
|
}
|
|
59433
59764
|
function featureContext(ctx, reader) {
|
|
59434
59765
|
const feature = getFeatureRow(ctx, reader.featureId);
|
|
59766
|
+
const carried = carriedWork(ctx, feature.id);
|
|
59767
|
+
const withheldDocs = withheldFeatureDocs({ carriedNotesOpen: carried.carriedNotes > 0 });
|
|
59435
59768
|
const docs2 = [];
|
|
59436
59769
|
const moreDocs = [];
|
|
59437
59770
|
for (const summary of listDocs(ctx, feature)) {
|
|
@@ -59445,7 +59778,7 @@ function featureContext(ctx, reader) {
|
|
|
59445
59778
|
docs2.push({ relPath: summary.relPath, content });
|
|
59446
59779
|
continue;
|
|
59447
59780
|
}
|
|
59448
|
-
const withheld =
|
|
59781
|
+
const withheld = withheldDocs[summary.relPath.toLowerCase()];
|
|
59449
59782
|
moreDocs.push({
|
|
59450
59783
|
relPath: summary.relPath,
|
|
59451
59784
|
title: summary.title,
|
|
@@ -59461,6 +59794,7 @@ function featureContext(ctx, reader) {
|
|
|
59461
59794
|
docs: docs2,
|
|
59462
59795
|
moreDocs,
|
|
59463
59796
|
docsNote: DOCS_NOTE,
|
|
59797
|
+
openDefects: carried.openDefects,
|
|
59464
59798
|
tickets: listByFeature2(ctx, feature.id).map(stripDigest),
|
|
59465
59799
|
annotatedModels: annotatedModels(ctx)
|
|
59466
59800
|
};
|
|
@@ -59507,9 +59841,16 @@ function toolResolveWaypoint(ctx, session, input) {
|
|
|
59507
59841
|
frontierIds: after3.map((w) => w.id)
|
|
59508
59842
|
};
|
|
59509
59843
|
}
|
|
59844
|
+
function refuseMisKindedReview(inputs) {
|
|
59845
|
+
const misKinded = inputs.find((t) => REVIEW_TITLE.test(t.title) && t.kind !== "review");
|
|
59846
|
+
if (!misKinded)
|
|
59847
|
+
return;
|
|
59848
|
+
throw new GateError(`ticket "${misKinded.title}" is titled like a review ticket but its kind is ` + `"${misKinded.kind ?? "implementation"}" \u2014 set kind: "review" if it is the batch-closing ` + "review (almost certainly), or retitle it if it genuinely is an implementation ticket. " + "Nothing was stored; fix it and re-emit the batch.");
|
|
59849
|
+
}
|
|
59510
59850
|
function toolEmitTickets(ctx, session, input) {
|
|
59511
59851
|
const feature = getFeatureRow(ctx, requireFeatureId(session));
|
|
59512
59852
|
refuseIfReadOnly(session, "emitting tickets");
|
|
59853
|
+
refuseMisKindedReview(input.tickets);
|
|
59513
59854
|
const stored = storeTickets(ctx, feature.id, input.tickets);
|
|
59514
59855
|
return {
|
|
59515
59856
|
stored: stored.length,
|
|
@@ -59571,14 +59912,26 @@ function toolCompletePhase(ctx, session, input) {
|
|
|
59571
59912
|
message: `session marked phase '${input.phase}' complete`,
|
|
59572
59913
|
data: { phase: input.phase, currentPhase: feature.phase }
|
|
59573
59914
|
});
|
|
59915
|
+
if (input.phase === "tickets" && isPastPhase(feature, "tickets")) {
|
|
59916
|
+
return {
|
|
59917
|
+
ok: true,
|
|
59918
|
+
nextPhase: feature.phase,
|
|
59919
|
+
note: "tickets phase already crossed \u2014 the burn has started; nothing left to complete"
|
|
59920
|
+
};
|
|
59921
|
+
}
|
|
59574
59922
|
const gate = nextGate(feature);
|
|
59575
59923
|
if (gate?.id === "G3") {
|
|
59924
|
+
const result = checkGate(ctx, gate.check, feature);
|
|
59925
|
+
if (!result.satisfied) {
|
|
59926
|
+
const failed = requirement(gate);
|
|
59927
|
+
return {
|
|
59928
|
+
ok: false,
|
|
59929
|
+
reason: result.reason ?? `gate ${gate.id} not satisfied`,
|
|
59930
|
+
...failed ? { gate: failed } : {}
|
|
59931
|
+
};
|
|
59932
|
+
}
|
|
59576
59933
|
const next4 = nextPhase(feature) ?? "implementation";
|
|
59577
|
-
|
|
59578
|
-
type: "tickets.awaiting_burn",
|
|
59579
|
-
message: "tickets complete \u2014 waiting on the human Burn click (gate G3)",
|
|
59580
|
-
data: { phase: feature.phase, nextPhase: next4, waitingOn: "human burn" }
|
|
59581
|
-
});
|
|
59934
|
+
markTicketsReady(ctx, feature.id, next4);
|
|
59582
59935
|
const after3 = requirement(nextGate({ ...feature, phase: next4 }));
|
|
59583
59936
|
return {
|
|
59584
59937
|
ok: true,
|
|
@@ -60226,7 +60579,7 @@ function buildMcpServer(audience) {
|
|
|
60226
60579
|
if (wants("complete_phase")) {
|
|
60227
60580
|
server.registerTool("complete_phase", {
|
|
60228
60581
|
title: "Complete phase",
|
|
60229
|
-
description: "Mark the named phase complete. Runs the gate check server-side and advances the " + "feature, or returns { ok: false, reason, gate } naming what the gate wanted. On " + "success it also returns `nextGate` \u2014 what the FOLLOWING gate requires \u2014 so the next " + 'step is not a guess. The tickets \u2192 implementation gate (G3) is the human "Burn" ' + "approval: completing the tickets phase records the work done and returns " + '{ ok: true, nextPhase: "implementation", waitingOn: "human burn" } WITHOUT advancing.',
|
|
60582
|
+
description: "Mark the named phase complete. Runs the gate check server-side and advances the " + "feature, or returns { ok: false, reason, gate } naming what the gate wanted. On " + "success it also returns `nextGate` \u2014 what the FOLLOWING gate requires \u2014 so the next " + 'step is not a guess. The tickets \u2192 implementation gate (G3) is the human "Burn" ' + "approval: completing the tickets phase records the work done and returns " + '{ ok: true, nextPhase: "implementation", waitingOn: "human burn" } WITHOUT advancing. ' + "That call is also what ARMS the Burn click \u2014 until it lands, the button waits on you " + "rather than burning tickets you are still writing, so make it the last thing you do " + "in the tickets phase. If the human burned while you were closing out, it comes back " + "{ ok: true, note } saying the phase was already crossed: nothing left to do.",
|
|
60230
60583
|
inputSchema: {
|
|
60231
60584
|
phase: CompletablePhase.describe("The phase you are finishing \u2014 normally the feature\u2019s current one. `shipped` is not " + "completable by any session: a feature ships when the human merges it.")
|
|
60232
60585
|
}
|
|
@@ -60253,7 +60606,7 @@ async function resolveAudience(sessionId, runId) {
|
|
|
60253
60606
|
return;
|
|
60254
60607
|
}
|
|
60255
60608
|
}
|
|
60256
|
-
var DOCS_NOTE, DRAFTING_KINDS, ADRS_NOTE, FEATURE_KINDS, FEATURE_WRITE_KINDS, PROJECT_KINDS, ALL_AUDIENCES, TOOL_AUDIENCES, CompletablePhase, EventType, mcp, server_default;
|
|
60609
|
+
var DOCS_NOTE, REVIEW_TITLE, DRAFTING_KINDS, ADRS_NOTE, FEATURE_KINDS, FEATURE_WRITE_KINDS, PROJECT_KINDS, ALL_AUDIENCES, TOOL_AUDIENCES, CompletablePhase, EventType, mcp, server_default;
|
|
60257
60610
|
var init_server = __esm(() => {
|
|
60258
60611
|
init_src();
|
|
60259
60612
|
init_paths();
|
|
@@ -60261,8 +60614,10 @@ var init_server = __esm(() => {
|
|
|
60261
60614
|
init_artifacts();
|
|
60262
60615
|
init_sessions();
|
|
60263
60616
|
init_features();
|
|
60617
|
+
init_carried_work();
|
|
60264
60618
|
init_events();
|
|
60265
60619
|
init_findings();
|
|
60620
|
+
init_gates();
|
|
60266
60621
|
init_review_findings();
|
|
60267
60622
|
init_git();
|
|
60268
60623
|
init_knowledge();
|
|
@@ -60271,6 +60626,7 @@ var init_server = __esm(() => {
|
|
|
60271
60626
|
init_tickets();
|
|
60272
60627
|
init_waypoints();
|
|
60273
60628
|
DOCS_NOTE = "docs[] holds this feature\u2019s canonical docs in full. moreDocs[] is everything else that " + "exists in docs/features/<slug>/ \u2014 not inlined, not gone: read any of them with " + "read_feature_doc({ relPath }). A `withheld` reason means it was left out on purpose; fetch " + "it anyway if a ticket points at it.";
|
|
60629
|
+
REVIEW_TITLE = /^review[: ]/i;
|
|
60274
60630
|
DRAFTING_KINDS = ["ideation", "revisit", "waypoint", "converge"];
|
|
60275
60631
|
ADRS_NOTE = "adrs[] is an index of the project\u2019s LIVE decisions (superseded ones are already omitted), not " + "their text. Read the ones your work touches with read_adr({ relPath }) \u2014 they bind you the " + "same either way. They are also plain files in this worktree at docs/adr/.";
|
|
60276
60632
|
FEATURE_KINDS = [
|
|
@@ -60394,7 +60750,7 @@ var init_ws = __esm(() => {
|
|
|
60394
60750
|
});
|
|
60395
60751
|
|
|
60396
60752
|
// src/services/prep.ts
|
|
60397
|
-
import { and as
|
|
60753
|
+
import { and as and11, desc as desc6, eq as eq14, inArray as inArray5 } from "drizzle-orm";
|
|
60398
60754
|
function keysToPrepare(ctx, project3) {
|
|
60399
60755
|
return unsetPreparedKeys(project3).filter((key) => isOverwritable(ctx, project3.id, key));
|
|
60400
60756
|
}
|
|
@@ -60404,10 +60760,10 @@ function isPrepared(ctx, project3) {
|
|
|
60404
60760
|
return hasCompletedProjectSession(ctx, project3.id, "prepare");
|
|
60405
60761
|
}
|
|
60406
60762
|
function preparedAt(ctx, projectId) {
|
|
60407
|
-
const prepareSessions = new Set(ctx.db.select({ id: sessions.id }).from(sessions).where(
|
|
60763
|
+
const prepareSessions = new Set(ctx.db.select({ id: sessions.id }).from(sessions).where(and11(eq14(sessions.projectId, projectId), eq14(sessions.kind, "prepare"))).all().map((r) => r.id));
|
|
60408
60764
|
if (prepareSessions.size === 0)
|
|
60409
60765
|
return null;
|
|
60410
|
-
const ends = ctx.db.select({ ts: events.ts, data: events.data }).from(events).where(
|
|
60766
|
+
const ends = ctx.db.select({ ts: events.ts, data: events.data }).from(events).where(and11(eq14(events.projectId, projectId), inArray5(events.type, ["session.ended", "session.reconciled"]))).orderBy(desc6(events.ts)).all();
|
|
60411
60767
|
for (const end5 of ends) {
|
|
60412
60768
|
const sessionId = end5.data?.sessionId;
|
|
60413
60769
|
if (sessionId && prepareSessions.has(sessionId))
|
|
@@ -60935,12 +61291,12 @@ var init_web = __esm(() => {
|
|
|
60935
61291
|
|
|
60936
61292
|
// src/version.ts
|
|
60937
61293
|
import { readFileSync as readFileSync12 } from "fs";
|
|
60938
|
-
import { dirname as
|
|
61294
|
+
import { dirname as dirname13, join as join21 } from "path";
|
|
60939
61295
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
60940
61296
|
function runcastleVersion() {
|
|
60941
61297
|
if (cached4)
|
|
60942
61298
|
return cached4;
|
|
60943
|
-
const here =
|
|
61299
|
+
const here = dirname13(fileURLToPath9(import.meta.url));
|
|
60944
61300
|
for (const pkgPath of [join21(here, "..", "package.json"), join21(here, "package.json")]) {
|
|
60945
61301
|
try {
|
|
60946
61302
|
const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
@@ -60959,7 +61315,7 @@ var init_version = () => {};
|
|
|
60959
61315
|
// src/services/install-id.ts
|
|
60960
61316
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
60961
61317
|
import { existsSync as existsSync22, mkdirSync as mkdirSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
|
|
60962
|
-
import { dirname as
|
|
61318
|
+
import { dirname as dirname14 } from "path";
|
|
60963
61319
|
function getInstallId() {
|
|
60964
61320
|
const file = installIdPath();
|
|
60965
61321
|
try {
|
|
@@ -60969,7 +61325,7 @@ function getInstallId() {
|
|
|
60969
61325
|
return existing;
|
|
60970
61326
|
}
|
|
60971
61327
|
const id3 = randomUUID3();
|
|
60972
|
-
mkdirSync13(
|
|
61328
|
+
mkdirSync13(dirname14(file), { recursive: true });
|
|
60973
61329
|
writeFileSync9(file, `${id3}
|
|
60974
61330
|
`);
|
|
60975
61331
|
return id3;
|
|
@@ -61165,7 +61521,7 @@ var init_events2 = __esm(() => {
|
|
|
61165
61521
|
// src/launcher/launcher.ts
|
|
61166
61522
|
import { existsSync as existsSync23 } from "fs";
|
|
61167
61523
|
import { join as join23 } from "path";
|
|
61168
|
-
import { and as
|
|
61524
|
+
import { and as and12, eq as eq15 } from "drizzle-orm";
|
|
61169
61525
|
function ptyExitMessage(exitCode) {
|
|
61170
61526
|
const label = typeof exitCode === "number" ? String(exitCode) : "unknown";
|
|
61171
61527
|
return `terminal exited (code ${label})`;
|
|
@@ -61185,7 +61541,7 @@ function renderCommand(runtime5, spec) {
|
|
|
61185
61541
|
return [...env, runtime5.binary, ...spec.argv].join(" ");
|
|
61186
61542
|
}
|
|
61187
61543
|
function activeRunFor(ctx, featureId) {
|
|
61188
|
-
const row = ctx.db.select().from(runs).where(
|
|
61544
|
+
const row = ctx.db.select().from(runs).where(and12(eq15(runs.featureId, featureId), eq15(runs.status, "running"))).limit(1).get();
|
|
61189
61545
|
return row ? rowToRun(row) : null;
|
|
61190
61546
|
}
|
|
61191
61547
|
function assertSpawnable(ctx, feature, excludeSessionId) {
|
|
@@ -61266,6 +61622,7 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
61266
61622
|
worktreePath,
|
|
61267
61623
|
model
|
|
61268
61624
|
});
|
|
61625
|
+
const carried = carriedWork(ctx, feature.id);
|
|
61269
61626
|
const plan = planKickoff({
|
|
61270
61627
|
kind: input.kind,
|
|
61271
61628
|
lap: feature.lap,
|
|
@@ -61274,7 +61631,8 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
61274
61631
|
lap: feature.lap,
|
|
61275
61632
|
phase: feature.phase,
|
|
61276
61633
|
ticketLaps: listByFeature2(ctx, feature.id).map((t2) => t2.lap)
|
|
61277
|
-
})
|
|
61634
|
+
}),
|
|
61635
|
+
carried
|
|
61278
61636
|
});
|
|
61279
61637
|
let waypoint;
|
|
61280
61638
|
let resumeSessionId;
|
|
@@ -61371,6 +61729,7 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
61371
61729
|
config: ctx.config,
|
|
61372
61730
|
waypoint,
|
|
61373
61731
|
lap: plan.lap,
|
|
61732
|
+
carried: plan.lap === undefined ? undefined : carried,
|
|
61374
61733
|
purpose: input.purpose,
|
|
61375
61734
|
worktreePath,
|
|
61376
61735
|
serverUrl: serverUrlFor(ctx.config),
|
|
@@ -61792,6 +62151,7 @@ var init_launcher = __esm(() => {
|
|
|
61792
62151
|
init_errors();
|
|
61793
62152
|
init_end_session();
|
|
61794
62153
|
init_registry();
|
|
62154
|
+
init_carried_work();
|
|
61795
62155
|
init_docs_watch();
|
|
61796
62156
|
init_events();
|
|
61797
62157
|
init_gates();
|
|
@@ -61958,6 +62318,7 @@ var init_feature = __esm(() => {
|
|
|
61958
62318
|
init_src();
|
|
61959
62319
|
init_launcher();
|
|
61960
62320
|
init_sessions();
|
|
62321
|
+
init_carried_work();
|
|
61961
62322
|
init_events();
|
|
61962
62323
|
init_features();
|
|
61963
62324
|
init_gates();
|
|
@@ -61997,7 +62358,7 @@ var init_feature = __esm(() => {
|
|
|
61997
62358
|
rethink: publicProcedure.input(z7.object({ featureId: z7.string() })).mutation(({ ctx, input }) => rethinkAndLaunch(ctx, input.featureId, (feature) => launchSession(ctx, {
|
|
61998
62359
|
featureId: input.featureId,
|
|
61999
62360
|
kind: "revisit",
|
|
62000
|
-
kickoffLine: lapKickoff(feature.lap)
|
|
62361
|
+
kickoffLine: lapKickoff(feature.lap, carriedWork(ctx, input.featureId))
|
|
62001
62362
|
}))),
|
|
62002
62363
|
resendKickoff: publicProcedure.input(z7.object({ sessionId: z7.string() })).mutation(({ ctx, input }) => resendKickoff(ctx, input.sessionId)),
|
|
62003
62364
|
endSession: publicProcedure.input(z7.object({ sessionId: z7.string() })).mutation(({ ctx, input }) => endSession(ctx, input.sessionId)),
|
|
@@ -62058,7 +62419,7 @@ var init_feature = __esm(() => {
|
|
|
62058
62419
|
// src/services/fsbrowse.ts
|
|
62059
62420
|
import { existsSync as existsSync24, readdirSync as readdirSync6, statSync as statSync8 } from "fs";
|
|
62060
62421
|
import { homedir as homedir4 } from "os";
|
|
62061
|
-
import { basename as basename3, dirname as
|
|
62422
|
+
import { basename as basename3, dirname as dirname15, isAbsolute as isAbsolute3, join as join26, resolve as resolve12, sep as sep4 } from "path";
|
|
62062
62423
|
function expandPath(input, home = homedir4()) {
|
|
62063
62424
|
const trimmed = input.trim();
|
|
62064
62425
|
if (trimmed === "")
|
|
@@ -62111,7 +62472,7 @@ function crumbsFor(dir) {
|
|
|
62111
62472
|
let cur = dir;
|
|
62112
62473
|
for (;; ) {
|
|
62113
62474
|
out.unshift({ name: basename3(cur) || cur, path: cur });
|
|
62114
|
-
const parent =
|
|
62475
|
+
const parent = dirname15(cur);
|
|
62115
62476
|
if (parent === cur)
|
|
62116
62477
|
break;
|
|
62117
62478
|
cur = parent;
|
|
@@ -62119,7 +62480,7 @@ function crumbsFor(dir) {
|
|
|
62119
62480
|
return out;
|
|
62120
62481
|
}
|
|
62121
62482
|
function parentOf(dir) {
|
|
62122
|
-
const parent =
|
|
62483
|
+
const parent = dirname15(dir);
|
|
62123
62484
|
return parent === dir ? null : parent;
|
|
62124
62485
|
}
|
|
62125
62486
|
function browseDir(input, showHidden = false, home = homedir4()) {
|
|
@@ -62133,7 +62494,7 @@ function browseDir(input, showHidden = false, home = homedir4()) {
|
|
|
62133
62494
|
let dir = target;
|
|
62134
62495
|
try {
|
|
62135
62496
|
if (!statSync8(target).isDirectory())
|
|
62136
|
-
dir =
|
|
62497
|
+
dir = dirname15(target);
|
|
62137
62498
|
} catch (e) {
|
|
62138
62499
|
throw new InvalidInputError(`cannot read path: ${target} (${errMsg4(e)})`);
|
|
62139
62500
|
}
|
|
@@ -62388,7 +62749,7 @@ var init_conversations = __esm(() => {
|
|
|
62388
62749
|
// src/services/projects.ts
|
|
62389
62750
|
import { existsSync as existsSync25 } from "fs";
|
|
62390
62751
|
import { basename as basename4, join as join27 } from "path";
|
|
62391
|
-
import { eq as
|
|
62752
|
+
import { eq as eq16, isNull } from "drizzle-orm";
|
|
62392
62753
|
function listProjects(ctx) {
|
|
62393
62754
|
return ctx.db.select().from(projects).where(isNull(projects.closedAt)).all().map(rowToProject);
|
|
62394
62755
|
}
|
|
@@ -62409,7 +62770,7 @@ async function openProject(ctx, rawPath) {
|
|
|
62409
62770
|
const name = basename4(repoPath) || repoPath;
|
|
62410
62771
|
const existing = getProjectByRepoPath(ctx, repoPath) ?? findProjectByCanonPath(ctx, repoPath);
|
|
62411
62772
|
if (existing) {
|
|
62412
|
-
ctx.db.update(projects).set({ repoPath, closedAt: null }).where(
|
|
62773
|
+
ctx.db.update(projects).set({ repoPath, closedAt: null }).where(eq16(projects.id, existing.id)).run();
|
|
62413
62774
|
const reopened = { ...existing, repoPath, closedAt: undefined };
|
|
62414
62775
|
emitProject(ctx, existing.id, {
|
|
62415
62776
|
type: "project.opened",
|
|
@@ -62439,7 +62800,7 @@ function closeProject(ctx, projectId) {
|
|
|
62439
62800
|
if (projectHasActiveRun(ctx, projectId)) {
|
|
62440
62801
|
throw new InvalidInputError(`cannot close ${project3.name}: a run is in flight \u2014 wait for it to finish or cancel it first`);
|
|
62441
62802
|
}
|
|
62442
|
-
ctx.db.update(projects).set({ closedAt: Date.now() }).where(
|
|
62803
|
+
ctx.db.update(projects).set({ closedAt: Date.now() }).where(eq16(projects.id, projectId)).run();
|
|
62443
62804
|
emitProject(ctx, projectId, {
|
|
62444
62805
|
type: "project.closed",
|
|
62445
62806
|
message: `project ${project3.name} closed`
|
|
@@ -62448,7 +62809,7 @@ function closeProject(ctx, projectId) {
|
|
|
62448
62809
|
}
|
|
62449
62810
|
function renameProject(ctx, projectId, name) {
|
|
62450
62811
|
const project3 = requireProjectById(ctx, projectId);
|
|
62451
|
-
ctx.db.update(projects).set({ name }).where(
|
|
62812
|
+
ctx.db.update(projects).set({ name }).where(eq16(projects.id, projectId)).run();
|
|
62452
62813
|
emitProject(ctx, projectId, {
|
|
62453
62814
|
type: "project.renamed",
|
|
62454
62815
|
message: `project renamed ${project3.name} \u2192 ${name}`,
|
|
@@ -62540,9 +62901,9 @@ var init_review_findings2 = __esm(() => {
|
|
|
62540
62901
|
});
|
|
62541
62902
|
|
|
62542
62903
|
// src/services/runs.ts
|
|
62543
|
-
import { and as
|
|
62904
|
+
import { and as and13, asc as asc6, eq as eq17, isNotNull as isNotNull3 } from "drizzle-orm";
|
|
62544
62905
|
function runTicketIds(ctx, runId) {
|
|
62545
|
-
const rows = ctx.db.select({ ticketId: events.ticketId }).from(events).where(
|
|
62906
|
+
const rows = ctx.db.select({ ticketId: events.ticketId }).from(events).where(and13(eq17(events.runId, runId), isNotNull3(events.ticketId))).orderBy(asc6(events.id)).all();
|
|
62546
62907
|
const seen = new Set;
|
|
62547
62908
|
for (const row of rows)
|
|
62548
62909
|
if (row.ticketId)
|
|
@@ -62550,7 +62911,7 @@ function runTicketIds(ctx, runId) {
|
|
|
62550
62911
|
return [...seen];
|
|
62551
62912
|
}
|
|
62552
62913
|
function runLap(ctx, runId) {
|
|
62553
|
-
const row = ctx.db.select({ lap: events.lap }).from(events).where(
|
|
62914
|
+
const row = ctx.db.select({ lap: events.lap }).from(events).where(eq17(events.runId, runId)).orderBy(asc6(events.id)).limit(1).get();
|
|
62554
62915
|
return row?.lap ?? 1;
|
|
62555
62916
|
}
|
|
62556
62917
|
function listRunSummaries(ctx, featureId) {
|
|
@@ -62578,6 +62939,7 @@ import * as z10 from "zod";
|
|
|
62578
62939
|
var runRouter;
|
|
62579
62940
|
var init_run = __esm(() => {
|
|
62580
62941
|
init_agent_stream();
|
|
62942
|
+
init_events();
|
|
62581
62943
|
init_repo();
|
|
62582
62944
|
init_runs();
|
|
62583
62945
|
init_runner();
|
|
@@ -62586,10 +62948,17 @@ var init_run = __esm(() => {
|
|
|
62586
62948
|
get: publicProcedure.input(z10.object({ runId: z10.string() })).query(({ ctx, input }) => getRunWithTickets(ctx, input.runId)),
|
|
62587
62949
|
listByFeature: publicProcedure.input(z10.object({ featureId: z10.string() })).query(({ ctx, input }) => listRunSummaries(ctx, input.featureId)),
|
|
62588
62950
|
agentTranscript: publicProcedure.input(z10.object({ ticketId: z10.string(), after: z10.number().int().optional() })).query(({ input }) => readTranscript(input.ticketId, input.after ?? -1)),
|
|
62589
|
-
cancel: publicProcedure.input(z10.object({ runId: z10.string() })).mutation(({ ctx, input }) => {
|
|
62590
|
-
getRunRow(ctx, input.runId);
|
|
62591
|
-
cancelRun(input.runId);
|
|
62592
|
-
|
|
62951
|
+
cancel: publicProcedure.input(z10.object({ runId: z10.string() })).mutation(async ({ ctx, input }) => {
|
|
62952
|
+
const run7 = getRunRow(ctx, input.runId);
|
|
62953
|
+
const { confirmed } = await cancelRun(input.runId);
|
|
62954
|
+
if (!confirmed) {
|
|
62955
|
+
emit(ctx, run7.featureId, {
|
|
62956
|
+
type: "run.cancel_timeout",
|
|
62957
|
+
message: "cancel timed out \u2014 an agent of this run may still be running",
|
|
62958
|
+
runId: run7.id
|
|
62959
|
+
});
|
|
62960
|
+
}
|
|
62961
|
+
return { ok: true, confirmed };
|
|
62593
62962
|
})
|
|
62594
62963
|
});
|
|
62595
62964
|
});
|
|
@@ -62607,6 +62976,31 @@ var init_settings2 = __esm(() => {
|
|
|
62607
62976
|
});
|
|
62608
62977
|
});
|
|
62609
62978
|
|
|
62979
|
+
// src/doctor/afk-env.ts
|
|
62980
|
+
import { existsSync as existsSync26, readFileSync as readFileSync15 } from "fs";
|
|
62981
|
+
function envWithAfkCredentials() {
|
|
62982
|
+
const merged = { ...process.env };
|
|
62983
|
+
try {
|
|
62984
|
+
const path2 = envPath();
|
|
62985
|
+
if (existsSync26(path2)) {
|
|
62986
|
+
const fromFile = parseEnvFile3(readFileSync15(path2, "utf8"));
|
|
62987
|
+
for (const runtime5 of AGENT_RUNTIMES) {
|
|
62988
|
+
const key = RUNTIME_SPECS[runtime5].afkKey;
|
|
62989
|
+
const value = fromFile[key];
|
|
62990
|
+
if (value && value.length > 0)
|
|
62991
|
+
merged[key] = value;
|
|
62992
|
+
}
|
|
62993
|
+
}
|
|
62994
|
+
} catch {}
|
|
62995
|
+
return merged;
|
|
62996
|
+
}
|
|
62997
|
+
var init_afk_env = __esm(() => {
|
|
62998
|
+
init_src();
|
|
62999
|
+
init_paths();
|
|
63000
|
+
init_ticket_burner();
|
|
63001
|
+
init_doctor();
|
|
63002
|
+
});
|
|
63003
|
+
|
|
62610
63004
|
// src/trpc/routers/setup.ts
|
|
62611
63005
|
import { isNotNull as isNotNull4 } from "drizzle-orm";
|
|
62612
63006
|
import * as z12 from "zod";
|
|
@@ -62614,8 +63008,10 @@ var setupRouter;
|
|
|
62614
63008
|
var init_setup2 = __esm(() => {
|
|
62615
63009
|
init_src();
|
|
62616
63010
|
init_schema();
|
|
63011
|
+
init_afk_env();
|
|
62617
63012
|
init_doctor();
|
|
62618
63013
|
init_system_exec();
|
|
63014
|
+
init_asset_paths();
|
|
62619
63015
|
init_registry();
|
|
62620
63016
|
init_setup();
|
|
62621
63017
|
init_resolve_executable();
|
|
@@ -62625,6 +63021,8 @@ var init_setup2 = __esm(() => {
|
|
|
62625
63021
|
const projectModels = ctx.db.select({ model: projects.model }).from(projects).where(isNotNull4(projects.model)).all().map((p) => p.model);
|
|
62626
63022
|
return runDoctor({
|
|
62627
63023
|
exec: createSystemExec(),
|
|
63024
|
+
burnerDockerfile: burnerDockerfilePath(),
|
|
63025
|
+
env: envWithAfkCredentials(),
|
|
62628
63026
|
runtimes: configuredRuntimes(ctx.config, projectModels),
|
|
62629
63027
|
...ctx.config.sandboxImage ? { imageName: ctx.config.sandboxImage } : {}
|
|
62630
63028
|
});
|
|
@@ -62738,8 +63136,17 @@ var init_test_notes2 = __esm(() => {
|
|
|
62738
63136
|
|
|
62739
63137
|
// src/trpc/routers/ticket.ts
|
|
62740
63138
|
import * as z15 from "zod";
|
|
63139
|
+
function noteStopTimeout(ctx, ticketId) {
|
|
63140
|
+
const ticket = getTicket(ctx, ticketId);
|
|
63141
|
+
emit(ctx, ticket.featureId, {
|
|
63142
|
+
type: "ticket.stop_timeout",
|
|
63143
|
+
message: `ticket #${ticket.seq}: stop timed out \u2014 the process may still be running`,
|
|
63144
|
+
ticketId: ticket.id
|
|
63145
|
+
});
|
|
63146
|
+
}
|
|
62741
63147
|
var ticketRouter;
|
|
62742
63148
|
var init_ticket = __esm(() => {
|
|
63149
|
+
init_events();
|
|
62743
63150
|
init_features();
|
|
62744
63151
|
init_repo();
|
|
62745
63152
|
init_tickets();
|
|
@@ -62747,17 +63154,27 @@ var init_ticket = __esm(() => {
|
|
|
62747
63154
|
init_context();
|
|
62748
63155
|
ticketRouter = router({
|
|
62749
63156
|
retry: publicProcedure.input(z15.object({ ticketId: z15.string(), fresh: z15.boolean().optional() })).mutation(({ ctx, input }) => retryTicket(ctx, input.ticketId, { fresh: input.fresh })),
|
|
62750
|
-
stop: publicProcedure.input(z15.object({ ticketId: z15.string() })).mutation(({ ctx, input }) => {
|
|
62751
|
-
|
|
62752
|
-
|
|
63157
|
+
stop: publicProcedure.input(z15.object({ ticketId: z15.string() })).mutation(async ({ ctx, input }) => {
|
|
63158
|
+
const { stopped, confirmed } = await stopTicketRun(input.ticketId);
|
|
63159
|
+
if (stopped) {
|
|
63160
|
+
if (!confirmed)
|
|
63161
|
+
noteStopTimeout(ctx, input.ticketId);
|
|
63162
|
+
return { stopped: true, swept: false, confirmed };
|
|
63163
|
+
}
|
|
62753
63164
|
const ticket = getTicket(ctx, input.ticketId);
|
|
62754
63165
|
if (ticket.status === "burning" && !hasActiveRun(ctx, ticket.featureId)) {
|
|
62755
63166
|
sweepOrphanedBurning(ctx, ticket.featureId, "orphaned \u2014 the run that was burning it is gone");
|
|
62756
|
-
return { stopped: false, swept: true };
|
|
63167
|
+
return { stopped: false, swept: true, confirmed };
|
|
62757
63168
|
}
|
|
62758
|
-
return { stopped: false, swept: false };
|
|
63169
|
+
return { stopped: false, swept: false, confirmed };
|
|
63170
|
+
}),
|
|
63171
|
+
cancel: publicProcedure.input(z15.object({ ticketId: z15.string(), reason: z15.string().optional() })).mutation(async ({ ctx, input }) => {
|
|
63172
|
+
const { stopped, confirmed } = await stopTicketRun(input.ticketId);
|
|
63173
|
+
if (stopped && !confirmed)
|
|
63174
|
+
noteStopTimeout(ctx, input.ticketId);
|
|
63175
|
+
const ticket = cancelTicket(ctx, input.ticketId, input.reason, { agentStopped: stopped });
|
|
63176
|
+
return { ticket, stopped, confirmed };
|
|
62759
63177
|
}),
|
|
62760
|
-
cancel: publicProcedure.input(z15.object({ ticketId: z15.string(), reason: z15.string().optional() })).mutation(({ ctx, input }) => cancelTicket(ctx, input.ticketId, input.reason)),
|
|
62761
63178
|
durationStats: publicProcedure.input(z15.object({ projectId: z15.string() })).query(({ ctx, input }) => ticketDurationStats(ctx, input.projectId)),
|
|
62762
63179
|
edit: publicProcedure.input(z15.object({
|
|
62763
63180
|
ticketId: z15.string(),
|
|
@@ -62804,14 +63221,14 @@ var init_router = __esm(() => {
|
|
|
62804
63221
|
});
|
|
62805
63222
|
|
|
62806
63223
|
// src/workflows/reconcile-runs.ts
|
|
62807
|
-
import { eq as
|
|
63224
|
+
import { eq as eq18 } from "drizzle-orm";
|
|
62808
63225
|
async function reconcileStaleRuns(ctx) {
|
|
62809
|
-
const stale = ctx.db.select().from(runs).where(
|
|
63226
|
+
const stale = ctx.db.select().from(runs).where(eq18(runs.status, "running")).all().map(rowToRun);
|
|
62810
63227
|
const reconciled = [];
|
|
62811
63228
|
for (const run7 of stale) {
|
|
62812
63229
|
if (isRunActive(run7.id))
|
|
62813
63230
|
continue;
|
|
62814
|
-
ctx.db.update(runs).set({ status: "failed", endedAt: Date.now(), summary: "orphaned by server restart" }).where(
|
|
63231
|
+
ctx.db.update(runs).set({ status: "failed", endedAt: Date.now(), summary: "orphaned by server restart" }).where(eq18(runs.id, run7.id)).run();
|
|
62815
63232
|
const released = releaseForSession(ctx, run7.id);
|
|
62816
63233
|
const swept = workflowClaimsFeatureBranch(run7.workflow) ? sweepOrphanedBurning(ctx, run7.featureId, "orphaned by server restart \u2014 retry to resume its commits") : [];
|
|
62817
63234
|
if (workflowClaimsFeatureBranch(run7.workflow)) {
|