runcastle 1.3.4 → 1.3.5
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 +1074 -411
- package/drizzle/0037_finding_carry_and_provenance.sql +5 -0
- package/drizzle/0037_project_sandbox_image.sql +1 -0
- package/index.js +1070 -409
- package/package.json +1 -1
- package/skills/burner/review-ticket.md +11 -5
- package/skills/packs/runcastle/skills/prepare/SKILL.md +35 -1
- package/skills/packs/runcastle/skills/revisit/SKILL.md +10 -2
- package/skills/packs/runcastle/skills/tickets/SKILL.md +1 -1
- package/web/assets/index-Vbh17gX3.js +186 -0
- package/web/index.html +1 -1
- package/web/assets/index-CgCA-f62.js +0 -186
package/bin/runcastle.js
CHANGED
|
@@ -53,8 +53,8 @@ var init_docs = __esm(() => {
|
|
|
53
53
|
|
|
54
54
|
// ../core/src/config.ts
|
|
55
55
|
import * as z from "zod";
|
|
56
|
-
function resolveSandboxImage(config) {
|
|
57
|
-
return config.sandboxImage
|
|
56
|
+
function resolveSandboxImage(config, project) {
|
|
57
|
+
return project?.sandboxImage?.trim() || config.sandboxImage?.trim() || DEFAULT_SANDBOX_IMAGE;
|
|
58
58
|
}
|
|
59
59
|
function resolveBurnCacheMode(config) {
|
|
60
60
|
return BURN_CACHE_SANDBOXES.includes(config.sandbox) ? config.burnCache : "off";
|
|
@@ -182,6 +182,14 @@ import * as z2 from "zod";
|
|
|
182
182
|
function isProjectSessionKind(kind) {
|
|
183
183
|
return PROJECT_SESSION_KINDS.includes(kind);
|
|
184
184
|
}
|
|
185
|
+
function driveDenialOf(result) {
|
|
186
|
+
return {
|
|
187
|
+
...result.deniedReason ? { deniedReason: result.deniedReason } : {},
|
|
188
|
+
...result.deniedCode ? { deniedCode: result.deniedCode } : {},
|
|
189
|
+
...result.retriable !== undefined ? { retriable: result.retriable } : {},
|
|
190
|
+
...result.dirtyFiles ? { dirtyFiles: result.dirtyFiles } : {}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
185
193
|
function unresolvedMergeConflict(events) {
|
|
186
194
|
let conflict = null;
|
|
187
195
|
for (const event of events) {
|
|
@@ -198,7 +206,7 @@ function unresolvedMergeConflict(events) {
|
|
|
198
206
|
}
|
|
199
207
|
return conflict;
|
|
200
208
|
}
|
|
201
|
-
var Phase, TicketStatus, SessionKind, PROJECT_SESSION_KINDS, SessionPurpose, MergeBranchPair, RunStatus, FeatureStatus, SessionStatus, TicketKind, FindingKind, FindingSeverity, FindingStatus, FindingOpenReason, ReviewFindingInput, ReviewFinding, TicketInput, Ticket, WaypointType, WaypointStatus, WaypointDisposition, WaypointInput, Waypoint, TestNoteStatus, TestNoteAuthor, TestNote, PREPARED_KEYS, PreparedKey, DRIVE_LOOP_KEYS, FindingSource, PROJECT_NAME_MAX = 80, ProjectName, Project, ProjectFinding, Feature, SessionRow, Run, SettingSource, SettingScope, SettingField, SettingsView, SettingsUpdateInput, EventRow, DriveState;
|
|
209
|
+
var Phase, TicketStatus, SessionKind, PROJECT_SESSION_KINDS, SessionPurpose, MergeBranchPair, RunStatus, FeatureStatus, SessionStatus, TicketKind, FindingKind, FindingSeverity, FindingStatus, FindingOpenReason, FindingResolvedBy, ReviewFindingInput, ReviewFinding, TicketInput, Ticket, WaypointType, WaypointStatus, WaypointDisposition, WaypointInput, Waypoint, TestNoteStatus, TestNoteAuthor, TestNote, PREPARED_KEYS, PreparedKey, PROVENANCE_KEYS, ProvenanceKey, DRIVE_LOOP_KEYS, FindingSource, PROJECT_NAME_MAX = 80, ProjectName, Project, ProjectFinding, Feature, SessionRow, Run, SettingSource, SettingScope, SettingField, SettingsView, SettingsUpdateInput, EventRow, DriveState;
|
|
202
210
|
var init_schemas = __esm(() => {
|
|
203
211
|
init_config();
|
|
204
212
|
Phase = z2.enum([
|
|
@@ -232,8 +240,9 @@ var init_schemas = __esm(() => {
|
|
|
232
240
|
TicketKind = z2.enum(["implementation", "review"]);
|
|
233
241
|
FindingKind = z2.enum(["defect", "observation"]);
|
|
234
242
|
FindingSeverity = z2.enum(["high", "medium", "low"]);
|
|
235
|
-
FindingStatus = z2.enum(["open", "fixing", "fixed", "failed", "dismissed"]);
|
|
243
|
+
FindingStatus = z2.enum(["open", "fixing", "fixed", "failed", "dismissed", "carried"]);
|
|
236
244
|
FindingOpenReason = z2.enum(["over-cap", "fix-failed", "verification"]);
|
|
245
|
+
FindingResolvedBy = z2.enum(["fix-ticket", "session"]);
|
|
237
246
|
ReviewFindingInput = z2.object({
|
|
238
247
|
kind: FindingKind,
|
|
239
248
|
severity: FindingSeverity,
|
|
@@ -257,6 +266,9 @@ var init_schemas = __esm(() => {
|
|
|
257
266
|
openReason: FindingOpenReason.nullable(),
|
|
258
267
|
failureReason: z2.string().nullable(),
|
|
259
268
|
fixTicketId: z2.string().nullable(),
|
|
269
|
+
carriedLap: z2.number().nullable(),
|
|
270
|
+
resolvedBy: FindingResolvedBy.nullable(),
|
|
271
|
+
resolutionNote: z2.string().nullable(),
|
|
260
272
|
createdAt: z2.number()
|
|
261
273
|
});
|
|
262
274
|
TicketInput = z2.object({
|
|
@@ -333,12 +345,14 @@ var init_schemas = __esm(() => {
|
|
|
333
345
|
"driveInstructions"
|
|
334
346
|
];
|
|
335
347
|
PreparedKey = z2.enum(PREPARED_KEYS);
|
|
348
|
+
PROVENANCE_KEYS = [...PREPARED_KEYS, "sandboxImage"];
|
|
349
|
+
ProvenanceKey = z2.enum(PROVENANCE_KEYS);
|
|
336
350
|
DRIVE_LOOP_KEYS = [
|
|
337
351
|
"devCommand",
|
|
338
352
|
"driveSetupCommand",
|
|
339
353
|
"driveStopCommand"
|
|
340
354
|
];
|
|
341
|
-
FindingSource = z2.enum(["prep", "human", "session"]);
|
|
355
|
+
FindingSource = z2.enum(["prep", "human", "session", "build"]);
|
|
342
356
|
ProjectName = z2.string().min(1, "a project needs a name").max(PROJECT_NAME_MAX, `a project name can be at most ${PROJECT_NAME_MAX} characters`);
|
|
343
357
|
Project = z2.object({
|
|
344
358
|
id: z2.string(),
|
|
@@ -347,6 +361,7 @@ var init_schemas = __esm(() => {
|
|
|
347
361
|
sessionBranch: z2.string().optional(),
|
|
348
362
|
devCommand: z2.string().optional(),
|
|
349
363
|
model: z2.string().optional(),
|
|
364
|
+
sandboxImage: z2.string().optional(),
|
|
350
365
|
setupCommand: z2.string().optional(),
|
|
351
366
|
verifyCommands: z2.string().optional(),
|
|
352
367
|
knownFailures: z2.string().optional(),
|
|
@@ -357,7 +372,7 @@ var init_schemas = __esm(() => {
|
|
|
357
372
|
closedAt: z2.number().optional()
|
|
358
373
|
});
|
|
359
374
|
ProjectFinding = z2.object({
|
|
360
|
-
key:
|
|
375
|
+
key: ProvenanceKey,
|
|
361
376
|
source: FindingSource,
|
|
362
377
|
evidence: z2.string().optional(),
|
|
363
378
|
establishedAt: z2.number(),
|
|
@@ -645,6 +660,7 @@ var init_db_schema = __esm(() => {
|
|
|
645
660
|
devCommand: text("dev_command"),
|
|
646
661
|
model: text("model"),
|
|
647
662
|
sandbox: text("sandbox"),
|
|
663
|
+
sandboxImage: text("sandbox_image"),
|
|
648
664
|
closedAt: integer("closed_at"),
|
|
649
665
|
setupCommand: text("setup_command"),
|
|
650
666
|
verifyCommands: text("verify_commands"),
|
|
@@ -739,6 +755,9 @@ var init_db_schema = __esm(() => {
|
|
|
739
755
|
openReason: text("open_reason").$type(),
|
|
740
756
|
failureReason: text("failure_reason"),
|
|
741
757
|
fixTicketId: text("fix_ticket_id"),
|
|
758
|
+
carriedLap: integer("carried_lap"),
|
|
759
|
+
resolvedBy: text("resolved_by").$type(),
|
|
760
|
+
resolutionNote: text("resolution_note"),
|
|
742
761
|
createdAt: integer("created_at").notNull()
|
|
743
762
|
});
|
|
744
763
|
testNotes = sqliteTable("test_notes", {
|
|
@@ -2130,6 +2149,7 @@ function rowToProject(row) {
|
|
|
2130
2149
|
sessionBranch: row.sessionBranch ?? undefined,
|
|
2131
2150
|
devCommand: row.devCommand ?? undefined,
|
|
2132
2151
|
model: row.model ?? undefined,
|
|
2152
|
+
sandboxImage: row.sandboxImage ?? undefined,
|
|
2133
2153
|
setupCommand: row.setupCommand ?? undefined,
|
|
2134
2154
|
verifyCommands: row.verifyCommands ?? undefined,
|
|
2135
2155
|
knownFailures: row.knownFailures ?? undefined,
|
|
@@ -2798,8 +2818,8 @@ var init_drive_env = () => {};
|
|
|
2798
2818
|
|
|
2799
2819
|
// src/services/findings.ts
|
|
2800
2820
|
import { and as and4, eq as eq4 } from "drizzle-orm";
|
|
2801
|
-
function
|
|
2802
|
-
return
|
|
2821
|
+
function isProvenanceKey(key) {
|
|
2822
|
+
return PROVENANCE_SET.has(key);
|
|
2803
2823
|
}
|
|
2804
2824
|
function findingRow(ctx, projectId, key) {
|
|
2805
2825
|
return ctx.db.select({
|
|
@@ -2841,7 +2861,7 @@ function recordFinding(ctx, projectId, input) {
|
|
|
2841
2861
|
});
|
|
2842
2862
|
}
|
|
2843
2863
|
function recordHuman(ctx, projectId, key, value) {
|
|
2844
|
-
if (!
|
|
2864
|
+
if (!isProvenanceKey(key))
|
|
2845
2865
|
return;
|
|
2846
2866
|
const existing = findingRow(ctx, projectId, key);
|
|
2847
2867
|
if (value === null) {
|
|
@@ -2885,8 +2905,8 @@ async function listFindings(ctx, project) {
|
|
|
2885
2905
|
distances.set(sha, await commitsSince(project.repoPath, sha, mainLine));
|
|
2886
2906
|
}
|
|
2887
2907
|
}
|
|
2888
|
-
const order = new Map(
|
|
2889
|
-
return rows.filter((row) =>
|
|
2908
|
+
const order = new Map(PROVENANCE_KEYS.map((k, i) => [k, i]));
|
|
2909
|
+
return rows.filter((row) => isProvenanceKey(row.key)).sort((a, b) => (order.get(a.key) ?? 0) - (order.get(b.key) ?? 0)).map((row) => {
|
|
2890
2910
|
const staleCommits = row.establishedSha ? distances.get(row.establishedSha) : undefined;
|
|
2891
2911
|
return {
|
|
2892
2912
|
key: row.key,
|
|
@@ -2906,7 +2926,7 @@ function unsetPreparedKeys(project) {
|
|
|
2906
2926
|
return value === undefined || value.trim() === "";
|
|
2907
2927
|
});
|
|
2908
2928
|
}
|
|
2909
|
-
var VALUE_COLUMN, COLUMN_NAME, PREPARED_SET, UNVERIFIED;
|
|
2929
|
+
var VALUE_COLUMN, COLUMN_NAME, PREPARED_SET, PROVENANCE_SET, UNVERIFIED;
|
|
2910
2930
|
var init_findings = __esm(() => {
|
|
2911
2931
|
init_src();
|
|
2912
2932
|
init_schema();
|
|
@@ -2919,7 +2939,8 @@ var init_findings = __esm(() => {
|
|
|
2919
2939
|
driveSetupCommand: projects.driveSetupCommand,
|
|
2920
2940
|
driveStopCommand: projects.driveStopCommand,
|
|
2921
2941
|
dbResetCommand: projects.dbResetCommand,
|
|
2922
|
-
driveInstructions: projects.driveInstructions
|
|
2942
|
+
driveInstructions: projects.driveInstructions,
|
|
2943
|
+
sandboxImage: projects.sandboxImage
|
|
2923
2944
|
};
|
|
2924
2945
|
COLUMN_NAME = {
|
|
2925
2946
|
setupCommand: "setupCommand",
|
|
@@ -2929,9 +2950,11 @@ var init_findings = __esm(() => {
|
|
|
2929
2950
|
driveSetupCommand: "driveSetupCommand",
|
|
2930
2951
|
driveStopCommand: "driveStopCommand",
|
|
2931
2952
|
dbResetCommand: "dbResetCommand",
|
|
2932
|
-
driveInstructions: "driveInstructions"
|
|
2953
|
+
driveInstructions: "driveInstructions",
|
|
2954
|
+
sandboxImage: "sandboxImage"
|
|
2933
2955
|
};
|
|
2934
2956
|
PREPARED_SET = new Set(PREPARED_KEYS);
|
|
2957
|
+
PROVENANCE_SET = new Set(PROVENANCE_KEYS);
|
|
2935
2958
|
UNVERIFIED = { verifiedAt: null, verifiedSha: null };
|
|
2936
2959
|
});
|
|
2937
2960
|
|
|
@@ -2964,6 +2987,18 @@ function deriveDriveState(info) {
|
|
|
2964
2987
|
function migrationPaths(paths) {
|
|
2965
2988
|
return paths.filter((p) => MIGRATION_DIR_RE.test(p) || DRIZZLE_SQL_RE.test(p));
|
|
2966
2989
|
}
|
|
2990
|
+
function deniedStart(deniedReason, deniedCode, dirtyFiles) {
|
|
2991
|
+
return {
|
|
2992
|
+
ok: false,
|
|
2993
|
+
deniedReason,
|
|
2994
|
+
deniedCode,
|
|
2995
|
+
retriable: deniedCode === "slot_held",
|
|
2996
|
+
...dirtyFiles ? { dirtyFiles } : {}
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
function truncatedFileList(files) {
|
|
3000
|
+
return `${files.slice(0, 5).join(", ")}${files.length > 5 ? ", \u2026" : ""}`;
|
|
3001
|
+
}
|
|
2967
3002
|
function featureBranch(slug) {
|
|
2968
3003
|
return `feature/${slug}`;
|
|
2969
3004
|
}
|
|
@@ -3693,6 +3728,10 @@ async function commitPipelineDocs(repoPath) {
|
|
|
3693
3728
|
await commitDocs(repoPath, "runcastle: docs the pipeline left uncommitted");
|
|
3694
3729
|
} catch {}
|
|
3695
3730
|
}
|
|
3731
|
+
async function driveBlockingPaths(repoPath) {
|
|
3732
|
+
await commitPipelineDocs(repoPath);
|
|
3733
|
+
return porcelainPaths((await git(repoPath).raw(["status", "--porcelain"])).trim());
|
|
3734
|
+
}
|
|
3696
3735
|
function activeTestDriveFeatureId() {
|
|
3697
3736
|
return testDriveState?.kind === "feature" ? testDriveState.featureId : undefined;
|
|
3698
3737
|
}
|
|
@@ -3780,7 +3819,7 @@ async function testDrive(ctx, project, feature, action, purpose = "human") {
|
|
|
3780
3819
|
if (carriedChanges.length > 0) {
|
|
3781
3820
|
emit(ctx, feature.id, {
|
|
3782
3821
|
type: "testdrive.carried_changes",
|
|
3783
|
-
message: `${carriedChanges.length} uncommitted file(s) came back with you onto ${previousBranch2}: ${carriedChanges
|
|
3822
|
+
message: `${carriedChanges.length} uncommitted file(s) came back with you onto ${previousBranch2}: ${truncatedFileList(carriedChanges)}`,
|
|
3784
3823
|
data: { branch: previousBranch2, files: carriedChanges }
|
|
3785
3824
|
});
|
|
3786
3825
|
}
|
|
@@ -3793,18 +3832,22 @@ async function testDrive(ctx, project, feature, action, purpose = "human") {
|
|
|
3793
3832
|
...teardown?.failure ? { hookFailure: teardown.failure } : {}
|
|
3794
3833
|
};
|
|
3795
3834
|
}
|
|
3796
|
-
await
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3835
|
+
const dirtyFiles = await driveBlockingPaths(project.repoPath);
|
|
3836
|
+
if (dirtyFiles.length > 0) {
|
|
3837
|
+
if (purpose === "review") {
|
|
3838
|
+
emit(ctx, feature.id, {
|
|
3839
|
+
type: "reviewdrive.denied",
|
|
3840
|
+
message: `review drive denied \u2014 ${dirtyFiles.length} uncommitted file(s) in the working tree: ${truncatedFileList(dirtyFiles)}`,
|
|
3841
|
+
data: { code: "dirty", dirtyFiles }
|
|
3842
|
+
});
|
|
3843
|
+
}
|
|
3844
|
+
return deniedStart(DENY_DIRTY, "dirty", dirtyFiles);
|
|
3845
|
+
}
|
|
3800
3846
|
if (testDriveState) {
|
|
3801
|
-
return
|
|
3802
|
-
ok: false,
|
|
3803
|
-
deniedReason: testDriveState.kind === "dryRun" ? DENY_DRY_RUN_ACTIVE : DENY_ACTIVE
|
|
3804
|
-
};
|
|
3847
|
+
return deniedStart(testDriveState.kind === "dryRun" ? DENY_DRY_RUN_ACTIVE : DENY_ACTIVE, "slot_held");
|
|
3805
3848
|
}
|
|
3806
3849
|
if (purpose === "human" && hasActiveRun(ctx, feature.id)) {
|
|
3807
|
-
return
|
|
3850
|
+
return deniedStart(DENY_ACTIVE_RUN, "active_run");
|
|
3808
3851
|
}
|
|
3809
3852
|
const talkWorktree = worktreeDir(project.id, feature.slug);
|
|
3810
3853
|
let detachedWorktree;
|
|
@@ -3864,7 +3907,7 @@ async function reviewDrive(ctx, project, feature, action) {
|
|
|
3864
3907
|
return {
|
|
3865
3908
|
ok: stop.ok,
|
|
3866
3909
|
action: "stop",
|
|
3867
|
-
...stop
|
|
3910
|
+
...driveDenialOf(stop),
|
|
3868
3911
|
drive: activeDriveInfo(),
|
|
3869
3912
|
...stop.hookFailure ? { hookFailure: stop.hookFailure } : {}
|
|
3870
3913
|
};
|
|
@@ -3893,7 +3936,7 @@ async function startReviewDrive(ctx, project, feature) {
|
|
|
3893
3936
|
return {
|
|
3894
3937
|
ok: start.ok,
|
|
3895
3938
|
action: "start",
|
|
3896
|
-
...start
|
|
3939
|
+
...driveDenialOf(start),
|
|
3897
3940
|
drive: activeDriveInfo(),
|
|
3898
3941
|
...start.hookFailure ? { hookFailure: start.hookFailure } : {}
|
|
3899
3942
|
};
|
|
@@ -4109,15 +4152,17 @@ ${result.output}` : ""}`,
|
|
|
4109
4152
|
}
|
|
4110
4153
|
async function dirtyPaths(g) {
|
|
4111
4154
|
try {
|
|
4112
|
-
|
|
4113
|
-
if (!out)
|
|
4114
|
-
return [];
|
|
4115
|
-
return out.split(`
|
|
4116
|
-
`).map((line) => line.slice(3).trim()).map((p) => p.includes(" -> ") ? p.split(" -> ").at(-1) ?? p : p).map((p) => p.replace(/^"|"$/g, "")).filter(Boolean);
|
|
4155
|
+
return porcelainPaths((await g.raw(["status", "--porcelain"])).trim());
|
|
4117
4156
|
} catch {
|
|
4118
4157
|
return [];
|
|
4119
4158
|
}
|
|
4120
4159
|
}
|
|
4160
|
+
function porcelainPaths(out) {
|
|
4161
|
+
if (!out)
|
|
4162
|
+
return [];
|
|
4163
|
+
return out.split(`
|
|
4164
|
+
`).map((line) => line.slice(3).trim()).map((p) => p.includes(" -> ") ? p.split(" -> ").at(-1) ?? p : p).map((p) => p.replace(/^"|"$/g, "")).filter(Boolean);
|
|
4165
|
+
}
|
|
4121
4166
|
async function detectDbDrift(ctx, project, feature, previousBranch, driveBranch) {
|
|
4122
4167
|
let files;
|
|
4123
4168
|
try {
|
|
@@ -4525,7 +4570,7 @@ function reportFinding(ctx, args) {
|
|
|
4525
4570
|
}
|
|
4526
4571
|
function updateStatus(ctx, findingId, patch) {
|
|
4527
4572
|
const current = getFinding(ctx, findingId);
|
|
4528
|
-
ctx.db.update(reviewFindings).set(patch).where(eq6(reviewFindings.id, findingId)).run();
|
|
4573
|
+
ctx.db.update(reviewFindings).set({ carriedLap: null, resolvedBy: null, resolutionNote: null, ...patch }).where(eq6(reviewFindings.id, findingId)).run();
|
|
4529
4574
|
emit(ctx, current.featureId, {
|
|
4530
4575
|
type: "finding.updated",
|
|
4531
4576
|
message: `finding ${findingId} marked ${patch.status}`,
|
|
@@ -4544,7 +4589,8 @@ function markFixed(ctx, findingId) {
|
|
|
4544
4589
|
return updateStatus(ctx, findingId, {
|
|
4545
4590
|
status: "fixed",
|
|
4546
4591
|
openReason: null,
|
|
4547
|
-
failureReason: null
|
|
4592
|
+
failureReason: null,
|
|
4593
|
+
resolvedBy: "fix-ticket"
|
|
4548
4594
|
});
|
|
4549
4595
|
}
|
|
4550
4596
|
function markFailed(ctx, findingId, reason) {
|
|
@@ -4571,6 +4617,8 @@ function dismiss(ctx, findingId) {
|
|
|
4571
4617
|
function defectState(finding, fixTicket) {
|
|
4572
4618
|
if (finding.status === "dismissed")
|
|
4573
4619
|
return "dismissed";
|
|
4620
|
+
if (finding.status === "carried")
|
|
4621
|
+
return "carried";
|
|
4574
4622
|
if (finding.status === "fixed" || fixTicket?.status === "done")
|
|
4575
4623
|
return "fixed";
|
|
4576
4624
|
if (fixTicket && fixTicket.status !== "failed" && fixTicket.status !== "cancelled") {
|
|
@@ -4579,11 +4627,15 @@ function defectState(finding, fixTicket) {
|
|
|
4579
4627
|
return finding.status === "open" || finding.status === "failed" || fixTicket?.status === "failed" || fixTicket?.status === "cancelled" ? "open" : "fixing";
|
|
4580
4628
|
}
|
|
4581
4629
|
function viewByFeature(ctx, featureId) {
|
|
4630
|
+
const feature = getFeatureRow(ctx, featureId);
|
|
4582
4631
|
const findings = listByFeature3(ctx, featureId);
|
|
4583
4632
|
const tickets2 = listByFeature2(ctx, featureId);
|
|
4584
4633
|
const summary = { found: 0, fixed: 0, open: 0, observations: 0 };
|
|
4585
4634
|
const openDefects = [];
|
|
4635
|
+
const carriedFindings = findings.filter(isCarried);
|
|
4586
4636
|
for (const finding of findings) {
|
|
4637
|
+
if (finding.lap !== feature.lap)
|
|
4638
|
+
continue;
|
|
4587
4639
|
if (finding.kind === "observation") {
|
|
4588
4640
|
summary.observations += 1;
|
|
4589
4641
|
continue;
|
|
@@ -4597,11 +4649,75 @@ function viewByFeature(ctx, featureId) {
|
|
|
4597
4649
|
openDefects.push(finding);
|
|
4598
4650
|
}
|
|
4599
4651
|
}
|
|
4600
|
-
return { findings, openDefects, summary };
|
|
4652
|
+
return { findings, openDefects, carriedFindings, summary };
|
|
4653
|
+
}
|
|
4654
|
+
function openDefectsAcrossLaps(ctx, featureId) {
|
|
4655
|
+
const tickets2 = listByFeature2(ctx, featureId);
|
|
4656
|
+
return listByFeature3(ctx, featureId).filter((finding) => finding.kind === "defect" && defectState(finding, fixTicketOf(finding, tickets2)) === "open");
|
|
4657
|
+
}
|
|
4658
|
+
function isCarried(finding) {
|
|
4659
|
+
return finding.status === "carried";
|
|
4660
|
+
}
|
|
4661
|
+
function carriedDefectsAcrossLaps(ctx, featureId) {
|
|
4662
|
+
return listByFeature3(ctx, featureId).filter(isCarried);
|
|
4663
|
+
}
|
|
4664
|
+
function undispositionedDefects(ctx, featureId) {
|
|
4665
|
+
const { lap } = getFeatureRow(ctx, featureId);
|
|
4666
|
+
return openDefectsAcrossLaps(ctx, featureId).filter((finding) => finding.lap < lap);
|
|
4601
4667
|
}
|
|
4602
4668
|
function fixTicketOf(finding, tickets2) {
|
|
4603
4669
|
return finding.fixTicketId ? tickets2.find((t) => t.id === finding.fixTicketId) : undefined;
|
|
4604
4670
|
}
|
|
4671
|
+
function requireDispositionable(ctx, featureId, findingId) {
|
|
4672
|
+
const finding = getFinding(ctx, findingId);
|
|
4673
|
+
const eligible = finding.featureId === featureId && (finding.status === "carried" || openDefectsAcrossLaps(ctx, featureId).some((defect) => defect.id === findingId));
|
|
4674
|
+
if (!eligible) {
|
|
4675
|
+
throw new InvalidInputError(`finding ${findingId} is not an open or carried defect of this feature`);
|
|
4676
|
+
}
|
|
4677
|
+
return finding;
|
|
4678
|
+
}
|
|
4679
|
+
function carryFinding(ctx, featureId, findingId, note) {
|
|
4680
|
+
requireDispositionable(ctx, featureId, findingId);
|
|
4681
|
+
return updateStatus(ctx, findingId, {
|
|
4682
|
+
status: "carried",
|
|
4683
|
+
openReason: null,
|
|
4684
|
+
failureReason: null,
|
|
4685
|
+
carriedLap: getFeatureRow(ctx, featureId).lap,
|
|
4686
|
+
resolutionNote: note?.trim() || null
|
|
4687
|
+
});
|
|
4688
|
+
}
|
|
4689
|
+
function closeAsAddressed(ctx, featureId, findingId, note) {
|
|
4690
|
+
const attestation = note.trim();
|
|
4691
|
+
if (!attestation) {
|
|
4692
|
+
throw new InvalidInputError("closing a defect as addressed requires a note saying what addressed it");
|
|
4693
|
+
}
|
|
4694
|
+
requireDispositionable(ctx, featureId, findingId);
|
|
4695
|
+
return updateStatus(ctx, findingId, {
|
|
4696
|
+
status: "fixed",
|
|
4697
|
+
openReason: null,
|
|
4698
|
+
failureReason: null,
|
|
4699
|
+
resolvedBy: "session",
|
|
4700
|
+
resolutionNote: attestation
|
|
4701
|
+
});
|
|
4702
|
+
}
|
|
4703
|
+
function requireLinkableFindings(ctx, featureId, findingIds) {
|
|
4704
|
+
if (new Set(findingIds).size !== findingIds.length) {
|
|
4705
|
+
throw new InvalidInputError("two tickets in this batch link to the same finding");
|
|
4706
|
+
}
|
|
4707
|
+
for (const findingId of findingIds)
|
|
4708
|
+
requireDispositionable(ctx, featureId, findingId);
|
|
4709
|
+
}
|
|
4710
|
+
function linkFixTicket(ctx, featureId, findingId, fixTicketId) {
|
|
4711
|
+
requireDispositionable(ctx, featureId, findingId);
|
|
4712
|
+
ctx.db.update(reviewFindings).set({ fixTicketId }).where(eq6(reviewFindings.id, findingId)).run();
|
|
4713
|
+
return markFixing(ctx, findingId);
|
|
4714
|
+
}
|
|
4715
|
+
function reopenFinding(ctx, findingId) {
|
|
4716
|
+
const finding = getFinding(ctx, findingId);
|
|
4717
|
+
if (finding.status !== "carried")
|
|
4718
|
+
throw new InvalidInputError("only a carried finding can be reopened");
|
|
4719
|
+
return updateStatus(ctx, findingId, { status: "open", openReason: null, failureReason: null });
|
|
4720
|
+
}
|
|
4605
4721
|
function promoteOpenDefects(ctx, featureId, findingIds) {
|
|
4606
4722
|
const open = viewByFeature(ctx, featureId).openDefects;
|
|
4607
4723
|
const defects = findingIds ? findingIds.map((id) => {
|
|
@@ -4647,12 +4763,17 @@ function carriedWork(ctx, featureId) {
|
|
|
4647
4763
|
const carried = ctx.db.select({ id: testNotes.id }).from(testNotes).where(and7(eq7(testNotes.featureId, featureId), eq7(testNotes.status, "carried"))).all();
|
|
4648
4764
|
return {
|
|
4649
4765
|
carriedNotes: carried.length,
|
|
4650
|
-
openDefects:
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4766
|
+
openDefects: openDefectsAcrossLaps(ctx, featureId).map(toCarriedDefect),
|
|
4767
|
+
carriedDefects: carriedDefectsAcrossLaps(ctx, featureId).map(toCarriedDefect)
|
|
4768
|
+
};
|
|
4769
|
+
}
|
|
4770
|
+
function toCarriedDefect(defect) {
|
|
4771
|
+
return {
|
|
4772
|
+
id: defect.id,
|
|
4773
|
+
title: defect.title,
|
|
4774
|
+
location: defect.location,
|
|
4775
|
+
detail: defect.detail,
|
|
4776
|
+
reproStep: defect.reproStep
|
|
4656
4777
|
};
|
|
4657
4778
|
}
|
|
4658
4779
|
function hasCarriedWork(carried) {
|
|
@@ -4957,8 +5078,12 @@ function renderRevisitPrompt(feature, lap, purpose, runtime = DEFAULT_RUNTIME, c
|
|
|
4957
5078
|
`**${carriedSummary} \u2014 address them.** They are this lap's opening agenda:`,
|
|
4958
5079
|
`- \`${docs2}/test-notes.md\`, section \`## Carried, still open\` \u2014 every note carried`,
|
|
4959
5080
|
" 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
|
|
5081
|
+
"- `get_feature_context` \u2192 `openDefects` \u2014 each open defect with its `id`, title,",
|
|
5082
|
+
" location, detail and repro step; and `carriedDefects`, the ones an earlier lap",
|
|
5083
|
+
" parked (agenda, never an obligation to re-carry). Every open defect from an",
|
|
5084
|
+
' EARLIER lap needs one of three answers before `complete_phase("tickets")` will',
|
|
5085
|
+
" pass: **link** it (emit this lap's ticket for it with `originFindingId` set),",
|
|
5086
|
+
" **carry** it, or **close it as addressed** \u2014 the last two are `resolve_finding`.",
|
|
4962
5087
|
`- \`${docs2}/spec.md\`, section \`## Later laps\` \u2014 scope parked by earlier laps,`,
|
|
4963
5088
|
" OPTIONAL; a missing one is normal, not an error."
|
|
4964
5089
|
] : [
|
|
@@ -5538,8 +5663,152 @@ function codexLoggedIn(env = process.env, fileExists = existsSync8) {
|
|
|
5538
5663
|
}
|
|
5539
5664
|
var init_codex_auth = () => {};
|
|
5540
5665
|
|
|
5666
|
+
// src/services/sandbox-image.ts
|
|
5667
|
+
import { createHash as createHash2 } from "crypto";
|
|
5668
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
|
|
5669
|
+
import { join as join10 } from "path";
|
|
5670
|
+
function projectSandboxDir(repoPath) {
|
|
5671
|
+
return join10(repoPath, ".runcastle", "sandbox");
|
|
5672
|
+
}
|
|
5673
|
+
function projectDockerfilePath(repoPath) {
|
|
5674
|
+
return join10(projectSandboxDir(repoPath), "Dockerfile");
|
|
5675
|
+
}
|
|
5676
|
+
function projectImageTag(projectId) {
|
|
5677
|
+
return `${DEFAULT_SANDBOX_IMAGE}-${projectId}`;
|
|
5678
|
+
}
|
|
5679
|
+
function hashDockerfileContents(contents) {
|
|
5680
|
+
return createHash2("sha256").update(contents).digest("hex");
|
|
5681
|
+
}
|
|
5682
|
+
function hashDockerfile(path) {
|
|
5683
|
+
try {
|
|
5684
|
+
return hashDockerfileContents(readFileSync4(path));
|
|
5685
|
+
} catch {
|
|
5686
|
+
return null;
|
|
5687
|
+
}
|
|
5688
|
+
}
|
|
5689
|
+
async function inspectBuiltImage(exec, runtime, tag) {
|
|
5690
|
+
const out = await exec(runtime, [
|
|
5691
|
+
"image",
|
|
5692
|
+
"inspect",
|
|
5693
|
+
"--format",
|
|
5694
|
+
`{{index .Config.Labels "${DOCKERFILE_HASH_LABEL}"}}`,
|
|
5695
|
+
tag
|
|
5696
|
+
]);
|
|
5697
|
+
if (!(out.ok && out.code === 0))
|
|
5698
|
+
return { present: false, hash: null };
|
|
5699
|
+
const value = out.stdout.trim();
|
|
5700
|
+
return { present: true, hash: value === "" || value === "<no value>" ? null : value };
|
|
5701
|
+
}
|
|
5702
|
+
function unmanagedImageReason(imageName, projectDockerfilePresent = false) {
|
|
5703
|
+
const route = projectDockerfilePresent ? "clear the sandbox image setting to let runcastle build the .runcastle/sandbox/Dockerfile this repo already ships" : `clear the sandbox image setting to go back to ${DEFAULT_SANDBOX_IMAGE}, or commit a .runcastle/sandbox/Dockerfile for runcastle to build`;
|
|
5704
|
+
return `${imageName} is a custom image managed outside runcastle \u2014 ${route}.`;
|
|
5705
|
+
}
|
|
5706
|
+
function unmanagedImage(project) {
|
|
5707
|
+
const { id, overwritable } = project;
|
|
5708
|
+
const stored = project.stored?.trim() ?? "";
|
|
5709
|
+
if (stored === "" || overwritable)
|
|
5710
|
+
return null;
|
|
5711
|
+
if (stored === DEFAULT_SANDBOX_IMAGE || stored === projectImageTag(id))
|
|
5712
|
+
return null;
|
|
5713
|
+
return stored;
|
|
5714
|
+
}
|
|
5715
|
+
function stockBuildArgs(runtime, ids2 = {
|
|
5716
|
+
uid: process.getuid?.(),
|
|
5717
|
+
gid: process.getgid?.()
|
|
5718
|
+
}) {
|
|
5719
|
+
if (runtime === "podman")
|
|
5720
|
+
return {};
|
|
5721
|
+
return {
|
|
5722
|
+
...ids2.uid === undefined ? {} : { AGENT_UID: String(ids2.uid) },
|
|
5723
|
+
...ids2.gid === undefined ? {} : { AGENT_GID: String(ids2.gid) }
|
|
5724
|
+
};
|
|
5725
|
+
}
|
|
5726
|
+
function shippedDockerfile(project) {
|
|
5727
|
+
if (!project)
|
|
5728
|
+
return null;
|
|
5729
|
+
const path = projectDockerfilePath(project.repoPath);
|
|
5730
|
+
return existsSync9(path) ? path : null;
|
|
5731
|
+
}
|
|
5732
|
+
function planImageBuild(input) {
|
|
5733
|
+
const { config: config2, project, stockContext, stockFresh, buildArgs } = input;
|
|
5734
|
+
const stockHash = hashDockerfile(join10(stockContext, "Dockerfile")) ?? "";
|
|
5735
|
+
const stockStep = {
|
|
5736
|
+
tag: DEFAULT_SANDBOX_IMAGE,
|
|
5737
|
+
context: stockContext,
|
|
5738
|
+
dockerfileHash: stockHash,
|
|
5739
|
+
buildArgs
|
|
5740
|
+
};
|
|
5741
|
+
const projectDockerfile = shippedDockerfile(project);
|
|
5742
|
+
const handTyped = project ? unmanagedImage({
|
|
5743
|
+
id: project.id,
|
|
5744
|
+
stored: project.sandboxImage ?? null,
|
|
5745
|
+
overwritable: project.sandboxImageOverwritable
|
|
5746
|
+
}) : null;
|
|
5747
|
+
if (handTyped !== null) {
|
|
5748
|
+
return {
|
|
5749
|
+
kind: "refused",
|
|
5750
|
+
imageName: handTyped,
|
|
5751
|
+
reason: unmanagedImageReason(handTyped, projectDockerfile !== null)
|
|
5752
|
+
};
|
|
5753
|
+
}
|
|
5754
|
+
if (project && projectDockerfile) {
|
|
5755
|
+
const projectTag = projectImageTag(project.id);
|
|
5756
|
+
return {
|
|
5757
|
+
kind: "chain",
|
|
5758
|
+
projectTag,
|
|
5759
|
+
steps: [
|
|
5760
|
+
...stockFresh ? [] : [stockStep],
|
|
5761
|
+
{
|
|
5762
|
+
tag: projectTag,
|
|
5763
|
+
context: projectSandboxDir(project.repoPath),
|
|
5764
|
+
dockerfileHash: hashDockerfile(projectDockerfile) ?? "",
|
|
5765
|
+
buildArgs: {}
|
|
5766
|
+
}
|
|
5767
|
+
]
|
|
5768
|
+
};
|
|
5769
|
+
}
|
|
5770
|
+
const imageName = resolveSandboxImage(config2, project);
|
|
5771
|
+
const managed = imageName === DEFAULT_SANDBOX_IMAGE || project !== null && imageName === projectImageTag(project.id);
|
|
5772
|
+
if (!managed)
|
|
5773
|
+
return { kind: "refused", imageName, reason: unmanagedImageReason(imageName) };
|
|
5774
|
+
return { kind: "stock", steps: [stockStep] };
|
|
5775
|
+
}
|
|
5776
|
+
function buildStepArgs(step) {
|
|
5777
|
+
return [
|
|
5778
|
+
"build",
|
|
5779
|
+
"-t",
|
|
5780
|
+
step.tag,
|
|
5781
|
+
"--label",
|
|
5782
|
+
`${DOCKERFILE_HASH_LABEL}=${step.dockerfileHash}`,
|
|
5783
|
+
...Object.entries(step.buildArgs).flatMap(([key, value]) => ["--build-arg", `${key}=${value}`]),
|
|
5784
|
+
step.context
|
|
5785
|
+
];
|
|
5786
|
+
}
|
|
5787
|
+
function shellQuote(arg, platform) {
|
|
5788
|
+
if (/^[\w@%+=:,./\\-]+$/.test(arg))
|
|
5789
|
+
return arg;
|
|
5790
|
+
if (platform === "win32")
|
|
5791
|
+
return `"${arg}"`;
|
|
5792
|
+
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
5793
|
+
}
|
|
5794
|
+
function imageBuildTerminal(runtime, plan, platform = process.platform) {
|
|
5795
|
+
const steps = plan.steps;
|
|
5796
|
+
const last = steps[steps.length - 1];
|
|
5797
|
+
if (!last)
|
|
5798
|
+
throw new Error("image build plan has no steps");
|
|
5799
|
+
const cwd = last.context;
|
|
5800
|
+
if (steps.length === 1)
|
|
5801
|
+
return { cmd: runtime, args: buildStepArgs(last), cwd };
|
|
5802
|
+
const line = steps.map((step) => [runtime, ...buildStepArgs(step)].map((a) => shellQuote(a, platform)).join(" ")).join(" && ");
|
|
5803
|
+
return platform === "win32" ? { cmd: process.env.ComSpec ?? "cmd.exe", args: ["/d", "/s", "/c", line], cwd } : { cmd: "/bin/sh", args: ["-c", line], cwd };
|
|
5804
|
+
}
|
|
5805
|
+
var DOCKERFILE_HASH_LABEL = "runcastle.dockerfile-hash";
|
|
5806
|
+
var init_sandbox_image = __esm(() => {
|
|
5807
|
+
init_src();
|
|
5808
|
+
});
|
|
5809
|
+
|
|
5541
5810
|
// src/doctor/doctor.ts
|
|
5542
|
-
import { existsSync as
|
|
5811
|
+
import { existsSync as existsSync10 } from "fs";
|
|
5543
5812
|
async function versionProbe(exec, id, label, bin, fix) {
|
|
5544
5813
|
const out = await exec(bin, ["--version"]);
|
|
5545
5814
|
if (out.ok && out.code === 0) {
|
|
@@ -5652,7 +5921,7 @@ function runtimeAfkKeyProbe(spec, env, severity = "error") {
|
|
|
5652
5921
|
};
|
|
5653
5922
|
}
|
|
5654
5923
|
async function runtimeProbes(spec, deps) {
|
|
5655
|
-
const fileExists = deps.fileExists ??
|
|
5924
|
+
const fileExists = deps.fileExists ?? existsSync10;
|
|
5656
5925
|
const afkKeyProbe = runtimeAfkKeyProbe(spec, deps.env, deps.severity);
|
|
5657
5926
|
return [
|
|
5658
5927
|
await runtimeBinaryProbe(spec, deps.exec, deps.severity),
|
|
@@ -5746,49 +6015,98 @@ async function containerRuntimeProbe(exec) {
|
|
|
5746
6015
|
fix: "Install Docker Desktop or Podman (see docs/research/PREREQS-NOTES.md \xA74). Not needed for interactive-only use."
|
|
5747
6016
|
};
|
|
5748
6017
|
}
|
|
5749
|
-
async function sandcastleImageProbe(
|
|
5750
|
-
const
|
|
5751
|
-
const
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
6018
|
+
async function sandcastleImageProbe(input) {
|
|
6019
|
+
const { exec, burnerDockerfile, dockerfileHash, project } = input;
|
|
6020
|
+
const runtime = await presentRuntime(exec);
|
|
6021
|
+
if (!runtime) {
|
|
6022
|
+
return {
|
|
6023
|
+
...IMAGE_ROW,
|
|
6024
|
+
status: "missing",
|
|
6025
|
+
severity: "error",
|
|
6026
|
+
detail: "no container runtime available to inspect the image",
|
|
6027
|
+
fix: "Install a container runtime (Docker or Podman) first, then build the image from the Enable AFK burns card in the app."
|
|
6028
|
+
};
|
|
6029
|
+
}
|
|
6030
|
+
const projectHash = project ? dockerfileHash(projectDockerfilePath(project.repoPath)) : null;
|
|
6031
|
+
const handTyped = project ? unmanagedImage(project) : null;
|
|
6032
|
+
let stored = project?.stored ?? null;
|
|
6033
|
+
if (project && stored !== null && projectHash === null && project.overwritable) {
|
|
6034
|
+
project.clearStored();
|
|
6035
|
+
stored = null;
|
|
6036
|
+
}
|
|
6037
|
+
const stockHash = dockerfileHash(burnerDockerfile);
|
|
6038
|
+
const stock = await inspectBuiltImage(exec, runtime, DEFAULT_SANDBOX_IMAGE);
|
|
6039
|
+
const stockFresh = stock.present && (stockHash === null || stock.hash === stockHash);
|
|
6040
|
+
if (project && handTyped === null && projectHash !== null) {
|
|
6041
|
+
const tag = projectImageTag(project.id);
|
|
6042
|
+
const image = await inspectBuiltImage(exec, runtime, tag);
|
|
6043
|
+
if (!image.present || project.overwritable && stored !== tag) {
|
|
6044
|
+
return {
|
|
6045
|
+
...IMAGE_ROW,
|
|
6046
|
+
status: "not-built-yet",
|
|
6047
|
+
severity: "error",
|
|
6048
|
+
detail: image.present ? `${tag} is built but is not this project's image yet` : `.runcastle/sandbox/Dockerfile is not built \u2014 ${tag} does not exist`,
|
|
6049
|
+
fix: "Open Settings \u2192 Burns (Build image) \u2014 runcastle builds .runcastle/sandbox/Dockerfile for you."
|
|
6050
|
+
};
|
|
6051
|
+
}
|
|
6052
|
+
if (image.hash !== projectHash) {
|
|
6053
|
+
return staleImage(`${tag} no longer matches .runcastle/sandbox/Dockerfile`);
|
|
6054
|
+
}
|
|
6055
|
+
if (!stockFresh) {
|
|
6056
|
+
const base = stock.present ? "no longer matches the burner Dockerfile" : "is not built";
|
|
6057
|
+
return staleImage(`${tag} is built on ${DEFAULT_SANDBOX_IMAGE}, which ${base}`);
|
|
5773
6058
|
}
|
|
5774
6059
|
return {
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
6060
|
+
...IMAGE_ROW,
|
|
6061
|
+
status: "ok",
|
|
6062
|
+
severity: "error",
|
|
6063
|
+
detail: `${tag} built from .runcastle/sandbox/Dockerfile`
|
|
6064
|
+
};
|
|
6065
|
+
}
|
|
6066
|
+
const imageName = resolveSandboxImage({ sandboxImage: input.imageName }, { sandboxImage: stored });
|
|
6067
|
+
if (imageName !== DEFAULT_SANDBOX_IMAGE) {
|
|
6068
|
+
const custom = await inspectBuiltImage(exec, runtime, imageName);
|
|
6069
|
+
const clauses = [
|
|
6070
|
+
`${imageName} is a custom image, managed outside runcastle`,
|
|
6071
|
+
...projectHash === null ? [] : [`and outranks this repo's .runcastle/sandbox/Dockerfile`],
|
|
6072
|
+
...custom.present ? [] : ["and is not built locally"]
|
|
6073
|
+
];
|
|
6074
|
+
return {
|
|
6075
|
+
...IMAGE_ROW,
|
|
6076
|
+
status: "custom",
|
|
6077
|
+
severity: custom.present ? "info" : "error",
|
|
6078
|
+
detail: clauses.join(" \u2014 "),
|
|
6079
|
+
fix: unmanagedImageReason(imageName, projectHash !== null)
|
|
6080
|
+
};
|
|
6081
|
+
}
|
|
6082
|
+
if (!stock.present) {
|
|
6083
|
+
return {
|
|
6084
|
+
...IMAGE_ROW,
|
|
5778
6085
|
status: "missing",
|
|
5779
6086
|
severity: "error",
|
|
5780
6087
|
detail: `image ${imageName} not found locally`,
|
|
5781
6088
|
fix: 'Start runcastle and click "Build image" on the Enable AFK burns card \u2014 it builds this for you (one click). Only needed for AFK/sandboxed burns.'
|
|
5782
6089
|
};
|
|
5783
6090
|
}
|
|
6091
|
+
if (!stockFresh)
|
|
6092
|
+
return staleImage(`${imageName} no longer matches the burner Dockerfile`);
|
|
6093
|
+
return { ...IMAGE_ROW, status: "ok", severity: "error", detail: `${imageName} present` };
|
|
6094
|
+
}
|
|
6095
|
+
async function presentRuntime(exec) {
|
|
6096
|
+
for (const runtime of ["docker", "podman"]) {
|
|
6097
|
+
const present = await exec(runtime, ["--version"]);
|
|
6098
|
+
if (present.ok && present.code === 0)
|
|
6099
|
+
return runtime;
|
|
6100
|
+
}
|
|
6101
|
+
return null;
|
|
6102
|
+
}
|
|
6103
|
+
function staleImage(detail) {
|
|
5784
6104
|
return {
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
tier: 2,
|
|
5788
|
-
status: "missing",
|
|
6105
|
+
...IMAGE_ROW,
|
|
6106
|
+
status: "stale",
|
|
5789
6107
|
severity: "error",
|
|
5790
|
-
detail:
|
|
5791
|
-
fix: "
|
|
6108
|
+
detail: `${detail} \u2014 rebuild`,
|
|
6109
|
+
fix: "Open Settings \u2192 Burns (Rebuild image)."
|
|
5792
6110
|
};
|
|
5793
6111
|
}
|
|
5794
6112
|
async function runDoctor(env) {
|
|
@@ -5796,7 +6114,7 @@ async function runDoctor(env) {
|
|
|
5796
6114
|
const processEnv = env.env ?? process.env;
|
|
5797
6115
|
const imageName = env.imageName ?? DEFAULT_SANDBOX_IMAGE;
|
|
5798
6116
|
const burnerDockerfile = env.burnerDockerfile ?? burnerDockerfilePath();
|
|
5799
|
-
const
|
|
6117
|
+
const dockerfileHash = env.dockerfileHash ?? hashDockerfile;
|
|
5800
6118
|
const required = new Set(env.runtimes ?? [DEFAULT_RUNTIME]);
|
|
5801
6119
|
const results = [
|
|
5802
6120
|
await bunProbe(exec),
|
|
@@ -5811,7 +6129,13 @@ async function runDoctor(env) {
|
|
|
5811
6129
|
severity: required.has(runtime) ? "error" : "info"
|
|
5812
6130
|
}));
|
|
5813
6131
|
}
|
|
5814
|
-
results.push(await gitIdentityProbe(exec, env.cwd), await containerRuntimeProbe(exec), await sandcastleImageProbe(
|
|
6132
|
+
results.push(await gitIdentityProbe(exec, env.cwd), await containerRuntimeProbe(exec), await sandcastleImageProbe({
|
|
6133
|
+
exec,
|
|
6134
|
+
imageName,
|
|
6135
|
+
burnerDockerfile,
|
|
6136
|
+
dockerfileHash,
|
|
6137
|
+
...env.projectImage ? { project: env.projectImage } : {}
|
|
6138
|
+
}));
|
|
5815
6139
|
const counts = (r) => r.severity === "error";
|
|
5816
6140
|
return {
|
|
5817
6141
|
results,
|
|
@@ -5824,11 +6148,12 @@ function exitCodeFor(report, mode) {
|
|
|
5824
6148
|
return report.tier1Ok ? 0 : 1;
|
|
5825
6149
|
return report.ok ? 0 : 1;
|
|
5826
6150
|
}
|
|
5827
|
-
var RUNTIME_SPECS;
|
|
6151
|
+
var RUNTIME_SPECS, IMAGE_ROW;
|
|
5828
6152
|
var init_doctor = __esm(() => {
|
|
5829
6153
|
init_src();
|
|
5830
6154
|
init_asset_paths();
|
|
5831
6155
|
init_codex_auth();
|
|
6156
|
+
init_sandbox_image();
|
|
5832
6157
|
RUNTIME_SPECS = {
|
|
5833
6158
|
"claude-code": {
|
|
5834
6159
|
runtime: "claude-code",
|
|
@@ -5860,21 +6185,22 @@ var init_doctor = __esm(() => {
|
|
|
5860
6185
|
authFile: codexAuthFile
|
|
5861
6186
|
}
|
|
5862
6187
|
};
|
|
6188
|
+
IMAGE_ROW = { id: "sandcastle-image", label: "Sandcastle container image", tier: 2 };
|
|
5863
6189
|
});
|
|
5864
6190
|
|
|
5865
6191
|
// src/launcher/runtimes/codex.ts
|
|
5866
6192
|
import {
|
|
5867
6193
|
copyFileSync,
|
|
5868
|
-
existsSync as
|
|
6194
|
+
existsSync as existsSync11,
|
|
5869
6195
|
mkdirSync as mkdirSync4,
|
|
5870
|
-
readFileSync as
|
|
6196
|
+
readFileSync as readFileSync5,
|
|
5871
6197
|
readdirSync as readdirSync2,
|
|
5872
|
-
statSync
|
|
6198
|
+
statSync,
|
|
5873
6199
|
writeFileSync as writeFileSync3
|
|
5874
6200
|
} from "fs";
|
|
5875
|
-
import { basename, dirname as dirname4, join as
|
|
6201
|
+
import { basename, dirname as dirname4, join as join11, resolve as resolve7 } from "path";
|
|
5876
6202
|
function codexHomeDir2(sessionId) {
|
|
5877
|
-
return
|
|
6203
|
+
return join11(sessionDir(sessionId), "codex-home");
|
|
5878
6204
|
}
|
|
5879
6205
|
function realCodexAuthFile(env = process.env) {
|
|
5880
6206
|
return codexAuthFile(env);
|
|
@@ -5922,11 +6248,11 @@ function mcpServerTables(userConfigToml) {
|
|
|
5922
6248
|
`).trim();
|
|
5923
6249
|
}
|
|
5924
6250
|
function readInheritedMcpServers(env = process.env) {
|
|
5925
|
-
const path =
|
|
5926
|
-
if (!
|
|
6251
|
+
const path = join11(dirname4(realCodexAuthFile(env)), "config.toml");
|
|
6252
|
+
if (!existsSync11(path))
|
|
5927
6253
|
return "";
|
|
5928
6254
|
try {
|
|
5929
|
-
return mcpServerTables(
|
|
6255
|
+
return mcpServerTables(readFileSync5(path, "utf8"));
|
|
5930
6256
|
} catch {
|
|
5931
6257
|
return "";
|
|
5932
6258
|
}
|
|
@@ -5947,19 +6273,19 @@ function renderCodexHooks(hookClient, kind, platform = process.platform) {
|
|
|
5947
6273
|
};
|
|
5948
6274
|
}
|
|
5949
6275
|
function writeCodexSkills(worktreePath, packDir) {
|
|
5950
|
-
if (!
|
|
6276
|
+
if (!existsSync11(worktreePath))
|
|
5951
6277
|
return [];
|
|
5952
|
-
const source =
|
|
5953
|
-
if (!
|
|
6278
|
+
const source = join11(packDir, "skills");
|
|
6279
|
+
if (!existsSync11(source))
|
|
5954
6280
|
return [];
|
|
5955
6281
|
const written = [];
|
|
5956
6282
|
for (const name of readdirSync2(source)) {
|
|
5957
|
-
const skill =
|
|
5958
|
-
if (!
|
|
6283
|
+
const skill = join11(source, name, "SKILL.md");
|
|
6284
|
+
if (!existsSync11(skill))
|
|
5959
6285
|
continue;
|
|
5960
|
-
const target =
|
|
6286
|
+
const target = join11(worktreePath, CODEX_SKILLS_REL, name);
|
|
5961
6287
|
mkdirSync4(target, { recursive: true });
|
|
5962
|
-
const path =
|
|
6288
|
+
const path = join11(target, "SKILL.md");
|
|
5963
6289
|
copyFileSync(skill, path);
|
|
5964
6290
|
written.push(path);
|
|
5965
6291
|
}
|
|
@@ -5973,8 +6299,8 @@ function excludeFromGit(worktreePath, entry) {
|
|
|
5973
6299
|
if (!info)
|
|
5974
6300
|
return;
|
|
5975
6301
|
mkdirSync4(info, { recursive: true });
|
|
5976
|
-
const path =
|
|
5977
|
-
const existing =
|
|
6302
|
+
const path = join11(info, "exclude");
|
|
6303
|
+
const existing = existsSync11(path) ? readFileSync5(path, "utf8") : "";
|
|
5978
6304
|
if (existing.split(/\r?\n/).some((line) => line.trim() === entry))
|
|
5979
6305
|
return;
|
|
5980
6306
|
const head = existing.length > 0 && !existing.endsWith(`
|
|
@@ -5985,17 +6311,17 @@ function excludeFromGit(worktreePath, entry) {
|
|
|
5985
6311
|
} catch {}
|
|
5986
6312
|
}
|
|
5987
6313
|
function gitInfoDir(worktreePath) {
|
|
5988
|
-
const dotGit =
|
|
5989
|
-
if (!
|
|
6314
|
+
const dotGit = join11(worktreePath, ".git");
|
|
6315
|
+
if (!existsSync11(dotGit))
|
|
5990
6316
|
return null;
|
|
5991
|
-
if (
|
|
5992
|
-
return
|
|
5993
|
-
const named = /^gitdir:\s*(.+)$/m.exec(
|
|
6317
|
+
if (statSync(dotGit).isDirectory())
|
|
6318
|
+
return join11(dotGit, "info");
|
|
6319
|
+
const named = /^gitdir:\s*(.+)$/m.exec(readFileSync5(dotGit, "utf8"));
|
|
5994
6320
|
if (!named)
|
|
5995
6321
|
return null;
|
|
5996
6322
|
const gitDir = resolve7(worktreePath, named[1].trim());
|
|
5997
6323
|
const parent = dirname4(gitDir);
|
|
5998
|
-
return basename(parent) === "worktrees" ?
|
|
6324
|
+
return basename(parent) === "worktrees" ? join11(dirname4(parent), "info") : join11(gitDir, "info");
|
|
5999
6325
|
}
|
|
6000
6326
|
function buildCodexArgs(input) {
|
|
6001
6327
|
return [
|
|
@@ -6014,7 +6340,7 @@ var init_codex = __esm(() => {
|
|
|
6014
6340
|
init_skills_root();
|
|
6015
6341
|
init_skills();
|
|
6016
6342
|
CODEX = RUNTIME_SPECS.codex;
|
|
6017
|
-
CODEX_SKILLS_REL =
|
|
6343
|
+
CODEX_SKILLS_REL = join11(".agents", "skills");
|
|
6018
6344
|
KICKOFF_LINES2 = kickoffLinesFor("codex");
|
|
6019
6345
|
codexRuntime = {
|
|
6020
6346
|
id: "codex",
|
|
@@ -6030,7 +6356,7 @@ var init_codex = __esm(() => {
|
|
|
6030
6356
|
doctorHint: 'Run `runcastle doctor` and fix its "Codex CLI" probe, then restart runcastle from a ' + "terminal where `codex --version` works."
|
|
6031
6357
|
};
|
|
6032
6358
|
}
|
|
6033
|
-
if (!
|
|
6359
|
+
if (!existsSync11(realCodexAuthFile())) {
|
|
6034
6360
|
return {
|
|
6035
6361
|
ok: false,
|
|
6036
6362
|
reason: "Codex is installed but not logged in \u2014 runcastle copies your own credentials into " + `each session's home, and there are none at ${realCodexAuthFile()}`,
|
|
@@ -6042,9 +6368,9 @@ var init_codex = __esm(() => {
|
|
|
6042
6368
|
async writeArtifacts(input) {
|
|
6043
6369
|
const home = codexHomeDir2(input.session.id);
|
|
6044
6370
|
mkdirSync4(home, { recursive: true });
|
|
6045
|
-
const configPath2 =
|
|
6046
|
-
const hooksPath =
|
|
6047
|
-
const agentsPath =
|
|
6371
|
+
const configPath2 = join11(home, "config.toml");
|
|
6372
|
+
const hooksPath = join11(home, "hooks.json");
|
|
6373
|
+
const agentsPath = join11(home, "AGENTS.md");
|
|
6048
6374
|
writeFileSync3(configPath2, renderCodexConfig({
|
|
6049
6375
|
session: input.session,
|
|
6050
6376
|
config: input.config,
|
|
@@ -6057,8 +6383,8 @@ var init_codex = __esm(() => {
|
|
|
6057
6383
|
writeFileSync3(agentsPath, renderSessionPrompt({ ...input, runtime: this.id }), "utf8");
|
|
6058
6384
|
const files = [configPath2, hooksPath, agentsPath];
|
|
6059
6385
|
const auth = realCodexAuthFile();
|
|
6060
|
-
if (
|
|
6061
|
-
const copied =
|
|
6386
|
+
if (existsSync11(auth)) {
|
|
6387
|
+
const copied = join11(home, "auth.json");
|
|
6062
6388
|
copyFileSync(auth, copied);
|
|
6063
6389
|
files.push(copied);
|
|
6064
6390
|
}
|
|
@@ -6100,7 +6426,7 @@ var init_runtimes = __esm(() => {
|
|
|
6100
6426
|
});
|
|
6101
6427
|
|
|
6102
6428
|
// src/launcher/sessions.ts
|
|
6103
|
-
import { statSync as
|
|
6429
|
+
import { statSync as statSync2 } from "fs";
|
|
6104
6430
|
import { and as and8, desc as desc3, eq as eq8, inArray as inArray2, isNotNull as isNotNull2, sql as sql2 } from "drizzle-orm";
|
|
6105
6431
|
function createSessionRow(ctx, input) {
|
|
6106
6432
|
const row = ctx.db.insert(sessions).values({
|
|
@@ -6455,7 +6781,7 @@ function transcriptBytes(session) {
|
|
|
6455
6781
|
if (!session?.transcriptPath)
|
|
6456
6782
|
return;
|
|
6457
6783
|
try {
|
|
6458
|
-
return
|
|
6784
|
+
return statSync2(session.transcriptPath).size;
|
|
6459
6785
|
} catch {
|
|
6460
6786
|
return;
|
|
6461
6787
|
}
|
|
@@ -6551,7 +6877,7 @@ async function getRuntimeCtx() {
|
|
|
6551
6877
|
var current = null;
|
|
6552
6878
|
|
|
6553
6879
|
// src/services/gates.ts
|
|
6554
|
-
import { existsSync as
|
|
6880
|
+
import { existsSync as existsSync12 } from "fs";
|
|
6555
6881
|
import { and as and9, desc as desc4, eq as eq9, inArray as inArray4 } from "drizzle-orm";
|
|
6556
6882
|
function checkGate(ctx, check, feature) {
|
|
6557
6883
|
switch (check) {
|
|
@@ -6587,6 +6913,10 @@ function checkGate(ctx, check, feature) {
|
|
|
6587
6913
|
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
6914
|
};
|
|
6589
6915
|
}
|
|
6916
|
+
const undispositioned = undispositionedDefects(ctx, feature.id);
|
|
6917
|
+
if (undispositioned.length > 0) {
|
|
6918
|
+
return { satisfied: false, reason: undispositionedReason(undispositioned) };
|
|
6919
|
+
}
|
|
6590
6920
|
return { satisfied: true };
|
|
6591
6921
|
}
|
|
6592
6922
|
case "all-tickets-terminal": {
|
|
@@ -6603,6 +6933,10 @@ function checkGate(ctx, check, feature) {
|
|
|
6603
6933
|
return { satisfied: false, reason: "use the Merge button to ship" };
|
|
6604
6934
|
}
|
|
6605
6935
|
}
|
|
6936
|
+
function undispositionedReason(defects) {
|
|
6937
|
+
const titles = defects.map((defect) => `"${defect.title}"`).join(", ");
|
|
6938
|
+
return `${defects.length} defect${defects.length === 1 ? "" : "s"} from an earlier lap ${defects.length === 1 ? "is" : "are"} still un-dispositioned: ${titles} \u2014 for each, either emit this lap's ticket for it with ` + "`originFindingId` set (link), or call `resolve_finding` to carry it or close it as " + "addressed. The human can also dismiss it from the review page.";
|
|
6939
|
+
}
|
|
6606
6940
|
function notYetTerminal(noun, statuses, order) {
|
|
6607
6941
|
const counts = new Map;
|
|
6608
6942
|
for (const s of statuses)
|
|
@@ -6616,7 +6950,7 @@ function notYetTerminal(noun, statuses, order) {
|
|
|
6616
6950
|
}
|
|
6617
6951
|
function fileGate(ctx, feature, fileName, reason) {
|
|
6618
6952
|
const project = projectForFeature(ctx, feature);
|
|
6619
|
-
return
|
|
6953
|
+
return existsSync12(featureDocPath(project, feature, fileName)) ? { satisfied: true } : { satisfied: false, reason };
|
|
6620
6954
|
}
|
|
6621
6955
|
function lapWorked(ctx, feature) {
|
|
6622
6956
|
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;
|
|
@@ -6676,20 +7010,21 @@ var init_gates = __esm(() => {
|
|
|
6676
7010
|
init_events();
|
|
6677
7011
|
init_feature_docs();
|
|
6678
7012
|
init_repo();
|
|
7013
|
+
init_review_findings();
|
|
6679
7014
|
init_tickets();
|
|
6680
7015
|
init_waypoints();
|
|
6681
7016
|
DOC_WRITING_KINDS = ["ideation", "revisit", "converge"];
|
|
6682
7017
|
});
|
|
6683
7018
|
|
|
6684
7019
|
// src/services/knowledge.ts
|
|
6685
|
-
import { existsSync as
|
|
6686
|
-
import { join as
|
|
7020
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
7021
|
+
import { join as join12, relative as relative2, resolve as resolve8, sep } from "path";
|
|
6687
7022
|
function scaffoldDocs(ctx, feature, opts = {}) {
|
|
6688
7023
|
const project = projectForFeature(ctx, feature);
|
|
6689
7024
|
const dir = featureDocsDir(project, feature);
|
|
6690
7025
|
mkdirSync5(dir, { recursive: true });
|
|
6691
7026
|
const briefPath = featureDocPath(project, feature, "brief.md");
|
|
6692
|
-
if (!
|
|
7027
|
+
if (!existsSync13(briefPath)) {
|
|
6693
7028
|
const override = opts.brief?.trim();
|
|
6694
7029
|
const created = new Date().toISOString();
|
|
6695
7030
|
const brief = override ? `${override}
|
|
@@ -6716,7 +7051,7 @@ function scaffoldDocs(ctx, feature, opts = {}) {
|
|
|
6716
7051
|
function scaffoldMapDoc(project, feature, seed) {
|
|
6717
7052
|
mkdirSync5(featureDocsDir(project, feature), { recursive: true });
|
|
6718
7053
|
const mapPath = featureDocPath(project, feature, "map.md");
|
|
6719
|
-
if (
|
|
7054
|
+
if (existsSync13(mapPath))
|
|
6720
7055
|
return;
|
|
6721
7056
|
writeFileSync4(mapPath, mapDocBody(feature, seed), "utf8");
|
|
6722
7057
|
}
|
|
@@ -6736,18 +7071,18 @@ function mapDocBody(feature, seed) {
|
|
|
6736
7071
|
`);
|
|
6737
7072
|
}
|
|
6738
7073
|
function readCharter(repoRoot) {
|
|
6739
|
-
const path =
|
|
6740
|
-
if (!
|
|
7074
|
+
const path = join12(repoRoot, CHARTER_FILE);
|
|
7075
|
+
if (!existsSync13(path))
|
|
6741
7076
|
return;
|
|
6742
7077
|
try {
|
|
6743
|
-
return
|
|
7078
|
+
return readFileSync6(path, "utf8");
|
|
6744
7079
|
} catch {
|
|
6745
7080
|
return;
|
|
6746
7081
|
}
|
|
6747
7082
|
}
|
|
6748
7083
|
function listLiveAdrs(repoRoot) {
|
|
6749
|
-
const dir =
|
|
6750
|
-
if (!
|
|
7084
|
+
const dir = join12(repoRoot, ADR_DIR_REL);
|
|
7085
|
+
if (!existsSync13(dir))
|
|
6751
7086
|
return [];
|
|
6752
7087
|
const docs2 = [];
|
|
6753
7088
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
@@ -6755,7 +7090,7 @@ function listLiveAdrs(repoRoot) {
|
|
|
6755
7090
|
continue;
|
|
6756
7091
|
let content;
|
|
6757
7092
|
try {
|
|
6758
|
-
content =
|
|
7093
|
+
content = readFileSync6(join12(dir, entry.name), "utf8");
|
|
6759
7094
|
} catch {
|
|
6760
7095
|
continue;
|
|
6761
7096
|
}
|
|
@@ -6768,10 +7103,10 @@ function listLiveAdrs(repoRoot) {
|
|
|
6768
7103
|
function listDocs(ctx, feature) {
|
|
6769
7104
|
const project = projectForFeature(ctx, feature);
|
|
6770
7105
|
const dir = featureDocsDir(project, feature);
|
|
6771
|
-
if (!
|
|
7106
|
+
if (!existsSync13(dir))
|
|
6772
7107
|
return [];
|
|
6773
7108
|
return readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => {
|
|
6774
|
-
const path =
|
|
7109
|
+
const path = join12(dir, e.name);
|
|
6775
7110
|
return { relPath: e.name, title: readTitle(path, e.name), ...writtenAt(path) };
|
|
6776
7111
|
}).sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
6777
7112
|
}
|
|
@@ -6784,20 +7119,20 @@ function readDoc(ctx, feature, relPath) {
|
|
|
6784
7119
|
if (rel === "" || rel.startsWith("..") || rel.split(sep).includes("..")) {
|
|
6785
7120
|
throw new InvalidInputError(`doc path escapes the feature docs dir: ${relPath}`);
|
|
6786
7121
|
}
|
|
6787
|
-
if (!
|
|
7122
|
+
if (!existsSync13(target))
|
|
6788
7123
|
throw new NotFoundError(`doc not found: ${relPath}`);
|
|
6789
|
-
return { content:
|
|
7124
|
+
return { content: readFileSync6(target, "utf8") };
|
|
6790
7125
|
}
|
|
6791
7126
|
function writtenAt(path) {
|
|
6792
7127
|
try {
|
|
6793
|
-
return { updatedAt: Math.floor(
|
|
7128
|
+
return { updatedAt: Math.floor(statSync3(path).mtimeMs) };
|
|
6794
7129
|
} catch {
|
|
6795
7130
|
return {};
|
|
6796
7131
|
}
|
|
6797
7132
|
}
|
|
6798
7133
|
function readTitle(path, fallback) {
|
|
6799
7134
|
try {
|
|
6800
|
-
const firstLine =
|
|
7135
|
+
const firstLine = readFileSync6(path, "utf8").split(`
|
|
6801
7136
|
`, 1)[0] ?? "";
|
|
6802
7137
|
const heading = firstLine.match(/^#\s+(.+)$/);
|
|
6803
7138
|
return heading ? heading[1].trim() : fallback;
|
|
@@ -7010,6 +7345,13 @@ var require$1, __create, __defProp2, __getOwnPropDesc, __getOwnPropNames, __getP
|
|
|
7010
7345
|
toString() {
|
|
7011
7346
|
return this.items.join(this.separator);
|
|
7012
7347
|
}
|
|
7348
|
+
}, elidePayloadTokens = (command) => command.replace(/'[A-Za-z0-9+\/=]{120,}'/g, "'\u2026'"), formatExecFailureMessage = (command, result, maxTailChars = MAX_TAIL_CHARS) => {
|
|
7349
|
+
const output = [result.stdout, result.stderr].map((stream) => (stream ?? "").trimEnd()).filter((stream) => stream.length > 0).join(`
|
|
7350
|
+
`);
|
|
7351
|
+
const tail = output.length > maxTailChars ? output.slice(output.length - maxTailChars) : output;
|
|
7352
|
+
const headline = `Command failed (exit ${result.exitCode}): ${elidePayloadTokens(command)}`;
|
|
7353
|
+
return tail ? `${headline}
|
|
7354
|
+
${tail}` : headline;
|
|
7013
7355
|
};
|
|
7014
7356
|
var init_chunk_NGBM7T3E = __esm(() => {
|
|
7015
7357
|
require$1 = createRequire3(import.meta.url);
|
|
@@ -7034,7 +7376,7 @@ import { createRequire as createRequire4 } from "module";
|
|
|
7034
7376
|
import * as clack from "@clack/prompts";
|
|
7035
7377
|
import { join as join$1, basename as basename2, dirname as dirname5, resolve as resolve9, posix as posix2, normalize, isAbsolute as isAbsolute2 } from "path";
|
|
7036
7378
|
import { styleText, promisify } from "util";
|
|
7037
|
-
import { existsSync as
|
|
7379
|
+
import { existsSync as existsSync14, statSync as statSync4 } from "fs";
|
|
7038
7380
|
import { tmpdir as tmpdir2, homedir as homedir3 } from "os";
|
|
7039
7381
|
import { mkdtemp, writeFile, mkdir, readFile, rm, readdir, stat } from "fs/promises";
|
|
7040
7382
|
import { exec, execFile } from "child_process";
|
|
@@ -8113,7 +8455,7 @@ var Effect_exports, isFunction = (input) => typeof input === "function", dual =
|
|
|
8113
8455
|
next4 = b2;
|
|
8114
8456
|
}
|
|
8115
8457
|
return out;
|
|
8116
|
-
}, getEquivalence2, dedupeWith, dedupe = (self) => dedupeWith(self, equivalence()),
|
|
8458
|
+
}, getEquivalence2, dedupeWith, dedupe = (self) => dedupeWith(self, equivalence()), join13, TypeId4, emptyArray, getEquivalence3 = (isEquivalent) => make((self, that) => self.length === that.length && toReadonlyArray(self).every((value5, i) => isEquivalent(value5, unsafeGet2(that, i)))), _equivalence3, ChunkProto, makeChunk = (backing) => {
|
|
8117
8459
|
const chunk4 = Object.create(ChunkProto);
|
|
8118
8460
|
chunk4.backing = backing;
|
|
8119
8461
|
switch (backing._tag) {
|
|
@@ -9812,7 +10154,7 @@ ${prefix}}`;
|
|
|
9812
10154
|
Object.defineProperty(error3, "toString", {
|
|
9813
10155
|
enumerable: false,
|
|
9814
10156
|
value() {
|
|
9815
|
-
const path2 = pipe(this.path,
|
|
10157
|
+
const path2 = pipe(this.path, join13(options.pathDelim));
|
|
9816
10158
|
return `(Invalid data at ${path2}: "${this.message}")`;
|
|
9817
10159
|
}
|
|
9818
10160
|
});
|
|
@@ -9827,7 +10169,7 @@ ${prefix}}`;
|
|
|
9827
10169
|
Object.defineProperty(error3, "toString", {
|
|
9828
10170
|
enumerable: false,
|
|
9829
10171
|
value() {
|
|
9830
|
-
const path2 = pipe(this.path,
|
|
10172
|
+
const path2 = pipe(this.path, join13(options.pathDelim));
|
|
9831
10173
|
return `(Missing data at ${path2}: "${this.message}")`;
|
|
9832
10174
|
}
|
|
9833
10175
|
});
|
|
@@ -9843,7 +10185,7 @@ ${prefix}}`;
|
|
|
9843
10185
|
Object.defineProperty(error3, "toString", {
|
|
9844
10186
|
enumerable: false,
|
|
9845
10187
|
value() {
|
|
9846
|
-
const path2 = pipe(this.path,
|
|
10188
|
+
const path2 = pipe(this.path, join13(options.pathDelim));
|
|
9847
10189
|
return `(Source unavailable at ${path2}: "${this.message}")`;
|
|
9848
10190
|
}
|
|
9849
10191
|
});
|
|
@@ -9858,7 +10200,7 @@ ${prefix}}`;
|
|
|
9858
10200
|
Object.defineProperty(error3, "toString", {
|
|
9859
10201
|
enumerable: false,
|
|
9860
10202
|
value() {
|
|
9861
|
-
const path2 = pipe(this.path,
|
|
10203
|
+
const path2 = pipe(this.path, join13(options.pathDelim));
|
|
9862
10204
|
return `(Unsupported operation at ${path2}: "${this.message}")`;
|
|
9863
10205
|
}
|
|
9864
10206
|
});
|
|
@@ -9888,7 +10230,7 @@ ${prefix}}`;
|
|
|
9888
10230
|
pathDelim: "_",
|
|
9889
10231
|
seqDelim: ","
|
|
9890
10232
|
}, options);
|
|
9891
|
-
const makePathString = (path) => pipe(path,
|
|
10233
|
+
const makePathString = (path) => pipe(path, join13(pathDelim));
|
|
9892
10234
|
const unmakePathString = (pathString) => pathString.split(pathDelim);
|
|
9893
10235
|
const getEnv = () => typeof process !== "undefined" && ("env" in process) && typeof process.env === "object" ? process.env : {};
|
|
9894
10236
|
const load = (path, primitive2, split2 = true) => {
|
|
@@ -10009,7 +10351,7 @@ ${prefix}}`;
|
|
|
10009
10351
|
return fail2(right3.left);
|
|
10010
10352
|
}
|
|
10011
10353
|
if (isRight2(left3) && isRight2(right3)) {
|
|
10012
|
-
const path = pipe(prefix,
|
|
10354
|
+
const path = pipe(prefix, join13("."));
|
|
10013
10355
|
const fail19 = fromFlatLoopFail(prefix, path);
|
|
10014
10356
|
const [lefts, rights] = extend(fail19, fail19, pipe(left3.right, map3(right2)), pipe(right3.right, map3(right2)));
|
|
10015
10357
|
return pipe(lefts, zip(rights), forEachSequential(([left4, right4]) => pipe(zip2(left4, right4), map10(([left5, right5]) => op.zip(left5, right5)))));
|
|
@@ -17941,7 +18283,7 @@ schema (${ast._tag}): ${ast}`;
|
|
|
17941
18283
|
const cowFlags = getCopyOnWriteFlags(process.platform);
|
|
17942
18284
|
for (const relativePath of paths) {
|
|
17943
18285
|
const src = join$1(hostRepoDir, relativePath);
|
|
17944
|
-
if (!
|
|
18286
|
+
if (!existsSync14(src)) {
|
|
17945
18287
|
continue;
|
|
17946
18288
|
}
|
|
17947
18289
|
const dest = join$1(worktreePath, relativePath);
|
|
@@ -18083,7 +18425,7 @@ ${e instanceof Error ? e.message : String(e)}`
|
|
|
18083
18425
|
await mkdir(patchesRoot, { recursive: true });
|
|
18084
18426
|
let dirName = base;
|
|
18085
18427
|
let counter6 = 0;
|
|
18086
|
-
while (
|
|
18428
|
+
while (existsSync14(join$1(patchesRoot, dirName))) {
|
|
18087
18429
|
counter6++;
|
|
18088
18430
|
dirName = `${base}-${counter6}`;
|
|
18089
18431
|
}
|
|
@@ -18255,8 +18597,7 @@ ${msg}`);
|
|
|
18255
18597
|
}), GIT_SETUP_TIMEOUT_MS = 1e4, HOOK_TIMEOUT_MS = 60000, COMMIT_COLLECTION_TIMEOUT_MS = 30000, MERGE_TO_HOST_TIMEOUT_MS = 30000, GIT_SETUP_MAX_RETRIES = 2, GIT_SETUP_RETRY_DELAY_MS = 250, TRANSIENT_EXEC_EXIT_CODES, isTransientExecError = (err) => err._tag === "ExecError" && err.exitCode !== undefined && TRANSIENT_EXEC_EXIT_CODES.has(err.exitCode), execOk2 = (sandbox3, command, options) => Effect_exports.flatMap(sandbox3.exec(command, options), (result) => result.exitCode !== 0 ? Effect_exports.fail(new ExecError({
|
|
18256
18598
|
command,
|
|
18257
18599
|
exitCode: result.exitCode,
|
|
18258
|
-
message:
|
|
18259
|
-
${result.stderr}`
|
|
18600
|
+
message: formatExecFailureMessage(command, result, sandbox3.maxOutputTailChars)
|
|
18260
18601
|
})) : Effect_exports.succeed(result)), execOkWithGitTimeout = (sandbox3, command, gitSetupTimeoutMs, options) => execOk2(sandbox3, command, options).pipe(withTimeout(gitSetupTimeoutMs, () => new GitSetupTimeoutError({
|
|
18261
18602
|
message: `Git command timed out after ${gitSetupTimeoutMs}ms: ${command}`,
|
|
18262
18603
|
timeoutMs: gitSetupTimeoutMs,
|
|
@@ -18592,7 +18933,7 @@ ${result.stderr}`
|
|
|
18592
18933
|
yield* Effect_exports.gen(function* () {
|
|
18593
18934
|
for (const relativePath of pathsToCopy) {
|
|
18594
18935
|
const hostPath = join$1(options.hostRepoDir, relativePath);
|
|
18595
|
-
if (!
|
|
18936
|
+
if (!existsSync14(hostPath)) {
|
|
18596
18937
|
continue;
|
|
18597
18938
|
}
|
|
18598
18939
|
const sandboxPath = posix2.join(handle.worktreePath, relativePath);
|
|
@@ -18635,6 +18976,7 @@ ${result.stderr}`
|
|
|
18635
18976
|
message: "copyIn is not supported for this sandbox provider"
|
|
18636
18977
|
}));
|
|
18637
18978
|
}, makeSandboxFromHandle = (handle) => ({
|
|
18979
|
+
maxOutputTailChars: handle.maxOutputTailChars,
|
|
18638
18980
|
exec: (command, options) => Effect_exports.tryPromise({
|
|
18639
18981
|
try: () => handle.exec(command, options),
|
|
18640
18982
|
catch: (e) => new ExecError({
|
|
@@ -18733,7 +19075,7 @@ Worktree removed (no uncommitted changes)`);
|
|
|
18733
19075
|
};
|
|
18734
19076
|
}
|
|
18735
19077
|
const resolvedHostPath = resolveHostPath(m.hostPath);
|
|
18736
|
-
if (!
|
|
19078
|
+
if (!existsSync14(resolvedHostPath)) {
|
|
18737
19079
|
throw new Error(`Mount hostPath does not exist: ${m.hostPath}` + (m.hostPath !== resolvedHostPath ? ` (resolved to ${resolvedHostPath})` : ""));
|
|
18738
19080
|
}
|
|
18739
19081
|
return {
|
|
@@ -18846,7 +19188,7 @@ Worktree removed (no uncommitted changes)`);
|
|
|
18846
19188
|
const selinux = mount.volume !== undefined ? undefined : selinuxLabel || undefined;
|
|
18847
19189
|
const options = [mount.readonly ? "ro" : undefined, selinux].filter((option5) => option5 !== undefined).join(",");
|
|
18848
19190
|
return options ? `${base}:${options}` : base;
|
|
18849
|
-
}, processFileMountParents = (mounts, sandboxHomedir, statFn =
|
|
19191
|
+
}, processFileMountParents = (mounts, sandboxHomedir, statFn = statSync4) => {
|
|
18850
19192
|
const parentDirs = /* @__PURE__ */ new Set;
|
|
18851
19193
|
for (const mount of mounts) {
|
|
18852
19194
|
let isFile2;
|
|
@@ -19847,7 +20189,7 @@ var init_chunk_VOG34SRF = __esm(() => {
|
|
|
19847
20189
|
}
|
|
19848
20190
|
return [];
|
|
19849
20191
|
});
|
|
19850
|
-
|
|
20192
|
+
join13 = /* @__PURE__ */ dual(2, (self, sep2) => fromIterable2(self).join(sep2));
|
|
19851
20193
|
TypeId4 = /* @__PURE__ */ Symbol.for("effect/Chunk");
|
|
19852
20194
|
emptyArray = [];
|
|
19853
20195
|
_equivalence3 = /* @__PURE__ */ getEquivalence3(equals);
|
|
@@ -53504,6 +53846,7 @@ var noSandbox = (options) => ({
|
|
|
53504
53846
|
const maxOutputTailChars = options?.maxOutputTailChars ?? MAX_TAIL_CHARS;
|
|
53505
53847
|
const handle = {
|
|
53506
53848
|
worktreePath,
|
|
53849
|
+
maxOutputTailChars,
|
|
53507
53850
|
exec: (command, opts) => {
|
|
53508
53851
|
const cwd = opts?.cwd ?? worktreePath;
|
|
53509
53852
|
const isWindows = process.platform === "win32";
|
|
@@ -53600,7 +53943,7 @@ var init_chunk_62WN33RK = __esm(() => {
|
|
|
53600
53943
|
// ../../node_modules/.bun/@ai-hero+sandcastle@0.12.0/node_modules/@ai-hero/sandcastle/dist/index.js
|
|
53601
53944
|
import { createRequire as createRequire8 } from "module";
|
|
53602
53945
|
import { mkdirSync as mkdirSync6, appendFileSync } from "fs";
|
|
53603
|
-
import path, { join as
|
|
53946
|
+
import path, { join as join15, posix as posix4, dirname as dirname7, relative as relative3 } from "path";
|
|
53604
53947
|
import { styleText as styleText2 } from "util";
|
|
53605
53948
|
import * as clack2 from "@clack/prompts";
|
|
53606
53949
|
import { readdir as readdir2, access as access3, readFile as readFile3, mkdir as mkdir3, writeFile as writeFile3, rm as rm3 } from "fs/promises";
|
|
@@ -53669,7 +54012,7 @@ async function run6(options) {
|
|
|
53669
54012
|
const targetBranch = effectiveBranchType === "merge-to-head" ? currentHostBranch : undefined;
|
|
53670
54013
|
const resolvedLogging = options.logging ?? {
|
|
53671
54014
|
type: "file",
|
|
53672
|
-
path:
|
|
54015
|
+
path: join15(hostRepoDir, ".sandcastle", "logs", buildLogFilename(resolvedBranch, targetBranch, options.name))
|
|
53673
54016
|
};
|
|
53674
54017
|
const displayLayer = resolvedLogging.type === "file" ? (() => {
|
|
53675
54018
|
printFileDisplayStartup({
|
|
@@ -54303,7 +54646,7 @@ ${errorDetail}`
|
|
|
54303
54646
|
}
|
|
54304
54647
|
return vars;
|
|
54305
54648
|
}), resolveEnv = (repoDir) => Effect_exports.gen(function* () {
|
|
54306
|
-
const sandcastleEnv = yield* parseEnvFile2(
|
|
54649
|
+
const sandcastleEnv = yield* parseEnvFile2(join15(repoDir, ".sandcastle", ".env"));
|
|
54307
54650
|
const result = {};
|
|
54308
54651
|
for (const key of Object.keys(sandcastleEnv)) {
|
|
54309
54652
|
const value = sandcastleEnv[key] || process.env[key];
|
|
@@ -54525,11 +54868,11 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
54525
54868
|
const normalized = isRoot ? cwd : cwd.replace(/[\\/]+$/, "");
|
|
54526
54869
|
return normalized.replace(/^([A-Za-z]):/, "$1").replace(/[\\/]/g, "-");
|
|
54527
54870
|
}, claudeHostSessionPath = (cwd, id3, projectsDir) => {
|
|
54528
|
-
const base = projectsDir ??
|
|
54529
|
-
return
|
|
54871
|
+
const base = projectsDir ?? join15(process.env.HOME ?? "~", ".claude", "projects");
|
|
54872
|
+
return join15(base, encodeProjectPath(cwd), `${id3}.jsonl`);
|
|
54530
54873
|
}, claudeSandboxSessionPath = (cwd, id3, projectsDir) => posix4.join(projectsDir, encodeProjectPath(cwd), `${id3}.jsonl`), claudeSubagentsDirInSandbox = (cwd, id3, projectsDir) => posix4.join(projectsDir, encodeProjectPath(cwd), id3, "subagents"), claudeSubagentsDirOnHost = (cwd, id3, projectsDir) => {
|
|
54531
|
-
const base = projectsDir ??
|
|
54532
|
-
return
|
|
54874
|
+
const base = projectsDir ?? join15(process.env.HOME ?? "~", ".claude", "projects");
|
|
54875
|
+
return join15(base, encodeProjectPath(cwd), id3, "subagents");
|
|
54533
54876
|
}, listClaudeSubagentSessionsInSandbox = async (cwd, id3, handle, sandboxProjectsDir) => {
|
|
54534
54877
|
const dir = claudeSubagentsDirInSandbox(cwd, id3, sandboxProjectsDir);
|
|
54535
54878
|
const result = await handle.exec(`find ${JSON.stringify(dir)} -type f -name ${JSON.stringify("agent-*.jsonl")} 2>/dev/null`);
|
|
@@ -54541,7 +54884,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
54541
54884
|
return stdout.split(`
|
|
54542
54885
|
`).filter((line) => line !== "");
|
|
54543
54886
|
}, findClaudeSessionOnHost = async (id3, projectsDir) => {
|
|
54544
|
-
const root = projectsDir ??
|
|
54887
|
+
const root = projectsDir ?? join15(process.env.HOME ?? "~", ".claude", "projects");
|
|
54545
54888
|
let entries;
|
|
54546
54889
|
try {
|
|
54547
54890
|
entries = await readdir2(root, { withFileTypes: true });
|
|
@@ -54551,7 +54894,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
54551
54894
|
for (const entry of entries) {
|
|
54552
54895
|
if (!entry.isDirectory())
|
|
54553
54896
|
continue;
|
|
54554
|
-
const candidate =
|
|
54897
|
+
const candidate = join15(root, entry.name, `${id3}.jsonl`);
|
|
54555
54898
|
if (await pathExists(candidate)) {
|
|
54556
54899
|
return { path: candidate, searchedRoot: root };
|
|
54557
54900
|
}
|
|
@@ -54587,7 +54930,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
54587
54930
|
return;
|
|
54588
54931
|
}
|
|
54589
54932
|
for (const entry of entries) {
|
|
54590
|
-
const child =
|
|
54933
|
+
const child = join15(dir, entry.name);
|
|
54591
54934
|
if (entry.isFile() && isCodexSessionFilename(entry.name, id3)) {
|
|
54592
54935
|
return child;
|
|
54593
54936
|
}
|
|
@@ -54601,11 +54944,11 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
54601
54944
|
};
|
|
54602
54945
|
return visit(rootDir);
|
|
54603
54946
|
}, findCodexSessionOnHost = async (id3, sessionsDir) => {
|
|
54604
|
-
const root = sessionsDir ??
|
|
54947
|
+
const root = sessionsDir ?? join15(process.env.HOME ?? "~", ".codex", "sessions");
|
|
54605
54948
|
const path2 = await findCodexSessionPath(root, id3);
|
|
54606
54949
|
return { path: path2, searchedRoot: root };
|
|
54607
54950
|
}, locateCodexHostSession = async (id3, sessionsDir) => {
|
|
54608
|
-
const root = sessionsDir ??
|
|
54951
|
+
const root = sessionsDir ?? join15(process.env.HOME ?? "~", ".codex", "sessions");
|
|
54609
54952
|
const path2 = await findCodexSessionPath(root, id3);
|
|
54610
54953
|
if (!path2)
|
|
54611
54954
|
throw new Error(`session ${id3} not found in ${root}`);
|
|
@@ -54681,7 +55024,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
54681
55024
|
} catch {}
|
|
54682
55025
|
return [];
|
|
54683
55026
|
}, CURSOR_PRINT_PROMPT_MAX_BYTES, readSandboxFile = async (handle, sandboxPath, tag) => {
|
|
54684
|
-
const tmpPath =
|
|
55027
|
+
const tmpPath = join15(tmpdir4(), `sandcastle-${tag}-${Date.now()}-${Math.random().toString(36).slice(2)}.jsonl`);
|
|
54685
55028
|
await handle.copyFileOut(sandboxPath, tmpPath);
|
|
54686
55029
|
try {
|
|
54687
55030
|
return await readFile3(tmpPath, "utf-8");
|
|
@@ -54689,7 +55032,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
54689
55032
|
await rm3(tmpPath, { force: true }).catch(() => {});
|
|
54690
55033
|
}
|
|
54691
55034
|
}, writeSandboxFile = async (handle, sandboxPath, content, tag) => {
|
|
54692
|
-
const tmpPath =
|
|
55035
|
+
const tmpPath = join15(tmpdir4(), `sandcastle-${tag}-${Date.now()}-${Math.random().toString(36).slice(2)}.jsonl`);
|
|
54693
55036
|
await writeFile3(tmpPath, content);
|
|
54694
55037
|
try {
|
|
54695
55038
|
await handle.exec(`mkdir -p ${JSON.stringify(posix4.dirname(sandboxPath))}`);
|
|
@@ -54739,7 +55082,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
54739
55082
|
sourcePath: sandboxSubagentPath,
|
|
54740
55083
|
fromCwd: sandboxCwd,
|
|
54741
55084
|
toCwd: hostCwd,
|
|
54742
|
-
destPath:
|
|
55085
|
+
destPath: join15(hostSubagentsDir, posix4.basename(sandboxSubagentPath)),
|
|
54743
55086
|
tag: "claude-sub"
|
|
54744
55087
|
});
|
|
54745
55088
|
} catch (err) {
|
|
@@ -54776,8 +55119,8 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
54776
55119
|
const located = await locateCodexSandboxSession(sessionId, handle, sandboxSessionsDir);
|
|
54777
55120
|
const jsonl = await readSandboxFile(handle, located.path, "codex-cap");
|
|
54778
55121
|
const rewritten = transferCodexSession(jsonl, sandboxCwd, hostCwd);
|
|
54779
|
-
const root = hostSessionsDir ??
|
|
54780
|
-
const target =
|
|
55122
|
+
const root = hostSessionsDir ?? join15(process.env.HOME ?? "~", ".codex", "sessions");
|
|
55123
|
+
const target = join15(root, located.relativePath);
|
|
54781
55124
|
await mkdir3(dirname7(target), { recursive: true });
|
|
54782
55125
|
await writeFile3(target, rewritten);
|
|
54783
55126
|
capturedPaths.set(sessionId, target);
|
|
@@ -54938,6 +55281,7 @@ var init_dist = __esm(() => {
|
|
|
54938
55281
|
init_chunk_BIWNFKGV();
|
|
54939
55282
|
init_chunk_62WN33RK();
|
|
54940
55283
|
init_chunk_NGBM7T3E();
|
|
55284
|
+
init_chunk_NGBM7T3E();
|
|
54941
55285
|
createRequire8(import.meta.url);
|
|
54942
55286
|
AgentStreamEmitter = class extends Context_exports.Tag("AgentStreamEmitter")() {
|
|
54943
55287
|
};
|
|
@@ -54983,7 +55327,7 @@ var init_dist = __esm(() => {
|
|
|
54983
55327
|
});
|
|
54984
55328
|
|
|
54985
55329
|
// src/services/settings.ts
|
|
54986
|
-
import { existsSync as
|
|
55330
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
54987
55331
|
import { dirname as dirname8 } from "path";
|
|
54988
55332
|
import * as z3 from "zod";
|
|
54989
55333
|
import { eq as eq10 } from "drizzle-orm";
|
|
@@ -54991,10 +55335,10 @@ function hostDefaults(logicalCpus) {
|
|
|
54991
55335
|
return { ...DEFAULTS, burnConcurrency: resolveDefaultBurnConcurrency(logicalCpus) };
|
|
54992
55336
|
}
|
|
54993
55337
|
function readRawConfig(configFile) {
|
|
54994
|
-
if (!
|
|
55338
|
+
if (!existsSync15(configFile))
|
|
54995
55339
|
return {};
|
|
54996
55340
|
try {
|
|
54997
|
-
const parsed = JSON.parse(
|
|
55341
|
+
const parsed = JSON.parse(readFileSync7(configFile, "utf8"));
|
|
54998
55342
|
if (typeof parsed !== "object" || parsed === null)
|
|
54999
55343
|
return {};
|
|
55000
55344
|
return foldLegacyModelConfig(parsed);
|
|
@@ -55021,6 +55365,7 @@ function projectOverrides(ctx, projectId) {
|
|
|
55021
55365
|
const row = ctx.db.select({
|
|
55022
55366
|
model: projects.model,
|
|
55023
55367
|
sandbox: projects.sandbox,
|
|
55368
|
+
sandboxImage: projects.sandboxImage,
|
|
55024
55369
|
sessionBranch: projects.sessionBranch,
|
|
55025
55370
|
devCommand: projects.devCommand,
|
|
55026
55371
|
setupCommand: projects.setupCommand,
|
|
@@ -55034,6 +55379,7 @@ function projectOverrides(ctx, projectId) {
|
|
|
55034
55379
|
return {
|
|
55035
55380
|
model: row?.model ?? null,
|
|
55036
55381
|
sandbox: row?.sandbox ?? null,
|
|
55382
|
+
sandboxImage: row?.sandboxImage ?? null,
|
|
55037
55383
|
sessionBranch: row?.sessionBranch ?? null,
|
|
55038
55384
|
devCommand: row?.devCommand ?? null,
|
|
55039
55385
|
setupCommand: row?.setupCommand ?? null,
|
|
@@ -55236,6 +55582,7 @@ var init_settings = __esm(() => {
|
|
|
55236
55582
|
key: "sandboxImage",
|
|
55237
55583
|
configKey: "sandboxImage",
|
|
55238
55584
|
envVar: "RUNCASTLE_SANDBOX_IMAGE",
|
|
55585
|
+
projectColumn: "sandboxImage",
|
|
55239
55586
|
restartRequired: false,
|
|
55240
55587
|
valueSchema: z3.string().min(1),
|
|
55241
55588
|
parseEnv: idEnv
|
|
@@ -55363,9 +55710,8 @@ var init_settings = __esm(() => {
|
|
|
55363
55710
|
});
|
|
55364
55711
|
|
|
55365
55712
|
// src/services/setup.ts
|
|
55366
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
55367
|
-
import { dirname as dirname9, join as
|
|
55368
|
-
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
55713
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync8, readdirSync as readdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
55714
|
+
import { dirname as dirname9, join as join16 } from "path";
|
|
55369
55715
|
async function writeGitIdentity(exec3, identity4) {
|
|
55370
55716
|
const name = identity4.name.trim();
|
|
55371
55717
|
const email = identity4.email.trim();
|
|
@@ -55465,55 +55811,26 @@ async function resolveRuntime(exec3, preferred) {
|
|
|
55465
55811
|
return other;
|
|
55466
55812
|
return preferred;
|
|
55467
55813
|
}
|
|
55468
|
-
function
|
|
55469
|
-
try {
|
|
55470
|
-
const mainUrl = import.meta.resolve("@ai-hero/sandcastle");
|
|
55471
|
-
let dir = dirname9(fileURLToPath6(mainUrl));
|
|
55472
|
-
for (let hops = 0;hops < 6; hops++) {
|
|
55473
|
-
const manifestPath = join15(dir, "package.json");
|
|
55474
|
-
if (existsSync15(manifestPath)) {
|
|
55475
|
-
const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
|
|
55476
|
-
if (manifest.name === "@ai-hero/sandcastle" && manifest.bin?.sandcastle) {
|
|
55477
|
-
return join15(dir, manifest.bin.sandcastle);
|
|
55478
|
-
}
|
|
55479
|
-
}
|
|
55480
|
-
const parent = dirname9(dir);
|
|
55481
|
-
if (parent === dir)
|
|
55482
|
-
break;
|
|
55483
|
-
dir = parent;
|
|
55484
|
-
}
|
|
55485
|
-
return null;
|
|
55486
|
-
} catch {
|
|
55487
|
-
return null;
|
|
55488
|
-
}
|
|
55489
|
-
}
|
|
55490
|
-
function terminalSpec(kind, opts) {
|
|
55814
|
+
function terminalSpec(kind) {
|
|
55491
55815
|
if (kind === "setup-token")
|
|
55492
55816
|
return { cmd: "claude", args: ["setup-token"] };
|
|
55493
55817
|
for (const spec of Object.values(RUNTIME_SPECS)) {
|
|
55494
55818
|
if (kind === LOGIN_TERMINAL_KIND[spec.runtime])
|
|
55495
55819
|
return { cmd: spec.bin, args: spec.loginArgs };
|
|
55496
55820
|
}
|
|
55497
|
-
|
|
55498
|
-
throw new NotFoundError("The bundled sandcastle CLI (@ai-hero/sandcastle) could not be located \u2014 reinstall runcastle.");
|
|
55499
|
-
}
|
|
55500
|
-
return {
|
|
55501
|
-
cmd: "node",
|
|
55502
|
-
args: [opts.sandcastleBin, opts.runtime, "build-image", "--image-name", opts.imageName]
|
|
55503
|
-
};
|
|
55821
|
+
throw new InvalidInputError(`no terminal command for ${kind}`);
|
|
55504
55822
|
}
|
|
55505
55823
|
function refreshSandcastleConfig(templateDir, targetDir) {
|
|
55506
|
-
const dir =
|
|
55824
|
+
const dir = join16(targetDir, ".sandcastle");
|
|
55507
55825
|
mkdirSync8(dir, { recursive: true });
|
|
55508
55826
|
for (const name of readdirSync4(templateDir)) {
|
|
55509
|
-
copyFileSync2(
|
|
55827
|
+
copyFileSync2(join16(templateDir, name), join16(dir, name));
|
|
55510
55828
|
}
|
|
55511
55829
|
return dir;
|
|
55512
55830
|
}
|
|
55513
55831
|
function prepareSandboxBuildContext(templateDir = sandcastleTemplateDir(), target = sandboxBuildDir()) {
|
|
55514
55832
|
mkdirSync8(target, { recursive: true });
|
|
55515
|
-
refreshSandcastleConfig(templateDir, target);
|
|
55516
|
-
return target;
|
|
55833
|
+
return refreshSandcastleConfig(templateDir, target);
|
|
55517
55834
|
}
|
|
55518
55835
|
function createCredentialVerifier(exec3, runtime5 = DEFAULT_RUNTIME) {
|
|
55519
55836
|
const spec = RUNTIME_SPECS[runtime5];
|
|
@@ -55548,7 +55865,7 @@ function createCredentialVerifier(exec3, runtime5 = DEFAULT_RUNTIME) {
|
|
|
55548
55865
|
function fileAfkTokenIo(exec3, runtime5 = DEFAULT_RUNTIME) {
|
|
55549
55866
|
const path2 = envPath();
|
|
55550
55867
|
return {
|
|
55551
|
-
read: () =>
|
|
55868
|
+
read: () => existsSync16(path2) ? readFileSync8(path2, "utf8") : "",
|
|
55552
55869
|
write: (content) => {
|
|
55553
55870
|
mkdirSync8(dirname9(path2), { recursive: true });
|
|
55554
55871
|
writeFileSync6(path2, content, "utf8");
|
|
@@ -55792,13 +56109,13 @@ var init_no_sandbox = __esm(() => {
|
|
|
55792
56109
|
});
|
|
55793
56110
|
|
|
55794
56111
|
// src/workflows/review-ticket.ts
|
|
55795
|
-
import { existsSync as
|
|
55796
|
-
import { delimiter, join as
|
|
56112
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
56113
|
+
import { delimiter, join as join17 } from "path";
|
|
55797
56114
|
function reviewTemplatePath(ticket) {
|
|
55798
56115
|
return burnerAssetPath(ticket?.passKind === "verification" ? "verify-fixes.md" : "review-ticket.md");
|
|
55799
56116
|
}
|
|
55800
56117
|
function renderReviewPrompt(templateOrTicket, values) {
|
|
55801
|
-
const template = typeof templateOrTicket === "string" ? templateOrTicket :
|
|
56118
|
+
const template = typeof templateOrTicket === "string" ? templateOrTicket : readFileSync9(reviewTemplatePath(templateOrTicket), "utf8");
|
|
55802
56119
|
return renderTemplate(template, values);
|
|
55803
56120
|
}
|
|
55804
56121
|
function findOnPath(bin, env = process.env, platform = process.platform) {
|
|
@@ -55806,8 +56123,8 @@ function findOnPath(bin, env = process.env, platform = process.platform) {
|
|
|
55806
56123
|
const suffixes = platform === "win32" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
55807
56124
|
for (const dir of dirs) {
|
|
55808
56125
|
for (const suffix of suffixes) {
|
|
55809
|
-
const candidate =
|
|
55810
|
-
if (
|
|
56126
|
+
const candidate = join17(dir, `${bin}${suffix}`);
|
|
56127
|
+
if (existsSync17(candidate))
|
|
55811
56128
|
return candidate;
|
|
55812
56129
|
}
|
|
55813
56130
|
}
|
|
@@ -55829,7 +56146,7 @@ function buildDriveAvailability(browserPath, devCommand, inheritedMode) {
|
|
|
55829
56146
|
}
|
|
55830
56147
|
return `A drive is **not** available: ${missing.join(", and ")}. So the mode is already decided \u2014 ` + "run Gates mode, whatever this lap touched, and do not call `review_drive`. This is not a " + "degraded review; it is the whole review this lap gets.";
|
|
55831
56148
|
}
|
|
55832
|
-
function inheritedReviewMode(verifiedTicketId, fileExists2 =
|
|
56149
|
+
function inheritedReviewMode(verifiedTicketId, fileExists2 = existsSync17) {
|
|
55833
56150
|
return verifiedTicketId && fileExists2(reviewWalkthroughPath(verifiedTicketId)) ? "drive" : "gates";
|
|
55834
56151
|
}
|
|
55835
56152
|
function buildDriveInstructions(instructions) {
|
|
@@ -55868,18 +56185,18 @@ function writeReviewArtifacts(ticket, runId, config2) {
|
|
|
55868
56185
|
const dir = reviewDir(ticket.id);
|
|
55869
56186
|
rmSync2(dir, { recursive: true, force: true });
|
|
55870
56187
|
mkdirSync9(dir, { recursive: true });
|
|
55871
|
-
const mcpConfigPath =
|
|
56188
|
+
const mcpConfigPath = join17(dir, "mcp.json");
|
|
55872
56189
|
const mcpConfig = renderRunMcpConfig(runId, config2);
|
|
55873
56190
|
writeFileSync7(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), "utf8");
|
|
55874
56191
|
return {
|
|
55875
56192
|
dir,
|
|
55876
56193
|
mcp: { path: mcpConfigPath, config: mcpConfig },
|
|
55877
|
-
digestPath:
|
|
55878
|
-
blockedPath:
|
|
56194
|
+
digestPath: join17(dir, "DIGEST.md"),
|
|
56195
|
+
blockedPath: join17(dir, "BLOCKED.md"),
|
|
55879
56196
|
walkthroughPath: reviewWalkthroughPath(ticket.id)
|
|
55880
56197
|
};
|
|
55881
56198
|
}
|
|
55882
|
-
function shouldStopAfterDigest(iteration, digestPath, fileExists2 =
|
|
56199
|
+
function shouldStopAfterDigest(iteration, digestPath, fileExists2 = existsSync17) {
|
|
55883
56200
|
return iteration >= 2 && fileExists2(digestPath);
|
|
55884
56201
|
}
|
|
55885
56202
|
async function releaseDriveQuietly() {
|
|
@@ -55957,7 +56274,7 @@ async function reviewTicketOutcome(ctx, ticket, deps, timer) {
|
|
|
55957
56274
|
maxIterations: deps.config.burnMaxIterations,
|
|
55958
56275
|
logging: {
|
|
55959
56276
|
type: "file",
|
|
55960
|
-
path:
|
|
56277
|
+
path: join17(logsDir(), `review-${feature.id}-${ticket.seq}.log`),
|
|
55961
56278
|
onAgentStreamEvent: onStreamEvent
|
|
55962
56279
|
}
|
|
55963
56280
|
};
|
|
@@ -56146,6 +56463,7 @@ var dockerExec = (args2) => Effect_exports.async((resume2) => {
|
|
|
56146
56463
|
const unregisterShutdown = registerShutdown(removeContainerSync);
|
|
56147
56464
|
const handle = {
|
|
56148
56465
|
worktreePath,
|
|
56466
|
+
maxOutputTailChars,
|
|
56149
56467
|
exec: (command, opts) => {
|
|
56150
56468
|
const effectiveCommand = opts?.sudo ? `sudo ${command}` : command;
|
|
56151
56469
|
const args2 = ["exec"];
|
|
@@ -56406,6 +56724,7 @@ var podman = (options) => {
|
|
|
56406
56724
|
const unregisterShutdown = registerShutdown(removeContainerSync);
|
|
56407
56725
|
const handle = {
|
|
56408
56726
|
worktreePath,
|
|
56727
|
+
maxOutputTailChars,
|
|
56409
56728
|
exec: (command, opts) => {
|
|
56410
56729
|
const effectiveCommand = opts?.sudo ? `sudo ${command}` : command;
|
|
56411
56730
|
const args2 = ["exec"];
|
|
@@ -56559,13 +56878,47 @@ var init_podman = __esm(() => {
|
|
|
56559
56878
|
});
|
|
56560
56879
|
|
|
56561
56880
|
// src/workflows/ticket-burner.ts
|
|
56562
|
-
import { existsSync as
|
|
56563
|
-
import { dirname as dirname10, join as
|
|
56564
|
-
import { fileURLToPath as
|
|
56881
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync10, readFileSync as readFileSync10, readdirSync as readdirSync5, rmSync as rmSync3 } from "fs";
|
|
56882
|
+
import { dirname as dirname10, join as join18, posix as posixPath, win32 as winPath } from "path";
|
|
56883
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
56565
56884
|
function missingImageRuntimeMessage(runtime5, image) {
|
|
56566
56885
|
const binary = RUNTIME_BINARY[runtime5];
|
|
56567
56886
|
return `${binary} is not installed in image ${image} \u2014 the image predates the burner Dockerfile. Rebuild it from Settings \u2192 Burns (Rebuild image).`;
|
|
56568
56887
|
}
|
|
56888
|
+
function extractCommandNames(command) {
|
|
56889
|
+
const names = [];
|
|
56890
|
+
for (const segment of (command ?? "").split(COMMAND_SEPARATORS)) {
|
|
56891
|
+
const words = segment.replace(/[(){}'"`]/g, " ").split(/\s+/).filter(Boolean);
|
|
56892
|
+
const word = words.find((w) => !ASSIGNMENT_PREFIX.test(w)) ?? "";
|
|
56893
|
+
if (!PLAIN_COMMAND_NAME.test(word) || NON_COMMAND_WORDS.has(word))
|
|
56894
|
+
continue;
|
|
56895
|
+
if (!names.includes(word))
|
|
56896
|
+
names.push(word);
|
|
56897
|
+
}
|
|
56898
|
+
return names;
|
|
56899
|
+
}
|
|
56900
|
+
function preflightCommandNames(input) {
|
|
56901
|
+
return [
|
|
56902
|
+
...new Set([
|
|
56903
|
+
input.agentBinary,
|
|
56904
|
+
...extractCommandNames(input.setupCommand),
|
|
56905
|
+
...extractCommandNames(input.verifyCommands)
|
|
56906
|
+
])
|
|
56907
|
+
].sort();
|
|
56908
|
+
}
|
|
56909
|
+
function buildToolchainProbeArgs(image, names) {
|
|
56910
|
+
const script = `for c in ${names.join(" ")}; do command -v "$c" >/dev/null 2>&1 || echo "$c"; done`;
|
|
56911
|
+
return ["run", "--rm", "--entrypoint", "sh", image, "-c", script];
|
|
56912
|
+
}
|
|
56913
|
+
function parseMissingCommands(stdout, names) {
|
|
56914
|
+
const absent = new Set(stdout.split(`
|
|
56915
|
+
`).map((line) => line.trim()));
|
|
56916
|
+
return names.filter((name) => absent.has(name));
|
|
56917
|
+
}
|
|
56918
|
+
function missingToolchainMessage(names, image) {
|
|
56919
|
+
const one = names.length === 1;
|
|
56920
|
+
return `${names.join(", ")} ${one ? "is" : "are"} not installed in image ${image} \u2014 add ${one ? "it" : "them"} to .runcastle/sandbox/Dockerfile and rebuild.`;
|
|
56921
|
+
}
|
|
56569
56922
|
function burnAuthReady(runtime5, token, loggedIn = codexLoggedIn) {
|
|
56570
56923
|
if (token !== undefined)
|
|
56571
56924
|
return true;
|
|
@@ -56581,6 +56934,9 @@ function verificationDue(tickets2, runTicketIds) {
|
|
|
56581
56934
|
const verifies = [...reviews].reverse().find((ticket) => ticket.status === "done") ?? null;
|
|
56582
56935
|
if (lastReview?.status === "failed")
|
|
56583
56936
|
return { due: false, landed: [], verifies };
|
|
56937
|
+
const failed = inRun.some((ticket) => !isReviewTicket(ticket) && ticket.status === "failed");
|
|
56938
|
+
if (failed)
|
|
56939
|
+
return { due: false, landed: [], verifies };
|
|
56584
56940
|
const cutoff = verifies?.completedAt ?? Number.NEGATIVE_INFINITY;
|
|
56585
56941
|
const landed = inRun.filter((ticket) => !isReviewTicket(ticket) && ticket.status === "done" && ticket.completedAt !== null && (ticket.completedAt > cutoff || ticket.completedAt === cutoff && verifies !== null && ticket.seq > verifies.seq));
|
|
56586
56942
|
return { due: landed.length > 0, landed, verifies };
|
|
@@ -56722,10 +57078,10 @@ _(The "${DROPPED_MAP_SECTIONS.join('" and "')}" sections are omitted here: they
|
|
|
56722
57078
|
}
|
|
56723
57079
|
function buildProjectStandards(repoPath) {
|
|
56724
57080
|
const present = [];
|
|
56725
|
-
if (
|
|
57081
|
+
if (existsSync18(join18(repoPath, AGENT_CONVENTIONS_FILE))) {
|
|
56726
57082
|
present.push("`CLAUDE.md` \u2014 this repo's agent-facing conventions, and the highest authority on them");
|
|
56727
57083
|
}
|
|
56728
|
-
if (
|
|
57084
|
+
if (existsSync18(join18(repoPath, CHARTER_FILE))) {
|
|
56729
57085
|
present.push(`\`${CHARTER_FILE}\` \u2014 the project charter: what this codebase is for`);
|
|
56730
57086
|
}
|
|
56731
57087
|
const adrs = listLiveAdrs(repoPath).map((a) => a.relPath);
|
|
@@ -56756,7 +57112,7 @@ function buildDriveNotes(settings) {
|
|
|
56756
57112
|
add5("stop", settings.driveStopCommand);
|
|
56757
57113
|
add5("dev server", settings.devCommand);
|
|
56758
57114
|
add5("db reset", settings.dbResetCommand);
|
|
56759
|
-
const hasDir =
|
|
57115
|
+
const hasDir = existsSync18(join18(settings.repoPath, ".runcastle"));
|
|
56760
57116
|
if (configured.length === 0 && !hasDir) {
|
|
56761
57117
|
return [
|
|
56762
57118
|
"This project has no test-drive machinery configured, so there is nothing here for you to keep in step \u2014 do not invent any.",
|
|
@@ -56976,10 +57332,10 @@ function parseSetupMarker(text3) {
|
|
|
56976
57332
|
return { cold: m[1] === "1", syncMs: Number(m[2]), installMs: Number(m[3]) };
|
|
56977
57333
|
}
|
|
56978
57334
|
function takeSetupMarker(worktreePath) {
|
|
56979
|
-
const file =
|
|
57335
|
+
const file = join18(worktreePath, SETUP_MARKER_FILE);
|
|
56980
57336
|
let text3;
|
|
56981
57337
|
try {
|
|
56982
|
-
text3 =
|
|
57338
|
+
text3 = readFileSync10(file, "utf8");
|
|
56983
57339
|
} catch {
|
|
56984
57340
|
return;
|
|
56985
57341
|
}
|
|
@@ -57052,7 +57408,7 @@ function attachedNoteIds(context5) {
|
|
|
57052
57408
|
return ids3;
|
|
57053
57409
|
}
|
|
57054
57410
|
function attachmentSources(context5) {
|
|
57055
|
-
return attachedNoteIds(context5).map(annotationPath).filter((p) =>
|
|
57411
|
+
return attachedNoteIds(context5).map(annotationPath).filter((p) => existsSync18(p));
|
|
57056
57412
|
}
|
|
57057
57413
|
function buildAttachmentCopyCommands(sources, platform = process.platform) {
|
|
57058
57414
|
if (sources.length === 0)
|
|
@@ -57072,7 +57428,7 @@ function buildAttachmentCopyCommands(sources, platform = process.platform) {
|
|
|
57072
57428
|
];
|
|
57073
57429
|
}
|
|
57074
57430
|
async function clearAttachments(workspacePath, repoPath) {
|
|
57075
|
-
rmSync3(
|
|
57431
|
+
rmSync3(join18(workspacePath, ATTACHMENTS_DIR), { recursive: true, force: true });
|
|
57076
57432
|
await unexcludePath(repoPath, ATTACHMENTS_PREFIX);
|
|
57077
57433
|
}
|
|
57078
57434
|
function buildVerifyNotes(config2) {
|
|
@@ -57094,12 +57450,12 @@ function buildVerifyNotes(config2) {
|
|
|
57094
57450
|
`);
|
|
57095
57451
|
}
|
|
57096
57452
|
function readRepoToolchain(repoPath) {
|
|
57097
|
-
const pkgPath =
|
|
57098
|
-
const hasPackageJson =
|
|
57453
|
+
const pkgPath = join18(repoPath, "package.json");
|
|
57454
|
+
const hasPackageJson = existsSync18(pkgPath);
|
|
57099
57455
|
let packageManagerField;
|
|
57100
57456
|
if (hasPackageJson) {
|
|
57101
57457
|
try {
|
|
57102
|
-
const pkg = JSON.parse(
|
|
57458
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
57103
57459
|
if (typeof pkg.packageManager === "string")
|
|
57104
57460
|
packageManagerField = pkg.packageManager;
|
|
57105
57461
|
} catch {}
|
|
@@ -57108,10 +57464,10 @@ function readRepoToolchain(repoPath) {
|
|
|
57108
57464
|
hasPackageJson,
|
|
57109
57465
|
...packageManagerField !== undefined ? { packageManagerField } : {},
|
|
57110
57466
|
lockfiles: {
|
|
57111
|
-
bun:
|
|
57112
|
-
pnpm:
|
|
57113
|
-
yarn:
|
|
57114
|
-
npm:
|
|
57467
|
+
bun: existsSync18(join18(repoPath, "bun.lock")) || existsSync18(join18(repoPath, "bun.lockb")),
|
|
57468
|
+
pnpm: existsSync18(join18(repoPath, "pnpm-lock.yaml")),
|
|
57469
|
+
yarn: existsSync18(join18(repoPath, "yarn.lock")),
|
|
57470
|
+
npm: existsSync18(join18(repoPath, "package-lock.json"))
|
|
57115
57471
|
}
|
|
57116
57472
|
};
|
|
57117
57473
|
}
|
|
@@ -57247,19 +57603,36 @@ function isMergeConflictError(err) {
|
|
|
57247
57603
|
const msg = err instanceof Error ? err.message : typeof err === "string" ? err : "";
|
|
57248
57604
|
return /conflict/i.test(msg) || /git branch -D/i.test(msg) || /automatic merge failed/i.test(msg);
|
|
57249
57605
|
}
|
|
57606
|
+
function refusalNamingCredential(refusal) {
|
|
57607
|
+
return new RegExp(`(?:${refusal}[^\\n]*${CREDENTIAL_SUBJECT}|${CREDENTIAL_SUBJECT}[^\\n]*${refusal})`, "i");
|
|
57608
|
+
}
|
|
57609
|
+
function missingCommandRegex(name) {
|
|
57610
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
57611
|
+
return new RegExp(`\\b${escaped}\\b[^\\n]*(?:exited with code 127|command not found|not found|no such file or directory)`, "i");
|
|
57612
|
+
}
|
|
57250
57613
|
function missingAgentBinaryMessage(err, runtime5, image) {
|
|
57251
57614
|
const msg = err instanceof Error ? err.message : typeof err === "string" ? err : String(err);
|
|
57252
57615
|
const binary = AGENT_BINARY[runtime5];
|
|
57253
|
-
|
|
57254
|
-
if (!missing.test(msg))
|
|
57616
|
+
if (!missingCommandRegex(binary).test(msg))
|
|
57255
57617
|
return;
|
|
57256
57618
|
return `${binary} is not installed in image ${image} \u2014 the image predates the burner Dockerfile. Rebuild it from Settings \u2192 Burns (Rebuild image).`;
|
|
57257
57619
|
}
|
|
57258
|
-
function
|
|
57620
|
+
function missingSetupBinaryMessage(err, setupCommand, image) {
|
|
57621
|
+
const msg = err instanceof Error ? err.message : typeof err === "string" ? err : String(err);
|
|
57622
|
+
const missing = extractCommandNames(setupCommand).find((name) => missingCommandRegex(name).test(msg));
|
|
57623
|
+
return missing ? missingToolchainMessage([missing], image) : undefined;
|
|
57624
|
+
}
|
|
57625
|
+
function classifyTicketRunError(err, runtime5, setupCommand) {
|
|
57259
57626
|
const msg = err instanceof Error ? err.message : typeof err === "string" ? err : String(err);
|
|
57260
57627
|
const runtimes = runtime5 ? [runtime5] : ["claude-code", "codex"];
|
|
57261
57628
|
const forRuntimes = (table) => runtimes.flatMap((r) => table[r]);
|
|
57262
57629
|
if (runtimes.some((r) => missingAgentBinaryMessage(msg, r, "image")))
|
|
57630
|
+
return "run-fatal";
|
|
57631
|
+
if ([...RUN_FATAL_ERROR_PATTERNS, ...forRuntimes(RUNTIME_RUN_FATAL_ERROR_PATTERNS)].some((p) => p.test(msg)))
|
|
57632
|
+
return "run-fatal";
|
|
57633
|
+
if (missingSetupBinaryMessage(msg, setupCommand, "image"))
|
|
57634
|
+
return "fatal";
|
|
57635
|
+
if (MISSING_COMMAND_PATTERNS.some((p) => p.test(msg)))
|
|
57263
57636
|
return "fatal";
|
|
57264
57637
|
if ([...FATAL_ERROR_PATTERNS, ...forRuntimes(RUNTIME_FATAL_ERROR_PATTERNS)].some((p) => p.test(msg)))
|
|
57265
57638
|
return "fatal";
|
|
@@ -57301,11 +57674,20 @@ function delayUnlessAborted(ms, signal) {
|
|
|
57301
57674
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
57302
57675
|
});
|
|
57303
57676
|
}
|
|
57304
|
-
|
|
57677
|
+
function stopTicketRun(ticketId) {
|
|
57678
|
+
return abortTicketRun(ticketId, new Error("ticket stopped by user"));
|
|
57679
|
+
}
|
|
57680
|
+
function haltTicketRun(ticketId, headline) {
|
|
57681
|
+
return abortTicketRun(ticketId, new RunHaltedAbort(headline));
|
|
57682
|
+
}
|
|
57683
|
+
function ticketStopReason(reason) {
|
|
57684
|
+
return reason instanceof RunHaltedAbort ? `stopped: run halted (${reason.headline})` : "stopped by user";
|
|
57685
|
+
}
|
|
57686
|
+
async function abortTicketRun(ticketId, reason) {
|
|
57305
57687
|
const controller = activeTicketAborts.get(ticketId);
|
|
57306
57688
|
if (!controller)
|
|
57307
57689
|
return { stopped: false, confirmed: true };
|
|
57308
|
-
controller.abort(
|
|
57690
|
+
controller.abort(reason);
|
|
57309
57691
|
const { confirmed } = await killRegistry().killAndWait(ticketId);
|
|
57310
57692
|
return { stopped: true, confirmed };
|
|
57311
57693
|
}
|
|
@@ -57369,11 +57751,7 @@ function errorHeadline(s) {
|
|
|
57369
57751
|
const causes = lines.filter((l) => /^(fatal|error):/i.test(l));
|
|
57370
57752
|
return causes.at(-1) ?? lines[0] ?? "";
|
|
57371
57753
|
}
|
|
57372
|
-
function
|
|
57373
|
-
const list = [...seqs].sort((a, b) => a - b).join(", ");
|
|
57374
|
-
return `> Reviewed with failed implementation ticket(s): ${list}.`;
|
|
57375
|
-
}
|
|
57376
|
-
async function burnTickets(ctx, tickets2, execute, concurrency = 1) {
|
|
57754
|
+
async function burnTickets(ctx, tickets2, execute, concurrency = 1, ticketRuntime) {
|
|
57377
57755
|
const width = Math.max(1, Math.floor(concurrency));
|
|
57378
57756
|
const scheduled = [...tickets2];
|
|
57379
57757
|
const bySeq = indexBySeq(scheduled);
|
|
@@ -57381,10 +57759,23 @@ async function burnTickets(ctx, tickets2, execute, concurrency = 1) {
|
|
|
57381
57759
|
const pending3 = new Set(scheduled.filter((t) => t.status === "pending").map((t) => t.seq));
|
|
57382
57760
|
const inFlight = new Map;
|
|
57383
57761
|
const digests = [];
|
|
57762
|
+
const deferred = [];
|
|
57763
|
+
const collapsed = [];
|
|
57384
57764
|
let admitted = 0;
|
|
57385
57765
|
let verificationChecked = false;
|
|
57386
|
-
|
|
57766
|
+
let halted;
|
|
57767
|
+
const satisfied = (s) => s === "done" || s === "cancelled";
|
|
57387
57768
|
const implementationsSettled = () => scheduled.every((t) => isReviewTicket(t) || !pending3.has(t.seq) && !inFlight.has(t.seq));
|
|
57769
|
+
const failedImplementations = () => scheduled.filter((t) => !isReviewTicket(t) && status3.get(t.seq) === "failed").map((t) => t.seq);
|
|
57770
|
+
const reviewGate = () => {
|
|
57771
|
+
if (!implementationsSettled())
|
|
57772
|
+
return "wait";
|
|
57773
|
+
if (failedImplementations().length > 0)
|
|
57774
|
+
return "defer";
|
|
57775
|
+
const implementations = scheduled.filter((t) => !isReviewTicket(t));
|
|
57776
|
+
const landed = implementations.some((t) => status3.get(t.seq) === "done");
|
|
57777
|
+
return implementations.length > 0 && !landed ? "collapsed" : "ready";
|
|
57778
|
+
};
|
|
57388
57779
|
const admitNewTickets = () => {
|
|
57389
57780
|
const added = [];
|
|
57390
57781
|
for (const t of ctx.listTickets?.() ?? []) {
|
|
@@ -57459,20 +57850,18 @@ async function burnTickets(ctx, tickets2, execute, concurrency = 1) {
|
|
|
57459
57850
|
for (const b of t.blockedBy) {
|
|
57460
57851
|
const present = bySeq.has(b);
|
|
57461
57852
|
const bs = present ? status3.get(b) : undefined;
|
|
57462
|
-
if (!present
|
|
57853
|
+
if (!present)
|
|
57854
|
+
return { blockedBy: b, present };
|
|
57855
|
+
if (bs === "failed" && !isReviewTicket(t))
|
|
57463
57856
|
return { blockedBy: b, present };
|
|
57464
57857
|
}
|
|
57465
|
-
if (isReviewTicket(t)
|
|
57466
|
-
return
|
|
57467
|
-
return t.blockedBy.every((b) => satisfied(
|
|
57858
|
+
if (isReviewTicket(t))
|
|
57859
|
+
return reviewGate();
|
|
57860
|
+
return t.blockedBy.every((b) => satisfied(status3.get(b))) ? "ready" : "wait";
|
|
57468
57861
|
};
|
|
57469
57862
|
const harvestDigest2 = (t, digest) => {
|
|
57470
|
-
|
|
57471
|
-
|
|
57472
|
-
|
|
57473
|
-
`);
|
|
57474
|
-
if (body)
|
|
57475
|
-
digests.push({ seq: t.seq, title: t.title, digest: body });
|
|
57863
|
+
if (digest)
|
|
57864
|
+
digests.push({ seq: t.seq, title: t.title, digest });
|
|
57476
57865
|
};
|
|
57477
57866
|
const failTicket = (seq2, error, extra, digest) => {
|
|
57478
57867
|
const t = bySeq.get(seq2);
|
|
@@ -57485,10 +57874,34 @@ async function burnTickets(ctx, tickets2, execute, concurrency = 1) {
|
|
|
57485
57874
|
if (extra)
|
|
57486
57875
|
ctx.emitEvent({ ...extra, ticketId: t.id });
|
|
57487
57876
|
};
|
|
57877
|
+
const haltRun = async (t, outcome) => {
|
|
57878
|
+
const halt = {
|
|
57879
|
+
runtime: outcome.runtime,
|
|
57880
|
+
ticketSeq: t.seq,
|
|
57881
|
+
headline: errorHeadline(outcome.error)
|
|
57882
|
+
};
|
|
57883
|
+
halted = halt;
|
|
57884
|
+
ctx.emitEvent({
|
|
57885
|
+
type: "run.halted",
|
|
57886
|
+
message: `run halted: ${halt.headline}`,
|
|
57887
|
+
ticketId: t.id,
|
|
57888
|
+
data: { runtime: halt.runtime, ticketSeq: halt.ticketSeq }
|
|
57889
|
+
});
|
|
57890
|
+
const doomed = [...inFlight.keys()].filter((other) => other !== t.seq).map((other) => bySeq.get(other)).filter((x) => x !== undefined).filter((x) => ticketRuntime === undefined || ticketRuntime(x) === outcome.runtime);
|
|
57891
|
+
await Promise.all(doomed.map((x) => haltTicketRun(x.id, halt.headline)));
|
|
57892
|
+
};
|
|
57893
|
+
const refreshedForLaunch = (t) => {
|
|
57894
|
+
const fresh3 = ctx.listTickets?.().find((row) => row.id === t.id);
|
|
57895
|
+
if (!fresh3)
|
|
57896
|
+
return t;
|
|
57897
|
+
bySeq.set(t.seq, fresh3);
|
|
57898
|
+
return fresh3;
|
|
57899
|
+
};
|
|
57488
57900
|
const runOne = async (seq2) => {
|
|
57489
|
-
const
|
|
57490
|
-
if (!
|
|
57901
|
+
const snapshot = bySeq.get(seq2);
|
|
57902
|
+
if (!snapshot)
|
|
57491
57903
|
return;
|
|
57904
|
+
const t = refreshedForLaunch(snapshot);
|
|
57492
57905
|
status3.set(seq2, "burning");
|
|
57493
57906
|
ctx.updateTicket(t.id, { status: "burning" });
|
|
57494
57907
|
mirrorFinding(t, "fixing");
|
|
@@ -57531,35 +57944,67 @@ async function burnTickets(ctx, tickets2, execute, concurrency = 1) {
|
|
|
57531
57944
|
ticketId: t.id,
|
|
57532
57945
|
data: { error: outcome.error }
|
|
57533
57946
|
});
|
|
57947
|
+
if (outcome.runFatal && !halted) {
|
|
57948
|
+
await haltRun(t, { error: outcome.error, runtime: outcome.runFatal.runtime });
|
|
57949
|
+
}
|
|
57534
57950
|
}
|
|
57535
57951
|
if (isReviewTicket(t))
|
|
57536
57952
|
admitNewTickets();
|
|
57537
57953
|
};
|
|
57538
57954
|
while (pending3.size > 0 || inFlight.size > 0 || !verificationChecked) {
|
|
57539
57955
|
ctx.signal.throwIfAborted();
|
|
57540
|
-
if (
|
|
57956
|
+
if (halted && inFlight.size === 0)
|
|
57957
|
+
break;
|
|
57958
|
+
if (!halted && pending3.size === 0 && inFlight.size === 0) {
|
|
57541
57959
|
appendVerificationIfDue();
|
|
57542
57960
|
if (pending3.size === 0)
|
|
57543
57961
|
break;
|
|
57544
57962
|
}
|
|
57545
|
-
for (const seq2 of [...pending3]) {
|
|
57963
|
+
for (const seq2 of halted ? [] : [...pending3]) {
|
|
57546
57964
|
const st = readyState(seq2);
|
|
57547
|
-
if (
|
|
57548
|
-
|
|
57549
|
-
|
|
57550
|
-
|
|
57551
|
-
|
|
57965
|
+
if (st === "ready" || st === "wait")
|
|
57966
|
+
continue;
|
|
57967
|
+
pending3.delete(seq2);
|
|
57968
|
+
const t = bySeq.get(seq2);
|
|
57969
|
+
if (st === "defer") {
|
|
57970
|
+
deferred.push(seq2);
|
|
57552
57971
|
if (t) {
|
|
57972
|
+
const failed = failedImplementations();
|
|
57553
57973
|
ctx.emitEvent({
|
|
57554
|
-
type: "ticket.
|
|
57555
|
-
message: `ticket ${t.seq} ${
|
|
57974
|
+
type: "ticket.deferred",
|
|
57975
|
+
message: `ticket ${t.seq} deferred to the next burn: implementation ticket(s) ${failed.join(", ")} failed \u2014 retry or cancel them, then burn again`,
|
|
57976
|
+
ticketId: t.id,
|
|
57977
|
+
data: { failed }
|
|
57978
|
+
});
|
|
57979
|
+
}
|
|
57980
|
+
continue;
|
|
57981
|
+
}
|
|
57982
|
+
if (st === "collapsed") {
|
|
57983
|
+
collapsed.push(seq2);
|
|
57984
|
+
status3.set(seq2, "cancelled");
|
|
57985
|
+
if (t) {
|
|
57986
|
+
ctx.updateTicket(t.id, { status: "cancelled", error: NOTHING_LANDED });
|
|
57987
|
+
ctx.emitEvent({
|
|
57988
|
+
type: "ticket.cancelled",
|
|
57989
|
+
message: `ticket ${t.seq} cancelled: ${NOTHING_LANDED}`,
|
|
57556
57990
|
ticketId: t.id,
|
|
57557
|
-
data: { reason
|
|
57991
|
+
data: { reason: NOTHING_LANDED }
|
|
57558
57992
|
});
|
|
57559
57993
|
}
|
|
57994
|
+
continue;
|
|
57995
|
+
}
|
|
57996
|
+
const reason = st.present ? `blocked by failed ticket ${st.blockedBy}` : `blocked by missing ticket ${st.blockedBy}`;
|
|
57997
|
+
failTicket(seq2, reason);
|
|
57998
|
+
if (t) {
|
|
57999
|
+
ctx.emitEvent({
|
|
58000
|
+
type: "ticket.blocked",
|
|
58001
|
+
message: `ticket ${t.seq} ${reason}`,
|
|
58002
|
+
ticketId: t.id,
|
|
58003
|
+
data: { reason, blockedBy: st.blockedBy }
|
|
58004
|
+
});
|
|
57560
58005
|
}
|
|
57561
58006
|
}
|
|
57562
|
-
while (inFlight.size < width) {
|
|
58007
|
+
while (!halted && inFlight.size < width) {
|
|
57563
58008
|
const readySeq = [...pending3].find((seq2) => readyState(seq2) === "ready");
|
|
57564
58009
|
if (readySeq === undefined)
|
|
57565
58010
|
break;
|
|
@@ -57595,7 +58040,7 @@ async function burnTickets(ctx, tickets2, execute, concurrency = 1) {
|
|
|
57595
58040
|
for (const s of status3.values())
|
|
57596
58041
|
if (s === "done")
|
|
57597
58042
|
done10 += 1;
|
|
57598
|
-
return { done: done10, admitted, digests };
|
|
58043
|
+
return { done: done10, admitted, digests, deferred, collapsed, ...halted ? { halted } : {} };
|
|
57599
58044
|
}
|
|
57600
58045
|
function composeRunDigest(entries) {
|
|
57601
58046
|
if (entries.length === 0)
|
|
@@ -57628,18 +58073,18 @@ async function burnRun(ctx, deps) {
|
|
|
57628
58073
|
return { status: "failed", summary: "burn aborted: auth token missing" };
|
|
57629
58074
|
}
|
|
57630
58075
|
if (deps.config.sandbox !== "noSandbox" && deps.exec) {
|
|
57631
|
-
const image = resolveSandboxImage(deps.config);
|
|
58076
|
+
const image = resolveSandboxImage(deps.config, ctx.project);
|
|
57632
58077
|
const binary = RUNTIME_BINARY[deps.runtime];
|
|
57633
|
-
const
|
|
57634
|
-
|
|
57635
|
-
|
|
57636
|
-
|
|
57637
|
-
|
|
57638
|
-
|
|
57639
|
-
|
|
57640
|
-
]
|
|
57641
|
-
if (
|
|
57642
|
-
const message = missingImageRuntimeMessage(deps.runtime, image);
|
|
58078
|
+
const prepared = resolvePreparedSettings(deps.config, ctx.project);
|
|
58079
|
+
const names = preflightCommandNames({
|
|
58080
|
+
agentBinary: binary,
|
|
58081
|
+
setupCommand: resolveSetupCommand(readRepoToolchain(ctx.project.repoPath), prepared.setupCommand),
|
|
58082
|
+
verifyCommands: prepared.verifyCommands
|
|
58083
|
+
});
|
|
58084
|
+
const probe = await deps.exec(deps.config.sandbox, buildToolchainProbeArgs(image, names));
|
|
58085
|
+
const missing = probe.ok && probe.code === 0 ? parseMissingCommands(probe.stdout, names) : [binary];
|
|
58086
|
+
if (missing.length > 0) {
|
|
58087
|
+
const message = missing.includes(binary) ? missingImageRuntimeMessage(deps.runtime, image) : missingToolchainMessage(missing, image);
|
|
57643
58088
|
ctx.emitEvent({ type: IMAGE_RUNTIME_MISSING_EVENT, message });
|
|
57644
58089
|
return { status: "failed", summary: message };
|
|
57645
58090
|
}
|
|
@@ -57654,12 +58099,22 @@ async function burnRun(ctx, deps) {
|
|
|
57654
58099
|
});
|
|
57655
58100
|
return { status: "failed", summary: `dependency cycle: ${path2}` };
|
|
57656
58101
|
}
|
|
57657
|
-
const { done: done10, admitted, digests } = await burnTickets(ctx, tickets2, gateTicketAuth(deps), deps.concurrency);
|
|
57658
|
-
const total = burnable.length + admitted;
|
|
57659
|
-
const
|
|
57660
|
-
|
|
58102
|
+
const { done: done10, admitted, digests, halted, deferred, collapsed } = await burnTickets(ctx, tickets2, gateTicketAuth(deps), deps.concurrency, deps.ticketRuntime);
|
|
58103
|
+
const total = burnable.length + admitted - collapsed.length;
|
|
58104
|
+
const cancelledCount = cancelled + collapsed.length;
|
|
58105
|
+
const counts = cancelledCount > 0 ? `${done10}/${total} tickets done (${cancelledCount} cancelled)` : `${done10}/${total} tickets done`;
|
|
58106
|
+
const summary = halted ? `run halted at ticket ${halted.ticketSeq}: ${halted.headline} \u2014 ${counts}` : deferred.length > 0 ? `${counts} \u2014 review deferred until the failed ticket(s) are retried or cancelled` : counts;
|
|
58107
|
+
ctx.emitEvent({
|
|
58108
|
+
type: "burn.summary",
|
|
58109
|
+
message: summary,
|
|
58110
|
+
data: { done: done10, total, cancelled: cancelledCount }
|
|
58111
|
+
});
|
|
57661
58112
|
const digest = composeRunDigest(digests);
|
|
57662
|
-
return {
|
|
58113
|
+
return {
|
|
58114
|
+
status: !halted && done10 === total ? "succeeded" : "failed",
|
|
58115
|
+
summary,
|
|
58116
|
+
...digest ? { digest } : {}
|
|
58117
|
+
};
|
|
57663
58118
|
}
|
|
57664
58119
|
function burnerTemplatePath() {
|
|
57665
58120
|
return burnerAssetPath("implement-ticket.md");
|
|
@@ -57668,8 +58123,8 @@ function resolverTemplatePath() {
|
|
|
57668
58123
|
return burnerAssetPath("resolve-conflict.md");
|
|
57669
58124
|
}
|
|
57670
58125
|
function burnerAssetPath(file) {
|
|
57671
|
-
const here = dirname10(
|
|
57672
|
-
return
|
|
58126
|
+
const here = dirname10(fileURLToPath6(import.meta.url));
|
|
58127
|
+
return join18(resolveSkillsRoot(here), "burner", file);
|
|
57673
58128
|
}
|
|
57674
58129
|
function listFeatureDocs(docsDir, prefix = "") {
|
|
57675
58130
|
const out = [];
|
|
@@ -57678,7 +58133,7 @@ function listFeatureDocs(docsDir, prefix = "") {
|
|
|
57678
58133
|
if (e.isFile() && e.name.endsWith(".md"))
|
|
57679
58134
|
out.push(rel);
|
|
57680
58135
|
else if (e.isDirectory() && !prefix)
|
|
57681
|
-
out.push(...listFeatureDocs(
|
|
58136
|
+
out.push(...listFeatureDocs(join18(docsDir, e.name), rel));
|
|
57682
58137
|
}
|
|
57683
58138
|
return out;
|
|
57684
58139
|
}
|
|
@@ -57692,11 +58147,11 @@ function readDocsDigest(projectId, slug) {
|
|
|
57692
58147
|
...missing ? { missing } : {}
|
|
57693
58148
|
});
|
|
57694
58149
|
const worktree = worktreeDir(projectId, slug);
|
|
57695
|
-
if (!
|
|
58150
|
+
if (!existsSync18(worktree)) {
|
|
57696
58151
|
return done10("_No talk worktree on disk \u2014 docs digest skipped._", "no-worktree");
|
|
57697
58152
|
}
|
|
57698
|
-
const docsDir =
|
|
57699
|
-
if (!
|
|
58153
|
+
const docsDir = join18(worktree, ...docsRel.split("/"));
|
|
58154
|
+
if (!existsSync18(docsDir)) {
|
|
57700
58155
|
return done10("_No docs/features dir in the talk worktree \u2014 docs digest skipped._", "no-docs-dir");
|
|
57701
58156
|
}
|
|
57702
58157
|
const names = listFeatureDocs(docsDir).sort((a, b) => agentDigestDocOrder(a) - agentDigestDocOrder(b) || a.localeCompare(b));
|
|
@@ -57712,7 +58167,7 @@ function readDocsDigest(projectId, slug) {
|
|
|
57712
58167
|
}
|
|
57713
58168
|
let content;
|
|
57714
58169
|
try {
|
|
57715
|
-
content =
|
|
58170
|
+
content = readFileSync10(join18(docsDir, ...name.split("/")), "utf8");
|
|
57716
58171
|
} catch {
|
|
57717
58172
|
continue;
|
|
57718
58173
|
}
|
|
@@ -57758,10 +58213,10 @@ function readAgentFile(dirs, file) {
|
|
|
57758
58213
|
for (const dir of dirs) {
|
|
57759
58214
|
if (!dir)
|
|
57760
58215
|
continue;
|
|
57761
|
-
const p =
|
|
57762
|
-
if (
|
|
58216
|
+
const p = join18(dir, file);
|
|
58217
|
+
if (existsSync18(p)) {
|
|
57763
58218
|
try {
|
|
57764
|
-
return
|
|
58219
|
+
return readFileSync10(p, "utf8");
|
|
57765
58220
|
} catch {}
|
|
57766
58221
|
}
|
|
57767
58222
|
}
|
|
@@ -57829,8 +58284,8 @@ function buildAgentExtraArgs(runtime5, options) {
|
|
|
57829
58284
|
function quoteArg(value) {
|
|
57830
58285
|
return `"${value}"`;
|
|
57831
58286
|
}
|
|
57832
|
-
function selectSandbox(config2, mounts = [], env = {}, kill = {}) {
|
|
57833
|
-
const imageOpts = buildSandboxOptions(config2, mounts, env, kill.containerName);
|
|
58287
|
+
function selectSandbox(config2, project3, mounts = [], env = {}, kill = {}) {
|
|
58288
|
+
const imageOpts = buildSandboxOptions(config2, project3, mounts, env, kill.containerName);
|
|
57834
58289
|
switch (config2.sandbox) {
|
|
57835
58290
|
case "docker":
|
|
57836
58291
|
return docker(imageOpts);
|
|
@@ -57842,9 +58297,9 @@ function selectSandbox(config2, mounts = [], env = {}, kill = {}) {
|
|
|
57842
58297
|
throw new Error(`no sandbox provider for sandbox "${config2.sandbox}" \u2014 refusing to run the agent unsandboxed`);
|
|
57843
58298
|
}
|
|
57844
58299
|
}
|
|
57845
|
-
function buildSandboxOptions(config2, mounts = [], env = {}, containerName) {
|
|
58300
|
+
function buildSandboxOptions(config2, project3, mounts = [], env = {}, containerName) {
|
|
57846
58301
|
return {
|
|
57847
|
-
imageName: resolveSandboxImage(config2),
|
|
58302
|
+
imageName: resolveSandboxImage(config2, project3),
|
|
57848
58303
|
...mounts.length > 0 ? { mounts } : {},
|
|
57849
58304
|
...config2.burnCpus !== undefined ? { cpus: config2.burnCpus } : {},
|
|
57850
58305
|
...Object.keys(env).length > 0 ? { env } : {},
|
|
@@ -57905,7 +58360,7 @@ async function burnTicket(ctx, ticket, config2, token, model, land, ensureIsolat
|
|
|
57905
58360
|
const guardInstalled = config2.burnGuard && config2.sandbox !== "noSandbox";
|
|
57906
58361
|
const prepared = resolvePreparedSettings(config2, project3);
|
|
57907
58362
|
const verifyNotes = buildVerifyNotes(prepared);
|
|
57908
|
-
const basePrompt = renderTicketPrompt(
|
|
58363
|
+
const basePrompt = renderTicketPrompt(readFileSync10(burnerTemplatePath(), "utf8"), {
|
|
57909
58364
|
WORKSPACE_NOTES: workspaceNotes,
|
|
57910
58365
|
PROJECT_STANDARDS: blocks.projectStandards,
|
|
57911
58366
|
FEATURE_BRIEF: featureBrief,
|
|
@@ -57933,10 +58388,10 @@ async function burnTicket(ctx, ticket, config2, token, model, land, ensureIsolat
|
|
|
57933
58388
|
const guardInstall = guardInstalled ? buildGuardInstallCommand(model.runtime) : undefined;
|
|
57934
58389
|
const withPrelude = (setup) => chainSetupCommands(codexAuthMount ? buildCodexAuthCopyCommand() : undefined, guardInstall, setup);
|
|
57935
58390
|
const agentOptions = guardInstalled ? { bypassHookTrust: true } : {};
|
|
57936
|
-
const slotStamp = buildSlotStamp(resolveSandboxImage(config2), toolchain.packageManagerField);
|
|
58391
|
+
const slotStamp = buildSlotStamp(resolveSandboxImage(config2, project3), toolchain.packageManagerField);
|
|
57937
58392
|
const setupHookFor = (branch) => withPrelude(slot !== undefined ? buildSlotSetupCommand(slot, branch, setupCommand, pm, slotStamp) : workspaceMode === "isolated" ? buildIsolatedSetupCommand(branch, setupCommand, pm) : setupCommand);
|
|
57938
58393
|
mkdirSync10(logsDir(), { recursive: true });
|
|
57939
|
-
const logFilePath =
|
|
58394
|
+
const logFilePath = join18(logsDir(), `burn-${feature.id}-${ticket.seq}.log`);
|
|
57940
58395
|
const throttle = createStreamThrottle((e) => ctx.emitEvent({ ...e, ticketId: ticket.id }));
|
|
57941
58396
|
beginTranscript(ticket.id);
|
|
57942
58397
|
const timer = createToolTimer();
|
|
@@ -58008,6 +58463,17 @@ async function burnTicket(ctx, ticket, config2, token, model, land, ensureIsolat
|
|
|
58008
58463
|
const preserveChain = (branch) => {
|
|
58009
58464
|
ctx.updateTicket(ticket.id, { attemptBranch: branch });
|
|
58010
58465
|
};
|
|
58466
|
+
const stoppedOutcome = (salvaged) => {
|
|
58467
|
+
const reason = ticketStopReason(ticketAbort.signal.reason);
|
|
58468
|
+
return {
|
|
58469
|
+
status: "failed",
|
|
58470
|
+
error: `${reason}${salvaged > 0 ? ` \u2014 ${salvaged} commit(s) preserved; retry to continue from them` : ""}`,
|
|
58471
|
+
event: {
|
|
58472
|
+
type: "ticket.stopped",
|
|
58473
|
+
message: `ticket ${ticket.seq} ${reason}${salvaged > 0 ? ` \u2014 ${salvaged} commit(s) preserved for retry` : ""}`
|
|
58474
|
+
}
|
|
58475
|
+
};
|
|
58476
|
+
};
|
|
58011
58477
|
const discards = new Map;
|
|
58012
58478
|
const discardWorktree = (branch) => {
|
|
58013
58479
|
const pending3 = discards.get(branch) ?? cleanupBurnWorktree(project3.repoPath, branch);
|
|
@@ -58018,7 +58484,7 @@ async function burnTicket(ctx, ticket, config2, token, model, land, ensureIsolat
|
|
|
58018
58484
|
const resolveBranch = ticketBranchName(feature.slug, ticket.seq, newId("r").slice(2, 10));
|
|
58019
58485
|
const containerName = burnContainerName(ctx.runId, ticket.seq, "resolve");
|
|
58020
58486
|
const otherSide = await commitSummaries(project3.repoPath, input.branch, feature.branch);
|
|
58021
|
-
const prompt = renderTemplate(
|
|
58487
|
+
const prompt = renderTemplate(readFileSync10(resolverTemplatePath(), "utf8"), {
|
|
58022
58488
|
TICKET_JSON: ticketJson,
|
|
58023
58489
|
FEATURE_BRIEF: featureBrief,
|
|
58024
58490
|
DOCS_DIGEST: docsDigest,
|
|
@@ -58047,7 +58513,7 @@ async function burnTicket(ctx, ticket, config2, token, model, land, ensureIsolat
|
|
|
58047
58513
|
makeKillable(containerName);
|
|
58048
58514
|
result = await run6({
|
|
58049
58515
|
agent: buildBurnAgent(config2, token, model, agentOptions),
|
|
58050
|
-
sandbox: selectSandbox(config2, mounts, sandboxEnv, killHandles(containerName)),
|
|
58516
|
+
sandbox: selectSandbox(config2, project3, mounts, sandboxEnv, killHandles(containerName)),
|
|
58051
58517
|
cwd: project3.repoPath,
|
|
58052
58518
|
prompt,
|
|
58053
58519
|
branchStrategy: { type: "branch", branch: resolveBranch, baseBranch: input.branch },
|
|
@@ -58180,7 +58646,7 @@ ${blocked3.trim()}`
|
|
|
58180
58646
|
}
|
|
58181
58647
|
const runOptions = {
|
|
58182
58648
|
agent: buildBurnAgent(config2, token, model, agentOptions),
|
|
58183
|
-
sandbox: selectSandbox(config2, mounts, sandboxEnv, killHandles(containerName)),
|
|
58649
|
+
sandbox: selectSandbox(config2, project3, mounts, sandboxEnv, killHandles(containerName)),
|
|
58184
58650
|
cwd: project3.repoPath,
|
|
58185
58651
|
prompt: retryNotes ? `${basePrompt}
|
|
58186
58652
|
|
|
@@ -58216,21 +58682,15 @@ ${retryNotes}` : basePrompt,
|
|
|
58216
58682
|
if (ctx.signal.aborted)
|
|
58217
58683
|
throw err;
|
|
58218
58684
|
const msg = err instanceof Error ? err.message : String(err);
|
|
58219
|
-
const
|
|
58685
|
+
const sandboxImage = resolveSandboxImage(config2, project3);
|
|
58686
|
+
const missingBinary = missingAgentBinaryMessage(err, model.runtime, sandboxImage) ?? missingSetupBinaryMessage(err, setupCommand, sandboxImage);
|
|
58220
58687
|
const salvaged = await branchCommitsAhead(project3.repoPath, feature.branch, tempBranch);
|
|
58221
58688
|
if (salvaged.length > 0)
|
|
58222
58689
|
baseBranch = tempBranch;
|
|
58223
58690
|
if (ticketAbort.signal.aborted) {
|
|
58224
58691
|
if (salvaged.length > 0)
|
|
58225
58692
|
preserveChain(tempBranch);
|
|
58226
|
-
return
|
|
58227
|
-
status: "failed",
|
|
58228
|
-
error: `stopped by user${salvaged.length > 0 ? ` \u2014 ${salvaged.length} commit(s) preserved; retry to continue from them` : ""}`,
|
|
58229
|
-
event: {
|
|
58230
|
-
type: "ticket.stopped",
|
|
58231
|
-
message: `ticket ${ticket.seq} stopped by user${salvaged.length > 0 ? ` \u2014 ${salvaged.length} commit(s) preserved for retry` : ""}`
|
|
58232
|
-
}
|
|
58233
|
-
};
|
|
58693
|
+
return stoppedOutcome(salvaged.length);
|
|
58234
58694
|
}
|
|
58235
58695
|
if (isWorktreeTeardownError(err)) {
|
|
58236
58696
|
const removed = await discardWorktree(tempBranch);
|
|
@@ -58261,9 +58721,10 @@ ${retryNotes}` : basePrompt,
|
|
|
58261
58721
|
if (missingBinary) {
|
|
58262
58722
|
if (salvaged.length > 0)
|
|
58263
58723
|
preserveChain(tempBranch);
|
|
58264
|
-
return { status: "failed", error: missingBinary };
|
|
58724
|
+
return { status: "failed", error: missingBinary, runFatal: { runtime: model.runtime } };
|
|
58265
58725
|
}
|
|
58266
|
-
|
|
58726
|
+
const verdict = classifyTicketRunError(err, model.runtime, setupCommand);
|
|
58727
|
+
if (verdict === "retryable" && attempt < maxAttempts) {
|
|
58267
58728
|
const headline = errorHeadline(msg);
|
|
58268
58729
|
retryNotes = buildRetryNotes({ error: headline, commitCount: salvaged.length });
|
|
58269
58730
|
ctx.emitEvent({
|
|
@@ -58283,20 +58744,17 @@ ${retryNotes}` : basePrompt,
|
|
|
58283
58744
|
if (ticketAbort.signal.aborted) {
|
|
58284
58745
|
if (salvaged.length > 0)
|
|
58285
58746
|
preserveChain(tempBranch);
|
|
58286
|
-
return
|
|
58287
|
-
status: "failed",
|
|
58288
|
-
error: `stopped by user${salvaged.length > 0 ? ` \u2014 ${salvaged.length} commit(s) preserved; retry to continue from them` : ""}`,
|
|
58289
|
-
event: {
|
|
58290
|
-
type: "ticket.stopped",
|
|
58291
|
-
message: `ticket ${ticket.seq} stopped by user${salvaged.length > 0 ? ` \u2014 ${salvaged.length} commit(s) preserved for retry` : ""}`
|
|
58292
|
-
}
|
|
58293
|
-
};
|
|
58747
|
+
return stoppedOutcome(salvaged.length);
|
|
58294
58748
|
}
|
|
58295
58749
|
continue;
|
|
58296
58750
|
}
|
|
58297
58751
|
if (salvaged.length > 0)
|
|
58298
58752
|
preserveChain(tempBranch);
|
|
58299
|
-
return {
|
|
58753
|
+
return {
|
|
58754
|
+
status: "failed",
|
|
58755
|
+
error: msg,
|
|
58756
|
+
...verdict === "run-fatal" ? { runFatal: { runtime: model.runtime } } : {}
|
|
58757
|
+
};
|
|
58300
58758
|
}
|
|
58301
58759
|
}
|
|
58302
58760
|
if (!result)
|
|
@@ -58340,7 +58798,7 @@ function resolveBurnDeps(ctx) {
|
|
|
58340
58798
|
const exec3 = createSystemExec({ cwd: ctx.project.repoPath });
|
|
58341
58799
|
const imageProbeCache = new Map;
|
|
58342
58800
|
const cachedExec = (command, args2) => {
|
|
58343
|
-
const key =
|
|
58801
|
+
const key = [resolveSandboxImage(config2, ctx.project), model.runtime, command, ...args2].join("\x00");
|
|
58344
58802
|
let result = imageProbeCache.get(key);
|
|
58345
58803
|
if (!result) {
|
|
58346
58804
|
result = exec3(command, args2);
|
|
@@ -58358,7 +58816,7 @@ function resolveBurnDeps(ctx) {
|
|
|
58358
58816
|
const engine = config2.sandbox === "podman" ? "podman" : "docker";
|
|
58359
58817
|
return cacheVolumeReady ??= ensureBurnCacheVolume({
|
|
58360
58818
|
engine,
|
|
58361
|
-
imageName: resolveSandboxImage(config2),
|
|
58819
|
+
imageName: resolveSandboxImage(config2, ctx.project),
|
|
58362
58820
|
projectId: ctx.project.id,
|
|
58363
58821
|
exec: exec3
|
|
58364
58822
|
});
|
|
@@ -58392,6 +58850,7 @@ function resolveBurnDeps(ctx) {
|
|
|
58392
58850
|
const { model: ticketModel, token: ticketToken } = ticketCredentials(ticket);
|
|
58393
58851
|
return burnAuthReady(ticketModel.runtime, ticketToken) ? undefined : ticketModel.runtime;
|
|
58394
58852
|
},
|
|
58853
|
+
ticketRuntime: (ticket) => ticketCredentials(ticket).model.runtime,
|
|
58395
58854
|
concurrency: config2.burnConcurrency,
|
|
58396
58855
|
executeTicketRun: (c, ticket, run7) => {
|
|
58397
58856
|
const { model: ticketModel, token: ticketToken } = ticketCredentials(ticket);
|
|
@@ -58408,9 +58867,9 @@ function resolveBurnDeps(ctx) {
|
|
|
58408
58867
|
function readTokenFromEnvFile(path2, runtime5) {
|
|
58409
58868
|
const key = RUNTIME_AUTH_KEY[runtime5];
|
|
58410
58869
|
let fromFile;
|
|
58411
|
-
if (
|
|
58870
|
+
if (existsSync18(path2)) {
|
|
58412
58871
|
try {
|
|
58413
|
-
fromFile = parseEnvFile3(
|
|
58872
|
+
fromFile = parseEnvFile3(readFileSync10(path2, "utf8"))[key];
|
|
58414
58873
|
} catch {
|
|
58415
58874
|
fromFile = undefined;
|
|
58416
58875
|
}
|
|
@@ -58418,7 +58877,7 @@ function readTokenFromEnvFile(path2, runtime5) {
|
|
|
58418
58877
|
const token = fromFile && fromFile.length > 0 ? fromFile : process.env[key];
|
|
58419
58878
|
return token && token.length > 0 ? token : undefined;
|
|
58420
58879
|
}
|
|
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;
|
|
58880
|
+
var ATTACHMENTS_PREFIX, AUTH_MISSING_EVENT = "auth.missing", IMAGE_RUNTIME_MISSING_EVENT = "burn.image_runtime_missing", RUNTIME_BINARY, COMMAND_SEPARATORS, ASSIGNMENT_PREFIX, NON_COMMAND_WORDS, PLAIN_COMMAND_NAME, 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, REFUSAL_STATUS, DETACHED_FORBIDDEN, CREDENTIAL_SUBJECT, ACCOUNT_PERMISSION_DENIED, RUN_FATAL_ERROR_PATTERNS, REFUSED_CREDENTIAL_STATUS, RUNTIME_RUN_FATAL_ERROR_PATTERNS, FATAL_ERROR_PATTERNS, RUNTIME_FATAL_ERROR_PATTERNS, RETRYABLE_ERROR_PATTERNS, RUNTIME_RETRYABLE_ERROR_PATTERNS, AGENT_BINARY, MISSING_COMMAND_PATTERNS, activeTicketAborts, RunHaltedAbort, NOTHING_LANDED = "nothing landed \u2014 every implementation ticket in the run was cancelled", SETUP_HOOK_TIMEOUT_MS, ticketBurner;
|
|
58422
58881
|
var init_ticket_burner = __esm(() => {
|
|
58423
58882
|
init_src();
|
|
58424
58883
|
init_config_load();
|
|
@@ -58443,6 +58902,26 @@ var init_ticket_burner = __esm(() => {
|
|
|
58443
58902
|
"claude-code": "claude",
|
|
58444
58903
|
codex: "codex"
|
|
58445
58904
|
};
|
|
58905
|
+
COMMAND_SEPARATORS = /&&|\|\||[;|\n]/;
|
|
58906
|
+
ASSIGNMENT_PREFIX = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
58907
|
+
NON_COMMAND_WORDS = new Set([
|
|
58908
|
+
"cd",
|
|
58909
|
+
"export",
|
|
58910
|
+
"set",
|
|
58911
|
+
"unset",
|
|
58912
|
+
"source",
|
|
58913
|
+
".",
|
|
58914
|
+
"exec",
|
|
58915
|
+
"env",
|
|
58916
|
+
"eval",
|
|
58917
|
+
"command",
|
|
58918
|
+
"time",
|
|
58919
|
+
"sudo",
|
|
58920
|
+
"echo",
|
|
58921
|
+
"true",
|
|
58922
|
+
"false"
|
|
58923
|
+
]);
|
|
58924
|
+
PLAIN_COMMAND_NAME = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
|
|
58446
58925
|
DROPPED_MAP_SECTIONS = ["Not yet specified", "Out of scope"];
|
|
58447
58926
|
PACKAGE_MANAGERS = ["bun", "pnpm", "yarn", "npm"];
|
|
58448
58927
|
PM_CACHE_SANDBOX_PATHS = {
|
|
@@ -58486,27 +58965,42 @@ var init_ticket_burner = __esm(() => {
|
|
|
58486
58965
|
/is not a working tree/i,
|
|
58487
58966
|
/resource busy|ebusy|eperm|eacces/i
|
|
58488
58967
|
];
|
|
58489
|
-
|
|
58968
|
+
REFUSAL_STATUS = String.raw`\b(?:401|403)\b`;
|
|
58969
|
+
DETACHED_FORBIDDEN = String.raw`(?<!${REFUSAL_STATUS}[\W_]*)forbidden(?![\W_]*${REFUSAL_STATUS})`;
|
|
58970
|
+
CREDENTIAL_SUBJECT = String.raw`(?:api[ _-]?key|token|credential|login|auth(?:entic\w*|ori[sz]\w*|[nz])?\b|unauthorized|${DETACHED_FORBIDDEN}|account|organi[sz]ation|\borg\b)`;
|
|
58971
|
+
ACCOUNT_PERMISSION_DENIED = refusalNamingCredential(String.raw`permission denied`);
|
|
58972
|
+
RUN_FATAL_ERROR_PATTERNS = [
|
|
58490
58973
|
/invalid (api key|x-api-key)/i,
|
|
58491
|
-
/authentication|unauthorized
|
|
58974
|
+
/authentication|unauthorized/i,
|
|
58975
|
+
ACCOUNT_PERMISSION_DENIED,
|
|
58492
58976
|
/credit balance|billing/i,
|
|
58493
58977
|
/oauth token|setup-token/i,
|
|
58494
|
-
/
|
|
58495
|
-
/does not support resumeSession|resumeSession .* not found/i
|
|
58978
|
+
/usage limit/i
|
|
58496
58979
|
];
|
|
58497
|
-
|
|
58980
|
+
REFUSED_CREDENTIAL_STATUS = refusalNamingCredential(String.raw`(?:${REFUSAL_STATUS}|forbidden)`);
|
|
58981
|
+
RUNTIME_RUN_FATAL_ERROR_PATTERNS = {
|
|
58498
58982
|
"claude-code": [],
|
|
58499
58983
|
codex: [
|
|
58500
|
-
/invalid_api_key
|
|
58501
|
-
|
|
58984
|
+
/invalid_api_key/i,
|
|
58985
|
+
REFUSED_CREDENTIAL_STATUS,
|
|
58502
58986
|
/insufficient_quota|exceeded your current quota/i,
|
|
58503
|
-
/model_not_found|does not exist or you do not have access/i,
|
|
58504
58987
|
/CODEX_API_KEY/,
|
|
58505
58988
|
/not logged in/i,
|
|
58506
58989
|
/\bauth (failed|required)\b/i,
|
|
58507
58990
|
/refresh token/i
|
|
58508
58991
|
]
|
|
58509
58992
|
};
|
|
58993
|
+
FATAL_ERROR_PATTERNS = [
|
|
58994
|
+
/issue with the selected model|model not found|unknown model/i,
|
|
58995
|
+
/does not support resumeSession|resumeSession .* not found/i
|
|
58996
|
+
];
|
|
58997
|
+
RUNTIME_FATAL_ERROR_PATTERNS = {
|
|
58998
|
+
"claude-code": [],
|
|
58999
|
+
codex: [
|
|
59000
|
+
/invalid_request_error/i,
|
|
59001
|
+
/model_not_found|does not exist or you do not have access/i
|
|
59002
|
+
]
|
|
59003
|
+
};
|
|
58510
59004
|
RETRYABLE_ERROR_PATTERNS = [
|
|
58511
59005
|
/exited with code \d+/i,
|
|
58512
59006
|
/idle timeout|AgentIdleTimeout/i,
|
|
@@ -58525,7 +59019,19 @@ var init_ticket_burner = __esm(() => {
|
|
|
58525
59019
|
"claude-code": "claude",
|
|
58526
59020
|
codex: "codex"
|
|
58527
59021
|
};
|
|
59022
|
+
MISSING_COMMAND_PATTERNS = [
|
|
59023
|
+
/\bexit(?:ed with)?(?: code)? 127\b/i,
|
|
59024
|
+
/command not found/i
|
|
59025
|
+
];
|
|
58528
59026
|
activeTicketAborts = new Map;
|
|
59027
|
+
RunHaltedAbort = class RunHaltedAbort extends Error {
|
|
59028
|
+
headline;
|
|
59029
|
+
constructor(headline) {
|
|
59030
|
+
super(`ticket stopped: run halted (${headline})`);
|
|
59031
|
+
this.headline = headline;
|
|
59032
|
+
this.name = "RunHaltedAbort";
|
|
59033
|
+
}
|
|
59034
|
+
};
|
|
58529
59035
|
SETUP_HOOK_TIMEOUT_MS = 15 * 60000;
|
|
58530
59036
|
ticketBurner = {
|
|
58531
59037
|
id: "ticket-burner",
|
|
@@ -58534,9 +59040,9 @@ var init_ticket_burner = __esm(() => {
|
|
|
58534
59040
|
});
|
|
58535
59041
|
|
|
58536
59042
|
// src/workflows/research.ts
|
|
58537
|
-
import { mkdirSync as mkdirSync11, readFileSync as
|
|
58538
|
-
import { dirname as dirname11, join as
|
|
58539
|
-
import { fileURLToPath as
|
|
59043
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync11 } from "fs";
|
|
59044
|
+
import { dirname as dirname11, join as join19 } from "path";
|
|
59045
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
58540
59046
|
function waypointSlug(waypoint) {
|
|
58541
59047
|
const base = waypoint.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
58542
59048
|
return `${waypoint.seq}-${base || "waypoint"}`;
|
|
@@ -58603,8 +59109,8 @@ function interpretResearchResult(result, docRelPath) {
|
|
|
58603
59109
|
return { status: "failed", error: "agent produced no commits \u2014 no research summary landed" };
|
|
58604
59110
|
}
|
|
58605
59111
|
function researchTemplatePath() {
|
|
58606
|
-
const here = dirname11(
|
|
58607
|
-
return
|
|
59112
|
+
const here = dirname11(fileURLToPath7(import.meta.url));
|
|
59113
|
+
return join19(resolveSkillsRoot(here), "burner", "research-waypoint.md");
|
|
58608
59114
|
}
|
|
58609
59115
|
function researchContainerName(runId, seq2) {
|
|
58610
59116
|
return `runcastle-${runId}-w${seq2}`;
|
|
@@ -58620,7 +59126,7 @@ async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
|
58620
59126
|
const { project: project3, feature } = ctx;
|
|
58621
59127
|
const docRelPath = researchDocRel(feature.slug, waypoint);
|
|
58622
59128
|
const tempBranch = researchBranchName(feature.slug, waypoint.seq, newId("b").slice(2, 10));
|
|
58623
|
-
const template =
|
|
59129
|
+
const template = readFileSync11(researchTemplatePath(), "utf8");
|
|
58624
59130
|
const prompt = renderResearchPrompt(template, {
|
|
58625
59131
|
WAYPOINT_JSON: buildWaypointJson(waypoint),
|
|
58626
59132
|
FEATURE_BRIEF: buildFeatureBrief(feature),
|
|
@@ -58628,12 +59134,12 @@ async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
|
58628
59134
|
RESEARCH_DOC_PATH: docRelPath
|
|
58629
59135
|
});
|
|
58630
59136
|
mkdirSync11(logsDir(), { recursive: true });
|
|
58631
|
-
const logFilePath =
|
|
59137
|
+
const logFilePath = join19(logsDir(), `research-${feature.id}-${waypoint.seq}.log`);
|
|
58632
59138
|
const throttle = createResearchStreamThrottle((e) => ctx.emitEvent(e));
|
|
58633
59139
|
const codexAuthMount = codexAuthMountFor(model.runtime, config2.sandbox);
|
|
58634
59140
|
const runOptions = {
|
|
58635
59141
|
agent: buildBurnAgent(config2, token, model),
|
|
58636
|
-
sandbox: selectSandbox(config2, codexAuthMount ? [codexAuthMount] : [], {}, registerResearchKill(config2, ctx.runId, waypoint.seq)),
|
|
59142
|
+
sandbox: selectSandbox(config2, project3, codexAuthMount ? [codexAuthMount] : [], {}, registerResearchKill(config2, ctx.runId, waypoint.seq)),
|
|
58637
59143
|
cwd: project3.repoPath,
|
|
58638
59144
|
prompt,
|
|
58639
59145
|
branchStrategy: { type: "branch", branch: tempBranch, baseBranch: feature.branch },
|
|
@@ -59234,16 +59740,30 @@ async function rethinkAndLaunch(ctx, featureId, launch3) {
|
|
|
59234
59740
|
function errMsg2(e) {
|
|
59235
59741
|
return e instanceof Error ? e.message : String(e);
|
|
59236
59742
|
}
|
|
59743
|
+
function latestRunDeniedDirty(ctx, featureId) {
|
|
59744
|
+
const latestRun = listRunsByFeature(ctx, featureId)[0];
|
|
59745
|
+
if (!latestRun)
|
|
59746
|
+
return false;
|
|
59747
|
+
const deniedAt = latestEventTs(ctx, featureId, "reviewdrive.denied");
|
|
59748
|
+
return deniedAt !== undefined && deniedAt >= latestRun.startedAt;
|
|
59749
|
+
}
|
|
59237
59750
|
async function retryTicket(ctx, ticketId, opts = {}) {
|
|
59238
59751
|
const ticket = getTicket(ctx, ticketId);
|
|
59239
59752
|
const feature = getFeatureRow(ctx, ticket.featureId);
|
|
59240
|
-
|
|
59753
|
+
const retryingDeniedReview = ticket.kind === "review" && ticket.status === "done" && latestRunDeniedDirty(ctx, feature.id);
|
|
59754
|
+
if (ticket.status !== "failed" && !retryingDeniedReview) {
|
|
59241
59755
|
throw new GateError(`only failed tickets can be retried \u2014 ticket ${ticket.seq} is ${ticket.status}`);
|
|
59242
59756
|
}
|
|
59243
59757
|
if (hasActiveRun(ctx, feature.id)) {
|
|
59244
59758
|
throw new GateError("a run is live for this feature \u2014 retry after it finishes, or cancel it first");
|
|
59245
59759
|
}
|
|
59246
59760
|
const project3 = projectForFeature(ctx, feature);
|
|
59761
|
+
if (retryingDeniedReview) {
|
|
59762
|
+
const stillDirty = await driveBlockingPaths(project3.repoPath);
|
|
59763
|
+
if (stillDirty.length > 0) {
|
|
59764
|
+
throw new GateError(`the working tree is still dirty \u2014 the review drive would be denied again. Commit or ` + `discard ${stillDirty.length} file(s) first: ${truncatedFileList(stillDirty)}`);
|
|
59765
|
+
}
|
|
59766
|
+
}
|
|
59247
59767
|
const all8 = listByFeature2(ctx, feature.id);
|
|
59248
59768
|
const bySeq = new Map(all8.map((t) => [t.seq, t]));
|
|
59249
59769
|
const toReset = new Map([[ticket.seq, ticket]]);
|
|
@@ -59425,7 +59945,7 @@ var init_features = __esm(() => {
|
|
|
59425
59945
|
});
|
|
59426
59946
|
|
|
59427
59947
|
// src/services/test-notes.ts
|
|
59428
|
-
import { existsSync as
|
|
59948
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync12, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
59429
59949
|
import { dirname as dirname12 } from "path";
|
|
59430
59950
|
import { and as and10, asc as asc5, eq as eq13, lt } from "drizzle-orm";
|
|
59431
59951
|
function rowToNote(row) {
|
|
@@ -59440,7 +59960,7 @@ function rowToNote(row) {
|
|
|
59440
59960
|
reviewTicketId: row.reviewTicketId ?? undefined,
|
|
59441
59961
|
carriedLap: row.carriedLap ?? undefined,
|
|
59442
59962
|
videoTimestamp: row.videoTimestamp ?? undefined,
|
|
59443
|
-
screenshotUrl:
|
|
59963
|
+
screenshotUrl: existsSync20(annotationPath(row.id)) ? noteScreenshotUrl(row.id) : undefined,
|
|
59444
59964
|
createdAt: row.createdAt,
|
|
59445
59965
|
updatedAt: row.updatedAt
|
|
59446
59966
|
});
|
|
@@ -59710,8 +60230,8 @@ var init_test_notes = __esm(() => {
|
|
|
59710
60230
|
});
|
|
59711
60231
|
|
|
59712
60232
|
// src/mcp/server.ts
|
|
59713
|
-
import { existsSync as
|
|
59714
|
-
import { join as
|
|
60233
|
+
import { existsSync as existsSync21, readFileSync as readFileSync12 } from "fs";
|
|
60234
|
+
import { join as join20, relative as relative4, resolve as resolve10, sep as sep2 } from "path";
|
|
59715
60235
|
import { StreamableHTTPTransport } from "@hono/mcp";
|
|
59716
60236
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
59717
60237
|
import { Hono } from "hono";
|
|
@@ -59796,6 +60316,7 @@ function featureContext(ctx, reader) {
|
|
|
59796
60316
|
moreDocs,
|
|
59797
60317
|
docsNote: DOCS_NOTE,
|
|
59798
60318
|
openDefects: carried.openDefects,
|
|
60319
|
+
carriedDefects: carried.carriedDefects,
|
|
59799
60320
|
tickets: listByFeature2(ctx, feature.id).map(stripDigest),
|
|
59800
60321
|
annotatedModels: annotatedModels(ctx)
|
|
59801
60322
|
};
|
|
@@ -59852,7 +60373,13 @@ function toolEmitTickets(ctx, session, input) {
|
|
|
59852
60373
|
const feature = getFeatureRow(ctx, requireFeatureId(session));
|
|
59853
60374
|
refuseIfReadOnly(session, "emitting tickets");
|
|
59854
60375
|
refuseMisKindedReview(input.tickets);
|
|
60376
|
+
const linkedFindingIds = input.tickets.flatMap((t) => t.originFindingId ?? []);
|
|
60377
|
+
requireLinkableFindings(ctx, feature.id, linkedFindingIds);
|
|
59855
60378
|
const stored = storeTickets(ctx, feature.id, input.tickets);
|
|
60379
|
+
for (const ticket of stored) {
|
|
60380
|
+
if (ticket.originFindingId)
|
|
60381
|
+
linkFixTicket(ctx, feature.id, ticket.originFindingId, ticket.id);
|
|
60382
|
+
}
|
|
59856
60383
|
return {
|
|
59857
60384
|
stored: stored.length,
|
|
59858
60385
|
tickets: stored.map((t) => ({ id: t.id, seq: t.seq, title: t.title }))
|
|
@@ -59877,6 +60404,13 @@ function toolCancelTicket(ctx, session, input) {
|
|
|
59877
60404
|
refuseIfReadOnly(session, "cancelling a ticket");
|
|
59878
60405
|
return { ok: true, ticket: cancelTicket(ctx, input.id, input.reason) };
|
|
59879
60406
|
}
|
|
60407
|
+
function toolResolveFinding(ctx, session, input) {
|
|
60408
|
+
const featureId = requireFeatureId(session);
|
|
60409
|
+
refuseIfReadOnly(session, "resolving a finding");
|
|
60410
|
+
const { findingId, disposition, note: note2 } = ResolveFindingInput.parse(input);
|
|
60411
|
+
const finding = disposition === "carry" ? carryFinding(ctx, featureId, findingId, note2) : closeAsAddressed(ctx, featureId, findingId, note2 ?? "");
|
|
60412
|
+
return { ok: true, finding };
|
|
60413
|
+
}
|
|
59880
60414
|
function toolEscalateToMap(ctx, session, input) {
|
|
59881
60415
|
return escalateToMap(ctx, requireFeatureId(session), input);
|
|
59882
60416
|
}
|
|
@@ -60143,16 +60677,16 @@ function adrRef(adr) {
|
|
|
60143
60677
|
}
|
|
60144
60678
|
function toolReadAdr(ctx, session, input) {
|
|
60145
60679
|
requireProject(ctx, session);
|
|
60146
|
-
const root = resolve10(
|
|
60680
|
+
const root = resolve10(join20(session.worktreePath, ADR_DIR_REL));
|
|
60147
60681
|
const name = input.relPath.replace(/\\/g, "/").replace(/^\.?\/*docs\/adr\//i, "");
|
|
60148
60682
|
const target = resolve10(root, name);
|
|
60149
60683
|
const rel = relative4(root, target);
|
|
60150
60684
|
if (rel === "" || rel.startsWith("..") || rel.split(sep2).includes("..")) {
|
|
60151
60685
|
throw new InvalidInputError(`adr path escapes ${ADR_DIR_REL}: ${input.relPath}`);
|
|
60152
60686
|
}
|
|
60153
|
-
if (!
|
|
60687
|
+
if (!existsSync21(target))
|
|
60154
60688
|
throw new NotFoundError(`adr not found: ${input.relPath}`);
|
|
60155
|
-
return { relPath: `${ADR_DIR_REL}/${name}`, content:
|
|
60689
|
+
return { relPath: `${ADR_DIR_REL}/${name}`, content: readFileSync12(target, "utf8") };
|
|
60156
60690
|
}
|
|
60157
60691
|
function featureIndexLine(feature) {
|
|
60158
60692
|
if (feature.status === "shipped") {
|
|
@@ -60291,7 +60825,7 @@ function buildMcpServer(audience) {
|
|
|
60291
60825
|
if (wants("review_drive")) {
|
|
60292
60826
|
server.registerTool("review_drive", {
|
|
60293
60827
|
title: "Drive the feature branch for review",
|
|
60294
|
-
description: "Boot the integrated feature branch on the human's checkout, under your run's " + "identity, so you can review it. ALWAYS stop what you started, including when the " + "review goes wrong: the drive holds a machine-wide slot and the human cannot use their " + "own checkout until you release it.
|
|
60828
|
+
description: "Boot the integrated feature branch on the human's checkout, under your run's " + "identity, so you can review it. ALWAYS stop what you started, including when the " + "review goes wrong: the drive holds a machine-wide slot and the human cannot use their " + "own checkout until you release it. A refused `start` says which refusal it is: " + '`deniedCode: "slot_held"` (`retriable: true`) means somebody else \u2014 a drive or a ' + "preparation dry run \u2014 holds the machine-wide slot, which frees itself when they " + "finish, so call `start` again about ten times roughly thirty seconds apart before you " + 'give up on it. `deniedCode: "dirty"` (`retriable: false`) is final \u2014 the uncommitted ' + "files in `dirtyFiles` are the human's to clear and no wait will do it, so report it " + "and review without the app. Refused unless your call carries a live run identity.",
|
|
60295
60829
|
inputSchema: {
|
|
60296
60830
|
action: z4.enum(["start", "status", "stop"]).describe("`start` switches the checkout to the feature branch, renders driveEnv (per-branch " + "database), runs driveSetupCommand and spawns devCommand. `status` reports the " + "drive and hands back the localhost URL once the dev server has printed one \u2014 " + "poll it, the URL is not ready at start. `stop` tears the environment down and " + "puts the checkout back.")
|
|
60297
60831
|
}
|
|
@@ -60511,6 +61045,18 @@ function buildMcpServer(audience) {
|
|
|
60511
61045
|
return ok(result);
|
|
60512
61046
|
});
|
|
60513
61047
|
}
|
|
61048
|
+
if (wants("resolve_finding")) {
|
|
61049
|
+
server.registerTool("resolve_finding", {
|
|
61050
|
+
title: "Resolve a review finding",
|
|
61051
|
+
description: "Say what this lap did about a defect an earlier lap\u2019s review left open: `carry` it into " + "a later lap, or close it as `addressed` because this lap\u2019s work already answers it. To " + "link it instead, emit the ticket that fixes it with `originFindingId` set \u2014 the burn " + "then closes the finding itself when that ticket lands. Every earlier-lap open defect " + 'must be linked, carried or closed before `complete_phase("tickets")` will pass.',
|
|
61052
|
+
inputSchema: ResolveFindingShape
|
|
61053
|
+
}, async (args2, extra) => {
|
|
61054
|
+
const rs = await resolveCtxSession(extra);
|
|
61055
|
+
if (!rs)
|
|
61056
|
+
return noSession();
|
|
61057
|
+
return ok(toolResolveFinding(rs.ctx, rs.session, args2));
|
|
61058
|
+
});
|
|
61059
|
+
}
|
|
60514
61060
|
if (wants("escalate_to_map")) {
|
|
60515
61061
|
server.registerTool("escalate_to_map", {
|
|
60516
61062
|
title: "Escalate to map",
|
|
@@ -60607,7 +61153,23 @@ async function resolveAudience(sessionId, runId) {
|
|
|
60607
61153
|
return;
|
|
60608
61154
|
}
|
|
60609
61155
|
}
|
|
60610
|
-
|
|
61156
|
+
async function readMcpBody(c) {
|
|
61157
|
+
if (c.req.method !== "POST")
|
|
61158
|
+
return;
|
|
61159
|
+
let text3;
|
|
61160
|
+
try {
|
|
61161
|
+
text3 = await c.req.text();
|
|
61162
|
+
} catch (e) {
|
|
61163
|
+
console.error(`[mcp] request body read failed after ${c.req.header("content-length") ?? "unknown"} ` + `declared bytes: ${e instanceof Error ? e.message : String(e)}`);
|
|
61164
|
+
return;
|
|
61165
|
+
}
|
|
61166
|
+
try {
|
|
61167
|
+
return JSON.parse(text3);
|
|
61168
|
+
} catch {
|
|
61169
|
+
return;
|
|
61170
|
+
}
|
|
61171
|
+
}
|
|
61172
|
+
var DOCS_NOTE, REVIEW_TITLE, ResolveFindingShape, ResolveFindingInput, DRAFTING_KINDS, ADRS_NOTE, FEATURE_KINDS, FEATURE_WRITE_KINDS, PROJECT_KINDS, ALL_AUDIENCES, TOOL_AUDIENCES, CompletablePhase, EventType, mcp, server_default;
|
|
60611
61173
|
var init_server = __esm(() => {
|
|
60612
61174
|
init_src();
|
|
60613
61175
|
init_paths();
|
|
@@ -60628,6 +61190,20 @@ var init_server = __esm(() => {
|
|
|
60628
61190
|
init_waypoints();
|
|
60629
61191
|
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
61192
|
REVIEW_TITLE = /^review[: ]/i;
|
|
61193
|
+
ResolveFindingShape = {
|
|
61194
|
+
findingId: z4.string().min(1).describe("The finding id, from `get_feature_context`\u2019s openDefects or carriedDefects."),
|
|
61195
|
+
disposition: z4.enum(["carry", "addressed"]).describe("`carry` parks it for a later lap \u2014 out of the open count, still visible, and the human can " + "reopen it. `addressed` closes it because this lap\u2019s work already answers it."),
|
|
61196
|
+
note: z4.string().optional().describe('REQUIRED for `addressed`: what addressed it, e.g. "lap 2\u2019s ticket 7 rewrote the endpoint". ' + "Optional for `carry`, where it records why it was parked.")
|
|
61197
|
+
};
|
|
61198
|
+
ResolveFindingInput = z4.object(ResolveFindingShape).superRefine((input, refinement) => {
|
|
61199
|
+
if (input.disposition === "addressed" && !input.note?.trim()) {
|
|
61200
|
+
refinement.addIssue({
|
|
61201
|
+
code: "custom",
|
|
61202
|
+
path: ["note"],
|
|
61203
|
+
message: "closing a defect as addressed requires a note saying what addressed it"
|
|
61204
|
+
});
|
|
61205
|
+
}
|
|
61206
|
+
});
|
|
60631
61207
|
DRAFTING_KINDS = ["ideation", "revisit", "waypoint", "converge"];
|
|
60632
61208
|
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/.";
|
|
60633
61209
|
FEATURE_KINDS = [
|
|
@@ -60649,6 +61225,7 @@ var init_server = __esm(() => {
|
|
|
60649
61225
|
update_ticket: FEATURE_WRITE_KINDS,
|
|
60650
61226
|
cancel_ticket: FEATURE_WRITE_KINDS,
|
|
60651
61227
|
complete_phase: FEATURE_WRITE_KINDS,
|
|
61228
|
+
resolve_finding: FEATURE_WRITE_KINDS,
|
|
60652
61229
|
escalate_to_map: FEATURE_KINDS,
|
|
60653
61230
|
emit_waypoints: FEATURE_KINDS,
|
|
60654
61231
|
resolve_waypoint: FEATURE_KINDS,
|
|
@@ -60674,11 +61251,12 @@ var init_server = __esm(() => {
|
|
|
60674
61251
|
}
|
|
60675
61252
|
});
|
|
60676
61253
|
mcp.all("*", async (c) => {
|
|
61254
|
+
const body = await readMcpBody(c);
|
|
60677
61255
|
const audience = await resolveAudience(c.req.header("X-Runcastle-Session"), c.req.header(RUN_HEADER));
|
|
60678
61256
|
const server = buildMcpServer(audience);
|
|
60679
61257
|
const transport = new StreamableHTTPTransport({ enableJsonResponse: true });
|
|
60680
61258
|
await server.connect(transport);
|
|
60681
|
-
const res = await transport.handleRequest(c);
|
|
61259
|
+
const res = await transport.handleRequest(c, body);
|
|
60682
61260
|
return res ?? c.body(null, 202);
|
|
60683
61261
|
});
|
|
60684
61262
|
server_default = mcp;
|
|
@@ -61019,12 +61597,12 @@ var init_hooks = __esm(() => {
|
|
|
61019
61597
|
});
|
|
61020
61598
|
|
|
61021
61599
|
// src/routes/reviews.ts
|
|
61022
|
-
import { createReadStream, statSync as
|
|
61600
|
+
import { createReadStream, statSync as statSync5 } from "fs";
|
|
61023
61601
|
import { Readable } from "stream";
|
|
61024
61602
|
import { Hono as Hono3 } from "hono";
|
|
61025
61603
|
function fileSize(path2) {
|
|
61026
61604
|
try {
|
|
61027
|
-
const stat3 =
|
|
61605
|
+
const stat3 = statSync5(path2);
|
|
61028
61606
|
return stat3.isFile() ? stat3.size : undefined;
|
|
61029
61607
|
} catch {
|
|
61030
61608
|
return;
|
|
@@ -61211,15 +61789,15 @@ var init_stream = __esm(() => {
|
|
|
61211
61789
|
});
|
|
61212
61790
|
|
|
61213
61791
|
// src/routes/web.ts
|
|
61214
|
-
import { existsSync as
|
|
61792
|
+
import { existsSync as existsSync22, statSync as statSync6 } from "fs";
|
|
61215
61793
|
import { readFile as readFile4 } from "fs/promises";
|
|
61216
|
-
import { extname, join as
|
|
61794
|
+
import { extname, join as join21, normalize as normalize2, resolve as resolve11, sep as sep3 } from "path";
|
|
61217
61795
|
function contentType(path2) {
|
|
61218
61796
|
return MIME[extname(path2).toLowerCase()] ?? "application/octet-stream";
|
|
61219
61797
|
}
|
|
61220
61798
|
function isFile2(path2) {
|
|
61221
61799
|
try {
|
|
61222
|
-
return
|
|
61800
|
+
return statSync6(path2).isFile();
|
|
61223
61801
|
} catch {
|
|
61224
61802
|
return false;
|
|
61225
61803
|
}
|
|
@@ -61231,7 +61809,7 @@ function safeJoin(root, pathname) {
|
|
|
61231
61809
|
} catch {
|
|
61232
61810
|
return null;
|
|
61233
61811
|
}
|
|
61234
|
-
const joined = normalize2(
|
|
61812
|
+
const joined = normalize2(join21(root, decoded));
|
|
61235
61813
|
if (joined !== root && !joined.startsWith(root + sep3))
|
|
61236
61814
|
return null;
|
|
61237
61815
|
return joined;
|
|
@@ -61244,7 +61822,7 @@ function resolveWebDist() {
|
|
|
61244
61822
|
}
|
|
61245
61823
|
function mountWebApp(app, distDir) {
|
|
61246
61824
|
const root = resolve11(distDir);
|
|
61247
|
-
const indexHtml =
|
|
61825
|
+
const indexHtml = join21(root, "index.html");
|
|
61248
61826
|
app.get("*", async (c, next4) => {
|
|
61249
61827
|
const { pathname } = new URL(c.req.url);
|
|
61250
61828
|
if (/^\/(api|mcp|ws|health)(\/|$)/.test(pathname))
|
|
@@ -61264,7 +61842,7 @@ function mountWebApp(app, distDir) {
|
|
|
61264
61842
|
}
|
|
61265
61843
|
function mountWebAppIfBuilt(app) {
|
|
61266
61844
|
const dist = resolveWebDist();
|
|
61267
|
-
if (
|
|
61845
|
+
if (existsSync22(join21(dist, "index.html")))
|
|
61268
61846
|
mountWebApp(app, dist);
|
|
61269
61847
|
}
|
|
61270
61848
|
var MIME;
|
|
@@ -61291,16 +61869,16 @@ var init_web = __esm(() => {
|
|
|
61291
61869
|
});
|
|
61292
61870
|
|
|
61293
61871
|
// src/version.ts
|
|
61294
|
-
import { readFileSync as
|
|
61295
|
-
import { dirname as dirname13, join as
|
|
61296
|
-
import { fileURLToPath as
|
|
61872
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
61873
|
+
import { dirname as dirname13, join as join23 } from "path";
|
|
61874
|
+
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
61297
61875
|
function runcastleVersion() {
|
|
61298
61876
|
if (cached4)
|
|
61299
61877
|
return cached4;
|
|
61300
|
-
const here = dirname13(
|
|
61301
|
-
for (const pkgPath of [
|
|
61878
|
+
const here = dirname13(fileURLToPath8(import.meta.url));
|
|
61879
|
+
for (const pkgPath of [join23(here, "..", "package.json"), join23(here, "package.json")]) {
|
|
61302
61880
|
try {
|
|
61303
|
-
const pkg = JSON.parse(
|
|
61881
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
61304
61882
|
if (typeof pkg.version === "string") {
|
|
61305
61883
|
cached4 = pkg.version;
|
|
61306
61884
|
return cached4;
|
|
@@ -61315,13 +61893,13 @@ var init_version = () => {};
|
|
|
61315
61893
|
|
|
61316
61894
|
// src/services/install-id.ts
|
|
61317
61895
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
61318
|
-
import { existsSync as
|
|
61896
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
61319
61897
|
import { dirname as dirname14 } from "path";
|
|
61320
61898
|
function getInstallId() {
|
|
61321
61899
|
const file = installIdPath();
|
|
61322
61900
|
try {
|
|
61323
|
-
if (
|
|
61324
|
-
const existing =
|
|
61901
|
+
if (existsSync23(file)) {
|
|
61902
|
+
const existing = readFileSync14(file, "utf8").trim();
|
|
61325
61903
|
if (UUID_RE.test(existing))
|
|
61326
61904
|
return existing;
|
|
61327
61905
|
}
|
|
@@ -61520,8 +62098,8 @@ var init_events2 = __esm(() => {
|
|
|
61520
62098
|
});
|
|
61521
62099
|
|
|
61522
62100
|
// src/launcher/launcher.ts
|
|
61523
|
-
import { existsSync as
|
|
61524
|
-
import { join as
|
|
62101
|
+
import { existsSync as existsSync24 } from "fs";
|
|
62102
|
+
import { join as join25 } from "path";
|
|
61525
62103
|
import { and as and12, eq as eq15 } from "drizzle-orm";
|
|
61526
62104
|
function ptyExitMessage(exitCode) {
|
|
61527
62105
|
const label = typeof exitCode === "number" ? String(exitCode) : "unknown";
|
|
@@ -61996,7 +62574,7 @@ async function buildPrepareBrief(ctx, project3) {
|
|
|
61996
62574
|
};
|
|
61997
62575
|
}
|
|
61998
62576
|
function probePrepareHost(repoPath) {
|
|
61999
|
-
const has4 = (rel) =>
|
|
62577
|
+
const has4 = (rel) => existsSync24(join25(repoPath, rel));
|
|
62000
62578
|
const lock = LOCKFILES.find(([file]) => has4(file));
|
|
62001
62579
|
return {
|
|
62002
62580
|
platform: process.platform,
|
|
@@ -62189,7 +62767,7 @@ var init_launcher = __esm(() => {
|
|
|
62189
62767
|
|
|
62190
62768
|
// src/services/outcome.ts
|
|
62191
62769
|
import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync10 } from "fs";
|
|
62192
|
-
import { join as
|
|
62770
|
+
import { join as join26 } from "path";
|
|
62193
62771
|
function digestHasSubstance(text3) {
|
|
62194
62772
|
return text3.split(`
|
|
62195
62773
|
`).some((line) => {
|
|
@@ -62289,9 +62867,9 @@ async function promoteOutcomeDoc(ctx, project3, feature, target, delta) {
|
|
|
62289
62867
|
delta: { commits: delta.commits ?? 0, files: delta.files ?? 0 }
|
|
62290
62868
|
});
|
|
62291
62869
|
await onBranch(project3.repoPath, target, async () => {
|
|
62292
|
-
const docsDir =
|
|
62870
|
+
const docsDir = join26(project3.repoPath, ...featureDocsRel(feature.slug).split("/"));
|
|
62293
62871
|
mkdirSync14(docsDir, { recursive: true });
|
|
62294
|
-
writeFileSync10(
|
|
62872
|
+
writeFileSync10(join26(docsDir, "outcome.md"), doc, "utf8");
|
|
62295
62873
|
await commitDocs(project3.repoPath, `runcastle: outcome for ${feature.slug}`);
|
|
62296
62874
|
});
|
|
62297
62875
|
} catch (error) {
|
|
@@ -62418,9 +62996,9 @@ var init_feature = __esm(() => {
|
|
|
62418
62996
|
});
|
|
62419
62997
|
|
|
62420
62998
|
// src/services/fsbrowse.ts
|
|
62421
|
-
import { existsSync as
|
|
62999
|
+
import { existsSync as existsSync25, readdirSync as readdirSync6, statSync as statSync7 } from "fs";
|
|
62422
63000
|
import { homedir as homedir4 } from "os";
|
|
62423
|
-
import { basename as basename3, dirname as dirname15, isAbsolute as isAbsolute3, join as
|
|
63001
|
+
import { basename as basename3, dirname as dirname15, isAbsolute as isAbsolute3, join as join27, resolve as resolve12, sep as sep4 } from "path";
|
|
62424
63002
|
function expandPath(input, home = homedir4()) {
|
|
62425
63003
|
const trimmed = input.trim();
|
|
62426
63004
|
if (trimmed === "")
|
|
@@ -62429,12 +63007,12 @@ function expandPath(input, home = homedir4()) {
|
|
|
62429
63007
|
if (out === "~")
|
|
62430
63008
|
out = home;
|
|
62431
63009
|
else if (out.startsWith("~/") || out.startsWith("~\\"))
|
|
62432
|
-
out =
|
|
63010
|
+
out = join27(home, out.slice(2));
|
|
62433
63011
|
return resolve12(out);
|
|
62434
63012
|
}
|
|
62435
63013
|
function looksLikeRepo(dir) {
|
|
62436
63014
|
try {
|
|
62437
|
-
return
|
|
63015
|
+
return existsSync25(join27(dir, ".git"));
|
|
62438
63016
|
} catch {
|
|
62439
63017
|
return false;
|
|
62440
63018
|
}
|
|
@@ -62445,20 +63023,20 @@ function listRoots(home = homedir4()) {
|
|
|
62445
63023
|
for (let code = 67;code <= 90; code++) {
|
|
62446
63024
|
const drive = `${String.fromCharCode(code)}:${sep4}`;
|
|
62447
63025
|
try {
|
|
62448
|
-
if (
|
|
63026
|
+
if (existsSync25(drive))
|
|
62449
63027
|
roots3.push({ label: drive.replace(/\\$/, ""), path: drive, kind: "drive" });
|
|
62450
63028
|
} catch {}
|
|
62451
63029
|
}
|
|
62452
63030
|
} else {
|
|
62453
63031
|
roots3.push({ label: "/", path: "/", kind: "drive" });
|
|
62454
63032
|
}
|
|
62455
|
-
if (
|
|
63033
|
+
if (existsSync25(home))
|
|
62456
63034
|
roots3.push({ label: "Home", path: home, kind: "home" });
|
|
62457
63035
|
const seen = new Set(roots3.map(pathKey));
|
|
62458
63036
|
for (const name of COMMON_DIRS) {
|
|
62459
|
-
const candidate =
|
|
63037
|
+
const candidate = join27(home, name);
|
|
62460
63038
|
const key = pathKey({ path: candidate });
|
|
62461
|
-
if (seen.has(key) || !
|
|
63039
|
+
if (seen.has(key) || !existsSync25(candidate))
|
|
62462
63040
|
continue;
|
|
62463
63041
|
seen.add(key);
|
|
62464
63042
|
roots3.push({ label: name, path: candidate, kind: "common" });
|
|
@@ -62489,12 +63067,12 @@ function browseDir(input, showHidden = false, home = homedir4()) {
|
|
|
62489
63067
|
if (!isAbsolute3(target)) {
|
|
62490
63068
|
throw new InvalidInputError(`path is not absolute: ${target}`);
|
|
62491
63069
|
}
|
|
62492
|
-
if (!
|
|
63070
|
+
if (!existsSync25(target)) {
|
|
62493
63071
|
throw new InvalidInputError(`path does not exist: ${target}`);
|
|
62494
63072
|
}
|
|
62495
63073
|
let dir = target;
|
|
62496
63074
|
try {
|
|
62497
|
-
if (!
|
|
63075
|
+
if (!statSync7(target).isDirectory())
|
|
62498
63076
|
dir = dirname15(target);
|
|
62499
63077
|
} catch (e) {
|
|
62500
63078
|
throw new InvalidInputError(`cannot read path: ${target} (${errMsg4(e)})`);
|
|
@@ -62517,14 +63095,14 @@ function browseDir(input, showHidden = false, home = homedir4()) {
|
|
|
62517
63095
|
continue;
|
|
62518
63096
|
if (!showHidden && name.startsWith("."))
|
|
62519
63097
|
continue;
|
|
62520
|
-
const path2 =
|
|
63098
|
+
const path2 = join27(dir, name);
|
|
62521
63099
|
const isSymlink = dirent.isSymbolicLink();
|
|
62522
63100
|
if (isSymlink && !showHidden)
|
|
62523
63101
|
continue;
|
|
62524
63102
|
let isDirectory = dirent.isDirectory();
|
|
62525
63103
|
if (isSymlink) {
|
|
62526
63104
|
try {
|
|
62527
|
-
isDirectory =
|
|
63105
|
+
isDirectory = statSync7(path2).isDirectory();
|
|
62528
63106
|
} catch {
|
|
62529
63107
|
continue;
|
|
62530
63108
|
}
|
|
@@ -62554,7 +63132,7 @@ var init_fsbrowse = __esm(() => {
|
|
|
62554
63132
|
});
|
|
62555
63133
|
|
|
62556
63134
|
// src/services/transcripts.ts
|
|
62557
|
-
import { readFileSync as
|
|
63135
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
62558
63136
|
function* entries(jsonl) {
|
|
62559
63137
|
for (const line of jsonl.split(`
|
|
62560
63138
|
`)) {
|
|
@@ -62635,7 +63213,7 @@ function readTranscript2(path2, runtime5) {
|
|
|
62635
63213
|
if (!path2)
|
|
62636
63214
|
return { status: "ok", turns: [] };
|
|
62637
63215
|
try {
|
|
62638
|
-
return parseTranscript(
|
|
63216
|
+
return parseTranscript(readFileSync15(path2, "utf8"), runtime5);
|
|
62639
63217
|
} catch {
|
|
62640
63218
|
return { status: "ok", turns: [] };
|
|
62641
63219
|
}
|
|
@@ -62748,8 +63326,8 @@ var init_conversations = __esm(() => {
|
|
|
62748
63326
|
});
|
|
62749
63327
|
|
|
62750
63328
|
// src/services/projects.ts
|
|
62751
|
-
import { existsSync as
|
|
62752
|
-
import { basename as basename4, join as
|
|
63329
|
+
import { existsSync as existsSync26 } from "fs";
|
|
63330
|
+
import { basename as basename4, join as join28 } from "path";
|
|
62753
63331
|
import { eq as eq16, isNull } from "drizzle-orm";
|
|
62754
63332
|
function listProjects(ctx) {
|
|
62755
63333
|
return ctx.db.select().from(projects).where(isNull(projects.closedAt)).all().map(rowToProject);
|
|
@@ -62828,7 +63406,7 @@ async function assertRepoTolerant(ctx, repoPath) {
|
|
|
62828
63406
|
} catch (e) {
|
|
62829
63407
|
if (!isNotImplemented(e))
|
|
62830
63408
|
throw e;
|
|
62831
|
-
if (!
|
|
63409
|
+
if (!existsSync26(join28(repoPath, ".git"))) {
|
|
62832
63410
|
throw new InvalidInputError(`not a git repository: ${repoPath}`);
|
|
62833
63411
|
}
|
|
62834
63412
|
}
|
|
@@ -62893,6 +63471,7 @@ var init_review_findings2 = __esm(() => {
|
|
|
62893
63471
|
reviewFindingsRouter = router({
|
|
62894
63472
|
listByFeature: publicProcedure.input(z9.object({ featureId: z9.string() })).query(({ ctx, input }) => viewByFeature(ctx, input.featureId)),
|
|
62895
63473
|
dismiss: publicProcedure.input(z9.object({ findingId: z9.string() })).mutation(({ ctx, input }) => dismiss(ctx, input.findingId)),
|
|
63474
|
+
reopen: publicProcedure.input(z9.object({ findingId: z9.string() })).mutation(({ ctx, input }) => reopenFinding(ctx, input.findingId)),
|
|
62896
63475
|
fixOpenDefects: publicProcedure.input(z9.object({ featureId: z9.string() })).mutation(async ({ ctx, input }) => {
|
|
62897
63476
|
const promoted = promoteOpenDefects(ctx, input.featureId);
|
|
62898
63477
|
const { runId } = await burn(ctx, input.featureId);
|
|
@@ -62978,13 +63557,13 @@ var init_settings2 = __esm(() => {
|
|
|
62978
63557
|
});
|
|
62979
63558
|
|
|
62980
63559
|
// src/doctor/afk-env.ts
|
|
62981
|
-
import { existsSync as
|
|
63560
|
+
import { existsSync as existsSync27, readFileSync as readFileSync16 } from "fs";
|
|
62982
63561
|
function envWithAfkCredentials() {
|
|
62983
63562
|
const merged = { ...process.env };
|
|
62984
63563
|
try {
|
|
62985
63564
|
const path2 = envPath();
|
|
62986
|
-
if (
|
|
62987
|
-
const fromFile = parseEnvFile3(
|
|
63565
|
+
if (existsSync27(path2)) {
|
|
63566
|
+
const fromFile = parseEnvFile3(readFileSync16(path2, "utf8"));
|
|
62988
63567
|
for (const runtime5 of AGENT_RUNTIMES) {
|
|
62989
63568
|
const key = RUNTIME_SPECS[runtime5].afkKey;
|
|
62990
63569
|
const value = fromFile[key];
|
|
@@ -63002,9 +63581,84 @@ var init_afk_env = __esm(() => {
|
|
|
63002
63581
|
init_doctor();
|
|
63003
63582
|
});
|
|
63004
63583
|
|
|
63584
|
+
// src/services/project-image.ts
|
|
63585
|
+
function adoptProjectImage(ctx, projectId, tag) {
|
|
63586
|
+
if (!isOverwritable(ctx, projectId, "sandboxImage"))
|
|
63587
|
+
return false;
|
|
63588
|
+
recordFinding(ctx, projectId, {
|
|
63589
|
+
key: "sandboxImage",
|
|
63590
|
+
value: tag,
|
|
63591
|
+
source: "build",
|
|
63592
|
+
evidence: `Built from .runcastle/sandbox/Dockerfile as ${tag}.`
|
|
63593
|
+
});
|
|
63594
|
+
emitProject(ctx, projectId, {
|
|
63595
|
+
type: "settings.updated",
|
|
63596
|
+
message: `sandboxImage set to ${tag} by the image build`,
|
|
63597
|
+
data: { key: "sandboxImage", scope: "project", value: tag }
|
|
63598
|
+
});
|
|
63599
|
+
return true;
|
|
63600
|
+
}
|
|
63601
|
+
function releaseProjectImage(ctx, projectId) {
|
|
63602
|
+
if (!isOverwritable(ctx, projectId, "sandboxImage"))
|
|
63603
|
+
return false;
|
|
63604
|
+
recordFinding(ctx, projectId, { key: "sandboxImage", value: null, source: "build" });
|
|
63605
|
+
emitProject(ctx, projectId, {
|
|
63606
|
+
type: "settings.updated",
|
|
63607
|
+
message: "sandboxImage cleared \u2014 .runcastle/sandbox/Dockerfile is gone",
|
|
63608
|
+
data: { key: "sandboxImage", scope: "project", value: null }
|
|
63609
|
+
});
|
|
63610
|
+
return true;
|
|
63611
|
+
}
|
|
63612
|
+
var init_project_image = __esm(() => {
|
|
63613
|
+
init_events();
|
|
63614
|
+
init_findings();
|
|
63615
|
+
});
|
|
63616
|
+
|
|
63005
63617
|
// src/trpc/routers/setup.ts
|
|
63618
|
+
import { join as join29 } from "path";
|
|
63006
63619
|
import { isNotNull as isNotNull4 } from "drizzle-orm";
|
|
63007
63620
|
import * as z12 from "zod";
|
|
63621
|
+
function projectImageEnv(ctx, project3) {
|
|
63622
|
+
return {
|
|
63623
|
+
id: project3.id,
|
|
63624
|
+
repoPath: project3.repoPath,
|
|
63625
|
+
stored: project3.sandboxImage ?? null,
|
|
63626
|
+
overwritable: isOverwritable(ctx, project3.id, "sandboxImage"),
|
|
63627
|
+
clearStored: () => releaseProjectImage(ctx, project3.id)
|
|
63628
|
+
};
|
|
63629
|
+
}
|
|
63630
|
+
async function buildImageTerminal(ctx, projectId) {
|
|
63631
|
+
const exec3 = createSystemExec();
|
|
63632
|
+
const runtime5 = await resolveRuntime(exec3, ctx.config.sandbox === "podman" ? "podman" : "docker");
|
|
63633
|
+
const project3 = projectId ? requireProjectById(ctx, projectId) : null;
|
|
63634
|
+
const stockContext = prepareSandboxBuildContext();
|
|
63635
|
+
const stockHash = hashDockerfile(join29(stockContext, "Dockerfile"));
|
|
63636
|
+
const plan = planImageBuild({
|
|
63637
|
+
config: ctx.config,
|
|
63638
|
+
project: project3 ? {
|
|
63639
|
+
id: project3.id,
|
|
63640
|
+
repoPath: project3.repoPath,
|
|
63641
|
+
sandboxImage: project3.sandboxImage,
|
|
63642
|
+
sandboxImageOverwritable: isOverwritable(ctx, project3.id, "sandboxImage")
|
|
63643
|
+
} : null,
|
|
63644
|
+
stockContext,
|
|
63645
|
+
stockFresh: stockHash !== null && (await inspectBuiltImage(exec3, runtime5, DEFAULT_SANDBOX_IMAGE)).hash === stockHash,
|
|
63646
|
+
buildArgs: stockBuildArgs(runtime5)
|
|
63647
|
+
});
|
|
63648
|
+
if (plan.kind === "refused")
|
|
63649
|
+
throw new InvalidInputError(plan.reason);
|
|
63650
|
+
const spec = imageBuildTerminal(runtime5, plan);
|
|
63651
|
+
if (plan.kind !== "chain" || !project3)
|
|
63652
|
+
return { spec };
|
|
63653
|
+
const { projectTag } = plan;
|
|
63654
|
+
return {
|
|
63655
|
+
spec,
|
|
63656
|
+
onExit: ({ exitCode }) => {
|
|
63657
|
+
if (exitCode === 0)
|
|
63658
|
+
adoptProjectImage(ctx, project3.id, projectTag);
|
|
63659
|
+
}
|
|
63660
|
+
};
|
|
63661
|
+
}
|
|
63008
63662
|
var setupRouter;
|
|
63009
63663
|
var init_setup2 = __esm(() => {
|
|
63010
63664
|
init_src();
|
|
@@ -63012,41 +63666,48 @@ var init_setup2 = __esm(() => {
|
|
|
63012
63666
|
init_afk_env();
|
|
63013
63667
|
init_doctor();
|
|
63014
63668
|
init_system_exec();
|
|
63669
|
+
init_errors();
|
|
63015
63670
|
init_asset_paths();
|
|
63016
63671
|
init_registry();
|
|
63672
|
+
init_findings();
|
|
63673
|
+
init_project_image();
|
|
63674
|
+
init_repo();
|
|
63675
|
+
init_sandbox_image();
|
|
63017
63676
|
init_setup();
|
|
63018
63677
|
init_resolve_executable();
|
|
63019
63678
|
init_context();
|
|
63020
63679
|
setupRouter = router({
|
|
63021
|
-
doctor: publicProcedure.
|
|
63680
|
+
doctor: publicProcedure.input(z12.object({
|
|
63681
|
+
projectId: z12.string().optional()
|
|
63682
|
+
}).optional()).query(({ ctx, input }) => {
|
|
63022
63683
|
const projectModels = ctx.db.select({ model: projects.model }).from(projects).where(isNotNull4(projects.model)).all().map((p) => p.model);
|
|
63684
|
+
const project3 = input?.projectId ? requireProjectById(ctx, input.projectId) : null;
|
|
63023
63685
|
return runDoctor({
|
|
63024
63686
|
exec: createSystemExec(),
|
|
63025
63687
|
burnerDockerfile: burnerDockerfilePath(),
|
|
63026
63688
|
env: envWithAfkCredentials(),
|
|
63027
63689
|
runtimes: configuredRuntimes(ctx.config, projectModels),
|
|
63028
|
-
|
|
63690
|
+
imageName: resolveSandboxImage(ctx.config),
|
|
63691
|
+
...project3 ? { projectImage: projectImageEnv(ctx, project3) } : {}
|
|
63029
63692
|
});
|
|
63030
63693
|
}),
|
|
63031
63694
|
runtimeGuide: publicProcedure.query(() => runtimeInstallGuide(process.platform)),
|
|
63032
63695
|
gitIdentity: publicProcedure.input(z12.object({ name: z12.string(), email: z12.string() })).mutation(({ input }) => writeGitIdentity(createSystemExec(), input)),
|
|
63033
63696
|
afkToken: publicProcedure.input(z12.object({ token: z12.string(), runtime: AgentRuntime.default("claude-code") })).mutation(({ input }) => saveAfkCredential(fileAfkTokenIo(createSystemExec(), input.runtime), input.token, input.runtime)),
|
|
63034
63697
|
seedModelDefaults: publicProcedure.input(z12.object({ runtimes: z12.array(AgentRuntime) })).mutation(({ ctx, input }) => seedModelDefaults(ctx, input.runtimes)),
|
|
63035
|
-
startTerminal: publicProcedure.input(z12.object({
|
|
63036
|
-
|
|
63037
|
-
|
|
63038
|
-
|
|
63039
|
-
const imageName = resolveSandboxImage(ctx.config);
|
|
63040
|
-
const sandcastleBin = input.kind === "build-image" ? resolveSandcastleBin() ?? undefined : undefined;
|
|
63041
|
-
const spec = terminalSpec(input.kind, { runtime: runtime5, imageName, sandcastleBin });
|
|
63698
|
+
startTerminal: publicProcedure.input(z12.object({
|
|
63699
|
+
kind: z12.enum(["setup-token", "build-image", "claude-login", "codex-login"]),
|
|
63700
|
+
projectId: z12.string().optional()
|
|
63701
|
+
})).mutation(async ({ ctx, input }) => {
|
|
63042
63702
|
const sessionId = newId("setup");
|
|
63043
|
-
const
|
|
63703
|
+
const { spec, onExit: onExit4 } = input.kind === "build-image" ? await buildImageTerminal(ctx, input.projectId) : { spec: { ...terminalSpec(input.kind), cwd: process.cwd() }, onExit: undefined };
|
|
63044
63704
|
const { file, args: args2 } = resolveSpawnTarget(spec.cmd, spec.args);
|
|
63045
63705
|
ptyRegistry().create({
|
|
63046
63706
|
sessionId,
|
|
63047
63707
|
cmd: file,
|
|
63048
63708
|
args: args2,
|
|
63049
|
-
opts: { cwd, env: process.env }
|
|
63709
|
+
opts: { cwd: spec.cwd, env: process.env },
|
|
63710
|
+
...onExit4 ? { onExit: onExit4 } : {}
|
|
63050
63711
|
});
|
|
63051
63712
|
return { sessionId };
|
|
63052
63713
|
})
|
|
@@ -63405,6 +64066,8 @@ var init_report = __esm(() => {
|
|
|
63405
64066
|
ok: "\u2713",
|
|
63406
64067
|
stale: "\u2717",
|
|
63407
64068
|
missing: "\u2717",
|
|
64069
|
+
"not-built-yet": "\u2717",
|
|
64070
|
+
custom: "\xB7",
|
|
63408
64071
|
"daemon-dead": "\u2717",
|
|
63409
64072
|
"machine-stopped": "\u2717",
|
|
63410
64073
|
unhealthy: "\u2717",
|
|
@@ -63464,7 +64127,7 @@ var init_cli = __esm(() => {
|
|
|
63464
64127
|
init_asset_paths();
|
|
63465
64128
|
init_version();
|
|
63466
64129
|
import { dirname as dirname16, resolve as resolve13 } from "path";
|
|
63467
|
-
import { fileURLToPath as
|
|
64130
|
+
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
63468
64131
|
function parseCommand(argv) {
|
|
63469
64132
|
const [first, ...rest] = argv;
|
|
63470
64133
|
if (first === undefined)
|
|
@@ -63488,7 +64151,7 @@ Usage:
|
|
|
63488
64151
|
runcastle --help Show this help
|
|
63489
64152
|
`;
|
|
63490
64153
|
async function main(argv) {
|
|
63491
|
-
applyInstalledAssetEnv(resolve13(dirname16(
|
|
64154
|
+
applyInstalledAssetEnv(resolve13(dirname16(fileURLToPath9(import.meta.url)), ".."));
|
|
63492
64155
|
const { command, args: args2 } = parseCommand(argv);
|
|
63493
64156
|
switch (command) {
|
|
63494
64157
|
case "version":
|