@tablation/crew 0.126.6 → 0.126.7
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 +202 -118
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -12004,6 +12004,21 @@ function slugFor(route2, agentId) {
|
|
|
12004
12004
|
const [workspaceSlug] = splitRoute(route2.route);
|
|
12005
12005
|
return seatKeySlug(workspaceSlug, agentId);
|
|
12006
12006
|
}
|
|
12007
|
+
function withFoundSeats(route2, found, previousReleaseAgentId) {
|
|
12008
|
+
const resolved = route2.resolved;
|
|
12009
|
+
if (!resolved) return route2;
|
|
12010
|
+
const releaseSeat2 = found.releaseSeat ?? resolved.releaseSeat;
|
|
12011
|
+
const releaseAgentId = resolved.releaseAgentId ?? previousReleaseAgentId;
|
|
12012
|
+
return {
|
|
12013
|
+
...route2,
|
|
12014
|
+
resolved: {
|
|
12015
|
+
...resolved,
|
|
12016
|
+
seats: found.seats,
|
|
12017
|
+
...releaseSeat2 ? { releaseSeat: releaseSeat2 } : {},
|
|
12018
|
+
...releaseAgentId ? { releaseAgentId } : {}
|
|
12019
|
+
}
|
|
12020
|
+
};
|
|
12021
|
+
}
|
|
12007
12022
|
function seatAgentId(route2, role) {
|
|
12008
12023
|
return route2.resolved?.agentPersonas?.[role]?.agentId;
|
|
12009
12024
|
}
|
|
@@ -12496,6 +12511,15 @@ printf '%s
|
|
|
12496
12511
|
' "$tip" > "$out/$name.tip"
|
|
12497
12512
|
echo "updated $tip"
|
|
12498
12513
|
`;
|
|
12514
|
+
var SHOW_SCRIPT = `
|
|
12515
|
+
set -e
|
|
12516
|
+
export GIT_TERMINAL_PROMPT=0
|
|
12517
|
+
m=${VOLUME_ROOT}/"$1"; base="$2"; path="$3"
|
|
12518
|
+
git config --global --add safe.directory '*'
|
|
12519
|
+
git -C "$m" rev-parse --verify --quiet "refs/heads/$base^{commit}" >/dev/null || { echo "the mirror has no branch $base" >&2; exit 3; }
|
|
12520
|
+
git -C "$m" cat-file -e "refs/heads/$base:$path" 2>/dev/null || exit 4
|
|
12521
|
+
git -C "$m" show "refs/heads/$base:$path"
|
|
12522
|
+
`;
|
|
12499
12523
|
var SandboxCache = class {
|
|
12500
12524
|
s;
|
|
12501
12525
|
exec;
|
|
@@ -12582,6 +12606,20 @@ var SandboxCache = class {
|
|
|
12582
12606
|
if (!m) throw new Error(`unexpected output materializing ${repo.host}/${repo.path}: ${stdout.trim().slice(0, 200)}`);
|
|
12583
12607
|
return { tip: m[2], changed: m[1] === "updated" };
|
|
12584
12608
|
}
|
|
12609
|
+
/**
|
|
12610
|
+
* The text of `path` at the tip of `base` in the repository's mirror, or
|
|
12611
|
+
* null when the mirror has no such file. Throws when the mirror or the
|
|
12612
|
+
* branch is missing, so the caller can tell "no contract" from "no mirror".
|
|
12613
|
+
*/
|
|
12614
|
+
async showFile(repo, base, path) {
|
|
12615
|
+
try {
|
|
12616
|
+
const { stdout } = await this.helper(this.s.helperImage, SHOW_SCRIPT, [mirrorRelPath(repo), base, path]);
|
|
12617
|
+
return stdout;
|
|
12618
|
+
} catch (e) {
|
|
12619
|
+
if (e.code === 4) return null;
|
|
12620
|
+
throw e;
|
|
12621
|
+
}
|
|
12622
|
+
}
|
|
12585
12623
|
/** Mirrors in the volume with their last refresh; empty when the volume does not exist. */
|
|
12586
12624
|
async list() {
|
|
12587
12625
|
try {
|
|
@@ -14055,7 +14093,7 @@ async function runPoolMember(deps) {
|
|
|
14055
14093
|
await deps.sleep(backoffMs(failures++));
|
|
14056
14094
|
}
|
|
14057
14095
|
};
|
|
14058
|
-
let
|
|
14096
|
+
let reported2;
|
|
14059
14097
|
let busy = false;
|
|
14060
14098
|
const report = async (state, o = {}) => {
|
|
14061
14099
|
const body = {
|
|
@@ -14078,7 +14116,7 @@ async function runPoolMember(deps) {
|
|
|
14078
14116
|
if (r.status === 401) return "credential";
|
|
14079
14117
|
if (r.status === 409) deps.log(`status ${state}: the platform refused the transition (409); not retrying`);
|
|
14080
14118
|
else if (r.status >= 400) deps.log(`status ${state}: platform answered ${r.status}; not retrying`);
|
|
14081
|
-
|
|
14119
|
+
reported2 = body;
|
|
14082
14120
|
deps.store.write({ ...deps.store.read(), lastState: state });
|
|
14083
14121
|
return "ok";
|
|
14084
14122
|
};
|
|
@@ -14161,7 +14199,7 @@ async function runPoolMember(deps) {
|
|
|
14161
14199
|
if (r === "stopped") return POOL_EXIT.ok;
|
|
14162
14200
|
}
|
|
14163
14201
|
}
|
|
14164
|
-
let relayReported =
|
|
14202
|
+
let relayReported = reported2?.state === "running" ? reported2.relayUrl : null;
|
|
14165
14203
|
let reregister = false;
|
|
14166
14204
|
while (!reregister) {
|
|
14167
14205
|
if (deps.stopping()) return POOL_EXIT.ok;
|
|
@@ -19076,6 +19114,73 @@ function runForge(a, io) {
|
|
|
19076
19114
|
`, err: "" };
|
|
19077
19115
|
}
|
|
19078
19116
|
|
|
19117
|
+
// src/mirror-contract.ts
|
|
19118
|
+
import { existsSync as existsSync40 } from "fs";
|
|
19119
|
+
|
|
19120
|
+
// src/passenger-mirror.ts
|
|
19121
|
+
import { mkdirSync as mkdirSync29 } from "fs";
|
|
19122
|
+
import { dirname as dirname25 } from "path";
|
|
19123
|
+
function mirrorRepoOf(spec) {
|
|
19124
|
+
const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
|
|
19125
|
+
if (fromOrigin) return fromOrigin;
|
|
19126
|
+
if (!spec.remote) return null;
|
|
19127
|
+
return { host: normalizeForgeHost(spec.forgeHost) || DEFAULT_FORGE_HOST, path: spec.remote };
|
|
19128
|
+
}
|
|
19129
|
+
async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCache(s.mirror.cache)) {
|
|
19130
|
+
const out = [];
|
|
19131
|
+
for (const spec of specs) {
|
|
19132
|
+
if (!spec.mirror) continue;
|
|
19133
|
+
const base = { name: spec.name, workspaceId: spec.workspaceId };
|
|
19134
|
+
const repo = mirrorRepoOf(spec);
|
|
19135
|
+
if (!repo) {
|
|
19136
|
+
out.push({ ...base, action: "failed", detail: `no remote known for ${spec.name}, so its mirror cannot be found` });
|
|
19137
|
+
continue;
|
|
19138
|
+
}
|
|
19139
|
+
try {
|
|
19140
|
+
const parent = dirname25(spec.dir);
|
|
19141
|
+
mkdirSync29(parent, { recursive: true });
|
|
19142
|
+
mkdirSync29(spec.dir, { recursive: true });
|
|
19143
|
+
const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
|
|
19144
|
+
out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
|
|
19145
|
+
} catch (e) {
|
|
19146
|
+
const err3 = e;
|
|
19147
|
+
out.push({ ...base, action: "failed", detail: (err3.stderr?.trim() || err3.message || String(e)).split("\n").slice(-3).join(" / ") });
|
|
19148
|
+
}
|
|
19149
|
+
}
|
|
19150
|
+
return out;
|
|
19151
|
+
}
|
|
19152
|
+
function mirrorOutcome(m) {
|
|
19153
|
+
if (m.action === "failed") return { action: "clone-failed", detail: m.detail, repeat: false };
|
|
19154
|
+
return { action: "cloned", detail: `${m.action} ${m.detail}` };
|
|
19155
|
+
}
|
|
19156
|
+
|
|
19157
|
+
// src/mirror-contract.ts
|
|
19158
|
+
function lacksCheckout(ship, dir) {
|
|
19159
|
+
return ship.runner === "sandbox" && !existsSync40(dir);
|
|
19160
|
+
}
|
|
19161
|
+
function mirrorRepoOfTarget(t) {
|
|
19162
|
+
return mirrorRepoOf({ remote: t.remote, forgeHost: t.forgeHost });
|
|
19163
|
+
}
|
|
19164
|
+
async function readMirrorContract(cache, repo, base) {
|
|
19165
|
+
for (const name of REPO_CONFIG_FILENAMES) {
|
|
19166
|
+
const text2 = await cache.showFile(repo, base, name);
|
|
19167
|
+
if (text2 !== null) return parseRepoConfig(text2, `${repo.host}/${repo.path}:${base}:${name}`);
|
|
19168
|
+
}
|
|
19169
|
+
return null;
|
|
19170
|
+
}
|
|
19171
|
+
function noCheckoutReleaseNote(repo, mode) {
|
|
19172
|
+
if (mode === "local" || mode === null) {
|
|
19173
|
+
return `${repo}: no checkout on this ship; release.mode "local" repositories are released by the host that owns the deploy hook, which takes a ticket at verified`;
|
|
19174
|
+
}
|
|
19175
|
+
return `${repo}: no checkout on this ship; release.mode "${mode}" needs the sandboxed release over a mirror-fed clone, which is not built (CREW-2940)`;
|
|
19176
|
+
}
|
|
19177
|
+
var reported = /* @__PURE__ */ new Set();
|
|
19178
|
+
function firstReport(key) {
|
|
19179
|
+
if (reported.has(key)) return false;
|
|
19180
|
+
reported.add(key);
|
|
19181
|
+
return true;
|
|
19182
|
+
}
|
|
19183
|
+
|
|
19079
19184
|
// src/whoami.ts
|
|
19080
19185
|
async function buildWhoami(routes, deps = {}) {
|
|
19081
19186
|
const doFetch = deps.fetch ?? fetch;
|
|
@@ -19247,7 +19352,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
19247
19352
|
}
|
|
19248
19353
|
|
|
19249
19354
|
// src/watch.ts
|
|
19250
|
-
import { watch as watchFile, existsSync as
|
|
19355
|
+
import { watch as watchFile, existsSync as existsSync41, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
|
|
19251
19356
|
var E = "\x1B[";
|
|
19252
19357
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
19253
19358
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -19348,7 +19453,7 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
19348
19453
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
19349
19454
|
}
|
|
19350
19455
|
function readFrom(file, from) {
|
|
19351
|
-
if (!
|
|
19456
|
+
if (!existsSync41(file)) return { events: [], offset: from };
|
|
19352
19457
|
const size = statSync11(file).size;
|
|
19353
19458
|
if (size < from) return readFrom(file, 0);
|
|
19354
19459
|
if (size === from) return { events: [], offset: from };
|
|
@@ -19591,8 +19696,8 @@ function nextRoles(decision, ran) {
|
|
|
19591
19696
|
}
|
|
19592
19697
|
|
|
19593
19698
|
// src/ship-name.ts
|
|
19594
|
-
import { existsSync as
|
|
19595
|
-
import { dirname as
|
|
19699
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync37, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
|
|
19700
|
+
import { dirname as dirname26 } from "path";
|
|
19596
19701
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
19597
19702
|
var ShipNameError = class extends Error {
|
|
19598
19703
|
};
|
|
@@ -19653,7 +19758,7 @@ async function syncRouteName(i) {
|
|
|
19653
19758
|
return { status: "ok", ...base, action, name: i.local };
|
|
19654
19759
|
}
|
|
19655
19760
|
function readSyncedName(resolvedFile) {
|
|
19656
|
-
if (!
|
|
19761
|
+
if (!existsSync42(resolvedFile)) return void 0;
|
|
19657
19762
|
try {
|
|
19658
19763
|
const v = JSON.parse(readFileSync37(resolvedFile, "utf8")).shipName;
|
|
19659
19764
|
return typeof v === "string" ? v : void 0;
|
|
@@ -19662,7 +19767,7 @@ function readSyncedName(resolvedFile) {
|
|
|
19662
19767
|
}
|
|
19663
19768
|
}
|
|
19664
19769
|
function writeSyncedName(resolvedFile, name) {
|
|
19665
|
-
if (!
|
|
19770
|
+
if (!existsSync42(resolvedFile)) return;
|
|
19666
19771
|
let raw;
|
|
19667
19772
|
try {
|
|
19668
19773
|
raw = JSON.parse(readFileSync37(resolvedFile, "utf8"));
|
|
@@ -19671,7 +19776,7 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
19671
19776
|
}
|
|
19672
19777
|
if (raw.shipName === name) return;
|
|
19673
19778
|
raw.shipName = name;
|
|
19674
|
-
|
|
19779
|
+
mkdirSync30(dirname26(resolvedFile), { recursive: true });
|
|
19675
19780
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
19676
19781
|
writeFileSync32(tmp, `${JSON.stringify(raw, null, 2)}
|
|
19677
19782
|
`);
|
|
@@ -20364,8 +20469,8 @@ function describeCapability(i) {
|
|
|
20364
20469
|
}
|
|
20365
20470
|
|
|
20366
20471
|
// src/agents-sync-run.ts
|
|
20367
|
-
import { existsSync as
|
|
20368
|
-
import { dirname as
|
|
20472
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync38, writeFileSync as writeFileSync33 } from "fs";
|
|
20473
|
+
import { dirname as dirname27 } from "path";
|
|
20369
20474
|
async function runAgentsSync(target, o) {
|
|
20370
20475
|
const dryRun = o.dryRun ?? false;
|
|
20371
20476
|
const force = o.force ?? false;
|
|
@@ -20398,11 +20503,11 @@ async function runAgentsSync(target, o) {
|
|
|
20398
20503
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
20399
20504
|
if (!dryRun) {
|
|
20400
20505
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
20401
|
-
const raw =
|
|
20506
|
+
const raw = existsSync43(resolvedPath) ? JSON.parse(readFileSync38(resolvedPath, "utf8")) : {};
|
|
20402
20507
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
20403
20508
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
20404
20509
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
20405
|
-
|
|
20510
|
+
mkdirSync31(dirname27(resolvedPath), { recursive: true });
|
|
20406
20511
|
writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
20407
20512
|
`);
|
|
20408
20513
|
if (target.resolved) {
|
|
@@ -21025,7 +21130,7 @@ ${run3.err}` });
|
|
|
21025
21130
|
}
|
|
21026
21131
|
|
|
21027
21132
|
// src/sample-ticket.ts
|
|
21028
|
-
import { basename as basename12, dirname as
|
|
21133
|
+
import { basename as basename12, dirname as dirname28, join as join44 } from "path";
|
|
21029
21134
|
var SAMPLE_REPO = "crew-sample";
|
|
21030
21135
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
21031
21136
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -21048,7 +21153,7 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
21048
21153
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
21049
21154
|
`;
|
|
21050
21155
|
function sampleDir(existing, reposBase, workspace) {
|
|
21051
|
-
if (existing.length > 0) return join44(
|
|
21156
|
+
if (existing.length > 0) return join44(dirname28(existing[0]), SAMPLE_REPO);
|
|
21052
21157
|
return join44(reposBase, workspace, SAMPLE_REPO);
|
|
21053
21158
|
}
|
|
21054
21159
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
@@ -21093,8 +21198,8 @@ function planSampleTicket(i) {
|
|
|
21093
21198
|
}
|
|
21094
21199
|
|
|
21095
21200
|
// src/worktree-sweep.ts
|
|
21096
|
-
import { existsSync as
|
|
21097
|
-
import { basename as basename13, dirname as
|
|
21201
|
+
import { existsSync as existsSync44, readFileSync as readFileSync40, readdirSync as readdirSync14 } from "fs";
|
|
21202
|
+
import { basename as basename13, dirname as dirname29, join as join45, resolve as resolve5 } from "path";
|
|
21098
21203
|
function planWorktreeSweep(target, tickets, contract) {
|
|
21099
21204
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
21100
21205
|
const verified = contract.statuses.verified;
|
|
@@ -21158,7 +21263,7 @@ function worktreeTicketNumbers(target) {
|
|
|
21158
21263
|
return [...nums];
|
|
21159
21264
|
}
|
|
21160
21265
|
function siblingWorktreeDirs(target) {
|
|
21161
|
-
const parent =
|
|
21266
|
+
const parent = dirname29(target.dir.replace(/[/\\]+$/, ""));
|
|
21162
21267
|
const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
21163
21268
|
const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
|
|
21164
21269
|
let names;
|
|
@@ -21206,7 +21311,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
21206
21311
|
let removed = 0;
|
|
21207
21312
|
let keptBranches = 0;
|
|
21208
21313
|
for (const a of actions) {
|
|
21209
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
21314
|
+
if (!dryRun && base && !a.keepBranch && existsSync44(a.path) && isHandBackMerge(a.path, base)) {
|
|
21210
21315
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
21211
21316
|
log.emit(
|
|
21212
21317
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -21214,7 +21319,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
21214
21319
|
);
|
|
21215
21320
|
}
|
|
21216
21321
|
}
|
|
21217
|
-
if (!dryRun &&
|
|
21322
|
+
if (!dryRun && existsSync44(a.path)) {
|
|
21218
21323
|
let dirty = false;
|
|
21219
21324
|
try {
|
|
21220
21325
|
dirty = status(a.path).length > 0;
|
|
@@ -21225,7 +21330,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
21225
21330
|
continue;
|
|
21226
21331
|
}
|
|
21227
21332
|
}
|
|
21228
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
21333
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync44(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
21229
21334
|
refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
21230
21335
|
continue;
|
|
21231
21336
|
}
|
|
@@ -21379,12 +21484,12 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
21379
21484
|
}
|
|
21380
21485
|
|
|
21381
21486
|
// src/stream-sweep.ts
|
|
21382
|
-
import { existsSync as
|
|
21487
|
+
import { existsSync as existsSync45, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
|
|
21383
21488
|
import { join as join46 } from "path";
|
|
21384
21489
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
21385
21490
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
21386
21491
|
const dir = join46(stateDir, "streams");
|
|
21387
|
-
if (!
|
|
21492
|
+
if (!existsSync45(dir)) return [];
|
|
21388
21493
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
21389
21494
|
return readdirSync15(dir).map((name) => join46(dir, name)).filter((path) => {
|
|
21390
21495
|
try {
|
|
@@ -21412,7 +21517,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
21412
21517
|
return removed;
|
|
21413
21518
|
}
|
|
21414
21519
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
21415
|
-
if (!
|
|
21520
|
+
if (!existsSync45(stateDir)) return [];
|
|
21416
21521
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
21417
21522
|
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join46(stateDir, name)).filter((path) => {
|
|
21418
21523
|
try {
|
|
@@ -21424,11 +21529,11 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
21424
21529
|
}
|
|
21425
21530
|
|
|
21426
21531
|
// src/cli.ts
|
|
21427
|
-
import { realpathSync as realpathSync3, readFileSync as readFileSync46, existsSync as
|
|
21532
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync46, existsSync as existsSync52, mkdirSync as mkdirSync38, writeFileSync as writeFileSync38, rmSync as rmSync25, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
|
|
21428
21533
|
import { createInterface } from "readline/promises";
|
|
21429
21534
|
|
|
21430
21535
|
// src/periscope-cmd.ts
|
|
21431
|
-
import { existsSync as
|
|
21536
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync32, readFileSync as readFileSync41, rmSync as rmSync21, writeFileSync as writeFileSync34 } from "fs";
|
|
21432
21537
|
import { join as join47 } from "path";
|
|
21433
21538
|
var PeriscopeError = class extends Error {
|
|
21434
21539
|
};
|
|
@@ -21446,7 +21551,7 @@ function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
|
21446
21551
|
}
|
|
21447
21552
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
21448
21553
|
try {
|
|
21449
|
-
|
|
21554
|
+
mkdirSync32(priorDir(stateDir), { recursive: true });
|
|
21450
21555
|
writeFileSync34(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
21451
21556
|
} catch {
|
|
21452
21557
|
}
|
|
@@ -21454,7 +21559,7 @@ function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
|
21454
21559
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
21455
21560
|
try {
|
|
21456
21561
|
const p = priorPath(stateDir, ticketRecordId);
|
|
21457
|
-
return
|
|
21562
|
+
return existsSync46(p) ? JSON.parse(readFileSync41(p, "utf8")) : void 0;
|
|
21458
21563
|
} catch {
|
|
21459
21564
|
return void 0;
|
|
21460
21565
|
}
|
|
@@ -22009,8 +22114,8 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
|
22009
22114
|
|
|
22010
22115
|
// src/ssh-keys.ts
|
|
22011
22116
|
import { execFileSync as execFileSync13 } from "child_process";
|
|
22012
|
-
import { existsSync as
|
|
22013
|
-
import { dirname as
|
|
22117
|
+
import { existsSync as existsSync47, mkdirSync as mkdirSync33, readFileSync as readFileSync42, chmodSync as chmodSync3 } from "fs";
|
|
22118
|
+
import { dirname as dirname30, join as join49 } from "path";
|
|
22014
22119
|
var SshKeyError = class extends Error {
|
|
22015
22120
|
};
|
|
22016
22121
|
function shipSshKeyPathFor(stateDir) {
|
|
@@ -22019,8 +22124,8 @@ function shipSshKeyPathFor(stateDir) {
|
|
|
22019
22124
|
function ensureShipSshKeypair(stateDir) {
|
|
22020
22125
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
22021
22126
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
22022
|
-
if (!
|
|
22023
|
-
|
|
22127
|
+
if (!existsSync47(privateKeyPath)) {
|
|
22128
|
+
mkdirSync33(dirname30(privateKeyPath), { recursive: true });
|
|
22024
22129
|
try {
|
|
22025
22130
|
execFileSync13("ssh-keygen", [
|
|
22026
22131
|
"-t",
|
|
@@ -22040,7 +22145,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
22040
22145
|
}
|
|
22041
22146
|
chmodSync3(privateKeyPath, 384);
|
|
22042
22147
|
}
|
|
22043
|
-
if (!
|
|
22148
|
+
if (!existsSync47(publicKeyPath)) {
|
|
22044
22149
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
22045
22150
|
}
|
|
22046
22151
|
const publicKey = readFileSync42(publicKeyPath, "utf8").trim();
|
|
@@ -22207,46 +22312,9 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
22207
22312
|
import { createServer as createHttpServer } from "http";
|
|
22208
22313
|
import { spawn as spawn9 } from "child_process";
|
|
22209
22314
|
import { randomBytes } from "crypto";
|
|
22210
|
-
import { existsSync as
|
|
22315
|
+
import { existsSync as existsSync48, mkdirSync as mkdirSync34, readFileSync as readFileSync43, writeFileSync as writeFileSync35, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
|
|
22211
22316
|
import { join as join50 } from "path";
|
|
22212
22317
|
|
|
22213
|
-
// src/passenger-mirror.ts
|
|
22214
|
-
import { mkdirSync as mkdirSync33 } from "fs";
|
|
22215
|
-
import { dirname as dirname30 } from "path";
|
|
22216
|
-
function mirrorRepoOf(spec) {
|
|
22217
|
-
const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
|
|
22218
|
-
if (fromOrigin) return fromOrigin;
|
|
22219
|
-
if (!spec.remote) return null;
|
|
22220
|
-
return { host: normalizeForgeHost(spec.forgeHost) || DEFAULT_FORGE_HOST, path: spec.remote };
|
|
22221
|
-
}
|
|
22222
|
-
async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCache(s.mirror.cache)) {
|
|
22223
|
-
const out = [];
|
|
22224
|
-
for (const spec of specs) {
|
|
22225
|
-
if (!spec.mirror) continue;
|
|
22226
|
-
const base = { name: spec.name, workspaceId: spec.workspaceId };
|
|
22227
|
-
const repo = mirrorRepoOf(spec);
|
|
22228
|
-
if (!repo) {
|
|
22229
|
-
out.push({ ...base, action: "failed", detail: `no remote known for ${spec.name}, so its mirror cannot be found` });
|
|
22230
|
-
continue;
|
|
22231
|
-
}
|
|
22232
|
-
try {
|
|
22233
|
-
const parent = dirname30(spec.dir);
|
|
22234
|
-
mkdirSync33(parent, { recursive: true });
|
|
22235
|
-
mkdirSync33(spec.dir, { recursive: true });
|
|
22236
|
-
const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
|
|
22237
|
-
out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
|
|
22238
|
-
} catch (e) {
|
|
22239
|
-
const err3 = e;
|
|
22240
|
-
out.push({ ...base, action: "failed", detail: (err3.stderr?.trim() || err3.message || String(e)).split("\n").slice(-3).join(" / ") });
|
|
22241
|
-
}
|
|
22242
|
-
}
|
|
22243
|
-
return out;
|
|
22244
|
-
}
|
|
22245
|
-
function mirrorOutcome(m) {
|
|
22246
|
-
if (m.action === "failed") return { action: "clone-failed", detail: m.detail, repeat: false };
|
|
22247
|
-
return { action: "cloned", detail: `${m.action} ${m.detail}` };
|
|
22248
|
-
}
|
|
22249
|
-
|
|
22250
22318
|
// src/secret-compare.ts
|
|
22251
22319
|
import { timingSafeEqual } from "crypto";
|
|
22252
22320
|
function secretsMatch(a, b) {
|
|
@@ -22342,7 +22410,7 @@ function syncDaemonStatePath(stateDir, workspaceId) {
|
|
|
22342
22410
|
}
|
|
22343
22411
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
22344
22412
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
22345
|
-
if (!
|
|
22413
|
+
if (!existsSync48(path)) return void 0;
|
|
22346
22414
|
try {
|
|
22347
22415
|
return JSON.parse(readFileSync43(path, "utf8"));
|
|
22348
22416
|
} catch {
|
|
@@ -22373,7 +22441,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
22373
22441
|
}
|
|
22374
22442
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
22375
22443
|
const dir = join50(stateDir, "passengers");
|
|
22376
|
-
if (!
|
|
22444
|
+
if (!existsSync48(dir)) return [];
|
|
22377
22445
|
const suffix = "-sync.json";
|
|
22378
22446
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
22379
22447
|
}
|
|
@@ -22893,7 +22961,7 @@ function contractFixers(dir, baseRef, tickets, locate) {
|
|
|
22893
22961
|
}
|
|
22894
22962
|
|
|
22895
22963
|
// src/daemon.ts
|
|
22896
|
-
import { existsSync as
|
|
22964
|
+
import { existsSync as existsSync49, mkdirSync as mkdirSync36, readFileSync as readFileSync44, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
|
|
22897
22965
|
import { join as join52 } from "path";
|
|
22898
22966
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
22899
22967
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
@@ -22991,7 +23059,7 @@ function computeTreeSignature(paths) {
|
|
|
22991
23059
|
}
|
|
22992
23060
|
function makeStaleChecker(paths, opts = {}) {
|
|
22993
23061
|
const initial = computeTreeSignature(paths);
|
|
22994
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
23062
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync49(opts.reloadSentinel);
|
|
22995
23063
|
}
|
|
22996
23064
|
function reloadSentinelFor(stateDir) {
|
|
22997
23065
|
return join52(stateDir, "reload-requested");
|
|
@@ -23242,8 +23310,8 @@ async function runOnePassUnguarded(o) {
|
|
|
23242
23310
|
set.add(run3);
|
|
23243
23311
|
void run3.finally(() => set.delete(run3));
|
|
23244
23312
|
}
|
|
23245
|
-
const
|
|
23246
|
-
const tookTicket =
|
|
23313
|
+
const reported2 = TICKET_ROLES.has(role) ? await settled : true;
|
|
23314
|
+
const tookTicket = reported2 !== false;
|
|
23247
23315
|
if (tookTicket) started.push(role);
|
|
23248
23316
|
if (roleCap(role) <= 1) {
|
|
23249
23317
|
live.delete(role);
|
|
@@ -23580,7 +23648,7 @@ import { createHash as createHash10 } from "crypto";
|
|
|
23580
23648
|
import {
|
|
23581
23649
|
chmodSync as chmodSync4,
|
|
23582
23650
|
closeSync as closeSync6,
|
|
23583
|
-
existsSync as
|
|
23651
|
+
existsSync as existsSync50,
|
|
23584
23652
|
fstatSync,
|
|
23585
23653
|
openSync as openSync6,
|
|
23586
23654
|
readSync as readSync3,
|
|
@@ -23951,7 +24019,7 @@ function lastLines2(path, n) {
|
|
|
23951
24019
|
}
|
|
23952
24020
|
function readTail(live, opts) {
|
|
23953
24021
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
23954
|
-
if (!
|
|
24022
|
+
if (!existsSync50(live)) {
|
|
23955
24023
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
23956
24024
|
}
|
|
23957
24025
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -24108,7 +24176,7 @@ async function route(deps, method, url, req, abort) {
|
|
|
24108
24176
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
24109
24177
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
24110
24178
|
const file = join53(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
24111
|
-
if (!
|
|
24179
|
+
if (!existsSync50(file)) return err2(404, "no such run");
|
|
24112
24180
|
return tailReply(file, url.searchParams, false, abort);
|
|
24113
24181
|
}
|
|
24114
24182
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -24152,7 +24220,7 @@ function send(res, r) {
|
|
|
24152
24220
|
res.end(body);
|
|
24153
24221
|
}
|
|
24154
24222
|
async function socketIsStale(path) {
|
|
24155
|
-
if (!
|
|
24223
|
+
if (!existsSync50(path)) return false;
|
|
24156
24224
|
return new Promise((resolve8) => {
|
|
24157
24225
|
const c = createConnection(path);
|
|
24158
24226
|
c.once("connect", () => {
|
|
@@ -24171,7 +24239,7 @@ async function startControlServer(deps) {
|
|
|
24171
24239
|
return null;
|
|
24172
24240
|
}
|
|
24173
24241
|
if (await socketIsStale(path)) rmSync23(path, { force: true });
|
|
24174
|
-
else if (
|
|
24242
|
+
else if (existsSync50(path)) {
|
|
24175
24243
|
log(`control API not started: ${path} is in use by another process`);
|
|
24176
24244
|
return null;
|
|
24177
24245
|
}
|
|
@@ -24264,7 +24332,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
24264
24332
|
|
|
24265
24333
|
// src/periscope-endpoints.ts
|
|
24266
24334
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
24267
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
24335
|
+
import { chmodSync as chmodSync5, existsSync as existsSync51, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync45, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
|
|
24268
24336
|
import { join as join54 } from "path";
|
|
24269
24337
|
var secretDir = (stateDir) => join54(stateDir, "periscope-mcp");
|
|
24270
24338
|
var secretFile = (stateDir, id) => join54(secretDir(stateDir), `${id}.json`);
|
|
@@ -24307,7 +24375,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
|
24307
24375
|
}
|
|
24308
24376
|
function listPeriscopeEndpoints(stateDir) {
|
|
24309
24377
|
const dir = endpointDir(stateDir);
|
|
24310
|
-
if (!
|
|
24378
|
+
if (!existsSync51(dir)) return [];
|
|
24311
24379
|
const out = [];
|
|
24312
24380
|
for (const name of readdirSync19(dir)) {
|
|
24313
24381
|
if (!name.endsWith(".json")) continue;
|
|
@@ -25290,7 +25358,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25290
25358
|
} else {
|
|
25291
25359
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
|
|
25292
25360
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
|
|
25293
|
-
if (configless && !
|
|
25361
|
+
if (configless && !existsSync52(findConfigFile(CREW_HOME))) {
|
|
25294
25362
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
25295
25363
|
} else {
|
|
25296
25364
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -25634,7 +25702,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25634
25702
|
if (host === "linux" && hasSystemd) {
|
|
25635
25703
|
const label = labelFor(CREW_HOME, "run");
|
|
25636
25704
|
const staleTimer = join56(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
25637
|
-
if (
|
|
25705
|
+
if (existsSync52(staleTimer)) {
|
|
25638
25706
|
process.stdout.write(
|
|
25639
25707
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
25640
25708
|
`
|
|
@@ -25663,7 +25731,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25663
25731
|
const { settled: doctorRepos, errors: refusedRepos } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
|
|
25664
25732
|
for (const e of refusedRepos) problems.push(`repo ${e.repo}: ${e.detail}`);
|
|
25665
25733
|
for (const r of doctorRepos) {
|
|
25666
|
-
if (!
|
|
25734
|
+
if (!existsSync52(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
25667
25735
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
25668
25736
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
25669
25737
|
}
|
|
@@ -25690,7 +25758,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25690
25758
|
}
|
|
25691
25759
|
async function doctorFix(o) {
|
|
25692
25760
|
const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
|
|
25693
|
-
const unitExists = (p) =>
|
|
25761
|
+
const unitExists = (p) => existsSync52(p);
|
|
25694
25762
|
const crontab = o.host === "linux" ? spawnSync11("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
25695
25763
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
|
|
25696
25764
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -25842,9 +25910,25 @@ ${changed2.map((c) => ` - ${c}
|
|
|
25842
25910
|
}
|
|
25843
25911
|
}
|
|
25844
25912
|
const precheckTickets = /* @__PURE__ */ new Map();
|
|
25913
|
+
const scope0 = (c, t) => releaseLockScope(c.route, t.name);
|
|
25845
25914
|
async function releasePhase(c, target, opts = {}) {
|
|
25846
25915
|
const scope = releaseLockScope(c.route, target.name);
|
|
25847
25916
|
const remit = emit.forRoute(c.route);
|
|
25917
|
+
if (lacksCheckout(cfg.ship, target.dir)) {
|
|
25918
|
+
let mode = null;
|
|
25919
|
+
const settings = cacheSettingsOf(cfg.ship.sandbox);
|
|
25920
|
+
const mirror = mirrorRepoOfTarget(target);
|
|
25921
|
+
if (settings && mirror) {
|
|
25922
|
+
try {
|
|
25923
|
+
mode = (await readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main"))?.release.mode ?? null;
|
|
25924
|
+
} catch (e) {
|
|
25925
|
+
if (firstReport(`${scope0(c, target)}:contract`)) remit.warn(`${target.name}: could not read the contract from the mirror: ${e.message}`, { step: "release" });
|
|
25926
|
+
}
|
|
25927
|
+
}
|
|
25928
|
+
const note = noCheckoutReleaseNote(target.name, mode);
|
|
25929
|
+
if (firstReport(`${scope0(c, target)}:release`)) remit.emit(note, { step: "release" });
|
|
25930
|
+
return { scope: releaseLockScope(c.route, target.name), tests: "skipped", outcome: "skipped", detail: note };
|
|
25931
|
+
}
|
|
25848
25932
|
const relLock = dryRun ? void 0 : state.acquire(`release-${scope}`);
|
|
25849
25933
|
if (relLock && !relLock.ok) {
|
|
25850
25934
|
remit.emit(`a previous release (pid ${relLock.heldBy}) is still running \u2014 skipping`, { step: "release" });
|
|
@@ -26104,10 +26188,10 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26104
26188
|
if (outcome.gateRed && !dryRun) {
|
|
26105
26189
|
try {
|
|
26106
26190
|
const g = outcome.gateRed;
|
|
26107
|
-
const
|
|
26191
|
+
const reported2 = await tracker.events("gate_red");
|
|
26108
26192
|
const seat = releaseSeat(c);
|
|
26109
26193
|
for (const t of gateRedTickets(outcome.merged, outcome.decision.merges)) {
|
|
26110
|
-
if (alreadyReported(t, g,
|
|
26194
|
+
if (alreadyReported(t, g, reported2)) continue;
|
|
26111
26195
|
await tracker.postEvent(t.id, gateRedComment(g), seat, "gate_red");
|
|
26112
26196
|
remit.warn(`test gate red for ${g.count} cycles \u2014 noted on the ticket`, { ticket: t.issue_id, step: "merge" });
|
|
26113
26197
|
}
|
|
@@ -26118,10 +26202,10 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26118
26202
|
}
|
|
26119
26203
|
if (outcome.localRefSkips?.length && !dryRun) {
|
|
26120
26204
|
try {
|
|
26121
|
-
const
|
|
26205
|
+
const reported2 = await tracker.events("local_ref_skip");
|
|
26122
26206
|
const seat = releaseSeat(c);
|
|
26123
26207
|
for (const s of outcome.localRefSkips) {
|
|
26124
|
-
if (localRefSkipReported(s.ticket, s.problem,
|
|
26208
|
+
if (localRefSkipReported(s.ticket, s.problem, reported2)) continue;
|
|
26125
26209
|
await tracker.postEvent(s.ticket.id, localRefSkipComment(cfg.ship.name, s.problem), seat, "local_ref_skip");
|
|
26126
26210
|
}
|
|
26127
26211
|
} catch (e) {
|
|
@@ -27191,7 +27275,7 @@ ${cu.tail.trimEnd()}
|
|
|
27191
27275
|
`);
|
|
27192
27276
|
if (!dryRun) {
|
|
27193
27277
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
27194
|
-
const raw =
|
|
27278
|
+
const raw = existsSync52(resolvedPath) ? JSON.parse(readFileSync46(resolvedPath, "utf8")) : {};
|
|
27195
27279
|
raw.agentSkills = result.agentSkills;
|
|
27196
27280
|
mkdirSync38(dirOf2(resolvedPath), { recursive: true });
|
|
27197
27281
|
writeFileSync38(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
@@ -27296,8 +27380,8 @@ ${cu.tail.trimEnd()}
|
|
|
27296
27380
|
try {
|
|
27297
27381
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
27298
27382
|
const dir = repoDirOf(configText, dirname34(cfg.configFile), a.route, a.name);
|
|
27299
|
-
if (!
|
|
27300
|
-
const files = { exists: (r) =>
|
|
27383
|
+
if (!existsSync52(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
27384
|
+
const files = { exists: (r) => existsSync52(resolvePath(dir, r)), list: (r) => {
|
|
27301
27385
|
try {
|
|
27302
27386
|
return readdirSync20(resolvePath(dir, r));
|
|
27303
27387
|
} catch {
|
|
@@ -27415,7 +27499,7 @@ ${cu.tail.trimEnd()}
|
|
|
27415
27499
|
if (sub === "list" && !routeName) usage2();
|
|
27416
27500
|
if (sub === "add") {
|
|
27417
27501
|
try {
|
|
27418
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
27502
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync52(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
|
|
27419
27503
|
routeName = a.route;
|
|
27420
27504
|
addPath = a.path;
|
|
27421
27505
|
addName = a.name;
|
|
@@ -27445,7 +27529,7 @@ ${cu.tail.trimEnd()}
|
|
|
27445
27529
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
27446
27530
|
if (sub === "list" || sub === "add") {
|
|
27447
27531
|
const liveRoute = cfg.routes.find((r) => r.route === routeName);
|
|
27448
|
-
if (liveRoute?.resolved?.reposModelId &&
|
|
27532
|
+
if (liveRoute?.resolved?.reposModelId && existsSync52(resolvedFile) && !dryRun) {
|
|
27449
27533
|
try {
|
|
27450
27534
|
await hydrateApiKeys([liveRoute]);
|
|
27451
27535
|
persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
|
|
@@ -27454,7 +27538,7 @@ ${cu.tail.trimEnd()}
|
|
|
27454
27538
|
}
|
|
27455
27539
|
}
|
|
27456
27540
|
let resolvedRemotes;
|
|
27457
|
-
if (
|
|
27541
|
+
if (existsSync52(resolvedFile)) {
|
|
27458
27542
|
try {
|
|
27459
27543
|
resolvedRemotes = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
27460
27544
|
} catch {
|
|
@@ -27463,7 +27547,7 @@ ${cu.tail.trimEnd()}
|
|
|
27463
27547
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
27464
27548
|
const text2 = configText;
|
|
27465
27549
|
const base = dirname34(cfg.configFile);
|
|
27466
|
-
const isCheckout = (d) =>
|
|
27550
|
+
const isCheckout = (d) => existsSync52(`${d}/.git`);
|
|
27467
27551
|
try {
|
|
27468
27552
|
if (sub === "list") {
|
|
27469
27553
|
if (flag("json")) {
|
|
@@ -27473,7 +27557,7 @@ ${cu.tail.trimEnd()}
|
|
|
27473
27557
|
base,
|
|
27474
27558
|
resolved: resolvedRemotes,
|
|
27475
27559
|
originOf,
|
|
27476
|
-
exists: (d) =>
|
|
27560
|
+
exists: (d) => existsSync52(d),
|
|
27477
27561
|
isCheckout,
|
|
27478
27562
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
27479
27563
|
try {
|
|
@@ -27495,7 +27579,7 @@ ${cu.tail.trimEnd()}
|
|
|
27495
27579
|
for (const r of repos) {
|
|
27496
27580
|
const origin2 = originOf(r.dir);
|
|
27497
27581
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
27498
|
-
const note = !
|
|
27582
|
+
const note = !existsSync52(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
27499
27583
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
27500
27584
|
`);
|
|
27501
27585
|
}
|
|
@@ -27507,9 +27591,9 @@ ${cu.tail.trimEnd()}
|
|
|
27507
27591
|
` : `${human}
|
|
27508
27592
|
`);
|
|
27509
27593
|
const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
27510
|
-
if (
|
|
27594
|
+
if (existsSync52(plan2.target) && (existsSync52(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
|
|
27511
27595
|
throw new ReposError(
|
|
27512
|
-
|
|
27596
|
+
existsSync52(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
|
|
27513
27597
|
);
|
|
27514
27598
|
}
|
|
27515
27599
|
if (dryRun) {
|
|
@@ -27534,7 +27618,7 @@ ${cu.tail.trimEnd()}
|
|
|
27534
27618
|
break;
|
|
27535
27619
|
}
|
|
27536
27620
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
|
|
27537
|
-
const top =
|
|
27621
|
+
const top = existsSync52(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
27538
27622
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
27539
27623
|
const origin = originOf(top);
|
|
27540
27624
|
const plan = planRepoAdd({
|
|
@@ -27586,7 +27670,7 @@ ${cu.tail.trimEnd()}
|
|
|
27586
27670
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
27587
27671
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
27588
27672
|
`);
|
|
27589
|
-
if (
|
|
27673
|
+
if (existsSync52(resolvedFile)) {
|
|
27590
27674
|
const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
27591
27675
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
27592
27676
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
@@ -28008,7 +28092,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28008
28092
|
}
|
|
28009
28093
|
}
|
|
28010
28094
|
let previous;
|
|
28011
|
-
const hadPreviousFile =
|
|
28095
|
+
const hadPreviousFile = existsSync52(resolvedPath);
|
|
28012
28096
|
if (hadPreviousFile) {
|
|
28013
28097
|
try {
|
|
28014
28098
|
previous = JSON.parse(readFileSync46(resolvedPath, "utf8"));
|
|
@@ -28153,7 +28237,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28153
28237
|
let mintOutcomes = [];
|
|
28154
28238
|
if (!dryRun && previous?.agentPersonas && existingRoute?.resolved) {
|
|
28155
28239
|
try {
|
|
28156
|
-
mintOutcomes = await mintMissingSeatKeys(
|
|
28240
|
+
mintOutcomes = await mintMissingSeatKeys(withFoundSeats(existingRoute, found, previous.releaseAgentId), { userAgent: cfg.ship.userAgent, shipName: cfg.ship.name });
|
|
28157
28241
|
for (const o of mintOutcomes) process.stderr.write(`${describeMintOutcome(o)}
|
|
28158
28242
|
`);
|
|
28159
28243
|
for (const p of await probeSeatReads({ ...existingRoute, resolved: { ...existingRoute.resolved, seats: found.seats } }, { userAgent: cfg.ship.userAgent })) {
|
|
@@ -28888,7 +28972,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28888
28972
|
prepare: async (path) => {
|
|
28889
28973
|
for (const f of repoWt.config.worktrees.copy) {
|
|
28890
28974
|
const src = join56(repoWt.dir, f);
|
|
28891
|
-
if (!
|
|
28975
|
+
if (!existsSync52(src) || existsSync52(join56(path, f))) continue;
|
|
28892
28976
|
mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
|
|
28893
28977
|
copyFileSync2(src, join56(path, f));
|
|
28894
28978
|
}
|
|
@@ -29111,7 +29195,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
29111
29195
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
29112
29196
|
const resumed = await runPeriscopeResume({
|
|
29113
29197
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
29114
|
-
worktreeExists: (p) =>
|
|
29198
|
+
worktreeExists: (p) => existsSync52(p),
|
|
29115
29199
|
prepareDropFile,
|
|
29116
29200
|
startProxy: (wt, url, n) => launchProxy(crewInvocation(CREW_HOME), wt, url, Number(n), { host: hit.row.host || "localhost", inject: hit.repo.config.periscope?.inspector !== "repo" }),
|
|
29117
29201
|
caps: periscopeCaps(cfg.ship),
|
|
@@ -29208,7 +29292,7 @@ ${where.join("\n")}`);
|
|
|
29208
29292
|
prepare: async (path) => {
|
|
29209
29293
|
for (const f of repoP.config.worktrees.copy) {
|
|
29210
29294
|
const src = join56(repoP.dir, f);
|
|
29211
|
-
if (!
|
|
29295
|
+
if (!existsSync52(src)) continue;
|
|
29212
29296
|
mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
|
|
29213
29297
|
copyFileSync2(src, join56(path, f));
|
|
29214
29298
|
}
|
|
@@ -29700,7 +29784,7 @@ ${where.join("\n")}`);
|
|
|
29700
29784
|
}
|
|
29701
29785
|
if (row) {
|
|
29702
29786
|
await tracker.deleteRepoRow(row.id);
|
|
29703
|
-
if (
|
|
29787
|
+
if (existsSync52(resolvedFile)) {
|
|
29704
29788
|
const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
29705
29789
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
29706
29790
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
@@ -29708,13 +29792,13 @@ ${where.join("\n")}`);
|
|
|
29708
29792
|
`);
|
|
29709
29793
|
}
|
|
29710
29794
|
}
|
|
29711
|
-
if (
|
|
29795
|
+
if (existsSync52(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
|
|
29712
29796
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
29713
29797
|
break;
|
|
29714
29798
|
}
|
|
29715
29799
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
29716
|
-
const fresh = !
|
|
29717
|
-
if (
|
|
29800
|
+
const fresh = !existsSync52(join56(dir, ".git"));
|
|
29801
|
+
if (existsSync52(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
29718
29802
|
if (dryRun) {
|
|
29719
29803
|
say("dry-run", { dir, newCheckout: fresh, repoRow: row?.id ?? null }, `would ${fresh ? "create" : "reuse"} ${dir}${row ? "" : ", create the Repos row"}, and file one ${tracker.contract.statuses.approved} ticket`);
|
|
29720
29804
|
break;
|
|
@@ -29740,7 +29824,7 @@ ${where.join("\n")}`);
|
|
|
29740
29824
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
29741
29825
|
});
|
|
29742
29826
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
29743
|
-
if (
|
|
29827
|
+
if (existsSync52(resolvedFile)) {
|
|
29744
29828
|
const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
29745
29829
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
29746
29830
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
@@ -30096,7 +30180,7 @@ watermark: ${state.watermark()}
|
|
|
30096
30180
|
}
|
|
30097
30181
|
for (const r of repos) {
|
|
30098
30182
|
const remote = r.config.branch.remote;
|
|
30099
|
-
if (
|
|
30183
|
+
if (existsSync52(r.dir)) {
|
|
30100
30184
|
pushAuth.push({
|
|
30101
30185
|
route: c.route,
|
|
30102
30186
|
repo: r.name,
|
|
@@ -30197,7 +30281,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
30197
30281
|
}
|
|
30198
30282
|
try {
|
|
30199
30283
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
30200
|
-
const evSize =
|
|
30284
|
+
const evSize = existsSync52(evFile) ? statSync16(evFile).size : 0;
|
|
30201
30285
|
process.stdout.write(
|
|
30202
30286
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
30203
30287
|
` : "events log: ok\n"
|
package/package.json
CHANGED