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