runcastle 1.4.0 → 1.4.2
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 +402 -182
- package/index.js +402 -182
- package/package.json +1 -1
- package/web/assets/index-D7diTU3w.css +1 -0
- package/web/assets/index-Ih8W7ADS.js +187 -0
- package/web/index.html +2 -2
- package/web/assets/index-CSp0YX_G.css +0 -1
- package/web/assets/index-DMGQBgZN.js +0 -187
package/bin/runcastle.js
CHANGED
|
@@ -909,14 +909,32 @@ var init_src = __esm(() => {
|
|
|
909
909
|
});
|
|
910
910
|
|
|
911
911
|
// ../core/src/paths.ts
|
|
912
|
-
import { homedir } from "os";
|
|
912
|
+
import { homedir, userInfo } from "os";
|
|
913
913
|
import { join, resolve } from "path";
|
|
914
914
|
function prodDataDir() {
|
|
915
915
|
return join(homedir(), ".runcastle");
|
|
916
916
|
}
|
|
917
917
|
function dataDir() {
|
|
918
918
|
const override = process.env.RUNCASTLE_DATA_DIR;
|
|
919
|
-
|
|
919
|
+
const dir = override ? resolve(override) : prodDataDir();
|
|
920
|
+
if (process.env.VITEST && sameDataDir(dir, join(realHomeDir(), ".runcastle"))) {
|
|
921
|
+
throw new Error(`dataDir() resolved to the real ${dir} under Vitest; a test must run against a temp data dir (useDataDir / withTempDataDir)`);
|
|
922
|
+
}
|
|
923
|
+
return dir;
|
|
924
|
+
}
|
|
925
|
+
function realHomeDir() {
|
|
926
|
+
try {
|
|
927
|
+
return userInfo().homedir;
|
|
928
|
+
} catch {
|
|
929
|
+
return homedir();
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
function sameDataDir(a, b) {
|
|
933
|
+
const canon = (p) => {
|
|
934
|
+
const abs = resolve(p).replace(/[\\/]+$/, "");
|
|
935
|
+
return process.platform === "win32" ? abs.toLowerCase().replace(/\//g, "\\") : abs;
|
|
936
|
+
};
|
|
937
|
+
return canon(a) === canon(b);
|
|
920
938
|
}
|
|
921
939
|
function dbPath() {
|
|
922
940
|
return join(dataDir(), "runcastle.db");
|
|
@@ -957,8 +975,11 @@ function projectNotePath(noteId) {
|
|
|
957
975
|
function attachmentRelPath(noteId) {
|
|
958
976
|
return `${ATTACHMENTS_DIR}/${noteId}.png`;
|
|
959
977
|
}
|
|
978
|
+
function worktreesRoot() {
|
|
979
|
+
return join(dataDir(), "worktrees");
|
|
980
|
+
}
|
|
960
981
|
function projectWorktreesDir(projectId) {
|
|
961
|
-
return join(
|
|
982
|
+
return join(worktreesRoot(), projectId);
|
|
962
983
|
}
|
|
963
984
|
function worktreeDir(projectId, slug) {
|
|
964
985
|
return join(projectWorktreesDir(projectId), slug);
|
|
@@ -3032,6 +3053,7 @@ import {
|
|
|
3032
3053
|
mkdtempSync,
|
|
3033
3054
|
readFileSync as readFileSync3,
|
|
3034
3055
|
realpathSync,
|
|
3056
|
+
rmdirSync,
|
|
3035
3057
|
rmSync,
|
|
3036
3058
|
writeFileSync
|
|
3037
3059
|
} from "fs";
|
|
@@ -3269,7 +3291,21 @@ async function reclaimTalkBranch(g, repoPath, worktreePath, branch) {
|
|
|
3269
3291
|
throw new InvalidInputError(`${branch} is checked out at ${strays[0] ?? repoPath}; end the test drive or remove that worktree`);
|
|
3270
3292
|
}
|
|
3271
3293
|
async function addWorktree(g, worktreePath, branch, label) {
|
|
3272
|
-
|
|
3294
|
+
const parent = dirname2(worktreePath);
|
|
3295
|
+
mkdirSync2(parent, { recursive: true });
|
|
3296
|
+
try {
|
|
3297
|
+
return await addWorktreeOrRepair(g, worktreePath, branch, label);
|
|
3298
|
+
} catch (e) {
|
|
3299
|
+
removeIfEmpty(parent);
|
|
3300
|
+
throw e;
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
function removeIfEmpty(dir) {
|
|
3304
|
+
try {
|
|
3305
|
+
rmdirSync(dir);
|
|
3306
|
+
} catch {}
|
|
3307
|
+
}
|
|
3308
|
+
async function addWorktreeOrRepair(g, worktreePath, branch, label) {
|
|
3273
3309
|
try {
|
|
3274
3310
|
await g.raw(["worktree", "add", worktreePath, branch]);
|
|
3275
3311
|
return worktreePath;
|
|
@@ -3524,14 +3560,18 @@ async function unexcludePath(repoPath, pattern) {
|
|
|
3524
3560
|
writeFileSync(file, next);
|
|
3525
3561
|
}
|
|
3526
3562
|
async function cleanupBurnWorktree(repoPath, branch, opts = {}) {
|
|
3527
|
-
|
|
3563
|
+
return discardWorktree(repoPath, burnWorktreePath(repoPath, branch), opts);
|
|
3564
|
+
}
|
|
3565
|
+
async function discardWorktree(repoPath, path, opts = {}) {
|
|
3528
3566
|
const attempts = Math.max(1, opts.attempts ?? 3);
|
|
3529
3567
|
const delayMs = opts.delayMs ?? 750;
|
|
3530
|
-
const
|
|
3531
|
-
for (let attempt = 1;attempt <= attempts && existsSync6(path); attempt++) {
|
|
3568
|
+
const gitQuietly = async (args) => {
|
|
3532
3569
|
try {
|
|
3533
|
-
await
|
|
3570
|
+
await git(repoPath).raw(args);
|
|
3534
3571
|
} catch {}
|
|
3572
|
+
};
|
|
3573
|
+
for (let attempt = 1;attempt <= attempts && existsSync6(path); attempt++) {
|
|
3574
|
+
await gitQuietly(["worktree", "remove", "--force", path]);
|
|
3535
3575
|
if (existsSync6(path) && attempt < attempts)
|
|
3536
3576
|
await sleep2(delayMs);
|
|
3537
3577
|
}
|
|
@@ -3540,9 +3580,7 @@ async function cleanupBurnWorktree(repoPath, branch, opts = {}) {
|
|
|
3540
3580
|
rmSync(path, { recursive: true, force: true });
|
|
3541
3581
|
} catch {}
|
|
3542
3582
|
}
|
|
3543
|
-
|
|
3544
|
-
await g.raw(["worktree", "prune"]);
|
|
3545
|
-
} catch {}
|
|
3583
|
+
await gitQuietly(["worktree", "prune"]);
|
|
3546
3584
|
return !existsSync6(path);
|
|
3547
3585
|
}
|
|
3548
3586
|
function sleep2(ms) {
|
|
@@ -3813,19 +3851,19 @@ async function cleanupTempBranches(repoPath) {
|
|
|
3813
3851
|
return { deleted, kept };
|
|
3814
3852
|
}
|
|
3815
3853
|
async function removeTalkWorktree(repoPath, worktreePath) {
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
await g.raw(["worktree", "remove", "--force", worktreePath]);
|
|
3819
|
-
} catch {
|
|
3820
|
-
try {
|
|
3821
|
-
rmSync(worktreePath, { recursive: true, force: true });
|
|
3822
|
-
} catch {}
|
|
3854
|
+
if (!await discardWorktree(repoPath, worktreePath, { attempts: 1 })) {
|
|
3855
|
+
throw new InvalidInputError(`could not remove talk worktree at ${worktreePath} \u2014 a file may be locked; ` + "close anything using it and retry the delete");
|
|
3823
3856
|
}
|
|
3857
|
+
}
|
|
3858
|
+
async function worktreeState(path) {
|
|
3859
|
+
if (!existsSync6(path))
|
|
3860
|
+
return "missing";
|
|
3861
|
+
if (!existsSync6(join6(path, ".git")))
|
|
3862
|
+
return "not-a-checkout";
|
|
3824
3863
|
try {
|
|
3825
|
-
await
|
|
3826
|
-
} catch {
|
|
3827
|
-
|
|
3828
|
-
throw new InvalidInputError(`could not remove talk worktree at ${worktreePath} \u2014 a file may be locked; ` + "close anything using it and retry the delete");
|
|
3864
|
+
return (await git(path).raw(["status", "--porcelain"])).trim() === "" ? "clean" : "dirty";
|
|
3865
|
+
} catch {
|
|
3866
|
+
return "unreadable";
|
|
3829
3867
|
}
|
|
3830
3868
|
}
|
|
3831
3869
|
async function deleteFeatureBranches(repoPath, slug) {
|
|
@@ -4042,6 +4080,7 @@ async function testDrive(ctx, project, feature, action, purpose = "human") {
|
|
|
4042
4080
|
testDriveState = {
|
|
4043
4081
|
kind: "feature",
|
|
4044
4082
|
purpose,
|
|
4083
|
+
projectId: project.id,
|
|
4045
4084
|
featureId: feature.id,
|
|
4046
4085
|
branch,
|
|
4047
4086
|
previousBranch,
|
|
@@ -4445,7 +4484,7 @@ async function detectDbDrift(ctx, project, feature, previousBranch, driveBranch)
|
|
|
4445
4484
|
}
|
|
4446
4485
|
async function mergeFeature(project, feature) {
|
|
4447
4486
|
const target = featureBase(feature);
|
|
4448
|
-
if (testDriveState) {
|
|
4487
|
+
if (testDriveState?.projectId === project.id) {
|
|
4449
4488
|
throw new GateError("Cannot merge while a test drive is active \u2014 stop it first");
|
|
4450
4489
|
}
|
|
4451
4490
|
const g = git(project.repoPath);
|
|
@@ -6949,6 +6988,15 @@ function staleImage(detail, unchecked = "") {
|
|
|
6949
6988
|
fix: "Open Settings \u2192 Burns (Rebuild image)."
|
|
6950
6989
|
};
|
|
6951
6990
|
}
|
|
6991
|
+
function imageUncheckable() {
|
|
6992
|
+
return {
|
|
6993
|
+
...IMAGE_ROW,
|
|
6994
|
+
status: "unhealthy",
|
|
6995
|
+
severity: "error",
|
|
6996
|
+
detail: "cannot inspect the image while the container runtime is not responding",
|
|
6997
|
+
fix: "Get the container runtime answering first (see the container runtime row), then re-run doctor."
|
|
6998
|
+
};
|
|
6999
|
+
}
|
|
6952
7000
|
async function runDoctor(env) {
|
|
6953
7001
|
const { exec } = env;
|
|
6954
7002
|
const processEnv = env.env ?? process.env;
|
|
@@ -6969,7 +7017,8 @@ async function runDoctor(env) {
|
|
|
6969
7017
|
severity: required.has(runtime) ? "error" : "info"
|
|
6970
7018
|
}));
|
|
6971
7019
|
}
|
|
6972
|
-
|
|
7020
|
+
const containerRuntime = await containerRuntimeProbe(exec);
|
|
7021
|
+
results.push(await gitIdentityProbe(exec, env.cwd), containerRuntime, RUNTIME_DOWN.has(containerRuntime.status) ? imageUncheckable() : await sandcastleImageProbe({
|
|
6973
7022
|
exec,
|
|
6974
7023
|
imageName,
|
|
6975
7024
|
burnerDockerfile,
|
|
@@ -6990,7 +7039,7 @@ function exitCodeFor(report, mode) {
|
|
|
6990
7039
|
return report.tier1Ok ? 0 : 1;
|
|
6991
7040
|
return report.ok ? 0 : 1;
|
|
6992
7041
|
}
|
|
6993
|
-
var RUNTIME_SPECS, IMAGE_ROW;
|
|
7042
|
+
var RUNTIME_SPECS, IMAGE_ROW, RUNTIME_DOWN;
|
|
6994
7043
|
var init_doctor = __esm(() => {
|
|
6995
7044
|
init_src();
|
|
6996
7045
|
init_asset_paths();
|
|
@@ -7029,6 +7078,7 @@ var init_doctor = __esm(() => {
|
|
|
7029
7078
|
}
|
|
7030
7079
|
};
|
|
7031
7080
|
IMAGE_ROW = { id: "sandcastle-image", label: "Sandcastle container image", tier: 2 };
|
|
7081
|
+
RUNTIME_DOWN = new Set(["daemon-dead", "machine-stopped"]);
|
|
7032
7082
|
});
|
|
7033
7083
|
|
|
7034
7084
|
// src/launcher/runtimes/codex.ts
|
|
@@ -7605,9 +7655,111 @@ var init_runtime = __esm(() => {
|
|
|
7605
7655
|
init_paths();
|
|
7606
7656
|
});
|
|
7607
7657
|
|
|
7658
|
+
// src/services/feature-worktrees.ts
|
|
7659
|
+
import { readdirSync as readdirSync3, rmSync as rmSync2 } from "fs";
|
|
7660
|
+
import { join as join14 } from "path";
|
|
7661
|
+
async function retireShippedWorktree(ctx, project, feature) {
|
|
7662
|
+
await retireWorktree(ctx, project, feature, { requireClean: false });
|
|
7663
|
+
}
|
|
7664
|
+
async function retireArchivedWorktree(ctx, project, feature) {
|
|
7665
|
+
await retireWorktree(ctx, project, feature, { requireClean: true });
|
|
7666
|
+
}
|
|
7667
|
+
async function retireWorktree(ctx, project, feature, opts) {
|
|
7668
|
+
const path = worktreeDir(project.id, feature.slug);
|
|
7669
|
+
const state = await worktreeState(path);
|
|
7670
|
+
if (state === "missing")
|
|
7671
|
+
return;
|
|
7672
|
+
const kept = keptReason(ctx, feature, state, opts);
|
|
7673
|
+
if (kept) {
|
|
7674
|
+
emit(ctx, feature.id, {
|
|
7675
|
+
type: "feature.worktree_kept",
|
|
7676
|
+
message: `talk worktree kept \u2014 ${kept}`,
|
|
7677
|
+
data: { path, reason: kept }
|
|
7678
|
+
});
|
|
7679
|
+
return;
|
|
7680
|
+
}
|
|
7681
|
+
if (await discardWorktree(project.repoPath, path)) {
|
|
7682
|
+
emit(ctx, feature.id, {
|
|
7683
|
+
type: "feature.worktree_removed",
|
|
7684
|
+
message: "talk worktree removed",
|
|
7685
|
+
data: { path }
|
|
7686
|
+
});
|
|
7687
|
+
} else {
|
|
7688
|
+
emit(ctx, feature.id, {
|
|
7689
|
+
type: "feature.worktree_remove_failed",
|
|
7690
|
+
message: `could not remove the talk worktree at ${path} \u2014 a file may be locked`,
|
|
7691
|
+
data: { path }
|
|
7692
|
+
});
|
|
7693
|
+
}
|
|
7694
|
+
}
|
|
7695
|
+
function keptReason(ctx, feature, state, opts) {
|
|
7696
|
+
if (activeSessionsForFeature(ctx, feature.id).length > 0)
|
|
7697
|
+
return "a session is live on it";
|
|
7698
|
+
if (hasActiveRun(ctx, feature.id))
|
|
7699
|
+
return "a run is in progress";
|
|
7700
|
+
if (opts.requireClean && state !== "clean")
|
|
7701
|
+
return "uncommitted changes";
|
|
7702
|
+
return;
|
|
7703
|
+
}
|
|
7704
|
+
async function sweepFeatureWorktrees(ctx) {
|
|
7705
|
+
const sweep = { removed: 0, kept: 0, failed: 0, orphanProjects: 0 };
|
|
7706
|
+
const root = worktreesRoot();
|
|
7707
|
+
const projectsById = new Map(allProjects(ctx).map((p) => [p.id, p]));
|
|
7708
|
+
const statusBySlug = new Map(ctx.db.select({ projectId: features.projectId, slug: features.slug, status: features.status }).from(features).all().map((f) => [`${f.projectId}/${f.slug}`, f.status]));
|
|
7709
|
+
for (const projectId of subdirs(root)) {
|
|
7710
|
+
const project = projectsById.get(projectId);
|
|
7711
|
+
if (!project) {
|
|
7712
|
+
try {
|
|
7713
|
+
rmSync2(join14(root, projectId), { recursive: true, force: true });
|
|
7714
|
+
sweep.orphanProjects++;
|
|
7715
|
+
} catch {
|
|
7716
|
+
sweep.failed++;
|
|
7717
|
+
}
|
|
7718
|
+
continue;
|
|
7719
|
+
}
|
|
7720
|
+
for (const slug of subdirs(join14(root, projectId))) {
|
|
7721
|
+
if (slug.startsWith("_"))
|
|
7722
|
+
continue;
|
|
7723
|
+
const status = statusBySlug.get(`${projectId}/${slug}`);
|
|
7724
|
+
if (status === "active" || status === "draft")
|
|
7725
|
+
continue;
|
|
7726
|
+
const path = worktreeDir(projectId, slug);
|
|
7727
|
+
const state = await worktreeState(path);
|
|
7728
|
+
if (!sweepable(status, state)) {
|
|
7729
|
+
sweep.kept++;
|
|
7730
|
+
} else if (await discardWorktree(project.repoPath, path, { attempts: 1 })) {
|
|
7731
|
+
sweep.removed++;
|
|
7732
|
+
} else {
|
|
7733
|
+
sweep.failed++;
|
|
7734
|
+
}
|
|
7735
|
+
}
|
|
7736
|
+
}
|
|
7737
|
+
return sweep;
|
|
7738
|
+
}
|
|
7739
|
+
function sweepable(status, state) {
|
|
7740
|
+
if (status === "shipped")
|
|
7741
|
+
return true;
|
|
7742
|
+
return state === "clean" || state === "not-a-checkout";
|
|
7743
|
+
}
|
|
7744
|
+
function subdirs(dir) {
|
|
7745
|
+
try {
|
|
7746
|
+
return readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
7747
|
+
} catch {
|
|
7748
|
+
return [];
|
|
7749
|
+
}
|
|
7750
|
+
}
|
|
7751
|
+
var init_feature_worktrees = __esm(() => {
|
|
7752
|
+
init_paths();
|
|
7753
|
+
init_schema();
|
|
7754
|
+
init_sessions();
|
|
7755
|
+
init_events();
|
|
7756
|
+
init_git();
|
|
7757
|
+
init_repo();
|
|
7758
|
+
});
|
|
7759
|
+
|
|
7608
7760
|
// src/services/knowledge.ts
|
|
7609
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync8, readdirSync as
|
|
7610
|
-
import { join as
|
|
7761
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync8, readdirSync as readdirSync4, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
7762
|
+
import { join as join15, relative as relative2, resolve as resolve8, sep } from "path";
|
|
7611
7763
|
function scaffoldDocs(ctx, feature, opts = {}) {
|
|
7612
7764
|
const project = projectForFeature(ctx, feature);
|
|
7613
7765
|
const dir = featureDocsDir(project, feature);
|
|
@@ -7660,7 +7812,7 @@ function mapDocBody(feature, seed) {
|
|
|
7660
7812
|
`);
|
|
7661
7813
|
}
|
|
7662
7814
|
function readCharter(repoRoot) {
|
|
7663
|
-
const path =
|
|
7815
|
+
const path = join15(repoRoot, CHARTER_FILE);
|
|
7664
7816
|
if (!existsSync12(path))
|
|
7665
7817
|
return;
|
|
7666
7818
|
try {
|
|
@@ -7670,16 +7822,16 @@ function readCharter(repoRoot) {
|
|
|
7670
7822
|
}
|
|
7671
7823
|
}
|
|
7672
7824
|
function listLiveAdrs(repoRoot) {
|
|
7673
|
-
const dir =
|
|
7825
|
+
const dir = join15(repoRoot, ADR_DIR_REL);
|
|
7674
7826
|
if (!existsSync12(dir))
|
|
7675
7827
|
return [];
|
|
7676
7828
|
const docs2 = [];
|
|
7677
|
-
for (const entry of
|
|
7829
|
+
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
7678
7830
|
if (!entry.isFile() || !entry.name.endsWith(".md"))
|
|
7679
7831
|
continue;
|
|
7680
7832
|
let content;
|
|
7681
7833
|
try {
|
|
7682
|
-
content = readFileSync8(
|
|
7834
|
+
content = readFileSync8(join15(dir, entry.name), "utf8");
|
|
7683
7835
|
} catch {
|
|
7684
7836
|
continue;
|
|
7685
7837
|
}
|
|
@@ -7694,8 +7846,8 @@ function listDocs(ctx, feature) {
|
|
|
7694
7846
|
const dir = featureDocsDir(project, feature);
|
|
7695
7847
|
if (!existsSync12(dir))
|
|
7696
7848
|
return [];
|
|
7697
|
-
return
|
|
7698
|
-
const path =
|
|
7849
|
+
return readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => {
|
|
7850
|
+
const path = join15(dir, e.name);
|
|
7699
7851
|
return { relPath: e.name, title: readTitle(path, e.name), ...writtenAt(path) };
|
|
7700
7852
|
}).sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
7701
7853
|
}
|
|
@@ -9175,7 +9327,7 @@ var Effect_exports, isFunction = (input) => typeof input === "function", dual =
|
|
|
9175
9327
|
next4 = b2;
|
|
9176
9328
|
}
|
|
9177
9329
|
return out;
|
|
9178
|
-
}, getEquivalence2, dedupeWith, dedupe = (self) => dedupeWith(self, equivalence()),
|
|
9330
|
+
}, getEquivalence2, dedupeWith, dedupe = (self) => dedupeWith(self, equivalence()), join16, 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) => {
|
|
9179
9331
|
const chunk4 = Object.create(ChunkProto);
|
|
9180
9332
|
chunk4.backing = backing;
|
|
9181
9333
|
switch (backing._tag) {
|
|
@@ -10874,7 +11026,7 @@ ${prefix}}`;
|
|
|
10874
11026
|
Object.defineProperty(error3, "toString", {
|
|
10875
11027
|
enumerable: false,
|
|
10876
11028
|
value() {
|
|
10877
|
-
const path2 = pipe(this.path,
|
|
11029
|
+
const path2 = pipe(this.path, join16(options.pathDelim));
|
|
10878
11030
|
return `(Invalid data at ${path2}: "${this.message}")`;
|
|
10879
11031
|
}
|
|
10880
11032
|
});
|
|
@@ -10889,7 +11041,7 @@ ${prefix}}`;
|
|
|
10889
11041
|
Object.defineProperty(error3, "toString", {
|
|
10890
11042
|
enumerable: false,
|
|
10891
11043
|
value() {
|
|
10892
|
-
const path2 = pipe(this.path,
|
|
11044
|
+
const path2 = pipe(this.path, join16(options.pathDelim));
|
|
10893
11045
|
return `(Missing data at ${path2}: "${this.message}")`;
|
|
10894
11046
|
}
|
|
10895
11047
|
});
|
|
@@ -10905,7 +11057,7 @@ ${prefix}}`;
|
|
|
10905
11057
|
Object.defineProperty(error3, "toString", {
|
|
10906
11058
|
enumerable: false,
|
|
10907
11059
|
value() {
|
|
10908
|
-
const path2 = pipe(this.path,
|
|
11060
|
+
const path2 = pipe(this.path, join16(options.pathDelim));
|
|
10909
11061
|
return `(Source unavailable at ${path2}: "${this.message}")`;
|
|
10910
11062
|
}
|
|
10911
11063
|
});
|
|
@@ -10920,7 +11072,7 @@ ${prefix}}`;
|
|
|
10920
11072
|
Object.defineProperty(error3, "toString", {
|
|
10921
11073
|
enumerable: false,
|
|
10922
11074
|
value() {
|
|
10923
|
-
const path2 = pipe(this.path,
|
|
11075
|
+
const path2 = pipe(this.path, join16(options.pathDelim));
|
|
10924
11076
|
return `(Unsupported operation at ${path2}: "${this.message}")`;
|
|
10925
11077
|
}
|
|
10926
11078
|
});
|
|
@@ -10950,7 +11102,7 @@ ${prefix}}`;
|
|
|
10950
11102
|
pathDelim: "_",
|
|
10951
11103
|
seqDelim: ","
|
|
10952
11104
|
}, options);
|
|
10953
|
-
const makePathString = (path) => pipe(path,
|
|
11105
|
+
const makePathString = (path) => pipe(path, join16(pathDelim));
|
|
10954
11106
|
const unmakePathString = (pathString) => pathString.split(pathDelim);
|
|
10955
11107
|
const getEnv = () => typeof process !== "undefined" && ("env" in process) && typeof process.env === "object" ? process.env : {};
|
|
10956
11108
|
const load = (path, primitive2, split2 = true) => {
|
|
@@ -11071,7 +11223,7 @@ ${prefix}}`;
|
|
|
11071
11223
|
return fail2(right3.left);
|
|
11072
11224
|
}
|
|
11073
11225
|
if (isRight2(left3) && isRight2(right3)) {
|
|
11074
|
-
const path = pipe(prefix,
|
|
11226
|
+
const path = pipe(prefix, join16("."));
|
|
11075
11227
|
const fail19 = fromFlatLoopFail(prefix, path);
|
|
11076
11228
|
const [lefts, rights] = extend(fail19, fail19, pipe(left3.right, map3(right2)), pipe(right3.right, map3(right2)));
|
|
11077
11229
|
return pipe(lefts, zip(rights), forEachSequential(([left4, right4]) => pipe(zip2(left4, right4), map10(([left5, right5]) => op.zip(left5, right5)))));
|
|
@@ -20909,7 +21061,7 @@ var init_chunk_VOG34SRF = __esm(() => {
|
|
|
20909
21061
|
}
|
|
20910
21062
|
return [];
|
|
20911
21063
|
});
|
|
20912
|
-
|
|
21064
|
+
join16 = /* @__PURE__ */ dual(2, (self, sep2) => fromIterable2(self).join(sep2));
|
|
20913
21065
|
TypeId4 = /* @__PURE__ */ Symbol.for("effect/Chunk");
|
|
20914
21066
|
emptyArray = [];
|
|
20915
21067
|
_equivalence3 = /* @__PURE__ */ getEquivalence3(equals);
|
|
@@ -54663,7 +54815,7 @@ var init_chunk_62WN33RK = __esm(() => {
|
|
|
54663
54815
|
// ../../node_modules/.bun/@ai-hero+sandcastle@0.12.0/node_modules/@ai-hero/sandcastle/dist/index.js
|
|
54664
54816
|
import { createRequire as createRequire8 } from "module";
|
|
54665
54817
|
import { mkdirSync as mkdirSync7, appendFileSync } from "fs";
|
|
54666
|
-
import path, { join as
|
|
54818
|
+
import path, { join as join18, posix as posix4, dirname as dirname8, relative as relative3 } from "path";
|
|
54667
54819
|
import { styleText as styleText2 } from "util";
|
|
54668
54820
|
import * as clack2 from "@clack/prompts";
|
|
54669
54821
|
import { readdir as readdir2, access as access3, readFile as readFile4, mkdir as mkdir3, writeFile as writeFile3, rm as rm3 } from "fs/promises";
|
|
@@ -54732,7 +54884,7 @@ async function run6(options) {
|
|
|
54732
54884
|
const targetBranch = effectiveBranchType === "merge-to-head" ? currentHostBranch : undefined;
|
|
54733
54885
|
const resolvedLogging = options.logging ?? {
|
|
54734
54886
|
type: "file",
|
|
54735
|
-
path:
|
|
54887
|
+
path: join18(hostRepoDir, ".sandcastle", "logs", buildLogFilename(resolvedBranch, targetBranch, options.name))
|
|
54736
54888
|
};
|
|
54737
54889
|
const displayLayer = resolvedLogging.type === "file" ? (() => {
|
|
54738
54890
|
printFileDisplayStartup({
|
|
@@ -55366,7 +55518,7 @@ ${errorDetail}`
|
|
|
55366
55518
|
}
|
|
55367
55519
|
return vars;
|
|
55368
55520
|
}), resolveEnv = (repoDir) => Effect_exports.gen(function* () {
|
|
55369
|
-
const sandcastleEnv = yield* parseEnvFile2(
|
|
55521
|
+
const sandcastleEnv = yield* parseEnvFile2(join18(repoDir, ".sandcastle", ".env"));
|
|
55370
55522
|
const result = {};
|
|
55371
55523
|
for (const key of Object.keys(sandcastleEnv)) {
|
|
55372
55524
|
const value = sandcastleEnv[key] || process.env[key];
|
|
@@ -55588,11 +55740,11 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
55588
55740
|
const normalized = isRoot ? cwd : cwd.replace(/[\\/]+$/, "");
|
|
55589
55741
|
return normalized.replace(/^([A-Za-z]):/, "$1").replace(/[\\/]/g, "-");
|
|
55590
55742
|
}, claudeHostSessionPath = (cwd, id3, projectsDir) => {
|
|
55591
|
-
const base = projectsDir ??
|
|
55592
|
-
return
|
|
55743
|
+
const base = projectsDir ?? join18(process.env.HOME ?? "~", ".claude", "projects");
|
|
55744
|
+
return join18(base, encodeProjectPath(cwd), `${id3}.jsonl`);
|
|
55593
55745
|
}, 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) => {
|
|
55594
|
-
const base = projectsDir ??
|
|
55595
|
-
return
|
|
55746
|
+
const base = projectsDir ?? join18(process.env.HOME ?? "~", ".claude", "projects");
|
|
55747
|
+
return join18(base, encodeProjectPath(cwd), id3, "subagents");
|
|
55596
55748
|
}, listClaudeSubagentSessionsInSandbox = async (cwd, id3, handle, sandboxProjectsDir) => {
|
|
55597
55749
|
const dir = claudeSubagentsDirInSandbox(cwd, id3, sandboxProjectsDir);
|
|
55598
55750
|
const result = await handle.exec(`find ${JSON.stringify(dir)} -type f -name ${JSON.stringify("agent-*.jsonl")} 2>/dev/null`);
|
|
@@ -55604,7 +55756,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
55604
55756
|
return stdout.split(`
|
|
55605
55757
|
`).filter((line) => line !== "");
|
|
55606
55758
|
}, findClaudeSessionOnHost = async (id3, projectsDir) => {
|
|
55607
|
-
const root = projectsDir ??
|
|
55759
|
+
const root = projectsDir ?? join18(process.env.HOME ?? "~", ".claude", "projects");
|
|
55608
55760
|
let entries;
|
|
55609
55761
|
try {
|
|
55610
55762
|
entries = await readdir2(root, { withFileTypes: true });
|
|
@@ -55614,7 +55766,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
55614
55766
|
for (const entry of entries) {
|
|
55615
55767
|
if (!entry.isDirectory())
|
|
55616
55768
|
continue;
|
|
55617
|
-
const candidate =
|
|
55769
|
+
const candidate = join18(root, entry.name, `${id3}.jsonl`);
|
|
55618
55770
|
if (await pathExists(candidate)) {
|
|
55619
55771
|
return { path: candidate, searchedRoot: root };
|
|
55620
55772
|
}
|
|
@@ -55650,7 +55802,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
55650
55802
|
return;
|
|
55651
55803
|
}
|
|
55652
55804
|
for (const entry of entries) {
|
|
55653
|
-
const child =
|
|
55805
|
+
const child = join18(dir, entry.name);
|
|
55654
55806
|
if (entry.isFile() && isCodexSessionFilename(entry.name, id3)) {
|
|
55655
55807
|
return child;
|
|
55656
55808
|
}
|
|
@@ -55664,11 +55816,11 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
55664
55816
|
};
|
|
55665
55817
|
return visit(rootDir);
|
|
55666
55818
|
}, findCodexSessionOnHost = async (id3, sessionsDir) => {
|
|
55667
|
-
const root = sessionsDir ??
|
|
55819
|
+
const root = sessionsDir ?? join18(process.env.HOME ?? "~", ".codex", "sessions");
|
|
55668
55820
|
const path2 = await findCodexSessionPath(root, id3);
|
|
55669
55821
|
return { path: path2, searchedRoot: root };
|
|
55670
55822
|
}, locateCodexHostSession = async (id3, sessionsDir) => {
|
|
55671
|
-
const root = sessionsDir ??
|
|
55823
|
+
const root = sessionsDir ?? join18(process.env.HOME ?? "~", ".codex", "sessions");
|
|
55672
55824
|
const path2 = await findCodexSessionPath(root, id3);
|
|
55673
55825
|
if (!path2)
|
|
55674
55826
|
throw new Error(`session ${id3} not found in ${root}`);
|
|
@@ -55744,7 +55896,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
55744
55896
|
} catch {}
|
|
55745
55897
|
return [];
|
|
55746
55898
|
}, CURSOR_PRINT_PROMPT_MAX_BYTES, readSandboxFile = async (handle, sandboxPath, tag) => {
|
|
55747
|
-
const tmpPath =
|
|
55899
|
+
const tmpPath = join18(tmpdir4(), `sandcastle-${tag}-${Date.now()}-${Math.random().toString(36).slice(2)}.jsonl`);
|
|
55748
55900
|
await handle.copyFileOut(sandboxPath, tmpPath);
|
|
55749
55901
|
try {
|
|
55750
55902
|
return await readFile4(tmpPath, "utf-8");
|
|
@@ -55752,7 +55904,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
55752
55904
|
await rm3(tmpPath, { force: true }).catch(() => {});
|
|
55753
55905
|
}
|
|
55754
55906
|
}, writeSandboxFile = async (handle, sandboxPath, content, tag) => {
|
|
55755
|
-
const tmpPath =
|
|
55907
|
+
const tmpPath = join18(tmpdir4(), `sandcastle-${tag}-${Date.now()}-${Math.random().toString(36).slice(2)}.jsonl`);
|
|
55756
55908
|
await writeFile3(tmpPath, content);
|
|
55757
55909
|
try {
|
|
55758
55910
|
await handle.exec(`mkdir -p ${JSON.stringify(posix4.dirname(sandboxPath))}`);
|
|
@@ -55802,7 +55954,7 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
55802
55954
|
sourcePath: sandboxSubagentPath,
|
|
55803
55955
|
fromCwd: sandboxCwd,
|
|
55804
55956
|
toCwd: hostCwd,
|
|
55805
|
-
destPath:
|
|
55957
|
+
destPath: join18(hostSubagentsDir, posix4.basename(sandboxSubagentPath)),
|
|
55806
55958
|
tag: "claude-sub"
|
|
55807
55959
|
});
|
|
55808
55960
|
} catch (err) {
|
|
@@ -55839,8 +55991,8 @@ Emit only a corrected <${error.tag}> block. Do not change files or run commands.
|
|
|
55839
55991
|
const located = await locateCodexSandboxSession(sessionId, handle, sandboxSessionsDir);
|
|
55840
55992
|
const jsonl = await readSandboxFile(handle, located.path, "codex-cap");
|
|
55841
55993
|
const rewritten = transferCodexSession(jsonl, sandboxCwd, hostCwd);
|
|
55842
|
-
const root = hostSessionsDir ??
|
|
55843
|
-
const target =
|
|
55994
|
+
const root = hostSessionsDir ?? join18(process.env.HOME ?? "~", ".codex", "sessions");
|
|
55995
|
+
const target = join18(root, located.relativePath);
|
|
55844
55996
|
await mkdir3(dirname8(target), { recursive: true });
|
|
55845
55997
|
await writeFile3(target, rewritten);
|
|
55846
55998
|
capturedPaths.set(sessionId, target);
|
|
@@ -56479,8 +56631,8 @@ var init_settings = __esm(() => {
|
|
|
56479
56631
|
});
|
|
56480
56632
|
|
|
56481
56633
|
// src/services/setup.ts
|
|
56482
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync10, readdirSync as
|
|
56483
|
-
import { dirname as dirname10, join as
|
|
56634
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync10, readdirSync as readdirSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
56635
|
+
import { dirname as dirname10, join as join19 } from "path";
|
|
56484
56636
|
async function writeGitIdentity(exec3, identity4) {
|
|
56485
56637
|
const name = identity4.name.trim();
|
|
56486
56638
|
const email = identity4.email.trim();
|
|
@@ -56590,10 +56742,10 @@ function terminalSpec(kind) {
|
|
|
56590
56742
|
throw new InvalidInputError(`no terminal command for ${kind}`);
|
|
56591
56743
|
}
|
|
56592
56744
|
function refreshSandcastleConfig(templateDir, targetDir) {
|
|
56593
|
-
const dir =
|
|
56745
|
+
const dir = join19(targetDir, ".sandcastle");
|
|
56594
56746
|
mkdirSync9(dir, { recursive: true });
|
|
56595
|
-
for (const name of
|
|
56596
|
-
copyFileSync2(
|
|
56747
|
+
for (const name of readdirSync5(templateDir)) {
|
|
56748
|
+
copyFileSync2(join19(templateDir, name), join19(dir, name));
|
|
56597
56749
|
}
|
|
56598
56750
|
return dir;
|
|
56599
56751
|
}
|
|
@@ -56669,11 +56821,33 @@ var init_setup = __esm(() => {
|
|
|
56669
56821
|
// src/doctor/system-exec.ts
|
|
56670
56822
|
import { spawn as spawn7 } from "child_process";
|
|
56671
56823
|
function createSystemExec(opts = {}) {
|
|
56824
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
|
|
56672
56825
|
return (command, args2) => new Promise((resolve10) => {
|
|
56673
56826
|
const { file, args: spawnArgs } = resolveSpawnTarget(command, args2);
|
|
56674
56827
|
let stdout = "";
|
|
56675
56828
|
let stderr = "";
|
|
56829
|
+
let settled = false;
|
|
56830
|
+
const settle = (outcome) => {
|
|
56831
|
+
if (settled)
|
|
56832
|
+
return;
|
|
56833
|
+
settled = true;
|
|
56834
|
+
clearTimeout(timer);
|
|
56835
|
+
resolve10(outcome);
|
|
56836
|
+
};
|
|
56676
56837
|
const child = spawn7(file, spawnArgs, { cwd: opts.cwd, windowsHide: true });
|
|
56838
|
+
const timer = setTimeout(() => {
|
|
56839
|
+
if (child.pid !== undefined)
|
|
56840
|
+
killProcessTree(child.pid);
|
|
56841
|
+
const note2 = `${command} ${args2.join(" ")} timed out after ${Math.round(timeoutMs / 1000)}s`;
|
|
56842
|
+
settle({
|
|
56843
|
+
ok: true,
|
|
56844
|
+
code: null,
|
|
56845
|
+
stdout,
|
|
56846
|
+
stderr: stderr ? `${stderr}
|
|
56847
|
+
${note2}` : note2,
|
|
56848
|
+
timedOut: true
|
|
56849
|
+
});
|
|
56850
|
+
}, timeoutMs);
|
|
56677
56851
|
child.stdout?.on("data", (d) => {
|
|
56678
56852
|
stdout += d.toString();
|
|
56679
56853
|
});
|
|
@@ -56681,14 +56855,16 @@ function createSystemExec(opts = {}) {
|
|
|
56681
56855
|
stderr += d.toString();
|
|
56682
56856
|
});
|
|
56683
56857
|
child.on("error", (err) => {
|
|
56684
|
-
|
|
56858
|
+
settle({ ok: false, code: null, stdout, stderr: stderr || String(err) });
|
|
56685
56859
|
});
|
|
56686
56860
|
child.on("close", (code) => {
|
|
56687
|
-
|
|
56861
|
+
settle({ ok: true, code: code ?? 0, stdout, stderr });
|
|
56688
56862
|
});
|
|
56689
56863
|
});
|
|
56690
56864
|
}
|
|
56865
|
+
var DEFAULT_EXEC_TIMEOUT_MS = 60000;
|
|
56691
56866
|
var init_system_exec = __esm(() => {
|
|
56867
|
+
init_kill_tree();
|
|
56692
56868
|
init_resolve_executable();
|
|
56693
56869
|
});
|
|
56694
56870
|
|
|
@@ -56880,13 +57056,13 @@ var init_no_sandbox = __esm(() => {
|
|
|
56880
57056
|
// src/workflows/recorder-reap.ts
|
|
56881
57057
|
import { spawn as spawn8 } from "child_process";
|
|
56882
57058
|
import { closeSync, existsSync as existsSync16, openSync } from "fs";
|
|
56883
|
-
import { delimiter, join as
|
|
57059
|
+
import { delimiter, join as join20 } from "path";
|
|
56884
57060
|
function findOnPath(bin, env = process.env, platform = process.platform) {
|
|
56885
57061
|
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
56886
57062
|
const suffixes = platform === "win32" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
56887
57063
|
for (const dir of dirs) {
|
|
56888
57064
|
for (const suffix of suffixes) {
|
|
56889
|
-
const candidate =
|
|
57065
|
+
const candidate = join20(dir, `${bin}${suffix}`);
|
|
56890
57066
|
if (existsSync16(candidate))
|
|
56891
57067
|
return candidate;
|
|
56892
57068
|
}
|
|
@@ -56984,9 +57160,9 @@ var init_recorder_reap = __esm(() => {
|
|
|
56984
57160
|
});
|
|
56985
57161
|
|
|
56986
57162
|
// src/workflows/review-ticket.ts
|
|
56987
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync10, readFileSync as readFileSync11, readdirSync as
|
|
57163
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync10, readFileSync as readFileSync11, readdirSync as readdirSync6, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
56988
57164
|
import { spawnSync } from "child_process";
|
|
56989
|
-
import { basename as basename3, dirname as dirname11, join as
|
|
57165
|
+
import { basename as basename3, dirname as dirname11, join as join21 } from "path";
|
|
56990
57166
|
function reviewTemplatePath(ticket) {
|
|
56991
57167
|
return burnerAssetPath(ticket?.passKind === "verification" ? "verify-fixes.md" : "review-ticket.md");
|
|
56992
57168
|
}
|
|
@@ -56994,18 +57170,34 @@ function renderReviewPrompt(templateOrTicket, values) {
|
|
|
56994
57170
|
const template = typeof templateOrTicket === "string" ? templateOrTicket : readFileSync11(reviewTemplatePath(templateOrTicket), "utf8");
|
|
56995
57171
|
return renderTemplate(template, values);
|
|
56996
57172
|
}
|
|
56997
|
-
function
|
|
56998
|
-
|
|
56999
|
-
|
|
57000
|
-
const
|
|
57001
|
-
|
|
57173
|
+
function probeExecutable(path2, spawn9) {
|
|
57174
|
+
const started = performance.now();
|
|
57175
|
+
const result = spawn9(path2);
|
|
57176
|
+
const elapsedMs = Math.round(performance.now() - started);
|
|
57177
|
+
const error = result.error ? result.error.code ?? result.error.message : undefined;
|
|
57178
|
+
return {
|
|
57179
|
+
ok: result.status === 0 && !result.error,
|
|
57180
|
+
path: path2,
|
|
57181
|
+
status: result.status,
|
|
57182
|
+
signal: result.signal,
|
|
57183
|
+
...error ? { error } : {},
|
|
57184
|
+
elapsedMs
|
|
57185
|
+
};
|
|
57002
57186
|
}
|
|
57003
|
-
function
|
|
57187
|
+
function checkExecutableHealth(path2, spawn9 = spawnVersion) {
|
|
57188
|
+
const first = probeExecutable(path2, spawn9);
|
|
57189
|
+
return first.ok ? first : probeExecutable(path2, spawn9);
|
|
57190
|
+
}
|
|
57191
|
+
function describeHealthFailure(health) {
|
|
57192
|
+
const cause3 = health.error ?? (health.signal ? `killed by ${health.signal}` : `exited with status ${health.status}`);
|
|
57193
|
+
return `${health.path} --version: ${cause3} after ${health.elapsedMs}ms`;
|
|
57194
|
+
}
|
|
57195
|
+
function missingDrivePieces(browserPath, devCommand, browserFailure, ffmpegPath) {
|
|
57004
57196
|
const missing = [];
|
|
57005
57197
|
if (!browserPath) {
|
|
57006
57198
|
missing.push(`\`${AGENT_BROWSER_BIN}\` is not on this machine's PATH, so there is no browser to walk the app with`);
|
|
57007
|
-
} else if (
|
|
57008
|
-
missing.push(`\`${AGENT_BROWSER_BIN}\` is on PATH but failed its health check`);
|
|
57199
|
+
} else if (browserFailure) {
|
|
57200
|
+
missing.push(`\`${AGENT_BROWSER_BIN}\` is on PATH but failed its health check (${browserFailure})`);
|
|
57009
57201
|
}
|
|
57010
57202
|
if (!ffmpegPath)
|
|
57011
57203
|
missing.push(`\`${FFMPEG_BIN}\` is not on this machine's PATH, so a drive cannot be recorded`);
|
|
@@ -57014,17 +57206,17 @@ function missingDrivePieces(browserPath, devCommand, browserHealthy, ffmpegPath)
|
|
|
57014
57206
|
}
|
|
57015
57207
|
return missing;
|
|
57016
57208
|
}
|
|
57017
|
-
function driveWithheldReason(browserPath, devCommand,
|
|
57018
|
-
const missing = missingDrivePieces(browserPath, devCommand,
|
|
57209
|
+
function driveWithheldReason(browserPath, devCommand, browserFailure, ffmpegPath) {
|
|
57210
|
+
const missing = missingDrivePieces(browserPath, devCommand, browserFailure, ffmpegPath);
|
|
57019
57211
|
if (missing.length === 0)
|
|
57020
57212
|
return;
|
|
57021
57213
|
return `Drive was unavailable: ${missing.join(", and ")}.`;
|
|
57022
57214
|
}
|
|
57023
|
-
function buildDriveAvailability(browserPath, devCommand, inheritedMode,
|
|
57215
|
+
function buildDriveAvailability(browserPath, devCommand, inheritedMode, browserFailure, ffmpegPath = browserPath) {
|
|
57024
57216
|
if (inheritedMode === "gates") {
|
|
57025
57217
|
return "Inherited mode: **Gates**. The pass being verified recorded Gates mode, so run Gates mode; do not choose Drive mode or call `review_drive`.";
|
|
57026
57218
|
}
|
|
57027
|
-
const missing = missingDrivePieces(browserPath, devCommand,
|
|
57219
|
+
const missing = missingDrivePieces(browserPath, devCommand, browserFailure, ffmpegPath);
|
|
57028
57220
|
if (inheritedMode === "drive" && missing.length === 0) {
|
|
57029
57221
|
return "Inherited mode: **Drive**. The pass being verified recorded Drive mode, so run Drive mode and record the full tour; do not choose Gates mode.";
|
|
57030
57222
|
}
|
|
@@ -57089,9 +57281,10 @@ function buildDriveInstructions(instructions) {
|
|
|
57089
57281
|
].join(`
|
|
57090
57282
|
`);
|
|
57091
57283
|
}
|
|
57092
|
-
function buildGateNotes(config2) {
|
|
57093
|
-
const
|
|
57094
|
-
const
|
|
57284
|
+
function buildGateNotes(config2, project3) {
|
|
57285
|
+
const prepared = resolvePreparedSettings(config2, project3);
|
|
57286
|
+
const commands = prepared.verifyCommands;
|
|
57287
|
+
const failures2 = prepared.knownFailures;
|
|
57095
57288
|
const out = [];
|
|
57096
57289
|
if (commands) {
|
|
57097
57290
|
out.push("Run exactly these, once each \u2014 they are this project's own verify commands, so do not go looking for alternatives, add concurrency flags, or re-run one to re-read its output (redirect to a file and read that instead):", "", "```", commands, "```");
|
|
@@ -57116,7 +57309,7 @@ function prepareReviewDirectory(dir, options = {}) {
|
|
|
57116
57309
|
if (!sibling.startsWith(stalePrefix))
|
|
57117
57310
|
continue;
|
|
57118
57311
|
try {
|
|
57119
|
-
fs.remove(
|
|
57312
|
+
fs.remove(join21(parent, sibling), { recursive: true, force: true });
|
|
57120
57313
|
} catch {}
|
|
57121
57314
|
}
|
|
57122
57315
|
} catch {}
|
|
@@ -57139,14 +57332,14 @@ async function prepareReviewArtifactsDirectory(ticketId, dir = reviewDir(ticketI
|
|
|
57139
57332
|
async function writeReviewArtifacts(ticket, runId, config2, recorderReap = reapRecorder) {
|
|
57140
57333
|
const dir = reviewDir(ticket.id);
|
|
57141
57334
|
await prepareReviewArtifactsDirectory(ticket.id, dir, { recorderReap });
|
|
57142
|
-
const mcpConfigPath =
|
|
57335
|
+
const mcpConfigPath = join21(dir, "mcp.json");
|
|
57143
57336
|
const mcpConfig = renderRunMcpConfig(runId, config2);
|
|
57144
57337
|
writeFileSync8(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), "utf8");
|
|
57145
57338
|
return {
|
|
57146
57339
|
dir,
|
|
57147
57340
|
mcp: { path: mcpConfigPath, config: mcpConfig },
|
|
57148
|
-
digestPath:
|
|
57149
|
-
blockedPath:
|
|
57341
|
+
digestPath: join21(dir, "DIGEST.md"),
|
|
57342
|
+
blockedPath: join21(dir, "BLOCKED.md"),
|
|
57150
57343
|
walkthroughPath: reviewWalkthroughPath(ticket.id)
|
|
57151
57344
|
};
|
|
57152
57345
|
}
|
|
@@ -57178,8 +57371,11 @@ async function reviewTicketOutcome(ctx, ticket, deps, timer) {
|
|
|
57178
57371
|
const inheritedMode = ticket.passKind === "verification" ? inheritedReviewMode(verifies?.reviewMode) : undefined;
|
|
57179
57372
|
const browserPath = findOnPath(AGENT_BROWSER_BIN);
|
|
57180
57373
|
const ffmpegPath = findOnPath(FFMPEG_BIN);
|
|
57181
|
-
const
|
|
57182
|
-
const
|
|
57374
|
+
const browserHealth = browserPath ? checkExecutableHealth(browserPath) : undefined;
|
|
57375
|
+
const browserFailure = browserHealth && !browserHealth.ok ? describeHealthFailure(browserHealth) : undefined;
|
|
57376
|
+
if (browserFailure)
|
|
57377
|
+
console.error(`[review] ${AGENT_BROWSER_BIN} failed its health check: ${browserFailure}`);
|
|
57378
|
+
const withheldReason = driveWithheldReason(browserPath, project3.devCommand, browserFailure, ffmpegPath);
|
|
57183
57379
|
const offeredMode = inheritedMode === "gates" || withheldReason ? "gates" : "drive";
|
|
57184
57380
|
const prompt = renderReviewPrompt(ticket, {
|
|
57185
57381
|
TICKET_JSON: buildTicketJson(ticket),
|
|
@@ -57188,9 +57384,9 @@ async function reviewTicketOutcome(ctx, ticket, deps, timer) {
|
|
|
57188
57384
|
LAP_DIGESTS: buildLapDigestsBlock(deps.lapDigests),
|
|
57189
57385
|
FEATURE_BRANCH: feature.branch,
|
|
57190
57386
|
BASE_BRANCH: feature.baseBranch,
|
|
57191
|
-
DRIVE_AVAILABILITY: buildDriveAvailability(browserPath, project3.devCommand, inheritedMode,
|
|
57387
|
+
DRIVE_AVAILABILITY: buildDriveAvailability(browserPath, project3.devCommand, inheritedMode, browserFailure, ffmpegPath),
|
|
57192
57388
|
DRIVE_INSTRUCTIONS: buildDriveInstructions(project3.driveInstructions),
|
|
57193
|
-
GATE_NOTES: buildGateNotes(deps.config),
|
|
57389
|
+
GATE_NOTES: buildGateNotes(deps.config, project3),
|
|
57194
57390
|
DIGEST_PATH: artifacts.digestPath,
|
|
57195
57391
|
BLOCKED_PATH: artifacts.blockedPath,
|
|
57196
57392
|
WALKTHROUGH_PATH: artifacts.walkthroughPath,
|
|
@@ -57237,7 +57433,7 @@ async function reviewTicketOutcome(ctx, ticket, deps, timer) {
|
|
|
57237
57433
|
maxIterations: deps.config.burnMaxIterations,
|
|
57238
57434
|
logging: {
|
|
57239
57435
|
type: "file",
|
|
57240
|
-
path:
|
|
57436
|
+
path: join21(logsDir(), `review-${feature.id}-${ticket.seq}.log`),
|
|
57241
57437
|
onAgentStreamEvent: onStreamEvent
|
|
57242
57438
|
}
|
|
57243
57439
|
};
|
|
@@ -57308,8 +57504,9 @@ function couldNotReview(ticket, reason, digest) {
|
|
|
57308
57504
|
**${headline}**` : `**${headline}**`
|
|
57309
57505
|
};
|
|
57310
57506
|
}
|
|
57311
|
-
var FFMPEG_BIN = "ffmpeg", DECLARATION, REVIEW_DIRECTORY_FS;
|
|
57507
|
+
var FFMPEG_BIN = "ffmpeg", spawnVersion = (path2) => spawnSync(path2, ["--version"], { timeout: 3000, stdio: "ignore" }), DECLARATION, REVIEW_DIRECTORY_FS;
|
|
57312
57508
|
var init_review_ticket = __esm(() => {
|
|
57509
|
+
init_src();
|
|
57313
57510
|
init_paths();
|
|
57314
57511
|
init_dist();
|
|
57315
57512
|
init_no_sandbox();
|
|
@@ -57324,9 +57521,9 @@ var init_review_ticket = __esm(() => {
|
|
|
57324
57521
|
DECLARATION = /(?:^|\n)REVIEW-MODE:\s*(drive|gates)\s*\nREVIEW-VERDICT:\s*(verified|unverified)\s*\nREVIEW-REASON:\s*([^\r\n]*)/i;
|
|
57325
57522
|
REVIEW_DIRECTORY_FS = {
|
|
57326
57523
|
mkdir: mkdirSync10,
|
|
57327
|
-
readDir:
|
|
57524
|
+
readDir: readdirSync6,
|
|
57328
57525
|
rename: renameSync,
|
|
57329
|
-
remove:
|
|
57526
|
+
remove: rmSync3
|
|
57330
57527
|
};
|
|
57331
57528
|
});
|
|
57332
57529
|
|
|
@@ -57884,8 +58081,8 @@ var init_podman = __esm(() => {
|
|
|
57884
58081
|
});
|
|
57885
58082
|
|
|
57886
58083
|
// src/workflows/ticket-burner.ts
|
|
57887
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync12, readdirSync as
|
|
57888
|
-
import { dirname as dirname12, join as
|
|
58084
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync12, readdirSync as readdirSync7, rmSync as rmSync4 } from "fs";
|
|
58085
|
+
import { dirname as dirname12, join as join23, posix as posixPath, win32 as winPath } from "path";
|
|
57889
58086
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
57890
58087
|
function missingImageRuntimeMessage(runtime5, image) {
|
|
57891
58088
|
const binary = RUNTIME_BINARY[runtime5];
|
|
@@ -58146,10 +58343,10 @@ _(The "${DROPPED_MAP_SECTIONS.join('" and "')}" sections are omitted here: they
|
|
|
58146
58343
|
}
|
|
58147
58344
|
function buildProjectStandards(repoPath) {
|
|
58148
58345
|
const present = [];
|
|
58149
|
-
if (existsSync18(
|
|
58346
|
+
if (existsSync18(join23(repoPath, AGENT_CONVENTIONS_FILE))) {
|
|
58150
58347
|
present.push("`CLAUDE.md` \u2014 this repo's agent-facing conventions, and the highest authority on them");
|
|
58151
58348
|
}
|
|
58152
|
-
if (existsSync18(
|
|
58349
|
+
if (existsSync18(join23(repoPath, CHARTER_FILE))) {
|
|
58153
58350
|
present.push(`\`${CHARTER_FILE}\` \u2014 the project charter: what this codebase is for`);
|
|
58154
58351
|
}
|
|
58155
58352
|
const adrs = listLiveAdrs(repoPath).map((a) => a.relPath);
|
|
@@ -58180,7 +58377,7 @@ function buildDriveNotes(settings) {
|
|
|
58180
58377
|
add5("stop", settings.driveStopCommand);
|
|
58181
58378
|
add5("dev server", settings.devCommand);
|
|
58182
58379
|
add5("db reset", settings.dbResetCommand);
|
|
58183
|
-
const hasDir = existsSync18(
|
|
58380
|
+
const hasDir = existsSync18(join23(settings.repoPath, ".runcastle"));
|
|
58184
58381
|
if (configured.length === 0 && !hasDir) {
|
|
58185
58382
|
return [
|
|
58186
58383
|
"This project has no test-drive machinery configured, so there is nothing here for you to keep in step \u2014 do not invent any.",
|
|
@@ -58400,14 +58597,14 @@ function parseSetupMarker(text3) {
|
|
|
58400
58597
|
return { cold: m[1] === "1", syncMs: Number(m[2]), installMs: Number(m[3]) };
|
|
58401
58598
|
}
|
|
58402
58599
|
function takeSetupMarker(worktreePath) {
|
|
58403
|
-
const file =
|
|
58600
|
+
const file = join23(worktreePath, SETUP_MARKER_FILE);
|
|
58404
58601
|
let text3;
|
|
58405
58602
|
try {
|
|
58406
58603
|
text3 = readFileSync12(file, "utf8");
|
|
58407
58604
|
} catch {
|
|
58408
58605
|
return;
|
|
58409
58606
|
}
|
|
58410
|
-
|
|
58607
|
+
rmSync4(file, { force: true });
|
|
58411
58608
|
return parseSetupMarker(text3);
|
|
58412
58609
|
}
|
|
58413
58610
|
function fmtSeconds(ms) {
|
|
@@ -58500,7 +58697,7 @@ function buildAttachmentCopyCommands(sources, platform = process.platform) {
|
|
|
58500
58697
|
];
|
|
58501
58698
|
}
|
|
58502
58699
|
async function clearAttachments(workspacePath, repoPath) {
|
|
58503
|
-
|
|
58700
|
+
rmSync4(join23(workspacePath, ATTACHMENTS_DIR), { recursive: true, force: true });
|
|
58504
58701
|
await unexcludePath(repoPath, ATTACHMENTS_PREFIX);
|
|
58505
58702
|
}
|
|
58506
58703
|
function buildVerifyNotes(config2) {
|
|
@@ -58522,7 +58719,7 @@ function buildVerifyNotes(config2) {
|
|
|
58522
58719
|
`);
|
|
58523
58720
|
}
|
|
58524
58721
|
function readRepoToolchain(repoPath) {
|
|
58525
|
-
const pkgPath =
|
|
58722
|
+
const pkgPath = join23(repoPath, "package.json");
|
|
58526
58723
|
const hasPackageJson = existsSync18(pkgPath);
|
|
58527
58724
|
let packageManagerField;
|
|
58528
58725
|
if (hasPackageJson) {
|
|
@@ -58536,10 +58733,10 @@ function readRepoToolchain(repoPath) {
|
|
|
58536
58733
|
hasPackageJson,
|
|
58537
58734
|
...packageManagerField !== undefined ? { packageManagerField } : {},
|
|
58538
58735
|
lockfiles: {
|
|
58539
|
-
bun: existsSync18(
|
|
58540
|
-
pnpm: existsSync18(
|
|
58541
|
-
yarn: existsSync18(
|
|
58542
|
-
npm: existsSync18(
|
|
58736
|
+
bun: existsSync18(join23(repoPath, "bun.lock")) || existsSync18(join23(repoPath, "bun.lockb")),
|
|
58737
|
+
pnpm: existsSync18(join23(repoPath, "pnpm-lock.yaml")),
|
|
58738
|
+
yarn: existsSync18(join23(repoPath, "yarn.lock")),
|
|
58739
|
+
npm: existsSync18(join23(repoPath, "package-lock.json"))
|
|
58543
58740
|
}
|
|
58544
58741
|
};
|
|
58545
58742
|
}
|
|
@@ -59293,16 +59490,16 @@ function resolverTemplatePath() {
|
|
|
59293
59490
|
}
|
|
59294
59491
|
function burnerAssetPath(file) {
|
|
59295
59492
|
const here = dirname12(fileURLToPath6(import.meta.url));
|
|
59296
|
-
return
|
|
59493
|
+
return join23(resolveSkillsRoot(here), "burner", file);
|
|
59297
59494
|
}
|
|
59298
59495
|
function listFeatureDocs(docsDir, prefix = "") {
|
|
59299
59496
|
const out = [];
|
|
59300
|
-
for (const e of
|
|
59497
|
+
for (const e of readdirSync7(docsDir, { withFileTypes: true })) {
|
|
59301
59498
|
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
59302
59499
|
if (e.isFile() && e.name.endsWith(".md"))
|
|
59303
59500
|
out.push(rel);
|
|
59304
59501
|
else if (e.isDirectory() && !prefix)
|
|
59305
|
-
out.push(...listFeatureDocs(
|
|
59502
|
+
out.push(...listFeatureDocs(join23(docsDir, e.name), rel));
|
|
59306
59503
|
}
|
|
59307
59504
|
return out;
|
|
59308
59505
|
}
|
|
@@ -59319,7 +59516,7 @@ function readDocsDigest(projectId, slug) {
|
|
|
59319
59516
|
if (!existsSync18(worktree)) {
|
|
59320
59517
|
return done10("_No talk worktree on disk \u2014 docs digest skipped._", "no-worktree");
|
|
59321
59518
|
}
|
|
59322
|
-
const docsDir =
|
|
59519
|
+
const docsDir = join23(worktree, ...docsRel.split("/"));
|
|
59323
59520
|
if (!existsSync18(docsDir)) {
|
|
59324
59521
|
return done10("_No docs/features dir in the talk worktree \u2014 docs digest skipped._", "no-docs-dir");
|
|
59325
59522
|
}
|
|
@@ -59336,7 +59533,7 @@ function readDocsDigest(projectId, slug) {
|
|
|
59336
59533
|
}
|
|
59337
59534
|
let content;
|
|
59338
59535
|
try {
|
|
59339
|
-
content = readFileSync12(
|
|
59536
|
+
content = readFileSync12(join23(docsDir, ...name.split("/")), "utf8");
|
|
59340
59537
|
} catch {
|
|
59341
59538
|
continue;
|
|
59342
59539
|
}
|
|
@@ -59383,7 +59580,7 @@ function readAgentFile(dirs, file) {
|
|
|
59383
59580
|
for (const dir of dirs) {
|
|
59384
59581
|
if (!dir)
|
|
59385
59582
|
continue;
|
|
59386
|
-
const p =
|
|
59583
|
+
const p = join23(dir, file);
|
|
59387
59584
|
if (existsSync18(p)) {
|
|
59388
59585
|
try {
|
|
59389
59586
|
return readFileSync12(p, "utf8");
|
|
@@ -59563,7 +59760,7 @@ async function burnTicket(ctx, ticket, config2, token, model, land2, ensureIsola
|
|
|
59563
59760
|
const slotStamp = buildSlotStamp(resolveSandboxImage(config2, project3), toolchain.packageManagerField);
|
|
59564
59761
|
const setupHookFor = (branch) => withPrelude(slot !== undefined ? buildSlotSetupCommand(slot, branch, setupCommand, pm, slotStamp) : workspaceMode === "isolated" ? buildIsolatedSetupCommand(branch, setupCommand, pm) : setupCommand);
|
|
59565
59762
|
mkdirSync11(logsDir(), { recursive: true });
|
|
59566
|
-
const logFilePath =
|
|
59763
|
+
const logFilePath = join23(logsDir(), `burn-${feature.id}-${ticket.seq}.log`);
|
|
59567
59764
|
const throttle = createStreamThrottle((e) => ctx.emitEvent({ ...e, ticketId: ticket.id }));
|
|
59568
59765
|
beginTranscript(ticket.id);
|
|
59569
59766
|
const timer = createToolTimer(model.runtime);
|
|
@@ -59647,7 +59844,7 @@ async function burnTicket(ctx, ticket, config2, token, model, land2, ensureIsola
|
|
|
59647
59844
|
};
|
|
59648
59845
|
};
|
|
59649
59846
|
const discards = new Map;
|
|
59650
|
-
const
|
|
59847
|
+
const discardWorktree2 = (branch) => {
|
|
59651
59848
|
const pending3 = discards.get(branch) ?? cleanupBurnWorktree(project3.repoPath, branch);
|
|
59652
59849
|
discards.set(branch, pending3);
|
|
59653
59850
|
return pending3;
|
|
@@ -59702,7 +59899,7 @@ async function burnTicket(ctx, ticket, config2, token, model, land2, ensureIsola
|
|
|
59702
59899
|
logging: { type: "file", path: logFilePath, onAgentStreamEvent: onStreamEvent }
|
|
59703
59900
|
});
|
|
59704
59901
|
} catch (err) {
|
|
59705
|
-
await
|
|
59902
|
+
await discardWorktree2(resolveBranch);
|
|
59706
59903
|
if (ctx.signal.aborted)
|
|
59707
59904
|
throw err;
|
|
59708
59905
|
const tip = await bestTip();
|
|
@@ -59716,7 +59913,7 @@ async function burnTicket(ctx, ticket, config2, token, model, land2, ensureIsola
|
|
|
59716
59913
|
}
|
|
59717
59914
|
}
|
|
59718
59915
|
const blocked3 = readAgentFile([result?.preservedWorktreePath, project3.repoPath], "BLOCKED.md");
|
|
59719
|
-
await
|
|
59916
|
+
await discardWorktree2(resolveBranch);
|
|
59720
59917
|
if (blocked3 !== undefined && blocked3.trim().length > 0) {
|
|
59721
59918
|
return {
|
|
59722
59919
|
ok: false,
|
|
@@ -59850,7 +60047,7 @@ ${retryNotes}` : basePrompt,
|
|
|
59850
60047
|
break;
|
|
59851
60048
|
} catch (err) {
|
|
59852
60049
|
await clearAttachmentsFor(tempBranch);
|
|
59853
|
-
await
|
|
60050
|
+
await discardWorktree2(tempBranch);
|
|
59854
60051
|
if (ctx.signal.aborted)
|
|
59855
60052
|
throw err;
|
|
59856
60053
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -59865,7 +60062,7 @@ ${retryNotes}` : basePrompt,
|
|
|
59865
60062
|
return stoppedOutcome(salvaged.length);
|
|
59866
60063
|
}
|
|
59867
60064
|
if (isWorktreeTeardownError(err)) {
|
|
59868
|
-
const removed = await
|
|
60065
|
+
const removed = await discardWorktree2(tempBranch);
|
|
59869
60066
|
if (salvaged.length > 0) {
|
|
59870
60067
|
ctx.emitEvent({
|
|
59871
60068
|
type: "burn.worktree.teardown-failed",
|
|
@@ -59938,7 +60135,7 @@ ${retryNotes}` : basePrompt,
|
|
|
59938
60135
|
const blocked3 = readAgentFile(agentFileDirs, "BLOCKED.md");
|
|
59939
60136
|
emitUnrunnableGates(ctx, ticket, readAgentFile(agentFileDirs, "GATE_UNRUNNABLE.md"));
|
|
59940
60137
|
harvestedDigest = harvestDigest(agentFileDirs);
|
|
59941
|
-
await
|
|
60138
|
+
await discardWorktree2(tempBranch);
|
|
59942
60139
|
const chain = await branchCommitsAhead(project3.repoPath, feature.branch, tempBranch);
|
|
59943
60140
|
let outcome;
|
|
59944
60141
|
if (result.commits.length === 0 && blocked3 !== undefined && blocked3.trim().length > 0) {
|
|
@@ -60236,7 +60433,7 @@ var init_ticket_burner = __esm(() => {
|
|
|
60236
60433
|
|
|
60237
60434
|
// src/workflows/research.ts
|
|
60238
60435
|
import { mkdirSync as mkdirSync12, readFileSync as readFileSync13 } from "fs";
|
|
60239
|
-
import { dirname as dirname13, join as
|
|
60436
|
+
import { dirname as dirname13, join as join25 } from "path";
|
|
60240
60437
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
60241
60438
|
function waypointSlug(waypoint) {
|
|
60242
60439
|
const base = waypoint.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -60305,7 +60502,7 @@ function interpretResearchResult(result, docRelPath) {
|
|
|
60305
60502
|
}
|
|
60306
60503
|
function researchTemplatePath() {
|
|
60307
60504
|
const here = dirname13(fileURLToPath7(import.meta.url));
|
|
60308
|
-
return
|
|
60505
|
+
return join25(resolveSkillsRoot(here), "burner", "research-waypoint.md");
|
|
60309
60506
|
}
|
|
60310
60507
|
function researchContainerName(runId, seq2) {
|
|
60311
60508
|
return `runcastle-${runId}-w${seq2}`;
|
|
@@ -60329,7 +60526,7 @@ async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
|
60329
60526
|
RESEARCH_DOC_PATH: docRelPath
|
|
60330
60527
|
});
|
|
60331
60528
|
mkdirSync12(logsDir(), { recursive: true });
|
|
60332
|
-
const logFilePath =
|
|
60529
|
+
const logFilePath = join25(logsDir(), `research-${feature.id}-${waypoint.seq}.log`);
|
|
60333
60530
|
const throttle = createResearchStreamThrottle((e) => ctx.emitEvent(e));
|
|
60334
60531
|
const codexAuthMount = codexAuthMountFor(model.runtime, config2.sandbox);
|
|
60335
60532
|
const runOptions = {
|
|
@@ -60592,29 +60789,43 @@ var init_runner = __esm(() => {
|
|
|
60592
60789
|
|
|
60593
60790
|
// src/services/features.ts
|
|
60594
60791
|
import { desc as desc4, eq as eq11 } from "drizzle-orm";
|
|
60595
|
-
import { rmSync as
|
|
60792
|
+
import { rmSync as rmSync5 } from "fs";
|
|
60596
60793
|
async function createFeature(ctx, input) {
|
|
60597
60794
|
const project3 = requireProjectById(ctx, input.projectId);
|
|
60598
60795
|
const slug = uniqueSlug(ctx, project3.id, input.title);
|
|
60599
60796
|
const branch = `feature/${slug}`;
|
|
60600
|
-
const cut
|
|
60797
|
+
const { cut, baseBranch, status: status3 } = await parkOrCut(ctx, project3, slug, input);
|
|
60601
60798
|
const row = {
|
|
60602
60799
|
id: newId("feat"),
|
|
60603
60800
|
projectId: project3.id,
|
|
60604
60801
|
slug,
|
|
60605
60802
|
title: input.title,
|
|
60606
60803
|
oneLiner: input.oneLiner,
|
|
60607
|
-
brief:
|
|
60804
|
+
brief: cut ? null : input.brief ?? null,
|
|
60608
60805
|
mapped: false,
|
|
60609
60806
|
lap: 1,
|
|
60610
60807
|
phase: "planning",
|
|
60611
60808
|
branch,
|
|
60612
|
-
baseBranch
|
|
60613
|
-
status:
|
|
60809
|
+
baseBranch,
|
|
60810
|
+
status: status3,
|
|
60614
60811
|
createdAt: Date.now()
|
|
60615
60812
|
};
|
|
60616
60813
|
const inserted = ctx.db.insert(features).values(row).returning().get();
|
|
60617
60814
|
const feature = rowToFeature(inserted);
|
|
60815
|
+
emitFeatureCreated(ctx, feature, cut);
|
|
60816
|
+
if (!cut)
|
|
60817
|
+
return feature;
|
|
60818
|
+
await scaffoldDocsOnFeatureBranch(ctx, project3, feature, { brief: input.brief });
|
|
60819
|
+
return feature;
|
|
60820
|
+
}
|
|
60821
|
+
async function parkOrCut(ctx, project3, slug, input) {
|
|
60822
|
+
if (input.draft)
|
|
60823
|
+
return { cut: null, baseBranch: null, status: "draft" };
|
|
60824
|
+
const cut = await ensureFeatureBranch(ctx, project3, slug, await requestedBase(ctx, project3, input.baseBranch));
|
|
60825
|
+
return { cut, baseBranch: cut.baseBranch, status: "active" };
|
|
60826
|
+
}
|
|
60827
|
+
function emitFeatureCreated(ctx, feature, cut) {
|
|
60828
|
+
const { slug, branch } = feature;
|
|
60618
60829
|
emit(ctx, feature.id, {
|
|
60619
60830
|
type: "feature.created",
|
|
60620
60831
|
message: !cut ? `feature.created (draft \u2014 ${branch} not cut yet)` : cut.branchReady ? `feature.created (${branch} \u2190 ${cut.baseBranch})` : "feature.created (branch pending)",
|
|
@@ -60626,10 +60837,6 @@ async function createFeature(ctx, input) {
|
|
|
60626
60837
|
draft: !cut
|
|
60627
60838
|
}
|
|
60628
60839
|
});
|
|
60629
|
-
if (!cut)
|
|
60630
|
-
return feature;
|
|
60631
|
-
await scaffoldDocsOnFeatureBranch(ctx, project3, feature, { brief: input.brief });
|
|
60632
|
-
return feature;
|
|
60633
60840
|
}
|
|
60634
60841
|
function requireNotDraft(feature) {
|
|
60635
60842
|
if (feature.status === "draft") {
|
|
@@ -60698,8 +60905,8 @@ async function quickChange(ctx, input) {
|
|
|
60698
60905
|
throw new InvalidInputError("a quick change needs a sentence describing the change");
|
|
60699
60906
|
const slug = uniqueSlug(ctx, project3.id, title);
|
|
60700
60907
|
const branch = `feature/${slug}`;
|
|
60701
|
-
const
|
|
60702
|
-
const {
|
|
60908
|
+
const brief = quickBrief(title, proses);
|
|
60909
|
+
const { cut, baseBranch, status: status3 } = await parkOrCut(ctx, project3, slug, input);
|
|
60703
60910
|
const inserted = ctx.db.insert(features).values({
|
|
60704
60911
|
id: newId("feat"),
|
|
60705
60912
|
projectId: project3.id,
|
|
@@ -60707,22 +60914,20 @@ async function quickChange(ctx, input) {
|
|
|
60707
60914
|
title,
|
|
60708
60915
|
oneLiner: proses[0].split(`
|
|
60709
60916
|
`)[0].trim(),
|
|
60917
|
+
brief: cut ? null : brief,
|
|
60710
60918
|
mapped: false,
|
|
60711
60919
|
lap: 1,
|
|
60712
60920
|
ticketsReadyLap: 1,
|
|
60713
60921
|
phase: "planning",
|
|
60714
60922
|
branch,
|
|
60715
60923
|
baseBranch,
|
|
60716
|
-
status:
|
|
60924
|
+
status: status3,
|
|
60717
60925
|
createdAt: Date.now()
|
|
60718
60926
|
}).returning().get();
|
|
60719
60927
|
const feature = rowToFeature(inserted);
|
|
60720
|
-
|
|
60721
|
-
|
|
60722
|
-
|
|
60723
|
-
data: { slug, branch, baseBranch, branchReady }
|
|
60724
|
-
});
|
|
60725
|
-
await scaffoldDocsOnFeatureBranch(ctx, project3, feature, { brief: quickBrief(title, proses) });
|
|
60928
|
+
emitFeatureCreated(ctx, feature, cut);
|
|
60929
|
+
if (cut)
|
|
60930
|
+
await scaffoldDocsOnFeatureBranch(ctx, project3, feature, { brief });
|
|
60726
60931
|
const stored = storeTickets(ctx, feature.id, [
|
|
60727
60932
|
...proses.map((prose) => ({
|
|
60728
60933
|
title: quickTicketTitle(prose),
|
|
@@ -61013,7 +61218,7 @@ function escalateToMap(ctx, featureId, input) {
|
|
|
61013
61218
|
});
|
|
61014
61219
|
return { ok: true };
|
|
61015
61220
|
}
|
|
61016
|
-
function archiveFeature(ctx, featureId) {
|
|
61221
|
+
async function archiveFeature(ctx, featureId) {
|
|
61017
61222
|
const feature = getFeatureRow(ctx, featureId);
|
|
61018
61223
|
requireNotDraft(feature);
|
|
61019
61224
|
if (feature.status === "archived") {
|
|
@@ -61028,6 +61233,7 @@ function archiveFeature(ctx, featureId) {
|
|
|
61028
61233
|
message: `feature ${feature.slug} archived`,
|
|
61029
61234
|
data: { from: feature.status }
|
|
61030
61235
|
});
|
|
61236
|
+
await retireArchivedWorktree(ctx, projectForFeature(ctx, feature), feature);
|
|
61031
61237
|
return { ...feature, status: "archived" };
|
|
61032
61238
|
}
|
|
61033
61239
|
function unarchiveFeature(ctx, featureId) {
|
|
@@ -61069,7 +61275,7 @@ async function deleteFeature(ctx, featureId) {
|
|
|
61069
61275
|
});
|
|
61070
61276
|
for (const s of listSessionsByFeature(ctx, featureId)) {
|
|
61071
61277
|
try {
|
|
61072
|
-
|
|
61278
|
+
rmSync5(sessionDir(s.id), { recursive: true, force: true });
|
|
61073
61279
|
} catch {}
|
|
61074
61280
|
}
|
|
61075
61281
|
deleteFeatureRows(ctx, featureId);
|
|
@@ -61104,6 +61310,7 @@ var init_features = __esm(() => {
|
|
|
61104
61310
|
init_schema();
|
|
61105
61311
|
init_errors();
|
|
61106
61312
|
init_events();
|
|
61313
|
+
init_feature_worktrees();
|
|
61107
61314
|
init_git();
|
|
61108
61315
|
init_knowledge();
|
|
61109
61316
|
init_repo();
|
|
@@ -61164,7 +61371,7 @@ var init_burn_warnings = __esm(() => {
|
|
|
61164
61371
|
});
|
|
61165
61372
|
|
|
61166
61373
|
// src/services/project-notes.ts
|
|
61167
|
-
import { existsSync as existsSync21, mkdirSync as mkdirSync13, rmSync as
|
|
61374
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync13, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
61168
61375
|
import { and as and9, count as count3, desc as desc5, eq as eq12, inArray as inArray4 } from "drizzle-orm";
|
|
61169
61376
|
function rowToNote(row) {
|
|
61170
61377
|
return ProjectNote.parse({
|
|
@@ -61229,7 +61436,7 @@ function deleteNote(ctx, noteId) {
|
|
|
61229
61436
|
const note2 = getNote(ctx, noteId);
|
|
61230
61437
|
requireOpen(note2, "delete");
|
|
61231
61438
|
ctx.db.delete(projectNotes).where(eq12(projectNotes.id, noteId)).run();
|
|
61232
|
-
|
|
61439
|
+
rmSync6(projectNotePath(noteId), { force: true });
|
|
61233
61440
|
emitProject(ctx, note2.projectId, { type: "project_note.deleted", message: "project note deleted", data: { noteId } });
|
|
61234
61441
|
}
|
|
61235
61442
|
function triage(ctx, noteIds, outcome, featureId, verb = "triaged") {
|
|
@@ -61354,7 +61561,7 @@ var init_runs = __esm(() => {
|
|
|
61354
61561
|
});
|
|
61355
61562
|
|
|
61356
61563
|
// src/services/test-notes.ts
|
|
61357
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync14, rmSync as
|
|
61564
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync14, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
61358
61565
|
import { dirname as dirname14 } from "path";
|
|
61359
61566
|
import { and as and11, asc as asc6, eq as eq14, lt } from "drizzle-orm";
|
|
61360
61567
|
function rowToNote2(row) {
|
|
@@ -61443,7 +61650,7 @@ function deleteNote2(ctx, noteId) {
|
|
|
61443
61650
|
const current2 = getNote2(ctx, noteId);
|
|
61444
61651
|
assertOpen(current2, "delete");
|
|
61445
61652
|
ctx.db.delete(testNotes).where(eq14(testNotes.id, noteId)).run();
|
|
61446
|
-
|
|
61653
|
+
rmSync7(annotationPath(noteId), { force: true });
|
|
61447
61654
|
emit(ctx, current2.featureId, {
|
|
61448
61655
|
type: "note.deleted",
|
|
61449
61656
|
message: "note deleted",
|
|
@@ -61644,7 +61851,7 @@ var MCP_READ_CEILING_CHARS = 60000;
|
|
|
61644
61851
|
|
|
61645
61852
|
// src/mcp/server.ts
|
|
61646
61853
|
import { existsSync as existsSync23, readFileSync as readFileSync14 } from "fs";
|
|
61647
|
-
import { join as
|
|
61854
|
+
import { join as join26, relative as relative4, resolve as resolve10, sep as sep2 } from "path";
|
|
61648
61855
|
import { StreamableHTTPTransport } from "@hono/mcp";
|
|
61649
61856
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
61650
61857
|
import { Hono } from "hono";
|
|
@@ -61727,7 +61934,7 @@ function featureContext(ctx, reader) {
|
|
|
61727
61934
|
doc: { relPath: summary.relPath, content },
|
|
61728
61935
|
ref: {
|
|
61729
61936
|
relPath: summary.relPath,
|
|
61730
|
-
absPath:
|
|
61937
|
+
absPath: join26(docsDir, summary.relPath),
|
|
61731
61938
|
bytes: Buffer.byteLength(content, "utf8"),
|
|
61732
61939
|
reason: notInlinedReason(summary.relPath)
|
|
61733
61940
|
}
|
|
@@ -62141,7 +62348,8 @@ async function toolCreateFeature(ctx, session, input) {
|
|
|
62141
62348
|
projectId: project3.id,
|
|
62142
62349
|
title: input.title,
|
|
62143
62350
|
tickets: input.tickets,
|
|
62144
|
-
baseBranch: input.baseBranch
|
|
62351
|
+
baseBranch: input.baseBranch,
|
|
62352
|
+
draft: input.draft
|
|
62145
62353
|
}) : await createFeature(ctx, {
|
|
62146
62354
|
projectId: project3.id,
|
|
62147
62355
|
title: input.title,
|
|
@@ -62219,7 +62427,7 @@ function adrRef(adr) {
|
|
|
62219
62427
|
}
|
|
62220
62428
|
function toolReadAdr(ctx, session, input) {
|
|
62221
62429
|
requireProject(ctx, session);
|
|
62222
|
-
const root = resolve10(
|
|
62430
|
+
const root = resolve10(join26(session.worktreePath, ADR_DIR_REL));
|
|
62223
62431
|
const name = input.relPath.replace(/\\/g, "/").replace(/^\.?\/*docs\/adr\//i, "");
|
|
62224
62432
|
const target = resolve10(root, name);
|
|
62225
62433
|
const rel = relative4(root, target);
|
|
@@ -62248,7 +62456,7 @@ function toolReadFeatureBrief(ctx, session, input) {
|
|
|
62248
62456
|
const feature = list(ctx, project3.id).find((f) => f.slug === input.slug);
|
|
62249
62457
|
if (!feature)
|
|
62250
62458
|
throw new NotFoundError(`feature not found: ${input.slug}`);
|
|
62251
|
-
const onDisk =
|
|
62459
|
+
const onDisk = join26(session.worktreePath, featureDocsRel(feature.slug), "brief.md");
|
|
62252
62460
|
const brief = feature.status === "shipped" && existsSync23(onDisk) ? readFileSync14(onDisk, "utf8") : feature.brief;
|
|
62253
62461
|
return {
|
|
62254
62462
|
slug: feature.slug,
|
|
@@ -62474,7 +62682,7 @@ function buildMcpServer(audience) {
|
|
|
62474
62682
|
oneLiner: z5.string().optional().describe("One line saying what this feature is. Required for every shape EXCEPT `tickets`, " + "where the first ticket\u2019s prose becomes the one-liner and anything sent here is " + "ignored."),
|
|
62475
62683
|
baseBranch: z5.string().optional().describe("Branch to cut from \u2014 always pass it explicitly, naming the base you told the " + "human you would use (see `get_project_context`\u2019s `baseBranches`). Omitted, it " + "falls back to whatever the project checkout happens to be on. Drafts pass none: " + "a parked feature picks its base at Start."),
|
|
62476
62684
|
brief: z5.string().optional().describe("The reasoning you just worked out with the human \u2014 why this feature exists and " + "what it must NOT swallow. Becomes brief.md verbatim, and without it that " + "reasoning is lost. Unused by the `tickets` shape, whose brief IS the proses."),
|
|
62477
|
-
draft: z5.boolean().optional().describe("PARK it instead of starting it: a row and its brief, no branch and no files, " + "until the human clicks Start. Ask them per feature whether to start or park."),
|
|
62685
|
+
draft: z5.boolean().optional().describe("PARK it instead of starting it: a row and its brief, no branch and no files, " + "until the human clicks Start. With `tickets`, the draft keeps them and Start lands " + "it ready to Burn. Ask them per feature whether to start or park."),
|
|
62478
62686
|
tickets: z5.array(z5.string().min(1)).min(1).optional().describe("The quick-change shape: work too small to deserve a grill. Creates ONE feature at " + "the implementation phase carrying a ticket per prose (plus the review ticket " + "every batch closes with). Send every sentence of one quick change in a SINGLE " + "call \u2014 calling this once per ticket makes one feature per ticket.")
|
|
62479
62687
|
}
|
|
62480
62688
|
}, async (args2, extra) => {
|
|
@@ -62996,7 +63204,7 @@ var init_ws = __esm(() => {
|
|
|
62996
63204
|
// src/services/prep.ts
|
|
62997
63205
|
import { and as and12, desc as desc6, eq as eq15, inArray as inArray6 } from "drizzle-orm";
|
|
62998
63206
|
import { readdir as readdir3 } from "fs/promises";
|
|
62999
|
-
import { join as
|
|
63207
|
+
import { join as join27 } from "path";
|
|
63000
63208
|
function keysToPrepare(ctx, project3) {
|
|
63001
63209
|
return unsetPreparedKeys(project3).filter((key) => isOverwritable(ctx, project3.id, key));
|
|
63002
63210
|
}
|
|
@@ -63023,7 +63231,7 @@ async function isEmptyRepo(repoPath) {
|
|
|
63023
63231
|
return false;
|
|
63024
63232
|
if (!rootEntries.includes("docs"))
|
|
63025
63233
|
return true;
|
|
63026
|
-
const docsEntries = await readdir3(
|
|
63234
|
+
const docsEntries = await readdir3(join27(repoPath, "docs"));
|
|
63027
63235
|
return docsEntries.every((entry) => SCAFFOLDED_DOCS_DIRS.includes(entry));
|
|
63028
63236
|
}
|
|
63029
63237
|
async function prepView(ctx, project3) {
|
|
@@ -63509,7 +63717,7 @@ var init_stream = __esm(() => {
|
|
|
63509
63717
|
// src/routes/web.ts
|
|
63510
63718
|
import { existsSync as existsSync24, statSync as statSync6 } from "fs";
|
|
63511
63719
|
import { readFile as readFile5 } from "fs/promises";
|
|
63512
|
-
import { extname, join as
|
|
63720
|
+
import { extname, join as join28, normalize as normalize2, resolve as resolve11, sep as sep3 } from "path";
|
|
63513
63721
|
function contentType(path2) {
|
|
63514
63722
|
return MIME[extname(path2).toLowerCase()] ?? "application/octet-stream";
|
|
63515
63723
|
}
|
|
@@ -63527,7 +63735,7 @@ function safeJoin(root, pathname) {
|
|
|
63527
63735
|
} catch {
|
|
63528
63736
|
return null;
|
|
63529
63737
|
}
|
|
63530
|
-
const joined = normalize2(
|
|
63738
|
+
const joined = normalize2(join28(root, decoded));
|
|
63531
63739
|
if (joined !== root && !joined.startsWith(root + sep3))
|
|
63532
63740
|
return null;
|
|
63533
63741
|
return joined;
|
|
@@ -63540,7 +63748,7 @@ function resolveWebDist() {
|
|
|
63540
63748
|
}
|
|
63541
63749
|
function mountWebApp(app, distDir) {
|
|
63542
63750
|
const root = resolve11(distDir);
|
|
63543
|
-
const indexHtml =
|
|
63751
|
+
const indexHtml = join28(root, "index.html");
|
|
63544
63752
|
app.get("*", async (c, next4) => {
|
|
63545
63753
|
const { pathname } = new URL(c.req.url);
|
|
63546
63754
|
if (/^\/(api|mcp|ws|health)(\/|$)/.test(pathname))
|
|
@@ -63560,7 +63768,7 @@ function mountWebApp(app, distDir) {
|
|
|
63560
63768
|
}
|
|
63561
63769
|
function mountWebAppIfBuilt(app) {
|
|
63562
63770
|
const dist = resolveWebDist();
|
|
63563
|
-
if (existsSync24(
|
|
63771
|
+
if (existsSync24(join28(dist, "index.html")))
|
|
63564
63772
|
mountWebApp(app, dist);
|
|
63565
63773
|
}
|
|
63566
63774
|
var MIME;
|
|
@@ -63588,13 +63796,13 @@ var init_web = __esm(() => {
|
|
|
63588
63796
|
|
|
63589
63797
|
// src/version.ts
|
|
63590
63798
|
import { readFileSync as readFileSync15 } from "fs";
|
|
63591
|
-
import { dirname as dirname15, join as
|
|
63799
|
+
import { dirname as dirname15, join as join29 } from "path";
|
|
63592
63800
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
63593
63801
|
function runcastleVersion() {
|
|
63594
63802
|
if (cached4)
|
|
63595
63803
|
return cached4;
|
|
63596
63804
|
const here = dirname15(fileURLToPath8(import.meta.url));
|
|
63597
|
-
for (const pkgPath of [
|
|
63805
|
+
for (const pkgPath of [join29(here, "..", "package.json"), join29(here, "package.json")]) {
|
|
63598
63806
|
try {
|
|
63599
63807
|
const pkg = JSON.parse(readFileSync15(pkgPath, "utf8"));
|
|
63600
63808
|
if (typeof pkg.version === "string") {
|
|
@@ -63652,6 +63860,10 @@ function compareSemver(a, b) {
|
|
|
63652
63860
|
if (diff8 !== 0)
|
|
63653
63861
|
return diff8;
|
|
63654
63862
|
}
|
|
63863
|
+
const la = pa.pre[0] === LOCAL_BUILD_PRERELEASE;
|
|
63864
|
+
const lb = pb.pre[0] === LOCAL_BUILD_PRERELEASE;
|
|
63865
|
+
if (la !== lb)
|
|
63866
|
+
return la ? 1 : -1;
|
|
63655
63867
|
if (pa.pre.length === 0 && pb.pre.length === 0)
|
|
63656
63868
|
return 0;
|
|
63657
63869
|
if (pa.pre.length === 0)
|
|
@@ -63733,7 +63945,7 @@ async function getUpdateInfo(current2, fetchImpl) {
|
|
|
63733
63945
|
cache = info;
|
|
63734
63946
|
return info;
|
|
63735
63947
|
}
|
|
63736
|
-
var PACKAGE_NAME = "runcastle", UPDATE_COMMAND, PING_URL = "https://ping.runcastle.dev/ping", NPM_LATEST_URL, NO_UPDATE = (current2) => ({
|
|
63948
|
+
var PACKAGE_NAME = "runcastle", UPDATE_COMMAND, PING_URL = "https://ping.runcastle.dev/ping", NPM_LATEST_URL, LOCAL_BUILD_PRERELEASE = "local", NO_UPDATE = (current2) => ({
|
|
63737
63949
|
current: current2,
|
|
63738
63950
|
latest: null,
|
|
63739
63951
|
updateAvailable: false,
|
|
@@ -63822,7 +64034,7 @@ var init_events2 = __esm(() => {
|
|
|
63822
64034
|
|
|
63823
64035
|
// src/launcher/launcher.ts
|
|
63824
64036
|
import { existsSync as existsSync26 } from "fs";
|
|
63825
|
-
import { join as
|
|
64037
|
+
import { join as join30 } from "path";
|
|
63826
64038
|
import { and as and13, eq as eq16 } from "drizzle-orm";
|
|
63827
64039
|
function ptyExitMessage(exitCode) {
|
|
63828
64040
|
const label = typeof exitCode === "number" ? String(exitCode) : "unknown";
|
|
@@ -64437,7 +64649,7 @@ async function buildPrepareBrief(ctx, project3) {
|
|
|
64437
64649
|
};
|
|
64438
64650
|
}
|
|
64439
64651
|
function probePrepareHost(repoPath) {
|
|
64440
|
-
const has4 = (rel) => existsSync26(
|
|
64652
|
+
const has4 = (rel) => existsSync26(join30(repoPath, rel));
|
|
64441
64653
|
const lock = LOCKFILES.find(([file]) => has4(file));
|
|
64442
64654
|
return {
|
|
64443
64655
|
platform: process.platform,
|
|
@@ -64590,7 +64802,7 @@ var init_launcher = __esm(() => {
|
|
|
64590
64802
|
|
|
64591
64803
|
// src/services/outcome.ts
|
|
64592
64804
|
import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync12 } from "fs";
|
|
64593
|
-
import { join as
|
|
64805
|
+
import { join as join31 } from "path";
|
|
64594
64806
|
function digestHasSubstance(text3) {
|
|
64595
64807
|
return text3.split(`
|
|
64596
64808
|
`).some((line) => {
|
|
@@ -64690,9 +64902,9 @@ async function promoteOutcomeDoc(ctx, project3, feature, target, delta) {
|
|
|
64690
64902
|
delta: { commits: delta.commits ?? 0, files: delta.files ?? 0 }
|
|
64691
64903
|
});
|
|
64692
64904
|
await onBranch(project3.repoPath, target, async () => {
|
|
64693
|
-
const docsDir =
|
|
64905
|
+
const docsDir = join31(project3.repoPath, ...featureDocsRel(feature.slug).split("/"));
|
|
64694
64906
|
mkdirSync16(docsDir, { recursive: true });
|
|
64695
|
-
writeFileSync12(
|
|
64907
|
+
writeFileSync12(join31(docsDir, "outcome.md"), doc, "utf8");
|
|
64696
64908
|
await commitDocs(project3.repoPath, docsCommitMessage(`outcome for ${feature.slug}`, project3.docsCommitPrefix));
|
|
64697
64909
|
});
|
|
64698
64910
|
} catch (error) {
|
|
@@ -64721,6 +64933,7 @@ var init_feature = __esm(() => {
|
|
|
64721
64933
|
init_launcher();
|
|
64722
64934
|
init_burn_warnings();
|
|
64723
64935
|
init_events();
|
|
64936
|
+
init_feature_worktrees();
|
|
64724
64937
|
init_features();
|
|
64725
64938
|
init_git();
|
|
64726
64939
|
init_outcome();
|
|
@@ -64800,6 +65013,7 @@ var init_feature = __esm(() => {
|
|
|
64800
65013
|
await promoteOutcomeDoc(ctx, project3, feature, res.target, delta);
|
|
64801
65014
|
setPhase(ctx, input.featureId, "shipped", "feature.shipped", `merged to ${res.target}`);
|
|
64802
65015
|
setFeatureStatus(ctx, input.featureId, "shipped");
|
|
65016
|
+
await retireShippedWorktree(ctx, project3, feature);
|
|
64803
65017
|
} else {
|
|
64804
65018
|
emit(ctx, input.featureId, {
|
|
64805
65019
|
type: "merge.conflict",
|
|
@@ -64813,9 +65027,9 @@ var init_feature = __esm(() => {
|
|
|
64813
65027
|
});
|
|
64814
65028
|
|
|
64815
65029
|
// src/services/fsbrowse.ts
|
|
64816
|
-
import { existsSync as existsSync27, readdirSync as
|
|
65030
|
+
import { existsSync as existsSync27, readdirSync as readdirSync8, statSync as statSync7 } from "fs";
|
|
64817
65031
|
import { homedir as homedir4 } from "os";
|
|
64818
|
-
import { basename as basename4, dirname as dirname17, isAbsolute as isAbsolute3, join as
|
|
65032
|
+
import { basename as basename4, dirname as dirname17, isAbsolute as isAbsolute3, join as join33, resolve as resolve12, sep as sep4 } from "path";
|
|
64819
65033
|
function expandPath(input, home = homedir4()) {
|
|
64820
65034
|
const trimmed = input.trim();
|
|
64821
65035
|
if (trimmed === "")
|
|
@@ -64824,12 +65038,12 @@ function expandPath(input, home = homedir4()) {
|
|
|
64824
65038
|
if (out === "~")
|
|
64825
65039
|
out = home;
|
|
64826
65040
|
else if (out.startsWith("~/") || out.startsWith("~\\"))
|
|
64827
|
-
out =
|
|
65041
|
+
out = join33(home, out.slice(2));
|
|
64828
65042
|
return resolve12(out);
|
|
64829
65043
|
}
|
|
64830
65044
|
function looksLikeRepo(dir) {
|
|
64831
65045
|
try {
|
|
64832
|
-
return existsSync27(
|
|
65046
|
+
return existsSync27(join33(dir, ".git"));
|
|
64833
65047
|
} catch {
|
|
64834
65048
|
return false;
|
|
64835
65049
|
}
|
|
@@ -64851,7 +65065,7 @@ function listRoots(home = homedir4()) {
|
|
|
64851
65065
|
roots3.push({ label: "Home", path: home, kind: "home" });
|
|
64852
65066
|
const seen = new Set(roots3.map(pathKey));
|
|
64853
65067
|
for (const name of COMMON_DIRS) {
|
|
64854
|
-
const candidate =
|
|
65068
|
+
const candidate = join33(home, name);
|
|
64855
65069
|
const key = pathKey({ path: candidate });
|
|
64856
65070
|
if (seen.has(key) || !existsSync27(candidate))
|
|
64857
65071
|
continue;
|
|
@@ -64896,7 +65110,7 @@ function browseDir(input, showHidden = false, home = homedir4()) {
|
|
|
64896
65110
|
}
|
|
64897
65111
|
let dirents;
|
|
64898
65112
|
try {
|
|
64899
|
-
dirents =
|
|
65113
|
+
dirents = readdirSync8(dir, { withFileTypes: true, encoding: "utf8" });
|
|
64900
65114
|
} catch (e) {
|
|
64901
65115
|
throw new InvalidInputError(`cannot read directory: ${dir} (${errMsg2(e)})`);
|
|
64902
65116
|
}
|
|
@@ -64912,7 +65126,7 @@ function browseDir(input, showHidden = false, home = homedir4()) {
|
|
|
64912
65126
|
continue;
|
|
64913
65127
|
if (!showHidden && name.startsWith("."))
|
|
64914
65128
|
continue;
|
|
64915
|
-
const path2 =
|
|
65129
|
+
const path2 = join33(dir, name);
|
|
64916
65130
|
const isSymlink = dirent.isSymbolicLink();
|
|
64917
65131
|
if (isSymlink && !showHidden)
|
|
64918
65132
|
continue;
|
|
@@ -65154,7 +65368,7 @@ var init_conversations = __esm(() => {
|
|
|
65154
65368
|
|
|
65155
65369
|
// src/services/projects.ts
|
|
65156
65370
|
import { existsSync as existsSync28 } from "fs";
|
|
65157
|
-
import { basename as basename5, join as
|
|
65371
|
+
import { basename as basename5, join as join35 } from "path";
|
|
65158
65372
|
import { eq as eq17, isNull } from "drizzle-orm";
|
|
65159
65373
|
function listProjects(ctx) {
|
|
65160
65374
|
return ctx.db.select().from(projects).where(isNull(projects.closedAt)).all().map(rowToProject);
|
|
@@ -65238,7 +65452,7 @@ async function assertRepoTolerant(ctx, repoPath) {
|
|
|
65238
65452
|
} catch (e) {
|
|
65239
65453
|
if (!isNotImplemented(e))
|
|
65240
65454
|
throw e;
|
|
65241
|
-
if (!existsSync28(
|
|
65455
|
+
if (!existsSync28(join35(repoPath, ".git"))) {
|
|
65242
65456
|
throw new InvalidInputError(`not a git repository: ${repoPath}`);
|
|
65243
65457
|
}
|
|
65244
65458
|
}
|
|
@@ -65442,7 +65656,7 @@ var init_project_image = __esm(() => {
|
|
|
65442
65656
|
});
|
|
65443
65657
|
|
|
65444
65658
|
// src/trpc/routers/setup.ts
|
|
65445
|
-
import { join as
|
|
65659
|
+
import { join as join36 } from "path";
|
|
65446
65660
|
import { isNotNull as isNotNull4 } from "drizzle-orm";
|
|
65447
65661
|
import * as z14 from "zod";
|
|
65448
65662
|
function projectImageEnv(ctx, project3) {
|
|
@@ -65463,7 +65677,7 @@ async function buildImageTerminal(ctx, projectId) {
|
|
|
65463
65677
|
const notices = Object.values(host.problems);
|
|
65464
65678
|
const stockFresh = imageFreshness({
|
|
65465
65679
|
image: await inspectBuiltImage(exec3, runtime5, DEFAULT_SANDBOX_IMAGE),
|
|
65466
|
-
expectedHash: hashDockerfile(
|
|
65680
|
+
expectedHash: hashDockerfile(join36(stockContext, "Dockerfile")),
|
|
65467
65681
|
host: host.versions
|
|
65468
65682
|
}).fresh;
|
|
65469
65683
|
const plan = planImageBuild({
|
|
@@ -65495,7 +65709,7 @@ async function buildImageTerminal(ctx, projectId) {
|
|
|
65495
65709
|
}
|
|
65496
65710
|
};
|
|
65497
65711
|
}
|
|
65498
|
-
var setupRouter;
|
|
65712
|
+
var DOCTOR_PROBE_TIMEOUT_MS = 1e4, setupRouter;
|
|
65499
65713
|
var init_setup2 = __esm(() => {
|
|
65500
65714
|
init_src();
|
|
65501
65715
|
init_schema();
|
|
@@ -65521,7 +65735,7 @@ var init_setup2 = __esm(() => {
|
|
|
65521
65735
|
const projectModels = ctx.db.select({ model: projects.model }).from(projects).where(isNotNull4(projects.model)).all().map((p) => p.model);
|
|
65522
65736
|
const project3 = input?.projectId ? requireProjectById(ctx, input.projectId) : null;
|
|
65523
65737
|
return runDoctor({
|
|
65524
|
-
exec: createSystemExec(),
|
|
65738
|
+
exec: createSystemExec({ timeoutMs: DOCTOR_PROBE_TIMEOUT_MS }),
|
|
65525
65739
|
burnerDockerfile: burnerDockerfilePath(),
|
|
65526
65740
|
env: envWithAfkCredentials(),
|
|
65527
65741
|
runtimes: configuredRuntimes(rosterConfig(ctx), projectModels),
|
|
@@ -65842,6 +66056,11 @@ async function startServer() {
|
|
|
65842
66056
|
if (staleRuns.length > 0) {
|
|
65843
66057
|
console.log(`reconciled ${staleRuns.length} stale run(s) from a previous server run`);
|
|
65844
66058
|
}
|
|
66059
|
+
sweepFeatureWorktrees(ctx).then((s) => {
|
|
66060
|
+
if (s.removed + s.orphanProjects + s.failed > 0) {
|
|
66061
|
+
console.log(`worktree sweep: removed ${s.removed} feature worktree(s) and ` + `${s.orphanProjects} orphaned project dir(s); kept ${s.kept} dirty, ${s.failed} failed`);
|
|
66062
|
+
}
|
|
66063
|
+
});
|
|
65845
66064
|
const server = Bun.serve({
|
|
65846
66065
|
port: config2.serverPort,
|
|
65847
66066
|
hostname: SERVE_HOSTNAME,
|
|
@@ -65884,6 +66103,7 @@ var init_src2 = __esm(() => {
|
|
|
65884
66103
|
init_reviews();
|
|
65885
66104
|
init_stream();
|
|
65886
66105
|
init_web();
|
|
66106
|
+
init_feature_worktrees();
|
|
65887
66107
|
init_model_discovery();
|
|
65888
66108
|
init_settings();
|
|
65889
66109
|
init_update_check();
|