runcastle 1.2.9 → 1.2.10
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 +295 -183
- package/drizzle/0030_productive_marvex.sql +1 -0
- package/drizzle/0031_backfill_feature_base_branch.sql +10 -0
- package/drizzle/0032_gigantic_tempest.sql +1 -0
- package/drizzle/meta/0030_snapshot.json +956 -0
- package/drizzle/meta/0031_snapshot.json +956 -0
- package/drizzle/meta/0032_snapshot.json +949 -0
- package/drizzle/meta/_journal.json +22 -1
- package/index.js +293 -181
- package/package.json +1 -1
- package/skills/packs/runcastle/skills/project/SKILL.md +11 -1
- package/web/assets/index-BCAA3_oq.css +1 -0
- package/web/assets/index-BlVLhZP1.js +142 -0
- package/web/index.html +2 -2
- package/web/assets/index-BRjcVBgj.js +0 -142
- package/web/assets/index-CNSn8X1O.css +0 -1
package/index.js
CHANGED
|
@@ -146,7 +146,6 @@ var init_config = __esm(() => {
|
|
|
146
146
|
models: z.array(ModelEntry).default([]),
|
|
147
147
|
sandbox: z.enum(["docker", "podman", "noSandbox"]).default("docker"),
|
|
148
148
|
sessionMcp: z.enum(["inherit", "runcastleOnly"]).default("inherit"),
|
|
149
|
-
mainBranch: z.string().default("main"),
|
|
150
149
|
sandboxImage: z.string().optional(),
|
|
151
150
|
burnConcurrency: z.number().int().min(1).max(8).default(3),
|
|
152
151
|
burnCpus: z.number().positive().max(256).optional(),
|
|
@@ -276,7 +275,7 @@ var init_schemas = __esm(() => {
|
|
|
276
275
|
id: z2.string(),
|
|
277
276
|
name: z2.string(),
|
|
278
277
|
repoPath: z2.string(),
|
|
279
|
-
|
|
278
|
+
sessionBranch: z2.string().optional(),
|
|
280
279
|
devCommand: z2.string().optional(),
|
|
281
280
|
model: z2.string().optional(),
|
|
282
281
|
setupCommand: z2.string().optional(),
|
|
@@ -543,7 +542,7 @@ var init_db_schema = __esm(() => {
|
|
|
543
542
|
id: text("id").primaryKey(),
|
|
544
543
|
name: text("name").notNull(),
|
|
545
544
|
repoPath: text("repo_path").notNull(),
|
|
546
|
-
|
|
545
|
+
sessionBranch: text("session_branch"),
|
|
547
546
|
devCommand: text("dev_command"),
|
|
548
547
|
model: text("model"),
|
|
549
548
|
sandbox: text("sandbox"),
|
|
@@ -816,8 +815,6 @@ function loadConfig(env = process.env) {
|
|
|
816
815
|
overrides.sandbox = env.RUNCASTLE_SANDBOX;
|
|
817
816
|
if (env.RUNCASTLE_SESSION_MCP)
|
|
818
817
|
overrides.sessionMcp = env.RUNCASTLE_SESSION_MCP;
|
|
819
|
-
if (env.RUNCASTLE_MAIN_BRANCH)
|
|
820
|
-
overrides.mainBranch = env.RUNCASTLE_MAIN_BRANCH;
|
|
821
818
|
if (env.RUNCASTLE_SANDBOX_IMAGE)
|
|
822
819
|
overrides.sandboxImage = env.RUNCASTLE_SANDBOX_IMAGE;
|
|
823
820
|
num("burnConcurrency", env.RUNCASTLE_BURN_CONCURRENCY);
|
|
@@ -1759,7 +1756,7 @@ function rowToProject(row) {
|
|
|
1759
1756
|
id: row.id,
|
|
1760
1757
|
name: row.name,
|
|
1761
1758
|
repoPath: row.repoPath,
|
|
1762
|
-
|
|
1759
|
+
sessionBranch: row.sessionBranch ?? undefined,
|
|
1763
1760
|
devCommand: row.devCommand ?? undefined,
|
|
1764
1761
|
model: row.model ?? undefined,
|
|
1765
1762
|
setupCommand: row.setupCommand ?? undefined,
|
|
@@ -2497,11 +2494,12 @@ function markVerified(ctx, projectId, keys, sha) {
|
|
|
2497
2494
|
async function listFindings(ctx, project) {
|
|
2498
2495
|
const rows = ctx.db.select().from(projectFindings).where(eq4(projectFindings.projectId, project.id)).all();
|
|
2499
2496
|
const distances = new Map;
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2497
|
+
const shas = new Set(rows.flatMap((row) => row.establishedSha ? [row.establishedSha] : []));
|
|
2498
|
+
if (shas.size > 0) {
|
|
2499
|
+
const mainLine = await detectMainBranch(project.repoPath);
|
|
2500
|
+
for (const sha of shas) {
|
|
2501
|
+
distances.set(sha, await commitsSince(project.repoPath, sha, mainLine));
|
|
2502
|
+
}
|
|
2505
2503
|
}
|
|
2506
2504
|
const order = new Map(PREPARED_KEYS.map((k, i) => [k, i]));
|
|
2507
2505
|
return rows.filter((row) => isPreparedKey(row.key)).sort((a, b) => (order.get(a.key) ?? 0) - (order.get(b.key) ?? 0)).map((row) => {
|
|
@@ -2570,8 +2568,11 @@ function migrationPaths(paths) {
|
|
|
2570
2568
|
function featureBranch(slug) {
|
|
2571
2569
|
return `feature/${slug}`;
|
|
2572
2570
|
}
|
|
2573
|
-
function
|
|
2574
|
-
|
|
2571
|
+
function featureBase(feature) {
|
|
2572
|
+
if (!feature.baseBranch) {
|
|
2573
|
+
throw new GateError(`feature ${feature.slug} has no recorded base branch \u2014 it has not been cut from one yet`);
|
|
2574
|
+
}
|
|
2575
|
+
return feature.baseBranch;
|
|
2575
2576
|
}
|
|
2576
2577
|
function git(repoPath) {
|
|
2577
2578
|
return simpleGit(repoPath);
|
|
@@ -2621,9 +2622,31 @@ async function detectMainBranch(repoPath) {
|
|
|
2621
2622
|
} catch {}
|
|
2622
2623
|
return "main";
|
|
2623
2624
|
}
|
|
2625
|
+
async function resolveSessionBranch(project) {
|
|
2626
|
+
const stored = project.sessionBranch?.trim();
|
|
2627
|
+
if (!stored)
|
|
2628
|
+
return detectMainBranch(project.repoPath);
|
|
2629
|
+
const local = await git(project.repoPath).branchLocal();
|
|
2630
|
+
if (!local.all.includes(stored)) {
|
|
2631
|
+
throw new GateError(`the configured session landing branch \`${stored}\` no longer exists in ${project.repoPath} \u2014 pick where this chat's work should land`);
|
|
2632
|
+
}
|
|
2633
|
+
return stored;
|
|
2634
|
+
}
|
|
2635
|
+
async function sessionBranchView(project) {
|
|
2636
|
+
const stored = project.sessionBranch?.trim() || null;
|
|
2637
|
+
const detected = await detectMainBranch(project.repoPath);
|
|
2638
|
+
return { stored, effective: stored ?? detected, detected };
|
|
2639
|
+
}
|
|
2640
|
+
async function currentCheckoutBranch(project) {
|
|
2641
|
+
const local = await git(project.repoPath).branchLocal();
|
|
2642
|
+
if (local.detached || !local.current) {
|
|
2643
|
+
throw new InvalidInputError(`${project.repoPath} has no branch checked out (detached HEAD) \u2014 name the branch to fork from`);
|
|
2644
|
+
}
|
|
2645
|
+
return local.current;
|
|
2646
|
+
}
|
|
2624
2647
|
async function createFeatureBranch(project, slug, base) {
|
|
2625
2648
|
const branch = featureBranch(slug);
|
|
2626
|
-
const from = base
|
|
2649
|
+
const from = base.trim();
|
|
2627
2650
|
const g = git(project.repoPath);
|
|
2628
2651
|
const branches = await g.branchLocal();
|
|
2629
2652
|
if (branches.all.includes(branch))
|
|
@@ -2637,7 +2660,9 @@ async function createFeatureBranch(project, slug, base) {
|
|
|
2637
2660
|
async function listBranches(project) {
|
|
2638
2661
|
const g = git(project.repoPath);
|
|
2639
2662
|
const local = await g.branchLocal();
|
|
2640
|
-
const
|
|
2663
|
+
const detachedRef = local.detached ? local.current : null;
|
|
2664
|
+
const current = local.detached ? "" : local.current;
|
|
2665
|
+
const branches = local.all.filter((name) => !name.startsWith("feature/") && name !== detachedRef);
|
|
2641
2666
|
const localSet = new Set(local.all);
|
|
2642
2667
|
let remoteBranches = [];
|
|
2643
2668
|
try {
|
|
@@ -2647,7 +2672,7 @@ async function listBranches(project) {
|
|
|
2647
2672
|
return !short.startsWith("feature/") && !localSet.has(short);
|
|
2648
2673
|
});
|
|
2649
2674
|
} catch {}
|
|
2650
|
-
return { current
|
|
2675
|
+
return { current, branches, remoteBranches };
|
|
2651
2676
|
}
|
|
2652
2677
|
async function resolveBaseBranch(project, base) {
|
|
2653
2678
|
const g = git(project.repoPath);
|
|
@@ -2673,7 +2698,7 @@ async function ensureTalkWorktree(project, feature) {
|
|
|
2673
2698
|
const worktreePath = worktreeDir(project.id, feature.slug);
|
|
2674
2699
|
const branch = featureBranch(feature.slug);
|
|
2675
2700
|
const g = git(project.repoPath);
|
|
2676
|
-
await ensureBranchExists(g, branch, feature
|
|
2701
|
+
await ensureBranchExists(g, branch, featureBase(feature));
|
|
2677
2702
|
if (await worktreeIsValid(g, worktreePath, branch))
|
|
2678
2703
|
return worktreePath;
|
|
2679
2704
|
if (existsSync6(worktreePath) && (await registeredWorktrees(g)).has(canon(worktreePath))) {
|
|
@@ -2755,8 +2780,8 @@ function slash(path) {
|
|
|
2755
2780
|
async function ensureProjectWorktree(project, onLanded) {
|
|
2756
2781
|
const worktreePath = worktreeDir(project.id, PROJECT_WORKTREE_SLUG);
|
|
2757
2782
|
const g = git(project.repoPath);
|
|
2758
|
-
const base = project
|
|
2759
|
-
const landed = await landProjectBranch(project);
|
|
2783
|
+
const base = await resolveSessionBranch(project);
|
|
2784
|
+
const landed = await landProjectBranch(project, base);
|
|
2760
2785
|
if (landed)
|
|
2761
2786
|
onLanded?.(landed);
|
|
2762
2787
|
const stillAhead = await branchCommitsAhead(project.repoPath, base, PROJECT_BRANCH);
|
|
@@ -2765,12 +2790,13 @@ async function ensureProjectWorktree(project, onLanded) {
|
|
|
2765
2790
|
await g.raw(["branch", PROJECT_BRANCH, base]);
|
|
2766
2791
|
}
|
|
2767
2792
|
if (await worktreeIsValid(g, worktreePath, PROJECT_BRANCH))
|
|
2768
|
-
return worktreePath;
|
|
2793
|
+
return { worktreePath, base };
|
|
2769
2794
|
if (existsSync6(worktreePath) && (await registeredWorktrees(g)).has(canon(worktreePath))) {
|
|
2770
2795
|
if (await checkoutInWorktree(worktreePath, PROJECT_BRANCH))
|
|
2771
|
-
return worktreePath;
|
|
2796
|
+
return { worktreePath, base };
|
|
2772
2797
|
}
|
|
2773
|
-
|
|
2798
|
+
const added = await addWorktree(g, worktreePath, PROJECT_BRANCH, "project worktree");
|
|
2799
|
+
return { worktreePath: added, base };
|
|
2774
2800
|
}
|
|
2775
2801
|
async function checkoutInWorktree(worktreePath, branch) {
|
|
2776
2802
|
const gw = git(worktreePath);
|
|
@@ -2934,7 +2960,7 @@ async function branchCommitsAhead(repoPath, base, branch) {
|
|
|
2934
2960
|
}
|
|
2935
2961
|
}
|
|
2936
2962
|
async function reviewCommitCount(project, feature) {
|
|
2937
|
-
const base =
|
|
2963
|
+
const base = featureBase(feature);
|
|
2938
2964
|
const branch = featureBranch(feature.slug);
|
|
2939
2965
|
try {
|
|
2940
2966
|
const out = (await git(project.repoPath).raw(["rev-list", "--count", `${base}..${branch}`])).trim();
|
|
@@ -2945,7 +2971,7 @@ async function reviewCommitCount(project, feature) {
|
|
|
2945
2971
|
}
|
|
2946
2972
|
}
|
|
2947
2973
|
async function featureBranchDelta(project, feature) {
|
|
2948
|
-
const base =
|
|
2974
|
+
const base = featureBase(feature);
|
|
2949
2975
|
const branch = featureBranch(feature.slug);
|
|
2950
2976
|
try {
|
|
2951
2977
|
const out = await git(project.repoPath).raw(["diff", "--stat", `${base}...${branch}`]);
|
|
@@ -3120,12 +3146,13 @@ async function deleteBranchDetachingWorktrees(g, repoPath, branch) {
|
|
|
3120
3146
|
return false;
|
|
3121
3147
|
}
|
|
3122
3148
|
}
|
|
3123
|
-
async function landProjectBranch(project) {
|
|
3124
|
-
const ahead = await branchCommitsAhead(project.repoPath,
|
|
3149
|
+
async function landProjectBranch(project, base) {
|
|
3150
|
+
const ahead = await branchCommitsAhead(project.repoPath, base, PROJECT_BRANCH);
|
|
3125
3151
|
if (ahead.length === 0)
|
|
3126
3152
|
return null;
|
|
3127
|
-
const res = await mergeTempBranch(project.repoPath,
|
|
3153
|
+
const res = await mergeTempBranch(project.repoPath, base, PROJECT_BRANCH);
|
|
3128
3154
|
return {
|
|
3155
|
+
base,
|
|
3129
3156
|
commits: ahead.length,
|
|
3130
3157
|
landed: res.ok,
|
|
3131
3158
|
...res.conflict ? { conflict: true } : {},
|
|
@@ -3506,7 +3533,7 @@ async function stopDryRun(ctx, project, state) {
|
|
|
3506
3533
|
});
|
|
3507
3534
|
const { participating, failure } = dryRunVerdict(project, state);
|
|
3508
3535
|
if (!failure && participating.length > 0) {
|
|
3509
|
-
const sha = await headSha(project.repoPath, project.
|
|
3536
|
+
const sha = await headSha(project.repoPath, await detectMainBranch(project.repoPath)) ?? null;
|
|
3510
3537
|
markVerified(ctx, project.id, participating, sha);
|
|
3511
3538
|
emitProject(ctx, project.id, {
|
|
3512
3539
|
type: "prep.dryrun.verified",
|
|
@@ -3668,7 +3695,7 @@ async function detectDbDrift(ctx, project, feature, previousBranch, driveBranch)
|
|
|
3668
3695
|
return drift;
|
|
3669
3696
|
}
|
|
3670
3697
|
async function mergeFeature(project, feature) {
|
|
3671
|
-
const target =
|
|
3698
|
+
const target = featureBase(feature);
|
|
3672
3699
|
if (testDriveState) {
|
|
3673
3700
|
throw new GateError("Cannot merge while a test drive is active \u2014 stop it first");
|
|
3674
3701
|
}
|
|
@@ -4272,7 +4299,7 @@ function renderDriveFixPrompt(brief) {
|
|
|
4272
4299
|
`);
|
|
4273
4300
|
}
|
|
4274
4301
|
function renderProjectPrompt(brief, runtime = DEFAULT_RUNTIME) {
|
|
4275
|
-
const { project, branch, worktreePath } = brief;
|
|
4302
|
+
const { project, branch, worktreePath, base } = brief;
|
|
4276
4303
|
return [
|
|
4277
4304
|
`# runcastle \u2014 ${project.name} (project session)`,
|
|
4278
4305
|
"",
|
|
@@ -4298,8 +4325,8 @@ function renderProjectPrompt(brief, runtime = DEFAULT_RUNTIME) {
|
|
|
4298
4325
|
"## Where you are working \u2014 and where your writes go",
|
|
4299
4326
|
`- Working directory: \`${worktreePath}\` \u2014 a runcastle-owned worktree, NOT the human's`,
|
|
4300
4327
|
` checkout at \`${project.repoPath}\`.`,
|
|
4301
|
-
`- Branch: \`${branch}\`, cut from \`${
|
|
4302
|
-
`- **Your commits land on \`${
|
|
4328
|
+
`- Branch: \`${branch}\`, cut from \`${base}\` when this session launched.`,
|
|
4329
|
+
`- **Your commits land on \`${base}\` when this terminal closes**, arriving`,
|
|
4303
4330
|
" in the human's checkout the way a `git pull` does. Write real code and real docs \u2014",
|
|
4304
4331
|
" there is no sandbox here \u2014 and commit everything you write before you finish.",
|
|
4305
4332
|
" A session that edits without committing leaves a dirty tree, which is exactly what",
|
|
@@ -4586,10 +4613,23 @@ var init_claude = __esm(() => {
|
|
|
4586
4613
|
};
|
|
4587
4614
|
});
|
|
4588
4615
|
|
|
4589
|
-
// src/
|
|
4616
|
+
// src/services/codex-auth.ts
|
|
4590
4617
|
import { existsSync as existsSync8 } from "fs";
|
|
4591
4618
|
import { homedir as homedir2 } from "os";
|
|
4592
4619
|
import { join as join9 } from "path";
|
|
4620
|
+
function codexHomeDir(env = process.env) {
|
|
4621
|
+
return env.CODEX_HOME ?? join9(env.HOME ?? env.USERPROFILE ?? homedir2(), ".codex");
|
|
4622
|
+
}
|
|
4623
|
+
function codexAuthFile(env = process.env) {
|
|
4624
|
+
return join9(codexHomeDir(env), "auth.json");
|
|
4625
|
+
}
|
|
4626
|
+
function codexLoggedIn(env = process.env, fileExists = existsSync8) {
|
|
4627
|
+
return fileExists(codexAuthFile(env));
|
|
4628
|
+
}
|
|
4629
|
+
var init_codex_auth = () => {};
|
|
4630
|
+
|
|
4631
|
+
// src/doctor/doctor.ts
|
|
4632
|
+
import { existsSync as existsSync9 } from "fs";
|
|
4593
4633
|
async function versionProbe(exec, id, label, bin, fix) {
|
|
4594
4634
|
const out = await exec(bin, ["--version"]);
|
|
4595
4635
|
if (out.ok && out.code === 0) {
|
|
@@ -4621,9 +4661,6 @@ async function nodeProbe(exec) {
|
|
|
4621
4661
|
async function gitProbe(exec) {
|
|
4622
4662
|
return versionProbe(exec, "git", "Git", "git", "Install Git: winget install Git.Git / brew install git / apt install git");
|
|
4623
4663
|
}
|
|
4624
|
-
function codexAuthFile(env) {
|
|
4625
|
-
return join9(env.CODEX_HOME ?? join9(env.HOME ?? env.USERPROFILE ?? homedir2(), ".codex"), "auth.json");
|
|
4626
|
-
}
|
|
4627
4664
|
function statusUnsupported(out) {
|
|
4628
4665
|
return /unknown command|unrecognized subcommand|unexpected argument|invalid subcommand/i.test(`${out.stderr} ${out.stdout}`);
|
|
4629
4666
|
}
|
|
@@ -4655,23 +4692,38 @@ async function runtimeAuthProbe(spec, exec, env, fileExists, severity) {
|
|
|
4655
4692
|
fix: spec.installFix
|
|
4656
4693
|
};
|
|
4657
4694
|
}
|
|
4695
|
+
const authFile = spec.authFile?.(env);
|
|
4696
|
+
if (authFile) {
|
|
4697
|
+
const present = fileExists(authFile);
|
|
4698
|
+
const statusCommand = `${spec.bin} ${spec.authStatusArgs.join(" ")}`;
|
|
4699
|
+
let coda;
|
|
4700
|
+
if (statusUnsupported(out)) {
|
|
4701
|
+
coda = ` \u2014 this ${spec.bin} cannot report login state`;
|
|
4702
|
+
} else if (out.code === 0 === present) {
|
|
4703
|
+
coda = ` \u2014 \`${statusCommand}\` agrees`;
|
|
4704
|
+
} else if (present) {
|
|
4705
|
+
coda = `, but \`${statusCommand}\` reports logged out \u2014 burns borrow the file, so this host counts as signed in`;
|
|
4706
|
+
} else {
|
|
4707
|
+
coda = `, but \`${statusCommand}\` reports a login \u2014 a burn borrows the file, and it is not there`;
|
|
4708
|
+
}
|
|
4709
|
+
return present ? { ...base, status: "ok", detail: `credentials found at ${authFile}${coda}` } : { ...loggedOut, detail: `no credentials at ${authFile}${coda}` };
|
|
4710
|
+
}
|
|
4658
4711
|
if (out.code === 0)
|
|
4659
4712
|
return { ...base, status: "ok", detail: `${spec.bin} reports a login` };
|
|
4660
4713
|
if (!statusUnsupported(out))
|
|
4661
4714
|
return loggedOut;
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
detail: `this ${spec.bin} cannot report login state \u2014 it prompts at launch`
|
|
4668
|
-
};
|
|
4669
|
-
}
|
|
4670
|
-
return fileExists(authFile) ? { ...base, status: "ok", detail: `credentials found at ${authFile}` } : { ...loggedOut, detail: `no credentials at ${authFile}` };
|
|
4715
|
+
return {
|
|
4716
|
+
...base,
|
|
4717
|
+
status: "ok",
|
|
4718
|
+
detail: `this ${spec.bin} cannot report login state \u2014 it prompts at launch`
|
|
4719
|
+
};
|
|
4671
4720
|
}
|
|
4672
4721
|
function runtimeAfkKeyProbe(spec, env, severity = "error") {
|
|
4722
|
+
const id = spec.ids.afkKey;
|
|
4723
|
+
if (!id)
|
|
4724
|
+
return;
|
|
4673
4725
|
const base = {
|
|
4674
|
-
id
|
|
4726
|
+
id,
|
|
4675
4727
|
label: `${spec.label} AFK ${spec.afkNoun} (${spec.afkKey})`,
|
|
4676
4728
|
tier: 2,
|
|
4677
4729
|
severity,
|
|
@@ -4690,11 +4742,12 @@ function runtimeAfkKeyProbe(spec, env, severity = "error") {
|
|
|
4690
4742
|
};
|
|
4691
4743
|
}
|
|
4692
4744
|
async function runtimeProbes(spec, deps) {
|
|
4693
|
-
const fileExists = deps.fileExists ??
|
|
4745
|
+
const fileExists = deps.fileExists ?? existsSync9;
|
|
4746
|
+
const afkKeyProbe = runtimeAfkKeyProbe(spec, deps.env, deps.severity);
|
|
4694
4747
|
return [
|
|
4695
4748
|
await runtimeBinaryProbe(spec, deps.exec, deps.severity),
|
|
4696
4749
|
await runtimeAuthProbe(spec, deps.exec, deps.env, fileExists, deps.severity),
|
|
4697
|
-
|
|
4750
|
+
...afkKeyProbe ? [afkKeyProbe] : []
|
|
4698
4751
|
];
|
|
4699
4752
|
}
|
|
4700
4753
|
async function gitIdentityProbe(exec, cwd) {
|
|
@@ -4848,6 +4901,7 @@ function exitCodeFor(report, mode) {
|
|
|
4848
4901
|
var RUNTIME_SPECS;
|
|
4849
4902
|
var init_doctor = __esm(() => {
|
|
4850
4903
|
init_src();
|
|
4904
|
+
init_codex_auth();
|
|
4851
4905
|
RUNTIME_SPECS = {
|
|
4852
4906
|
"claude-code": {
|
|
4853
4907
|
runtime: "claude-code",
|
|
@@ -4867,13 +4921,13 @@ var init_doctor = __esm(() => {
|
|
|
4867
4921
|
runtime: "codex",
|
|
4868
4922
|
label: "Codex",
|
|
4869
4923
|
bin: "codex",
|
|
4870
|
-
ids: { binary: "codex", auth: "codex-auth"
|
|
4924
|
+
ids: { binary: "codex", auth: "codex-auth" },
|
|
4871
4925
|
authStatusArgs: ["login", "status"],
|
|
4872
4926
|
loginCommand: "codex login",
|
|
4873
4927
|
loginArgs: ["login"],
|
|
4874
4928
|
afkKey: "CODEX_API_KEY",
|
|
4875
4929
|
afkNoun: "API key",
|
|
4876
|
-
afkFix: "
|
|
4930
|
+
afkFix: "Set CODEX_API_KEY in ~/.runcastle/.env to bill an OpenAI API key instead of your ChatGPT login",
|
|
4877
4931
|
installFix: "Install Codex: npm install -g @openai/codex (or: brew install codex)",
|
|
4878
4932
|
binOverrideEnv: "RUNCASTLE_CODEX_BIN",
|
|
4879
4933
|
authFile: codexAuthFile
|
|
@@ -4884,31 +4938,33 @@ var init_doctor = __esm(() => {
|
|
|
4884
4938
|
// src/launcher/runtimes/codex.ts
|
|
4885
4939
|
import {
|
|
4886
4940
|
copyFileSync,
|
|
4887
|
-
existsSync as
|
|
4941
|
+
existsSync as existsSync10,
|
|
4888
4942
|
mkdirSync as mkdirSync4,
|
|
4889
4943
|
readFileSync as readFileSync4,
|
|
4890
4944
|
readdirSync as readdirSync2,
|
|
4891
4945
|
statSync,
|
|
4892
4946
|
writeFileSync as writeFileSync3
|
|
4893
4947
|
} from "fs";
|
|
4894
|
-
import { homedir as homedir3 } from "os";
|
|
4895
4948
|
import { basename, dirname as dirname3, join as join10, resolve as resolve7 } from "path";
|
|
4896
|
-
function
|
|
4949
|
+
function codexHomeDir2(sessionId) {
|
|
4897
4950
|
return join10(sessionDir(sessionId), "codex-home");
|
|
4898
4951
|
}
|
|
4899
4952
|
function realCodexAuthFile(env = process.env) {
|
|
4900
|
-
return
|
|
4953
|
+
return codexAuthFile(env);
|
|
4901
4954
|
}
|
|
4902
4955
|
function toml(value) {
|
|
4903
4956
|
return JSON.stringify(value);
|
|
4904
4957
|
}
|
|
4958
|
+
function approvalPolicyFor(permissionMode) {
|
|
4959
|
+
return permissionMode === undefined || permissionMode === "acceptEdits" ? "never" : "untrusted";
|
|
4960
|
+
}
|
|
4905
4961
|
function renderCodexConfig(input) {
|
|
4906
|
-
const { session, config: config2, model, worktreePath, inheritedMcpServers } = input;
|
|
4962
|
+
const { session, config: config2, model, worktreePath, inheritedMcpServers, permissionMode } = input;
|
|
4907
4963
|
return [
|
|
4908
4964
|
`# Generated by runcastle for session ${session.id} \u2014 rewritten at every launch.`,
|
|
4909
4965
|
`model = ${toml(model)}`,
|
|
4910
4966
|
'sandbox_mode = "workspace-write"',
|
|
4911
|
-
|
|
4967
|
+
`approval_policy = ${toml(approvalPolicyFor(permissionMode))}`,
|
|
4912
4968
|
"",
|
|
4913
4969
|
`[projects.${toml(worktreePath)}]`,
|
|
4914
4970
|
'trust_level = "trusted"',
|
|
@@ -4940,7 +4996,7 @@ function mcpServerTables(userConfigToml) {
|
|
|
4940
4996
|
}
|
|
4941
4997
|
function readInheritedMcpServers(env = process.env) {
|
|
4942
4998
|
const path = join10(dirname3(realCodexAuthFile(env)), "config.toml");
|
|
4943
|
-
if (!
|
|
4999
|
+
if (!existsSync10(path))
|
|
4944
5000
|
return "";
|
|
4945
5001
|
try {
|
|
4946
5002
|
return mcpServerTables(readFileSync4(path, "utf8"));
|
|
@@ -4964,15 +5020,15 @@ function renderCodexHooks(hookClient, kind, platform = process.platform) {
|
|
|
4964
5020
|
};
|
|
4965
5021
|
}
|
|
4966
5022
|
function writeCodexSkills(worktreePath, packDir) {
|
|
4967
|
-
if (!
|
|
5023
|
+
if (!existsSync10(worktreePath))
|
|
4968
5024
|
return [];
|
|
4969
5025
|
const source = join10(packDir, "skills");
|
|
4970
|
-
if (!
|
|
5026
|
+
if (!existsSync10(source))
|
|
4971
5027
|
return [];
|
|
4972
5028
|
const written = [];
|
|
4973
5029
|
for (const name of readdirSync2(source)) {
|
|
4974
5030
|
const skill = join10(source, name, "SKILL.md");
|
|
4975
|
-
if (!
|
|
5031
|
+
if (!existsSync10(skill))
|
|
4976
5032
|
continue;
|
|
4977
5033
|
const target = join10(worktreePath, CODEX_SKILLS_REL, name);
|
|
4978
5034
|
mkdirSync4(target, { recursive: true });
|
|
@@ -4991,7 +5047,7 @@ function excludeFromGit(worktreePath, entry) {
|
|
|
4991
5047
|
return;
|
|
4992
5048
|
mkdirSync4(info, { recursive: true });
|
|
4993
5049
|
const path = join10(info, "exclude");
|
|
4994
|
-
const existing =
|
|
5050
|
+
const existing = existsSync10(path) ? readFileSync4(path, "utf8") : "";
|
|
4995
5051
|
if (existing.split(/\r?\n/).some((line) => line.trim() === entry))
|
|
4996
5052
|
return;
|
|
4997
5053
|
const head = existing.length > 0 && !existing.endsWith(`
|
|
@@ -5003,7 +5059,7 @@ function excludeFromGit(worktreePath, entry) {
|
|
|
5003
5059
|
}
|
|
5004
5060
|
function gitInfoDir(worktreePath) {
|
|
5005
5061
|
const dotGit = join10(worktreePath, ".git");
|
|
5006
|
-
if (!
|
|
5062
|
+
if (!existsSync10(dotGit))
|
|
5007
5063
|
return null;
|
|
5008
5064
|
if (statSync(dotGit).isDirectory())
|
|
5009
5065
|
return join10(dotGit, "info");
|
|
@@ -5024,6 +5080,7 @@ var CODEX_BIN = "codex", CODEX, CODEX_SKILLS_REL, CODEX_SKILLS_IGNORE = ".agents
|
|
|
5024
5080
|
var init_codex = __esm(() => {
|
|
5025
5081
|
init_paths();
|
|
5026
5082
|
init_doctor();
|
|
5083
|
+
init_codex_auth();
|
|
5027
5084
|
init_resolve_executable();
|
|
5028
5085
|
init_artifacts();
|
|
5029
5086
|
init_edit_guard();
|
|
@@ -5046,7 +5103,7 @@ var init_codex = __esm(() => {
|
|
|
5046
5103
|
doctorHint: 'Run `runcastle doctor` and fix its "Codex CLI" probe, then restart runcastle from a ' + "terminal where `codex --version` works."
|
|
5047
5104
|
};
|
|
5048
5105
|
}
|
|
5049
|
-
if (!
|
|
5106
|
+
if (!existsSync10(realCodexAuthFile())) {
|
|
5050
5107
|
return {
|
|
5051
5108
|
ok: false,
|
|
5052
5109
|
reason: "Codex is installed but not logged in \u2014 runcastle copies your own credentials into " + `each session's home, and there are none at ${realCodexAuthFile()}`,
|
|
@@ -5056,7 +5113,7 @@ var init_codex = __esm(() => {
|
|
|
5056
5113
|
return { ok: true };
|
|
5057
5114
|
},
|
|
5058
5115
|
async writeArtifacts(input) {
|
|
5059
|
-
const home =
|
|
5116
|
+
const home = codexHomeDir2(input.session.id);
|
|
5060
5117
|
mkdirSync4(home, { recursive: true });
|
|
5061
5118
|
const configPath2 = join10(home, "config.toml");
|
|
5062
5119
|
const hooksPath = join10(home, "hooks.json");
|
|
@@ -5066,13 +5123,14 @@ var init_codex = __esm(() => {
|
|
|
5066
5123
|
config: input.config,
|
|
5067
5124
|
model: input.model,
|
|
5068
5125
|
worktreePath: input.worktreePath,
|
|
5126
|
+
...input.permissionMode ? { permissionMode: input.permissionMode } : {},
|
|
5069
5127
|
...input.config.sessionMcp === "inherit" ? { inheritedMcpServers: readInheritedMcpServers() } : {}
|
|
5070
5128
|
}), "utf8");
|
|
5071
5129
|
writeFileSync3(hooksPath, JSON.stringify(renderCodexHooks(hookClientPath(), input.session.kind), null, 2), "utf8");
|
|
5072
5130
|
writeFileSync3(agentsPath, renderSessionPrompt({ ...input, runtime: this.id }), "utf8");
|
|
5073
5131
|
const files = [configPath2, hooksPath, agentsPath];
|
|
5074
5132
|
const auth = realCodexAuthFile();
|
|
5075
|
-
if (
|
|
5133
|
+
if (existsSync10(auth)) {
|
|
5076
5134
|
const copied = join10(home, "auth.json");
|
|
5077
5135
|
copyFileSync(auth, copied);
|
|
5078
5136
|
files.push(copied);
|
|
@@ -5374,20 +5432,18 @@ function landProjectSession(ctx, session) {
|
|
|
5374
5432
|
const project = getProjectById(ctx, session.projectId);
|
|
5375
5433
|
if (!project)
|
|
5376
5434
|
return;
|
|
5377
|
-
const
|
|
5378
|
-
|
|
5379
|
-
reportProjectLanding(ctx, project, res, { sessionId: session.id });
|
|
5380
|
-
}).catch((e) => {
|
|
5435
|
+
const landFailed = (base, e) => {
|
|
5436
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
5381
5437
|
emitProject(ctx, project.id, {
|
|
5382
5438
|
type: "project.land_failed",
|
|
5383
|
-
message: `could not land the project session's work onto ${
|
|
5384
|
-
data: {
|
|
5385
|
-
sessionId: session.id,
|
|
5386
|
-
branch: PROJECT_BRANCH,
|
|
5387
|
-
error: e instanceof Error ? e.message : String(e)
|
|
5388
|
-
}
|
|
5439
|
+
message: `could not land the project session's work${base ? ` onto ${base}` : ""}: ${error} \u2014 ${LANDING_KEPT}`,
|
|
5440
|
+
data: { sessionId: session.id, branch: PROJECT_BRANCH, error }
|
|
5389
5441
|
});
|
|
5390
|
-
}
|
|
5442
|
+
};
|
|
5443
|
+
const landing = resolveSessionBranch(project).then((base) => landProjectBranch(project, base).then((res) => {
|
|
5444
|
+
if (res)
|
|
5445
|
+
reportProjectLanding(ctx, project, res, { sessionId: session.id });
|
|
5446
|
+
}, (e) => landFailed(base, e)), (e) => landFailed(null, e));
|
|
5391
5447
|
inFlightLandings.add(landing);
|
|
5392
5448
|
landing.finally(() => inFlightLandings.delete(landing));
|
|
5393
5449
|
}
|
|
@@ -5400,11 +5456,11 @@ function reportProjectLanding(ctx, project, res, data = {}) {
|
|
|
5400
5456
|
});
|
|
5401
5457
|
};
|
|
5402
5458
|
if (res.landed) {
|
|
5403
|
-
report("project.landed", `landed ${res.commits} commit(s) from the project session onto ${
|
|
5459
|
+
report("project.landed", `landed ${res.commits} commit(s) from the project session onto ${res.base}`, { commits: res.commits });
|
|
5404
5460
|
} else if (res.conflict) {
|
|
5405
|
-
report("project.land_conflict", `the project session's ${res.commits} commit(s) conflict with ${
|
|
5461
|
+
report("project.land_conflict", `the project session's ${res.commits} commit(s) conflict with ${res.base} \u2014 nothing was overwritten; ${LANDING_KEPT}`, { commits: res.commits, files: res.files ?? [] });
|
|
5406
5462
|
} else {
|
|
5407
|
-
report("project.land_failed", `could not land the project session's ${res.commits} commit(s) onto ${
|
|
5463
|
+
report("project.land_failed", `could not land the project session's ${res.commits} commit(s) onto ${res.base}: ${res.error ?? "unknown error"} \u2014 ${LANDING_KEPT}`, { commits: res.commits, error: res.error ?? null });
|
|
5408
5464
|
}
|
|
5409
5465
|
}
|
|
5410
5466
|
function markSessionEnded(ctx, id) {
|
|
@@ -5745,7 +5801,7 @@ var init_tickets = __esm(() => {
|
|
|
5745
5801
|
});
|
|
5746
5802
|
|
|
5747
5803
|
// src/services/gates.ts
|
|
5748
|
-
import { existsSync as
|
|
5804
|
+
import { existsSync as existsSync11 } from "fs";
|
|
5749
5805
|
import { and as and7, desc as desc4, eq as eq7, inArray as inArray3 } from "drizzle-orm";
|
|
5750
5806
|
function checkGate(ctx, check, feature) {
|
|
5751
5807
|
switch (check) {
|
|
@@ -5795,7 +5851,7 @@ function notYetTerminal(noun, statuses, order) {
|
|
|
5795
5851
|
}
|
|
5796
5852
|
function fileGate(ctx, feature, fileName, reason) {
|
|
5797
5853
|
const project = projectForFeature(ctx, feature);
|
|
5798
|
-
return
|
|
5854
|
+
return existsSync11(featureDocPath(project, feature, fileName)) ? { satisfied: true } : { satisfied: false, reason };
|
|
5799
5855
|
}
|
|
5800
5856
|
function docGate(ctx, feature, fileName, reason) {
|
|
5801
5857
|
const file = fileGate(ctx, feature, fileName, reason);
|
|
@@ -5859,14 +5915,14 @@ var init_gates = __esm(() => {
|
|
|
5859
5915
|
});
|
|
5860
5916
|
|
|
5861
5917
|
// src/services/knowledge.ts
|
|
5862
|
-
import { existsSync as
|
|
5918
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
5863
5919
|
import { join as join11, relative as relative2, resolve as resolve8, sep } from "path";
|
|
5864
5920
|
function scaffoldDocs(ctx, feature, opts = {}) {
|
|
5865
5921
|
const project = projectForFeature(ctx, feature);
|
|
5866
5922
|
const dir = featureDocsDir(project, feature);
|
|
5867
5923
|
mkdirSync5(dir, { recursive: true });
|
|
5868
5924
|
const briefPath = featureDocPath(project, feature, "brief.md");
|
|
5869
|
-
if (!
|
|
5925
|
+
if (!existsSync12(briefPath)) {
|
|
5870
5926
|
const override = opts.brief?.trim();
|
|
5871
5927
|
const created = new Date().toISOString();
|
|
5872
5928
|
const brief = override ? `${override}
|
|
@@ -5893,7 +5949,7 @@ function scaffoldDocs(ctx, feature, opts = {}) {
|
|
|
5893
5949
|
function scaffoldMapDoc(project, feature, seed) {
|
|
5894
5950
|
mkdirSync5(featureDocsDir(project, feature), { recursive: true });
|
|
5895
5951
|
const mapPath = featureDocPath(project, feature, "map.md");
|
|
5896
|
-
if (
|
|
5952
|
+
if (existsSync12(mapPath))
|
|
5897
5953
|
return;
|
|
5898
5954
|
writeFileSync4(mapPath, mapDocBody(feature, seed), "utf8");
|
|
5899
5955
|
}
|
|
@@ -5914,7 +5970,7 @@ function mapDocBody(feature, seed) {
|
|
|
5914
5970
|
}
|
|
5915
5971
|
function readCharter(repoRoot) {
|
|
5916
5972
|
const path = join11(repoRoot, CHARTER_FILE);
|
|
5917
|
-
if (!
|
|
5973
|
+
if (!existsSync12(path))
|
|
5918
5974
|
return;
|
|
5919
5975
|
try {
|
|
5920
5976
|
return readFileSync5(path, "utf8");
|
|
@@ -5924,7 +5980,7 @@ function readCharter(repoRoot) {
|
|
|
5924
5980
|
}
|
|
5925
5981
|
function listLiveAdrs(repoRoot) {
|
|
5926
5982
|
const dir = join11(repoRoot, ADR_DIR_REL);
|
|
5927
|
-
if (!
|
|
5983
|
+
if (!existsSync12(dir))
|
|
5928
5984
|
return [];
|
|
5929
5985
|
const docs2 = [];
|
|
5930
5986
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
@@ -5945,7 +6001,7 @@ function listLiveAdrs(repoRoot) {
|
|
|
5945
6001
|
function listDocs(ctx, feature) {
|
|
5946
6002
|
const project = projectForFeature(ctx, feature);
|
|
5947
6003
|
const dir = featureDocsDir(project, feature);
|
|
5948
|
-
if (!
|
|
6004
|
+
if (!existsSync12(dir))
|
|
5949
6005
|
return [];
|
|
5950
6006
|
return readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => ({ relPath: e.name, title: readTitle(join11(dir, e.name), e.name) })).sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
5951
6007
|
}
|
|
@@ -5958,7 +6014,7 @@ function readDoc(ctx, feature, relPath) {
|
|
|
5958
6014
|
if (rel === "" || rel.startsWith("..") || rel.split(sep).includes("..")) {
|
|
5959
6015
|
throw new InvalidInputError(`doc path escapes the feature docs dir: ${relPath}`);
|
|
5960
6016
|
}
|
|
5961
|
-
if (!
|
|
6017
|
+
if (!existsSync12(target))
|
|
5962
6018
|
throw new NotFoundError(`doc not found: ${relPath}`);
|
|
5963
6019
|
return { content: readFileSync5(target, "utf8") };
|
|
5964
6020
|
}
|
|
@@ -6016,12 +6072,12 @@ var init_end_session = __esm(() => {
|
|
|
6016
6072
|
});
|
|
6017
6073
|
|
|
6018
6074
|
// src/services/settings.ts
|
|
6019
|
-
import { existsSync as
|
|
6075
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
6020
6076
|
import { dirname as dirname4 } from "path";
|
|
6021
6077
|
import * as z3 from "zod";
|
|
6022
6078
|
import { eq as eq8 } from "drizzle-orm";
|
|
6023
6079
|
function readRawConfig(configFile) {
|
|
6024
|
-
if (!
|
|
6080
|
+
if (!existsSync13(configFile))
|
|
6025
6081
|
return {};
|
|
6026
6082
|
try {
|
|
6027
6083
|
const parsed = JSON.parse(readFileSync6(configFile, "utf8"));
|
|
@@ -6051,6 +6107,7 @@ function projectOverrides(ctx, projectId) {
|
|
|
6051
6107
|
const row = ctx.db.select({
|
|
6052
6108
|
model: projects.model,
|
|
6053
6109
|
sandbox: projects.sandbox,
|
|
6110
|
+
sessionBranch: projects.sessionBranch,
|
|
6054
6111
|
devCommand: projects.devCommand,
|
|
6055
6112
|
setupCommand: projects.setupCommand,
|
|
6056
6113
|
verifyCommands: projects.verifyCommands,
|
|
@@ -6062,6 +6119,7 @@ function projectOverrides(ctx, projectId) {
|
|
|
6062
6119
|
return {
|
|
6063
6120
|
model: row?.model ?? null,
|
|
6064
6121
|
sandbox: row?.sandbox ?? null,
|
|
6122
|
+
sessionBranch: row?.sessionBranch ?? null,
|
|
6065
6123
|
devCommand: row?.devCommand ?? null,
|
|
6066
6124
|
setupCommand: row?.setupCommand ?? null,
|
|
6067
6125
|
verifyCommands: row?.verifyCommands ?? null,
|
|
@@ -6340,16 +6398,15 @@ var init_settings = __esm(() => {
|
|
|
6340
6398
|
parseEnv: idEnv
|
|
6341
6399
|
},
|
|
6342
6400
|
{
|
|
6343
|
-
key: "
|
|
6344
|
-
|
|
6345
|
-
envVar: "RUNCASTLE_MAIN_BRANCH",
|
|
6401
|
+
key: "devCommand",
|
|
6402
|
+
projectColumn: "devCommand",
|
|
6346
6403
|
restartRequired: false,
|
|
6347
6404
|
valueSchema: z3.string().min(1),
|
|
6348
6405
|
parseEnv: idEnv
|
|
6349
6406
|
},
|
|
6350
6407
|
{
|
|
6351
|
-
key: "
|
|
6352
|
-
projectColumn: "
|
|
6408
|
+
key: "sessionBranch",
|
|
6409
|
+
projectColumn: "sessionBranch",
|
|
6353
6410
|
restartRequired: false,
|
|
6354
6411
|
valueSchema: z3.string().min(1),
|
|
6355
6412
|
parseEnv: idEnv
|
|
@@ -6381,7 +6438,7 @@ var init_settings = __esm(() => {
|
|
|
6381
6438
|
});
|
|
6382
6439
|
|
|
6383
6440
|
// src/services/setup.ts
|
|
6384
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
6441
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
6385
6442
|
import { dirname as dirname5, join as join12 } from "path";
|
|
6386
6443
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6387
6444
|
async function writeGitIdentity(exec, identity) {
|
|
@@ -6489,7 +6546,7 @@ function resolveSandcastleBin() {
|
|
|
6489
6546
|
let dir = dirname5(fileURLToPath4(mainUrl));
|
|
6490
6547
|
for (let hops = 0;hops < 6; hops++) {
|
|
6491
6548
|
const manifestPath = join12(dir, "package.json");
|
|
6492
|
-
if (
|
|
6549
|
+
if (existsSync14(manifestPath)) {
|
|
6493
6550
|
const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
|
|
6494
6551
|
if (manifest.name === "@ai-hero/sandcastle" && manifest.bin?.sandcastle) {
|
|
6495
6552
|
return join12(dir, manifest.bin.sandcastle);
|
|
@@ -6525,7 +6582,7 @@ function sandcastleTemplateDir() {
|
|
|
6525
6582
|
}
|
|
6526
6583
|
function scaffoldSandcastleConfig(templateDir, targetDir) {
|
|
6527
6584
|
const dir = join12(targetDir, ".sandcastle");
|
|
6528
|
-
if (
|
|
6585
|
+
if (existsSync14(dir))
|
|
6529
6586
|
return { scaffolded: false, dir };
|
|
6530
6587
|
mkdirSync7(dir, { recursive: true });
|
|
6531
6588
|
for (const name of readdirSync4(templateDir)) {
|
|
@@ -6572,7 +6629,7 @@ function createCredentialVerifier(exec, runtime = DEFAULT_RUNTIME) {
|
|
|
6572
6629
|
function fileAfkTokenIo(exec, runtime = DEFAULT_RUNTIME) {
|
|
6573
6630
|
const path = envPath();
|
|
6574
6631
|
return {
|
|
6575
|
-
read: () =>
|
|
6632
|
+
read: () => existsSync14(path) ? readFileSync7(path, "utf8") : "",
|
|
6576
6633
|
write: (content) => {
|
|
6577
6634
|
mkdirSync7(dirname5(path), { recursive: true });
|
|
6578
6635
|
writeFileSync6(path, content, "utf8");
|
|
@@ -6596,7 +6653,7 @@ var init_setup = __esm(() => {
|
|
|
6596
6653
|
};
|
|
6597
6654
|
RUNTIME_AUTH_SETUP_HINT = {
|
|
6598
6655
|
"claude-code": RUNTIME_SPECS["claude-code"].afkFix,
|
|
6599
|
-
codex: RUNTIME_SPECS.codex.
|
|
6656
|
+
codex: `Run \`${RUNTIME_SPECS.codex.loginCommand}\` on this host, then burn again`
|
|
6600
6657
|
};
|
|
6601
6658
|
LOGIN_TERMINAL_KIND = {
|
|
6602
6659
|
"claude-code": "claude-login",
|
|
@@ -6753,7 +6810,7 @@ var init_burn_guard = __esm(() => {
|
|
|
6753
6810
|
});
|
|
6754
6811
|
|
|
6755
6812
|
// src/workflows/review-ticket.ts
|
|
6756
|
-
import { existsSync as
|
|
6813
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
6757
6814
|
import { delimiter, join as join13 } from "path";
|
|
6758
6815
|
import { run } from "@ai-hero/sandcastle";
|
|
6759
6816
|
import { noSandbox } from "@ai-hero/sandcastle/sandboxes/no-sandbox";
|
|
@@ -6769,7 +6826,7 @@ function findOnPath(bin, env = process.env, platform = process.platform) {
|
|
|
6769
6826
|
for (const dir of dirs) {
|
|
6770
6827
|
for (const suffix of suffixes) {
|
|
6771
6828
|
const candidate = join13(dir, `${bin}${suffix}`);
|
|
6772
|
-
if (
|
|
6829
|
+
if (existsSync15(candidate))
|
|
6773
6830
|
return candidate;
|
|
6774
6831
|
}
|
|
6775
6832
|
}
|
|
@@ -6797,6 +6854,9 @@ async function releaseDriveQuietly() {
|
|
|
6797
6854
|
}
|
|
6798
6855
|
async function executeReviewTicket(ctx, ticket, deps) {
|
|
6799
6856
|
const { project, feature } = ctx;
|
|
6857
|
+
if (!feature.baseBranch) {
|
|
6858
|
+
return couldNotReview(ticket, `feature ${feature.slug} has no recorded base branch, so there is nothing to diff \`${feature.branch}\` against.`);
|
|
6859
|
+
}
|
|
6800
6860
|
if (!findOnPath(AGENT_BROWSER_BIN)) {
|
|
6801
6861
|
return couldNotReview(ticket, `\`${AGENT_BROWSER_BIN}\` is not on this machine's PATH, so there is no way to drive the app. Install it and re-burn this ticket.`);
|
|
6802
6862
|
}
|
|
@@ -6807,7 +6867,7 @@ async function executeReviewTicket(ctx, ticket, deps) {
|
|
|
6807
6867
|
DOCS_DIGEST: deps.docsDigest,
|
|
6808
6868
|
LAP_DIGESTS: buildLapDigestsBlock(deps.lapDigests),
|
|
6809
6869
|
FEATURE_BRANCH: feature.branch,
|
|
6810
|
-
BASE_BRANCH:
|
|
6870
|
+
BASE_BRANCH: feature.baseBranch,
|
|
6811
6871
|
DIGEST_PATH: artifacts.digestPath,
|
|
6812
6872
|
BLOCKED_PATH: artifacts.blockedPath,
|
|
6813
6873
|
WALKTHROUGH_PATH: artifacts.walkthroughPath
|
|
@@ -6889,13 +6949,18 @@ var init_review_ticket = __esm(() => {
|
|
|
6889
6949
|
});
|
|
6890
6950
|
|
|
6891
6951
|
// src/workflows/ticket-burner.ts
|
|
6892
|
-
import { existsSync as
|
|
6952
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync9, readdirSync as readdirSync5, rmSync as rmSync3 } from "fs";
|
|
6893
6953
|
import { dirname as dirname6, join as join14, posix as posixPath, win32 as winPath } from "path";
|
|
6894
6954
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
6895
6955
|
import { claudeCode, codex, run as run2 } from "@ai-hero/sandcastle";
|
|
6896
6956
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
6897
6957
|
import { podman } from "@ai-hero/sandcastle/sandboxes/podman";
|
|
6898
6958
|
import { noSandbox as noSandbox2 } from "@ai-hero/sandcastle/sandboxes/no-sandbox";
|
|
6959
|
+
function burnAuthReady(runtime, token, loggedIn = codexLoggedIn) {
|
|
6960
|
+
if (token !== undefined)
|
|
6961
|
+
return true;
|
|
6962
|
+
return runtime === "codex" && loggedIn();
|
|
6963
|
+
}
|
|
6899
6964
|
function isReviewTicket(ticket) {
|
|
6900
6965
|
return ticket.kind === "review";
|
|
6901
6966
|
}
|
|
@@ -7036,10 +7101,10 @@ _(The "${DROPPED_MAP_SECTIONS.join('" and "')}" sections are omitted here: they
|
|
|
7036
7101
|
}
|
|
7037
7102
|
function buildProjectStandards(repoPath) {
|
|
7038
7103
|
const present = [];
|
|
7039
|
-
if (
|
|
7104
|
+
if (existsSync16(join14(repoPath, AGENT_CONVENTIONS_FILE))) {
|
|
7040
7105
|
present.push("`CLAUDE.md` \u2014 this repo's agent-facing conventions, and the highest authority on them");
|
|
7041
7106
|
}
|
|
7042
|
-
if (
|
|
7107
|
+
if (existsSync16(join14(repoPath, CHARTER_FILE))) {
|
|
7043
7108
|
present.push(`\`${CHARTER_FILE}\` \u2014 the project charter: what this codebase is for`);
|
|
7044
7109
|
}
|
|
7045
7110
|
const adrs = listLiveAdrs(repoPath).map((a) => a.relPath);
|
|
@@ -7070,7 +7135,7 @@ function buildDriveNotes(settings) {
|
|
|
7070
7135
|
add("stop", settings.driveStopCommand);
|
|
7071
7136
|
add("dev server", settings.devCommand);
|
|
7072
7137
|
add("db reset", settings.dbResetCommand);
|
|
7073
|
-
const hasDir =
|
|
7138
|
+
const hasDir = existsSync16(join14(settings.repoPath, ".runcastle"));
|
|
7074
7139
|
if (configured.length === 0 && !hasDir) {
|
|
7075
7140
|
return [
|
|
7076
7141
|
"This project has no test-drive machinery configured, so there is nothing here for you to keep in step \u2014 do not invent any.",
|
|
@@ -7206,6 +7271,18 @@ function cacheMountFor(pm, hostPath) {
|
|
|
7206
7271
|
const sandboxPath = PM_CACHE_SANDBOX_PATHS[pm];
|
|
7207
7272
|
return sandboxPath ? { hostPath, sandboxPath } : undefined;
|
|
7208
7273
|
}
|
|
7274
|
+
function codexAuthMountFor(runtime, sandbox, env = process.env, loggedIn = codexLoggedIn) {
|
|
7275
|
+
if (runtime !== "codex" || sandbox === "noSandbox" || !loggedIn(env))
|
|
7276
|
+
return;
|
|
7277
|
+
return { hostPath: codexHomeDir(env), sandboxPath: CODEX_HOST_MOUNT_PATH, readonly: true };
|
|
7278
|
+
}
|
|
7279
|
+
function buildCodexAuthCopyCommand() {
|
|
7280
|
+
return `mkdir -p "$HOME/.codex" && cp "${CODEX_HOST_MOUNT_PATH}/auth.json" "$HOME/.codex/auth.json"`;
|
|
7281
|
+
}
|
|
7282
|
+
function chainSetupCommands(...steps) {
|
|
7283
|
+
const present = steps.filter((step) => !!step);
|
|
7284
|
+
return present.length > 0 ? present.join(" && ") : undefined;
|
|
7285
|
+
}
|
|
7209
7286
|
function resolveBurnWorkspaceMode(config2, platform = process.platform) {
|
|
7210
7287
|
if (config2.sandbox === "noSandbox")
|
|
7211
7288
|
return "mounted";
|
|
@@ -7268,7 +7345,7 @@ function attachedNoteIds(context) {
|
|
|
7268
7345
|
return ids2;
|
|
7269
7346
|
}
|
|
7270
7347
|
function attachmentSources(context) {
|
|
7271
|
-
return attachedNoteIds(context).map(annotationPath).filter((p) =>
|
|
7348
|
+
return attachedNoteIds(context).map(annotationPath).filter((p) => existsSync16(p));
|
|
7272
7349
|
}
|
|
7273
7350
|
function buildAttachmentCopyCommands(sources, platform = process.platform) {
|
|
7274
7351
|
if (sources.length === 0)
|
|
@@ -7311,7 +7388,7 @@ function buildVerifyNotes(config2) {
|
|
|
7311
7388
|
}
|
|
7312
7389
|
function readRepoToolchain(repoPath) {
|
|
7313
7390
|
const pkgPath = join14(repoPath, "package.json");
|
|
7314
|
-
const hasPackageJson =
|
|
7391
|
+
const hasPackageJson = existsSync16(pkgPath);
|
|
7315
7392
|
let packageManagerField;
|
|
7316
7393
|
if (hasPackageJson) {
|
|
7317
7394
|
try {
|
|
@@ -7324,10 +7401,10 @@ function readRepoToolchain(repoPath) {
|
|
|
7324
7401
|
hasPackageJson,
|
|
7325
7402
|
...packageManagerField !== undefined ? { packageManagerField } : {},
|
|
7326
7403
|
lockfiles: {
|
|
7327
|
-
bun:
|
|
7328
|
-
pnpm:
|
|
7329
|
-
yarn:
|
|
7330
|
-
npm:
|
|
7404
|
+
bun: existsSync16(join14(repoPath, "bun.lock")) || existsSync16(join14(repoPath, "bun.lockb")),
|
|
7405
|
+
pnpm: existsSync16(join14(repoPath, "pnpm-lock.yaml")),
|
|
7406
|
+
yarn: existsSync16(join14(repoPath, "yarn.lock")),
|
|
7407
|
+
npm: existsSync16(join14(repoPath, "package-lock.json"))
|
|
7331
7408
|
}
|
|
7332
7409
|
};
|
|
7333
7410
|
}
|
|
@@ -7703,6 +7780,19 @@ ${e.digest.trim()}`).join(`
|
|
|
7703
7780
|
|
|
7704
7781
|
`);
|
|
7705
7782
|
}
|
|
7783
|
+
function gateTicketAuth(deps) {
|
|
7784
|
+
const authMissing = deps.ticketAuthMissing;
|
|
7785
|
+
if (!authMissing || deps.config.sandbox === "noSandbox")
|
|
7786
|
+
return deps.executeTicketRun;
|
|
7787
|
+
return async (ctx, ticket, run3) => {
|
|
7788
|
+
const runtime = authMissing(ticket);
|
|
7789
|
+
if (!runtime)
|
|
7790
|
+
return deps.executeTicketRun(ctx, ticket, run3);
|
|
7791
|
+
const hint = `${runtime} is not authenticated \u2014 ${RUNTIME_AUTH_SETUP_HINT[runtime]}`;
|
|
7792
|
+
ctx.emitEvent({ type: AUTH_MISSING_EVENT, message: hint, ticketId: ticket.id });
|
|
7793
|
+
return { status: "failed", error: hint };
|
|
7794
|
+
};
|
|
7795
|
+
}
|
|
7706
7796
|
async function burnRun(ctx, deps) {
|
|
7707
7797
|
const tickets2 = ctx.tickets;
|
|
7708
7798
|
const burnable = tickets2.filter((t) => t.status !== "cancelled");
|
|
@@ -7722,7 +7812,7 @@ async function burnRun(ctx, deps) {
|
|
|
7722
7812
|
});
|
|
7723
7813
|
return { status: "failed", summary: `dependency cycle: ${path}` };
|
|
7724
7814
|
}
|
|
7725
|
-
const { done, digests } = await burnTickets(ctx, tickets2, deps
|
|
7815
|
+
const { done, digests } = await burnTickets(ctx, tickets2, gateTicketAuth(deps), deps.concurrency);
|
|
7726
7816
|
const summary = cancelled > 0 ? `${done}/${total} tickets done (${cancelled} cancelled)` : `${done}/${total} tickets done`;
|
|
7727
7817
|
ctx.emitEvent({ type: "burn.summary", message: summary, data: { done, total, cancelled } });
|
|
7728
7818
|
const digest = composeRunDigest(digests);
|
|
@@ -7759,11 +7849,11 @@ function readDocsDigest(projectId, slug) {
|
|
|
7759
7849
|
...missing ? { missing } : {}
|
|
7760
7850
|
});
|
|
7761
7851
|
const worktree = worktreeDir(projectId, slug);
|
|
7762
|
-
if (!
|
|
7852
|
+
if (!existsSync16(worktree)) {
|
|
7763
7853
|
return done("_No talk worktree on disk \u2014 docs digest skipped._", "no-worktree");
|
|
7764
7854
|
}
|
|
7765
7855
|
const docsDir = join14(worktree, ...docsRel.split("/"));
|
|
7766
|
-
if (!
|
|
7856
|
+
if (!existsSync16(docsDir)) {
|
|
7767
7857
|
return done("_No docs/features dir in the talk worktree \u2014 docs digest skipped._", "no-docs-dir");
|
|
7768
7858
|
}
|
|
7769
7859
|
const names = listFeatureDocs(docsDir).sort((a, b) => agentDigestDocOrder(a) - agentDigestDocOrder(b) || a.localeCompare(b));
|
|
@@ -7826,7 +7916,7 @@ function readAgentFile(dirs, file) {
|
|
|
7826
7916
|
if (!dir)
|
|
7827
7917
|
continue;
|
|
7828
7918
|
const p = join14(dir, file);
|
|
7829
|
-
if (
|
|
7919
|
+
if (existsSync16(p)) {
|
|
7830
7920
|
try {
|
|
7831
7921
|
return readFileSync9(p, "utf8");
|
|
7832
7922
|
} catch {}
|
|
@@ -7961,8 +8051,11 @@ async function realExecuteTicketRun(ctx, ticket, config2, token, model, land, en
|
|
|
7961
8051
|
mounts.push(mount);
|
|
7962
8052
|
}
|
|
7963
8053
|
}
|
|
8054
|
+
const codexAuthMount = codexAuthMountFor(model.runtime, config2.sandbox);
|
|
8055
|
+
if (codexAuthMount)
|
|
8056
|
+
mounts.push(codexAuthMount);
|
|
7964
8057
|
const guardInstall = guardInstalled ? buildGuardInstallCommand(model.runtime) : undefined;
|
|
7965
|
-
const
|
|
8058
|
+
const withPrelude = (setup) => chainSetupCommands(codexAuthMount ? buildCodexAuthCopyCommand() : undefined, guardInstall, setup);
|
|
7966
8059
|
const agentOptions = guardInstalled ? { bypassHookTrust: true } : {};
|
|
7967
8060
|
mkdirSync9(logsDir(), { recursive: true });
|
|
7968
8061
|
const logFilePath = join14(logsDir(), `burn-${feature.id}-${ticket.seq}.log`);
|
|
@@ -8022,7 +8115,7 @@ async function realExecuteTicketRun(ctx, ticket, config2, token, model, land, en
|
|
|
8022
8115
|
OTHER_SIDE: buildOtherSideBlock(otherSide),
|
|
8023
8116
|
MERGE_COMMAND: resolveMergeCommand(workspaceMode, feature.branch)
|
|
8024
8117
|
});
|
|
8025
|
-
const hookCommand =
|
|
8118
|
+
const hookCommand = withPrelude(workspaceMode === "isolated" ? buildIsolatedSetupCommand(resolveBranch, setupCommand, pm) : setupCommand);
|
|
8026
8119
|
appendTranscript(ticket.id, {
|
|
8027
8120
|
kind: "text",
|
|
8028
8121
|
text: `
|
|
@@ -8157,7 +8250,7 @@ ${blocked.trim()}`
|
|
|
8157
8250
|
let tempBranch = "";
|
|
8158
8251
|
for (let attempt = 1;; attempt++) {
|
|
8159
8252
|
tempBranch = ticketBranchName(feature.slug, ticket.seq, newId("b").slice(2, 10));
|
|
8160
|
-
const hookCommand =
|
|
8253
|
+
const hookCommand = withPrelude(workspaceMode === "isolated" ? buildIsolatedSetupCommand(tempBranch, setupCommand, pm) : setupCommand);
|
|
8161
8254
|
if (hookCommand && attempt === 1) {
|
|
8162
8255
|
ctx.emitEvent({
|
|
8163
8256
|
type: "burn.setup",
|
|
@@ -8336,14 +8429,24 @@ function resolveBurnDeps(ctx) {
|
|
|
8336
8429
|
dbResetCommand: ctx.project.dbResetCommand
|
|
8337
8430
|
})
|
|
8338
8431
|
};
|
|
8432
|
+
const ticketCredentials = (ticket) => {
|
|
8433
|
+
const ticketModel = resolveTicketModel(config2, ctx.project, ctx.modelOverride, ticket);
|
|
8434
|
+
return {
|
|
8435
|
+
model: ticketModel,
|
|
8436
|
+
token: ticketModel.runtime === model.runtime ? token : readTokenFromEnvFile(envPath(), ticketModel.runtime)
|
|
8437
|
+
};
|
|
8438
|
+
};
|
|
8339
8439
|
return {
|
|
8340
8440
|
config: config2,
|
|
8341
8441
|
runtime: model.runtime,
|
|
8342
|
-
hasAuthToken: token
|
|
8442
|
+
hasAuthToken: burnAuthReady(model.runtime, token),
|
|
8443
|
+
ticketAuthMissing: (ticket) => {
|
|
8444
|
+
const { model: ticketModel, token: ticketToken } = ticketCredentials(ticket);
|
|
8445
|
+
return burnAuthReady(ticketModel.runtime, ticketToken) ? undefined : ticketModel.runtime;
|
|
8446
|
+
},
|
|
8343
8447
|
concurrency: config2.burnConcurrency,
|
|
8344
8448
|
executeTicketRun: (c, ticket, run3) => {
|
|
8345
|
-
const
|
|
8346
|
-
const ticketToken = ticketModel.runtime === model.runtime ? token : readTokenFromEnvFile(envPath(), ticketModel.runtime);
|
|
8449
|
+
const { model: ticketModel, token: ticketToken } = ticketCredentials(ticket);
|
|
8347
8450
|
return isReviewTicket(ticket) ? executeReviewTicket(c, ticket, {
|
|
8348
8451
|
config: config2,
|
|
8349
8452
|
token: ticketToken,
|
|
@@ -8357,7 +8460,7 @@ function resolveBurnDeps(ctx) {
|
|
|
8357
8460
|
function readTokenFromEnvFile(path, runtime) {
|
|
8358
8461
|
const key = RUNTIME_AUTH_KEY[runtime];
|
|
8359
8462
|
let fromFile;
|
|
8360
|
-
if (
|
|
8463
|
+
if (existsSync16(path)) {
|
|
8361
8464
|
try {
|
|
8362
8465
|
fromFile = parseEnvFile2(readFileSync9(path, "utf8"))[key];
|
|
8363
8466
|
} catch {
|
|
@@ -8367,12 +8470,13 @@ function readTokenFromEnvFile(path, runtime) {
|
|
|
8367
8470
|
const token = fromFile && fromFile.length > 0 ? fromFile : process.env[key];
|
|
8368
8471
|
return token && token.length > 0 ? token : undefined;
|
|
8369
8472
|
}
|
|
8370
|
-
var AUTH_MISSING_EVENT = "auth.missing", DROPPED_MAP_SECTIONS, AGENT_CONVENTIONS_FILE = "CLAUDE.md", PACKAGE_MANAGERS, PM_CACHE_SANDBOX_PATHS, SANDBOX_WORKSPACE_PATH = "/home/agent/workspace", ISOLATED_REPO_PATH = "/home/agent/repo", RUN, BASH_PATTERNS, TOOL_NAME_CATEGORY, MAX_ATTRIBUTABLE_GAP_MS, BURN_WORKTREE_PATH, WORKTREE_REMOVAL_FAILURES, FATAL_ERROR_PATTERNS, RUNTIME_FATAL_ERROR_PATTERNS, RETRYABLE_ERROR_PATTERNS, RUNTIME_RETRYABLE_ERROR_PATTERNS, activeTicketAborts, SETUP_HOOK_TIMEOUT_MS, ticketBurner;
|
|
8473
|
+
var AUTH_MISSING_EVENT = "auth.missing", DROPPED_MAP_SECTIONS, AGENT_CONVENTIONS_FILE = "CLAUDE.md", PACKAGE_MANAGERS, PM_CACHE_SANDBOX_PATHS, CODEX_HOST_MOUNT_PATH = "/mnt/host-codex", SANDBOX_WORKSPACE_PATH = "/home/agent/workspace", ISOLATED_REPO_PATH = "/home/agent/repo", RUN, BASH_PATTERNS, TOOL_NAME_CATEGORY, MAX_ATTRIBUTABLE_GAP_MS, BURN_WORKTREE_PATH, WORKTREE_REMOVAL_FAILURES, FATAL_ERROR_PATTERNS, RUNTIME_FATAL_ERROR_PATTERNS, RETRYABLE_ERROR_PATTERNS, RUNTIME_RETRYABLE_ERROR_PATTERNS, activeTicketAborts, SETUP_HOOK_TIMEOUT_MS, ticketBurner;
|
|
8371
8474
|
var init_ticket_burner = __esm(() => {
|
|
8372
8475
|
init_src();
|
|
8373
8476
|
init_config_load();
|
|
8374
8477
|
init_paths();
|
|
8375
8478
|
init_skills_root();
|
|
8479
|
+
init_codex_auth();
|
|
8376
8480
|
init_knowledge();
|
|
8377
8481
|
init_setup();
|
|
8378
8482
|
init_agent_stream();
|
|
@@ -8431,7 +8535,10 @@ var init_ticket_burner = __esm(() => {
|
|
|
8431
8535
|
/\b401\b|\b403\b/,
|
|
8432
8536
|
/insufficient_quota|exceeded your current quota/i,
|
|
8433
8537
|
/model_not_found|does not exist or you do not have access/i,
|
|
8434
|
-
/CODEX_API_KEY
|
|
8538
|
+
/CODEX_API_KEY/,
|
|
8539
|
+
/not logged in/i,
|
|
8540
|
+
/\bauth (failed|required)\b/i,
|
|
8541
|
+
/refresh token/i
|
|
8435
8542
|
]
|
|
8436
8543
|
};
|
|
8437
8544
|
RETRYABLE_ERROR_PATTERNS = [
|
|
@@ -8544,14 +8651,16 @@ async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
|
8544
8651
|
mkdirSync10(logsDir(), { recursive: true });
|
|
8545
8652
|
const logFilePath = join15(logsDir(), `research-${feature.id}-${waypoint.seq}.log`);
|
|
8546
8653
|
const throttle = createResearchStreamThrottle((e) => ctx.emitEvent(e));
|
|
8654
|
+
const codexAuthMount = codexAuthMountFor(model.runtime, config2.sandbox);
|
|
8547
8655
|
const runOptions = {
|
|
8548
8656
|
agent: buildBurnAgent(config2, token, model),
|
|
8549
|
-
sandbox: selectSandbox(config2),
|
|
8657
|
+
sandbox: selectSandbox(config2, codexAuthMount ? [codexAuthMount] : []),
|
|
8550
8658
|
cwd: project.repoPath,
|
|
8551
8659
|
prompt,
|
|
8552
8660
|
branchStrategy: { type: "branch", branch: tempBranch, baseBranch: feature.branch },
|
|
8553
8661
|
signal: ctx.signal,
|
|
8554
8662
|
name: `research-${waypoint.seq}`,
|
|
8663
|
+
...codexAuthMount ? { hooks: { sandbox: { onSandboxReady: [{ command: buildCodexAuthCopyCommand() }] } } } : {},
|
|
8555
8664
|
logging: { type: "file", path: logFilePath, onAgentStreamEvent: throttle.onEvent }
|
|
8556
8665
|
};
|
|
8557
8666
|
let result;
|
|
@@ -8599,7 +8708,7 @@ function resolveResearchDeps(ctx) {
|
|
|
8599
8708
|
return {
|
|
8600
8709
|
config: config2,
|
|
8601
8710
|
runtime: model.runtime,
|
|
8602
|
-
hasAuthToken: token
|
|
8711
|
+
hasAuthToken: burnAuthReady(model.runtime, token),
|
|
8603
8712
|
executeResearchRun: (c, waypoint) => realExecuteResearchRun(c, waypoint, config2, token, model)
|
|
8604
8713
|
};
|
|
8605
8714
|
}
|
|
@@ -8785,8 +8894,7 @@ async function createFeature(ctx, input) {
|
|
|
8785
8894
|
const project = requireProjectById(ctx, input.projectId);
|
|
8786
8895
|
const slug = uniqueSlug(ctx, project.id, input.title);
|
|
8787
8896
|
const branch = `feature/${slug}`;
|
|
8788
|
-
const
|
|
8789
|
-
const cut = input.draft ? null : await ensureFeatureBranch(project, slug, requestedBase);
|
|
8897
|
+
const cut = input.draft ? null : await ensureFeatureBranch(project, slug, await requestedBase(project, input.baseBranch));
|
|
8790
8898
|
const row = {
|
|
8791
8899
|
id: newId("feat"),
|
|
8792
8900
|
projectId: project.id,
|
|
@@ -8836,8 +8944,8 @@ async function startDraft(ctx, featureId, opts = {}) {
|
|
|
8836
8944
|
throw new GateError(`feature ${feature.slug} is not a draft \u2014 it has already been started`);
|
|
8837
8945
|
}
|
|
8838
8946
|
const project = projectForFeature(ctx, feature);
|
|
8839
|
-
const
|
|
8840
|
-
const { branchReady, baseBranch } = await ensureFeatureBranch(project, feature.slug,
|
|
8947
|
+
const base = await requestedBase(project, opts.baseBranch);
|
|
8948
|
+
const { branchReady, baseBranch } = await ensureFeatureBranch(project, feature.slug, base);
|
|
8841
8949
|
ctx.db.update(features).set({ status: "active", baseBranch }).where(eq10(features.id, featureId)).run();
|
|
8842
8950
|
const started = { ...feature, status: "active", baseBranch };
|
|
8843
8951
|
emit(ctx, featureId, {
|
|
@@ -8897,8 +9005,8 @@ async function quickChange(ctx, input) {
|
|
|
8897
9005
|
throw new InvalidInputError("a quick change needs a sentence describing the change");
|
|
8898
9006
|
const slug = uniqueSlug(ctx, project.id, title);
|
|
8899
9007
|
const branch = `feature/${slug}`;
|
|
8900
|
-
const
|
|
8901
|
-
const { branchReady, baseBranch } = await ensureFeatureBranch(project, slug,
|
|
9008
|
+
const base = await requestedBase(project, input.baseBranch);
|
|
9009
|
+
const { branchReady, baseBranch } = await ensureFeatureBranch(project, slug, base);
|
|
8902
9010
|
const inserted = ctx.db.insert(features).values({
|
|
8903
9011
|
id: newId("feat"),
|
|
8904
9012
|
projectId: project.id,
|
|
@@ -8947,14 +9055,17 @@ async function quickChange(ctx, input) {
|
|
|
8947
9055
|
}
|
|
8948
9056
|
return feature;
|
|
8949
9057
|
}
|
|
8950
|
-
async function
|
|
9058
|
+
async function requestedBase(project, picked) {
|
|
9059
|
+
return picked?.trim() || await currentCheckoutBranch(project);
|
|
9060
|
+
}
|
|
9061
|
+
async function ensureFeatureBranch(project, slug, base) {
|
|
8951
9062
|
try {
|
|
8952
|
-
const baseBranch = await resolveBaseBranch(project,
|
|
9063
|
+
const baseBranch = await resolveBaseBranch(project, base);
|
|
8953
9064
|
await createFeatureBranch(project, slug, baseBranch);
|
|
8954
9065
|
return { branchReady: true, baseBranch };
|
|
8955
9066
|
} catch (e) {
|
|
8956
9067
|
if (isNotImplemented(e))
|
|
8957
|
-
return { branchReady: false, baseBranch:
|
|
9068
|
+
return { branchReady: false, baseBranch: base };
|
|
8958
9069
|
throw e;
|
|
8959
9070
|
}
|
|
8960
9071
|
}
|
|
@@ -9297,7 +9408,7 @@ var init_features = __esm(() => {
|
|
|
9297
9408
|
});
|
|
9298
9409
|
|
|
9299
9410
|
// src/services/test-notes.ts
|
|
9300
|
-
import { existsSync as
|
|
9411
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
9301
9412
|
import { dirname as dirname8 } from "path";
|
|
9302
9413
|
import { asc as asc4, eq as eq11 } from "drizzle-orm";
|
|
9303
9414
|
function rowToNote(row) {
|
|
@@ -9310,7 +9421,7 @@ function rowToNote(row) {
|
|
|
9310
9421
|
author: row.author,
|
|
9311
9422
|
ticketId: row.ticketId ?? undefined,
|
|
9312
9423
|
videoTimestamp: row.videoTimestamp ?? undefined,
|
|
9313
|
-
screenshotUrl:
|
|
9424
|
+
screenshotUrl: existsSync18(annotationPath(row.id)) ? noteScreenshotUrl(row.id) : undefined,
|
|
9314
9425
|
createdAt: row.createdAt,
|
|
9315
9426
|
updatedAt: row.updatedAt
|
|
9316
9427
|
});
|
|
@@ -9518,7 +9629,7 @@ var init_test_notes = __esm(() => {
|
|
|
9518
9629
|
});
|
|
9519
9630
|
|
|
9520
9631
|
// src/mcp/server.ts
|
|
9521
|
-
import { existsSync as
|
|
9632
|
+
import { existsSync as existsSync19, readFileSync as readFileSync11 } from "fs";
|
|
9522
9633
|
import { join as join16, relative as relative3, resolve as resolve9, sep as sep2 } from "path";
|
|
9523
9634
|
import { StreamableHTTPTransport } from "@hono/mcp";
|
|
9524
9635
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -9872,7 +9983,7 @@ async function toolCreateFeature(ctx, session, input) {
|
|
|
9872
9983
|
phase: feature.phase
|
|
9873
9984
|
};
|
|
9874
9985
|
}
|
|
9875
|
-
function toolGetProjectContext(ctx, session) {
|
|
9986
|
+
async function toolGetProjectContext(ctx, session) {
|
|
9876
9987
|
const project = requireProject(ctx, session);
|
|
9877
9988
|
const charter = readCharter(session.worktreePath);
|
|
9878
9989
|
return {
|
|
@@ -9880,7 +9991,18 @@ function toolGetProjectContext(ctx, session) {
|
|
|
9880
9991
|
...charter !== undefined ? { charter } : {},
|
|
9881
9992
|
adrs: listLiveAdrs(session.worktreePath).map(adrRef),
|
|
9882
9993
|
adrsNote: ADRS_NOTE,
|
|
9883
|
-
featureIndex: list(ctx, project.id).map(featureIndexLine)
|
|
9994
|
+
featureIndex: list(ctx, project.id).map(featureIndexLine),
|
|
9995
|
+
baseBranches: await readBaseBranches(project)
|
|
9996
|
+
};
|
|
9997
|
+
}
|
|
9998
|
+
async function readBaseBranches(project) {
|
|
9999
|
+
const { current: current2, branches, remoteBranches } = await listBranches(project);
|
|
10000
|
+
const selectable = [...branches, ...remoteBranches];
|
|
10001
|
+
return {
|
|
10002
|
+
current: current2,
|
|
10003
|
+
currentIsSelectable: selectable.includes(current2),
|
|
10004
|
+
selectable,
|
|
10005
|
+
detectedMain: await detectMainBranch(project.repoPath)
|
|
9884
10006
|
};
|
|
9885
10007
|
}
|
|
9886
10008
|
function adrRef(adr) {
|
|
@@ -9902,7 +10024,7 @@ function toolReadAdr(ctx, session, input) {
|
|
|
9902
10024
|
if (rel === "" || rel.startsWith("..") || rel.split(sep2).includes("..")) {
|
|
9903
10025
|
throw new InvalidInputError(`adr path escapes ${ADR_DIR_REL}: ${input.relPath}`);
|
|
9904
10026
|
}
|
|
9905
|
-
if (!
|
|
10027
|
+
if (!existsSync19(target))
|
|
9906
10028
|
throw new NotFoundError(`adr not found: ${input.relPath}`);
|
|
9907
10029
|
return { relPath: `${ADR_DIR_REL}/${name}`, content: readFileSync11(target, "utf8") };
|
|
9908
10030
|
}
|
|
@@ -10099,7 +10221,7 @@ function buildMcpServer(audience) {
|
|
|
10099
10221
|
inputSchema: {
|
|
10100
10222
|
title: z4.string().min(1).describe("The feature\u2019s name; its slug and branch derive from this."),
|
|
10101
10223
|
oneLiner: z4.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."),
|
|
10102
|
-
baseBranch: z4.string().optional().describe("Branch to cut from
|
|
10224
|
+
baseBranch: z4.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."),
|
|
10103
10225
|
brief: z4.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."),
|
|
10104
10226
|
draft: z4.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."),
|
|
10105
10227
|
tickets: z4.array(z4.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.")
|
|
@@ -10114,13 +10236,13 @@ function buildMcpServer(audience) {
|
|
|
10114
10236
|
if (wants("get_project_context")) {
|
|
10115
10237
|
server.registerTool("get_project_context", {
|
|
10116
10238
|
title: "Get project context",
|
|
10117
|
-
description: "The project as it stands: the project row, the charter (CONTEXT.md) in full, an INDEX " + "of every live ADR (superseded ones omitted) and a one-line index of every feature. " + "ADR bodies are not inlined \u2014 read the ones your work touches with `read_adr`. Shipped " + "features carry their one-liner and docs path (readable on disk); in-flight ones carry " + "slug, phase, lap and ticket counts but no docs path, because their docs live on an " + "unmerged branch. Use the slug with `get_work_record` to see what one actually did.",
|
|
10239
|
+
description: "The project as it stands: the project row, the charter (CONTEXT.md) in full, an INDEX " + "of every live ADR (superseded ones omitted) and a one-line index of every feature. " + "ADR bodies are not inlined \u2014 read the ones your work touches with `read_adr`. Shipped " + "features carry their one-liner and docs path (readable on disk); in-flight ones carry " + "slug, phase, lap and ticket counts but no docs path, because their docs live on an " + "unmerged branch. Use the slug with `get_work_record` to see what one actually did. " + "Plus `baseBranches` \u2014 the branch a new feature would cut from: the checkout\u2019s `current` " + "branch, `currentIsSelectable` (false mid test drive or on a detached HEAD, the one case " + "with no default), every `selectable` base, and the `detectedMain` line to suggest then.",
|
|
10118
10240
|
inputSchema: {}
|
|
10119
10241
|
}, async (_args, extra) => {
|
|
10120
10242
|
const rs = await resolveCtxSession(extra);
|
|
10121
10243
|
if (!rs)
|
|
10122
10244
|
return noSession();
|
|
10123
|
-
return ok(toolGetProjectContext(rs.ctx, rs.session));
|
|
10245
|
+
return ok(await toolGetProjectContext(rs.ctx, rs.session));
|
|
10124
10246
|
});
|
|
10125
10247
|
}
|
|
10126
10248
|
if (wants("read_adr")) {
|
|
@@ -10942,7 +11064,7 @@ var init_stream = __esm(() => {
|
|
|
10942
11064
|
});
|
|
10943
11065
|
|
|
10944
11066
|
// src/routes/web.ts
|
|
10945
|
-
import { existsSync as
|
|
11067
|
+
import { existsSync as existsSync20, statSync as statSync4 } from "fs";
|
|
10946
11068
|
import { readFile } from "fs/promises";
|
|
10947
11069
|
import { extname, join as join17, normalize, resolve as resolve10, sep as sep3 } from "path";
|
|
10948
11070
|
function contentType(path) {
|
|
@@ -10995,7 +11117,7 @@ function mountWebApp(app, distDir) {
|
|
|
10995
11117
|
}
|
|
10996
11118
|
function mountWebAppIfBuilt(app) {
|
|
10997
11119
|
const dist = resolveWebDist();
|
|
10998
|
-
if (
|
|
11120
|
+
if (existsSync20(join17(dist, "index.html")))
|
|
10999
11121
|
mountWebApp(app, dist);
|
|
11000
11122
|
}
|
|
11001
11123
|
var MIME;
|
|
@@ -11046,12 +11168,12 @@ var init_version = () => {};
|
|
|
11046
11168
|
|
|
11047
11169
|
// src/services/install-id.ts
|
|
11048
11170
|
import { randomUUID } from "crypto";
|
|
11049
|
-
import { existsSync as
|
|
11171
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
|
|
11050
11172
|
import { dirname as dirname10 } from "path";
|
|
11051
11173
|
function getInstallId() {
|
|
11052
11174
|
const file = installIdPath();
|
|
11053
11175
|
try {
|
|
11054
|
-
if (
|
|
11176
|
+
if (existsSync21(file)) {
|
|
11055
11177
|
const existing = readFileSync13(file, "utf8").trim();
|
|
11056
11178
|
if (UUID_RE.test(existing))
|
|
11057
11179
|
return existing;
|
|
@@ -11251,7 +11373,7 @@ var init_events2 = __esm(() => {
|
|
|
11251
11373
|
});
|
|
11252
11374
|
|
|
11253
11375
|
// src/launcher/launcher.ts
|
|
11254
|
-
import { existsSync as
|
|
11376
|
+
import { existsSync as existsSync22 } from "fs";
|
|
11255
11377
|
import { join as join19 } from "path";
|
|
11256
11378
|
import { and as and9, eq as eq13 } from "drizzle-orm";
|
|
11257
11379
|
function ptyExitMessage(exitCode) {
|
|
@@ -11637,7 +11759,7 @@ async function launchProjectSession(ctx, input, opts = {}) {
|
|
|
11637
11759
|
const model = resolveModelEntry("project", ctx.config, project);
|
|
11638
11760
|
const runtime = runtimeAdapterFor(model.runtime);
|
|
11639
11761
|
assertRuntimeReady(runtime, opts);
|
|
11640
|
-
const worktreePath = await ensureProjectWorktree(project, (res) => reportProjectLanding(ctx, project, res, { retried: true }));
|
|
11762
|
+
const { worktreePath, base } = await ensureProjectWorktree(project, (res) => reportProjectLanding(ctx, project, res, { retried: true }));
|
|
11641
11763
|
const session = createSessionRow(ctx, {
|
|
11642
11764
|
projectId: project.id,
|
|
11643
11765
|
kind: "project",
|
|
@@ -11690,7 +11812,7 @@ async function launchProjectSession(ctx, input, opts = {}) {
|
|
|
11690
11812
|
session,
|
|
11691
11813
|
project,
|
|
11692
11814
|
config: ctx.config,
|
|
11693
|
-
projectBrief: { project, branch: PROJECT_BRANCH, worktreePath },
|
|
11815
|
+
projectBrief: { project, branch: PROJECT_BRANCH, worktreePath, base },
|
|
11694
11816
|
worktreePath,
|
|
11695
11817
|
serverUrl: serverUrlFor(ctx.config),
|
|
11696
11818
|
model: model.id,
|
|
@@ -11724,7 +11846,7 @@ async function buildPrepareBrief(ctx, project) {
|
|
|
11724
11846
|
};
|
|
11725
11847
|
}
|
|
11726
11848
|
function probePrepareHost(repoPath) {
|
|
11727
|
-
const has = (rel) =>
|
|
11849
|
+
const has = (rel) => existsSync22(join19(repoPath, rel));
|
|
11728
11850
|
const lock = LOCKFILES.find(([file]) => has(file));
|
|
11729
11851
|
return {
|
|
11730
11852
|
platform: process.platform,
|
|
@@ -12074,10 +12196,10 @@ var init_feature = __esm(() => {
|
|
|
12074
12196
|
});
|
|
12075
12197
|
|
|
12076
12198
|
// src/services/fsbrowse.ts
|
|
12077
|
-
import { existsSync as
|
|
12078
|
-
import { homedir as
|
|
12199
|
+
import { existsSync as existsSync23, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
12200
|
+
import { homedir as homedir3 } from "os";
|
|
12079
12201
|
import { basename as basename3, dirname as dirname11, isAbsolute as isAbsolute2, join as join21, resolve as resolve11, sep as sep4 } from "path";
|
|
12080
|
-
function expandPath(input, home =
|
|
12202
|
+
function expandPath(input, home = homedir3()) {
|
|
12081
12203
|
const trimmed = input.trim();
|
|
12082
12204
|
if (trimmed === "")
|
|
12083
12205
|
return trimmed;
|
|
@@ -12090,31 +12212,31 @@ function expandPath(input, home = homedir4()) {
|
|
|
12090
12212
|
}
|
|
12091
12213
|
function looksLikeRepo(dir) {
|
|
12092
12214
|
try {
|
|
12093
|
-
return
|
|
12215
|
+
return existsSync23(join21(dir, ".git"));
|
|
12094
12216
|
} catch {
|
|
12095
12217
|
return false;
|
|
12096
12218
|
}
|
|
12097
12219
|
}
|
|
12098
|
-
function listRoots(home =
|
|
12220
|
+
function listRoots(home = homedir3()) {
|
|
12099
12221
|
const roots = [];
|
|
12100
12222
|
if (process.platform === "win32") {
|
|
12101
12223
|
for (let code = 67;code <= 90; code++) {
|
|
12102
12224
|
const drive = `${String.fromCharCode(code)}:${sep4}`;
|
|
12103
12225
|
try {
|
|
12104
|
-
if (
|
|
12226
|
+
if (existsSync23(drive))
|
|
12105
12227
|
roots.push({ label: drive.replace(/\\$/, ""), path: drive, kind: "drive" });
|
|
12106
12228
|
} catch {}
|
|
12107
12229
|
}
|
|
12108
12230
|
} else {
|
|
12109
12231
|
roots.push({ label: "/", path: "/", kind: "drive" });
|
|
12110
12232
|
}
|
|
12111
|
-
if (
|
|
12233
|
+
if (existsSync23(home))
|
|
12112
12234
|
roots.push({ label: "Home", path: home, kind: "home" });
|
|
12113
12235
|
const seen = new Set(roots.map(pathKey));
|
|
12114
12236
|
for (const name of COMMON_DIRS) {
|
|
12115
12237
|
const candidate = join21(home, name);
|
|
12116
12238
|
const key = pathKey({ path: candidate });
|
|
12117
|
-
if (seen.has(key) || !
|
|
12239
|
+
if (seen.has(key) || !existsSync23(candidate))
|
|
12118
12240
|
continue;
|
|
12119
12241
|
seen.add(key);
|
|
12120
12242
|
roots.push({ label: name, path: candidate, kind: "common" });
|
|
@@ -12140,12 +12262,12 @@ function parentOf(dir) {
|
|
|
12140
12262
|
const parent = dirname11(dir);
|
|
12141
12263
|
return parent === dir ? null : parent;
|
|
12142
12264
|
}
|
|
12143
|
-
function browseDir(input, showHidden = false, home =
|
|
12265
|
+
function browseDir(input, showHidden = false, home = homedir3()) {
|
|
12144
12266
|
const target = input && input.trim() !== "" ? expandPath(input, home) : home;
|
|
12145
12267
|
if (!isAbsolute2(target)) {
|
|
12146
12268
|
throw new InvalidInputError(`path is not absolute: ${target}`);
|
|
12147
12269
|
}
|
|
12148
|
-
if (!
|
|
12270
|
+
if (!existsSync23(target)) {
|
|
12149
12271
|
throw new InvalidInputError(`path does not exist: ${target}`);
|
|
12150
12272
|
}
|
|
12151
12273
|
let dir = target;
|
|
@@ -12370,7 +12492,7 @@ var init_conversations = __esm(() => {
|
|
|
12370
12492
|
});
|
|
12371
12493
|
|
|
12372
12494
|
// src/services/projects.ts
|
|
12373
|
-
import { existsSync as
|
|
12495
|
+
import { existsSync as existsSync24 } from "fs";
|
|
12374
12496
|
import { basename as basename4, join as join22 } from "path";
|
|
12375
12497
|
import { eq as eq14, isNull } from "drizzle-orm";
|
|
12376
12498
|
function listProjects(ctx) {
|
|
@@ -12390,15 +12512,14 @@ function warnIfTranslatedMount(ctx, projectId, repoPath) {
|
|
|
12390
12512
|
async function openProject(ctx, rawPath) {
|
|
12391
12513
|
const repoPath = expandPath(rawPath);
|
|
12392
12514
|
await assertRepoTolerant(ctx, repoPath);
|
|
12393
|
-
const mainBranch = await detectMainBranchTolerant(ctx, repoPath);
|
|
12394
12515
|
const name = basename4(repoPath) || repoPath;
|
|
12395
12516
|
const existing = getProjectByRepoPath(ctx, repoPath) ?? findProjectByCanonPath(ctx, repoPath);
|
|
12396
12517
|
if (existing) {
|
|
12397
|
-
ctx.db.update(projects).set({
|
|
12398
|
-
const reopened = { ...existing,
|
|
12518
|
+
ctx.db.update(projects).set({ repoPath, closedAt: null }).where(eq14(projects.id, existing.id)).run();
|
|
12519
|
+
const reopened = { ...existing, repoPath, closedAt: undefined };
|
|
12399
12520
|
emitProject(ctx, existing.id, {
|
|
12400
12521
|
type: "project.opened",
|
|
12401
|
-
message: `project ${existing.name} re-opened at ${repoPath}
|
|
12522
|
+
message: `project ${existing.name} re-opened at ${repoPath}`
|
|
12402
12523
|
});
|
|
12403
12524
|
warnIfTranslatedMount(ctx, existing.id, repoPath);
|
|
12404
12525
|
return reopened;
|
|
@@ -12407,7 +12528,6 @@ async function openProject(ctx, rawPath) {
|
|
|
12407
12528
|
id: newId("proj"),
|
|
12408
12529
|
name,
|
|
12409
12530
|
repoPath,
|
|
12410
|
-
mainBranch,
|
|
12411
12531
|
devCommand: null,
|
|
12412
12532
|
closedAt: null
|
|
12413
12533
|
};
|
|
@@ -12415,7 +12535,7 @@ async function openProject(ctx, rawPath) {
|
|
|
12415
12535
|
const project = rowToProject(inserted);
|
|
12416
12536
|
emitProject(ctx, project.id, {
|
|
12417
12537
|
type: "project.opened",
|
|
12418
|
-
message: `project ${name} at ${repoPath}
|
|
12538
|
+
message: `project ${name} at ${repoPath}`
|
|
12419
12539
|
});
|
|
12420
12540
|
warnIfTranslatedMount(ctx, project.id, repoPath);
|
|
12421
12541
|
return project;
|
|
@@ -12452,20 +12572,11 @@ async function assertRepoTolerant(ctx, repoPath) {
|
|
|
12452
12572
|
} catch (e) {
|
|
12453
12573
|
if (!isNotImplemented(e))
|
|
12454
12574
|
throw e;
|
|
12455
|
-
if (!
|
|
12575
|
+
if (!existsSync24(join22(repoPath, ".git"))) {
|
|
12456
12576
|
throw new InvalidInputError(`not a git repository: ${repoPath}`);
|
|
12457
12577
|
}
|
|
12458
12578
|
}
|
|
12459
12579
|
}
|
|
12460
|
-
async function detectMainBranchTolerant(ctx, repoPath) {
|
|
12461
|
-
try {
|
|
12462
|
-
return await detectMainBranch(repoPath);
|
|
12463
|
-
} catch (e) {
|
|
12464
|
-
if (!isNotImplemented(e))
|
|
12465
|
-
throw e;
|
|
12466
|
-
return ctx.config.mainBranch;
|
|
12467
|
-
}
|
|
12468
|
-
}
|
|
12469
12580
|
var init_projects = __esm(() => {
|
|
12470
12581
|
init_src();
|
|
12471
12582
|
init_schema();
|
|
@@ -12495,6 +12606,7 @@ var init_project = __esm(() => {
|
|
|
12495
12606
|
roots: publicProcedure.query(() => listRoots()),
|
|
12496
12607
|
browse: publicProcedure.input(z8.object({ dir: z8.string().optional(), showHidden: z8.boolean().optional() }).optional()).query(({ input }) => browseDir(input?.dir, input?.showHidden ?? false)),
|
|
12497
12608
|
branches: publicProcedure.input(z8.object({ projectId: z8.string() })).query(({ ctx, input }) => listBranches(requireProjectById(ctx, input.projectId))),
|
|
12609
|
+
sessionBranch: publicProcedure.input(z8.object({ projectId: z8.string() })).query(({ ctx, input }) => sessionBranchView(requireProjectById(ctx, input.projectId))),
|
|
12498
12610
|
open: publicProcedure.input(z8.object({ repoPath: z8.string().min(1) })).mutation(({ ctx, input }) => openProject(ctx, input.repoPath)),
|
|
12499
12611
|
close: publicProcedure.input(z8.object({ projectId: z8.string() })).mutation(({ ctx, input }) => closeProject(ctx, input.projectId)),
|
|
12500
12612
|
rename: publicProcedure.input(z8.object({ projectId: z8.string(), name: ProjectName })).mutation(({ ctx, input }) => renameProject(ctx, input.projectId, input.name)),
|