@tablation/crew 0.108.0 → 0.109.0
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/dist/cli.js +425 -131
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -243,7 +243,7 @@ function shellInitScript(shell) {
|
|
|
243
243
|
|
|
244
244
|
// src/config.ts
|
|
245
245
|
import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
|
|
246
|
-
import { resolve, dirname as dirname4, isAbsolute, join as join12, basename as
|
|
246
|
+
import { resolve, dirname as dirname4, isAbsolute, join as join12, basename as basename4 } from "path";
|
|
247
247
|
import { homedir as homedir6, tmpdir } from "os";
|
|
248
248
|
import { parse as parse2 } from "yaml";
|
|
249
249
|
|
|
@@ -2071,8 +2071,204 @@ function explain(ship, required) {
|
|
|
2071
2071
|
|
|
2072
2072
|
// src/repo-config.ts
|
|
2073
2073
|
import { readFileSync as readFileSync10, existsSync as existsSync9 } from "fs";
|
|
2074
|
-
import { join as join11, basename as
|
|
2074
|
+
import { join as join11, basename as basename3 } from "path";
|
|
2075
2075
|
import { parse } from "yaml";
|
|
2076
|
+
|
|
2077
|
+
// src/sandbox-policy.ts
|
|
2078
|
+
import { basename as basename2 } from "path";
|
|
2079
|
+
import { stringify } from "yaml";
|
|
2080
|
+
var SandboxPolicyError = class extends Error {
|
|
2081
|
+
};
|
|
2082
|
+
var PROVIDER_HOSTS = {
|
|
2083
|
+
anthropic: ["api.anthropic.com", "claude.ai", "platform.claude.com"],
|
|
2084
|
+
openai: ["api.openai.com", "auth.openai.com", "chatgpt.com"],
|
|
2085
|
+
google: ["generativelanguage.googleapis.com", "oauth2.googleapis.com", "cloudcode-pa.googleapis.com"]
|
|
2086
|
+
};
|
|
2087
|
+
var TOOLCHAIN_HOSTS = {
|
|
2088
|
+
corepack: ["registry.npmjs.org"],
|
|
2089
|
+
pnpm: ["registry.npmjs.org"],
|
|
2090
|
+
yarn: ["registry.npmjs.org"],
|
|
2091
|
+
npm: ["registry.npmjs.org"],
|
|
2092
|
+
prisma: ["binaries.prisma.sh"],
|
|
2093
|
+
playwright: ["cdn.playwright.dev", "playwright.download.prss.microsoft.com"],
|
|
2094
|
+
pip: ["pypi.org", "files.pythonhosted.org"],
|
|
2095
|
+
cargo: ["index.crates.io", "static.crates.io"],
|
|
2096
|
+
go: ["proxy.golang.org", "sum.golang.org"],
|
|
2097
|
+
swift: ["download.swift.org"]
|
|
2098
|
+
};
|
|
2099
|
+
var FORBIDDEN_HOSTS = /* @__PURE__ */ new Set(["169.254.169.254", "fd00:ec2::254", "metadata.google.internal", "metadata", "localhost"]);
|
|
2100
|
+
var IMDS_ADDRESS = "169.254.169.254";
|
|
2101
|
+
var HOST = /^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/;
|
|
2102
|
+
var IPV4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
2103
|
+
function isForbiddenIp(host) {
|
|
2104
|
+
const m = IPV4.exec(host);
|
|
2105
|
+
if (m) {
|
|
2106
|
+
const [a, b] = [Number(m[1]), Number(m[2])];
|
|
2107
|
+
return a === 127 || a === 0 || a === 10 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
|
|
2108
|
+
}
|
|
2109
|
+
return host.includes(":");
|
|
2110
|
+
}
|
|
2111
|
+
function isAmbiguousNumericHost(host) {
|
|
2112
|
+
const labels = host.split(".");
|
|
2113
|
+
if (!labels.every((l) => /^(0x[0-9a-f]*|\d+)$/.test(l))) return false;
|
|
2114
|
+
const canonical = labels.length === 4 && labels.every((l) => /^(0|[1-9]\d{0,2})$/.test(l) && Number(l) <= 255);
|
|
2115
|
+
return !canonical;
|
|
2116
|
+
}
|
|
2117
|
+
function parseEndpoint(raw, defaultPort = 443) {
|
|
2118
|
+
const s = raw.trim().toLowerCase();
|
|
2119
|
+
const m = /^([^:/\s]+)(?::(\d{1,5}))?$/.exec(s);
|
|
2120
|
+
if (!m) throw new SandboxPolicyError(`"${raw}" is not a host or host:port`);
|
|
2121
|
+
const host = m[1];
|
|
2122
|
+
const port = m[2] ? Number(m[2]) : defaultPort;
|
|
2123
|
+
if (port < 1 || port > 65535) throw new SandboxPolicyError(`"${raw}" has an invalid port`);
|
|
2124
|
+
if (FORBIDDEN_HOSTS.has(host) || isForbiddenIp(host)) {
|
|
2125
|
+
throw new SandboxPolicyError(`"${raw}" is never reachable from a sandbox (loopback, private, link-local and cloud metadata addresses are always denied)`);
|
|
2126
|
+
}
|
|
2127
|
+
if (isAmbiguousNumericHost(host)) {
|
|
2128
|
+
throw new SandboxPolicyError(`"${raw}" is a numeric host in a non-canonical form (hex, octal, integer or short dotted); write an IPv4 address as four plain decimal octets or use a DNS name`);
|
|
2129
|
+
}
|
|
2130
|
+
if (!IPV4.test(host) && !HOST.test(host)) throw new SandboxPolicyError(`"${raw}" is not a valid host name`);
|
|
2131
|
+
return { host, port };
|
|
2132
|
+
}
|
|
2133
|
+
function dedupe(eps) {
|
|
2134
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2135
|
+
return eps.filter((e) => {
|
|
2136
|
+
const k = `${e.host}:${e.port}`;
|
|
2137
|
+
if (seen.has(k)) return false;
|
|
2138
|
+
seen.add(k);
|
|
2139
|
+
return true;
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
function tablationEndpoint(url) {
|
|
2143
|
+
let u;
|
|
2144
|
+
try {
|
|
2145
|
+
u = new URL(url);
|
|
2146
|
+
} catch {
|
|
2147
|
+
throw new SandboxPolicyError(`the route's base URL "${url}" is not a URL`);
|
|
2148
|
+
}
|
|
2149
|
+
const port = u.port ? Number(u.port) : u.protocol === "http:" ? 80 : 443;
|
|
2150
|
+
const host = u.hostname.toLowerCase();
|
|
2151
|
+
if (host !== "localhost" && (FORBIDDEN_HOSTS.has(host) || host.startsWith("169.254."))) {
|
|
2152
|
+
throw new SandboxPolicyError(`the route's base URL host "${host}" is never reachable from a sandbox`);
|
|
2153
|
+
}
|
|
2154
|
+
return { host, port };
|
|
2155
|
+
}
|
|
2156
|
+
var TOOL_BINARIES = ["/usr/bin/curl", "/usr/bin/node", "/usr/local/bin/node", "/usr/bin/npm", "/usr/local/bin/npm", "/usr/local/bin/pnpm", "/usr/bin/corepack", "/usr/local/bin/corepack"];
|
|
2157
|
+
var GIT_BINARIES = ["/usr/bin/git", "/usr/lib/git-core/git-remote-http", "/usr/lib/git-core/git-remote-https"];
|
|
2158
|
+
function policyGroups(input) {
|
|
2159
|
+
const harness = `/usr/local/bin/${basename2(input.harnessBin)}`;
|
|
2160
|
+
const harnessBins = [.../* @__PURE__ */ new Set([harness, `/usr/bin/${basename2(input.harnessBin)}`, "/usr/bin/node", "/usr/local/bin/node"])];
|
|
2161
|
+
const groups = [];
|
|
2162
|
+
const tablation = tablationEndpoint(input.tablationUrl);
|
|
2163
|
+
groups.push({
|
|
2164
|
+
name: "tablation",
|
|
2165
|
+
purpose: input.authMode === "workspace" ? "the tracker and the inference endpoint (the only route to a model)" : "the tracker",
|
|
2166
|
+
endpoints: [tablation],
|
|
2167
|
+
// curl too: the lane briefs drive the tracker API with it.
|
|
2168
|
+
binaries: [...harnessBins, "/usr/bin/curl"]
|
|
2169
|
+
});
|
|
2170
|
+
const forge = dedupe((input.forgeHosts.length > 0 ? input.forgeHosts : ["github.com"]).map((h) => parseEndpoint(h)));
|
|
2171
|
+
groups.push({ name: "forge", purpose: "clone and push the ticket branch", endpoints: forge, binaries: GIT_BINARIES });
|
|
2172
|
+
if (input.authMode !== "workspace") {
|
|
2173
|
+
const hosts = PROVIDER_HOSTS[input.provider];
|
|
2174
|
+
if (hosts) {
|
|
2175
|
+
groups.push({ name: "model", purpose: `the ${input.provider} model route (auth: ${input.authMode})`, endpoints: hosts.map((h) => parseEndpoint(h)), binaries: harnessBins });
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
const toolHosts = (input.toolchains ?? []).flatMap((t) => {
|
|
2179
|
+
const hosts = TOOLCHAIN_HOSTS[t.toLowerCase()];
|
|
2180
|
+
if (!hosts) throw new SandboxPolicyError(`sandbox toolchain "${t}" is not one crew knows the download hosts of (known: ${Object.keys(TOOLCHAIN_HOSTS).join(", ")}); declare its hosts under sandbox.hosts instead`);
|
|
2181
|
+
return hosts.map((h) => parseEndpoint(h));
|
|
2182
|
+
});
|
|
2183
|
+
if (toolHosts.length > 0) {
|
|
2184
|
+
groups.push({ name: "toolchains", purpose: "run-time downloads of the declared toolchains", endpoints: dedupe(toolHosts), binaries: TOOL_BINARIES });
|
|
2185
|
+
}
|
|
2186
|
+
const extras = dedupe((input.extraHosts ?? []).map((h) => parseEndpoint(h)));
|
|
2187
|
+
if (extras.length > 0) {
|
|
2188
|
+
groups.push({ name: "declared", purpose: "hosts the repository's sandbox: section declares", endpoints: extras, binaries: [.../* @__PURE__ */ new Set([...TOOL_BINARIES, ...harnessBins])] });
|
|
2189
|
+
}
|
|
2190
|
+
return groups;
|
|
2191
|
+
}
|
|
2192
|
+
function generateSandboxPolicy(input) {
|
|
2193
|
+
const groups = policyGroups(input);
|
|
2194
|
+
const mirror = input.mirrorDir ?? "/cache";
|
|
2195
|
+
const work = input.workDir ?? "/sandbox/work";
|
|
2196
|
+
const doc = {
|
|
2197
|
+
version: 1,
|
|
2198
|
+
// Fixed to the image's defaults; only the cache mount is added, read-only.
|
|
2199
|
+
filesystem_policy: {
|
|
2200
|
+
include_workdir: true,
|
|
2201
|
+
read_only: ["/usr", "/lib", "/proc", "/dev/urandom", "/app", "/etc", "/var/log", "/opt/crew", mirror],
|
|
2202
|
+
read_write: ["/sandbox", "/tmp", "/dev/null", work]
|
|
2203
|
+
},
|
|
2204
|
+
// A host that cannot enforce fails the run instead of running unconfined.
|
|
2205
|
+
landlock: { compatibility: "hard_requirement" },
|
|
2206
|
+
network_policies: Object.fromEntries(groups.map((g) => [g.name, {
|
|
2207
|
+
name: g.name,
|
|
2208
|
+
endpoints: g.endpoints.map((e) => ({ host: e.host, port: e.port })),
|
|
2209
|
+
binaries: g.binaries.map((path) => ({ path }))
|
|
2210
|
+
}]))
|
|
2211
|
+
};
|
|
2212
|
+
const header = [
|
|
2213
|
+
"# Generated by crew for one sandboxed run. Do not edit: the next run replaces it.",
|
|
2214
|
+
`# Auth mode: ${input.authMode}. Everything not listed below is denied, including package registries.`,
|
|
2215
|
+
`# The cloud metadata address ${IMDS_ADDRESS} is denied; no rule here can match it.`,
|
|
2216
|
+
...groups.map((g) => `# ${g.name}: ${g.purpose}`),
|
|
2217
|
+
""
|
|
2218
|
+
].join("\n");
|
|
2219
|
+
return { yaml: header + stringify(doc), groups, extraHosts: (input.extraHosts ?? []).map((h) => h.trim().toLowerCase()) };
|
|
2220
|
+
}
|
|
2221
|
+
function policyPathFor(streamPath) {
|
|
2222
|
+
return streamPath.replace(/\.jsonl$/, "") + ".policy.yaml";
|
|
2223
|
+
}
|
|
2224
|
+
function parseSandboxNetwork(raw, file, fail) {
|
|
2225
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
2226
|
+
if (typeof raw !== "object" || Array.isArray(raw)) throw fail(`${file}: sandbox must be a mapping`);
|
|
2227
|
+
const r = raw;
|
|
2228
|
+
const list = (v, where) => {
|
|
2229
|
+
if (v === void 0 || v === null) return [];
|
|
2230
|
+
if (!Array.isArray(v) || v.some((x) => typeof x !== "string" || x.trim() === "")) throw fail(`${file}: ${where} must be a list of non-empty strings`);
|
|
2231
|
+
return v;
|
|
2232
|
+
};
|
|
2233
|
+
const hosts = list(r.hosts, "sandbox.hosts");
|
|
2234
|
+
const toolchains = list(r.toolchains, "sandbox.toolchains");
|
|
2235
|
+
for (const h of hosts) {
|
|
2236
|
+
try {
|
|
2237
|
+
parseEndpoint(h);
|
|
2238
|
+
} catch (e) {
|
|
2239
|
+
throw fail(`${file}: sandbox.hosts entry ${e.message}`);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
for (const t of toolchains) {
|
|
2243
|
+
if (!TOOLCHAIN_HOSTS[t.toLowerCase()]) throw fail(`${file}: sandbox.toolchains entry "${t}" is not known (known: ${Object.keys(TOOLCHAIN_HOSTS).join(", ")})`);
|
|
2244
|
+
}
|
|
2245
|
+
if (hosts.length === 0 && toolchains.length === 0) return void 0;
|
|
2246
|
+
return { hosts, toolchains };
|
|
2247
|
+
}
|
|
2248
|
+
function parseDeniedRequests(log) {
|
|
2249
|
+
const out = /* @__PURE__ */ new Map();
|
|
2250
|
+
for (const line of log.split("\n")) {
|
|
2251
|
+
if (!/\bden(y|ied|ies|ying)\b/i.test(line)) continue;
|
|
2252
|
+
const pick = (keys) => {
|
|
2253
|
+
for (const k of keys) {
|
|
2254
|
+
const m = new RegExp(`\\b${k}[=:]\\s*"?([^\\s",}]+)`, "i").exec(line);
|
|
2255
|
+
if (m) return m[1];
|
|
2256
|
+
}
|
|
2257
|
+
return void 0;
|
|
2258
|
+
};
|
|
2259
|
+
const host = pick(["dst_host", "destination_host", "host"]) ?? "?";
|
|
2260
|
+
const portRaw = pick(["dst_port", "destination_port", "port"]);
|
|
2261
|
+
const binary = pick(["binary", "bin", "exe", "process"]);
|
|
2262
|
+
const port = portRaw && /^\d+$/.test(portRaw) ? Number(portRaw) : void 0;
|
|
2263
|
+
const key = `${host}|${port ?? ""}|${binary ?? ""}`;
|
|
2264
|
+
const prior = out.get(key);
|
|
2265
|
+
if (prior) prior.count++;
|
|
2266
|
+
else out.set(key, { host, ...port !== void 0 ? { port } : {}, ...binary ? { binary } : {}, count: 1, line: line.trim().slice(0, 400) });
|
|
2267
|
+
}
|
|
2268
|
+
return [...out.values()];
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
// src/repo-config.ts
|
|
2076
2272
|
var SPEC_VERSION = 1;
|
|
2077
2273
|
var REPO_CONFIG_FILENAMES = [".crew.yaml", ".crew.yml", ".crew.json"];
|
|
2078
2274
|
var RepoConfigError = class extends Error {
|
|
@@ -2204,6 +2400,7 @@ function parseSandbox(raw, file) {
|
|
|
2204
2400
|
throw new RepoConfigError(`${file}: sandbox.image and sandbox.packages are alternatives \u2014 a published image already carries its packages; put the rest in sandbox.setup`);
|
|
2205
2401
|
}
|
|
2206
2402
|
}
|
|
2403
|
+
if (!image && packages.length === 0 && setup.length === 0 && warm.length === 0) return void 0;
|
|
2207
2404
|
return { ...image ? { image } : {}, packages, setup, warm };
|
|
2208
2405
|
}
|
|
2209
2406
|
function parseRepoConfig(text2, file) {
|
|
@@ -2224,7 +2421,8 @@ function parseRepoConfig(text2, file) {
|
|
|
2224
2421
|
rejectUnknown(raw.branch, BRANCH_KEYS, "branch", file);
|
|
2225
2422
|
rejectUnknown(raw.worktrees, /* @__PURE__ */ new Set(["copy", "prefix", "name"]), "worktrees", file);
|
|
2226
2423
|
rejectUnknown(raw.docs, /* @__PURE__ */ new Set(["triagePolicy", "designGuide"]), "docs", file);
|
|
2227
|
-
rejectUnknown(raw.sandbox, /* @__PURE__ */ new Set(["image", "packages", "setup", "warm"]), "sandbox", file);
|
|
2424
|
+
rejectUnknown(raw.sandbox, /* @__PURE__ */ new Set(["image", "packages", "setup", "warm", "hosts", "toolchains"]), "sandbox", file);
|
|
2425
|
+
const sandboxNetwork = parseSandboxNetwork(raw.sandbox, file, (m) => new RepoConfigError(m));
|
|
2228
2426
|
if (raw.version === void 0) throw new RepoConfigError(`${file}: version is required`);
|
|
2229
2427
|
if (raw.version !== SPEC_VERSION) {
|
|
2230
2428
|
throw new RepoConfigError(
|
|
@@ -2415,6 +2613,7 @@ function parseRepoConfig(text2, file) {
|
|
|
2415
2613
|
shell: raw.shell ? String(raw.shell) : void 0,
|
|
2416
2614
|
hooks,
|
|
2417
2615
|
labels,
|
|
2616
|
+
...sandboxNetwork ? { sandboxNetwork } : {},
|
|
2418
2617
|
release: {
|
|
2419
2618
|
mode,
|
|
2420
2619
|
ship,
|
|
@@ -2475,7 +2674,7 @@ function renderWorktreeName(template, ctx) {
|
|
|
2475
2674
|
return renderBranchName(template.replace(/\{dir\}/g, ctx.dir), ctx);
|
|
2476
2675
|
}
|
|
2477
2676
|
function defaultWorktreePrefix(dir) {
|
|
2478
|
-
const name =
|
|
2677
|
+
const name = basename3(dir.replace(/[/\\]+$/, ""));
|
|
2479
2678
|
return name ? `${name}-issue-` : "issue-";
|
|
2480
2679
|
}
|
|
2481
2680
|
var DEFAULTS = {
|
|
@@ -2660,7 +2859,7 @@ function effectiveBranchTemplate(cfg, prefix) {
|
|
|
2660
2859
|
function effectiveWorktreeDirName(cfg, dir, branchName, number, renderName) {
|
|
2661
2860
|
if (cfg.worktrees.name && renderName) return renderName(cfg.worktrees.name);
|
|
2662
2861
|
if (cfg.provenance["worktrees.prefix"] !== "default") return `${cfg.worktrees.prefix}${number}`;
|
|
2663
|
-
return `${
|
|
2862
|
+
return `${basename3(dir.replace(/[/\\]+$/, ""))}-${branchName}`;
|
|
2664
2863
|
}
|
|
2665
2864
|
function validateEffective(cfg) {
|
|
2666
2865
|
const problems = [];
|
|
@@ -3483,7 +3682,7 @@ function dirForRepo(r, repoId) {
|
|
|
3483
3682
|
}
|
|
3484
3683
|
function repoTargetFor(r, repoId) {
|
|
3485
3684
|
const names = r.resolved?.repoNames;
|
|
3486
|
-
if (!repoId || !names) return Object.keys(r.repos).length || r.dirIsCloneBase ? null : { name:
|
|
3685
|
+
if (!repoId || !names) return Object.keys(r.repos).length || r.dirIsCloneBase ? null : { name: basename4(r.dir), dir: r.dir };
|
|
3487
3686
|
const name = names[repoId];
|
|
3488
3687
|
if (!name) return null;
|
|
3489
3688
|
return reposOf(r).find((t) => t.name === name) ?? null;
|
|
@@ -3585,7 +3784,7 @@ function reposOf(r) {
|
|
|
3585
3784
|
...hosts[id] ? { forgeHost: hosts[id] } : {}
|
|
3586
3785
|
}));
|
|
3587
3786
|
}
|
|
3588
|
-
if (trackerNames.length <= 1) return [{ name:
|
|
3787
|
+
if (trackerNames.length <= 1) return [{ name: basename4(r.dir), dir: r.dir }];
|
|
3589
3788
|
const remotes = r.resolved?.repoRemotes ?? {};
|
|
3590
3789
|
const remoteByName = {};
|
|
3591
3790
|
const hostByName = {};
|
|
@@ -4490,7 +4689,7 @@ import {
|
|
|
4490
4689
|
readdirSync as readdirSync4,
|
|
4491
4690
|
rmSync as rmSync8
|
|
4492
4691
|
} from "fs";
|
|
4493
|
-
import { dirname as dirname6, basename as
|
|
4692
|
+
import { dirname as dirname6, basename as basename5, join as join14 } from "path";
|
|
4494
4693
|
var GitError = class extends Error {
|
|
4495
4694
|
};
|
|
4496
4695
|
function streamText(x) {
|
|
@@ -4634,7 +4833,7 @@ function deleteRemoteBranch(cwd, remote, branch) {
|
|
|
4634
4833
|
}
|
|
4635
4834
|
function worktreeForNumber(cwd, num) {
|
|
4636
4835
|
const re = new RegExp(`(?:^|-)${num}$`);
|
|
4637
|
-
return worktrees(cwd).find((w) => re.test(
|
|
4836
|
+
return worktrees(cwd).find((w) => re.test(basename5(w.path))) ?? null;
|
|
4638
4837
|
}
|
|
4639
4838
|
var commitBodies = (cwd, range) => gitOk(cwd, ["log", range, "--format=%B"]) ?? "";
|
|
4640
4839
|
var resolve2 = (cwd, ref) => gitOk(cwd, ["rev-parse", "-q", "--verify", `${ref}^{commit}`]);
|
|
@@ -4871,7 +5070,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
|
|
|
4871
5070
|
else if (line.startsWith("branch refs/heads/crew/release-") && path) {
|
|
4872
5071
|
gitOk(cwd, ["worktree", "remove", "--force", path]);
|
|
4873
5072
|
const root = dirname6(path);
|
|
4874
|
-
if (
|
|
5073
|
+
if (basename5(root).startsWith("crew-release-")) rmSync8(root, { recursive: true, force: true });
|
|
4875
5074
|
}
|
|
4876
5075
|
}
|
|
4877
5076
|
const stranded = (gitOk(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads/crew/release-*"]) ?? "").split("\n").filter(Boolean);
|
|
@@ -5377,8 +5576,8 @@ function attentionProblems(items, already) {
|
|
|
5377
5576
|
import { createHash as createHash6 } from "crypto";
|
|
5378
5577
|
import { spawn as spawn4 } from "child_process";
|
|
5379
5578
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
5380
|
-
import { mkdirSync as
|
|
5381
|
-
import { dirname as
|
|
5579
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync21, renameSync as renameSync3, writeFileSync as writeFileSync15, rmSync as rmSync13 } from "fs";
|
|
5580
|
+
import { dirname as dirname10, join as join26 } from "path";
|
|
5382
5581
|
import { tmpdir as tmpdir3 } from "os";
|
|
5383
5582
|
|
|
5384
5583
|
// src/passenger-containers.ts
|
|
@@ -5399,10 +5598,10 @@ import { join as join24 } from "path";
|
|
|
5399
5598
|
|
|
5400
5599
|
// src/agent.ts
|
|
5401
5600
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5402
|
-
import { readFileSync as readFileSync19, existsSync as existsSync21, statSync as statSync7, mkdirSync as
|
|
5601
|
+
import { readFileSync as readFileSync19, existsSync as existsSync21, statSync as statSync7, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
|
|
5403
5602
|
import { StringDecoder } from "string_decoder";
|
|
5404
5603
|
import { finished } from "stream/promises";
|
|
5405
|
-
import { join as join23, dirname as
|
|
5604
|
+
import { join as join23, dirname as dirname9, basename as basename9 } from "path";
|
|
5406
5605
|
import { createHash as createHash4 } from "crypto";
|
|
5407
5606
|
|
|
5408
5607
|
// src/state.ts
|
|
@@ -5908,7 +6107,7 @@ var State = class _State {
|
|
|
5908
6107
|
};
|
|
5909
6108
|
|
|
5910
6109
|
// src/environment.ts
|
|
5911
|
-
import { basename as
|
|
6110
|
+
import { basename as basename6 } from "path";
|
|
5912
6111
|
var API_KEY_VAR = "CREW_API_KEY";
|
|
5913
6112
|
function authorshipBullets(a, contract) {
|
|
5914
6113
|
if (!a) return [];
|
|
@@ -5961,7 +6160,7 @@ function repoSection(r, key, heading) {
|
|
|
5961
6160
|
const repo = r.config;
|
|
5962
6161
|
const branchTemplate = effectiveBranchTemplate(repo, repoPrefix(repo));
|
|
5963
6162
|
const branchName = branchExample(repo, key);
|
|
5964
|
-
const checkoutName =
|
|
6163
|
+
const checkoutName = basename6(r.dir.replace(/[/\\]+$/, ""));
|
|
5965
6164
|
const worktreeName = effectiveWorktreeDirName(
|
|
5966
6165
|
repo,
|
|
5967
6166
|
r.dir,
|
|
@@ -6848,7 +7047,7 @@ import { existsSync as existsSync18, mkdirSync as mkdirSync11, readdirSync as re
|
|
|
6848
7047
|
import { join as join19 } from "path";
|
|
6849
7048
|
|
|
6850
7049
|
// src/ticket-branch.ts
|
|
6851
|
-
import { basename as
|
|
7050
|
+
import { basename as basename7 } from "path";
|
|
6852
7051
|
|
|
6853
7052
|
// src/tracker.ts
|
|
6854
7053
|
import { hostname } from "os";
|
|
@@ -7866,7 +8065,7 @@ function worktreeForTicket(dir, cfg, t, role) {
|
|
|
7866
8065
|
const number = t.issue_id.replace(/^\D+/, "");
|
|
7867
8066
|
const { tag } = ticketBranchContext(t);
|
|
7868
8067
|
return effectiveWorktreeDirName(cfg, dir, branchName, number, (template) => renderWorktreeName(template, {
|
|
7869
|
-
dir:
|
|
8068
|
+
dir: basename7(dir.replace(/[/\\]+$/, "")),
|
|
7870
8069
|
key: t.issue_id,
|
|
7871
8070
|
title: t.title ?? void 0,
|
|
7872
8071
|
role,
|
|
@@ -9433,9 +9632,9 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
9433
9632
|
|
|
9434
9633
|
// src/runner.ts
|
|
9435
9634
|
import { spawn as spawn2, execFile, execFileSync as execFileSync4 } from "child_process";
|
|
9436
|
-
import { mkdtempSync, writeFileSync as writeFileSync13, rmSync as rmSync12 } from "fs";
|
|
9635
|
+
import { mkdtempSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync13, rmSync as rmSync12 } from "fs";
|
|
9437
9636
|
import { tmpdir as tmpdir2 } from "os";
|
|
9438
|
-
import { basename as
|
|
9637
|
+
import { basename as basename8, dirname as dirname8, join as join22 } from "path";
|
|
9439
9638
|
import { promisify } from "util";
|
|
9440
9639
|
|
|
9441
9640
|
// src/run-limit.ts
|
|
@@ -9555,8 +9754,20 @@ var SandboxRunner = class {
|
|
|
9555
9754
|
if (cfg.forgeTokenVar && o.env[cfg.forgeTokenVar] !== void 0 && !secretKeys.includes(cfg.forgeTokenVar)) secretKeys.push(cfg.forgeTokenVar);
|
|
9556
9755
|
const createEnv = { ...process.env, ...plan.setEnv };
|
|
9557
9756
|
if (cfg.forgeTokenVar && o.env[cfg.forgeTokenVar] !== void 0) createEnv[cfg.forgeTokenVar] = o.env[cfg.forgeTokenVar];
|
|
9757
|
+
let policyFile = cfg.policy;
|
|
9758
|
+
let generated;
|
|
9759
|
+
if (!policyFile && plan.sandboxPolicy) {
|
|
9760
|
+
try {
|
|
9761
|
+
generated = generateSandboxPolicy({ mirrorDir: cfg.mirrorDir, workDir: cfg.workDir, ...plan.sandboxPolicy });
|
|
9762
|
+
} catch (e) {
|
|
9763
|
+
throw new Error(`cannot generate the sandbox policy: ${e.message}`);
|
|
9764
|
+
}
|
|
9765
|
+
policyFile = policyPathFor(plan.streamPath);
|
|
9766
|
+
mkdirSync12(dirname8(policyFile), { recursive: true });
|
|
9767
|
+
writeFileSync13(policyFile, generated.yaml, { mode: 384 });
|
|
9768
|
+
}
|
|
9558
9769
|
const create2 = ["sandbox", "create", "--name", name, "--detach"];
|
|
9559
|
-
if (
|
|
9770
|
+
if (policyFile) create2.push("--policy", policyFile);
|
|
9560
9771
|
if (cfg.image) create2.push("--from", cfg.image);
|
|
9561
9772
|
if (cfg.cacheVolume) {
|
|
9562
9773
|
create2.push("--driver-config-json", JSON.stringify({
|
|
@@ -9575,6 +9786,13 @@ var SandboxRunner = class {
|
|
|
9575
9786
|
}
|
|
9576
9787
|
o.emit.emit(`sandbox_created: ${name}`, { role: plan.role, data: { event: "sandbox_created", sandbox: name, ms: Date.now() - t0 } });
|
|
9577
9788
|
const handle = { runner: "sandbox", sandbox: name, sandboxBin: cfg.bin };
|
|
9789
|
+
if (generated) {
|
|
9790
|
+
const hosts = generated.groups.flatMap((g) => g.endpoints.map((e) => `${e.host}:${e.port}`));
|
|
9791
|
+
o.emit.emit(`sandbox_policy: ${hosts.length} allowed endpoint(s)${generated.extraHosts.length ? `; declared by the repository: ${generated.extraHosts.join(", ")}` : ""}`, {
|
|
9792
|
+
role: plan.role,
|
|
9793
|
+
data: { event: "sandbox_policy", sandbox: name, file: policyFile, allowed: hosts, declared: generated.extraHosts }
|
|
9794
|
+
});
|
|
9795
|
+
}
|
|
9578
9796
|
try {
|
|
9579
9797
|
const branch = `crew-run-${name.slice(-8)}`;
|
|
9580
9798
|
const clone = cloneScript({
|
|
@@ -9586,7 +9804,7 @@ var SandboxRunner = class {
|
|
|
9586
9804
|
lockfile: cfg.depsLockfile ?? DEFAULT_CACHE_SETTINGS.depsLockfile
|
|
9587
9805
|
});
|
|
9588
9806
|
await this.os(["sandbox", "exec", "-n", name, "--no-login-shell", "--", "sh", "-c", clone]);
|
|
9589
|
-
const harness = cfg.agentBin ??
|
|
9807
|
+
const harness = cfg.agentBin ?? basename8(plan.bin);
|
|
9590
9808
|
const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
|
|
9591
9809
|
if (viaUpload) {
|
|
9592
9810
|
const tmp = mkdtempSync(join22(tmpdir2(), "crew-prompt-"));
|
|
@@ -9622,10 +9840,28 @@ var SandboxRunner = class {
|
|
|
9622
9840
|
}
|
|
9623
9841
|
async release(handle, emit) {
|
|
9624
9842
|
if (!handle.sandbox) return;
|
|
9843
|
+
await this.foldDenied(handle.sandbox, handle.sandboxBin, emit);
|
|
9625
9844
|
const t0 = Date.now();
|
|
9626
9845
|
await this.deleteQuietly(handle.sandbox, handle.sandboxBin);
|
|
9627
9846
|
emit.emit(`sandbox_deleted: ${handle.sandbox}`, { data: { event: "sandbox_deleted", sandbox: handle.sandbox, ms: Date.now() - t0 } });
|
|
9628
9847
|
}
|
|
9848
|
+
/**
|
|
9849
|
+
* Denied requests from the sandbox log become `sandbox_denied` events in the
|
|
9850
|
+
* run's event stream (CREW-2801), so an agent blocked by policy is diagnosable
|
|
9851
|
+
* from the Mac app. Read before the delete, which takes the log with it.
|
|
9852
|
+
*/
|
|
9853
|
+
async foldDenied(name, bin, emit) {
|
|
9854
|
+
let log;
|
|
9855
|
+
try {
|
|
9856
|
+
log = (await execFileP(bin ?? this.cfg.bin, ["logs", name], { timeout: 15e3, maxBuffer: 16 * 1024 * 1024 })).stdout;
|
|
9857
|
+
} catch {
|
|
9858
|
+
return;
|
|
9859
|
+
}
|
|
9860
|
+
for (const d of parseDeniedRequests(log)) {
|
|
9861
|
+
const where = `${d.host}${d.port !== void 0 ? `:${d.port}` : ""}${d.binary ? ` by ${d.binary}` : ""}`;
|
|
9862
|
+
emit.warn(`sandbox_denied: ${where}${d.count > 1 ? ` (${d.count}x)` : ""} \u2014 blocked by the sandbox policy`, { data: { event: "sandbox_denied", sandbox: name, ...d } });
|
|
9863
|
+
}
|
|
9864
|
+
}
|
|
9629
9865
|
async deleteQuietly(name, bin = this.cfg.bin) {
|
|
9630
9866
|
try {
|
|
9631
9867
|
await execFileP(bin, ["sandbox", "delete", name], { timeout: 6e4 });
|
|
@@ -9687,6 +9923,26 @@ function assemblePrompt(p) {
|
|
|
9687
9923
|
}
|
|
9688
9924
|
var AgentError = class extends Error {
|
|
9689
9925
|
};
|
|
9926
|
+
function sandboxPolicyInputFor(route2, toolchain, o) {
|
|
9927
|
+
const repos = reposOf(route2);
|
|
9928
|
+
const forgeHosts = [...new Set(repos.map((r) => r.forgeHost ?? "github.com"))];
|
|
9929
|
+
const dir = repos.find((r) => r.name === o.repo)?.dir ?? route2.dir;
|
|
9930
|
+
let declared = null;
|
|
9931
|
+
try {
|
|
9932
|
+
declared = loadRepoConfig(dir);
|
|
9933
|
+
} catch {
|
|
9934
|
+
}
|
|
9935
|
+
return {
|
|
9936
|
+
forgeHosts,
|
|
9937
|
+
tablationUrl: route2.baseUrl,
|
|
9938
|
+
authMode: toolchain.auth ?? "inherit",
|
|
9939
|
+
provider: toolchainProvider(toolchain),
|
|
9940
|
+
harnessBin: o.sandbox?.agentBin ?? o.harnessBin,
|
|
9941
|
+
...declared?.sandboxNetwork ? { extraHosts: declared.sandboxNetwork.hosts, toolchains: declared.sandboxNetwork.toolchains } : {},
|
|
9942
|
+
...o.sandbox?.mirrorDir ? { mirrorDir: o.sandbox.mirrorDir } : {},
|
|
9943
|
+
...o.sandbox?.workDir ? { workDir: o.sandbox.workDir } : {}
|
|
9944
|
+
};
|
|
9945
|
+
}
|
|
9690
9946
|
var AgentSpawnError = class extends AgentError {
|
|
9691
9947
|
};
|
|
9692
9948
|
var WorkspaceAuthRefused = class extends AgentSpawnError {
|
|
@@ -9822,7 +10078,11 @@ function planAgentRun(o) {
|
|
|
9822
10078
|
workspaceAuth: toolchain.auth === "workspace",
|
|
9823
10079
|
provider: toolchainProvider(toolchain),
|
|
9824
10080
|
maxRunMinutes: resolveMaxRunMinutes(o.route.maxRunMinutes, toolchain.maxRunMinutes),
|
|
9825
|
-
...o.ship.runner === "sandbox" ? {
|
|
10081
|
+
...o.ship.runner === "sandbox" ? {
|
|
10082
|
+
runner: "sandbox",
|
|
10083
|
+
...o.ship.sandbox ? { sandboxConfig: o.ship.sandbox } : {},
|
|
10084
|
+
sandboxPolicy: sandboxPolicyInputFor(o.route, toolchain, { harnessBin: invocation.bin, repo: o.repo, sandbox: o.ship.sandbox })
|
|
10085
|
+
} : {},
|
|
9826
10086
|
resolution: { toolchain: seat.toolchainWhy, model: seat.modelWhy },
|
|
9827
10087
|
warnings: [...seat.warnings ?? [], ...expanded.warnings],
|
|
9828
10088
|
prompt,
|
|
@@ -9926,7 +10186,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
|
|
|
9926
10186
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
9927
10187
|
function openSink(path) {
|
|
9928
10188
|
try {
|
|
9929
|
-
|
|
10189
|
+
mkdirSync13(dirname9(path), { recursive: true });
|
|
9930
10190
|
return createWriteStream(path, { flags: "a" });
|
|
9931
10191
|
} catch {
|
|
9932
10192
|
return void 0;
|
|
@@ -10215,7 +10475,7 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey, hintMoved)
|
|
|
10215
10475
|
if (sent.counterWarning) emit.warn(sent.counterWarning, { step: "agent" });
|
|
10216
10476
|
if (sent.counterRetry) {
|
|
10217
10477
|
try {
|
|
10218
|
-
queueTicketCounters(stateDirOfStream(id.streamPath),
|
|
10478
|
+
queueTicketCounters(stateDirOfStream(id.streamPath), basename9(id.streamPath, ".jsonl"), {
|
|
10219
10479
|
version: 1,
|
|
10220
10480
|
route: id.route,
|
|
10221
10481
|
role: id.role,
|
|
@@ -10230,7 +10490,7 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey, hintMoved)
|
|
|
10230
10490
|
} catch (e) {
|
|
10231
10491
|
if (e instanceof AgentLogUnreachableError) {
|
|
10232
10492
|
try {
|
|
10233
|
-
queueAgentRun(stateDirOfStream(id.streamPath),
|
|
10493
|
+
queueAgentRun(stateDirOfStream(id.streamPath), basename9(id.streamPath, ".jsonl"), {
|
|
10234
10494
|
version: 1,
|
|
10235
10495
|
route: id.route,
|
|
10236
10496
|
role: id.role,
|
|
@@ -10268,7 +10528,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10268
10528
|
const id = identityOf(plan, adapter.harness);
|
|
10269
10529
|
let streamFd;
|
|
10270
10530
|
try {
|
|
10271
|
-
|
|
10531
|
+
mkdirSync13(dirname9(plan.streamPath), { recursive: true });
|
|
10272
10532
|
streamFd = openSync3(plan.streamPath, "a");
|
|
10273
10533
|
} catch (e) {
|
|
10274
10534
|
void endSink(eventsSink);
|
|
@@ -11543,7 +11803,7 @@ function readSandboxState(stateDir) {
|
|
|
11543
11803
|
}
|
|
11544
11804
|
}
|
|
11545
11805
|
function writeSandboxState(stateDir, state) {
|
|
11546
|
-
|
|
11806
|
+
mkdirSync14(stateDir, { recursive: true });
|
|
11547
11807
|
const file = sandboxStatePath(stateDir);
|
|
11548
11808
|
writeFileSync15(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
|
|
11549
11809
|
`);
|
|
@@ -11596,17 +11856,17 @@ async function buildSandboxImage(plan, deps) {
|
|
|
11596
11856
|
return { kind: "reused", tag: plan.tag };
|
|
11597
11857
|
}
|
|
11598
11858
|
const logDir = sandboxLogDir(deps.stateDir);
|
|
11599
|
-
|
|
11859
|
+
mkdirSync14(logDir, { recursive: true });
|
|
11600
11860
|
const logPath = join26(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
11601
11861
|
const builtLayers = [];
|
|
11602
11862
|
for (const layer of plan.layers) {
|
|
11603
11863
|
if (deps.hasImage(layer.tag)) continue;
|
|
11604
11864
|
const ctx = join26(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
|
|
11605
|
-
|
|
11865
|
+
mkdirSync14(ctx, { recursive: true });
|
|
11606
11866
|
try {
|
|
11607
11867
|
writeFileSync15(join26(ctx, "Dockerfile"), layer.dockerfile);
|
|
11608
11868
|
for (const [name, content] of Object.entries(layer.files)) {
|
|
11609
|
-
|
|
11869
|
+
mkdirSync14(dirname10(join26(ctx, name)), { recursive: true });
|
|
11610
11870
|
writeFileSync15(join26(ctx, name), content);
|
|
11611
11871
|
}
|
|
11612
11872
|
const code = await deps.run(["build", "--tag", layer.tag, "--file", join26(ctx, "Dockerfile"), ctx], logPath);
|
|
@@ -11787,7 +12047,7 @@ function roleCapWarnings(ship) {
|
|
|
11787
12047
|
|
|
11788
12048
|
// src/selftest.ts
|
|
11789
12049
|
import { spawn as spawn5 } from "child_process";
|
|
11790
|
-
import { existsSync as existsSync25, mkdirSync as
|
|
12050
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync2, readFileSync as readFileSync22, rmSync as rmSync14, writeFileSync as writeFileSync16 } from "fs";
|
|
11791
12051
|
import { tmpdir as tmpdir4 } from "os";
|
|
11792
12052
|
import { join as join27 } from "path";
|
|
11793
12053
|
var SELFTEST_TIMEOUT_MS = 12e4;
|
|
@@ -11995,7 +12255,7 @@ function readSelftest(stateDir) {
|
|
|
11995
12255
|
function recordSelftest(stateDir, results) {
|
|
11996
12256
|
const all = readSelftest(stateDir);
|
|
11997
12257
|
for (const r of results) all[r.toolchain] = r;
|
|
11998
|
-
|
|
12258
|
+
mkdirSync15(stateDir, { recursive: true });
|
|
11999
12259
|
writeFileSync16(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
|
|
12000
12260
|
`);
|
|
12001
12261
|
}
|
|
@@ -12008,7 +12268,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
12008
12268
|
}
|
|
12009
12269
|
|
|
12010
12270
|
// src/cli.ts
|
|
12011
|
-
import { resolve as resolve6, dirname as
|
|
12271
|
+
import { resolve as resolve6, dirname as dirname28, join as join49, basename as basename16 } from "path";
|
|
12012
12272
|
import { fileURLToPath } from "url";
|
|
12013
12273
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
12014
12274
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
@@ -12205,8 +12465,8 @@ function rosterMarkdown(roster, meId) {
|
|
|
12205
12465
|
}
|
|
12206
12466
|
|
|
12207
12467
|
// src/connect-resume.ts
|
|
12208
|
-
import { mkdirSync as
|
|
12209
|
-
import { dirname as
|
|
12468
|
+
import { mkdirSync as mkdirSync16, readFileSync as readFileSync23, rmSync as rmSync15, writeFileSync as writeFileSync17 } from "fs";
|
|
12469
|
+
import { dirname as dirname11, join as join28 } from "path";
|
|
12210
12470
|
function lastSessionFile(stateDir) {
|
|
12211
12471
|
return join28(stateDir, "connect-session.json");
|
|
12212
12472
|
}
|
|
@@ -12226,7 +12486,7 @@ function readPointers(stateDir) {
|
|
|
12226
12486
|
}
|
|
12227
12487
|
function writePointers(stateDir, pointers) {
|
|
12228
12488
|
const file = lastSessionFile(stateDir);
|
|
12229
|
-
|
|
12489
|
+
mkdirSync16(dirname11(file), { recursive: true });
|
|
12230
12490
|
writeFileSync17(file, `${JSON.stringify(pointers, null, 2)}
|
|
12231
12491
|
`, { mode: 384 });
|
|
12232
12492
|
}
|
|
@@ -12312,8 +12572,8 @@ async function signOutSessions(o) {
|
|
|
12312
12572
|
}
|
|
12313
12573
|
|
|
12314
12574
|
// src/reset.ts
|
|
12315
|
-
import { existsSync as existsSync26, mkdirSync as
|
|
12316
|
-
import { dirname as
|
|
12575
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync24, readdirSync as readdirSync12, rmSync as rmSync16, statSync as statSync8, writeFileSync as writeFileSync18 } from "fs";
|
|
12576
|
+
import { dirname as dirname12, join as join29 } from "path";
|
|
12317
12577
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
12318
12578
|
function clonedCheckoutsFile(stateDir) {
|
|
12319
12579
|
return join29(stateDir, "cloned-checkouts.json");
|
|
@@ -12330,7 +12590,7 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
12330
12590
|
const known = readClonedCheckouts(stateDir);
|
|
12331
12591
|
if (known.includes(dir)) return;
|
|
12332
12592
|
const file = clonedCheckoutsFile(stateDir);
|
|
12333
|
-
|
|
12593
|
+
mkdirSync17(dirname12(file), { recursive: true });
|
|
12334
12594
|
writeFileSync18(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
12335
12595
|
`);
|
|
12336
12596
|
}
|
|
@@ -12511,9 +12771,9 @@ async function runDeprovision(o, fx, deps) {
|
|
|
12511
12771
|
}
|
|
12512
12772
|
|
|
12513
12773
|
// src/pool-member.ts
|
|
12514
|
-
import { mkdirSync as
|
|
12774
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync25, renameSync as renameSync4, writeFileSync as writeFileSync19 } from "fs";
|
|
12515
12775
|
import { homedir as homedir8 } from "os";
|
|
12516
|
-
import { dirname as
|
|
12776
|
+
import { dirname as dirname13, join as join30 } from "path";
|
|
12517
12777
|
var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
|
|
12518
12778
|
function backoffMs(attempt) {
|
|
12519
12779
|
return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
|
|
@@ -12715,7 +12975,7 @@ function fileMemberStore(dir) {
|
|
|
12715
12975
|
}
|
|
12716
12976
|
},
|
|
12717
12977
|
write(f) {
|
|
12718
|
-
|
|
12978
|
+
mkdirSync18(dirname13(file), { recursive: true, mode: 448 });
|
|
12719
12979
|
const tmp = `${file}.tmp`;
|
|
12720
12980
|
writeFileSync19(tmp, `${JSON.stringify(f)}
|
|
12721
12981
|
`, { mode: 384 });
|
|
@@ -12775,8 +13035,8 @@ import { hostname as hostname3 } from "os";
|
|
|
12775
13035
|
// src/machine-id.ts
|
|
12776
13036
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
12777
13037
|
import { createHash as createHash7 } from "crypto";
|
|
12778
|
-
import { existsSync as existsSync27, mkdirSync as
|
|
12779
|
-
import { dirname as
|
|
13038
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync19, readFileSync as readFileSync26, writeFileSync as writeFileSync20 } from "fs";
|
|
13039
|
+
import { dirname as dirname14, join as join31 } from "path";
|
|
12780
13040
|
function machineIdPathFor(stateDir) {
|
|
12781
13041
|
return join31(stateDir, "machine-id");
|
|
12782
13042
|
}
|
|
@@ -12810,7 +13070,7 @@ function ensureMachineId(stateDir, opts = {}) {
|
|
|
12810
13070
|
const raw = (opts.read ?? readRawMachineId)();
|
|
12811
13071
|
if (!raw) return void 0;
|
|
12812
13072
|
const hashed = sha256(raw);
|
|
12813
|
-
|
|
13073
|
+
mkdirSync19(dirname14(path), { recursive: true });
|
|
12814
13074
|
writeFileSync20(path, `${hashed}
|
|
12815
13075
|
`, { mode: 384 });
|
|
12816
13076
|
return hashed;
|
|
@@ -12833,8 +13093,8 @@ import { parseDocument as parseDocument2 } from "yaml";
|
|
|
12833
13093
|
|
|
12834
13094
|
// src/repos-cmd.ts
|
|
12835
13095
|
import { homedir as homedir9 } from "os";
|
|
12836
|
-
import { basename as
|
|
12837
|
-
import { isMap, isSeq, parseDocument, stringify } from "yaml";
|
|
13096
|
+
import { basename as basename10, join as join32, resolve as resolve3 } from "path";
|
|
13097
|
+
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
12838
13098
|
var ReposError = class extends Error {
|
|
12839
13099
|
};
|
|
12840
13100
|
function normalizeRemote(url) {
|
|
@@ -12905,7 +13165,7 @@ function configuredRepos(text2, route2, base) {
|
|
|
12905
13165
|
});
|
|
12906
13166
|
}
|
|
12907
13167
|
const dir = m.get("dir");
|
|
12908
|
-
return dir ? [{ name:
|
|
13168
|
+
return dir ? [{ name: basename10(String(dir)), dir: expandDir(String(dir), base) }] : [];
|
|
12909
13169
|
}
|
|
12910
13170
|
function cloneBaseOf(text2, route2, base, isCheckout) {
|
|
12911
13171
|
const m = routeMapOf(parseDocument(text2), route2);
|
|
@@ -12994,7 +13254,7 @@ function planRepoAdd(i) {
|
|
|
12994
13254
|
throw new ReposError(`${target} is already configured on route ${i.route}`);
|
|
12995
13255
|
}
|
|
12996
13256
|
const matched = matchRepoName(i.origin, i.resolved);
|
|
12997
|
-
const name = matched ?? i.name ?? normalizeRemote(i.origin)?.split("/").pop() ??
|
|
13257
|
+
const name = matched ?? i.name ?? normalizeRemote(i.origin)?.split("/").pop() ?? basename10(target);
|
|
12998
13258
|
let warning;
|
|
12999
13259
|
if (!matched) {
|
|
13000
13260
|
const known = Object.values(i.resolved?.repoNames ?? {});
|
|
@@ -13047,7 +13307,7 @@ function planRepoRemove(text2, base, route2, path) {
|
|
|
13047
13307
|
return { text: String(doc), name: hit.name };
|
|
13048
13308
|
}
|
|
13049
13309
|
function renderBlock(m) {
|
|
13050
|
-
return
|
|
13310
|
+
return stringify2([m.toJSON()]).trimEnd();
|
|
13051
13311
|
}
|
|
13052
13312
|
var LOCAL_RELEASE_MODES = /* @__PURE__ */ new Set(["integrate", "external"]);
|
|
13053
13313
|
function planRepoRow(i) {
|
|
@@ -13454,7 +13714,7 @@ host=${host}
|
|
|
13454
13714
|
|
|
13455
13715
|
// src/tunnel.ts
|
|
13456
13716
|
import { spawn as spawn6 } from "child_process";
|
|
13457
|
-
import { existsSync as existsSync29, mkdirSync as
|
|
13717
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync20, readFileSync as readFileSync28, writeFileSync as writeFileSync22, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
|
|
13458
13718
|
import { randomInt } from "crypto";
|
|
13459
13719
|
import { join as join33 } from "path";
|
|
13460
13720
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
@@ -13541,7 +13801,7 @@ function readPersistedTunnel(stateDir, workspaceId) {
|
|
|
13541
13801
|
}
|
|
13542
13802
|
function writePersistedTunnel(stateDir, state) {
|
|
13543
13803
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
13544
|
-
|
|
13804
|
+
mkdirSync20(join33(stateDir, "passengers"), { recursive: true });
|
|
13545
13805
|
writeFileSync22(path, `${JSON.stringify(state, null, 2)}
|
|
13546
13806
|
`);
|
|
13547
13807
|
}
|
|
@@ -15005,8 +15265,8 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
15005
15265
|
}
|
|
15006
15266
|
|
|
15007
15267
|
// src/repo-refresh.ts
|
|
15008
|
-
import { existsSync as existsSync31, mkdirSync as
|
|
15009
|
-
import { dirname as
|
|
15268
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync21, readFileSync as readFileSync29, writeFileSync as writeFileSync24 } from "fs";
|
|
15269
|
+
import { dirname as dirname16 } from "path";
|
|
15010
15270
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
15011
15271
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
15012
15272
|
var lastAsked = /* @__PURE__ */ new Map();
|
|
@@ -15038,7 +15298,7 @@ function persistRepoRows(stateDir, routeName, rows) {
|
|
|
15038
15298
|
if (!existsSync31(path)) return;
|
|
15039
15299
|
const raw = JSON.parse(readFileSync29(path, "utf8"));
|
|
15040
15300
|
applyRepoRows(raw, rows);
|
|
15041
|
-
|
|
15301
|
+
mkdirSync21(dirname16(path), { recursive: true });
|
|
15042
15302
|
writeFileSync24(path, `${JSON.stringify(raw, null, 2)}
|
|
15043
15303
|
`);
|
|
15044
15304
|
}
|
|
@@ -15489,9 +15749,9 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
15489
15749
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
15490
15750
|
|
|
15491
15751
|
// src/connect-wizard.ts
|
|
15492
|
-
import { delimiter, dirname as
|
|
15493
|
-
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync32, mkdirSync as
|
|
15494
|
-
import { stringify as
|
|
15752
|
+
import { delimiter, dirname as dirname17, join as join35 } from "path";
|
|
15753
|
+
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync32, mkdirSync as mkdirSync22, writeFileSync as writeFileSync25 } from "fs";
|
|
15754
|
+
import { stringify as stringify3 } from "yaml";
|
|
15495
15755
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
15496
15756
|
function shouldRunWizard(opts) {
|
|
15497
15757
|
if (!opts.isTTY) return false;
|
|
@@ -15538,9 +15798,9 @@ async function runWizard(p, env) {
|
|
|
15538
15798
|
if (entered && isExec(entered)) agentBin = entered;
|
|
15539
15799
|
else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
|
|
15540
15800
|
}
|
|
15541
|
-
const nodeDir =
|
|
15801
|
+
const nodeDir = dirname17(env.nodePath);
|
|
15542
15802
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
15543
|
-
const baseDirs = [nodeDir, pnpmPath ?
|
|
15803
|
+
const baseDirs = [nodeDir, pnpmPath ? dirname17(pnpmPath) : void 0];
|
|
15544
15804
|
p.say(
|
|
15545
15805
|
`
|
|
15546
15806
|
Host Passengers lets this machine run the agent sessions that other people's
|
|
@@ -15551,14 +15811,14 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
|
|
|
15551
15811
|
p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
|
|
15552
15812
|
}
|
|
15553
15813
|
const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
15554
|
-
const extraPath = deriveExtraPath([...baseDirs, dockerPath ?
|
|
15814
|
+
const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname17(dockerPath) : void 0]);
|
|
15555
15815
|
if (extraPath) {
|
|
15556
15816
|
p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
|
|
15557
15817
|
}
|
|
15558
15818
|
return { name, platform: env.platform, agentBin, extraPath, hostPassengers };
|
|
15559
15819
|
}
|
|
15560
15820
|
function renderShipBlock(a) {
|
|
15561
|
-
return
|
|
15821
|
+
return stringify3({
|
|
15562
15822
|
ship: {
|
|
15563
15823
|
name: a.name,
|
|
15564
15824
|
platform: a.platform,
|
|
@@ -15573,7 +15833,7 @@ routes:
|
|
|
15573
15833
|
${routeBlock}`;
|
|
15574
15834
|
}
|
|
15575
15835
|
function writeNewConfig(path, text2) {
|
|
15576
|
-
|
|
15836
|
+
mkdirSync22(dirname17(path), { recursive: true });
|
|
15577
15837
|
try {
|
|
15578
15838
|
writeFileSync25(path, text2, { mode: 384, flag: "wx" });
|
|
15579
15839
|
return true;
|
|
@@ -15609,7 +15869,7 @@ Wrote ${o.configPath}.
|
|
|
15609
15869
|
// src/connect-json.ts
|
|
15610
15870
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
15611
15871
|
import { homedir as homedir10 } from "os";
|
|
15612
|
-
import { dirname as
|
|
15872
|
+
import { dirname as dirname18, join as join36 } from "path";
|
|
15613
15873
|
import { delimiter as delimiter2 } from "path";
|
|
15614
15874
|
var CHOICE_EXIT_CODE = 3;
|
|
15615
15875
|
function createEmitter(json2, write) {
|
|
@@ -15675,7 +15935,7 @@ function flagWizardAnswers(f, env) {
|
|
|
15675
15935
|
const agentBin = f.agentBin ?? found?.bin;
|
|
15676
15936
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
15677
15937
|
const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
15678
|
-
const extraPath = f.extraPath ?? deriveExtraPath([
|
|
15938
|
+
const extraPath = f.extraPath ?? deriveExtraPath([dirname18(env.nodePath), pnpmPath ? dirname18(pnpmPath) : void 0, dockerPath ? dirname18(dockerPath) : void 0]);
|
|
15679
15939
|
return {
|
|
15680
15940
|
answers: {
|
|
15681
15941
|
name: f.shipName?.trim() || env.hostname,
|
|
@@ -15689,8 +15949,8 @@ function flagWizardAnswers(f, env) {
|
|
|
15689
15949
|
}
|
|
15690
15950
|
|
|
15691
15951
|
// src/release-run.ts
|
|
15692
|
-
import { readFileSync as readFileSync30, writeFileSync as writeFileSync26, existsSync as existsSync33, copyFileSync, mkdirSync as
|
|
15693
|
-
import { dirname as
|
|
15952
|
+
import { readFileSync as readFileSync30, writeFileSync as writeFileSync26, existsSync as existsSync33, copyFileSync, mkdirSync as mkdirSync23, mkdtempSync as mkdtempSync3, rmSync as rmSync17 } from "fs";
|
|
15953
|
+
import { dirname as dirname19, join as join37 } from "path";
|
|
15694
15954
|
import { tmpdir as tmpdir5 } from "os";
|
|
15695
15955
|
|
|
15696
15956
|
// src/release.ts
|
|
@@ -16576,7 +16836,7 @@ function openReleaseCheckout(o) {
|
|
|
16576
16836
|
for (const f of o.repo.worktrees.copy) {
|
|
16577
16837
|
const src = join37(o.cwd, f);
|
|
16578
16838
|
if (!existsSync33(src)) continue;
|
|
16579
|
-
|
|
16839
|
+
mkdirSync23(dirname19(join37(dir, f)), { recursive: true });
|
|
16580
16840
|
copyFileSync(src, join37(dir, f));
|
|
16581
16841
|
}
|
|
16582
16842
|
return { dir, branch, close };
|
|
@@ -17895,8 +18155,8 @@ function nextRoles(decision, ran) {
|
|
|
17895
18155
|
}
|
|
17896
18156
|
|
|
17897
18157
|
// src/ship-name.ts
|
|
17898
|
-
import { existsSync as existsSync35, mkdirSync as
|
|
17899
|
-
import { dirname as
|
|
18158
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync24, readFileSync as readFileSync31, renameSync as renameSync5, writeFileSync as writeFileSync27 } from "fs";
|
|
18159
|
+
import { dirname as dirname20 } from "path";
|
|
17900
18160
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
17901
18161
|
var ShipNameError = class extends Error {
|
|
17902
18162
|
};
|
|
@@ -17975,7 +18235,7 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
17975
18235
|
}
|
|
17976
18236
|
if (raw.shipName === name) return;
|
|
17977
18237
|
raw.shipName = name;
|
|
17978
|
-
|
|
18238
|
+
mkdirSync24(dirname20(resolvedFile), { recursive: true });
|
|
17979
18239
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
17980
18240
|
writeFileSync27(tmp, `${JSON.stringify(raw, null, 2)}
|
|
17981
18241
|
`);
|
|
@@ -18668,8 +18928,8 @@ function describeCapability(i) {
|
|
|
18668
18928
|
}
|
|
18669
18929
|
|
|
18670
18930
|
// src/agents-sync-run.ts
|
|
18671
|
-
import { existsSync as existsSync36, mkdirSync as
|
|
18672
|
-
import { dirname as
|
|
18931
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync25, readFileSync as readFileSync32, writeFileSync as writeFileSync28 } from "fs";
|
|
18932
|
+
import { dirname as dirname21 } from "path";
|
|
18673
18933
|
async function runAgentsSync(target, o) {
|
|
18674
18934
|
const dryRun = o.dryRun ?? false;
|
|
18675
18935
|
const force = o.force ?? false;
|
|
@@ -18706,7 +18966,7 @@ async function runAgentsSync(target, o) {
|
|
|
18706
18966
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
18707
18967
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
18708
18968
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
18709
|
-
|
|
18969
|
+
mkdirSync25(dirname21(resolvedPath), { recursive: true });
|
|
18710
18970
|
writeFileSync28(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
18711
18971
|
`);
|
|
18712
18972
|
if (target.resolved) {
|
|
@@ -18812,11 +19072,11 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
18812
19072
|
// src/repos-check.ts
|
|
18813
19073
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
18814
19074
|
import { readFileSync as readFileSync33 } from "fs";
|
|
18815
|
-
import { basename as
|
|
19075
|
+
import { basename as basename11 } from "path";
|
|
18816
19076
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
18817
19077
|
function checkHookSyntax(script, shell) {
|
|
18818
19078
|
const spec = resolveShell(shell);
|
|
18819
|
-
const name =
|
|
19079
|
+
const name = basename11(spec.bin).replace(/\.exe$/i, "").toLowerCase();
|
|
18820
19080
|
let run3;
|
|
18821
19081
|
if (POSIX_SHELL.test(name)) {
|
|
18822
19082
|
run3 = spawnSync9(spec.bin, ["-n"], { input: script, encoding: "utf8" });
|
|
@@ -18835,7 +19095,7 @@ function checkHookSyntax(script, shell) {
|
|
|
18835
19095
|
return { status: "error", message };
|
|
18836
19096
|
}
|
|
18837
19097
|
function renderSamples(cfg, dir) {
|
|
18838
|
-
const checkout =
|
|
19098
|
+
const checkout = basename11(dir.replace(/[/\\]+$/, ""));
|
|
18839
19099
|
const tagged = { key: "ISSUE-123", title: "Fix the widget", role: "dev", tag: "TABL-123", prefix: "tabl", lowercase: cfg.branch.lowercase };
|
|
18840
19100
|
const untagged = { key: "ISSUE-123", title: "Fix the widget", role: "dev", lowercase: cfg.branch.lowercase };
|
|
18841
19101
|
return [["with a project tag", tagged], ["without a project tag", untagged]].map(([label, ctx]) => {
|
|
@@ -19299,7 +19559,7 @@ ${run3.err}` });
|
|
|
19299
19559
|
}
|
|
19300
19560
|
|
|
19301
19561
|
// src/sample-ticket.ts
|
|
19302
|
-
import { basename as
|
|
19562
|
+
import { basename as basename12, dirname as dirname22, join as join38 } from "path";
|
|
19303
19563
|
var SAMPLE_REPO = "crew-sample";
|
|
19304
19564
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
19305
19565
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -19322,7 +19582,7 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
19322
19582
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
19323
19583
|
`;
|
|
19324
19584
|
function sampleDir(existing, reposBase, workspace) {
|
|
19325
|
-
if (existing.length > 0) return join38(
|
|
19585
|
+
if (existing.length > 0) return join38(dirname22(existing[0]), SAMPLE_REPO);
|
|
19326
19586
|
return join38(reposBase, workspace, SAMPLE_REPO);
|
|
19327
19587
|
}
|
|
19328
19588
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
@@ -19368,7 +19628,7 @@ function planSampleTicket(i) {
|
|
|
19368
19628
|
|
|
19369
19629
|
// src/worktree-sweep.ts
|
|
19370
19630
|
import { existsSync as existsSync37, readFileSync as readFileSync34, readdirSync as readdirSync14 } from "fs";
|
|
19371
|
-
import { basename as
|
|
19631
|
+
import { basename as basename13, dirname as dirname23, join as join39, resolve as resolve4 } from "path";
|
|
19372
19632
|
function planWorktreeSweep(target, tickets, contract) {
|
|
19373
19633
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
19374
19634
|
const verified = contract.statuses.verified;
|
|
@@ -19409,7 +19669,7 @@ function ticketForWorktree(w, byNumber, contract) {
|
|
|
19409
19669
|
const t = num ? byNumber.get(num) : void 0;
|
|
19410
19670
|
if (t && ticketStems(t).has(stem)) return t;
|
|
19411
19671
|
}
|
|
19412
|
-
const dir =
|
|
19672
|
+
const dir = basename13(w.path);
|
|
19413
19673
|
const m = DIR_NUMBER.exec(dir);
|
|
19414
19674
|
if (m && byNumber.has(m[1])) return byNumber.get(m[1]);
|
|
19415
19675
|
if (!w.branch) {
|
|
@@ -19425,15 +19685,15 @@ function worktreeTicketNumbers(target) {
|
|
|
19425
19685
|
const n = /-(\d+)$/.exec(BRANCH_STEM.exec(w.branch)?.[1] ?? "")?.[1];
|
|
19426
19686
|
if (n) nums.add(n);
|
|
19427
19687
|
}
|
|
19428
|
-
const m = DIR_NUMBER_SUFFIXED.exec(
|
|
19688
|
+
const m = DIR_NUMBER_SUFFIXED.exec(basename13(w.path));
|
|
19429
19689
|
if (m) nums.add(m[1]);
|
|
19430
19690
|
}
|
|
19431
19691
|
for (const d of siblingWorktreeDirs(target)) nums.add(d.number);
|
|
19432
19692
|
return [...nums];
|
|
19433
19693
|
}
|
|
19434
19694
|
function siblingWorktreeDirs(target) {
|
|
19435
|
-
const parent =
|
|
19436
|
-
const stem = `${
|
|
19695
|
+
const parent = dirname23(target.dir.replace(/[/\\]+$/, ""));
|
|
19696
|
+
const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
19437
19697
|
const registered = new Set(worktrees(target.dir).map((w) => resolve4(w.path)));
|
|
19438
19698
|
let names;
|
|
19439
19699
|
try {
|
|
@@ -19458,7 +19718,7 @@ function orphanedWorktreeDirs(target, tickets, contract) {
|
|
|
19458
19718
|
}
|
|
19459
19719
|
function warnOrphanedWorktreeDirs(paths, log, held) {
|
|
19460
19720
|
for (const p of paths) {
|
|
19461
|
-
const number = DIR_NUMBER_SUFFIXED.exec(
|
|
19721
|
+
const number = DIR_NUMBER_SUFFIXED.exec(basename13(p))?.[1];
|
|
19462
19722
|
const when = held && number ? noteHeldWorktree(held.state, { repo: held.repo, path: p, ticket: number, status: "deployed", reason: "orphaned directory" }) : "first";
|
|
19463
19723
|
if (when) {
|
|
19464
19724
|
log.warn(`orphaned directory ${p}: its ticket is deployed but it is not a registered worktree; the sweep will not delete it \u2014 remove it by hand`, { step: "worktree" });
|
|
@@ -19602,7 +19862,7 @@ function isTicketBranch(name, base, stems) {
|
|
|
19602
19862
|
}
|
|
19603
19863
|
function sweepCandidatesExist(cwd, base) {
|
|
19604
19864
|
const linked = worktrees(cwd).slice(1);
|
|
19605
|
-
if (linked.some((w) => DIR_NUMBER_SUFFIXED.test(
|
|
19865
|
+
if (linked.some((w) => DIR_NUMBER_SUFFIXED.test(basename13(w.path)) || w.branch && BRANCH_STEM.test(w.branch))) return true;
|
|
19606
19866
|
if (siblingWorktreeDirs({ name: "", dir: cwd }).length) return true;
|
|
19607
19867
|
return localBranches(cwd).some((b) => b !== base && /-\d+(?:-.*)?$/.test(b));
|
|
19608
19868
|
}
|
|
@@ -19698,11 +19958,11 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
19698
19958
|
}
|
|
19699
19959
|
|
|
19700
19960
|
// src/cli.ts
|
|
19701
|
-
import { realpathSync as realpathSync3, readFileSync as readFileSync40, existsSync as existsSync45, mkdirSync as
|
|
19961
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync40, existsSync as existsSync45, mkdirSync as mkdirSync32, writeFileSync as writeFileSync33, rmSync as rmSync23, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync14, readdirSync as readdirSync20 } from "fs";
|
|
19702
19962
|
import { createInterface } from "readline/promises";
|
|
19703
19963
|
|
|
19704
19964
|
// src/periscope-cmd.ts
|
|
19705
|
-
import { existsSync as existsSync39, mkdirSync as
|
|
19965
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync26, readFileSync as readFileSync35, rmSync as rmSync19, writeFileSync as writeFileSync29 } from "fs";
|
|
19706
19966
|
import { join as join41 } from "path";
|
|
19707
19967
|
var PeriscopeError = class extends Error {
|
|
19708
19968
|
};
|
|
@@ -19720,7 +19980,7 @@ function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
|
19720
19980
|
}
|
|
19721
19981
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
19722
19982
|
try {
|
|
19723
|
-
|
|
19983
|
+
mkdirSync26(priorDir(stateDir), { recursive: true });
|
|
19724
19984
|
writeFileSync29(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
19725
19985
|
} catch {
|
|
19726
19986
|
}
|
|
@@ -20223,8 +20483,8 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
|
20223
20483
|
|
|
20224
20484
|
// src/ssh-keys.ts
|
|
20225
20485
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
20226
|
-
import { existsSync as existsSync40, mkdirSync as
|
|
20227
|
-
import { dirname as
|
|
20486
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync27, readFileSync as readFileSync36, chmodSync as chmodSync3 } from "fs";
|
|
20487
|
+
import { dirname as dirname24, join as join43 } from "path";
|
|
20228
20488
|
var SshKeyError = class extends Error {
|
|
20229
20489
|
};
|
|
20230
20490
|
function shipSshKeyPathFor(stateDir) {
|
|
@@ -20234,7 +20494,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
20234
20494
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
20235
20495
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
20236
20496
|
if (!existsSync40(privateKeyPath)) {
|
|
20237
|
-
|
|
20497
|
+
mkdirSync27(dirname24(privateKeyPath), { recursive: true });
|
|
20238
20498
|
try {
|
|
20239
20499
|
execFileSync11("ssh-keygen", [
|
|
20240
20500
|
"-t",
|
|
@@ -20421,12 +20681,12 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
20421
20681
|
import { createServer as createHttpServer } from "http";
|
|
20422
20682
|
import { spawn as spawn8 } from "child_process";
|
|
20423
20683
|
import { randomBytes } from "crypto";
|
|
20424
|
-
import { existsSync as existsSync41, mkdirSync as
|
|
20684
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync29, readFileSync as readFileSync37, writeFileSync as writeFileSync30, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
|
|
20425
20685
|
import { join as join44 } from "path";
|
|
20426
20686
|
|
|
20427
20687
|
// src/passenger-mirror.ts
|
|
20428
|
-
import { mkdirSync as
|
|
20429
|
-
import { dirname as
|
|
20688
|
+
import { mkdirSync as mkdirSync28 } from "fs";
|
|
20689
|
+
import { dirname as dirname25 } from "path";
|
|
20430
20690
|
function mirrorRepoOf(spec) {
|
|
20431
20691
|
const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
|
|
20432
20692
|
if (fromOrigin) return fromOrigin;
|
|
@@ -20444,9 +20704,9 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
|
|
|
20444
20704
|
continue;
|
|
20445
20705
|
}
|
|
20446
20706
|
try {
|
|
20447
|
-
const parent =
|
|
20448
|
-
|
|
20449
|
-
|
|
20707
|
+
const parent = dirname25(spec.dir);
|
|
20708
|
+
mkdirSync28(parent, { recursive: true });
|
|
20709
|
+
mkdirSync28(spec.dir, { recursive: true });
|
|
20450
20710
|
const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
|
|
20451
20711
|
out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
|
|
20452
20712
|
} catch (e) {
|
|
@@ -20565,7 +20825,7 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
|
20565
20825
|
}
|
|
20566
20826
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
20567
20827
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
20568
|
-
|
|
20828
|
+
mkdirSync29(join44(stateDir, "passengers"), { recursive: true });
|
|
20569
20829
|
writeFileSync30(path, `${JSON.stringify(state, null, 2)}
|
|
20570
20830
|
`);
|
|
20571
20831
|
}
|
|
@@ -20675,7 +20935,7 @@ function describeCause(err3) {
|
|
|
20675
20935
|
}
|
|
20676
20936
|
|
|
20677
20937
|
// src/daemon.ts
|
|
20678
|
-
import { existsSync as existsSync42, mkdirSync as
|
|
20938
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, readdirSync as readdirSync17, rmSync as rmSync20, statSync as statSync12, writeFileSync as writeFileSync31 } from "fs";
|
|
20679
20939
|
import { join as join45 } from "path";
|
|
20680
20940
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
20681
20941
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
@@ -20789,7 +21049,7 @@ function daemonStampFor(stateDir) {
|
|
|
20789
21049
|
return join45(stateDir, "daemon.started");
|
|
20790
21050
|
}
|
|
20791
21051
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
20792
|
-
|
|
21052
|
+
mkdirSync30(stateDir, { recursive: true });
|
|
20793
21053
|
rmSync20(reloadSentinelFor(stateDir), { force: true });
|
|
20794
21054
|
writeFileSync31(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
20795
21055
|
`);
|
|
@@ -21373,7 +21633,7 @@ import {
|
|
|
21373
21633
|
} from "fs";
|
|
21374
21634
|
import { createConnection } from "net";
|
|
21375
21635
|
import { createServer } from "http";
|
|
21376
|
-
import { basename as
|
|
21636
|
+
import { basename as basename14, dirname as dirname26, join as join46 } from "path";
|
|
21377
21637
|
import { execFile as execFile3, spawn as spawn10 } from "child_process";
|
|
21378
21638
|
|
|
21379
21639
|
// src/periscope-control.ts
|
|
@@ -21689,7 +21949,7 @@ function candidates(live, rotated) {
|
|
|
21689
21949
|
const out = [live];
|
|
21690
21950
|
if (!rotated) return out;
|
|
21691
21951
|
try {
|
|
21692
|
-
for (const n of readdirSync18(
|
|
21952
|
+
for (const n of readdirSync18(dirname26(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join46(dirname26(live), n));
|
|
21693
21953
|
} catch {
|
|
21694
21954
|
}
|
|
21695
21955
|
return out;
|
|
@@ -21812,7 +22072,7 @@ function pauseDoc(state) {
|
|
|
21812
22072
|
}
|
|
21813
22073
|
function describeStream(r) {
|
|
21814
22074
|
return {
|
|
21815
|
-
run:
|
|
22075
|
+
run: basename14(r.streamPath).replace(/\.jsonl$/, ""),
|
|
21816
22076
|
pid: r.pid,
|
|
21817
22077
|
ticket: r.ticket,
|
|
21818
22078
|
role: r.role,
|
|
@@ -21849,7 +22109,7 @@ async function tailReply(path, q2, rotated, abort) {
|
|
|
21849
22109
|
const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
|
|
21850
22110
|
let r = readTail(path, { after, tail: tail3, rotated });
|
|
21851
22111
|
while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
|
|
21852
|
-
await waitForChange(
|
|
22112
|
+
await waitForChange(dirname26(path), deadline - Date.now(), abort);
|
|
21853
22113
|
r = readTail(path, { after: r.cursor, rotated });
|
|
21854
22114
|
}
|
|
21855
22115
|
return reply2(200, r);
|
|
@@ -22039,21 +22299,21 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
22039
22299
|
|
|
22040
22300
|
// src/periscope-mcp.ts
|
|
22041
22301
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
22042
|
-
import { basename as
|
|
22302
|
+
import { basename as basename15, dirname as dirname27, isAbsolute as isAbsolute3, join as join48, relative as relative2, resolve as resolve5, sep } from "path";
|
|
22043
22303
|
import { z } from "zod";
|
|
22044
22304
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
22045
22305
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
22046
22306
|
|
|
22047
22307
|
// src/periscope-endpoints.ts
|
|
22048
22308
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
22049
|
-
import { chmodSync as chmodSync5, existsSync as existsSync44, mkdirSync as
|
|
22309
|
+
import { chmodSync as chmodSync5, existsSync as existsSync44, mkdirSync as mkdirSync31, readdirSync as readdirSync19, readFileSync as readFileSync39, rmSync as rmSync22, writeFileSync as writeFileSync32 } from "fs";
|
|
22050
22310
|
import { join as join47 } from "path";
|
|
22051
22311
|
var secretDir = (stateDir) => join47(stateDir, "periscope-mcp");
|
|
22052
22312
|
var secretFile = (stateDir, id) => join47(secretDir(stateDir), `${id}.json`);
|
|
22053
22313
|
var endpointDir = (stateDir) => join47(stateDir, "periscope-endpoints");
|
|
22054
22314
|
var endpointFile = (stateDir, id) => join47(endpointDir(stateDir), `${id}.json`);
|
|
22055
22315
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
22056
|
-
|
|
22316
|
+
mkdirSync31(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
22057
22317
|
const secret = randomBytes3(32).toString("base64url");
|
|
22058
22318
|
const file = secretFile(stateDir, ticketRecordId);
|
|
22059
22319
|
writeFileSync32(file, `${JSON.stringify({ secret })}
|
|
@@ -22080,7 +22340,7 @@ function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
|
22080
22340
|
}
|
|
22081
22341
|
}
|
|
22082
22342
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
22083
|
-
|
|
22343
|
+
mkdirSync31(endpointDir(stateDir), { recursive: true });
|
|
22084
22344
|
writeFileSync32(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
22085
22345
|
`);
|
|
22086
22346
|
}
|
|
@@ -22177,12 +22437,12 @@ async function realAncestor(p) {
|
|
|
22177
22437
|
}
|
|
22178
22438
|
if (isLink) {
|
|
22179
22439
|
if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
|
|
22180
|
-
cur = resolve5(
|
|
22440
|
+
cur = resolve5(dirname27(cur), await readlink(cur));
|
|
22181
22441
|
continue;
|
|
22182
22442
|
}
|
|
22183
|
-
const parent =
|
|
22443
|
+
const parent = dirname27(cur);
|
|
22184
22444
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
22185
|
-
tail3.push(
|
|
22445
|
+
tail3.push(basename15(cur));
|
|
22186
22446
|
cur = parent;
|
|
22187
22447
|
}
|
|
22188
22448
|
}
|
|
@@ -22291,7 +22551,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22291
22551
|
if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
|
|
22292
22552
|
const abs = await inTree(path);
|
|
22293
22553
|
if (abs === await realpath(root)) return text("path must name a file", true);
|
|
22294
|
-
await mkdir(
|
|
22554
|
+
await mkdir(dirname27(abs), { recursive: true });
|
|
22295
22555
|
await inTree(path);
|
|
22296
22556
|
await writeFile(abs, content);
|
|
22297
22557
|
return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
|
|
@@ -22331,9 +22591,9 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22331
22591
|
inputSchema: { path: z.string() },
|
|
22332
22592
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
22333
22593
|
}, async ({ path }) => {
|
|
22334
|
-
const parent = await inTree(
|
|
22335
|
-
const abs = join48(parent,
|
|
22336
|
-
if (
|
|
22594
|
+
const parent = await inTree(dirname27(path) === "" ? "." : dirname27(path));
|
|
22595
|
+
const abs = join48(parent, basename15(path));
|
|
22596
|
+
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
22337
22597
|
const st = await lstat(abs).catch(() => void 0);
|
|
22338
22598
|
if (!st) return text(`${path} does not exist`, true);
|
|
22339
22599
|
if (st.isDirectory()) return text(`${path} is a directory; only files are deleted`, true);
|
|
@@ -22453,7 +22713,7 @@ function findPeriscope(sidecars, segment) {
|
|
|
22453
22713
|
}
|
|
22454
22714
|
|
|
22455
22715
|
// src/cli.ts
|
|
22456
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
22716
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname28(process.execPath) : resolve6(dirname28(fileURLToPath(import.meta.url)), "..");
|
|
22457
22717
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
22458
22718
|
var CYCLE_SECONDS2 = 120;
|
|
22459
22719
|
var STALE_CYCLES = 3;
|
|
@@ -22620,6 +22880,7 @@ function usage(problem, requested = false) {
|
|
|
22620
22880
|
crew selftest [route] [--toolchain NAME] [--json] launch each toolchain as a run would and prove it reaches the tracker
|
|
22621
22881
|
crew sample-ticket [route] [--remove] [--dry-run] [--json] file one ticket on a throwaway repo that walks the dev, QA and release loop
|
|
22622
22882
|
crew ports [route] which checkout owns which ports, and what is up
|
|
22883
|
+
crew sandbox policy --route R --role ROLE [--repo NAME] [--json] print the sandbox policy a run of that role would get
|
|
22623
22884
|
crew reap [route] kill orphaned servers, drop worktrees for closed tickets
|
|
22624
22885
|
crew drop [route] NNN remove a merged ticket's worktree and branch
|
|
22625
22886
|
crew rotate-passenger-url [route] force a Host Passengers tunnel to reconnect with a fresh public URL
|
|
@@ -24878,7 +25139,7 @@ ${cu.tail.trimEnd()}
|
|
|
24878
25139
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
24879
25140
|
const raw = existsSync45(resolvedPath) ? JSON.parse(readFileSync40(resolvedPath, "utf8")) : {};
|
|
24880
25141
|
raw.agentSkills = result.agentSkills;
|
|
24881
|
-
|
|
25142
|
+
mkdirSync32(dirOf2(resolvedPath), { recursive: true });
|
|
24882
25143
|
writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
24883
25144
|
`);
|
|
24884
25145
|
}
|
|
@@ -24956,7 +25217,7 @@ ${cu.tail.trimEnd()}
|
|
|
24956
25217
|
const found = cfg.routes.find((r) => r.route === routeArg);
|
|
24957
25218
|
if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
|
|
24958
25219
|
const named2 = value("route") ? void 0 : positional[3];
|
|
24959
|
-
const dir = named2 ? repoDirOf(configText,
|
|
25220
|
+
const dir = named2 ? repoDirOf(configText, dirname28(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
|
|
24960
25221
|
const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
|
|
24961
25222
|
const o = target ? found.repoOverrides[target.name] : void 0;
|
|
24962
25223
|
runReposCheck(dir, {
|
|
@@ -24979,7 +25240,7 @@ ${cu.tail.trimEnd()}
|
|
|
24979
25240
|
if (sub === "hooks") {
|
|
24980
25241
|
try {
|
|
24981
25242
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
24982
|
-
const dir = repoDirOf(configText,
|
|
25243
|
+
const dir = repoDirOf(configText, dirname28(cfg.configFile), a.route, a.name);
|
|
24983
25244
|
if (!existsSync45(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
24984
25245
|
const files = { exists: (r) => existsSync45(resolvePath(dir, r)), list: (r) => {
|
|
24985
25246
|
try {
|
|
@@ -25146,7 +25407,7 @@ ${cu.tail.trimEnd()}
|
|
|
25146
25407
|
}
|
|
25147
25408
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
25148
25409
|
const text2 = configText;
|
|
25149
|
-
const base =
|
|
25410
|
+
const base = dirname28(cfg.configFile);
|
|
25150
25411
|
const isCheckout = (d) => existsSync45(`${d}/.git`);
|
|
25151
25412
|
try {
|
|
25152
25413
|
if (sub === "list") {
|
|
@@ -25822,7 +26083,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
25822
26083
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
25823
26084
|
`);
|
|
25824
26085
|
} else {
|
|
25825
|
-
|
|
26086
|
+
mkdirSync32(dirname28(resolvedPath), { recursive: true });
|
|
25826
26087
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
25827
26088
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
25828
26089
|
writeFileSync33(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
@@ -25871,7 +26132,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
25871
26132
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
25872
26133
|
`);
|
|
25873
26134
|
} else {
|
|
25874
|
-
|
|
26135
|
+
mkdirSync32(dirname28(apiKeyFile), { recursive: true });
|
|
25875
26136
|
writeFileSync33(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
25876
26137
|
`, { mode: 384 });
|
|
25877
26138
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
@@ -26171,6 +26432,39 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26171
26432
|
}
|
|
26172
26433
|
break;
|
|
26173
26434
|
}
|
|
26435
|
+
case "sandbox": {
|
|
26436
|
+
if (positional[1] !== "policy") {
|
|
26437
|
+
process.stderr.write("crew sandbox policy --route ROUTE --role ROLE [--repo NAME] [--json]\n");
|
|
26438
|
+
process.exit(2);
|
|
26439
|
+
}
|
|
26440
|
+
const only = value("route");
|
|
26441
|
+
const wanted = only ? cfg.routes.filter((r2) => r2.route === only) : cfg.routes;
|
|
26442
|
+
if (wanted.length !== 1) {
|
|
26443
|
+
process.stderr.write(`crew sandbox policy: ${only ? `no route "${only}"` : "name the route with --route"} (have: ${cfg.routes.map((r2) => r2.route).join(", ")})
|
|
26444
|
+
`);
|
|
26445
|
+
process.exit(2);
|
|
26446
|
+
}
|
|
26447
|
+
const role = value("role") ?? "dev";
|
|
26448
|
+
if (!ROLE_NAMES.includes(role)) {
|
|
26449
|
+
process.stderr.write(`crew sandbox policy: --role must be one of ${ROLE_NAMES.join(", ")}
|
|
26450
|
+
`);
|
|
26451
|
+
process.exit(2);
|
|
26452
|
+
}
|
|
26453
|
+
const r = wanted[0];
|
|
26454
|
+
const repoName = value("repo");
|
|
26455
|
+
const seat = resolveSeat(cfg.ship, r, role, {}, repoName);
|
|
26456
|
+
const input = sandboxPolicyInputFor(r, seat.toolchain, { harnessBin: seat.toolchain.bin, repo: repoName, sandbox: cfg.ship.sandbox });
|
|
26457
|
+
try {
|
|
26458
|
+
const policy = generateSandboxPolicy({ mirrorDir: cfg.ship.sandbox?.mirrorDir, workDir: cfg.ship.sandbox?.workDir, ...input });
|
|
26459
|
+
process.stdout.write(flag("json") ? `${JSON.stringify({ route: r.route, role, input, groups: policy.groups, yaml: policy.yaml }, null, 2)}
|
|
26460
|
+
` : policy.yaml);
|
|
26461
|
+
} catch (e) {
|
|
26462
|
+
process.stderr.write(`crew sandbox policy: ${e.message}
|
|
26463
|
+
`);
|
|
26464
|
+
process.exit(1);
|
|
26465
|
+
}
|
|
26466
|
+
break;
|
|
26467
|
+
}
|
|
26174
26468
|
case "reap": {
|
|
26175
26469
|
emit.enter("worktree");
|
|
26176
26470
|
let anything = false;
|
|
@@ -26768,7 +27062,7 @@ ${where.join("\n")}`);
|
|
|
26768
27062
|
for (const f of repoP.config.worktrees.copy) {
|
|
26769
27063
|
const src = join49(repoP.dir, f);
|
|
26770
27064
|
if (!existsSync45(src)) continue;
|
|
26771
|
-
|
|
27065
|
+
mkdirSync32(dirOf2(join49(path, f)), { recursive: true });
|
|
26772
27066
|
copyFileSync2(src, join49(path, f));
|
|
26773
27067
|
}
|
|
26774
27068
|
const setup = repoP.config.hooks.setup;
|
|
@@ -27117,7 +27411,7 @@ ${where.join("\n")}`);
|
|
|
27117
27411
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
27118
27412
|
break;
|
|
27119
27413
|
}
|
|
27120
|
-
|
|
27414
|
+
mkdirSync32(cfg.ship.stateDir, { recursive: true });
|
|
27121
27415
|
writeFileSync33(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
27122
27416
|
`);
|
|
27123
27417
|
process.stdout.write(
|
|
@@ -27207,7 +27501,7 @@ ${where.join("\n")}`);
|
|
|
27207
27501
|
if (!route2.resolved) fail(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
27208
27502
|
const tracker = new Tracker(route2, cfg.ship);
|
|
27209
27503
|
const configText = readFileSync40(cfg.configFile, "utf8");
|
|
27210
|
-
const cfgBase =
|
|
27504
|
+
const cfgBase = dirname28(cfg.configFile);
|
|
27211
27505
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
27212
27506
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
27213
27507
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
|
|
@@ -27251,7 +27545,7 @@ ${where.join("\n")}`);
|
|
|
27251
27545
|
`);
|
|
27252
27546
|
}
|
|
27253
27547
|
}
|
|
27254
|
-
if (existsSync45(join49(dir, ".git")) &&
|
|
27548
|
+
if (existsSync45(join49(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync23(dir, { recursive: true, force: true });
|
|
27255
27549
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
27256
27550
|
break;
|
|
27257
27551
|
}
|
|
@@ -27263,7 +27557,7 @@ ${where.join("\n")}`);
|
|
|
27263
27557
|
break;
|
|
27264
27558
|
}
|
|
27265
27559
|
if (fresh) {
|
|
27266
|
-
|
|
27560
|
+
mkdirSync32(dir, { recursive: true });
|
|
27267
27561
|
writeFileSync33(join49(dir, "README.md"), SAMPLE_README);
|
|
27268
27562
|
writeFileSync33(join49(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
27269
27563
|
const g = (...a) => {
|
|
@@ -27567,7 +27861,7 @@ ${where.join("\n")}`);
|
|
|
27567
27861
|
// `synthesis-tabl-123`. `worktreePrefix` above is kept for
|
|
27568
27862
|
// config-provenance display; this is the field that actually
|
|
27569
27863
|
// describes where a worktree lands by default.
|
|
27570
|
-
worktreeDirPattern: r.config.worktrees.name ?? (r.config.provenance["worktrees.prefix"] !== "default" ? `${r.config.worktrees.prefix}<number>` : `${
|
|
27864
|
+
worktreeDirPattern: r.config.worktrees.name ?? (r.config.provenance["worktrees.prefix"] !== "default" ? `${r.config.worktrees.prefix}<number>` : `${basename16(r.dir.replace(/[/\\]+$/, ""))}-<branch>`),
|
|
27571
27865
|
worktreeParent: resolvePath(r.dir, "..")
|
|
27572
27866
|
})),
|
|
27573
27867
|
waiting: waiting ? { ticket: waiting.ticket, since: waiting.since, streak: fairness.streak() } : null
|
|
@@ -27825,7 +28119,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
27825
28119
|
);
|
|
27826
28120
|
for (const r of await resolvedRepos(route2)) {
|
|
27827
28121
|
const { config } = r;
|
|
27828
|
-
const worktreePattern = config.worktrees.name ?? (config.provenance["worktrees.prefix"] !== "default" ? `${config.worktrees.prefix}<number>` : `${
|
|
28122
|
+
const worktreePattern = config.worktrees.name ?? (config.provenance["worktrees.prefix"] !== "default" ? `${config.worktrees.prefix}<number>` : `${basename16(r.dir.replace(/[/\\]+$/, ""))}-${effectiveBranchTemplate(config, void 0)}`);
|
|
27829
28123
|
process.stdout.write(
|
|
27830
28124
|
`${`repo ${r.name}:`.padEnd(19)}${r.dir}
|
|
27831
28125
|
worktrees at ../${worktreePattern} (${config.provenance["worktrees.name"] ?? config.provenance["worktrees.prefix"] ?? "default"}), branch ${config.branch.name}
|