@tablation/crew 0.47.1 → 0.47.3
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 +159 -80
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { resolve as resolve4, dirname as dirname12, join as
|
|
4
|
+
import { resolve as resolve4, dirname as dirname12, join as join24, basename as basename9 } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { hostname as hostname2, homedir as homedir8 } from "os";
|
|
7
7
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
@@ -4030,9 +4030,11 @@ import {
|
|
|
4030
4030
|
statSync as statSync4,
|
|
4031
4031
|
unlinkSync as unlinkSync2,
|
|
4032
4032
|
readFileSync as readFileSync9,
|
|
4033
|
-
writeFileSync as writeFileSync6
|
|
4033
|
+
writeFileSync as writeFileSync6,
|
|
4034
|
+
readdirSync as readdirSync3,
|
|
4035
|
+
rmSync as rmSync6
|
|
4034
4036
|
} from "fs";
|
|
4035
|
-
import { dirname as dirname5, basename as basename4 } from "path";
|
|
4037
|
+
import { dirname as dirname5, basename as basename4, join as join10 } from "path";
|
|
4036
4038
|
var GitError = class extends Error {
|
|
4037
4039
|
};
|
|
4038
4040
|
function streamText(x) {
|
|
@@ -4115,15 +4117,36 @@ function remoteBranchForIssue(cwd, remote, issueKey, templates = { name: "issue-
|
|
|
4115
4117
|
}
|
|
4116
4118
|
return null;
|
|
4117
4119
|
}
|
|
4118
|
-
function
|
|
4119
|
-
|
|
4120
|
-
|
|
4120
|
+
function levelRemoteBranch(cwd, remote, branch) {
|
|
4121
|
+
const local = gitOk(cwd, ["rev-parse", "-q", "--verify", `refs/heads/${branch}`]);
|
|
4122
|
+
if (!remoteBranchExists(cwd, remote, branch)) return { exists: local !== null };
|
|
4123
|
+
if (local === null) {
|
|
4124
|
+
return { exists: gitOk(cwd, ["branch", "--track", branch, `${remote}/${branch}`]) !== null };
|
|
4125
|
+
}
|
|
4126
|
+
const remoteTip2 = gitOk(cwd, ["rev-parse", "-q", "--verify", `refs/remotes/${remote}/${branch}`]);
|
|
4127
|
+
if (!remoteTip2 || remoteTip2 === local) return { exists: true };
|
|
4128
|
+
if (!isAncestor(cwd, local, remoteTip2)) return { exists: true };
|
|
4129
|
+
const holder = worktrees(cwd).find((w) => w.branch === branch && !w.prunable);
|
|
4130
|
+
if (holder) {
|
|
4131
|
+
if (gitOk(holder.path, ["status", "--porcelain"])) {
|
|
4132
|
+
return {
|
|
4133
|
+
exists: true,
|
|
4134
|
+
problem: `${branch} is behind ${remote}/${branch} and the worktree ${holder.path} that holds it has uncommitted or mid-merge changes`
|
|
4135
|
+
};
|
|
4136
|
+
}
|
|
4137
|
+
try {
|
|
4138
|
+
git(holder.path, ["merge", "--ff-only", "--quiet", `${remote}/${branch}`]);
|
|
4139
|
+
} catch (e) {
|
|
4140
|
+
return { exists: true, problem: `could not fast-forward ${branch} in ${holder.path}: ${e.message}` };
|
|
4141
|
+
}
|
|
4142
|
+
return { exists: true };
|
|
4121
4143
|
}
|
|
4122
|
-
|
|
4123
|
-
|
|
4144
|
+
try {
|
|
4145
|
+
git(cwd, ["fetch", "--quiet", remote, `${branch}:${branch}`]);
|
|
4146
|
+
} catch (e) {
|
|
4147
|
+
return { exists: true, problem: `could not move ${branch} to ${remote}/${branch}: ${e.message}` };
|
|
4124
4148
|
}
|
|
4125
|
-
|
|
4126
|
-
return true;
|
|
4149
|
+
return { exists: true };
|
|
4127
4150
|
}
|
|
4128
4151
|
function deleteRemoteBranch(cwd, remote, branch) {
|
|
4129
4152
|
if (!remoteBranchExists(cwd, remote, branch)) return true;
|
|
@@ -4367,12 +4390,36 @@ function recoverStrandedReleaseCheckouts(cwd) {
|
|
|
4367
4390
|
if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
|
|
4368
4391
|
else if (line.startsWith("branch refs/heads/crew/release-") && path) {
|
|
4369
4392
|
gitOk(cwd, ["worktree", "remove", "--force", path]);
|
|
4393
|
+
const root = dirname5(path);
|
|
4394
|
+
if (basename4(root).startsWith("crew-release-")) rmSync6(root, { recursive: true, force: true });
|
|
4370
4395
|
}
|
|
4371
4396
|
}
|
|
4372
4397
|
const stranded = (gitOk(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads/crew/release-*"]) ?? "").split("\n").filter(Boolean);
|
|
4373
4398
|
for (const b of stranded) gitOk(cwd, ["branch", "-q", "-D", b]);
|
|
4374
4399
|
return stranded;
|
|
4375
4400
|
}
|
|
4401
|
+
function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3) {
|
|
4402
|
+
const removed = [];
|
|
4403
|
+
let names;
|
|
4404
|
+
try {
|
|
4405
|
+
names = readdirSync3(tmp);
|
|
4406
|
+
} catch {
|
|
4407
|
+
return removed;
|
|
4408
|
+
}
|
|
4409
|
+
for (const name of names) {
|
|
4410
|
+
if (!name.startsWith("crew-release-")) continue;
|
|
4411
|
+
const root = join10(tmp, name);
|
|
4412
|
+
try {
|
|
4413
|
+
const st = statSync4(root);
|
|
4414
|
+
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
4415
|
+
if (existsSync9(join10(root, "checkout"))) continue;
|
|
4416
|
+
rmSync6(root, { recursive: true, force: true });
|
|
4417
|
+
removed.push(name);
|
|
4418
|
+
} catch {
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
return removed;
|
|
4422
|
+
}
|
|
4376
4423
|
function pushBranch(cwd, remote, branch, dst = branch) {
|
|
4377
4424
|
git(cwd, ["push", remote, `${branch}:${dst}`]);
|
|
4378
4425
|
}
|
|
@@ -6259,16 +6306,17 @@ function locateBranchForTicket(dir, cfg, t, role, fetchedDirs) {
|
|
|
6259
6306
|
const found = remoteBranchForIssue(dir, remote, t.issue_id, branchTemplates(cfg, t), branchRenderer(t, role));
|
|
6260
6307
|
return found ? { branch: found, where: "remote" } : null;
|
|
6261
6308
|
}
|
|
6262
|
-
function materializeBranchForTicket(dir, cfg, t, role) {
|
|
6309
|
+
function materializeBranchForTicket(dir, cfg, t, role, onProblem) {
|
|
6263
6310
|
const loc = locateBranchForTicket(dir, cfg, t, role);
|
|
6264
6311
|
if (!loc) return null;
|
|
6265
|
-
|
|
6312
|
+
const r = levelRemoteBranch(dir, cfg.branch.remote, loc.branch);
|
|
6313
|
+
if (r.problem) onProblem?.(r.problem);
|
|
6266
6314
|
return loc.branch;
|
|
6267
6315
|
}
|
|
6268
6316
|
|
|
6269
6317
|
// src/poll.ts
|
|
6270
6318
|
import { writeFileSync as writeFileSync7 } from "fs";
|
|
6271
|
-
import { join as
|
|
6319
|
+
import { join as join11 } from "path";
|
|
6272
6320
|
|
|
6273
6321
|
// src/stalled.ts
|
|
6274
6322
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -6545,7 +6593,7 @@ async function decideCycle(o) {
|
|
|
6545
6593
|
}
|
|
6546
6594
|
return decision;
|
|
6547
6595
|
}
|
|
6548
|
-
var digestPath = (stateDir, route, role) =>
|
|
6596
|
+
var digestPath = (stateDir, route, role) => join11(stateDir, `digest-${route}-${role}.md`);
|
|
6549
6597
|
function writeDigest(o, d, role, stateDir) {
|
|
6550
6598
|
const me = o.route.resolved?.seats[role];
|
|
6551
6599
|
if (!me) return false;
|
|
@@ -6720,6 +6768,11 @@ function planConflictBounce(cwd, ticket, branch, base, paths, events) {
|
|
|
6720
6768
|
function refreshBranch(worktree, base) {
|
|
6721
6769
|
const dirty = gitOk(worktree, ["status", "--porcelain"]);
|
|
6722
6770
|
if (dirty) return { clean: false, paths: [], why: "the worktree has uncommitted changes" };
|
|
6771
|
+
const upstream = gitOk(worktree, ["rev-parse", "-q", "--verify", "@{upstream}"]);
|
|
6772
|
+
const head = gitOk(worktree, ["rev-parse", "HEAD"]);
|
|
6773
|
+
if (upstream && head && upstream !== head && isAncestor(worktree, head, upstream)) {
|
|
6774
|
+
return { clean: false, paths: [], behind: true, why: "the branch is behind its remote; fast-forward it before merging the base in" };
|
|
6775
|
+
}
|
|
6723
6776
|
try {
|
|
6724
6777
|
git(worktree, ["merge", "--no-edit", base]);
|
|
6725
6778
|
return { clean: true, paths: [] };
|
|
@@ -6787,6 +6840,10 @@ async function applyConflictBounce(writer, b, contract, memberId, log, dryRun) {
|
|
|
6787
6840
|
log.emit(`merged ${b.base} into ${b.branch} cleanly \u2014 it was stale, not conflicting`, t);
|
|
6788
6841
|
return { kind: "refreshed" };
|
|
6789
6842
|
}
|
|
6843
|
+
if (r.behind) {
|
|
6844
|
+
log.warn(`not handing ${b.ticket.issue_id} back: ${r.why}`, t);
|
|
6845
|
+
return { kind: "failed", why: r.why ?? "branch behind its remote" };
|
|
6846
|
+
}
|
|
6790
6847
|
merged = r.paths.length > 0;
|
|
6791
6848
|
if (r.why) log.warn(`could not merge ${b.base} into ${b.branch}: ${r.why}`, t);
|
|
6792
6849
|
if (r.paths.length) b.paths = r.paths;
|
|
@@ -6993,7 +7050,7 @@ var alreadyReported = (ticket, g, events) => events.some((e) => e.record_id ===
|
|
|
6993
7050
|
import { spawn as spawn2 } from "child_process";
|
|
6994
7051
|
import { readFileSync as readFileSync12, existsSync as existsSync12, statSync as statSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync8, createWriteStream } from "fs";
|
|
6995
7052
|
import { finished } from "stream/promises";
|
|
6996
|
-
import { join as
|
|
7053
|
+
import { join as join14, dirname as dirname6 } from "path";
|
|
6997
7054
|
import { createHash as createHash2 } from "crypto";
|
|
6998
7055
|
|
|
6999
7056
|
// src/environment.ts
|
|
@@ -7408,14 +7465,14 @@ function expandSkillMarkers(text, lookup, opts = {}) {
|
|
|
7408
7465
|
}
|
|
7409
7466
|
|
|
7410
7467
|
// src/skills.ts
|
|
7411
|
-
import { readFileSync as readFileSync11, readdirSync as
|
|
7412
|
-
import { join as
|
|
7468
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync4, existsSync as existsSync11 } from "fs";
|
|
7469
|
+
import { join as join13 } from "path";
|
|
7413
7470
|
import { parse as parseYaml } from "yaml";
|
|
7414
7471
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError8, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
7415
7472
|
|
|
7416
7473
|
// src/agents.ts
|
|
7417
7474
|
import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
|
|
7418
|
-
import { join as
|
|
7475
|
+
import { join as join12 } from "path";
|
|
7419
7476
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError7, TablationApiError } from "@tablation/client";
|
|
7420
7477
|
var PERSONA_NAME = {
|
|
7421
7478
|
dev: "Developer",
|
|
@@ -7429,10 +7486,10 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
7429
7486
|
var AgentsSyncError = class extends Error {
|
|
7430
7487
|
};
|
|
7431
7488
|
function personaDefaultPrompt(promptsDir, role) {
|
|
7432
|
-
const briefPath =
|
|
7489
|
+
const briefPath = join12(promptsDir, "personas", `lane-${role}.md`);
|
|
7433
7490
|
if (!existsSync10(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
7434
7491
|
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync10(briefPath, "utf8");
|
|
7435
|
-
const commonPath =
|
|
7492
|
+
const commonPath = join12(promptsDir, "personas", "common.md");
|
|
7436
7493
|
if (!existsSync10(commonPath)) return readFileSync10(briefPath, "utf8");
|
|
7437
7494
|
return `${readFileSync10(commonPath, "utf8")}${readFileSync10(briefPath, "utf8")}`;
|
|
7438
7495
|
}
|
|
@@ -7812,9 +7869,9 @@ function parseSkillFile(path) {
|
|
|
7812
7869
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
7813
7870
|
}
|
|
7814
7871
|
function listLocalSkills(promptsDir) {
|
|
7815
|
-
const dir =
|
|
7872
|
+
const dir = join13(promptsDir, "skills");
|
|
7816
7873
|
if (!existsSync11(dir)) return [];
|
|
7817
|
-
return
|
|
7874
|
+
return readdirSync4(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join13(dir, f)));
|
|
7818
7875
|
}
|
|
7819
7876
|
async function syncSkills(route, opts) {
|
|
7820
7877
|
if (!route.resolved) {
|
|
@@ -8047,15 +8104,15 @@ var AgentError = class extends Error {
|
|
|
8047
8104
|
var AgentSpawnError = class extends AgentError {
|
|
8048
8105
|
};
|
|
8049
8106
|
function planAgentRun(o) {
|
|
8050
|
-
const briefPath =
|
|
8107
|
+
const briefPath = join14(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
8051
8108
|
if (!existsSync12(briefPath)) {
|
|
8052
8109
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
8053
8110
|
}
|
|
8054
|
-
const commonPath =
|
|
8111
|
+
const commonPath = join14(o.route.promptsDir, "personas", "common.md");
|
|
8055
8112
|
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync12(commonPath);
|
|
8056
8113
|
const now = o.now ?? (() => Date.now());
|
|
8057
8114
|
const routeLabel = routeSlug(o.route.route);
|
|
8058
|
-
const digestPath2 =
|
|
8115
|
+
const digestPath2 = join14(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
|
|
8059
8116
|
let digest;
|
|
8060
8117
|
let ageSeconds;
|
|
8061
8118
|
if (existsSync12(digestPath2)) {
|
|
@@ -8085,7 +8142,7 @@ function planAgentRun(o) {
|
|
|
8085
8142
|
digest
|
|
8086
8143
|
});
|
|
8087
8144
|
const promptSha = createHash2("sha256").update(expanded.text).digest("hex");
|
|
8088
|
-
const streamsDir =
|
|
8145
|
+
const streamsDir = join14(o.stateDir, "streams");
|
|
8089
8146
|
const base = `${routeLabel}-${o.role}-${o.cycle}`;
|
|
8090
8147
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
8091
8148
|
const { toolchain, model } = seat;
|
|
@@ -8141,8 +8198,8 @@ function planAgentRun(o) {
|
|
|
8141
8198
|
digestAttached: digest !== void 0,
|
|
8142
8199
|
digestAgeSeconds: ageSeconds,
|
|
8143
8200
|
model,
|
|
8144
|
-
streamPath:
|
|
8145
|
-
eventsPath:
|
|
8201
|
+
streamPath: join14(streamsDir, `${base}.jsonl`),
|
|
8202
|
+
eventsPath: join14(streamsDir, `${base}.events.jsonl`),
|
|
8146
8203
|
ticket: o.ticket,
|
|
8147
8204
|
agentLog,
|
|
8148
8205
|
promptVersion: o.route.resolved?.agentPersonas?.[o.role]?.historyId,
|
|
@@ -8349,12 +8406,12 @@ function spawnAgent(plan, emit) {
|
|
|
8349
8406
|
|
|
8350
8407
|
// src/run-worktree.ts
|
|
8351
8408
|
import { existsSync as existsSync14 } from "fs";
|
|
8352
|
-
import { dirname as dirname7, join as
|
|
8409
|
+
import { dirname as dirname7, join as join16 } from "path";
|
|
8353
8410
|
|
|
8354
8411
|
// src/ports.ts
|
|
8355
8412
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
8356
8413
|
import { existsSync as existsSync13 } from "fs";
|
|
8357
|
-
import { join as
|
|
8414
|
+
import { join as join15 } from "path";
|
|
8358
8415
|
var run = (cmd, args) => {
|
|
8359
8416
|
try {
|
|
8360
8417
|
return execFileSync2(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -8399,7 +8456,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
|
|
|
8399
8456
|
}
|
|
8400
8457
|
function worktreeExists(parent, prefix, n) {
|
|
8401
8458
|
for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
|
|
8402
|
-
if (existsSync13(
|
|
8459
|
+
if (existsSync13(join15(parent, name, ".git"))) return true;
|
|
8403
8460
|
}
|
|
8404
8461
|
return false;
|
|
8405
8462
|
}
|
|
@@ -8487,7 +8544,7 @@ function planRunWorktree(dir, cfg, t, role) {
|
|
|
8487
8544
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
8488
8545
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
8489
8546
|
}
|
|
8490
|
-
const path =
|
|
8547
|
+
const path = join16(dirname7(dir), worktreeForTicket(dir, cfg, t, role));
|
|
8491
8548
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
8492
8549
|
return { action: "cut-remote", path, branch: located.branch };
|
|
8493
8550
|
}
|
|
@@ -8695,7 +8752,7 @@ async function retireRunWorktree(tracker, dir, cfg, t, role, seatId, shipName2,
|
|
|
8695
8752
|
}
|
|
8696
8753
|
|
|
8697
8754
|
// src/connect-wizard.ts
|
|
8698
|
-
import { delimiter, dirname as dirname8, join as
|
|
8755
|
+
import { delimiter, dirname as dirname8, join as join17 } from "path";
|
|
8699
8756
|
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync15, mkdirSync as mkdirSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
8700
8757
|
import { stringify } from "yaml";
|
|
8701
8758
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
@@ -8718,7 +8775,7 @@ function defaultIsExecutable(p) {
|
|
|
8718
8775
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
8719
8776
|
for (const dir of pathEnv.split(delimiter)) {
|
|
8720
8777
|
if (!dir) continue;
|
|
8721
|
-
const candidate =
|
|
8778
|
+
const candidate = join17(dir, name);
|
|
8722
8779
|
if (isExecutable(candidate)) return candidate;
|
|
8723
8780
|
}
|
|
8724
8781
|
return void 0;
|
|
@@ -8813,8 +8870,8 @@ Wrote ${o.configPath}.
|
|
|
8813
8870
|
}
|
|
8814
8871
|
|
|
8815
8872
|
// src/release-run.ts
|
|
8816
|
-
import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync16, copyFileSync, mkdirSync as mkdirSync8, mkdtempSync, rmSync as
|
|
8817
|
-
import { dirname as dirname9, join as
|
|
8873
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync16, copyFileSync, mkdirSync as mkdirSync8, mkdtempSync, rmSync as rmSync7 } from "fs";
|
|
8874
|
+
import { dirname as dirname9, join as join18 } from "path";
|
|
8818
8875
|
import { tmpdir as tmpdir2 } from "os";
|
|
8819
8876
|
|
|
8820
8877
|
// src/release.ts
|
|
@@ -9472,7 +9529,7 @@ async function bump(o, size) {
|
|
|
9472
9529
|
}
|
|
9473
9530
|
return printed;
|
|
9474
9531
|
}
|
|
9475
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
9532
|
+
const files = o.repo.release.versionFiles.map((f) => join18(o.cwd, f));
|
|
9476
9533
|
const first = files[0];
|
|
9477
9534
|
if (!existsSync16(first)) {
|
|
9478
9535
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
@@ -9504,7 +9561,7 @@ async function bump(o, size) {
|
|
|
9504
9561
|
function writeChangelog(o, version, entries) {
|
|
9505
9562
|
const file = o.repo.release.changelog;
|
|
9506
9563
|
if (!file || entries.length === 0) return;
|
|
9507
|
-
const path =
|
|
9564
|
+
const path = join18(o.cwd, file);
|
|
9508
9565
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
9509
9566
|
if (o.dryRun) {
|
|
9510
9567
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
@@ -9589,17 +9646,25 @@ async function confirmLastRelease(o, decision) {
|
|
|
9589
9646
|
if (!confirmed) o.emit.warn(`${decision.lastTag} is still not confirmed live \u2014 tickets stay verified`);
|
|
9590
9647
|
return { confirmed, version };
|
|
9591
9648
|
}
|
|
9649
|
+
function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
|
|
9650
|
+
try {
|
|
9651
|
+
run2(cwd, ["worktree", "remove", "--force", dir]);
|
|
9652
|
+
} finally {
|
|
9653
|
+
try {
|
|
9654
|
+
rmSync7(root, { recursive: true, force: true });
|
|
9655
|
+
} finally {
|
|
9656
|
+
run2(cwd, ["worktree", "prune"]);
|
|
9657
|
+
run2(cwd, ["branch", "-D", branch]);
|
|
9658
|
+
}
|
|
9659
|
+
}
|
|
9660
|
+
}
|
|
9592
9661
|
function openReleaseCheckout(o) {
|
|
9593
9662
|
const { remote, base } = o.repo.branch;
|
|
9594
9663
|
const branch = `crew/release-${Date.now()}`;
|
|
9595
|
-
const root = mkdtempSync(
|
|
9596
|
-
const dir =
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
rmSync6(root, { recursive: true, force: true });
|
|
9600
|
-
gitOk(o.cwd, ["worktree", "prune"]);
|
|
9601
|
-
gitOk(o.cwd, ["branch", "-D", branch]);
|
|
9602
|
-
};
|
|
9664
|
+
const root = mkdtempSync(join18(tmpdir2(), "crew-release-"));
|
|
9665
|
+
const dir = join18(root, "checkout");
|
|
9666
|
+
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
9667
|
+
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
9603
9668
|
try {
|
|
9604
9669
|
git(o.cwd, ["worktree", "add", "-q", "-b", branch, dir, `${remote}/${base}`]);
|
|
9605
9670
|
} catch (e) {
|
|
@@ -9607,10 +9672,10 @@ function openReleaseCheckout(o) {
|
|
|
9607
9672
|
throw e;
|
|
9608
9673
|
}
|
|
9609
9674
|
for (const f of o.repo.worktrees.copy) {
|
|
9610
|
-
const src =
|
|
9675
|
+
const src = join18(o.cwd, f);
|
|
9611
9676
|
if (!existsSync16(src)) continue;
|
|
9612
|
-
mkdirSync8(dirname9(
|
|
9613
|
-
copyFileSync(src,
|
|
9677
|
+
mkdirSync8(dirname9(join18(dir, f)), { recursive: true });
|
|
9678
|
+
copyFileSync(src, join18(dir, f));
|
|
9614
9679
|
}
|
|
9615
9680
|
return { dir, branch, close };
|
|
9616
9681
|
}
|
|
@@ -9630,7 +9695,19 @@ async function runRelease(o) {
|
|
|
9630
9695
|
const tagPattern = o.repo.release.tagPattern ?? "v*";
|
|
9631
9696
|
o.emit.enter("release");
|
|
9632
9697
|
const fresh = refreshBase(o);
|
|
9633
|
-
|
|
9698
|
+
let releasable = o.tickets;
|
|
9699
|
+
if (!o.dryRun) {
|
|
9700
|
+
const skipped = /* @__PURE__ */ new Set();
|
|
9701
|
+
for (const t of o.tickets) {
|
|
9702
|
+
if (t.status !== o.contract.statuses.verified) continue;
|
|
9703
|
+
materializeBranchForTicket(o.cwd, o.repo, t, void 0, (problem) => {
|
|
9704
|
+
skipped.add(t.id);
|
|
9705
|
+
o.emit.error(`not releasing ${t.issue_id} this cycle: ${problem}`, { ticket: t.issue_id });
|
|
9706
|
+
});
|
|
9707
|
+
}
|
|
9708
|
+
if (skipped.size) releasable = o.tickets.filter((t) => !skipped.has(t.id));
|
|
9709
|
+
}
|
|
9710
|
+
const decision = decideRelease(o.cwd, releasable, o.contract, {
|
|
9634
9711
|
tagPattern,
|
|
9635
9712
|
base: o.repo.branch.base,
|
|
9636
9713
|
blockerInfo: o.blockerInfo,
|
|
@@ -9689,6 +9766,8 @@ async function runRelease(o) {
|
|
|
9689
9766
|
let checkout;
|
|
9690
9767
|
const stranded = recoverStrandedReleaseCheckouts(o.cwd);
|
|
9691
9768
|
if (stranded.length > 0) o.emit.warn(scoped(`dropped release checkout(s) stranded by a crash: ${stranded.join(", ")}`));
|
|
9769
|
+
const swept = sweepStaleReleaseRoots(tmpdir2());
|
|
9770
|
+
if (swept.length > 0) o.emit.emit(scoped(`removed ${swept.length} stale crew-release-* temp dir(s)`), { step: "release" });
|
|
9692
9771
|
try {
|
|
9693
9772
|
checkout = openReleaseCheckout(o);
|
|
9694
9773
|
} catch (e) {
|
|
@@ -11451,14 +11530,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
11451
11530
|
}
|
|
11452
11531
|
|
|
11453
11532
|
// src/stream-sweep.ts
|
|
11454
|
-
import { existsSync as existsSync19, readdirSync as
|
|
11455
|
-
import { join as
|
|
11533
|
+
import { existsSync as existsSync19, readdirSync as readdirSync5, rmSync as rmSync8, statSync as statSync7 } from "fs";
|
|
11534
|
+
import { join as join19 } from "path";
|
|
11456
11535
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
11457
11536
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
11458
|
-
const dir =
|
|
11537
|
+
const dir = join19(stateDir, "streams");
|
|
11459
11538
|
if (!existsSync19(dir)) return [];
|
|
11460
11539
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
11461
|
-
return
|
|
11540
|
+
return readdirSync5(dir).map((name) => join19(dir, name)).filter((path) => {
|
|
11462
11541
|
try {
|
|
11463
11542
|
return statSync7(path).mtimeMs < cutoff;
|
|
11464
11543
|
} catch {
|
|
@@ -11474,7 +11553,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
11474
11553
|
continue;
|
|
11475
11554
|
}
|
|
11476
11555
|
try {
|
|
11477
|
-
|
|
11556
|
+
rmSync8(path, { force: true });
|
|
11478
11557
|
removed++;
|
|
11479
11558
|
log.emit(`removed stream artifact ${path}`, { step: "worktree" });
|
|
11480
11559
|
} catch (e) {
|
|
@@ -11486,7 +11565,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
11486
11565
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
11487
11566
|
if (!existsSync19(stateDir)) return [];
|
|
11488
11567
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
11489
|
-
return
|
|
11568
|
+
return readdirSync5(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join19(stateDir, name)).filter((path) => {
|
|
11490
11569
|
try {
|
|
11491
11570
|
return statSync7(path).mtimeMs < cutoff;
|
|
11492
11571
|
} catch {
|
|
@@ -11503,11 +11582,11 @@ import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
|
11503
11582
|
// src/ssh-keys.ts
|
|
11504
11583
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
11505
11584
|
import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync14, chmodSync } from "fs";
|
|
11506
|
-
import { dirname as dirname11, join as
|
|
11585
|
+
import { dirname as dirname11, join as join20 } from "path";
|
|
11507
11586
|
var SshKeyError = class extends Error {
|
|
11508
11587
|
};
|
|
11509
11588
|
function shipSshKeyPathFor(stateDir) {
|
|
11510
|
-
return
|
|
11589
|
+
return join20(stateDir, "ssh", "id_ed25519");
|
|
11511
11590
|
}
|
|
11512
11591
|
function ensureShipSshKeypair(stateDir) {
|
|
11513
11592
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
@@ -11752,9 +11831,9 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
11752
11831
|
|
|
11753
11832
|
// src/tunnel.ts
|
|
11754
11833
|
import { spawn as spawn3 } from "child_process";
|
|
11755
|
-
import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync3, readdirSync as
|
|
11834
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync3, readdirSync as readdirSync6 } from "fs";
|
|
11756
11835
|
import { randomInt } from "crypto";
|
|
11757
|
-
import { join as
|
|
11836
|
+
import { join as join21 } from "path";
|
|
11758
11837
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
11759
11838
|
var defaultSpawn = (cmd, args) => spawn3(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
11760
11839
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -11821,7 +11900,7 @@ function startTunnel(opts) {
|
|
|
11821
11900
|
};
|
|
11822
11901
|
}
|
|
11823
11902
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
11824
|
-
return
|
|
11903
|
+
return join21(stateDir, "passengers", `${workspaceId}.json`);
|
|
11825
11904
|
}
|
|
11826
11905
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
11827
11906
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
@@ -11834,7 +11913,7 @@ function readPersistedTunnel(stateDir, workspaceId) {
|
|
|
11834
11913
|
}
|
|
11835
11914
|
function writePersistedTunnel(stateDir, state) {
|
|
11836
11915
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
11837
|
-
mkdirSync10(
|
|
11916
|
+
mkdirSync10(join21(stateDir, "passengers"), { recursive: true });
|
|
11838
11917
|
writeFileSync11(path, `${JSON.stringify(state, null, 2)}
|
|
11839
11918
|
`);
|
|
11840
11919
|
}
|
|
@@ -11932,9 +12011,9 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
11932
12011
|
}
|
|
11933
12012
|
}
|
|
11934
12013
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
11935
|
-
const dir =
|
|
12014
|
+
const dir = join21(stateDir, "passengers");
|
|
11936
12015
|
if (!existsSync21(dir)) return [];
|
|
11937
|
-
return
|
|
12016
|
+
return readdirSync6(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
11938
12017
|
}
|
|
11939
12018
|
|
|
11940
12019
|
// src/mcp-endpoint-registry.ts
|
|
@@ -12054,8 +12133,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
12054
12133
|
import { createServer as createHttpServer } from "http";
|
|
12055
12134
|
import { spawn as spawn4 } from "child_process";
|
|
12056
12135
|
import { randomBytes } from "crypto";
|
|
12057
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4, readdirSync as
|
|
12058
|
-
import { join as
|
|
12136
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4, readdirSync as readdirSync7 } from "fs";
|
|
12137
|
+
import { join as join22 } from "path";
|
|
12059
12138
|
|
|
12060
12139
|
// src/secret-compare.ts
|
|
12061
12140
|
import { timingSafeEqual } from "crypto";
|
|
@@ -12144,7 +12223,7 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
12144
12223
|
return child;
|
|
12145
12224
|
};
|
|
12146
12225
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
12147
|
-
return
|
|
12226
|
+
return join22(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
12148
12227
|
}
|
|
12149
12228
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
12150
12229
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
@@ -12157,7 +12236,7 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
|
12157
12236
|
}
|
|
12158
12237
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
12159
12238
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
12160
|
-
mkdirSync11(
|
|
12239
|
+
mkdirSync11(join22(stateDir, "passengers"), { recursive: true });
|
|
12161
12240
|
writeFileSync12(path, `${JSON.stringify(state, null, 2)}
|
|
12162
12241
|
`);
|
|
12163
12242
|
}
|
|
@@ -12178,10 +12257,10 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
12178
12257
|
}
|
|
12179
12258
|
}
|
|
12180
12259
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
12181
|
-
const dir =
|
|
12260
|
+
const dir = join22(stateDir, "passengers");
|
|
12182
12261
|
if (!existsSync22(dir)) return [];
|
|
12183
12262
|
const suffix = "-sync.json";
|
|
12184
|
-
return
|
|
12263
|
+
return readdirSync7(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
12185
12264
|
}
|
|
12186
12265
|
function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
12187
12266
|
const alive = deps.isPidAlive ?? isPidAlive;
|
|
@@ -12236,8 +12315,8 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
12236
12315
|
}
|
|
12237
12316
|
|
|
12238
12317
|
// src/daemon.ts
|
|
12239
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync17, readdirSync as
|
|
12240
|
-
import { join as
|
|
12318
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync17, readdirSync as readdirSync8, rmSync as rmSync9, statSync as statSync8, writeFileSync as writeFileSync13 } from "fs";
|
|
12319
|
+
import { join as join23 } from "path";
|
|
12241
12320
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
12242
12321
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
12243
12322
|
var defaultSleep = (ms2) => new Promise((resolve5) => setTimeout(resolve5, ms2));
|
|
@@ -12275,8 +12354,8 @@ function latestMtime(path) {
|
|
|
12275
12354
|
if (stat.isFile()) return stat.mtimeMs;
|
|
12276
12355
|
if (!stat.isDirectory()) return 0;
|
|
12277
12356
|
let latest = stat.mtimeMs;
|
|
12278
|
-
for (const entry of
|
|
12279
|
-
latest = Math.max(latest, latestMtime(
|
|
12357
|
+
for (const entry of readdirSync8(path, { withFileTypes: true })) {
|
|
12358
|
+
latest = Math.max(latest, latestMtime(join23(path, entry.name)));
|
|
12280
12359
|
}
|
|
12281
12360
|
return latest;
|
|
12282
12361
|
}
|
|
@@ -12288,14 +12367,14 @@ function makeStaleChecker(paths, opts = {}) {
|
|
|
12288
12367
|
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync23(opts.reloadSentinel);
|
|
12289
12368
|
}
|
|
12290
12369
|
function reloadSentinelFor(stateDir) {
|
|
12291
|
-
return
|
|
12370
|
+
return join23(stateDir, "reload-requested");
|
|
12292
12371
|
}
|
|
12293
12372
|
function daemonStampFor(stateDir) {
|
|
12294
|
-
return
|
|
12373
|
+
return join23(stateDir, "daemon.started");
|
|
12295
12374
|
}
|
|
12296
12375
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
12297
12376
|
mkdirSync12(stateDir, { recursive: true });
|
|
12298
|
-
|
|
12377
|
+
rmSync9(reloadSentinelFor(stateDir), { force: true });
|
|
12299
12378
|
writeFileSync13(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
12300
12379
|
`);
|
|
12301
12380
|
}
|
|
@@ -13115,7 +13194,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
13115
13194
|
}
|
|
13116
13195
|
if (host === "linux" && hasSystemd) {
|
|
13117
13196
|
const label = labelFor(CREW_HOME, "run");
|
|
13118
|
-
const staleTimer =
|
|
13197
|
+
const staleTimer = join24(homedir8(), ".config", "systemd", "user", `${label}.timer`);
|
|
13119
13198
|
if (existsSync24(staleTimer)) {
|
|
13120
13199
|
process.stdout.write(
|
|
13121
13200
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
@@ -14123,7 +14202,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
14123
14202
|
process.exit(1);
|
|
14124
14203
|
}
|
|
14125
14204
|
requireArmed("run the daemon loop");
|
|
14126
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
14205
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join24(CREW_HOME, "src"), join24(CREW_HOME, "dist"), join24(CREW_HOME, "bin")];
|
|
14127
14206
|
const configPaths = daemonConfigPaths(cfg);
|
|
14128
14207
|
watchPaths.push(...configPaths);
|
|
14129
14208
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -14803,7 +14882,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
14803
14882
|
const keyPersisted = keychainBacked || apiKeyFile !== void 0;
|
|
14804
14883
|
const wizard = firstRun;
|
|
14805
14884
|
const writeFirstRun = wizard !== void 0 && !dryRun && keyPersisted;
|
|
14806
|
-
const routeDir = value("dir") ?? (writeFirstRun ?
|
|
14885
|
+
const routeDir = value("dir") ?? (writeFirstRun ? join24(DEFAULT_REPOS_BASE_PATH, found.workspaceSlug) : void 0);
|
|
14807
14886
|
const routeBlock = renderConnection(
|
|
14808
14887
|
found,
|
|
14809
14888
|
route2,
|
package/package.json
CHANGED