runcastle 1.0.11 → 1.0.13
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 +93 -53
- package/index.js +93 -53
- 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}`;
|
|
@@ -2140,7 +2141,7 @@ function ticketBranchName(slug, ticketSeq, unique) {
|
|
|
2140
2141
|
return `${TICKET_BRANCH_PREFIX}${tempBranchSlugSegment(slug)}/${ticketSeq}-${unique}`;
|
|
2141
2142
|
}
|
|
2142
2143
|
async function allowPushToCheckedOutBranches(repoPath) {
|
|
2143
|
-
await git(repoPath).addConfig("receive.denyCurrentBranch", "
|
|
2144
|
+
await git(repoPath).addConfig("receive.denyCurrentBranch", "ignore");
|
|
2144
2145
|
}
|
|
2145
2146
|
async function mergeTempBranch(repoPath, featureBranchName, tempBranch) {
|
|
2146
2147
|
const g = git(repoPath);
|
|
@@ -2171,13 +2172,47 @@ async function mergeTempBranch(repoPath, featureBranchName, tempBranch) {
|
|
|
2171
2172
|
} else {
|
|
2172
2173
|
try {
|
|
2173
2174
|
await g.raw(["fetch", ".", `${tempBranch}:${featureBranchName}`]);
|
|
2174
|
-
} catch
|
|
2175
|
-
|
|
2175
|
+
} catch {
|
|
2176
|
+
const merged = await mergeInDisposableWorktree(g, featureBranchName, tempBranch);
|
|
2177
|
+
if (!merged.ok)
|
|
2178
|
+
return merged;
|
|
2176
2179
|
}
|
|
2177
2180
|
}
|
|
2178
2181
|
await deleteBranchDetachingWorktrees(g, repoPath, tempBranch);
|
|
2179
2182
|
return { ok: true };
|
|
2180
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
|
+
}
|
|
2181
2216
|
async function deleteBranchDetachingWorktrees(g, repoPath, branch) {
|
|
2182
2217
|
for (const holder of await worktreesOnBranch(g, branch, repoPath)) {
|
|
2183
2218
|
try {
|
|
@@ -2383,7 +2418,7 @@ var init_git = __esm(() => {
|
|
|
2383
2418
|
|
|
2384
2419
|
// src/services/knowledge.ts
|
|
2385
2420
|
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync } from "fs";
|
|
2386
|
-
import { join as
|
|
2421
|
+
import { join as join7, relative, resolve as resolve4, sep } from "path";
|
|
2387
2422
|
function scaffoldDocs(ctx, feature) {
|
|
2388
2423
|
const project = projectForFeature(ctx, feature);
|
|
2389
2424
|
const dir = featureDocsDir(project, feature);
|
|
@@ -2438,7 +2473,7 @@ function listDocs(ctx, feature) {
|
|
|
2438
2473
|
const dir = featureDocsDir(project, feature);
|
|
2439
2474
|
if (!existsSync8(dir))
|
|
2440
2475
|
return [];
|
|
2441
|
-
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));
|
|
2442
2477
|
}
|
|
2443
2478
|
function readDoc(ctx, feature, relPath) {
|
|
2444
2479
|
const project = projectForFeature(ctx, feature);
|
|
@@ -2479,21 +2514,21 @@ var init_knowledge = __esm(() => {
|
|
|
2479
2514
|
|
|
2480
2515
|
// src/launcher/skills-root.ts
|
|
2481
2516
|
import { existsSync as existsSync9 } from "fs";
|
|
2482
|
-
import { dirname as dirname2, join as
|
|
2517
|
+
import { dirname as dirname2, join as join8, resolve as resolve5 } from "path";
|
|
2483
2518
|
function resolveSkillsRoot(fromDir) {
|
|
2484
2519
|
const override = process.env[SKILLS_DIR_ENV];
|
|
2485
2520
|
if (override) {
|
|
2486
2521
|
const root = resolve5(override);
|
|
2487
|
-
if (existsSync9(
|
|
2522
|
+
if (existsSync9(join8(root, SKILLS_MARKER)))
|
|
2488
2523
|
return root;
|
|
2489
2524
|
throw new Error(`${SKILLS_DIR_ENV}=${override} is not a runcastle skills root (missing ${SKILLS_MARKER})`);
|
|
2490
2525
|
}
|
|
2491
2526
|
const searched = [];
|
|
2492
2527
|
let dir = fromDir;
|
|
2493
2528
|
for (let i = 0;i < 8; i += 1) {
|
|
2494
|
-
const candidate =
|
|
2529
|
+
const candidate = join8(dir, "packages", "skills");
|
|
2495
2530
|
searched.push(candidate);
|
|
2496
|
-
if (existsSync9(
|
|
2531
|
+
if (existsSync9(join8(candidate, SKILLS_MARKER)))
|
|
2497
2532
|
return candidate;
|
|
2498
2533
|
const parent = dirname2(dir);
|
|
2499
2534
|
if (parent === dir)
|
|
@@ -2506,7 +2541,7 @@ function resolveSkillsRoot(fromDir) {
|
|
|
2506
2541
|
}
|
|
2507
2542
|
var SKILLS_DIR_ENV = "RUNCASTLE_SKILLS_DIR", SKILLS_MARKER;
|
|
2508
2543
|
var init_skills_root = __esm(() => {
|
|
2509
|
-
SKILLS_MARKER =
|
|
2544
|
+
SKILLS_MARKER = join8("packs", "runcastle");
|
|
2510
2545
|
});
|
|
2511
2546
|
|
|
2512
2547
|
// src/services/agent-stream.ts
|
|
@@ -2551,7 +2586,7 @@ var init_agent_stream = __esm(() => {
|
|
|
2551
2586
|
|
|
2552
2587
|
// src/workflows/ticket-burner.ts
|
|
2553
2588
|
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
|
|
2554
|
-
import { dirname as dirname3, join as
|
|
2589
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
2555
2590
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2556
2591
|
import { claudeCode, run } from "@ai-hero/sandcastle";
|
|
2557
2592
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
@@ -2705,16 +2740,21 @@ function resolveBurnWorkspaceMode(config2, platform = process.platform) {
|
|
|
2705
2740
|
return platform === "linux" ? "mounted" : "isolated";
|
|
2706
2741
|
return config2.burnWorkspace;
|
|
2707
2742
|
}
|
|
2708
|
-
function buildIsolatedSetupCommand(tempBranch, setupCommand) {
|
|
2743
|
+
function buildIsolatedSetupCommand(tempBranch, setupCommand, pm) {
|
|
2709
2744
|
const hookFile = `${ISOLATED_REPO_PATH}/.git/hooks/post-commit`;
|
|
2710
2745
|
const parts = [
|
|
2711
2746
|
`git config --global --add safe.directory '*'`,
|
|
2712
2747
|
`git clone ${SANDBOX_WORKSPACE_PATH} ${ISOLATED_REPO_PATH}`,
|
|
2713
|
-
`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}`,
|
|
2714
2749
|
`chmod +x ${hookFile}`
|
|
2715
2750
|
];
|
|
2751
|
+
if (pm === "pnpm" || pm === "yarn") {
|
|
2752
|
+
const shim = `$HOME/.local/bin/${pm}`;
|
|
2753
|
+
parts.push(`mkdir -p "$HOME/.local/bin"`, `printf '#!/bin/sh\\nexec corepack ${pm} "$@"\\n' > "${shim}"`, `chmod +x "${shim}"`);
|
|
2754
|
+
}
|
|
2716
2755
|
if (setupCommand)
|
|
2717
2756
|
parts.push(`cd ${ISOLATED_REPO_PATH} && ${setupCommand}`);
|
|
2757
|
+
parts.push(`git -C ${ISOLATED_REPO_PATH} config core.hooksPath ${ISOLATED_REPO_PATH}/.git/hooks`);
|
|
2718
2758
|
return parts.join(" && ");
|
|
2719
2759
|
}
|
|
2720
2760
|
function buildWorkspaceNotes(mode) {
|
|
@@ -2724,14 +2764,14 @@ function buildWorkspaceNotes(mode) {
|
|
|
2724
2764
|
return [
|
|
2725
2765
|
`Your working repository is \`${ISOLATED_REPO_PATH}\` \u2014 a clone on the container's fast native filesystem, with dependencies already installed. Do ALL work there: \`cd ${ISOLATED_REPO_PATH}\` first; every file you read, edit, test, and commit lives under it.`,
|
|
2726
2766
|
"",
|
|
2727
|
-
`The directory you start in (\`${SANDBOX_WORKSPACE_PATH}\`) is a slow mounted mirror used only to collect your commits \u2014 never edit files, install, or run tests there. Your commits sync back automatically (a post-commit hook pushes them); just commit as normal.`,
|
|
2767
|
+
`The directory you start in (\`${SANDBOX_WORKSPACE_PATH}\`) is a slow mounted mirror used only to collect your commits \u2014 never edit files, install, or run tests there. Your commits sync back automatically (a post-commit hook pushes them); just commit as normal. If you re-run the dependency install and it reconfigures git hooks (husky), run \`git -C ${ISOLATED_REPO_PATH} config core.hooksPath ${ISOLATED_REPO_PATH}/.git/hooks\` afterwards so the sync hook stays armed.`,
|
|
2728
2768
|
"",
|
|
2729
2769
|
`If you are blocked and write \`BLOCKED.md\`, write it at \`${ISOLATED_REPO_PATH}/BLOCKED.md\` AND copy it to \`${SANDBOX_WORKSPACE_PATH}/BLOCKED.md\` so the orchestrator can see it.`
|
|
2730
2770
|
].join(`
|
|
2731
2771
|
`);
|
|
2732
2772
|
}
|
|
2733
2773
|
function readRepoToolchain(repoPath) {
|
|
2734
|
-
const pkgPath =
|
|
2774
|
+
const pkgPath = join9(repoPath, "package.json");
|
|
2735
2775
|
const hasPackageJson = existsSync10(pkgPath);
|
|
2736
2776
|
let packageManagerField;
|
|
2737
2777
|
if (hasPackageJson) {
|
|
@@ -2745,10 +2785,10 @@ function readRepoToolchain(repoPath) {
|
|
|
2745
2785
|
hasPackageJson,
|
|
2746
2786
|
...packageManagerField !== undefined ? { packageManagerField } : {},
|
|
2747
2787
|
lockfiles: {
|
|
2748
|
-
bun: existsSync10(
|
|
2749
|
-
pnpm: existsSync10(
|
|
2750
|
-
yarn: existsSync10(
|
|
2751
|
-
npm: existsSync10(
|
|
2788
|
+
bun: existsSync10(join9(repoPath, "bun.lock")) || existsSync10(join9(repoPath, "bun.lockb")),
|
|
2789
|
+
pnpm: existsSync10(join9(repoPath, "pnpm-lock.yaml")),
|
|
2790
|
+
yarn: existsSync10(join9(repoPath, "yarn.lock")),
|
|
2791
|
+
npm: existsSync10(join9(repoPath, "package-lock.json"))
|
|
2752
2792
|
}
|
|
2753
2793
|
};
|
|
2754
2794
|
}
|
|
@@ -2958,23 +2998,23 @@ async function burnRun(ctx, deps) {
|
|
|
2958
2998
|
}
|
|
2959
2999
|
function burnerTemplatePath() {
|
|
2960
3000
|
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
2961
|
-
return
|
|
3001
|
+
return join9(resolveSkillsRoot(here), "burner", "implement-ticket.md");
|
|
2962
3002
|
}
|
|
2963
3003
|
function readDocsDigestFromDisk(projectId, slug) {
|
|
2964
3004
|
const worktree = worktreeDir(projectId, slug);
|
|
2965
3005
|
if (!existsSync10(worktree))
|
|
2966
3006
|
return "_No talk worktree on disk \u2014 docs digest skipped._";
|
|
2967
|
-
const docsDir =
|
|
3007
|
+
const docsDir = join9(worktree, ...featureDocsRel(slug).split("/"));
|
|
2968
3008
|
if (!existsSync10(docsDir))
|
|
2969
3009
|
return "_No docs/features dir in the talk worktree \u2014 docs digest skipped._";
|
|
2970
|
-
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(
|
|
3010
|
+
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") }));
|
|
2971
3011
|
return buildDocsDigest(files);
|
|
2972
3012
|
}
|
|
2973
3013
|
function readBlockedFile(dirs) {
|
|
2974
3014
|
for (const dir of dirs) {
|
|
2975
3015
|
if (!dir)
|
|
2976
3016
|
continue;
|
|
2977
|
-
const p =
|
|
3017
|
+
const p = join9(dir, "BLOCKED.md");
|
|
2978
3018
|
if (existsSync10(p)) {
|
|
2979
3019
|
try {
|
|
2980
3020
|
return readFileSync4(p, "utf8");
|
|
@@ -3042,7 +3082,7 @@ async function realExecuteTicketRun(ctx, ticket, config2, token, model, land, en
|
|
|
3042
3082
|
mounts.push(mount);
|
|
3043
3083
|
}
|
|
3044
3084
|
}
|
|
3045
|
-
const hookCommand = workspaceMode === "isolated" ? buildIsolatedSetupCommand(tempBranch, setupCommand) : setupCommand;
|
|
3085
|
+
const hookCommand = workspaceMode === "isolated" ? buildIsolatedSetupCommand(tempBranch, setupCommand, pm) : setupCommand;
|
|
3046
3086
|
if (hookCommand) {
|
|
3047
3087
|
ctx.emitEvent({
|
|
3048
3088
|
type: "burn.setup",
|
|
@@ -3051,7 +3091,7 @@ async function realExecuteTicketRun(ctx, ticket, config2, token, model, land, en
|
|
|
3051
3091
|
});
|
|
3052
3092
|
}
|
|
3053
3093
|
mkdirSync4(logsDir(), { recursive: true });
|
|
3054
|
-
const logFilePath =
|
|
3094
|
+
const logFilePath = join9(logsDir(), `burn-${feature.id}-${ticket.seq}.log`);
|
|
3055
3095
|
const throttle = createStreamThrottle((e) => ctx.emitEvent({ ...e, ticketId: ticket.id }));
|
|
3056
3096
|
beginTranscript(ticket.id);
|
|
3057
3097
|
const onStreamEvent = (event) => {
|
|
@@ -3187,7 +3227,7 @@ var init_ticket_burner = __esm(() => {
|
|
|
3187
3227
|
|
|
3188
3228
|
// src/workflows/research.ts
|
|
3189
3229
|
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "fs";
|
|
3190
|
-
import { dirname as dirname4, join as
|
|
3230
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
3191
3231
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3192
3232
|
import { run as run2 } from "@ai-hero/sandcastle";
|
|
3193
3233
|
import { docker as docker2 } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
@@ -3259,16 +3299,16 @@ function interpretResearchResult(result, docRelPath) {
|
|
|
3259
3299
|
}
|
|
3260
3300
|
function researchTemplatePath() {
|
|
3261
3301
|
const here = dirname4(fileURLToPath3(import.meta.url));
|
|
3262
|
-
return
|
|
3302
|
+
return join10(resolveSkillsRoot(here), "burner", "research-waypoint.md");
|
|
3263
3303
|
}
|
|
3264
3304
|
function readDocsDigestFromDisk2(projectId, slug) {
|
|
3265
3305
|
const worktree = worktreeDir(projectId, slug);
|
|
3266
3306
|
if (!existsSync11(worktree))
|
|
3267
3307
|
return "_No talk worktree on disk \u2014 docs digest skipped._";
|
|
3268
|
-
const docsDir =
|
|
3308
|
+
const docsDir = join10(worktree, ...featureDocsRel(slug).split("/"));
|
|
3269
3309
|
if (!existsSync11(docsDir))
|
|
3270
3310
|
return "_No docs/features dir in the talk worktree \u2014 docs digest skipped._";
|
|
3271
|
-
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(
|
|
3311
|
+
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") }));
|
|
3272
3312
|
return buildDocsDigest(files);
|
|
3273
3313
|
}
|
|
3274
3314
|
async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
@@ -3283,7 +3323,7 @@ async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
|
3283
3323
|
RESEARCH_DOC_PATH: docRelPath
|
|
3284
3324
|
});
|
|
3285
3325
|
mkdirSync5(logsDir(), { recursive: true });
|
|
3286
|
-
const logFilePath =
|
|
3326
|
+
const logFilePath = join10(logsDir(), `research-${feature.id}-${waypoint.seq}.log`);
|
|
3287
3327
|
const throttle = createResearchStreamThrottle((e) => ctx.emitEvent(e));
|
|
3288
3328
|
const runOptions = {
|
|
3289
3329
|
agent: buildBurnAgent(config2, token, model),
|
|
@@ -4144,7 +4184,7 @@ var init_hooks = __esm(() => {
|
|
|
4144
4184
|
// src/routes/web.ts
|
|
4145
4185
|
import { existsSync as existsSync12, statSync } from "fs";
|
|
4146
4186
|
import { readFile } from "fs/promises";
|
|
4147
|
-
import { extname, join as
|
|
4187
|
+
import { extname, join as join11, normalize, resolve as resolve6, sep as sep2 } from "path";
|
|
4148
4188
|
function contentType(path) {
|
|
4149
4189
|
return MIME[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
4150
4190
|
}
|
|
@@ -4162,7 +4202,7 @@ function safeJoin(root, pathname) {
|
|
|
4162
4202
|
} catch {
|
|
4163
4203
|
return null;
|
|
4164
4204
|
}
|
|
4165
|
-
const joined = normalize(
|
|
4205
|
+
const joined = normalize(join11(root, decoded));
|
|
4166
4206
|
if (joined !== root && !joined.startsWith(root + sep2))
|
|
4167
4207
|
return null;
|
|
4168
4208
|
return joined;
|
|
@@ -4175,7 +4215,7 @@ function resolveWebDist() {
|
|
|
4175
4215
|
}
|
|
4176
4216
|
function mountWebApp(app, distDir) {
|
|
4177
4217
|
const root = resolve6(distDir);
|
|
4178
|
-
const indexHtml =
|
|
4218
|
+
const indexHtml = join11(root, "index.html");
|
|
4179
4219
|
app.get("*", async (c, next) => {
|
|
4180
4220
|
const { pathname } = new URL(c.req.url);
|
|
4181
4221
|
if (/^\/(api|mcp|ws|health)(\/|$)/.test(pathname))
|
|
@@ -4195,7 +4235,7 @@ function mountWebApp(app, distDir) {
|
|
|
4195
4235
|
}
|
|
4196
4236
|
function mountWebAppIfBuilt(app) {
|
|
4197
4237
|
const dist = resolveWebDist();
|
|
4198
|
-
if (existsSync12(
|
|
4238
|
+
if (existsSync12(join11(dist, "index.html")))
|
|
4199
4239
|
mountWebApp(app, dist);
|
|
4200
4240
|
}
|
|
4201
4241
|
var MIME;
|
|
@@ -4271,7 +4311,7 @@ var init_events2 = __esm(() => {
|
|
|
4271
4311
|
// src/launcher/artifacts.ts
|
|
4272
4312
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
4273
4313
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4274
|
-
import { join as
|
|
4314
|
+
import { join as join12 } from "path";
|
|
4275
4315
|
function hookClientPath() {
|
|
4276
4316
|
return resolveAsset(ASSET_ENV.hookClient, fileURLToPath4(new URL("./hook-client.ts", import.meta.url)));
|
|
4277
4317
|
}
|
|
@@ -4483,9 +4523,9 @@ async function writeSessionArtifacts(input) {
|
|
|
4483
4523
|
const { session, feature, config: config2, waypoint } = input;
|
|
4484
4524
|
const dir = sessionDir(session.id);
|
|
4485
4525
|
mkdirSync6(dir, { recursive: true });
|
|
4486
|
-
const systemPromptPath =
|
|
4487
|
-
const settingsPath =
|
|
4488
|
-
const mcpConfigPath =
|
|
4526
|
+
const systemPromptPath = join12(dir, "system-prompt.md");
|
|
4527
|
+
const settingsPath = join12(dir, "settings.json");
|
|
4528
|
+
const mcpConfigPath = join12(dir, "mcp.json");
|
|
4489
4529
|
writeFileSync2(systemPromptPath, renderSystemPrompt(feature, session.kind, waypoint), "utf8");
|
|
4490
4530
|
writeFileSync2(settingsPath, JSON.stringify(renderSettings(hookClientPath()), null, 2), "utf8");
|
|
4491
4531
|
writeFileSync2(mcpConfigPath, JSON.stringify(renderMcpConfig(session, config2), null, 2), "utf8");
|
|
@@ -4547,7 +4587,7 @@ var init_end_session = __esm(() => {
|
|
|
4547
4587
|
|
|
4548
4588
|
// src/launcher/launcher.ts
|
|
4549
4589
|
import { existsSync as existsSync13 } from "fs";
|
|
4550
|
-
import { dirname as dirname5, join as
|
|
4590
|
+
import { dirname as dirname5, join as join13, resolve as resolve7 } from "path";
|
|
4551
4591
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
4552
4592
|
import { and as and5, eq as eq8 } from "drizzle-orm";
|
|
4553
4593
|
function ptyExitMessage(exitCode) {
|
|
@@ -4585,10 +4625,10 @@ function claudeSpawnTarget(claudeArgs) {
|
|
|
4585
4625
|
return { file: exe, args: claudeArgs };
|
|
4586
4626
|
}
|
|
4587
4627
|
function resolvePluginDir(fromDir = dirname5(fileURLToPath5(import.meta.url))) {
|
|
4588
|
-
const rel =
|
|
4628
|
+
const rel = join13("packages", "skills", "packs", "runcastle");
|
|
4589
4629
|
const override = process.env[SKILLS_DIR_ENV];
|
|
4590
4630
|
if (override) {
|
|
4591
|
-
const dir2 =
|
|
4631
|
+
const dir2 = join13(resolve7(override), "packs", "runcastle");
|
|
4592
4632
|
if (existsSync13(dir2))
|
|
4593
4633
|
return dir2;
|
|
4594
4634
|
throw new Error(`${SKILLS_DIR_ENV}=${override} has no plugin dir at ${dir2}`);
|
|
@@ -4596,7 +4636,7 @@ function resolvePluginDir(fromDir = dirname5(fileURLToPath5(import.meta.url))) {
|
|
|
4596
4636
|
const searched = [];
|
|
4597
4637
|
let dir = fromDir;
|
|
4598
4638
|
for (let i = 0;i < 8; i += 1) {
|
|
4599
|
-
const candidate =
|
|
4639
|
+
const candidate = join13(dir, rel);
|
|
4600
4640
|
searched.push(candidate);
|
|
4601
4641
|
if (existsSync13(candidate))
|
|
4602
4642
|
return candidate;
|
|
@@ -4950,7 +4990,7 @@ var init_feature = __esm(() => {
|
|
|
4950
4990
|
|
|
4951
4991
|
// src/services/projects.ts
|
|
4952
4992
|
import { existsSync as existsSync14 } from "fs";
|
|
4953
|
-
import { basename, join as
|
|
4993
|
+
import { basename, join as join14 } from "path";
|
|
4954
4994
|
import { eq as eq9, isNull } from "drizzle-orm";
|
|
4955
4995
|
function listProjects(ctx) {
|
|
4956
4996
|
return ctx.db.select().from(projects).where(isNull(projects.closedAt)).all().map(rowToProject);
|
|
@@ -5026,7 +5066,7 @@ async function assertRepoTolerant(ctx, repoPath) {
|
|
|
5026
5066
|
} catch (e) {
|
|
5027
5067
|
if (!isNotImplemented(e))
|
|
5028
5068
|
throw e;
|
|
5029
|
-
if (!existsSync14(
|
|
5069
|
+
if (!existsSync14(join14(repoPath, ".git"))) {
|
|
5030
5070
|
throw new InvalidInputError(`not a git repository: ${repoPath}`);
|
|
5031
5071
|
}
|
|
5032
5072
|
}
|
|
@@ -5553,7 +5593,7 @@ var init_system_exec = __esm(() => {
|
|
|
5553
5593
|
|
|
5554
5594
|
// src/services/setup.ts
|
|
5555
5595
|
import { copyFileSync, existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
5556
|
-
import { dirname as dirname7, join as
|
|
5596
|
+
import { dirname as dirname7, join as join15 } from "path";
|
|
5557
5597
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
5558
5598
|
async function writeGitIdentity(exec, identity) {
|
|
5559
5599
|
const name = identity.name.trim();
|
|
@@ -5631,11 +5671,11 @@ function resolveSandcastleBin() {
|
|
|
5631
5671
|
const mainUrl = import.meta.resolve("@ai-hero/sandcastle");
|
|
5632
5672
|
let dir = dirname7(fileURLToPath6(mainUrl));
|
|
5633
5673
|
for (let hops = 0;hops < 6; hops++) {
|
|
5634
|
-
const manifestPath =
|
|
5674
|
+
const manifestPath = join15(dir, "package.json");
|
|
5635
5675
|
if (existsSync16(manifestPath)) {
|
|
5636
5676
|
const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
|
|
5637
5677
|
if (manifest.name === "@ai-hero/sandcastle" && manifest.bin?.sandcastle) {
|
|
5638
|
-
return
|
|
5678
|
+
return join15(dir, manifest.bin.sandcastle);
|
|
5639
5679
|
}
|
|
5640
5680
|
}
|
|
5641
5681
|
const parent = dirname7(dir);
|
|
@@ -5663,12 +5703,12 @@ function sandcastleTemplateDir() {
|
|
|
5663
5703
|
return resolveAsset(ASSET_ENV.sandcastleTemplate, fileURLToPath6(new URL("../assets/sandcastle", import.meta.url)));
|
|
5664
5704
|
}
|
|
5665
5705
|
function scaffoldSandcastleConfig(templateDir, targetDir) {
|
|
5666
|
-
const dir =
|
|
5706
|
+
const dir = join15(targetDir, ".sandcastle");
|
|
5667
5707
|
if (existsSync16(dir))
|
|
5668
5708
|
return { scaffolded: false, dir };
|
|
5669
5709
|
mkdirSync8(dir, { recursive: true });
|
|
5670
5710
|
for (const name of readdirSync5(templateDir)) {
|
|
5671
|
-
copyFileSync(
|
|
5711
|
+
copyFileSync(join15(templateDir, name), join15(dir, name));
|
|
5672
5712
|
}
|
|
5673
5713
|
return { scaffolded: true, dir };
|
|
5674
5714
|
}
|
|
@@ -5832,13 +5872,13 @@ var init_update_check = __esm(() => {
|
|
|
5832
5872
|
|
|
5833
5873
|
// src/version.ts
|
|
5834
5874
|
import { readFileSync as readFileSync8 } from "fs";
|
|
5835
|
-
import { dirname as dirname8, join as
|
|
5875
|
+
import { dirname as dirname8, join as join16 } from "path";
|
|
5836
5876
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
5837
5877
|
function runcastleVersion() {
|
|
5838
5878
|
if (cached2)
|
|
5839
5879
|
return cached2;
|
|
5840
5880
|
const here = dirname8(fileURLToPath7(import.meta.url));
|
|
5841
|
-
for (const pkgPath of [
|
|
5881
|
+
for (const pkgPath of [join16(here, "..", "package.json"), join16(here, "package.json")]) {
|
|
5842
5882
|
try {
|
|
5843
5883
|
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
5844
5884
|
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}`;
|
|
@@ -2139,7 +2140,7 @@ function ticketBranchName(slug, ticketSeq, unique) {
|
|
|
2139
2140
|
return `${TICKET_BRANCH_PREFIX}${tempBranchSlugSegment(slug)}/${ticketSeq}-${unique}`;
|
|
2140
2141
|
}
|
|
2141
2142
|
async function allowPushToCheckedOutBranches(repoPath) {
|
|
2142
|
-
await git(repoPath).addConfig("receive.denyCurrentBranch", "
|
|
2143
|
+
await git(repoPath).addConfig("receive.denyCurrentBranch", "ignore");
|
|
2143
2144
|
}
|
|
2144
2145
|
async function mergeTempBranch(repoPath, featureBranchName, tempBranch) {
|
|
2145
2146
|
const g = git(repoPath);
|
|
@@ -2170,13 +2171,47 @@ async function mergeTempBranch(repoPath, featureBranchName, tempBranch) {
|
|
|
2170
2171
|
} else {
|
|
2171
2172
|
try {
|
|
2172
2173
|
await g.raw(["fetch", ".", `${tempBranch}:${featureBranchName}`]);
|
|
2173
|
-
} catch
|
|
2174
|
-
|
|
2174
|
+
} catch {
|
|
2175
|
+
const merged = await mergeInDisposableWorktree(g, featureBranchName, tempBranch);
|
|
2176
|
+
if (!merged.ok)
|
|
2177
|
+
return merged;
|
|
2175
2178
|
}
|
|
2176
2179
|
}
|
|
2177
2180
|
await deleteBranchDetachingWorktrees(g, repoPath, tempBranch);
|
|
2178
2181
|
return { ok: true };
|
|
2179
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
|
+
}
|
|
2180
2215
|
async function deleteBranchDetachingWorktrees(g, repoPath, branch) {
|
|
2181
2216
|
for (const holder of await worktreesOnBranch(g, branch, repoPath)) {
|
|
2182
2217
|
try {
|
|
@@ -2382,7 +2417,7 @@ var init_git = __esm(() => {
|
|
|
2382
2417
|
|
|
2383
2418
|
// src/services/knowledge.ts
|
|
2384
2419
|
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync } from "fs";
|
|
2385
|
-
import { join as
|
|
2420
|
+
import { join as join7, relative, resolve as resolve4, sep } from "path";
|
|
2386
2421
|
function scaffoldDocs(ctx, feature) {
|
|
2387
2422
|
const project = projectForFeature(ctx, feature);
|
|
2388
2423
|
const dir = featureDocsDir(project, feature);
|
|
@@ -2437,7 +2472,7 @@ function listDocs(ctx, feature) {
|
|
|
2437
2472
|
const dir = featureDocsDir(project, feature);
|
|
2438
2473
|
if (!existsSync8(dir))
|
|
2439
2474
|
return [];
|
|
2440
|
-
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));
|
|
2441
2476
|
}
|
|
2442
2477
|
function readDoc(ctx, feature, relPath) {
|
|
2443
2478
|
const project = projectForFeature(ctx, feature);
|
|
@@ -2478,21 +2513,21 @@ var init_knowledge = __esm(() => {
|
|
|
2478
2513
|
|
|
2479
2514
|
// src/launcher/skills-root.ts
|
|
2480
2515
|
import { existsSync as existsSync9 } from "fs";
|
|
2481
|
-
import { dirname as dirname2, join as
|
|
2516
|
+
import { dirname as dirname2, join as join8, resolve as resolve5 } from "path";
|
|
2482
2517
|
function resolveSkillsRoot(fromDir) {
|
|
2483
2518
|
const override = process.env[SKILLS_DIR_ENV];
|
|
2484
2519
|
if (override) {
|
|
2485
2520
|
const root = resolve5(override);
|
|
2486
|
-
if (existsSync9(
|
|
2521
|
+
if (existsSync9(join8(root, SKILLS_MARKER)))
|
|
2487
2522
|
return root;
|
|
2488
2523
|
throw new Error(`${SKILLS_DIR_ENV}=${override} is not a runcastle skills root (missing ${SKILLS_MARKER})`);
|
|
2489
2524
|
}
|
|
2490
2525
|
const searched = [];
|
|
2491
2526
|
let dir = fromDir;
|
|
2492
2527
|
for (let i = 0;i < 8; i += 1) {
|
|
2493
|
-
const candidate =
|
|
2528
|
+
const candidate = join8(dir, "packages", "skills");
|
|
2494
2529
|
searched.push(candidate);
|
|
2495
|
-
if (existsSync9(
|
|
2530
|
+
if (existsSync9(join8(candidate, SKILLS_MARKER)))
|
|
2496
2531
|
return candidate;
|
|
2497
2532
|
const parent = dirname2(dir);
|
|
2498
2533
|
if (parent === dir)
|
|
@@ -2505,7 +2540,7 @@ function resolveSkillsRoot(fromDir) {
|
|
|
2505
2540
|
}
|
|
2506
2541
|
var SKILLS_DIR_ENV = "RUNCASTLE_SKILLS_DIR", SKILLS_MARKER;
|
|
2507
2542
|
var init_skills_root = __esm(() => {
|
|
2508
|
-
SKILLS_MARKER =
|
|
2543
|
+
SKILLS_MARKER = join8("packs", "runcastle");
|
|
2509
2544
|
});
|
|
2510
2545
|
|
|
2511
2546
|
// src/services/agent-stream.ts
|
|
@@ -2550,7 +2585,7 @@ var init_agent_stream = __esm(() => {
|
|
|
2550
2585
|
|
|
2551
2586
|
// src/workflows/ticket-burner.ts
|
|
2552
2587
|
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
|
|
2553
|
-
import { dirname as dirname3, join as
|
|
2588
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
2554
2589
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2555
2590
|
import { claudeCode, run } from "@ai-hero/sandcastle";
|
|
2556
2591
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
@@ -2704,16 +2739,21 @@ function resolveBurnWorkspaceMode(config2, platform = process.platform) {
|
|
|
2704
2739
|
return platform === "linux" ? "mounted" : "isolated";
|
|
2705
2740
|
return config2.burnWorkspace;
|
|
2706
2741
|
}
|
|
2707
|
-
function buildIsolatedSetupCommand(tempBranch, setupCommand) {
|
|
2742
|
+
function buildIsolatedSetupCommand(tempBranch, setupCommand, pm) {
|
|
2708
2743
|
const hookFile = `${ISOLATED_REPO_PATH}/.git/hooks/post-commit`;
|
|
2709
2744
|
const parts = [
|
|
2710
2745
|
`git config --global --add safe.directory '*'`,
|
|
2711
2746
|
`git clone ${SANDBOX_WORKSPACE_PATH} ${ISOLATED_REPO_PATH}`,
|
|
2712
|
-
`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}`,
|
|
2713
2748
|
`chmod +x ${hookFile}`
|
|
2714
2749
|
];
|
|
2750
|
+
if (pm === "pnpm" || pm === "yarn") {
|
|
2751
|
+
const shim = `$HOME/.local/bin/${pm}`;
|
|
2752
|
+
parts.push(`mkdir -p "$HOME/.local/bin"`, `printf '#!/bin/sh\\nexec corepack ${pm} "$@"\\n' > "${shim}"`, `chmod +x "${shim}"`);
|
|
2753
|
+
}
|
|
2715
2754
|
if (setupCommand)
|
|
2716
2755
|
parts.push(`cd ${ISOLATED_REPO_PATH} && ${setupCommand}`);
|
|
2756
|
+
parts.push(`git -C ${ISOLATED_REPO_PATH} config core.hooksPath ${ISOLATED_REPO_PATH}/.git/hooks`);
|
|
2717
2757
|
return parts.join(" && ");
|
|
2718
2758
|
}
|
|
2719
2759
|
function buildWorkspaceNotes(mode) {
|
|
@@ -2723,14 +2763,14 @@ function buildWorkspaceNotes(mode) {
|
|
|
2723
2763
|
return [
|
|
2724
2764
|
`Your working repository is \`${ISOLATED_REPO_PATH}\` \u2014 a clone on the container's fast native filesystem, with dependencies already installed. Do ALL work there: \`cd ${ISOLATED_REPO_PATH}\` first; every file you read, edit, test, and commit lives under it.`,
|
|
2725
2765
|
"",
|
|
2726
|
-
`The directory you start in (\`${SANDBOX_WORKSPACE_PATH}\`) is a slow mounted mirror used only to collect your commits \u2014 never edit files, install, or run tests there. Your commits sync back automatically (a post-commit hook pushes them); just commit as normal.`,
|
|
2766
|
+
`The directory you start in (\`${SANDBOX_WORKSPACE_PATH}\`) is a slow mounted mirror used only to collect your commits \u2014 never edit files, install, or run tests there. Your commits sync back automatically (a post-commit hook pushes them); just commit as normal. If you re-run the dependency install and it reconfigures git hooks (husky), run \`git -C ${ISOLATED_REPO_PATH} config core.hooksPath ${ISOLATED_REPO_PATH}/.git/hooks\` afterwards so the sync hook stays armed.`,
|
|
2727
2767
|
"",
|
|
2728
2768
|
`If you are blocked and write \`BLOCKED.md\`, write it at \`${ISOLATED_REPO_PATH}/BLOCKED.md\` AND copy it to \`${SANDBOX_WORKSPACE_PATH}/BLOCKED.md\` so the orchestrator can see it.`
|
|
2729
2769
|
].join(`
|
|
2730
2770
|
`);
|
|
2731
2771
|
}
|
|
2732
2772
|
function readRepoToolchain(repoPath) {
|
|
2733
|
-
const pkgPath =
|
|
2773
|
+
const pkgPath = join9(repoPath, "package.json");
|
|
2734
2774
|
const hasPackageJson = existsSync10(pkgPath);
|
|
2735
2775
|
let packageManagerField;
|
|
2736
2776
|
if (hasPackageJson) {
|
|
@@ -2744,10 +2784,10 @@ function readRepoToolchain(repoPath) {
|
|
|
2744
2784
|
hasPackageJson,
|
|
2745
2785
|
...packageManagerField !== undefined ? { packageManagerField } : {},
|
|
2746
2786
|
lockfiles: {
|
|
2747
|
-
bun: existsSync10(
|
|
2748
|
-
pnpm: existsSync10(
|
|
2749
|
-
yarn: existsSync10(
|
|
2750
|
-
npm: existsSync10(
|
|
2787
|
+
bun: existsSync10(join9(repoPath, "bun.lock")) || existsSync10(join9(repoPath, "bun.lockb")),
|
|
2788
|
+
pnpm: existsSync10(join9(repoPath, "pnpm-lock.yaml")),
|
|
2789
|
+
yarn: existsSync10(join9(repoPath, "yarn.lock")),
|
|
2790
|
+
npm: existsSync10(join9(repoPath, "package-lock.json"))
|
|
2751
2791
|
}
|
|
2752
2792
|
};
|
|
2753
2793
|
}
|
|
@@ -2957,23 +2997,23 @@ async function burnRun(ctx, deps) {
|
|
|
2957
2997
|
}
|
|
2958
2998
|
function burnerTemplatePath() {
|
|
2959
2999
|
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
2960
|
-
return
|
|
3000
|
+
return join9(resolveSkillsRoot(here), "burner", "implement-ticket.md");
|
|
2961
3001
|
}
|
|
2962
3002
|
function readDocsDigestFromDisk(projectId, slug) {
|
|
2963
3003
|
const worktree = worktreeDir(projectId, slug);
|
|
2964
3004
|
if (!existsSync10(worktree))
|
|
2965
3005
|
return "_No talk worktree on disk \u2014 docs digest skipped._";
|
|
2966
|
-
const docsDir =
|
|
3006
|
+
const docsDir = join9(worktree, ...featureDocsRel(slug).split("/"));
|
|
2967
3007
|
if (!existsSync10(docsDir))
|
|
2968
3008
|
return "_No docs/features dir in the talk worktree \u2014 docs digest skipped._";
|
|
2969
|
-
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(
|
|
3009
|
+
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") }));
|
|
2970
3010
|
return buildDocsDigest(files);
|
|
2971
3011
|
}
|
|
2972
3012
|
function readBlockedFile(dirs) {
|
|
2973
3013
|
for (const dir of dirs) {
|
|
2974
3014
|
if (!dir)
|
|
2975
3015
|
continue;
|
|
2976
|
-
const p =
|
|
3016
|
+
const p = join9(dir, "BLOCKED.md");
|
|
2977
3017
|
if (existsSync10(p)) {
|
|
2978
3018
|
try {
|
|
2979
3019
|
return readFileSync4(p, "utf8");
|
|
@@ -3041,7 +3081,7 @@ async function realExecuteTicketRun(ctx, ticket, config2, token, model, land, en
|
|
|
3041
3081
|
mounts.push(mount);
|
|
3042
3082
|
}
|
|
3043
3083
|
}
|
|
3044
|
-
const hookCommand = workspaceMode === "isolated" ? buildIsolatedSetupCommand(tempBranch, setupCommand) : setupCommand;
|
|
3084
|
+
const hookCommand = workspaceMode === "isolated" ? buildIsolatedSetupCommand(tempBranch, setupCommand, pm) : setupCommand;
|
|
3045
3085
|
if (hookCommand) {
|
|
3046
3086
|
ctx.emitEvent({
|
|
3047
3087
|
type: "burn.setup",
|
|
@@ -3050,7 +3090,7 @@ async function realExecuteTicketRun(ctx, ticket, config2, token, model, land, en
|
|
|
3050
3090
|
});
|
|
3051
3091
|
}
|
|
3052
3092
|
mkdirSync4(logsDir(), { recursive: true });
|
|
3053
|
-
const logFilePath =
|
|
3093
|
+
const logFilePath = join9(logsDir(), `burn-${feature.id}-${ticket.seq}.log`);
|
|
3054
3094
|
const throttle = createStreamThrottle((e) => ctx.emitEvent({ ...e, ticketId: ticket.id }));
|
|
3055
3095
|
beginTranscript(ticket.id);
|
|
3056
3096
|
const onStreamEvent = (event) => {
|
|
@@ -3186,7 +3226,7 @@ var init_ticket_burner = __esm(() => {
|
|
|
3186
3226
|
|
|
3187
3227
|
// src/workflows/research.ts
|
|
3188
3228
|
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "fs";
|
|
3189
|
-
import { dirname as dirname4, join as
|
|
3229
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
3190
3230
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3191
3231
|
import { run as run2 } from "@ai-hero/sandcastle";
|
|
3192
3232
|
import { docker as docker2 } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
@@ -3258,16 +3298,16 @@ function interpretResearchResult(result, docRelPath) {
|
|
|
3258
3298
|
}
|
|
3259
3299
|
function researchTemplatePath() {
|
|
3260
3300
|
const here = dirname4(fileURLToPath3(import.meta.url));
|
|
3261
|
-
return
|
|
3301
|
+
return join10(resolveSkillsRoot(here), "burner", "research-waypoint.md");
|
|
3262
3302
|
}
|
|
3263
3303
|
function readDocsDigestFromDisk2(projectId, slug) {
|
|
3264
3304
|
const worktree = worktreeDir(projectId, slug);
|
|
3265
3305
|
if (!existsSync11(worktree))
|
|
3266
3306
|
return "_No talk worktree on disk \u2014 docs digest skipped._";
|
|
3267
|
-
const docsDir =
|
|
3307
|
+
const docsDir = join10(worktree, ...featureDocsRel(slug).split("/"));
|
|
3268
3308
|
if (!existsSync11(docsDir))
|
|
3269
3309
|
return "_No docs/features dir in the talk worktree \u2014 docs digest skipped._";
|
|
3270
|
-
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(
|
|
3310
|
+
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") }));
|
|
3271
3311
|
return buildDocsDigest(files);
|
|
3272
3312
|
}
|
|
3273
3313
|
async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
@@ -3282,7 +3322,7 @@ async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
|
3282
3322
|
RESEARCH_DOC_PATH: docRelPath
|
|
3283
3323
|
});
|
|
3284
3324
|
mkdirSync5(logsDir(), { recursive: true });
|
|
3285
|
-
const logFilePath =
|
|
3325
|
+
const logFilePath = join10(logsDir(), `research-${feature.id}-${waypoint.seq}.log`);
|
|
3286
3326
|
const throttle = createResearchStreamThrottle((e) => ctx.emitEvent(e));
|
|
3287
3327
|
const runOptions = {
|
|
3288
3328
|
agent: buildBurnAgent(config2, token, model),
|
|
@@ -4143,7 +4183,7 @@ var init_hooks = __esm(() => {
|
|
|
4143
4183
|
// src/routes/web.ts
|
|
4144
4184
|
import { existsSync as existsSync12, statSync } from "fs";
|
|
4145
4185
|
import { readFile } from "fs/promises";
|
|
4146
|
-
import { extname, join as
|
|
4186
|
+
import { extname, join as join11, normalize, resolve as resolve6, sep as sep2 } from "path";
|
|
4147
4187
|
function contentType(path) {
|
|
4148
4188
|
return MIME[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
4149
4189
|
}
|
|
@@ -4161,7 +4201,7 @@ function safeJoin(root, pathname) {
|
|
|
4161
4201
|
} catch {
|
|
4162
4202
|
return null;
|
|
4163
4203
|
}
|
|
4164
|
-
const joined = normalize(
|
|
4204
|
+
const joined = normalize(join11(root, decoded));
|
|
4165
4205
|
if (joined !== root && !joined.startsWith(root + sep2))
|
|
4166
4206
|
return null;
|
|
4167
4207
|
return joined;
|
|
@@ -4174,7 +4214,7 @@ function resolveWebDist() {
|
|
|
4174
4214
|
}
|
|
4175
4215
|
function mountWebApp(app, distDir) {
|
|
4176
4216
|
const root = resolve6(distDir);
|
|
4177
|
-
const indexHtml =
|
|
4217
|
+
const indexHtml = join11(root, "index.html");
|
|
4178
4218
|
app.get("*", async (c, next) => {
|
|
4179
4219
|
const { pathname } = new URL(c.req.url);
|
|
4180
4220
|
if (/^\/(api|mcp|ws|health)(\/|$)/.test(pathname))
|
|
@@ -4194,7 +4234,7 @@ function mountWebApp(app, distDir) {
|
|
|
4194
4234
|
}
|
|
4195
4235
|
function mountWebAppIfBuilt(app) {
|
|
4196
4236
|
const dist = resolveWebDist();
|
|
4197
|
-
if (existsSync12(
|
|
4237
|
+
if (existsSync12(join11(dist, "index.html")))
|
|
4198
4238
|
mountWebApp(app, dist);
|
|
4199
4239
|
}
|
|
4200
4240
|
var MIME;
|
|
@@ -4270,7 +4310,7 @@ var init_events2 = __esm(() => {
|
|
|
4270
4310
|
// src/launcher/artifacts.ts
|
|
4271
4311
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
4272
4312
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4273
|
-
import { join as
|
|
4313
|
+
import { join as join12 } from "path";
|
|
4274
4314
|
function hookClientPath() {
|
|
4275
4315
|
return resolveAsset(ASSET_ENV.hookClient, fileURLToPath4(new URL("./hook-client.ts", import.meta.url)));
|
|
4276
4316
|
}
|
|
@@ -4482,9 +4522,9 @@ async function writeSessionArtifacts(input) {
|
|
|
4482
4522
|
const { session, feature, config: config2, waypoint } = input;
|
|
4483
4523
|
const dir = sessionDir(session.id);
|
|
4484
4524
|
mkdirSync6(dir, { recursive: true });
|
|
4485
|
-
const systemPromptPath =
|
|
4486
|
-
const settingsPath =
|
|
4487
|
-
const mcpConfigPath =
|
|
4525
|
+
const systemPromptPath = join12(dir, "system-prompt.md");
|
|
4526
|
+
const settingsPath = join12(dir, "settings.json");
|
|
4527
|
+
const mcpConfigPath = join12(dir, "mcp.json");
|
|
4488
4528
|
writeFileSync2(systemPromptPath, renderSystemPrompt(feature, session.kind, waypoint), "utf8");
|
|
4489
4529
|
writeFileSync2(settingsPath, JSON.stringify(renderSettings(hookClientPath()), null, 2), "utf8");
|
|
4490
4530
|
writeFileSync2(mcpConfigPath, JSON.stringify(renderMcpConfig(session, config2), null, 2), "utf8");
|
|
@@ -4546,7 +4586,7 @@ var init_end_session = __esm(() => {
|
|
|
4546
4586
|
|
|
4547
4587
|
// src/launcher/launcher.ts
|
|
4548
4588
|
import { existsSync as existsSync13 } from "fs";
|
|
4549
|
-
import { dirname as dirname5, join as
|
|
4589
|
+
import { dirname as dirname5, join as join13, resolve as resolve7 } from "path";
|
|
4550
4590
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
4551
4591
|
import { and as and5, eq as eq8 } from "drizzle-orm";
|
|
4552
4592
|
function ptyExitMessage(exitCode) {
|
|
@@ -4584,10 +4624,10 @@ function claudeSpawnTarget(claudeArgs) {
|
|
|
4584
4624
|
return { file: exe, args: claudeArgs };
|
|
4585
4625
|
}
|
|
4586
4626
|
function resolvePluginDir(fromDir = dirname5(fileURLToPath5(import.meta.url))) {
|
|
4587
|
-
const rel =
|
|
4627
|
+
const rel = join13("packages", "skills", "packs", "runcastle");
|
|
4588
4628
|
const override = process.env[SKILLS_DIR_ENV];
|
|
4589
4629
|
if (override) {
|
|
4590
|
-
const dir2 =
|
|
4630
|
+
const dir2 = join13(resolve7(override), "packs", "runcastle");
|
|
4591
4631
|
if (existsSync13(dir2))
|
|
4592
4632
|
return dir2;
|
|
4593
4633
|
throw new Error(`${SKILLS_DIR_ENV}=${override} has no plugin dir at ${dir2}`);
|
|
@@ -4595,7 +4635,7 @@ function resolvePluginDir(fromDir = dirname5(fileURLToPath5(import.meta.url))) {
|
|
|
4595
4635
|
const searched = [];
|
|
4596
4636
|
let dir = fromDir;
|
|
4597
4637
|
for (let i = 0;i < 8; i += 1) {
|
|
4598
|
-
const candidate =
|
|
4638
|
+
const candidate = join13(dir, rel);
|
|
4599
4639
|
searched.push(candidate);
|
|
4600
4640
|
if (existsSync13(candidate))
|
|
4601
4641
|
return candidate;
|
|
@@ -4949,7 +4989,7 @@ var init_feature = __esm(() => {
|
|
|
4949
4989
|
|
|
4950
4990
|
// src/services/projects.ts
|
|
4951
4991
|
import { existsSync as existsSync14 } from "fs";
|
|
4952
|
-
import { basename, join as
|
|
4992
|
+
import { basename, join as join14 } from "path";
|
|
4953
4993
|
import { eq as eq9, isNull } from "drizzle-orm";
|
|
4954
4994
|
function listProjects(ctx) {
|
|
4955
4995
|
return ctx.db.select().from(projects).where(isNull(projects.closedAt)).all().map(rowToProject);
|
|
@@ -5025,7 +5065,7 @@ async function assertRepoTolerant(ctx, repoPath) {
|
|
|
5025
5065
|
} catch (e) {
|
|
5026
5066
|
if (!isNotImplemented(e))
|
|
5027
5067
|
throw e;
|
|
5028
|
-
if (!existsSync14(
|
|
5068
|
+
if (!existsSync14(join14(repoPath, ".git"))) {
|
|
5029
5069
|
throw new InvalidInputError(`not a git repository: ${repoPath}`);
|
|
5030
5070
|
}
|
|
5031
5071
|
}
|
|
@@ -5552,7 +5592,7 @@ var init_system_exec = __esm(() => {
|
|
|
5552
5592
|
|
|
5553
5593
|
// src/services/setup.ts
|
|
5554
5594
|
import { copyFileSync, existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
5555
|
-
import { dirname as dirname7, join as
|
|
5595
|
+
import { dirname as dirname7, join as join15 } from "path";
|
|
5556
5596
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
5557
5597
|
async function writeGitIdentity(exec, identity) {
|
|
5558
5598
|
const name = identity.name.trim();
|
|
@@ -5630,11 +5670,11 @@ function resolveSandcastleBin() {
|
|
|
5630
5670
|
const mainUrl = import.meta.resolve("@ai-hero/sandcastle");
|
|
5631
5671
|
let dir = dirname7(fileURLToPath6(mainUrl));
|
|
5632
5672
|
for (let hops = 0;hops < 6; hops++) {
|
|
5633
|
-
const manifestPath =
|
|
5673
|
+
const manifestPath = join15(dir, "package.json");
|
|
5634
5674
|
if (existsSync16(manifestPath)) {
|
|
5635
5675
|
const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
|
|
5636
5676
|
if (manifest.name === "@ai-hero/sandcastle" && manifest.bin?.sandcastle) {
|
|
5637
|
-
return
|
|
5677
|
+
return join15(dir, manifest.bin.sandcastle);
|
|
5638
5678
|
}
|
|
5639
5679
|
}
|
|
5640
5680
|
const parent = dirname7(dir);
|
|
@@ -5662,12 +5702,12 @@ function sandcastleTemplateDir() {
|
|
|
5662
5702
|
return resolveAsset(ASSET_ENV.sandcastleTemplate, fileURLToPath6(new URL("../assets/sandcastle", import.meta.url)));
|
|
5663
5703
|
}
|
|
5664
5704
|
function scaffoldSandcastleConfig(templateDir, targetDir) {
|
|
5665
|
-
const dir =
|
|
5705
|
+
const dir = join15(targetDir, ".sandcastle");
|
|
5666
5706
|
if (existsSync16(dir))
|
|
5667
5707
|
return { scaffolded: false, dir };
|
|
5668
5708
|
mkdirSync8(dir, { recursive: true });
|
|
5669
5709
|
for (const name of readdirSync5(templateDir)) {
|
|
5670
|
-
copyFileSync(
|
|
5710
|
+
copyFileSync(join15(templateDir, name), join15(dir, name));
|
|
5671
5711
|
}
|
|
5672
5712
|
return { scaffolded: true, dir };
|
|
5673
5713
|
}
|
|
@@ -5831,13 +5871,13 @@ var init_update_check = __esm(() => {
|
|
|
5831
5871
|
|
|
5832
5872
|
// src/version.ts
|
|
5833
5873
|
import { readFileSync as readFileSync8 } from "fs";
|
|
5834
|
-
import { dirname as dirname8, join as
|
|
5874
|
+
import { dirname as dirname8, join as join16 } from "path";
|
|
5835
5875
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
5836
5876
|
function runcastleVersion() {
|
|
5837
5877
|
if (cached2)
|
|
5838
5878
|
return cached2;
|
|
5839
5879
|
const here = dirname8(fileURLToPath7(import.meta.url));
|
|
5840
|
-
for (const pkgPath of [
|
|
5880
|
+
for (const pkgPath of [join16(here, "..", "package.json"), join16(here, "package.json")]) {
|
|
5841
5881
|
try {
|
|
5842
5882
|
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
5843
5883
|
if (typeof pkg.version === "string") {
|