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