runcastle 1.0.10 → 1.0.12
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 +94 -52
- package/index.js +94 -52
- package/package.json +1 -1
package/bin/runcastle.js
CHANGED
|
@@ -1931,8 +1931,9 @@ var init_dev_pane = __esm(() => {
|
|
|
1931
1931
|
});
|
|
1932
1932
|
|
|
1933
1933
|
// src/services/git.ts
|
|
1934
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync2, realpathSync } from "fs";
|
|
1935
|
-
import {
|
|
1934
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, mkdtempSync, realpathSync, rmSync } from "fs";
|
|
1935
|
+
import { tmpdir } from "os";
|
|
1936
|
+
import { dirname, join as join6, resolve as resolve3 } from "path";
|
|
1936
1937
|
import { simpleGit } from "simple-git";
|
|
1937
1938
|
function featureBranch(slug) {
|
|
1938
1939
|
return `feature/${slug}`;
|
|
@@ -2139,6 +2140,9 @@ function researchBranchName(slug, waypointSeq, unique) {
|
|
|
2139
2140
|
function ticketBranchName(slug, ticketSeq, unique) {
|
|
2140
2141
|
return `${TICKET_BRANCH_PREFIX}${tempBranchSlugSegment(slug)}/${ticketSeq}-${unique}`;
|
|
2141
2142
|
}
|
|
2143
|
+
async function allowPushToCheckedOutBranches(repoPath) {
|
|
2144
|
+
await git(repoPath).addConfig("receive.denyCurrentBranch", "ignore");
|
|
2145
|
+
}
|
|
2142
2146
|
async function mergeTempBranch(repoPath, featureBranchName, tempBranch) {
|
|
2143
2147
|
const g = git(repoPath);
|
|
2144
2148
|
let branches;
|
|
@@ -2168,13 +2172,47 @@ async function mergeTempBranch(repoPath, featureBranchName, tempBranch) {
|
|
|
2168
2172
|
} else {
|
|
2169
2173
|
try {
|
|
2170
2174
|
await g.raw(["fetch", ".", `${tempBranch}:${featureBranchName}`]);
|
|
2171
|
-
} catch
|
|
2172
|
-
|
|
2175
|
+
} catch {
|
|
2176
|
+
const merged = await mergeInDisposableWorktree(g, featureBranchName, tempBranch);
|
|
2177
|
+
if (!merged.ok)
|
|
2178
|
+
return merged;
|
|
2173
2179
|
}
|
|
2174
2180
|
}
|
|
2175
2181
|
await deleteBranchDetachingWorktrees(g, repoPath, tempBranch);
|
|
2176
2182
|
return { ok: true };
|
|
2177
2183
|
}
|
|
2184
|
+
async function mergeInDisposableWorktree(g, featureBranchName, tempBranch) {
|
|
2185
|
+
let dir;
|
|
2186
|
+
try {
|
|
2187
|
+
dir = mkdtempSync(join6(tmpdir(), "rc-land-"));
|
|
2188
|
+
} catch (e) {
|
|
2189
|
+
return { ok: false, error: errMsg(e) };
|
|
2190
|
+
}
|
|
2191
|
+
const wt = join6(dir, "wt");
|
|
2192
|
+
try {
|
|
2193
|
+
await g.raw(["worktree", "add", wt, featureBranchName]);
|
|
2194
|
+
const gw = git(wt);
|
|
2195
|
+
try {
|
|
2196
|
+
await gw.merge([tempBranch]);
|
|
2197
|
+
} catch (e) {
|
|
2198
|
+
if (await mergeInProgress(gw)) {
|
|
2199
|
+
await gw.raw(["merge", "--abort"]);
|
|
2200
|
+
return { ok: false, conflict: true, error: errMsg(e) };
|
|
2201
|
+
}
|
|
2202
|
+
return { ok: false, error: errMsg(e) };
|
|
2203
|
+
}
|
|
2204
|
+
return { ok: true };
|
|
2205
|
+
} catch (e) {
|
|
2206
|
+
return { ok: false, error: errMsg(e) };
|
|
2207
|
+
} finally {
|
|
2208
|
+
try {
|
|
2209
|
+
await g.raw(["worktree", "remove", "--force", wt]);
|
|
2210
|
+
} catch {}
|
|
2211
|
+
try {
|
|
2212
|
+
rmSync(dir, { recursive: true, force: true });
|
|
2213
|
+
} catch {}
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2178
2216
|
async function deleteBranchDetachingWorktrees(g, repoPath, branch) {
|
|
2179
2217
|
for (const holder of await worktreesOnBranch(g, branch, repoPath)) {
|
|
2180
2218
|
try {
|
|
@@ -2380,7 +2418,7 @@ var init_git = __esm(() => {
|
|
|
2380
2418
|
|
|
2381
2419
|
// src/services/knowledge.ts
|
|
2382
2420
|
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync } from "fs";
|
|
2383
|
-
import { join as
|
|
2421
|
+
import { join as join7, relative, resolve as resolve4, sep } from "path";
|
|
2384
2422
|
function scaffoldDocs(ctx, feature) {
|
|
2385
2423
|
const project = projectForFeature(ctx, feature);
|
|
2386
2424
|
const dir = featureDocsDir(project, feature);
|
|
@@ -2435,7 +2473,7 @@ function listDocs(ctx, feature) {
|
|
|
2435
2473
|
const dir = featureDocsDir(project, feature);
|
|
2436
2474
|
if (!existsSync8(dir))
|
|
2437
2475
|
return [];
|
|
2438
|
-
return readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => ({ relPath: e.name, title: readTitle(
|
|
2476
|
+
return readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => ({ relPath: e.name, title: readTitle(join7(dir, e.name), e.name) })).sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
2439
2477
|
}
|
|
2440
2478
|
function readDoc(ctx, feature, relPath) {
|
|
2441
2479
|
const project = projectForFeature(ctx, feature);
|
|
@@ -2476,21 +2514,21 @@ var init_knowledge = __esm(() => {
|
|
|
2476
2514
|
|
|
2477
2515
|
// src/launcher/skills-root.ts
|
|
2478
2516
|
import { existsSync as existsSync9 } from "fs";
|
|
2479
|
-
import { dirname as dirname2, join as
|
|
2517
|
+
import { dirname as dirname2, join as join8, resolve as resolve5 } from "path";
|
|
2480
2518
|
function resolveSkillsRoot(fromDir) {
|
|
2481
2519
|
const override = process.env[SKILLS_DIR_ENV];
|
|
2482
2520
|
if (override) {
|
|
2483
2521
|
const root = resolve5(override);
|
|
2484
|
-
if (existsSync9(
|
|
2522
|
+
if (existsSync9(join8(root, SKILLS_MARKER)))
|
|
2485
2523
|
return root;
|
|
2486
2524
|
throw new Error(`${SKILLS_DIR_ENV}=${override} is not a runcastle skills root (missing ${SKILLS_MARKER})`);
|
|
2487
2525
|
}
|
|
2488
2526
|
const searched = [];
|
|
2489
2527
|
let dir = fromDir;
|
|
2490
2528
|
for (let i = 0;i < 8; i += 1) {
|
|
2491
|
-
const candidate =
|
|
2529
|
+
const candidate = join8(dir, "packages", "skills");
|
|
2492
2530
|
searched.push(candidate);
|
|
2493
|
-
if (existsSync9(
|
|
2531
|
+
if (existsSync9(join8(candidate, SKILLS_MARKER)))
|
|
2494
2532
|
return candidate;
|
|
2495
2533
|
const parent = dirname2(dir);
|
|
2496
2534
|
if (parent === dir)
|
|
@@ -2503,7 +2541,7 @@ function resolveSkillsRoot(fromDir) {
|
|
|
2503
2541
|
}
|
|
2504
2542
|
var SKILLS_DIR_ENV = "RUNCASTLE_SKILLS_DIR", SKILLS_MARKER;
|
|
2505
2543
|
var init_skills_root = __esm(() => {
|
|
2506
|
-
SKILLS_MARKER =
|
|
2544
|
+
SKILLS_MARKER = join8("packs", "runcastle");
|
|
2507
2545
|
});
|
|
2508
2546
|
|
|
2509
2547
|
// src/services/agent-stream.ts
|
|
@@ -2548,7 +2586,7 @@ var init_agent_stream = __esm(() => {
|
|
|
2548
2586
|
|
|
2549
2587
|
// src/workflows/ticket-burner.ts
|
|
2550
2588
|
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
|
|
2551
|
-
import { dirname as dirname3, join as
|
|
2589
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
2552
2590
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2553
2591
|
import { claudeCode, run } from "@ai-hero/sandcastle";
|
|
2554
2592
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
@@ -2705,9 +2743,9 @@ function resolveBurnWorkspaceMode(config2, platform = process.platform) {
|
|
|
2705
2743
|
function buildIsolatedSetupCommand(tempBranch, setupCommand) {
|
|
2706
2744
|
const hookFile = `${ISOLATED_REPO_PATH}/.git/hooks/post-commit`;
|
|
2707
2745
|
const parts = [
|
|
2708
|
-
`git
|
|
2746
|
+
`git config --global --add safe.directory '*'`,
|
|
2709
2747
|
`git clone ${SANDBOX_WORKSPACE_PATH} ${ISOLATED_REPO_PATH}`,
|
|
2710
|
-
`printf '#!/bin/sh\\
|
|
2748
|
+
`printf '#!/bin/sh\\nunset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE\\ngit push --quiet origin HEAD:%s && exec git -C ${SANDBOX_WORKSPACE_PATH} reset --hard --quiet %s\\n' '${tempBranch}' '${tempBranch}' > ${hookFile}`,
|
|
2711
2749
|
`chmod +x ${hookFile}`
|
|
2712
2750
|
];
|
|
2713
2751
|
if (setupCommand)
|
|
@@ -2728,7 +2766,7 @@ function buildWorkspaceNotes(mode) {
|
|
|
2728
2766
|
`);
|
|
2729
2767
|
}
|
|
2730
2768
|
function readRepoToolchain(repoPath) {
|
|
2731
|
-
const pkgPath =
|
|
2769
|
+
const pkgPath = join9(repoPath, "package.json");
|
|
2732
2770
|
const hasPackageJson = existsSync10(pkgPath);
|
|
2733
2771
|
let packageManagerField;
|
|
2734
2772
|
if (hasPackageJson) {
|
|
@@ -2742,10 +2780,10 @@ function readRepoToolchain(repoPath) {
|
|
|
2742
2780
|
hasPackageJson,
|
|
2743
2781
|
...packageManagerField !== undefined ? { packageManagerField } : {},
|
|
2744
2782
|
lockfiles: {
|
|
2745
|
-
bun: existsSync10(
|
|
2746
|
-
pnpm: existsSync10(
|
|
2747
|
-
yarn: existsSync10(
|
|
2748
|
-
npm: existsSync10(
|
|
2783
|
+
bun: existsSync10(join9(repoPath, "bun.lock")) || existsSync10(join9(repoPath, "bun.lockb")),
|
|
2784
|
+
pnpm: existsSync10(join9(repoPath, "pnpm-lock.yaml")),
|
|
2785
|
+
yarn: existsSync10(join9(repoPath, "yarn.lock")),
|
|
2786
|
+
npm: existsSync10(join9(repoPath, "package-lock.json"))
|
|
2749
2787
|
}
|
|
2750
2788
|
};
|
|
2751
2789
|
}
|
|
@@ -2955,23 +2993,23 @@ async function burnRun(ctx, deps) {
|
|
|
2955
2993
|
}
|
|
2956
2994
|
function burnerTemplatePath() {
|
|
2957
2995
|
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
2958
|
-
return
|
|
2996
|
+
return join9(resolveSkillsRoot(here), "burner", "implement-ticket.md");
|
|
2959
2997
|
}
|
|
2960
2998
|
function readDocsDigestFromDisk(projectId, slug) {
|
|
2961
2999
|
const worktree = worktreeDir(projectId, slug);
|
|
2962
3000
|
if (!existsSync10(worktree))
|
|
2963
3001
|
return "_No talk worktree on disk \u2014 docs digest skipped._";
|
|
2964
|
-
const docsDir =
|
|
3002
|
+
const docsDir = join9(worktree, ...featureDocsRel(slug).split("/"));
|
|
2965
3003
|
if (!existsSync10(docsDir))
|
|
2966
3004
|
return "_No docs/features dir in the talk worktree \u2014 docs digest skipped._";
|
|
2967
|
-
const files = readdirSync3(docsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name)).map((e) => ({ name: e.name, content: readFileSync4(
|
|
3005
|
+
const files = readdirSync3(docsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name)).map((e) => ({ name: e.name, content: readFileSync4(join9(docsDir, e.name), "utf8") }));
|
|
2968
3006
|
return buildDocsDigest(files);
|
|
2969
3007
|
}
|
|
2970
3008
|
function readBlockedFile(dirs) {
|
|
2971
3009
|
for (const dir of dirs) {
|
|
2972
3010
|
if (!dir)
|
|
2973
3011
|
continue;
|
|
2974
|
-
const p =
|
|
3012
|
+
const p = join9(dir, "BLOCKED.md");
|
|
2975
3013
|
if (existsSync10(p)) {
|
|
2976
3014
|
try {
|
|
2977
3015
|
return readFileSync4(p, "utf8");
|
|
@@ -3014,10 +3052,12 @@ function selectSandbox(config2, mounts = []) {
|
|
|
3014
3052
|
return noSandbox();
|
|
3015
3053
|
}
|
|
3016
3054
|
}
|
|
3017
|
-
async function realExecuteTicketRun(ctx, ticket, config2, token, model, land) {
|
|
3055
|
+
async function realExecuteTicketRun(ctx, ticket, config2, token, model, land, ensureIsolatedPushTarget) {
|
|
3018
3056
|
const { project, feature } = ctx;
|
|
3019
3057
|
const tempBranch = ticketBranchName(feature.slug, ticket.seq, newId("b").slice(2, 10));
|
|
3020
3058
|
const workspaceMode = resolveBurnWorkspaceMode(config2);
|
|
3059
|
+
if (workspaceMode === "isolated")
|
|
3060
|
+
await ensureIsolatedPushTarget();
|
|
3021
3061
|
const template = readFileSync4(burnerTemplatePath(), "utf8");
|
|
3022
3062
|
const prompt = renderTicketPrompt(template, {
|
|
3023
3063
|
TICKET_JSON: buildTicketJson(ticket),
|
|
@@ -3046,7 +3086,7 @@ async function realExecuteTicketRun(ctx, ticket, config2, token, model, land) {
|
|
|
3046
3086
|
});
|
|
3047
3087
|
}
|
|
3048
3088
|
mkdirSync4(logsDir(), { recursive: true });
|
|
3049
|
-
const logFilePath =
|
|
3089
|
+
const logFilePath = join9(logsDir(), `burn-${feature.id}-${ticket.seq}.log`);
|
|
3050
3090
|
const throttle = createStreamThrottle((e) => ctx.emitEvent({ ...e, ticketId: ticket.id }));
|
|
3051
3091
|
beginTranscript(ticket.id);
|
|
3052
3092
|
const onStreamEvent = (event) => {
|
|
@@ -3131,11 +3171,13 @@ function resolveBurnDeps(ctx) {
|
|
|
3131
3171
|
const token = readTokenFromEnvFile(envPath());
|
|
3132
3172
|
const model = resolveModel("implement", config2, ctx.project, ctx.modelOverride);
|
|
3133
3173
|
const land = createSerialQueue();
|
|
3174
|
+
let pushTargetReady;
|
|
3175
|
+
const ensureIsolatedPushTarget = () => pushTargetReady ??= allowPushToCheckedOutBranches(ctx.project.repoPath);
|
|
3134
3176
|
return {
|
|
3135
3177
|
config: config2,
|
|
3136
3178
|
hasAuthToken: token !== undefined,
|
|
3137
3179
|
concurrency: config2.burnConcurrency,
|
|
3138
|
-
executeTicketRun: (c, ticket) => realExecuteTicketRun(c, ticket, config2, token, model, land)
|
|
3180
|
+
executeTicketRun: (c, ticket) => realExecuteTicketRun(c, ticket, config2, token, model, land, ensureIsolatedPushTarget)
|
|
3139
3181
|
};
|
|
3140
3182
|
}
|
|
3141
3183
|
function readTokenFromEnvFile(path) {
|
|
@@ -3180,7 +3222,7 @@ var init_ticket_burner = __esm(() => {
|
|
|
3180
3222
|
|
|
3181
3223
|
// src/workflows/research.ts
|
|
3182
3224
|
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "fs";
|
|
3183
|
-
import { dirname as dirname4, join as
|
|
3225
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
3184
3226
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3185
3227
|
import { run as run2 } from "@ai-hero/sandcastle";
|
|
3186
3228
|
import { docker as docker2 } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
@@ -3252,16 +3294,16 @@ function interpretResearchResult(result, docRelPath) {
|
|
|
3252
3294
|
}
|
|
3253
3295
|
function researchTemplatePath() {
|
|
3254
3296
|
const here = dirname4(fileURLToPath3(import.meta.url));
|
|
3255
|
-
return
|
|
3297
|
+
return join10(resolveSkillsRoot(here), "burner", "research-waypoint.md");
|
|
3256
3298
|
}
|
|
3257
3299
|
function readDocsDigestFromDisk2(projectId, slug) {
|
|
3258
3300
|
const worktree = worktreeDir(projectId, slug);
|
|
3259
3301
|
if (!existsSync11(worktree))
|
|
3260
3302
|
return "_No talk worktree on disk \u2014 docs digest skipped._";
|
|
3261
|
-
const docsDir =
|
|
3303
|
+
const docsDir = join10(worktree, ...featureDocsRel(slug).split("/"));
|
|
3262
3304
|
if (!existsSync11(docsDir))
|
|
3263
3305
|
return "_No docs/features dir in the talk worktree \u2014 docs digest skipped._";
|
|
3264
|
-
const files = readdirSync4(docsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name)).map((e) => ({ name: e.name, content: readFileSync5(
|
|
3306
|
+
const files = readdirSync4(docsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name)).map((e) => ({ name: e.name, content: readFileSync5(join10(docsDir, e.name), "utf8") }));
|
|
3265
3307
|
return buildDocsDigest(files);
|
|
3266
3308
|
}
|
|
3267
3309
|
async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
@@ -3276,7 +3318,7 @@ async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
|
3276
3318
|
RESEARCH_DOC_PATH: docRelPath
|
|
3277
3319
|
});
|
|
3278
3320
|
mkdirSync5(logsDir(), { recursive: true });
|
|
3279
|
-
const logFilePath =
|
|
3321
|
+
const logFilePath = join10(logsDir(), `research-${feature.id}-${waypoint.seq}.log`);
|
|
3280
3322
|
const throttle = createResearchStreamThrottle((e) => ctx.emitEvent(e));
|
|
3281
3323
|
const runOptions = {
|
|
3282
3324
|
agent: buildBurnAgent(config2, token, model),
|
|
@@ -4137,7 +4179,7 @@ var init_hooks = __esm(() => {
|
|
|
4137
4179
|
// src/routes/web.ts
|
|
4138
4180
|
import { existsSync as existsSync12, statSync } from "fs";
|
|
4139
4181
|
import { readFile } from "fs/promises";
|
|
4140
|
-
import { extname, join as
|
|
4182
|
+
import { extname, join as join11, normalize, resolve as resolve6, sep as sep2 } from "path";
|
|
4141
4183
|
function contentType(path) {
|
|
4142
4184
|
return MIME[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
4143
4185
|
}
|
|
@@ -4155,7 +4197,7 @@ function safeJoin(root, pathname) {
|
|
|
4155
4197
|
} catch {
|
|
4156
4198
|
return null;
|
|
4157
4199
|
}
|
|
4158
|
-
const joined = normalize(
|
|
4200
|
+
const joined = normalize(join11(root, decoded));
|
|
4159
4201
|
if (joined !== root && !joined.startsWith(root + sep2))
|
|
4160
4202
|
return null;
|
|
4161
4203
|
return joined;
|
|
@@ -4168,7 +4210,7 @@ function resolveWebDist() {
|
|
|
4168
4210
|
}
|
|
4169
4211
|
function mountWebApp(app, distDir) {
|
|
4170
4212
|
const root = resolve6(distDir);
|
|
4171
|
-
const indexHtml =
|
|
4213
|
+
const indexHtml = join11(root, "index.html");
|
|
4172
4214
|
app.get("*", async (c, next) => {
|
|
4173
4215
|
const { pathname } = new URL(c.req.url);
|
|
4174
4216
|
if (/^\/(api|mcp|ws|health)(\/|$)/.test(pathname))
|
|
@@ -4188,7 +4230,7 @@ function mountWebApp(app, distDir) {
|
|
|
4188
4230
|
}
|
|
4189
4231
|
function mountWebAppIfBuilt(app) {
|
|
4190
4232
|
const dist = resolveWebDist();
|
|
4191
|
-
if (existsSync12(
|
|
4233
|
+
if (existsSync12(join11(dist, "index.html")))
|
|
4192
4234
|
mountWebApp(app, dist);
|
|
4193
4235
|
}
|
|
4194
4236
|
var MIME;
|
|
@@ -4264,7 +4306,7 @@ var init_events2 = __esm(() => {
|
|
|
4264
4306
|
// src/launcher/artifacts.ts
|
|
4265
4307
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
4266
4308
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4267
|
-
import { join as
|
|
4309
|
+
import { join as join12 } from "path";
|
|
4268
4310
|
function hookClientPath() {
|
|
4269
4311
|
return resolveAsset(ASSET_ENV.hookClient, fileURLToPath4(new URL("./hook-client.ts", import.meta.url)));
|
|
4270
4312
|
}
|
|
@@ -4476,9 +4518,9 @@ async function writeSessionArtifacts(input) {
|
|
|
4476
4518
|
const { session, feature, config: config2, waypoint } = input;
|
|
4477
4519
|
const dir = sessionDir(session.id);
|
|
4478
4520
|
mkdirSync6(dir, { recursive: true });
|
|
4479
|
-
const systemPromptPath =
|
|
4480
|
-
const settingsPath =
|
|
4481
|
-
const mcpConfigPath =
|
|
4521
|
+
const systemPromptPath = join12(dir, "system-prompt.md");
|
|
4522
|
+
const settingsPath = join12(dir, "settings.json");
|
|
4523
|
+
const mcpConfigPath = join12(dir, "mcp.json");
|
|
4482
4524
|
writeFileSync2(systemPromptPath, renderSystemPrompt(feature, session.kind, waypoint), "utf8");
|
|
4483
4525
|
writeFileSync2(settingsPath, JSON.stringify(renderSettings(hookClientPath()), null, 2), "utf8");
|
|
4484
4526
|
writeFileSync2(mcpConfigPath, JSON.stringify(renderMcpConfig(session, config2), null, 2), "utf8");
|
|
@@ -4540,7 +4582,7 @@ var init_end_session = __esm(() => {
|
|
|
4540
4582
|
|
|
4541
4583
|
// src/launcher/launcher.ts
|
|
4542
4584
|
import { existsSync as existsSync13 } from "fs";
|
|
4543
|
-
import { dirname as dirname5, join as
|
|
4585
|
+
import { dirname as dirname5, join as join13, resolve as resolve7 } from "path";
|
|
4544
4586
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
4545
4587
|
import { and as and5, eq as eq8 } from "drizzle-orm";
|
|
4546
4588
|
function ptyExitMessage(exitCode) {
|
|
@@ -4578,10 +4620,10 @@ function claudeSpawnTarget(claudeArgs) {
|
|
|
4578
4620
|
return { file: exe, args: claudeArgs };
|
|
4579
4621
|
}
|
|
4580
4622
|
function resolvePluginDir(fromDir = dirname5(fileURLToPath5(import.meta.url))) {
|
|
4581
|
-
const rel =
|
|
4623
|
+
const rel = join13("packages", "skills", "packs", "runcastle");
|
|
4582
4624
|
const override = process.env[SKILLS_DIR_ENV];
|
|
4583
4625
|
if (override) {
|
|
4584
|
-
const dir2 =
|
|
4626
|
+
const dir2 = join13(resolve7(override), "packs", "runcastle");
|
|
4585
4627
|
if (existsSync13(dir2))
|
|
4586
4628
|
return dir2;
|
|
4587
4629
|
throw new Error(`${SKILLS_DIR_ENV}=${override} has no plugin dir at ${dir2}`);
|
|
@@ -4589,7 +4631,7 @@ function resolvePluginDir(fromDir = dirname5(fileURLToPath5(import.meta.url))) {
|
|
|
4589
4631
|
const searched = [];
|
|
4590
4632
|
let dir = fromDir;
|
|
4591
4633
|
for (let i = 0;i < 8; i += 1) {
|
|
4592
|
-
const candidate =
|
|
4634
|
+
const candidate = join13(dir, rel);
|
|
4593
4635
|
searched.push(candidate);
|
|
4594
4636
|
if (existsSync13(candidate))
|
|
4595
4637
|
return candidate;
|
|
@@ -4943,7 +4985,7 @@ var init_feature = __esm(() => {
|
|
|
4943
4985
|
|
|
4944
4986
|
// src/services/projects.ts
|
|
4945
4987
|
import { existsSync as existsSync14 } from "fs";
|
|
4946
|
-
import { basename, join as
|
|
4988
|
+
import { basename, join as join14 } from "path";
|
|
4947
4989
|
import { eq as eq9, isNull } from "drizzle-orm";
|
|
4948
4990
|
function listProjects(ctx) {
|
|
4949
4991
|
return ctx.db.select().from(projects).where(isNull(projects.closedAt)).all().map(rowToProject);
|
|
@@ -5019,7 +5061,7 @@ async function assertRepoTolerant(ctx, repoPath) {
|
|
|
5019
5061
|
} catch (e) {
|
|
5020
5062
|
if (!isNotImplemented(e))
|
|
5021
5063
|
throw e;
|
|
5022
|
-
if (!existsSync14(
|
|
5064
|
+
if (!existsSync14(join14(repoPath, ".git"))) {
|
|
5023
5065
|
throw new InvalidInputError(`not a git repository: ${repoPath}`);
|
|
5024
5066
|
}
|
|
5025
5067
|
}
|
|
@@ -5546,7 +5588,7 @@ var init_system_exec = __esm(() => {
|
|
|
5546
5588
|
|
|
5547
5589
|
// src/services/setup.ts
|
|
5548
5590
|
import { copyFileSync, existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
5549
|
-
import { dirname as dirname7, join as
|
|
5591
|
+
import { dirname as dirname7, join as join15 } from "path";
|
|
5550
5592
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
5551
5593
|
async function writeGitIdentity(exec, identity) {
|
|
5552
5594
|
const name = identity.name.trim();
|
|
@@ -5624,11 +5666,11 @@ function resolveSandcastleBin() {
|
|
|
5624
5666
|
const mainUrl = import.meta.resolve("@ai-hero/sandcastle");
|
|
5625
5667
|
let dir = dirname7(fileURLToPath6(mainUrl));
|
|
5626
5668
|
for (let hops = 0;hops < 6; hops++) {
|
|
5627
|
-
const manifestPath =
|
|
5669
|
+
const manifestPath = join15(dir, "package.json");
|
|
5628
5670
|
if (existsSync16(manifestPath)) {
|
|
5629
5671
|
const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
|
|
5630
5672
|
if (manifest.name === "@ai-hero/sandcastle" && manifest.bin?.sandcastle) {
|
|
5631
|
-
return
|
|
5673
|
+
return join15(dir, manifest.bin.sandcastle);
|
|
5632
5674
|
}
|
|
5633
5675
|
}
|
|
5634
5676
|
const parent = dirname7(dir);
|
|
@@ -5656,12 +5698,12 @@ function sandcastleTemplateDir() {
|
|
|
5656
5698
|
return resolveAsset(ASSET_ENV.sandcastleTemplate, fileURLToPath6(new URL("../assets/sandcastle", import.meta.url)));
|
|
5657
5699
|
}
|
|
5658
5700
|
function scaffoldSandcastleConfig(templateDir, targetDir) {
|
|
5659
|
-
const dir =
|
|
5701
|
+
const dir = join15(targetDir, ".sandcastle");
|
|
5660
5702
|
if (existsSync16(dir))
|
|
5661
5703
|
return { scaffolded: false, dir };
|
|
5662
5704
|
mkdirSync8(dir, { recursive: true });
|
|
5663
5705
|
for (const name of readdirSync5(templateDir)) {
|
|
5664
|
-
copyFileSync(
|
|
5706
|
+
copyFileSync(join15(templateDir, name), join15(dir, name));
|
|
5665
5707
|
}
|
|
5666
5708
|
return { scaffolded: true, dir };
|
|
5667
5709
|
}
|
|
@@ -5825,13 +5867,13 @@ var init_update_check = __esm(() => {
|
|
|
5825
5867
|
|
|
5826
5868
|
// src/version.ts
|
|
5827
5869
|
import { readFileSync as readFileSync8 } from "fs";
|
|
5828
|
-
import { dirname as dirname8, join as
|
|
5870
|
+
import { dirname as dirname8, join as join16 } from "path";
|
|
5829
5871
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
5830
5872
|
function runcastleVersion() {
|
|
5831
5873
|
if (cached2)
|
|
5832
5874
|
return cached2;
|
|
5833
5875
|
const here = dirname8(fileURLToPath7(import.meta.url));
|
|
5834
|
-
for (const pkgPath of [
|
|
5876
|
+
for (const pkgPath of [join16(here, "..", "package.json"), join16(here, "package.json")]) {
|
|
5835
5877
|
try {
|
|
5836
5878
|
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
5837
5879
|
if (typeof pkg.version === "string") {
|
package/index.js
CHANGED
|
@@ -1930,8 +1930,9 @@ var init_dev_pane = __esm(() => {
|
|
|
1930
1930
|
});
|
|
1931
1931
|
|
|
1932
1932
|
// src/services/git.ts
|
|
1933
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync2, realpathSync } from "fs";
|
|
1934
|
-
import {
|
|
1933
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, mkdtempSync, realpathSync, rmSync } from "fs";
|
|
1934
|
+
import { tmpdir } from "os";
|
|
1935
|
+
import { dirname, join as join6, resolve as resolve3 } from "path";
|
|
1935
1936
|
import { simpleGit } from "simple-git";
|
|
1936
1937
|
function featureBranch(slug) {
|
|
1937
1938
|
return `feature/${slug}`;
|
|
@@ -2138,6 +2139,9 @@ function researchBranchName(slug, waypointSeq, unique) {
|
|
|
2138
2139
|
function ticketBranchName(slug, ticketSeq, unique) {
|
|
2139
2140
|
return `${TICKET_BRANCH_PREFIX}${tempBranchSlugSegment(slug)}/${ticketSeq}-${unique}`;
|
|
2140
2141
|
}
|
|
2142
|
+
async function allowPushToCheckedOutBranches(repoPath) {
|
|
2143
|
+
await git(repoPath).addConfig("receive.denyCurrentBranch", "ignore");
|
|
2144
|
+
}
|
|
2141
2145
|
async function mergeTempBranch(repoPath, featureBranchName, tempBranch) {
|
|
2142
2146
|
const g = git(repoPath);
|
|
2143
2147
|
let branches;
|
|
@@ -2167,13 +2171,47 @@ async function mergeTempBranch(repoPath, featureBranchName, tempBranch) {
|
|
|
2167
2171
|
} else {
|
|
2168
2172
|
try {
|
|
2169
2173
|
await g.raw(["fetch", ".", `${tempBranch}:${featureBranchName}`]);
|
|
2170
|
-
} catch
|
|
2171
|
-
|
|
2174
|
+
} catch {
|
|
2175
|
+
const merged = await mergeInDisposableWorktree(g, featureBranchName, tempBranch);
|
|
2176
|
+
if (!merged.ok)
|
|
2177
|
+
return merged;
|
|
2172
2178
|
}
|
|
2173
2179
|
}
|
|
2174
2180
|
await deleteBranchDetachingWorktrees(g, repoPath, tempBranch);
|
|
2175
2181
|
return { ok: true };
|
|
2176
2182
|
}
|
|
2183
|
+
async function mergeInDisposableWorktree(g, featureBranchName, tempBranch) {
|
|
2184
|
+
let dir;
|
|
2185
|
+
try {
|
|
2186
|
+
dir = mkdtempSync(join6(tmpdir(), "rc-land-"));
|
|
2187
|
+
} catch (e) {
|
|
2188
|
+
return { ok: false, error: errMsg(e) };
|
|
2189
|
+
}
|
|
2190
|
+
const wt = join6(dir, "wt");
|
|
2191
|
+
try {
|
|
2192
|
+
await g.raw(["worktree", "add", wt, featureBranchName]);
|
|
2193
|
+
const gw = git(wt);
|
|
2194
|
+
try {
|
|
2195
|
+
await gw.merge([tempBranch]);
|
|
2196
|
+
} catch (e) {
|
|
2197
|
+
if (await mergeInProgress(gw)) {
|
|
2198
|
+
await gw.raw(["merge", "--abort"]);
|
|
2199
|
+
return { ok: false, conflict: true, error: errMsg(e) };
|
|
2200
|
+
}
|
|
2201
|
+
return { ok: false, error: errMsg(e) };
|
|
2202
|
+
}
|
|
2203
|
+
return { ok: true };
|
|
2204
|
+
} catch (e) {
|
|
2205
|
+
return { ok: false, error: errMsg(e) };
|
|
2206
|
+
} finally {
|
|
2207
|
+
try {
|
|
2208
|
+
await g.raw(["worktree", "remove", "--force", wt]);
|
|
2209
|
+
} catch {}
|
|
2210
|
+
try {
|
|
2211
|
+
rmSync(dir, { recursive: true, force: true });
|
|
2212
|
+
} catch {}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2177
2215
|
async function deleteBranchDetachingWorktrees(g, repoPath, branch) {
|
|
2178
2216
|
for (const holder of await worktreesOnBranch(g, branch, repoPath)) {
|
|
2179
2217
|
try {
|
|
@@ -2379,7 +2417,7 @@ var init_git = __esm(() => {
|
|
|
2379
2417
|
|
|
2380
2418
|
// src/services/knowledge.ts
|
|
2381
2419
|
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync } from "fs";
|
|
2382
|
-
import { join as
|
|
2420
|
+
import { join as join7, relative, resolve as resolve4, sep } from "path";
|
|
2383
2421
|
function scaffoldDocs(ctx, feature) {
|
|
2384
2422
|
const project = projectForFeature(ctx, feature);
|
|
2385
2423
|
const dir = featureDocsDir(project, feature);
|
|
@@ -2434,7 +2472,7 @@ function listDocs(ctx, feature) {
|
|
|
2434
2472
|
const dir = featureDocsDir(project, feature);
|
|
2435
2473
|
if (!existsSync8(dir))
|
|
2436
2474
|
return [];
|
|
2437
|
-
return readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => ({ relPath: e.name, title: readTitle(
|
|
2475
|
+
return readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => ({ relPath: e.name, title: readTitle(join7(dir, e.name), e.name) })).sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
2438
2476
|
}
|
|
2439
2477
|
function readDoc(ctx, feature, relPath) {
|
|
2440
2478
|
const project = projectForFeature(ctx, feature);
|
|
@@ -2475,21 +2513,21 @@ var init_knowledge = __esm(() => {
|
|
|
2475
2513
|
|
|
2476
2514
|
// src/launcher/skills-root.ts
|
|
2477
2515
|
import { existsSync as existsSync9 } from "fs";
|
|
2478
|
-
import { dirname as dirname2, join as
|
|
2516
|
+
import { dirname as dirname2, join as join8, resolve as resolve5 } from "path";
|
|
2479
2517
|
function resolveSkillsRoot(fromDir) {
|
|
2480
2518
|
const override = process.env[SKILLS_DIR_ENV];
|
|
2481
2519
|
if (override) {
|
|
2482
2520
|
const root = resolve5(override);
|
|
2483
|
-
if (existsSync9(
|
|
2521
|
+
if (existsSync9(join8(root, SKILLS_MARKER)))
|
|
2484
2522
|
return root;
|
|
2485
2523
|
throw new Error(`${SKILLS_DIR_ENV}=${override} is not a runcastle skills root (missing ${SKILLS_MARKER})`);
|
|
2486
2524
|
}
|
|
2487
2525
|
const searched = [];
|
|
2488
2526
|
let dir = fromDir;
|
|
2489
2527
|
for (let i = 0;i < 8; i += 1) {
|
|
2490
|
-
const candidate =
|
|
2528
|
+
const candidate = join8(dir, "packages", "skills");
|
|
2491
2529
|
searched.push(candidate);
|
|
2492
|
-
if (existsSync9(
|
|
2530
|
+
if (existsSync9(join8(candidate, SKILLS_MARKER)))
|
|
2493
2531
|
return candidate;
|
|
2494
2532
|
const parent = dirname2(dir);
|
|
2495
2533
|
if (parent === dir)
|
|
@@ -2502,7 +2540,7 @@ function resolveSkillsRoot(fromDir) {
|
|
|
2502
2540
|
}
|
|
2503
2541
|
var SKILLS_DIR_ENV = "RUNCASTLE_SKILLS_DIR", SKILLS_MARKER;
|
|
2504
2542
|
var init_skills_root = __esm(() => {
|
|
2505
|
-
SKILLS_MARKER =
|
|
2543
|
+
SKILLS_MARKER = join8("packs", "runcastle");
|
|
2506
2544
|
});
|
|
2507
2545
|
|
|
2508
2546
|
// src/services/agent-stream.ts
|
|
@@ -2547,7 +2585,7 @@ var init_agent_stream = __esm(() => {
|
|
|
2547
2585
|
|
|
2548
2586
|
// src/workflows/ticket-burner.ts
|
|
2549
2587
|
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
|
|
2550
|
-
import { dirname as dirname3, join as
|
|
2588
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
2551
2589
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2552
2590
|
import { claudeCode, run } from "@ai-hero/sandcastle";
|
|
2553
2591
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
@@ -2704,9 +2742,9 @@ function resolveBurnWorkspaceMode(config2, platform = process.platform) {
|
|
|
2704
2742
|
function buildIsolatedSetupCommand(tempBranch, setupCommand) {
|
|
2705
2743
|
const hookFile = `${ISOLATED_REPO_PATH}/.git/hooks/post-commit`;
|
|
2706
2744
|
const parts = [
|
|
2707
|
-
`git
|
|
2745
|
+
`git config --global --add safe.directory '*'`,
|
|
2708
2746
|
`git clone ${SANDBOX_WORKSPACE_PATH} ${ISOLATED_REPO_PATH}`,
|
|
2709
|
-
`printf '#!/bin/sh\\
|
|
2747
|
+
`printf '#!/bin/sh\\nunset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE\\ngit push --quiet origin HEAD:%s && exec git -C ${SANDBOX_WORKSPACE_PATH} reset --hard --quiet %s\\n' '${tempBranch}' '${tempBranch}' > ${hookFile}`,
|
|
2710
2748
|
`chmod +x ${hookFile}`
|
|
2711
2749
|
];
|
|
2712
2750
|
if (setupCommand)
|
|
@@ -2727,7 +2765,7 @@ function buildWorkspaceNotes(mode) {
|
|
|
2727
2765
|
`);
|
|
2728
2766
|
}
|
|
2729
2767
|
function readRepoToolchain(repoPath) {
|
|
2730
|
-
const pkgPath =
|
|
2768
|
+
const pkgPath = join9(repoPath, "package.json");
|
|
2731
2769
|
const hasPackageJson = existsSync10(pkgPath);
|
|
2732
2770
|
let packageManagerField;
|
|
2733
2771
|
if (hasPackageJson) {
|
|
@@ -2741,10 +2779,10 @@ function readRepoToolchain(repoPath) {
|
|
|
2741
2779
|
hasPackageJson,
|
|
2742
2780
|
...packageManagerField !== undefined ? { packageManagerField } : {},
|
|
2743
2781
|
lockfiles: {
|
|
2744
|
-
bun: existsSync10(
|
|
2745
|
-
pnpm: existsSync10(
|
|
2746
|
-
yarn: existsSync10(
|
|
2747
|
-
npm: existsSync10(
|
|
2782
|
+
bun: existsSync10(join9(repoPath, "bun.lock")) || existsSync10(join9(repoPath, "bun.lockb")),
|
|
2783
|
+
pnpm: existsSync10(join9(repoPath, "pnpm-lock.yaml")),
|
|
2784
|
+
yarn: existsSync10(join9(repoPath, "yarn.lock")),
|
|
2785
|
+
npm: existsSync10(join9(repoPath, "package-lock.json"))
|
|
2748
2786
|
}
|
|
2749
2787
|
};
|
|
2750
2788
|
}
|
|
@@ -2954,23 +2992,23 @@ async function burnRun(ctx, deps) {
|
|
|
2954
2992
|
}
|
|
2955
2993
|
function burnerTemplatePath() {
|
|
2956
2994
|
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
2957
|
-
return
|
|
2995
|
+
return join9(resolveSkillsRoot(here), "burner", "implement-ticket.md");
|
|
2958
2996
|
}
|
|
2959
2997
|
function readDocsDigestFromDisk(projectId, slug) {
|
|
2960
2998
|
const worktree = worktreeDir(projectId, slug);
|
|
2961
2999
|
if (!existsSync10(worktree))
|
|
2962
3000
|
return "_No talk worktree on disk \u2014 docs digest skipped._";
|
|
2963
|
-
const docsDir =
|
|
3001
|
+
const docsDir = join9(worktree, ...featureDocsRel(slug).split("/"));
|
|
2964
3002
|
if (!existsSync10(docsDir))
|
|
2965
3003
|
return "_No docs/features dir in the talk worktree \u2014 docs digest skipped._";
|
|
2966
|
-
const files = readdirSync3(docsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name)).map((e) => ({ name: e.name, content: readFileSync4(
|
|
3004
|
+
const files = readdirSync3(docsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name)).map((e) => ({ name: e.name, content: readFileSync4(join9(docsDir, e.name), "utf8") }));
|
|
2967
3005
|
return buildDocsDigest(files);
|
|
2968
3006
|
}
|
|
2969
3007
|
function readBlockedFile(dirs) {
|
|
2970
3008
|
for (const dir of dirs) {
|
|
2971
3009
|
if (!dir)
|
|
2972
3010
|
continue;
|
|
2973
|
-
const p =
|
|
3011
|
+
const p = join9(dir, "BLOCKED.md");
|
|
2974
3012
|
if (existsSync10(p)) {
|
|
2975
3013
|
try {
|
|
2976
3014
|
return readFileSync4(p, "utf8");
|
|
@@ -3013,10 +3051,12 @@ function selectSandbox(config2, mounts = []) {
|
|
|
3013
3051
|
return noSandbox();
|
|
3014
3052
|
}
|
|
3015
3053
|
}
|
|
3016
|
-
async function realExecuteTicketRun(ctx, ticket, config2, token, model, land) {
|
|
3054
|
+
async function realExecuteTicketRun(ctx, ticket, config2, token, model, land, ensureIsolatedPushTarget) {
|
|
3017
3055
|
const { project, feature } = ctx;
|
|
3018
3056
|
const tempBranch = ticketBranchName(feature.slug, ticket.seq, newId("b").slice(2, 10));
|
|
3019
3057
|
const workspaceMode = resolveBurnWorkspaceMode(config2);
|
|
3058
|
+
if (workspaceMode === "isolated")
|
|
3059
|
+
await ensureIsolatedPushTarget();
|
|
3020
3060
|
const template = readFileSync4(burnerTemplatePath(), "utf8");
|
|
3021
3061
|
const prompt = renderTicketPrompt(template, {
|
|
3022
3062
|
TICKET_JSON: buildTicketJson(ticket),
|
|
@@ -3045,7 +3085,7 @@ async function realExecuteTicketRun(ctx, ticket, config2, token, model, land) {
|
|
|
3045
3085
|
});
|
|
3046
3086
|
}
|
|
3047
3087
|
mkdirSync4(logsDir(), { recursive: true });
|
|
3048
|
-
const logFilePath =
|
|
3088
|
+
const logFilePath = join9(logsDir(), `burn-${feature.id}-${ticket.seq}.log`);
|
|
3049
3089
|
const throttle = createStreamThrottle((e) => ctx.emitEvent({ ...e, ticketId: ticket.id }));
|
|
3050
3090
|
beginTranscript(ticket.id);
|
|
3051
3091
|
const onStreamEvent = (event) => {
|
|
@@ -3130,11 +3170,13 @@ function resolveBurnDeps(ctx) {
|
|
|
3130
3170
|
const token = readTokenFromEnvFile(envPath());
|
|
3131
3171
|
const model = resolveModel("implement", config2, ctx.project, ctx.modelOverride);
|
|
3132
3172
|
const land = createSerialQueue();
|
|
3173
|
+
let pushTargetReady;
|
|
3174
|
+
const ensureIsolatedPushTarget = () => pushTargetReady ??= allowPushToCheckedOutBranches(ctx.project.repoPath);
|
|
3133
3175
|
return {
|
|
3134
3176
|
config: config2,
|
|
3135
3177
|
hasAuthToken: token !== undefined,
|
|
3136
3178
|
concurrency: config2.burnConcurrency,
|
|
3137
|
-
executeTicketRun: (c, ticket) => realExecuteTicketRun(c, ticket, config2, token, model, land)
|
|
3179
|
+
executeTicketRun: (c, ticket) => realExecuteTicketRun(c, ticket, config2, token, model, land, ensureIsolatedPushTarget)
|
|
3138
3180
|
};
|
|
3139
3181
|
}
|
|
3140
3182
|
function readTokenFromEnvFile(path) {
|
|
@@ -3179,7 +3221,7 @@ var init_ticket_burner = __esm(() => {
|
|
|
3179
3221
|
|
|
3180
3222
|
// src/workflows/research.ts
|
|
3181
3223
|
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "fs";
|
|
3182
|
-
import { dirname as dirname4, join as
|
|
3224
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
3183
3225
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3184
3226
|
import { run as run2 } from "@ai-hero/sandcastle";
|
|
3185
3227
|
import { docker as docker2 } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
@@ -3251,16 +3293,16 @@ function interpretResearchResult(result, docRelPath) {
|
|
|
3251
3293
|
}
|
|
3252
3294
|
function researchTemplatePath() {
|
|
3253
3295
|
const here = dirname4(fileURLToPath3(import.meta.url));
|
|
3254
|
-
return
|
|
3296
|
+
return join10(resolveSkillsRoot(here), "burner", "research-waypoint.md");
|
|
3255
3297
|
}
|
|
3256
3298
|
function readDocsDigestFromDisk2(projectId, slug) {
|
|
3257
3299
|
const worktree = worktreeDir(projectId, slug);
|
|
3258
3300
|
if (!existsSync11(worktree))
|
|
3259
3301
|
return "_No talk worktree on disk \u2014 docs digest skipped._";
|
|
3260
|
-
const docsDir =
|
|
3302
|
+
const docsDir = join10(worktree, ...featureDocsRel(slug).split("/"));
|
|
3261
3303
|
if (!existsSync11(docsDir))
|
|
3262
3304
|
return "_No docs/features dir in the talk worktree \u2014 docs digest skipped._";
|
|
3263
|
-
const files = readdirSync4(docsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name)).map((e) => ({ name: e.name, content: readFileSync5(
|
|
3305
|
+
const files = readdirSync4(docsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name)).map((e) => ({ name: e.name, content: readFileSync5(join10(docsDir, e.name), "utf8") }));
|
|
3264
3306
|
return buildDocsDigest(files);
|
|
3265
3307
|
}
|
|
3266
3308
|
async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
@@ -3275,7 +3317,7 @@ async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
|
3275
3317
|
RESEARCH_DOC_PATH: docRelPath
|
|
3276
3318
|
});
|
|
3277
3319
|
mkdirSync5(logsDir(), { recursive: true });
|
|
3278
|
-
const logFilePath =
|
|
3320
|
+
const logFilePath = join10(logsDir(), `research-${feature.id}-${waypoint.seq}.log`);
|
|
3279
3321
|
const throttle = createResearchStreamThrottle((e) => ctx.emitEvent(e));
|
|
3280
3322
|
const runOptions = {
|
|
3281
3323
|
agent: buildBurnAgent(config2, token, model),
|
|
@@ -4136,7 +4178,7 @@ var init_hooks = __esm(() => {
|
|
|
4136
4178
|
// src/routes/web.ts
|
|
4137
4179
|
import { existsSync as existsSync12, statSync } from "fs";
|
|
4138
4180
|
import { readFile } from "fs/promises";
|
|
4139
|
-
import { extname, join as
|
|
4181
|
+
import { extname, join as join11, normalize, resolve as resolve6, sep as sep2 } from "path";
|
|
4140
4182
|
function contentType(path) {
|
|
4141
4183
|
return MIME[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
4142
4184
|
}
|
|
@@ -4154,7 +4196,7 @@ function safeJoin(root, pathname) {
|
|
|
4154
4196
|
} catch {
|
|
4155
4197
|
return null;
|
|
4156
4198
|
}
|
|
4157
|
-
const joined = normalize(
|
|
4199
|
+
const joined = normalize(join11(root, decoded));
|
|
4158
4200
|
if (joined !== root && !joined.startsWith(root + sep2))
|
|
4159
4201
|
return null;
|
|
4160
4202
|
return joined;
|
|
@@ -4167,7 +4209,7 @@ function resolveWebDist() {
|
|
|
4167
4209
|
}
|
|
4168
4210
|
function mountWebApp(app, distDir) {
|
|
4169
4211
|
const root = resolve6(distDir);
|
|
4170
|
-
const indexHtml =
|
|
4212
|
+
const indexHtml = join11(root, "index.html");
|
|
4171
4213
|
app.get("*", async (c, next) => {
|
|
4172
4214
|
const { pathname } = new URL(c.req.url);
|
|
4173
4215
|
if (/^\/(api|mcp|ws|health)(\/|$)/.test(pathname))
|
|
@@ -4187,7 +4229,7 @@ function mountWebApp(app, distDir) {
|
|
|
4187
4229
|
}
|
|
4188
4230
|
function mountWebAppIfBuilt(app) {
|
|
4189
4231
|
const dist = resolveWebDist();
|
|
4190
|
-
if (existsSync12(
|
|
4232
|
+
if (existsSync12(join11(dist, "index.html")))
|
|
4191
4233
|
mountWebApp(app, dist);
|
|
4192
4234
|
}
|
|
4193
4235
|
var MIME;
|
|
@@ -4263,7 +4305,7 @@ var init_events2 = __esm(() => {
|
|
|
4263
4305
|
// src/launcher/artifacts.ts
|
|
4264
4306
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
4265
4307
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4266
|
-
import { join as
|
|
4308
|
+
import { join as join12 } from "path";
|
|
4267
4309
|
function hookClientPath() {
|
|
4268
4310
|
return resolveAsset(ASSET_ENV.hookClient, fileURLToPath4(new URL("./hook-client.ts", import.meta.url)));
|
|
4269
4311
|
}
|
|
@@ -4475,9 +4517,9 @@ async function writeSessionArtifacts(input) {
|
|
|
4475
4517
|
const { session, feature, config: config2, waypoint } = input;
|
|
4476
4518
|
const dir = sessionDir(session.id);
|
|
4477
4519
|
mkdirSync6(dir, { recursive: true });
|
|
4478
|
-
const systemPromptPath =
|
|
4479
|
-
const settingsPath =
|
|
4480
|
-
const mcpConfigPath =
|
|
4520
|
+
const systemPromptPath = join12(dir, "system-prompt.md");
|
|
4521
|
+
const settingsPath = join12(dir, "settings.json");
|
|
4522
|
+
const mcpConfigPath = join12(dir, "mcp.json");
|
|
4481
4523
|
writeFileSync2(systemPromptPath, renderSystemPrompt(feature, session.kind, waypoint), "utf8");
|
|
4482
4524
|
writeFileSync2(settingsPath, JSON.stringify(renderSettings(hookClientPath()), null, 2), "utf8");
|
|
4483
4525
|
writeFileSync2(mcpConfigPath, JSON.stringify(renderMcpConfig(session, config2), null, 2), "utf8");
|
|
@@ -4539,7 +4581,7 @@ var init_end_session = __esm(() => {
|
|
|
4539
4581
|
|
|
4540
4582
|
// src/launcher/launcher.ts
|
|
4541
4583
|
import { existsSync as existsSync13 } from "fs";
|
|
4542
|
-
import { dirname as dirname5, join as
|
|
4584
|
+
import { dirname as dirname5, join as join13, resolve as resolve7 } from "path";
|
|
4543
4585
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
4544
4586
|
import { and as and5, eq as eq8 } from "drizzle-orm";
|
|
4545
4587
|
function ptyExitMessage(exitCode) {
|
|
@@ -4577,10 +4619,10 @@ function claudeSpawnTarget(claudeArgs) {
|
|
|
4577
4619
|
return { file: exe, args: claudeArgs };
|
|
4578
4620
|
}
|
|
4579
4621
|
function resolvePluginDir(fromDir = dirname5(fileURLToPath5(import.meta.url))) {
|
|
4580
|
-
const rel =
|
|
4622
|
+
const rel = join13("packages", "skills", "packs", "runcastle");
|
|
4581
4623
|
const override = process.env[SKILLS_DIR_ENV];
|
|
4582
4624
|
if (override) {
|
|
4583
|
-
const dir2 =
|
|
4625
|
+
const dir2 = join13(resolve7(override), "packs", "runcastle");
|
|
4584
4626
|
if (existsSync13(dir2))
|
|
4585
4627
|
return dir2;
|
|
4586
4628
|
throw new Error(`${SKILLS_DIR_ENV}=${override} has no plugin dir at ${dir2}`);
|
|
@@ -4588,7 +4630,7 @@ function resolvePluginDir(fromDir = dirname5(fileURLToPath5(import.meta.url))) {
|
|
|
4588
4630
|
const searched = [];
|
|
4589
4631
|
let dir = fromDir;
|
|
4590
4632
|
for (let i = 0;i < 8; i += 1) {
|
|
4591
|
-
const candidate =
|
|
4633
|
+
const candidate = join13(dir, rel);
|
|
4592
4634
|
searched.push(candidate);
|
|
4593
4635
|
if (existsSync13(candidate))
|
|
4594
4636
|
return candidate;
|
|
@@ -4942,7 +4984,7 @@ var init_feature = __esm(() => {
|
|
|
4942
4984
|
|
|
4943
4985
|
// src/services/projects.ts
|
|
4944
4986
|
import { existsSync as existsSync14 } from "fs";
|
|
4945
|
-
import { basename, join as
|
|
4987
|
+
import { basename, join as join14 } from "path";
|
|
4946
4988
|
import { eq as eq9, isNull } from "drizzle-orm";
|
|
4947
4989
|
function listProjects(ctx) {
|
|
4948
4990
|
return ctx.db.select().from(projects).where(isNull(projects.closedAt)).all().map(rowToProject);
|
|
@@ -5018,7 +5060,7 @@ async function assertRepoTolerant(ctx, repoPath) {
|
|
|
5018
5060
|
} catch (e) {
|
|
5019
5061
|
if (!isNotImplemented(e))
|
|
5020
5062
|
throw e;
|
|
5021
|
-
if (!existsSync14(
|
|
5063
|
+
if (!existsSync14(join14(repoPath, ".git"))) {
|
|
5022
5064
|
throw new InvalidInputError(`not a git repository: ${repoPath}`);
|
|
5023
5065
|
}
|
|
5024
5066
|
}
|
|
@@ -5545,7 +5587,7 @@ var init_system_exec = __esm(() => {
|
|
|
5545
5587
|
|
|
5546
5588
|
// src/services/setup.ts
|
|
5547
5589
|
import { copyFileSync, existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
5548
|
-
import { dirname as dirname7, join as
|
|
5590
|
+
import { dirname as dirname7, join as join15 } from "path";
|
|
5549
5591
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
5550
5592
|
async function writeGitIdentity(exec, identity) {
|
|
5551
5593
|
const name = identity.name.trim();
|
|
@@ -5623,11 +5665,11 @@ function resolveSandcastleBin() {
|
|
|
5623
5665
|
const mainUrl = import.meta.resolve("@ai-hero/sandcastle");
|
|
5624
5666
|
let dir = dirname7(fileURLToPath6(mainUrl));
|
|
5625
5667
|
for (let hops = 0;hops < 6; hops++) {
|
|
5626
|
-
const manifestPath =
|
|
5668
|
+
const manifestPath = join15(dir, "package.json");
|
|
5627
5669
|
if (existsSync16(manifestPath)) {
|
|
5628
5670
|
const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
|
|
5629
5671
|
if (manifest.name === "@ai-hero/sandcastle" && manifest.bin?.sandcastle) {
|
|
5630
|
-
return
|
|
5672
|
+
return join15(dir, manifest.bin.sandcastle);
|
|
5631
5673
|
}
|
|
5632
5674
|
}
|
|
5633
5675
|
const parent = dirname7(dir);
|
|
@@ -5655,12 +5697,12 @@ function sandcastleTemplateDir() {
|
|
|
5655
5697
|
return resolveAsset(ASSET_ENV.sandcastleTemplate, fileURLToPath6(new URL("../assets/sandcastle", import.meta.url)));
|
|
5656
5698
|
}
|
|
5657
5699
|
function scaffoldSandcastleConfig(templateDir, targetDir) {
|
|
5658
|
-
const dir =
|
|
5700
|
+
const dir = join15(targetDir, ".sandcastle");
|
|
5659
5701
|
if (existsSync16(dir))
|
|
5660
5702
|
return { scaffolded: false, dir };
|
|
5661
5703
|
mkdirSync8(dir, { recursive: true });
|
|
5662
5704
|
for (const name of readdirSync5(templateDir)) {
|
|
5663
|
-
copyFileSync(
|
|
5705
|
+
copyFileSync(join15(templateDir, name), join15(dir, name));
|
|
5664
5706
|
}
|
|
5665
5707
|
return { scaffolded: true, dir };
|
|
5666
5708
|
}
|
|
@@ -5824,13 +5866,13 @@ var init_update_check = __esm(() => {
|
|
|
5824
5866
|
|
|
5825
5867
|
// src/version.ts
|
|
5826
5868
|
import { readFileSync as readFileSync8 } from "fs";
|
|
5827
|
-
import { dirname as dirname8, join as
|
|
5869
|
+
import { dirname as dirname8, join as join16 } from "path";
|
|
5828
5870
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
5829
5871
|
function runcastleVersion() {
|
|
5830
5872
|
if (cached2)
|
|
5831
5873
|
return cached2;
|
|
5832
5874
|
const here = dirname8(fileURLToPath7(import.meta.url));
|
|
5833
|
-
for (const pkgPath of [
|
|
5875
|
+
for (const pkgPath of [join16(here, "..", "package.json"), join16(here, "package.json")]) {
|
|
5834
5876
|
try {
|
|
5835
5877
|
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
5836
5878
|
if (typeof pkg.version === "string") {
|