runcastle 1.2.2 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/runcastle.js +346 -204
- package/drizzle/0019_freezing_natasha_romanoff.sql +2 -0
- package/drizzle/meta/0019_snapshot.json +860 -0
- package/drizzle/meta/_journal.json +7 -0
- package/index.js +341 -199
- package/package.json +1 -1
- package/web/assets/index-CzDDtq7w.js +142 -0
- package/web/index.html +1 -1
- package/web/assets/index-Co7aViQi.js +0 -142
package/bin/runcastle.js
CHANGED
|
@@ -26,6 +26,7 @@ __export(exports_paths, {
|
|
|
26
26
|
projectWorktreesDir: () => projectWorktreesDir,
|
|
27
27
|
prodDataDir: () => prodDataDir,
|
|
28
28
|
logsDir: () => logsDir,
|
|
29
|
+
installIdPath: () => installIdPath,
|
|
29
30
|
featureDocsRel: () => featureDocsRel,
|
|
30
31
|
envPath: () => envPath,
|
|
31
32
|
devDataDir: () => devDataDir,
|
|
@@ -64,6 +65,9 @@ function configPath() {
|
|
|
64
65
|
function envPath() {
|
|
65
66
|
return join(dataDir(), ".env");
|
|
66
67
|
}
|
|
68
|
+
function installIdPath() {
|
|
69
|
+
return join(dataDir(), "install-id");
|
|
70
|
+
}
|
|
67
71
|
function logsDir() {
|
|
68
72
|
return join(dataDir(), "logs");
|
|
69
73
|
}
|
|
@@ -246,7 +250,7 @@ var init_ids = () => {};
|
|
|
246
250
|
|
|
247
251
|
// ../core/src/schemas.ts
|
|
248
252
|
import * as z2 from "zod";
|
|
249
|
-
var Phase, TicketStatus, SessionKind, RunStatus, FeatureStatus, SessionStatus, TicketInput, Ticket, WaypointType, WaypointStatus, WaypointDisposition, WaypointInput, Waypoint, TestNoteStatus, TestNote, PREPARED_KEYS, PreparedKey, DRIVE_LOOP_KEYS, FindingSource, PROJECT_NAME_MAX = 80, ProjectName, Project, ProjectFinding, Feature, SessionRow, Run, SettingSource, SettingScope, SettingField, SettingsView, SettingsUpdateInput, EventRow;
|
|
253
|
+
var Phase, TicketStatus, SessionKind, SessionPurpose, MergeBranchPair, RunStatus, FeatureStatus, SessionStatus, TicketInput, Ticket, WaypointType, WaypointStatus, WaypointDisposition, WaypointInput, Waypoint, TestNoteStatus, TestNote, PREPARED_KEYS, PreparedKey, DRIVE_LOOP_KEYS, FindingSource, PROJECT_NAME_MAX = 80, ProjectName, Project, ProjectFinding, Feature, SessionRow, Run, SettingSource, SettingScope, SettingField, SettingsView, SettingsUpdateInput, EventRow;
|
|
250
254
|
var init_schemas = __esm(() => {
|
|
251
255
|
Phase = z2.enum([
|
|
252
256
|
"ideation",
|
|
@@ -266,6 +270,11 @@ var init_schemas = __esm(() => {
|
|
|
266
270
|
"prepare",
|
|
267
271
|
"project"
|
|
268
272
|
]);
|
|
273
|
+
SessionPurpose = z2.enum(["resolve-conflict"]);
|
|
274
|
+
MergeBranchPair = z2.object({
|
|
275
|
+
mergeFrom: z2.string(),
|
|
276
|
+
mergeInto: z2.string()
|
|
277
|
+
});
|
|
269
278
|
RunStatus = z2.enum(["running", "succeeded", "failed", "cancelled"]);
|
|
270
279
|
FeatureStatus = z2.enum(["active", "shipped", "archived"]);
|
|
271
280
|
SessionStatus = z2.enum(["launching", "live", "ended"]);
|
|
@@ -382,6 +391,8 @@ var init_schemas = __esm(() => {
|
|
|
382
391
|
featureId: z2.string().optional(),
|
|
383
392
|
projectId: z2.string().optional(),
|
|
384
393
|
kind: SessionKind,
|
|
394
|
+
purpose: SessionPurpose.optional(),
|
|
395
|
+
purposeData: MergeBranchPair.optional(),
|
|
385
396
|
ccSessionId: z2.string().optional(),
|
|
386
397
|
transcriptPath: z2.string().optional(),
|
|
387
398
|
status: SessionStatus,
|
|
@@ -620,6 +631,8 @@ var init_db_schema = __esm(() => {
|
|
|
620
631
|
projectId: text("project_id"),
|
|
621
632
|
lap: integer("lap").notNull().default(1),
|
|
622
633
|
kind: text("kind").notNull().$type(),
|
|
634
|
+
purpose: text("purpose").$type(),
|
|
635
|
+
purposeData: text("purpose_data", { mode: "json" }).$type(),
|
|
623
636
|
ccSessionId: text("cc_session_id"),
|
|
624
637
|
transcriptPath: text("transcript_path"),
|
|
625
638
|
status: text("status").notNull().$type(),
|
|
@@ -914,6 +927,23 @@ var init_resolve_executable = __esm(() => {
|
|
|
914
927
|
};
|
|
915
928
|
});
|
|
916
929
|
|
|
930
|
+
// src/pty/kill-tree.ts
|
|
931
|
+
import { execFile } from "child_process";
|
|
932
|
+
import { promisify } from "util";
|
|
933
|
+
async function killProcessTree(pid) {
|
|
934
|
+
try {
|
|
935
|
+
if (process.platform === "win32") {
|
|
936
|
+
await execFileAsync("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true });
|
|
937
|
+
} else {
|
|
938
|
+
process.kill(-pid, "SIGTERM");
|
|
939
|
+
}
|
|
940
|
+
} catch {}
|
|
941
|
+
}
|
|
942
|
+
var execFileAsync;
|
|
943
|
+
var init_kill_tree = __esm(() => {
|
|
944
|
+
execFileAsync = promisify(execFile);
|
|
945
|
+
});
|
|
946
|
+
|
|
917
947
|
// src/pty/pty-sidecar.ts
|
|
918
948
|
import { spawn } from "child_process";
|
|
919
949
|
import { createRequire } from "module";
|
|
@@ -1095,6 +1125,9 @@ ${why.replace(/\n/g, `\r
|
|
|
1095
1125
|
}
|
|
1096
1126
|
}, 500);
|
|
1097
1127
|
},
|
|
1128
|
+
killTree() {
|
|
1129
|
+
return killProcessTree(process.platform === "win32" ? child.pid ?? pid : pid);
|
|
1130
|
+
},
|
|
1098
1131
|
get pid() {
|
|
1099
1132
|
return pid;
|
|
1100
1133
|
},
|
|
@@ -1107,6 +1140,7 @@ var HOST_PATH;
|
|
|
1107
1140
|
var init_pty_sidecar = __esm(() => {
|
|
1108
1141
|
init_asset_paths();
|
|
1109
1142
|
init_resolve_executable();
|
|
1143
|
+
init_kill_tree();
|
|
1110
1144
|
HOST_PATH = resolveAsset(ASSET_ENV.ptyHost, fileURLToPath(new URL("./pty-host.cjs", import.meta.url)));
|
|
1111
1145
|
});
|
|
1112
1146
|
|
|
@@ -1193,6 +1227,9 @@ function createNativePtySession(cmd, args, opts) {
|
|
|
1193
1227
|
proc.kill();
|
|
1194
1228
|
} catch {}
|
|
1195
1229
|
},
|
|
1230
|
+
killTree() {
|
|
1231
|
+
return killProcessTree(proc.pid);
|
|
1232
|
+
},
|
|
1196
1233
|
get pid() {
|
|
1197
1234
|
return proc.pid;
|
|
1198
1235
|
},
|
|
@@ -1204,6 +1241,7 @@ function createNativePtySession(cmd, args, opts) {
|
|
|
1204
1241
|
var cached = null, BACKEND_LOGGED;
|
|
1205
1242
|
var init_pty = __esm(() => {
|
|
1206
1243
|
init_resolve_executable();
|
|
1244
|
+
init_kill_tree();
|
|
1207
1245
|
init_pty_sidecar();
|
|
1208
1246
|
BACKEND_LOGGED = Symbol.for("runcastle.pty.backend.logged");
|
|
1209
1247
|
});
|
|
@@ -1541,6 +1579,8 @@ function rowToSession(row) {
|
|
|
1541
1579
|
featureId: row.featureId ?? undefined,
|
|
1542
1580
|
projectId: row.projectId ?? undefined,
|
|
1543
1581
|
kind: row.kind,
|
|
1582
|
+
purpose: row.purpose ?? undefined,
|
|
1583
|
+
purposeData: row.purposeData ?? undefined,
|
|
1544
1584
|
ccSessionId: row.ccSessionId ?? undefined,
|
|
1545
1585
|
transcriptPath: row.transcriptPath ?? undefined,
|
|
1546
1586
|
status: row.status,
|
|
@@ -1796,7 +1836,6 @@ var init_waypoints = __esm(() => {
|
|
|
1796
1836
|
});
|
|
1797
1837
|
|
|
1798
1838
|
// src/pty/dev-pane.ts
|
|
1799
|
-
import { execFileSync } from "child_process";
|
|
1800
1839
|
function drivePaneId(ownerId) {
|
|
1801
1840
|
return `${DRIVE_PREFIX}${ownerId}`;
|
|
1802
1841
|
}
|
|
@@ -1856,24 +1895,24 @@ function devPaneLive(paneId) {
|
|
|
1856
1895
|
const entry = ptyRegistry().get(paneId);
|
|
1857
1896
|
return !!entry && !entry.exited;
|
|
1858
1897
|
}
|
|
1859
|
-
function
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1898
|
+
function killTreeBounded(pty) {
|
|
1899
|
+
return new Promise((resolve4) => {
|
|
1900
|
+
const timer = setTimeout(resolve4, KILL_TREE_TIMEOUT_MS);
|
|
1901
|
+
pty.killTree().catch(() => {}).then(() => {
|
|
1902
|
+
clearTimeout(timer);
|
|
1903
|
+
resolve4();
|
|
1904
|
+
});
|
|
1905
|
+
});
|
|
1867
1906
|
}
|
|
1868
|
-
function stopDevPane(paneId) {
|
|
1907
|
+
async function stopDevPane(paneId) {
|
|
1869
1908
|
const reg = ptyRegistry();
|
|
1870
1909
|
const entry = reg.get(paneId);
|
|
1871
1910
|
if (entry && !entry.exited)
|
|
1872
|
-
|
|
1911
|
+
await killTreeBounded(entry.pty);
|
|
1873
1912
|
reg.kill(paneId);
|
|
1874
1913
|
reg.remove(paneId);
|
|
1875
1914
|
}
|
|
1876
|
-
var DRIVE_PREFIX = "drive:", SNIFF_WINDOW = 8192;
|
|
1915
|
+
var DRIVE_PREFIX = "drive:", SNIFF_WINDOW = 8192, KILL_TREE_TIMEOUT_MS = 5000;
|
|
1877
1916
|
var init_dev_pane = __esm(() => {
|
|
1878
1917
|
init_events();
|
|
1879
1918
|
init_registry();
|
|
@@ -2802,7 +2841,7 @@ async function testDrive(ctx, project, feature, action) {
|
|
|
2802
2841
|
const detachedWorktree2 = testDriveState.detachedWorktree;
|
|
2803
2842
|
const devPaneId = testDriveState.devPaneId;
|
|
2804
2843
|
if (devPaneId)
|
|
2805
|
-
stopDevPane(devPaneId);
|
|
2844
|
+
await stopDevPane(devPaneId);
|
|
2806
2845
|
const teardown = await runDriveHookStep(ctx, scope, project.repoPath, "teardown", project.driveStopCommand, driveEnvFor(ctx, project, { slug: feature.slug, branch }, scope).env);
|
|
2807
2846
|
const carriedChanges = await dirtyPaths(g);
|
|
2808
2847
|
await g.checkout(previousBranch2);
|
|
@@ -2942,7 +2981,7 @@ async function startDryRun(ctx, project) {
|
|
|
2942
2981
|
async function stopDryRun(ctx, project, state) {
|
|
2943
2982
|
const scope = { projectId: project.id };
|
|
2944
2983
|
if (state.devPaneId)
|
|
2945
|
-
stopDevPane(state.devPaneId);
|
|
2984
|
+
await stopDevPane(state.devPaneId);
|
|
2946
2985
|
const teardown = await runDriveHookStep(ctx, scope, project.repoPath, "teardown", project.driveStopCommand, state.env);
|
|
2947
2986
|
state.observed.teardownOk = teardown?.result.ok;
|
|
2948
2987
|
testDriveState = undefined;
|
|
@@ -3157,10 +3196,24 @@ async function conflictedFiles(g) {
|
|
|
3157
3196
|
return [];
|
|
3158
3197
|
}
|
|
3159
3198
|
}
|
|
3199
|
+
async function mergeInProgressAt(worktreePath) {
|
|
3200
|
+
try {
|
|
3201
|
+
return await mergeInProgress(git(worktreePath));
|
|
3202
|
+
} catch {
|
|
3203
|
+
return false;
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
async function isAncestor(repoPath, ancestor, descendant) {
|
|
3207
|
+
try {
|
|
3208
|
+
const out = await git(repoPath).raw(["rev-list", "--count", ancestor, `^${descendant}`]);
|
|
3209
|
+
return out.trim() === "0";
|
|
3210
|
+
} catch {
|
|
3211
|
+
return false;
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3160
3214
|
async function mergeInProgress(g) {
|
|
3161
3215
|
try {
|
|
3162
|
-
await g.raw(["rev-parse", "--verify", "--quiet", "MERGE_HEAD"]);
|
|
3163
|
-
return true;
|
|
3216
|
+
return (await g.raw(["rev-parse", "--verify", "--quiet", "MERGE_HEAD"])).trim() !== "";
|
|
3164
3217
|
} catch {
|
|
3165
3218
|
return false;
|
|
3166
3219
|
}
|
|
@@ -3190,6 +3243,8 @@ function createSessionRow(ctx, input) {
|
|
|
3190
3243
|
projectId: input.projectId ?? null,
|
|
3191
3244
|
lap: input.featureId ? getFeatureRow(ctx, input.featureId).lap : 1,
|
|
3192
3245
|
kind: input.kind,
|
|
3246
|
+
purpose: input.purpose ?? null,
|
|
3247
|
+
purposeData: input.purposeData ?? null,
|
|
3193
3248
|
ccSessionId: null,
|
|
3194
3249
|
transcriptPath: null,
|
|
3195
3250
|
status: "launching",
|
|
@@ -3437,7 +3492,7 @@ function landProjectSession(ctx, session) {
|
|
|
3437
3492
|
data: { sessionId: session.id, branch: PROJECT_BRANCH, ...data }
|
|
3438
3493
|
});
|
|
3439
3494
|
};
|
|
3440
|
-
landProjectBranch(project).then((res) => {
|
|
3495
|
+
const landing = landProjectBranch(project).then((res) => {
|
|
3441
3496
|
if (!res)
|
|
3442
3497
|
return;
|
|
3443
3498
|
if (res.landed) {
|
|
@@ -3450,6 +3505,8 @@ function landProjectSession(ctx, session) {
|
|
|
3450
3505
|
}).catch((e) => {
|
|
3451
3506
|
report("project.land_failed", `could not land the project session's work onto ${project.mainBranch}: ${e instanceof Error ? e.message : String(e)} \u2014 ${kept}`, { error: e instanceof Error ? e.message : String(e) });
|
|
3452
3507
|
});
|
|
3508
|
+
inFlightLandings.add(landing);
|
|
3509
|
+
landing.finally(() => inFlightLandings.delete(landing));
|
|
3453
3510
|
}
|
|
3454
3511
|
function markSessionEnded(ctx, id) {
|
|
3455
3512
|
const existing = getSessionRow(ctx, id);
|
|
@@ -3478,7 +3535,7 @@ function mostRecentLiveSession(ctx) {
|
|
|
3478
3535
|
const row = ctx.db.select().from(sessions).where(eq5(sessions.status, "live")).orderBy(desc3(sql2`rowid`)).limit(1).get();
|
|
3479
3536
|
return row ? rowToSession(row) : null;
|
|
3480
3537
|
}
|
|
3481
|
-
var KICKOFF_DELAY_MS = 1500, KICKOFF_SUBMIT_DELAY_MS = 350, KICKOFF_CONFIRM_MS = 12000, KICKOFF_MAX_ATTEMPTS = 3, CLEAR_INPUT = "\x15", SESSION_READY_TIMEOUT_MS = 25000, CONVERGE_KICKOFF_LINE, KICKOFF_LINES, RESUME_KICKOFF_PREFIX, pendingKickoffOverrides, deliveries, MATCH_PREFIX = 40, landedProjectSessions;
|
|
3538
|
+
var KICKOFF_DELAY_MS = 1500, KICKOFF_SUBMIT_DELAY_MS = 350, KICKOFF_CONFIRM_MS = 12000, KICKOFF_MAX_ATTEMPTS = 3, CLEAR_INPUT = "\x15", SESSION_READY_TIMEOUT_MS = 25000, CONVERGE_KICKOFF_LINE, KICKOFF_LINES, RESUME_KICKOFF_PREFIX, pendingKickoffOverrides, deliveries, MATCH_PREFIX = 40, landedProjectSessions, inFlightLandings;
|
|
3482
3539
|
var init_sessions = __esm(() => {
|
|
3483
3540
|
init_src();
|
|
3484
3541
|
init_schema();
|
|
@@ -3501,6 +3558,7 @@ var init_sessions = __esm(() => {
|
|
|
3501
3558
|
pendingKickoffOverrides = new Map;
|
|
3502
3559
|
deliveries = new Map;
|
|
3503
3560
|
landedProjectSessions = new Set;
|
|
3561
|
+
inFlightLandings = new Set;
|
|
3504
3562
|
});
|
|
3505
3563
|
|
|
3506
3564
|
// src/launcher/reconcile.ts
|
|
@@ -6631,6 +6689,8 @@ function guardsEdits(kind) {
|
|
|
6631
6689
|
function evaluateEditGuard(input) {
|
|
6632
6690
|
if (!guardsEdits(input.kind))
|
|
6633
6691
|
return null;
|
|
6692
|
+
if (input.purpose === "resolve-conflict" && input.mergeInProgress)
|
|
6693
|
+
return null;
|
|
6634
6694
|
if (!input.toolName || !EDIT_TOOLS.includes(input.toolName))
|
|
6635
6695
|
return null;
|
|
6636
6696
|
if (!input.filePath)
|
|
@@ -6807,22 +6867,40 @@ function handleUserPrompt(ctx, sessionId, feature, payload) {
|
|
|
6807
6867
|
}
|
|
6808
6868
|
};
|
|
6809
6869
|
}
|
|
6810
|
-
function handleSessionEnd(ctx,
|
|
6811
|
-
markSessionEnded(ctx,
|
|
6812
|
-
releaseForSession(ctx,
|
|
6870
|
+
async function handleSessionEnd(ctx, session, feature) {
|
|
6871
|
+
markSessionEnded(ctx, session.id);
|
|
6872
|
+
releaseForSession(ctx, session.id);
|
|
6813
6873
|
emit(ctx, feature.id, {
|
|
6814
6874
|
type: "session.ended",
|
|
6815
6875
|
message: "session ended",
|
|
6816
|
-
data: { sessionId }
|
|
6876
|
+
data: { sessionId: session.id }
|
|
6817
6877
|
});
|
|
6878
|
+
await noteResolvedMerge(ctx, session, feature);
|
|
6818
6879
|
return {};
|
|
6819
6880
|
}
|
|
6820
|
-
function
|
|
6881
|
+
async function noteResolvedMerge(ctx, session, feature) {
|
|
6882
|
+
const pair = session.purpose === "resolve-conflict" ? session.purposeData : undefined;
|
|
6883
|
+
if (!pair)
|
|
6884
|
+
return;
|
|
6885
|
+
try {
|
|
6886
|
+
if (!await isAncestor(session.worktreePath, pair.mergeFrom, pair.mergeInto))
|
|
6887
|
+
return;
|
|
6888
|
+
emit(ctx, feature.id, {
|
|
6889
|
+
type: "merge.resolved",
|
|
6890
|
+
message: `merge conflict resolved \u2014 ${pair.mergeFrom} is in ${pair.mergeInto}`,
|
|
6891
|
+
data: { sessionId: session.id, ...pair }
|
|
6892
|
+
});
|
|
6893
|
+
} catch {}
|
|
6894
|
+
}
|
|
6895
|
+
async function handlePreToolUse(session, feature, payload) {
|
|
6821
6896
|
const toolName = typeof payload?.tool_name === "string" ? payload.tool_name : undefined;
|
|
6822
6897
|
const toolInput = payload?.tool_input ?? {};
|
|
6823
6898
|
const path = typeof toolInput.file_path === "string" ? toolInput.file_path : typeof toolInput.notebook_path === "string" ? toolInput.notebook_path : undefined;
|
|
6899
|
+
const mergeInProgress2 = session.purpose === "resolve-conflict" ? await mergeInProgressAt(session.worktreePath) : false;
|
|
6824
6900
|
const denial = evaluateEditGuard({
|
|
6825
6901
|
kind: session.kind,
|
|
6902
|
+
purpose: session.purpose,
|
|
6903
|
+
mergeInProgress: mergeInProgress2,
|
|
6826
6904
|
worktreePath: session.worktreePath,
|
|
6827
6905
|
toolName,
|
|
6828
6906
|
filePath: path,
|
|
@@ -6851,6 +6929,7 @@ var init_hooks = __esm(() => {
|
|
|
6851
6929
|
init_edit_guard();
|
|
6852
6930
|
init_sessions();
|
|
6853
6931
|
init_events();
|
|
6932
|
+
init_git();
|
|
6854
6933
|
init_prep();
|
|
6855
6934
|
init_repo();
|
|
6856
6935
|
init_tickets();
|
|
@@ -6888,7 +6967,7 @@ var init_hooks = __esm(() => {
|
|
|
6888
6967
|
case "session-end":
|
|
6889
6968
|
return c.json(handleProjectScopedSessionEnd(ctx, session));
|
|
6890
6969
|
case "pre-tool":
|
|
6891
|
-
return c.json(handlePreToolUse(session, undefined, body.payload));
|
|
6970
|
+
return c.json(await handlePreToolUse(session, undefined, body.payload));
|
|
6892
6971
|
default:
|
|
6893
6972
|
return c.json({});
|
|
6894
6973
|
}
|
|
@@ -6902,9 +6981,9 @@ var init_hooks = __esm(() => {
|
|
|
6902
6981
|
case "user-prompt":
|
|
6903
6982
|
return c.json(handleUserPrompt(ctx, sessionId, feature, body.payload));
|
|
6904
6983
|
case "session-end":
|
|
6905
|
-
return c.json(handleSessionEnd(ctx,
|
|
6984
|
+
return c.json(await handleSessionEnd(ctx, session, feature));
|
|
6906
6985
|
case "pre-tool":
|
|
6907
|
-
return c.json(handlePreToolUse(session, feature, body.payload));
|
|
6986
|
+
return c.json(await handlePreToolUse(session, feature, body.payload));
|
|
6908
6987
|
default:
|
|
6909
6988
|
return c.json({});
|
|
6910
6989
|
}
|
|
@@ -7046,6 +7125,166 @@ var init_web = __esm(() => {
|
|
|
7046
7125
|
};
|
|
7047
7126
|
});
|
|
7048
7127
|
|
|
7128
|
+
// src/version.ts
|
|
7129
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
7130
|
+
import { dirname as dirname5, join as join12 } from "path";
|
|
7131
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7132
|
+
function runcastleVersion() {
|
|
7133
|
+
if (cached2)
|
|
7134
|
+
return cached2;
|
|
7135
|
+
const here = dirname5(fileURLToPath4(import.meta.url));
|
|
7136
|
+
for (const pkgPath of [join12(here, "..", "package.json"), join12(here, "package.json")]) {
|
|
7137
|
+
try {
|
|
7138
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
7139
|
+
if (typeof pkg.version === "string") {
|
|
7140
|
+
cached2 = pkg.version;
|
|
7141
|
+
return cached2;
|
|
7142
|
+
}
|
|
7143
|
+
} catch {}
|
|
7144
|
+
}
|
|
7145
|
+
cached2 = UNKNOWN_VERSION;
|
|
7146
|
+
return cached2;
|
|
7147
|
+
}
|
|
7148
|
+
var cached2, UNKNOWN_VERSION = "0.0.0";
|
|
7149
|
+
var init_version = () => {};
|
|
7150
|
+
|
|
7151
|
+
// src/services/install-id.ts
|
|
7152
|
+
import { randomUUID } from "crypto";
|
|
7153
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
|
|
7154
|
+
import { dirname as dirname6 } from "path";
|
|
7155
|
+
function getInstallId() {
|
|
7156
|
+
const file = installIdPath();
|
|
7157
|
+
try {
|
|
7158
|
+
if (existsSync13(file)) {
|
|
7159
|
+
const existing = readFileSync7(file, "utf8").trim();
|
|
7160
|
+
if (UUID_RE.test(existing))
|
|
7161
|
+
return existing;
|
|
7162
|
+
}
|
|
7163
|
+
const id = randomUUID();
|
|
7164
|
+
mkdirSync6(dirname6(file), { recursive: true });
|
|
7165
|
+
writeFileSync2(file, `${id}
|
|
7166
|
+
`);
|
|
7167
|
+
return id;
|
|
7168
|
+
} catch {
|
|
7169
|
+
return randomUUID();
|
|
7170
|
+
}
|
|
7171
|
+
}
|
|
7172
|
+
var UUID_RE;
|
|
7173
|
+
var init_install_id = __esm(() => {
|
|
7174
|
+
init_paths();
|
|
7175
|
+
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
7176
|
+
});
|
|
7177
|
+
|
|
7178
|
+
// src/services/update-check.ts
|
|
7179
|
+
function parse(version) {
|
|
7180
|
+
const cleaned = version.trim().replace(/^v/, "");
|
|
7181
|
+
const [core = "", pre = ""] = cleaned.split("-", 2);
|
|
7182
|
+
const release2 = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
7183
|
+
return { release: release2, pre: pre ? pre.split(".") : [] };
|
|
7184
|
+
}
|
|
7185
|
+
function compareSemver(a, b) {
|
|
7186
|
+
const pa = parse(a);
|
|
7187
|
+
const pb = parse(b);
|
|
7188
|
+
const len = Math.max(pa.release.length, pb.release.length);
|
|
7189
|
+
for (let i = 0;i < len; i++) {
|
|
7190
|
+
const diff = (pa.release[i] ?? 0) - (pb.release[i] ?? 0);
|
|
7191
|
+
if (diff !== 0)
|
|
7192
|
+
return diff;
|
|
7193
|
+
}
|
|
7194
|
+
if (pa.pre.length === 0 && pb.pre.length === 0)
|
|
7195
|
+
return 0;
|
|
7196
|
+
if (pa.pre.length === 0)
|
|
7197
|
+
return 1;
|
|
7198
|
+
if (pb.pre.length === 0)
|
|
7199
|
+
return -1;
|
|
7200
|
+
const preLen = Math.max(pa.pre.length, pb.pre.length);
|
|
7201
|
+
for (let i = 0;i < preLen; i++) {
|
|
7202
|
+
const x = pa.pre[i];
|
|
7203
|
+
const y = pb.pre[i];
|
|
7204
|
+
if (x === undefined)
|
|
7205
|
+
return -1;
|
|
7206
|
+
if (y === undefined)
|
|
7207
|
+
return 1;
|
|
7208
|
+
const nx = Number(x);
|
|
7209
|
+
const ny = Number(y);
|
|
7210
|
+
const bothNumeric = !Number.isNaN(nx) && !Number.isNaN(ny);
|
|
7211
|
+
const cmp = bothNumeric ? nx - ny : x < y ? -1 : x > y ? 1 : 0;
|
|
7212
|
+
if (cmp !== 0)
|
|
7213
|
+
return cmp;
|
|
7214
|
+
}
|
|
7215
|
+
return 0;
|
|
7216
|
+
}
|
|
7217
|
+
function doNotTrack(env) {
|
|
7218
|
+
const raw = env.DO_NOT_TRACK;
|
|
7219
|
+
return raw !== undefined && raw !== "" && raw !== "0";
|
|
7220
|
+
}
|
|
7221
|
+
async function latestFromNpm(fetchImpl) {
|
|
7222
|
+
try {
|
|
7223
|
+
const res = await fetchImpl(NPM_LATEST_URL);
|
|
7224
|
+
if (!res.ok)
|
|
7225
|
+
return null;
|
|
7226
|
+
const body = await res.json();
|
|
7227
|
+
return typeof body.version === "string" ? body.version : null;
|
|
7228
|
+
} catch {
|
|
7229
|
+
return null;
|
|
7230
|
+
}
|
|
7231
|
+
}
|
|
7232
|
+
async function latestFromPing(fetchImpl, current2) {
|
|
7233
|
+
try {
|
|
7234
|
+
const res = await fetchImpl(PING_URL, {
|
|
7235
|
+
method: "POST",
|
|
7236
|
+
headers: { "content-type": "application/json" },
|
|
7237
|
+
body: JSON.stringify({
|
|
7238
|
+
installId: getInstallId(),
|
|
7239
|
+
version: current2,
|
|
7240
|
+
platform: process.platform
|
|
7241
|
+
})
|
|
7242
|
+
});
|
|
7243
|
+
if (!res.ok)
|
|
7244
|
+
return null;
|
|
7245
|
+
const body = await res.json();
|
|
7246
|
+
return typeof body.latest === "string" ? body.latest : null;
|
|
7247
|
+
} catch {
|
|
7248
|
+
return null;
|
|
7249
|
+
}
|
|
7250
|
+
}
|
|
7251
|
+
async function checkForUpdate(opts) {
|
|
7252
|
+
const { current: current2 } = opts;
|
|
7253
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
7254
|
+
const env = opts.env ?? process.env;
|
|
7255
|
+
if (current2 === UNKNOWN_VERSION)
|
|
7256
|
+
return NO_UPDATE(current2);
|
|
7257
|
+
const latest = doNotTrack(env) ? await latestFromNpm(fetchImpl) : await latestFromPing(fetchImpl, current2) ?? await latestFromNpm(fetchImpl);
|
|
7258
|
+
if (!latest)
|
|
7259
|
+
return NO_UPDATE(current2);
|
|
7260
|
+
return {
|
|
7261
|
+
current: current2,
|
|
7262
|
+
latest,
|
|
7263
|
+
updateAvailable: compareSemver(latest, current2) > 0,
|
|
7264
|
+
command: UPDATE_COMMAND
|
|
7265
|
+
};
|
|
7266
|
+
}
|
|
7267
|
+
async function getUpdateInfo(current2, fetchImpl) {
|
|
7268
|
+
if (cache)
|
|
7269
|
+
return cache;
|
|
7270
|
+
const info = await checkForUpdate({ current: current2, fetchImpl });
|
|
7271
|
+
if (info.latest !== null)
|
|
7272
|
+
cache = info;
|
|
7273
|
+
return info;
|
|
7274
|
+
}
|
|
7275
|
+
var PACKAGE_NAME = "runcastle", UPDATE_COMMAND, PING_URL = "https://ping.runcastle.dev/ping", NPM_LATEST_URL, NO_UPDATE = (current2) => ({
|
|
7276
|
+
current: current2,
|
|
7277
|
+
latest: null,
|
|
7278
|
+
updateAvailable: false,
|
|
7279
|
+
command: UPDATE_COMMAND
|
|
7280
|
+
}), cache;
|
|
7281
|
+
var init_update_check = __esm(() => {
|
|
7282
|
+
init_version();
|
|
7283
|
+
init_install_id();
|
|
7284
|
+
UPDATE_COMMAND = `bun add -g ${PACKAGE_NAME}@latest`;
|
|
7285
|
+
NPM_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
7286
|
+
});
|
|
7287
|
+
|
|
7049
7288
|
// src/trpc/context.ts
|
|
7050
7289
|
import { initTRPC } from "@trpc/server";
|
|
7051
7290
|
var t, errorMapping, router, publicProcedure, createCallerFactory;
|
|
@@ -7094,11 +7333,11 @@ var init_events2 = __esm(() => {
|
|
|
7094
7333
|
});
|
|
7095
7334
|
|
|
7096
7335
|
// src/launcher/artifacts.ts
|
|
7097
|
-
import { mkdirSync as
|
|
7098
|
-
import { fileURLToPath as
|
|
7099
|
-
import { join as
|
|
7336
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync3 } from "fs";
|
|
7337
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7338
|
+
import { join as join13 } from "path";
|
|
7100
7339
|
function hookClientPath() {
|
|
7101
|
-
return resolveAsset(ASSET_ENV.hookClient,
|
|
7340
|
+
return resolveAsset(ASSET_ENV.hookClient, fileURLToPath5(new URL("./hook-client.ts", import.meta.url)));
|
|
7102
7341
|
}
|
|
7103
7342
|
function serverUrlFor(config2) {
|
|
7104
7343
|
return `http://localhost:${config2.serverPort}`;
|
|
@@ -7520,16 +7759,16 @@ function renderMcpConfig(session, config2) {
|
|
|
7520
7759
|
async function writeSessionArtifacts(input) {
|
|
7521
7760
|
const { session, feature, config: config2, waypoint, prepare, projectBrief, lap } = input;
|
|
7522
7761
|
const dir = sessionDir(session.id);
|
|
7523
|
-
|
|
7524
|
-
const systemPromptPath =
|
|
7525
|
-
const settingsPath =
|
|
7526
|
-
const mcpConfigPath =
|
|
7762
|
+
mkdirSync7(dir, { recursive: true });
|
|
7763
|
+
const systemPromptPath = join13(dir, "system-prompt.md");
|
|
7764
|
+
const settingsPath = join13(dir, "settings.json");
|
|
7765
|
+
const mcpConfigPath = join13(dir, "mcp.json");
|
|
7527
7766
|
const systemPrompt = feature ? renderSystemPrompt(feature, session.kind, waypoint, lap) : prepare ? renderPreparePrompt(prepare) : projectBrief ? renderProjectPrompt(projectBrief) : (() => {
|
|
7528
7767
|
throw new Error(`session ${session.id} has no feature and no project-session brief`);
|
|
7529
7768
|
})();
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7769
|
+
writeFileSync3(systemPromptPath, systemPrompt, "utf8");
|
|
7770
|
+
writeFileSync3(settingsPath, JSON.stringify(renderSettings(hookClientPath(), session.kind), null, 2), "utf8");
|
|
7771
|
+
writeFileSync3(mcpConfigPath, JSON.stringify(renderMcpConfig(session, config2), null, 2), "utf8");
|
|
7533
7772
|
return { systemPromptPath, settingsPath, mcpConfigPath };
|
|
7534
7773
|
}
|
|
7535
7774
|
var RUNCASTLE_MCP_ALLOW_RULES, SESSION_BASH_READ_RULES, SESSION_BASH_WRITE_RULES, SESSION_BASH_ALLOW_RULES, SESSION_START_SOURCES;
|
|
@@ -7573,9 +7812,9 @@ var init_artifacts = __esm(() => {
|
|
|
7573
7812
|
});
|
|
7574
7813
|
|
|
7575
7814
|
// src/launcher/launcher.ts
|
|
7576
|
-
import { existsSync as
|
|
7577
|
-
import { dirname as
|
|
7578
|
-
import { fileURLToPath as
|
|
7815
|
+
import { existsSync as existsSync14 } from "fs";
|
|
7816
|
+
import { dirname as dirname7, join as join14, resolve as resolve9 } from "path";
|
|
7817
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
7579
7818
|
import { and as and9, eq as eq11 } from "drizzle-orm";
|
|
7580
7819
|
function ptyExitMessage(exitCode) {
|
|
7581
7820
|
const label = typeof exitCode === "number" ? String(exitCode) : "unknown";
|
|
@@ -7607,23 +7846,23 @@ function resolveClaudeExecutable() {
|
|
|
7607
7846
|
function claudeSpawnTarget(claudeArgs) {
|
|
7608
7847
|
return spawnTargetFor(resolveClaudeExecutable(), claudeArgs);
|
|
7609
7848
|
}
|
|
7610
|
-
function resolvePluginDir(fromDir =
|
|
7611
|
-
const rel =
|
|
7849
|
+
function resolvePluginDir(fromDir = dirname7(fileURLToPath6(import.meta.url))) {
|
|
7850
|
+
const rel = join14("packages", "skills", "packs", "runcastle");
|
|
7612
7851
|
const override = process.env[SKILLS_DIR_ENV];
|
|
7613
7852
|
if (override) {
|
|
7614
|
-
const dir2 =
|
|
7615
|
-
if (
|
|
7853
|
+
const dir2 = join14(resolve9(override), "packs", "runcastle");
|
|
7854
|
+
if (existsSync14(dir2))
|
|
7616
7855
|
return dir2;
|
|
7617
7856
|
throw new Error(`${SKILLS_DIR_ENV}=${override} has no plugin dir at ${dir2}`);
|
|
7618
7857
|
}
|
|
7619
7858
|
const searched = [];
|
|
7620
7859
|
let dir = fromDir;
|
|
7621
7860
|
for (let i = 0;i < 8; i += 1) {
|
|
7622
|
-
const candidate =
|
|
7861
|
+
const candidate = join14(dir, rel);
|
|
7623
7862
|
searched.push(candidate);
|
|
7624
|
-
if (
|
|
7863
|
+
if (existsSync14(candidate))
|
|
7625
7864
|
return candidate;
|
|
7626
|
-
const parent =
|
|
7865
|
+
const parent = dirname7(dir);
|
|
7627
7866
|
if (parent === dir)
|
|
7628
7867
|
break;
|
|
7629
7868
|
dir = parent;
|
|
@@ -7692,6 +7931,8 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
7692
7931
|
const session = createSessionRow(ctx, {
|
|
7693
7932
|
featureId: feature.id,
|
|
7694
7933
|
kind: input.kind,
|
|
7934
|
+
purpose: input.purpose,
|
|
7935
|
+
purposeData: input.purposeData,
|
|
7695
7936
|
worktreePath
|
|
7696
7937
|
});
|
|
7697
7938
|
const plan = planKickoff({ kind: input.kind, lap: feature.lap, kickoffLine: input.kickoffLine });
|
|
@@ -8139,7 +8380,9 @@ var init_feature = __esm(() => {
|
|
|
8139
8380
|
launchSession: publicProcedure.input(z6.object({
|
|
8140
8381
|
featureId: z6.string(),
|
|
8141
8382
|
kind: SessionKind,
|
|
8142
|
-
kickoffLine: z6.string().min(1).optional()
|
|
8383
|
+
kickoffLine: z6.string().min(1).optional(),
|
|
8384
|
+
purpose: SessionPurpose.optional(),
|
|
8385
|
+
purposeData: MergeBranchPair.optional()
|
|
8143
8386
|
})).mutation(({ ctx, input }) => launchSession(ctx, input)),
|
|
8144
8387
|
workWaypoint: publicProcedure.input(z6.object({ featureId: z6.string(), waypointId: z6.string(), endLive: z6.boolean().optional() })).mutation(({ ctx, input }) => workWaypoint(ctx, input)),
|
|
8145
8388
|
converge: publicProcedure.input(z6.object({ featureId: z6.string(), overrideReason: z6.string().min(1).optional() })).mutation(({ ctx, input }) => converge(ctx, { featureId: input.featureId, overrideReason: input.overrideReason })),
|
|
@@ -8190,9 +8433,9 @@ var init_feature = __esm(() => {
|
|
|
8190
8433
|
});
|
|
8191
8434
|
|
|
8192
8435
|
// src/services/fsbrowse.ts
|
|
8193
|
-
import { existsSync as
|
|
8436
|
+
import { existsSync as existsSync15, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
|
|
8194
8437
|
import { homedir as homedir2 } from "os";
|
|
8195
|
-
import { basename, dirname as
|
|
8438
|
+
import { basename, dirname as dirname8, isAbsolute as isAbsolute2, join as join15, resolve as resolve10, sep as sep3 } from "path";
|
|
8196
8439
|
function expandPath(input, home = homedir2()) {
|
|
8197
8440
|
const trimmed = input.trim();
|
|
8198
8441
|
if (trimmed === "")
|
|
@@ -8201,12 +8444,12 @@ function expandPath(input, home = homedir2()) {
|
|
|
8201
8444
|
if (out === "~")
|
|
8202
8445
|
out = home;
|
|
8203
8446
|
else if (out.startsWith("~/") || out.startsWith("~\\"))
|
|
8204
|
-
out =
|
|
8447
|
+
out = join15(home, out.slice(2));
|
|
8205
8448
|
return resolve10(out);
|
|
8206
8449
|
}
|
|
8207
8450
|
function looksLikeRepo(dir) {
|
|
8208
8451
|
try {
|
|
8209
|
-
return
|
|
8452
|
+
return existsSync15(join15(dir, ".git"));
|
|
8210
8453
|
} catch {
|
|
8211
8454
|
return false;
|
|
8212
8455
|
}
|
|
@@ -8217,20 +8460,20 @@ function listRoots(home = homedir2()) {
|
|
|
8217
8460
|
for (let code = 67;code <= 90; code++) {
|
|
8218
8461
|
const drive = `${String.fromCharCode(code)}:${sep3}`;
|
|
8219
8462
|
try {
|
|
8220
|
-
if (
|
|
8463
|
+
if (existsSync15(drive))
|
|
8221
8464
|
roots.push({ label: drive.replace(/\\$/, ""), path: drive, kind: "drive" });
|
|
8222
8465
|
} catch {}
|
|
8223
8466
|
}
|
|
8224
8467
|
} else {
|
|
8225
8468
|
roots.push({ label: "/", path: "/", kind: "drive" });
|
|
8226
8469
|
}
|
|
8227
|
-
if (
|
|
8470
|
+
if (existsSync15(home))
|
|
8228
8471
|
roots.push({ label: "Home", path: home, kind: "home" });
|
|
8229
8472
|
const seen = new Set(roots.map(pathKey));
|
|
8230
8473
|
for (const name of COMMON_DIRS) {
|
|
8231
|
-
const candidate =
|
|
8474
|
+
const candidate = join15(home, name);
|
|
8232
8475
|
const key = pathKey({ path: candidate });
|
|
8233
|
-
if (seen.has(key) || !
|
|
8476
|
+
if (seen.has(key) || !existsSync15(candidate))
|
|
8234
8477
|
continue;
|
|
8235
8478
|
seen.add(key);
|
|
8236
8479
|
roots.push({ label: name, path: candidate, kind: "common" });
|
|
@@ -8245,7 +8488,7 @@ function crumbsFor(dir) {
|
|
|
8245
8488
|
let cur = dir;
|
|
8246
8489
|
for (;; ) {
|
|
8247
8490
|
out.unshift({ name: basename(cur) || cur, path: cur });
|
|
8248
|
-
const parent =
|
|
8491
|
+
const parent = dirname8(cur);
|
|
8249
8492
|
if (parent === cur)
|
|
8250
8493
|
break;
|
|
8251
8494
|
cur = parent;
|
|
@@ -8253,7 +8496,7 @@ function crumbsFor(dir) {
|
|
|
8253
8496
|
return out;
|
|
8254
8497
|
}
|
|
8255
8498
|
function parentOf(dir) {
|
|
8256
|
-
const parent =
|
|
8499
|
+
const parent = dirname8(dir);
|
|
8257
8500
|
return parent === dir ? null : parent;
|
|
8258
8501
|
}
|
|
8259
8502
|
function browseDir(input, showHidden = false, home = homedir2()) {
|
|
@@ -8261,13 +8504,13 @@ function browseDir(input, showHidden = false, home = homedir2()) {
|
|
|
8261
8504
|
if (!isAbsolute2(target)) {
|
|
8262
8505
|
throw new InvalidInputError(`path is not absolute: ${target}`);
|
|
8263
8506
|
}
|
|
8264
|
-
if (!
|
|
8507
|
+
if (!existsSync15(target)) {
|
|
8265
8508
|
throw new InvalidInputError(`path does not exist: ${target}`);
|
|
8266
8509
|
}
|
|
8267
8510
|
let dir = target;
|
|
8268
8511
|
try {
|
|
8269
8512
|
if (!statSync2(target).isDirectory())
|
|
8270
|
-
dir =
|
|
8513
|
+
dir = dirname8(target);
|
|
8271
8514
|
} catch (e) {
|
|
8272
8515
|
throw new InvalidInputError(`cannot read path: ${target} (${errMsg4(e)})`);
|
|
8273
8516
|
}
|
|
@@ -8289,7 +8532,7 @@ function browseDir(input, showHidden = false, home = homedir2()) {
|
|
|
8289
8532
|
continue;
|
|
8290
8533
|
if (!showHidden && name.startsWith("."))
|
|
8291
8534
|
continue;
|
|
8292
|
-
const path =
|
|
8535
|
+
const path = join15(dir, name);
|
|
8293
8536
|
const isSymlink = dirent.isSymbolicLink();
|
|
8294
8537
|
if (isSymlink && !showHidden)
|
|
8295
8538
|
continue;
|
|
@@ -8326,8 +8569,8 @@ var init_fsbrowse = __esm(() => {
|
|
|
8326
8569
|
});
|
|
8327
8570
|
|
|
8328
8571
|
// src/services/projects.ts
|
|
8329
|
-
import { existsSync as
|
|
8330
|
-
import { basename as basename2, join as
|
|
8572
|
+
import { existsSync as existsSync16 } from "fs";
|
|
8573
|
+
import { basename as basename2, join as join16 } from "path";
|
|
8331
8574
|
import { eq as eq12, isNull } from "drizzle-orm";
|
|
8332
8575
|
function listProjects(ctx) {
|
|
8333
8576
|
return ctx.db.select().from(projects).where(isNull(projects.closedAt)).all().map(rowToProject);
|
|
@@ -8408,7 +8651,7 @@ async function assertRepoTolerant(ctx, repoPath) {
|
|
|
8408
8651
|
} catch (e) {
|
|
8409
8652
|
if (!isNotImplemented(e))
|
|
8410
8653
|
throw e;
|
|
8411
|
-
if (!
|
|
8654
|
+
if (!existsSync16(join16(repoPath, ".git"))) {
|
|
8412
8655
|
throw new InvalidInputError(`not a git repository: ${repoPath}`);
|
|
8413
8656
|
}
|
|
8414
8657
|
}
|
|
@@ -8481,15 +8724,15 @@ var init_run = __esm(() => {
|
|
|
8481
8724
|
});
|
|
8482
8725
|
|
|
8483
8726
|
// src/services/settings.ts
|
|
8484
|
-
import { existsSync as
|
|
8485
|
-
import { dirname as
|
|
8727
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
8728
|
+
import { dirname as dirname9 } from "path";
|
|
8486
8729
|
import * as z9 from "zod";
|
|
8487
8730
|
import { eq as eq13 } from "drizzle-orm";
|
|
8488
8731
|
function readRawConfig(configFile) {
|
|
8489
|
-
if (!
|
|
8732
|
+
if (!existsSync17(configFile))
|
|
8490
8733
|
return {};
|
|
8491
8734
|
try {
|
|
8492
|
-
const parsed = JSON.parse(
|
|
8735
|
+
const parsed = JSON.parse(readFileSync8(configFile, "utf8"));
|
|
8493
8736
|
if (typeof parsed !== "object" || parsed === null)
|
|
8494
8737
|
return {};
|
|
8495
8738
|
return foldLegacyModelConfig(parsed);
|
|
@@ -8628,8 +8871,8 @@ function updateSettings(ctx, input, io = {}) {
|
|
|
8628
8871
|
function writeGlobal(configFile, configKey, value) {
|
|
8629
8872
|
const raw = readRawConfig(configFile);
|
|
8630
8873
|
raw[configKey] = value;
|
|
8631
|
-
|
|
8632
|
-
|
|
8874
|
+
mkdirSync8(dirname9(configFile), { recursive: true });
|
|
8875
|
+
writeFileSync4(configFile, `${JSON.stringify(raw, null, 2)}
|
|
8633
8876
|
`);
|
|
8634
8877
|
}
|
|
8635
8878
|
function updateStepModel(ctx, input, configFile, io) {
|
|
@@ -8667,8 +8910,8 @@ function writeStepModel(configFile, step, value) {
|
|
|
8667
8910
|
else
|
|
8668
8911
|
stepModels[step] = value;
|
|
8669
8912
|
raw.stepModels = stepModels;
|
|
8670
|
-
|
|
8671
|
-
|
|
8913
|
+
mkdirSync8(dirname9(configFile), { recursive: true });
|
|
8914
|
+
writeFileSync4(configFile, `${JSON.stringify(raw, null, 2)}
|
|
8672
8915
|
`);
|
|
8673
8916
|
}
|
|
8674
8917
|
function field(view, key) {
|
|
@@ -9045,9 +9288,9 @@ var init_system_exec = __esm(() => {
|
|
|
9045
9288
|
});
|
|
9046
9289
|
|
|
9047
9290
|
// src/services/setup.ts
|
|
9048
|
-
import { copyFileSync, existsSync as
|
|
9049
|
-
import { dirname as
|
|
9050
|
-
import { fileURLToPath as
|
|
9291
|
+
import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync9, readdirSync as readdirSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
9292
|
+
import { dirname as dirname10, join as join17 } from "path";
|
|
9293
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
9051
9294
|
async function writeGitIdentity(exec, identity) {
|
|
9052
9295
|
const name = identity.name.trim();
|
|
9053
9296
|
const email = identity.email.trim();
|
|
@@ -9122,16 +9365,16 @@ async function resolveRuntime(exec, preferred) {
|
|
|
9122
9365
|
function resolveSandcastleBin() {
|
|
9123
9366
|
try {
|
|
9124
9367
|
const mainUrl = import.meta.resolve("@ai-hero/sandcastle");
|
|
9125
|
-
let dir =
|
|
9368
|
+
let dir = dirname10(fileURLToPath7(mainUrl));
|
|
9126
9369
|
for (let hops = 0;hops < 6; hops++) {
|
|
9127
|
-
const manifestPath =
|
|
9128
|
-
if (
|
|
9129
|
-
const manifest = JSON.parse(
|
|
9370
|
+
const manifestPath = join17(dir, "package.json");
|
|
9371
|
+
if (existsSync18(manifestPath)) {
|
|
9372
|
+
const manifest = JSON.parse(readFileSync9(manifestPath, "utf8"));
|
|
9130
9373
|
if (manifest.name === "@ai-hero/sandcastle" && manifest.bin?.sandcastle) {
|
|
9131
|
-
return
|
|
9374
|
+
return join17(dir, manifest.bin.sandcastle);
|
|
9132
9375
|
}
|
|
9133
9376
|
}
|
|
9134
|
-
const parent =
|
|
9377
|
+
const parent = dirname10(dir);
|
|
9135
9378
|
if (parent === dir)
|
|
9136
9379
|
break;
|
|
9137
9380
|
dir = parent;
|
|
@@ -9153,21 +9396,21 @@ function terminalSpec(kind, opts) {
|
|
|
9153
9396
|
};
|
|
9154
9397
|
}
|
|
9155
9398
|
function sandcastleTemplateDir() {
|
|
9156
|
-
return resolveAsset(ASSET_ENV.sandcastleTemplate,
|
|
9399
|
+
return resolveAsset(ASSET_ENV.sandcastleTemplate, fileURLToPath7(new URL("../assets/sandcastle", import.meta.url)));
|
|
9157
9400
|
}
|
|
9158
9401
|
function scaffoldSandcastleConfig(templateDir, targetDir) {
|
|
9159
|
-
const dir =
|
|
9160
|
-
if (
|
|
9402
|
+
const dir = join17(targetDir, ".sandcastle");
|
|
9403
|
+
if (existsSync18(dir))
|
|
9161
9404
|
return { scaffolded: false, dir };
|
|
9162
|
-
|
|
9405
|
+
mkdirSync9(dir, { recursive: true });
|
|
9163
9406
|
for (const name of readdirSync6(templateDir)) {
|
|
9164
|
-
copyFileSync(
|
|
9407
|
+
copyFileSync(join17(templateDir, name), join17(dir, name));
|
|
9165
9408
|
}
|
|
9166
9409
|
return { scaffolded: true, dir };
|
|
9167
9410
|
}
|
|
9168
9411
|
function prepareSandboxBuildContext() {
|
|
9169
9412
|
const target = sandboxBuildDir();
|
|
9170
|
-
|
|
9413
|
+
mkdirSync9(target, { recursive: true });
|
|
9171
9414
|
scaffoldSandcastleConfig(sandcastleTemplateDir(), target);
|
|
9172
9415
|
return target;
|
|
9173
9416
|
}
|
|
@@ -9203,10 +9446,10 @@ function createTokenVerifier(exec) {
|
|
|
9203
9446
|
function fileAfkTokenIo(exec) {
|
|
9204
9447
|
const path = envPath();
|
|
9205
9448
|
return {
|
|
9206
|
-
read: () =>
|
|
9449
|
+
read: () => existsSync18(path) ? readFileSync9(path, "utf8") : "",
|
|
9207
9450
|
write: (content) => {
|
|
9208
|
-
|
|
9209
|
-
|
|
9451
|
+
mkdirSync9(dirname10(path), { recursive: true });
|
|
9452
|
+
writeFileSync5(path, content, "utf8");
|
|
9210
9453
|
},
|
|
9211
9454
|
verify: createTokenVerifier(exec)
|
|
9212
9455
|
};
|
|
@@ -9259,110 +9502,6 @@ var init_setup2 = __esm(() => {
|
|
|
9259
9502
|
});
|
|
9260
9503
|
});
|
|
9261
9504
|
|
|
9262
|
-
// src/version.ts
|
|
9263
|
-
import { readFileSync as readFileSync8 } from "fs";
|
|
9264
|
-
import { dirname as dirname9, join as join17 } from "path";
|
|
9265
|
-
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
9266
|
-
function runcastleVersion() {
|
|
9267
|
-
if (cached2)
|
|
9268
|
-
return cached2;
|
|
9269
|
-
const here = dirname9(fileURLToPath7(import.meta.url));
|
|
9270
|
-
for (const pkgPath of [join17(here, "..", "package.json"), join17(here, "package.json")]) {
|
|
9271
|
-
try {
|
|
9272
|
-
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
9273
|
-
if (typeof pkg.version === "string") {
|
|
9274
|
-
cached2 = pkg.version;
|
|
9275
|
-
return cached2;
|
|
9276
|
-
}
|
|
9277
|
-
} catch {}
|
|
9278
|
-
}
|
|
9279
|
-
cached2 = UNKNOWN_VERSION;
|
|
9280
|
-
return cached2;
|
|
9281
|
-
}
|
|
9282
|
-
var cached2, UNKNOWN_VERSION = "0.0.0";
|
|
9283
|
-
var init_version = () => {};
|
|
9284
|
-
|
|
9285
|
-
// src/services/update-check.ts
|
|
9286
|
-
function parse(version) {
|
|
9287
|
-
const cleaned = version.trim().replace(/^v/, "");
|
|
9288
|
-
const [core = "", pre = ""] = cleaned.split("-", 2);
|
|
9289
|
-
const release2 = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
9290
|
-
return { release: release2, pre: pre ? pre.split(".") : [] };
|
|
9291
|
-
}
|
|
9292
|
-
function compareSemver(a, b) {
|
|
9293
|
-
const pa = parse(a);
|
|
9294
|
-
const pb = parse(b);
|
|
9295
|
-
const len = Math.max(pa.release.length, pb.release.length);
|
|
9296
|
-
for (let i = 0;i < len; i++) {
|
|
9297
|
-
const diff = (pa.release[i] ?? 0) - (pb.release[i] ?? 0);
|
|
9298
|
-
if (diff !== 0)
|
|
9299
|
-
return diff;
|
|
9300
|
-
}
|
|
9301
|
-
if (pa.pre.length === 0 && pb.pre.length === 0)
|
|
9302
|
-
return 0;
|
|
9303
|
-
if (pa.pre.length === 0)
|
|
9304
|
-
return 1;
|
|
9305
|
-
if (pb.pre.length === 0)
|
|
9306
|
-
return -1;
|
|
9307
|
-
const preLen = Math.max(pa.pre.length, pb.pre.length);
|
|
9308
|
-
for (let i = 0;i < preLen; i++) {
|
|
9309
|
-
const x = pa.pre[i];
|
|
9310
|
-
const y = pb.pre[i];
|
|
9311
|
-
if (x === undefined)
|
|
9312
|
-
return -1;
|
|
9313
|
-
if (y === undefined)
|
|
9314
|
-
return 1;
|
|
9315
|
-
const nx = Number(x);
|
|
9316
|
-
const ny = Number(y);
|
|
9317
|
-
const bothNumeric = !Number.isNaN(nx) && !Number.isNaN(ny);
|
|
9318
|
-
const cmp = bothNumeric ? nx - ny : x < y ? -1 : x > y ? 1 : 0;
|
|
9319
|
-
if (cmp !== 0)
|
|
9320
|
-
return cmp;
|
|
9321
|
-
}
|
|
9322
|
-
return 0;
|
|
9323
|
-
}
|
|
9324
|
-
async function checkForUpdate(opts) {
|
|
9325
|
-
const { current: current2 } = opts;
|
|
9326
|
-
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
9327
|
-
if (current2 === UNKNOWN_VERSION)
|
|
9328
|
-
return NO_UPDATE(current2);
|
|
9329
|
-
try {
|
|
9330
|
-
const res = await fetchImpl(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`);
|
|
9331
|
-
if (!res.ok)
|
|
9332
|
-
return NO_UPDATE(current2);
|
|
9333
|
-
const body = await res.json();
|
|
9334
|
-
const latest = typeof body.version === "string" ? body.version : null;
|
|
9335
|
-
if (!latest)
|
|
9336
|
-
return NO_UPDATE(current2);
|
|
9337
|
-
return {
|
|
9338
|
-
current: current2,
|
|
9339
|
-
latest,
|
|
9340
|
-
updateAvailable: compareSemver(latest, current2) > 0,
|
|
9341
|
-
command: UPDATE_COMMAND
|
|
9342
|
-
};
|
|
9343
|
-
} catch {
|
|
9344
|
-
return NO_UPDATE(current2);
|
|
9345
|
-
}
|
|
9346
|
-
}
|
|
9347
|
-
async function getUpdateInfo(current2, fetchImpl) {
|
|
9348
|
-
if (cache)
|
|
9349
|
-
return cache;
|
|
9350
|
-
const info = await checkForUpdate({ current: current2, fetchImpl });
|
|
9351
|
-
if (info.latest !== null)
|
|
9352
|
-
cache = info;
|
|
9353
|
-
return info;
|
|
9354
|
-
}
|
|
9355
|
-
var PACKAGE_NAME = "runcastle", UPDATE_COMMAND, NO_UPDATE = (current2) => ({
|
|
9356
|
-
current: current2,
|
|
9357
|
-
latest: null,
|
|
9358
|
-
updateAvailable: false,
|
|
9359
|
-
command: UPDATE_COMMAND
|
|
9360
|
-
}), cache;
|
|
9361
|
-
var init_update_check = __esm(() => {
|
|
9362
|
-
init_version();
|
|
9363
|
-
UPDATE_COMMAND = `bun add -g ${PACKAGE_NAME}@latest`;
|
|
9364
|
-
});
|
|
9365
|
-
|
|
9366
9505
|
// src/trpc/routers/system.ts
|
|
9367
9506
|
var systemRouter;
|
|
9368
9507
|
var init_system = __esm(() => {
|
|
@@ -9376,8 +9515,8 @@ var init_system = __esm(() => {
|
|
|
9376
9515
|
});
|
|
9377
9516
|
|
|
9378
9517
|
// src/services/test-notes.ts
|
|
9379
|
-
import { mkdirSync as
|
|
9380
|
-
import { dirname as
|
|
9518
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync6 } from "fs";
|
|
9519
|
+
import { dirname as dirname11 } from "path";
|
|
9381
9520
|
import { asc as asc4, eq as eq14 } from "drizzle-orm";
|
|
9382
9521
|
function rowToNote(row) {
|
|
9383
9522
|
return {
|
|
@@ -9522,8 +9661,8 @@ function renderTestNotes(ctx, feature) {
|
|
|
9522
9661
|
lines.push(noteLine(ctx, note));
|
|
9523
9662
|
}
|
|
9524
9663
|
const path = featureDocPath(projectForFeature(ctx, feature), feature, "test-notes.md");
|
|
9525
|
-
|
|
9526
|
-
|
|
9664
|
+
mkdirSync10(dirname11(path), { recursive: true });
|
|
9665
|
+
writeFileSync6(path, `${lines.join(`
|
|
9527
9666
|
`)}
|
|
9528
9667
|
`, "utf8");
|
|
9529
9668
|
}
|
|
@@ -9722,6 +9861,7 @@ async function startServer() {
|
|
|
9722
9861
|
websocket: terminalWebSocket
|
|
9723
9862
|
});
|
|
9724
9863
|
console.log(`runcastle${process.env.RUNCASTLE_DEV ? " [dev]" : ""} server listening on ` + `http://localhost:${config2.serverPort} \u2014 data dir: ${dataDir()}`);
|
|
9864
|
+
getUpdateInfo(runcastleVersion()).catch(() => {});
|
|
9725
9865
|
const SHUTDOWN_KEY = Symbol.for("runcastle.shutdown.wired");
|
|
9726
9866
|
const g = globalThis;
|
|
9727
9867
|
if (!g[SHUTDOWN_KEY]) {
|
|
@@ -9747,7 +9887,9 @@ var init_src2 = __esm(() => {
|
|
|
9747
9887
|
init_hooks();
|
|
9748
9888
|
init_stream();
|
|
9749
9889
|
init_web();
|
|
9890
|
+
init_update_check();
|
|
9750
9891
|
init_router();
|
|
9892
|
+
init_version();
|
|
9751
9893
|
init_reconcile_runs();
|
|
9752
9894
|
});
|
|
9753
9895
|
|
|
@@ -9805,7 +9947,7 @@ __export(exports_cli, {
|
|
|
9805
9947
|
resolveDoctorEnv: () => resolveDoctorEnv,
|
|
9806
9948
|
parseMode: () => parseMode
|
|
9807
9949
|
});
|
|
9808
|
-
import { existsSync as
|
|
9950
|
+
import { existsSync as existsSync19, readFileSync as readFileSync10 } from "fs";
|
|
9809
9951
|
function parseMode(argv) {
|
|
9810
9952
|
return argv.includes("--gate") || argv.includes("--boot") ? "gate" : "diagnostic";
|
|
9811
9953
|
}
|
|
@@ -9813,8 +9955,8 @@ function envWithToken() {
|
|
|
9813
9955
|
const merged = { ...process.env };
|
|
9814
9956
|
try {
|
|
9815
9957
|
const path = envPath();
|
|
9816
|
-
if (
|
|
9817
|
-
const fromFile = parseEnvFile(
|
|
9958
|
+
if (existsSync19(path)) {
|
|
9959
|
+
const fromFile = parseEnvFile(readFileSync10(path, "utf8")).CLAUDE_CODE_OAUTH_TOKEN;
|
|
9818
9960
|
if (fromFile && fromFile.length > 0)
|
|
9819
9961
|
merged.CLAUDE_CODE_OAUTH_TOKEN = fromFile;
|
|
9820
9962
|
}
|
|
@@ -9856,7 +9998,7 @@ var init_cli = __esm(() => {
|
|
|
9856
9998
|
// src/bin/runcastle.ts
|
|
9857
9999
|
init_asset_paths();
|
|
9858
10000
|
init_version();
|
|
9859
|
-
import { dirname as
|
|
10001
|
+
import { dirname as dirname12, resolve as resolve11 } from "path";
|
|
9860
10002
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
9861
10003
|
function parseCommand(argv) {
|
|
9862
10004
|
const [first, ...rest] = argv;
|
|
@@ -9881,7 +10023,7 @@ Usage:
|
|
|
9881
10023
|
runcastle --help Show this help
|
|
9882
10024
|
`;
|
|
9883
10025
|
async function main(argv) {
|
|
9884
|
-
applyInstalledAssetEnv(resolve11(
|
|
10026
|
+
applyInstalledAssetEnv(resolve11(dirname12(fileURLToPath8(import.meta.url)), ".."));
|
|
9885
10027
|
const { command, args } = parseCommand(argv);
|
|
9886
10028
|
switch (command) {
|
|
9887
10029
|
case "version":
|