@tablation/crew 0.8.0 → 0.10.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/README.md +2 -2
- package/dist/cli.js +206 -47
- package/package.json +1 -1
- package/prompts/default/personas/common.md +7 -4
- package/prompts/dev-qa/personas/common.md +7 -4
package/README.md
CHANGED
|
@@ -213,8 +213,8 @@ crew inbox [--member NAME] your tickets across every workspace
|
|
|
213
213
|
crew connect WS[/PROJECT] resolve a workspace/project's ids into the state tree
|
|
214
214
|
crew repos add [ROUTE] [NAME] [PATH] attach a local checkout to a route in crew.yaml, creating its Repos row if missing (--dry-run previews)
|
|
215
215
|
crew repos list ROUTE the checkouts a route has, and the tracker Repos row each matches
|
|
216
|
-
crew install
|
|
217
|
-
crew uninstall
|
|
216
|
+
crew install [--replace] write and load this platform's scheduler unit (macOS: clears a crew installed from another path)
|
|
217
|
+
crew uninstall [--all] unload and remove it (--all: every crew launchd unit, whichever path it came from)
|
|
218
218
|
```
|
|
219
219
|
|
|
220
220
|
Every command that could change something takes `--dry-run`.
|
package/dist/cli.js
CHANGED
|
@@ -1930,7 +1930,7 @@ function describeRelease(o, route) {
|
|
|
1930
1930
|
// src/install.ts
|
|
1931
1931
|
import { dirname as dirname3, join as join5, extname } from "path";
|
|
1932
1932
|
import { homedir as homedir2 } from "os";
|
|
1933
|
-
import { accessSync, constants, existsSync as existsSync5, mkdirSync as mkdirSync3, statSync as statSync2, writeFileSync as writeFileSync2, unlinkSync, realpathSync } from "fs";
|
|
1933
|
+
import { accessSync, constants, existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync5, statSync as statSync2, writeFileSync as writeFileSync2, unlinkSync, realpathSync } from "fs";
|
|
1934
1934
|
import { spawnSync } from "child_process";
|
|
1935
1935
|
import { createHash } from "crypto";
|
|
1936
1936
|
|
|
@@ -2164,7 +2164,8 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
2164
2164
|
mechanism: "launchd",
|
|
2165
2165
|
unitPaths: [unitPath],
|
|
2166
2166
|
crontabLine: null,
|
|
2167
|
-
unloadCommands: [["launchctl", "unload", unitPath]]
|
|
2167
|
+
unloadCommands: [["launchctl", "unload", unitPath]],
|
|
2168
|
+
removeLabel: label
|
|
2168
2169
|
};
|
|
2169
2170
|
}
|
|
2170
2171
|
if (hostShip === "linux" && hasSystemd) {
|
|
@@ -2282,6 +2283,15 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
2282
2283
|
if (!ok) log.warn("unload command failed \u2014 it may not have been loaded; continuing to remove files");
|
|
2283
2284
|
}
|
|
2284
2285
|
}
|
|
2286
|
+
if (plan.removeLabel) {
|
|
2287
|
+
if (dryRun) {
|
|
2288
|
+
log.emit(`would run: launchctl remove ${plan.removeLabel}`);
|
|
2289
|
+
} else {
|
|
2290
|
+
if (spawnSync("launchctl", ["remove", plan.removeLabel], { encoding: "utf8" }).status === 0) {
|
|
2291
|
+
log.emit(`removed loaded job ${plan.removeLabel}`);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2285
2295
|
for (const path of plan.unitPaths) {
|
|
2286
2296
|
if (!existsSync5(path)) continue;
|
|
2287
2297
|
if (dryRun) {
|
|
@@ -2300,6 +2310,91 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
2300
2310
|
}
|
|
2301
2311
|
}
|
|
2302
2312
|
}
|
|
2313
|
+
var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
|
|
2314
|
+
function launchAgentsDir() {
|
|
2315
|
+
return join5(homedir2(), "Library", "LaunchAgents");
|
|
2316
|
+
}
|
|
2317
|
+
function xmlUnescape(s) {
|
|
2318
|
+
return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
2319
|
+
}
|
|
2320
|
+
function programFromPlist(text) {
|
|
2321
|
+
const block = /<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(text);
|
|
2322
|
+
if (!block) return null;
|
|
2323
|
+
const args = [...block[1].matchAll(/<string>([\s\S]*?)<\/string>/g)].map((m) => xmlUnescape(m[1]));
|
|
2324
|
+
return args.find((a) => a.endsWith("/bin/crew")) ?? args[0] ?? null;
|
|
2325
|
+
}
|
|
2326
|
+
function listLaunchdUnits(dir = launchAgentsDir()) {
|
|
2327
|
+
if (!existsSync5(dir)) return [];
|
|
2328
|
+
return readdirSync(dir).filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
2329
|
+
const plistPath = join5(dir, f);
|
|
2330
|
+
let program = null;
|
|
2331
|
+
try {
|
|
2332
|
+
program = programFromPlist(readFileSync5(plistPath, "utf8"));
|
|
2333
|
+
} catch {
|
|
2334
|
+
}
|
|
2335
|
+
return {
|
|
2336
|
+
label: f.slice(0, -".plist".length),
|
|
2337
|
+
plistPath,
|
|
2338
|
+
program,
|
|
2339
|
+
programExists: program !== null && existsSync5(program)
|
|
2340
|
+
};
|
|
2341
|
+
});
|
|
2342
|
+
}
|
|
2343
|
+
function foreignLaunchdUnits(crewHome, dir) {
|
|
2344
|
+
const own = new Set(["run", "release", "passengers"].map((job) => labelFor(crewHome, job)));
|
|
2345
|
+
return listLaunchdUnits(dir).filter((u) => !own.has(u.label));
|
|
2346
|
+
}
|
|
2347
|
+
function removeLaunchdUnit(unit, dryRun, log) {
|
|
2348
|
+
if (dryRun) {
|
|
2349
|
+
log.emit(`would unload and remove ${unit.label}`);
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
if (existsSync5(unit.plistPath)) spawnSync("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
|
|
2353
|
+
spawnSync("launchctl", ["remove", unit.label], { encoding: "utf8" });
|
|
2354
|
+
if (existsSync5(unit.plistPath)) unlinkSync(unit.plistPath);
|
|
2355
|
+
log.emit(`removed ${unit.label} (${unit.plistPath})`);
|
|
2356
|
+
}
|
|
2357
|
+
function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir) {
|
|
2358
|
+
const foreign = foreignLaunchdUnits(crewHome, dir);
|
|
2359
|
+
const blocking = foreign.filter((u) => u.program === null || u.programExists);
|
|
2360
|
+
if (blocking.length > 0 && !replace) {
|
|
2361
|
+
log.warn(
|
|
2362
|
+
`two crew installs target this machine: ${blocking.map((u) => `${u.label} runs ${u.program ?? "an unreadable ProgramArguments"}`).join("; ")}. Run \`crew uninstall\` from the other one, or pass --replace.`
|
|
2363
|
+
);
|
|
2364
|
+
return false;
|
|
2365
|
+
}
|
|
2366
|
+
for (const unit of foreign) {
|
|
2367
|
+
const why = unit.program === null ? "replacing a unit with no readable ProgramArguments" : unit.programExists ? "replacing a live other install" : "removing a stale unit whose program is gone";
|
|
2368
|
+
log.emit(`${why}: ${unit.label}`);
|
|
2369
|
+
removeLaunchdUnit(unit, dryRun, log);
|
|
2370
|
+
}
|
|
2371
|
+
return true;
|
|
2372
|
+
}
|
|
2373
|
+
function removeAllLaunchdUnits(dryRun, log, dir) {
|
|
2374
|
+
for (const unit of listLaunchdUnits(dir)) removeLaunchdUnit(unit, dryRun, log);
|
|
2375
|
+
}
|
|
2376
|
+
function parseLaunchctlList(text) {
|
|
2377
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
2378
|
+
for (const line of text.split("\n")) {
|
|
2379
|
+
const cols = line.split(" ");
|
|
2380
|
+
if (cols.length !== 3 || cols[2] === "Label") continue;
|
|
2381
|
+
jobs.set(cols[2], { pid: cols[0], lastExit: cols[1] });
|
|
2382
|
+
}
|
|
2383
|
+
return jobs;
|
|
2384
|
+
}
|
|
2385
|
+
function describeForeignLaunchdUnits(crewHome, dir) {
|
|
2386
|
+
const foreign = foreignLaunchdUnits(crewHome, dir);
|
|
2387
|
+
if (foreign.length === 0) return [];
|
|
2388
|
+
const list = spawnSync("launchctl", ["list"], { encoding: "utf8" });
|
|
2389
|
+
const jobs = parseLaunchctlList(list.status === 0 ? list.stdout ?? "" : "");
|
|
2390
|
+
return foreign.map((u) => {
|
|
2391
|
+
const status2 = jobs.get(u.label);
|
|
2392
|
+
const loaded = status2 ? `loaded, last exit ${status2.lastExit}` : "not loaded";
|
|
2393
|
+
const program = u.program ? `${u.program} (${u.programExists ? "present" : "GONE"})` : "no readable ProgramArguments";
|
|
2394
|
+
const stale = status2 && !u.programExists ? " \u2014 stale: `crew install` removes it" : "";
|
|
2395
|
+
return ` ${u.label}: ${loaded}; runs ${program}${stale}`;
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2303
2398
|
function readCrontab() {
|
|
2304
2399
|
const res = spawnSync("crontab", ["-l"], { encoding: "utf8" });
|
|
2305
2400
|
return res.status === 0 ? res.stdout ?? "" : "";
|
|
@@ -3860,6 +3955,13 @@ function fastForward(cwd, branch) {
|
|
|
3860
3955
|
if (!s.canFastForward) return false;
|
|
3861
3956
|
return gitOk(cwd, ["merge", "--ff-only", s.upstream]) !== null;
|
|
3862
3957
|
}
|
|
3958
|
+
function baseBranchUnsafe(r) {
|
|
3959
|
+
return r.action === "diverged" || r.action === "ahead" || r.action === "ff-failed";
|
|
3960
|
+
}
|
|
3961
|
+
function describeUnsafeBase(r, remote, base) {
|
|
3962
|
+
const counts = r.action === "diverged" ? `${r.ahead} ahead, ${r.behind} behind ${remote}/${base}` : r.action === "ahead" ? `${r.ahead} ahead of ${remote}/${base}` : `${r.behind} behind ${remote}/${base}, cannot fast-forward`;
|
|
3963
|
+
return `${r.detail} [${counts}] \u2014 reconcile it (\`crew sync\` afterwards), or \`git reset --hard ${remote}/${base}\` if the local commits duplicate a release`;
|
|
3964
|
+
}
|
|
3863
3965
|
function refreshBaseBranch(cwd, remote, base, dryRun = false) {
|
|
3864
3966
|
if (currentBranch(cwd) !== base || status(cwd).length > 0) {
|
|
3865
3967
|
return { action: "not-applicable", detail: `${cwd} is not on a clean ${base}` };
|
|
@@ -3886,6 +3988,9 @@ function refreshBaseBranch(cwd, remote, base, dryRun = false) {
|
|
|
3886
3988
|
detail: `${base} has diverged from ${upstream} (${ahead} ahead, ${behind} behind)`
|
|
3887
3989
|
};
|
|
3888
3990
|
}
|
|
3991
|
+
if (behind === 0 && ahead > 0) {
|
|
3992
|
+
return { action: "ahead", ahead, detail: `${base} has ${ahead} commit(s) ${upstream} does not` };
|
|
3993
|
+
}
|
|
3889
3994
|
if (behind === 0) return { action: "level", detail: `${base} is level with ${upstream}` };
|
|
3890
3995
|
if (dryRun) {
|
|
3891
3996
|
return { action: "would-fast-forward", behind, detail: `would fast-forward ${base}: ${behind} behind ${upstream}` };
|
|
@@ -4511,7 +4616,7 @@ var alreadyReported = (ticket, g, comments) => comments.some((c) => c.ticket_id
|
|
|
4511
4616
|
|
|
4512
4617
|
// src/agent.ts
|
|
4513
4618
|
import { spawn as spawn2 } from "child_process";
|
|
4514
|
-
import { readFileSync as
|
|
4619
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync4, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4, createWriteStream } from "fs";
|
|
4515
4620
|
import { finished } from "stream/promises";
|
|
4516
4621
|
import { join as join7, dirname as dirname5 } from "path";
|
|
4517
4622
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -4954,10 +5059,10 @@ function planAgentRun(o) {
|
|
|
4954
5059
|
let ageSeconds;
|
|
4955
5060
|
if (existsSync7(digestPath2)) {
|
|
4956
5061
|
ageSeconds = Math.floor((now() - statSync4(digestPath2).mtimeMs) / 1e3);
|
|
4957
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
5062
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync6(digestPath2, "utf8");
|
|
4958
5063
|
}
|
|
4959
|
-
const common =
|
|
4960
|
-
const brief =
|
|
5064
|
+
const common = readFileSync6(commonPath, "utf8");
|
|
5065
|
+
const brief = readFileSync6(briefPath, "utf8");
|
|
4961
5066
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
4962
5067
|
const effectiveBrief = o.divergedPrompt ?? brief;
|
|
4963
5068
|
const prompt = assemblePrompt({
|
|
@@ -5337,7 +5442,7 @@ Wrote ${o.configPath}.
|
|
|
5337
5442
|
}
|
|
5338
5443
|
|
|
5339
5444
|
// src/release-run.ts
|
|
5340
|
-
import { readFileSync as
|
|
5445
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
|
|
5341
5446
|
import { join as join9 } from "path";
|
|
5342
5447
|
|
|
5343
5448
|
// src/release.ts
|
|
@@ -5802,7 +5907,7 @@ async function bump(o, size) {
|
|
|
5802
5907
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
5803
5908
|
return null;
|
|
5804
5909
|
}
|
|
5805
|
-
const current = JSON.parse(
|
|
5910
|
+
const current = JSON.parse(readFileSync7(first, "utf8")).version;
|
|
5806
5911
|
if (!current) {
|
|
5807
5912
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
5808
5913
|
return null;
|
|
@@ -5815,7 +5920,7 @@ async function bump(o, size) {
|
|
|
5815
5920
|
return version;
|
|
5816
5921
|
}
|
|
5817
5922
|
for (const f of files) {
|
|
5818
|
-
const src =
|
|
5923
|
+
const src = readFileSync7(f, "utf8");
|
|
5819
5924
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
5820
5925
|
if (out === src) {
|
|
5821
5926
|
o.emit.error(`no version field in ${f}`);
|
|
@@ -5834,7 +5939,7 @@ function writeChangelog(o, version, entries) {
|
|
|
5834
5939
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
5835
5940
|
return;
|
|
5836
5941
|
}
|
|
5837
|
-
const existing = existsSync9(path) ?
|
|
5942
|
+
const existing = existsSync9(path) ? readFileSync7(path, "utf8") : "# Changelog\n";
|
|
5838
5943
|
writeFileSync6(path, insertChangelogSection(existing, section));
|
|
5839
5944
|
}
|
|
5840
5945
|
async function confirm(o, expected) {
|
|
@@ -5884,9 +5989,13 @@ function refreshBase(o) {
|
|
|
5884
5989
|
// base. Fast-forwarding is impossible and merging would be this crew
|
|
5885
5990
|
// inventing a resolution nobody asked for, on the branch everything
|
|
5886
5991
|
// ships from.
|
|
5992
|
+
// AHEAD is a stop too (CREW-1379): local commits nobody asked for on the
|
|
5993
|
+
// base are how a second ship's release came to diverge from origin's.
|
|
5994
|
+
// Releasing on top of a hand commit is the failure; reconcile it first.
|
|
5995
|
+
case "ahead":
|
|
5887
5996
|
case "diverged":
|
|
5888
5997
|
case "ff-failed":
|
|
5889
|
-
return { ok: false, why: r.
|
|
5998
|
+
return { ok: false, why: describeUnsafeBase(r, o.repo.branch.remote, o.repo.branch.base) };
|
|
5890
5999
|
}
|
|
5891
6000
|
}
|
|
5892
6001
|
async function reconfirmLastRelease(o, decision) {
|
|
@@ -6682,7 +6791,7 @@ function nextRoles(decision, ran) {
|
|
|
6682
6791
|
}
|
|
6683
6792
|
|
|
6684
6793
|
// src/agents.ts
|
|
6685
|
-
import { readFileSync as
|
|
6794
|
+
import { readFileSync as readFileSync8, existsSync as existsSync12 } from "fs";
|
|
6686
6795
|
import { join as join11 } from "path";
|
|
6687
6796
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError6, TablationApiError } from "@tablation/client";
|
|
6688
6797
|
var PERSONA_NAME = {
|
|
@@ -6699,10 +6808,10 @@ var AgentsSyncError = class extends Error {
|
|
|
6699
6808
|
function personaDefaultPrompt(promptsDir, role) {
|
|
6700
6809
|
const briefPath = join11(promptsDir, "personas", `lane-${role}.md`);
|
|
6701
6810
|
if (!existsSync12(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
6702
|
-
if (role === "pair") return
|
|
6811
|
+
if (role === "pair") return readFileSync8(briefPath, "utf8");
|
|
6703
6812
|
const commonPath = join11(promptsDir, "personas", "common.md");
|
|
6704
6813
|
if (!existsSync12(commonPath)) throw new AgentsSyncError(`no shared policy at ${commonPath}`);
|
|
6705
|
-
return `${
|
|
6814
|
+
return `${readFileSync8(commonPath, "utf8")}${readFileSync8(briefPath, "utf8")}`;
|
|
6706
6815
|
}
|
|
6707
6816
|
async function resolveAgentId(route, role, opts) {
|
|
6708
6817
|
const cached = route.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -7323,14 +7432,14 @@ import {
|
|
|
7323
7432
|
} from "@tablation/client";
|
|
7324
7433
|
|
|
7325
7434
|
// src/skills.ts
|
|
7326
|
-
import { readFileSync as
|
|
7435
|
+
import { readFileSync as readFileSync9, readdirSync as readdirSync2, existsSync as existsSync13 } from "fs";
|
|
7327
7436
|
import { join as join12 } from "path";
|
|
7328
7437
|
import { parse as parseYaml } from "yaml";
|
|
7329
7438
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError7, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
7330
7439
|
var SkillSyncError = class extends Error {
|
|
7331
7440
|
};
|
|
7332
7441
|
function parseSkillFile(path) {
|
|
7333
|
-
const text =
|
|
7442
|
+
const text = readFileSync9(path, "utf8");
|
|
7334
7443
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text);
|
|
7335
7444
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
7336
7445
|
const frontmatter = match[1];
|
|
@@ -7345,7 +7454,7 @@ function parseSkillFile(path) {
|
|
|
7345
7454
|
function listLocalSkills(promptsDir) {
|
|
7346
7455
|
const dir = join12(promptsDir, "skills");
|
|
7347
7456
|
if (!existsSync13(dir)) return [];
|
|
7348
|
-
return
|
|
7457
|
+
return readdirSync2(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join12(dir, f)));
|
|
7349
7458
|
}
|
|
7350
7459
|
async function syncSkills(route, opts) {
|
|
7351
7460
|
if (!route.resolved) {
|
|
@@ -7670,7 +7779,7 @@ function resolveRepoProject(projects, opt, routeAreaId) {
|
|
|
7670
7779
|
|
|
7671
7780
|
// src/claude-mcp.ts
|
|
7672
7781
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
7673
|
-
import { readFileSync as
|
|
7782
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
7674
7783
|
import { homedir as homedir4 } from "os";
|
|
7675
7784
|
import { join as join13 } from "path";
|
|
7676
7785
|
var MCP_SERVER_NAME = "tablation";
|
|
@@ -7685,7 +7794,7 @@ function runClaude(bin, args) {
|
|
|
7685
7794
|
}
|
|
7686
7795
|
function userScopeEntry(home) {
|
|
7687
7796
|
try {
|
|
7688
|
-
const j = JSON.parse(
|
|
7797
|
+
const j = JSON.parse(readFileSync10(join13(home, ".claude.json"), "utf8"));
|
|
7689
7798
|
return j.mcpServers?.[MCP_SERVER_NAME];
|
|
7690
7799
|
} catch {
|
|
7691
7800
|
return void 0;
|
|
@@ -7884,14 +7993,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
7884
7993
|
}
|
|
7885
7994
|
|
|
7886
7995
|
// src/stream-sweep.ts
|
|
7887
|
-
import { existsSync as existsSync15, readdirSync as
|
|
7996
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, rmSync as rmSync2, statSync as statSync6 } from "fs";
|
|
7888
7997
|
import { join as join14 } from "path";
|
|
7889
7998
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
7890
7999
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
7891
8000
|
const dir = join14(stateDir, "streams");
|
|
7892
8001
|
if (!existsSync15(dir)) return [];
|
|
7893
8002
|
const cutoff = now - retentionDays * DAY_MS;
|
|
7894
|
-
return
|
|
8003
|
+
return readdirSync3(dir).map((name) => join14(dir, name)).filter((path) => {
|
|
7895
8004
|
try {
|
|
7896
8005
|
return statSync6(path).mtimeMs < cutoff;
|
|
7897
8006
|
} catch {
|
|
@@ -7918,13 +8027,13 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
7918
8027
|
}
|
|
7919
8028
|
|
|
7920
8029
|
// src/cli.ts
|
|
7921
|
-
import { readFileSync as
|
|
8030
|
+
import { readFileSync as readFileSync14, existsSync as existsSync19, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9, unlinkSync as unlinkSync5, copyFileSync, renameSync } from "fs";
|
|
7922
8031
|
import { createInterface } from "readline/promises";
|
|
7923
8032
|
import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
7924
8033
|
|
|
7925
8034
|
// src/ssh-keys.ts
|
|
7926
8035
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
7927
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as
|
|
8036
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11, chmodSync } from "fs";
|
|
7928
8037
|
import { dirname as dirname8, join as join15 } from "path";
|
|
7929
8038
|
var SshKeyError = class extends Error {
|
|
7930
8039
|
};
|
|
@@ -7958,7 +8067,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
7958
8067
|
if (!existsSync16(publicKeyPath)) {
|
|
7959
8068
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
7960
8069
|
}
|
|
7961
|
-
const publicKey =
|
|
8070
|
+
const publicKey = readFileSync11(publicKeyPath, "utf8").trim();
|
|
7962
8071
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
7963
8072
|
}
|
|
7964
8073
|
function sshKeygenAvailable() {
|
|
@@ -8172,7 +8281,7 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
8172
8281
|
|
|
8173
8282
|
// src/tunnel.ts
|
|
8174
8283
|
import { spawn as spawn3 } from "child_process";
|
|
8175
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as
|
|
8284
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3, readdirSync as readdirSync4 } from "fs";
|
|
8176
8285
|
import { randomInt } from "crypto";
|
|
8177
8286
|
import { join as join16 } from "path";
|
|
8178
8287
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
@@ -8247,7 +8356,7 @@ function readPersistedTunnel(stateDir, workspaceId) {
|
|
|
8247
8356
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
8248
8357
|
if (!existsSync17(path)) return void 0;
|
|
8249
8358
|
try {
|
|
8250
|
-
return JSON.parse(
|
|
8359
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
8251
8360
|
} catch {
|
|
8252
8361
|
return void 0;
|
|
8253
8362
|
}
|
|
@@ -8354,7 +8463,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
8354
8463
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
8355
8464
|
const dir = join16(stateDir, "passengers");
|
|
8356
8465
|
if (!existsSync17(dir)) return [];
|
|
8357
|
-
return
|
|
8466
|
+
return readdirSync4(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
8358
8467
|
}
|
|
8359
8468
|
|
|
8360
8469
|
// src/mcp-endpoint-registry.ts
|
|
@@ -8474,7 +8583,7 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
8474
8583
|
import { createServer as createHttpServer } from "http";
|
|
8475
8584
|
import { spawn as spawn4 } from "child_process";
|
|
8476
8585
|
import { randomBytes } from "crypto";
|
|
8477
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as
|
|
8586
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync13, writeFileSync as writeFileSync8, unlinkSync as unlinkSync4, readdirSync as readdirSync5 } from "fs";
|
|
8478
8587
|
import { join as join17 } from "path";
|
|
8479
8588
|
|
|
8480
8589
|
// src/secret-compare.ts
|
|
@@ -8570,7 +8679,7 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
|
8570
8679
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
8571
8680
|
if (!existsSync18(path)) return void 0;
|
|
8572
8681
|
try {
|
|
8573
|
-
return JSON.parse(
|
|
8682
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
8574
8683
|
} catch {
|
|
8575
8684
|
return void 0;
|
|
8576
8685
|
}
|
|
@@ -8601,7 +8710,7 @@ function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
|
8601
8710
|
const dir = join17(stateDir, "passengers");
|
|
8602
8711
|
if (!existsSync18(dir)) return [];
|
|
8603
8712
|
const suffix = "-sync.json";
|
|
8604
|
-
return
|
|
8713
|
+
return readdirSync5(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
8605
8714
|
}
|
|
8606
8715
|
function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
8607
8716
|
const alive = deps.isPidAlive ?? isPidAlive;
|
|
@@ -8656,7 +8765,7 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
8656
8765
|
}
|
|
8657
8766
|
|
|
8658
8767
|
// src/daemon.ts
|
|
8659
|
-
import { readdirSync as
|
|
8768
|
+
import { readdirSync as readdirSync6, statSync as statSync7 } from "fs";
|
|
8660
8769
|
import { join as join18 } from "path";
|
|
8661
8770
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
8662
8771
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
@@ -8695,7 +8804,7 @@ function latestMtime(path) {
|
|
|
8695
8804
|
if (stat.isFile()) return stat.mtimeMs;
|
|
8696
8805
|
if (!stat.isDirectory()) return 0;
|
|
8697
8806
|
let latest = stat.mtimeMs;
|
|
8698
|
-
for (const entry of
|
|
8807
|
+
for (const entry of readdirSync6(path, { withFileTypes: true })) {
|
|
8699
8808
|
latest = Math.max(latest, latestMtime(join18(path, entry.name)));
|
|
8700
8809
|
}
|
|
8701
8810
|
return latest;
|
|
@@ -9175,6 +9284,11 @@ if (command === "passenger-sync-daemon") {
|
|
|
9175
9284
|
if (!why) return false;
|
|
9176
9285
|
emit.emit(`${why} \u2014 release skipped`, { step: "release" });
|
|
9177
9286
|
return true;
|
|
9287
|
+
}, baseHealth = function(r) {
|
|
9288
|
+
const { remote, base } = r.config.branch;
|
|
9289
|
+
const out = refreshBaseBranch(r.dir, remote, base, true);
|
|
9290
|
+
const unsafe = baseBranchUnsafe(out);
|
|
9291
|
+
return { action: out.action, unsafe, detail: unsafe ? describeUnsafeBase(out, remote, base) : out.detail };
|
|
9178
9292
|
}, reportUnplaceable = function(c, unplaceable, verified, emit2) {
|
|
9179
9293
|
if (unplaceableReported.has(c.route)) return;
|
|
9180
9294
|
unplaceableReported.add(c.route);
|
|
@@ -9183,7 +9297,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
9183
9297
|
else emit2.emit(note.message, { step: "release" });
|
|
9184
9298
|
}
|
|
9185
9299
|
};
|
|
9186
|
-
requireArmed2 = requireArmed, parseTicketNumber2 = parseTicketNumber, passengerApiKeys2 = passengerApiKeys, endpointApiFor2 = endpointApiFor, endpointReconcileDeps2 = endpointReconcileDeps, releaseRefused2 = releaseRefused, reportUnplaceable2 = reportUnplaceable;
|
|
9300
|
+
requireArmed2 = requireArmed, parseTicketNumber2 = parseTicketNumber, passengerApiKeys2 = passengerApiKeys, endpointApiFor2 = endpointApiFor, endpointReconcileDeps2 = endpointReconcileDeps, releaseRefused2 = releaseRefused, baseHealth2 = baseHealth, reportUnplaceable2 = reportUnplaceable;
|
|
9187
9301
|
let cfg;
|
|
9188
9302
|
let firstRun;
|
|
9189
9303
|
try {
|
|
@@ -9374,12 +9488,21 @@ if (command === "passenger-sync-daemon") {
|
|
|
9374
9488
|
const trimmed = typeof raw === "string" ? raw.trim() : "";
|
|
9375
9489
|
return trimmed ? `${trimmed.toLowerCase()}-` : void 0;
|
|
9376
9490
|
}
|
|
9377
|
-
async function installScheduler() {
|
|
9491
|
+
async function installScheduler(opts = {}) {
|
|
9378
9492
|
const host = hostPlatform();
|
|
9379
9493
|
if (host === "windows") {
|
|
9380
9494
|
process.stderr.write("crew install: no scheduler support yet for Windows (Task Scheduler is planned, not built)\n");
|
|
9381
9495
|
process.exit(2);
|
|
9382
9496
|
}
|
|
9497
|
+
if (host === "macos") {
|
|
9498
|
+
const ok = clearForeignLaunchdUnits(CREW_HOME, !!opts.replace, dryRun, {
|
|
9499
|
+
emit: (m) => process.stdout.write(`${m}
|
|
9500
|
+
`),
|
|
9501
|
+
warn: (m) => process.stderr.write(`crew install: ${m}
|
|
9502
|
+
`)
|
|
9503
|
+
});
|
|
9504
|
+
if (!ok) return false;
|
|
9505
|
+
}
|
|
9383
9506
|
const installPathProblem = cfg.routes.some((r) => r.enabled && r.hostPassengers) ? dockerPathProblem(cfg.ship) : void 0;
|
|
9384
9507
|
if (installPathProblem) {
|
|
9385
9508
|
process.stderr.write(`crew install: Host Passengers is on but ${installPathProblem}
|
|
@@ -9431,6 +9554,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
9431
9554
|
}
|
|
9432
9555
|
}
|
|
9433
9556
|
}
|
|
9557
|
+
return true;
|
|
9434
9558
|
}
|
|
9435
9559
|
async function routeProblems(c) {
|
|
9436
9560
|
const host = hostPlatform();
|
|
@@ -9477,14 +9601,13 @@ if (command === "passenger-sync-daemon") {
|
|
|
9477
9601
|
for (const a of plan.actions) {
|
|
9478
9602
|
if (a.kind === "enable") {
|
|
9479
9603
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
9480
|
-
writeFileSync9(cfg.configFile, planEnable(
|
|
9604
|
+
writeFileSync9(cfg.configFile, planEnable(readFileSync14(cfg.configFile, "utf8"), a.route));
|
|
9481
9605
|
enabledSound.add(a.route);
|
|
9482
9606
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
9483
9607
|
} else {
|
|
9484
9608
|
if (enabledSound.size === 0) continue;
|
|
9485
9609
|
if (prompter && !await prompter.confirm("No scheduler is installed for this ship \u2014 run `crew install` now?", false)) continue;
|
|
9486
|
-
await installScheduler();
|
|
9487
|
-
changed2.push("ran `crew install` (scheduler units written and loaded)");
|
|
9610
|
+
if (await installScheduler()) changed2.push("ran `crew install` (scheduler units written and loaded)");
|
|
9488
9611
|
}
|
|
9489
9612
|
}
|
|
9490
9613
|
} finally {
|
|
@@ -10376,7 +10499,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10376
10499
|
`);
|
|
10377
10500
|
if (!dryRun) {
|
|
10378
10501
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
10379
|
-
const raw = existsSync19(resolvedPath) ? JSON.parse(
|
|
10502
|
+
const raw = existsSync19(resolvedPath) ? JSON.parse(readFileSync14(resolvedPath, "utf8")) : {};
|
|
10380
10503
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
10381
10504
|
raw.agentSkills = skillResult.agentSkills;
|
|
10382
10505
|
mkdirSync10(dirOf(resolvedPath), { recursive: true });
|
|
@@ -10425,7 +10548,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10425
10548
|
`);
|
|
10426
10549
|
if (!dryRun) {
|
|
10427
10550
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
10428
|
-
const raw = existsSync19(resolvedPath) ? JSON.parse(
|
|
10551
|
+
const raw = existsSync19(resolvedPath) ? JSON.parse(readFileSync14(resolvedPath, "utf8")) : {};
|
|
10429
10552
|
raw.agentSkills = result.agentSkills;
|
|
10430
10553
|
mkdirSync10(dirOf(resolvedPath), { recursive: true });
|
|
10431
10554
|
writeFileSync9(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
@@ -10441,7 +10564,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10441
10564
|
process.exit(2);
|
|
10442
10565
|
};
|
|
10443
10566
|
if (sub !== "add" && sub !== "list") usage2();
|
|
10444
|
-
const configText =
|
|
10567
|
+
const configText = readFileSync14(cfg.configFile, "utf8");
|
|
10445
10568
|
let routeName = positional[2];
|
|
10446
10569
|
let addPath = ".";
|
|
10447
10570
|
let addName;
|
|
@@ -10465,7 +10588,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10465
10588
|
let resolvedRemotes;
|
|
10466
10589
|
if (existsSync19(resolvedFile)) {
|
|
10467
10590
|
try {
|
|
10468
|
-
resolvedRemotes = JSON.parse(
|
|
10591
|
+
resolvedRemotes = JSON.parse(readFileSync14(resolvedFile, "utf8"));
|
|
10469
10592
|
} catch {
|
|
10470
10593
|
}
|
|
10471
10594
|
}
|
|
@@ -10536,7 +10659,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10536
10659
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
10537
10660
|
`);
|
|
10538
10661
|
if (existsSync19(resolvedFile)) {
|
|
10539
|
-
const raw = JSON.parse(
|
|
10662
|
+
const raw = JSON.parse(readFileSync14(resolvedFile, "utf8"));
|
|
10540
10663
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
10541
10664
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
10542
10665
|
writeFileSync9(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
@@ -10771,7 +10894,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
10771
10894
|
const hadPreviousFile = existsSync19(resolvedPath);
|
|
10772
10895
|
if (hadPreviousFile) {
|
|
10773
10896
|
try {
|
|
10774
|
-
previous = JSON.parse(
|
|
10897
|
+
previous = JSON.parse(readFileSync14(resolvedPath, "utf8"));
|
|
10775
10898
|
} catch {
|
|
10776
10899
|
}
|
|
10777
10900
|
}
|
|
@@ -11214,14 +11337,16 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
11214
11337
|
let acted = 0;
|
|
11215
11338
|
let baseChecked = 0;
|
|
11216
11339
|
let baseActed = 0;
|
|
11340
|
+
let baseUnsafe = 0;
|
|
11217
11341
|
for (const r of await resolvedRepos(route)) {
|
|
11218
11342
|
const base = refreshBaseBranch(r.dir, r.config.branch.remote, r.config.branch.base, dryRun);
|
|
11219
11343
|
if (base.action !== "not-applicable") {
|
|
11220
11344
|
baseChecked++;
|
|
11221
11345
|
if (base.action === "fetch-failed") emit.warn(base.detail);
|
|
11222
|
-
else if (base
|
|
11346
|
+
else if (baseBranchUnsafe(base)) {
|
|
11223
11347
|
baseActed++;
|
|
11224
|
-
|
|
11348
|
+
baseUnsafe++;
|
|
11349
|
+
emit.error(`${r.name}: DO NOT CUT A WORKTREE FROM THIS BASE \u2014 ${describeUnsafeBase(base, r.config.branch.remote, r.config.branch.base)}`);
|
|
11225
11350
|
} else if (base.action === "would-fast-forward" || base.action === "fast-forwarded") {
|
|
11226
11351
|
baseActed++;
|
|
11227
11352
|
emit.emit(base.detail);
|
|
@@ -11253,6 +11378,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
11253
11378
|
}
|
|
11254
11379
|
}
|
|
11255
11380
|
}
|
|
11381
|
+
if (baseUnsafe > 0) process.exitCode = 1;
|
|
11256
11382
|
if (baseChecked > 0 && baseActed === 0) emit.emit(`${baseChecked} base branch(es) checked, all level with the remote`);
|
|
11257
11383
|
if (tracked === 0) emit.emit("no worktree tracks a remote branch \u2014 nothing to sync");
|
|
11258
11384
|
else if (acted === 0) emit.emit(`${tracked} worktree(s) tracking a remote, all level with it`);
|
|
@@ -11274,7 +11400,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
11274
11400
|
break;
|
|
11275
11401
|
}
|
|
11276
11402
|
case "install": {
|
|
11277
|
-
await installScheduler();
|
|
11403
|
+
if (!await installScheduler({ replace: flag("replace") })) process.exit(2);
|
|
11278
11404
|
break;
|
|
11279
11405
|
}
|
|
11280
11406
|
case "uninstall": {
|
|
@@ -11291,6 +11417,16 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
11291
11417
|
emit: (m) => process.stdout.write(`${m}
|
|
11292
11418
|
`),
|
|
11293
11419
|
warn: (m) => process.stderr.write(`crew uninstall: ${m}
|
|
11420
|
+
`)
|
|
11421
|
+
});
|
|
11422
|
+
}
|
|
11423
|
+
if (flag("all") && host === "macos") {
|
|
11424
|
+
process.stdout.write(`uninstalling every crew launchd unit${dryRun ? " (dry run)" : ""}
|
|
11425
|
+
`);
|
|
11426
|
+
removeAllLaunchdUnits(dryRun, {
|
|
11427
|
+
emit: (m) => process.stdout.write(`${m}
|
|
11428
|
+
`),
|
|
11429
|
+
warn: (m) => process.stderr.write(`crew uninstall: ${m}
|
|
11294
11430
|
`)
|
|
11295
11431
|
});
|
|
11296
11432
|
}
|
|
@@ -11302,7 +11438,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
11302
11438
|
}
|
|
11303
11439
|
case "log": {
|
|
11304
11440
|
try {
|
|
11305
|
-
const text =
|
|
11441
|
+
const text = readFileSync14(cfg.ship.logFile, "utf8").split("\n");
|
|
11306
11442
|
process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
11307
11443
|
`);
|
|
11308
11444
|
} catch {
|
|
@@ -11373,6 +11509,9 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
11373
11509
|
repos: repos.map((r) => ({
|
|
11374
11510
|
name: r.name,
|
|
11375
11511
|
dir: r.dir,
|
|
11512
|
+
// Additive (CREW-1379): `unsafe` means a ship must not cut a
|
|
11513
|
+
// worktree or release from this base. `version` stays 1.
|
|
11514
|
+
baseBranch: baseHealth(r),
|
|
11376
11515
|
worktreePrefix: r.config.worktrees.prefix,
|
|
11377
11516
|
// A repo with no explicit `worktrees.prefix` no longer names its
|
|
11378
11517
|
// worktrees `<prefix><number>` (ISSUE-969) — the checkout's own
|
|
@@ -11413,6 +11552,11 @@ watermark: ${state.watermark()}
|
|
|
11413
11552
|
}
|
|
11414
11553
|
for (const i of openShipAttention(state)) {
|
|
11415
11554
|
process.stdout.write(`attention: ${i.kind} \u2014 ${i.message}
|
|
11555
|
+
`);
|
|
11556
|
+
}
|
|
11557
|
+
for (const r of await resolvedRepos(route)) {
|
|
11558
|
+
const bh = baseHealth(r);
|
|
11559
|
+
if (bh.unsafe) process.stdout.write(`base: ${r.name} \u2014 UNSAFE, no worktree cuts or releases: ${bh.detail}
|
|
11416
11560
|
`);
|
|
11417
11561
|
}
|
|
11418
11562
|
if (waiting) {
|
|
@@ -11467,6 +11611,12 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
11467
11611
|
process.stdout.write(` - ${p2}
|
|
11468
11612
|
`);
|
|
11469
11613
|
}
|
|
11614
|
+
const bh = baseHealth(r);
|
|
11615
|
+
if (bh.action !== "not-applicable") {
|
|
11616
|
+
process.stdout.write(` base ${config.branch.base}: ${bh.unsafe ? "ERROR \u2014 " : ""}${bh.detail}
|
|
11617
|
+
`);
|
|
11618
|
+
if (bh.unsafe) process.exitCode = 1;
|
|
11619
|
+
}
|
|
11470
11620
|
}
|
|
11471
11621
|
const missingAuthorship = await tracker.missingAuthorshipColumns();
|
|
11472
11622
|
process.stdout.write(
|
|
@@ -11557,6 +11707,14 @@ git: ${gitPath ? `OK (${gitPath})` : "NOT FOUND on the scheduler P
|
|
|
11557
11707
|
process.stdout.write(`attention: ${i.kind} \u2014 ${i.message} (since ${i.since})
|
|
11558
11708
|
`);
|
|
11559
11709
|
}
|
|
11710
|
+
if (host === "macos") {
|
|
11711
|
+
const foreign = describeForeignLaunchdUnits(CREW_HOME);
|
|
11712
|
+
if (foreign.length > 0) {
|
|
11713
|
+
process.stdout.write(`foreign launchd units (not this checkout's \u2014 \`crew install\` or \`crew uninstall --all\` clears them):
|
|
11714
|
+
${foreign.join("\n")}
|
|
11715
|
+
`);
|
|
11716
|
+
}
|
|
11717
|
+
}
|
|
11560
11718
|
const passengerRoutes = cfg.routes.filter((r) => r.enabled && r.hostPassengers);
|
|
11561
11719
|
if (passengerRoutes.length === 0) {
|
|
11562
11720
|
process.stdout.write("host passengers: off (no enabled route sets hostPassengers: true)\n");
|
|
@@ -11639,4 +11797,5 @@ var passengerApiKeys2;
|
|
|
11639
11797
|
var endpointApiFor2;
|
|
11640
11798
|
var endpointReconcileDeps2;
|
|
11641
11799
|
var releaseRefused2;
|
|
11800
|
+
var baseHealth2;
|
|
11642
11801
|
var reportUnplaceable2;
|
package/package.json
CHANGED
|
@@ -346,10 +346,13 @@ if `report_type` is either of those.
|
|
|
346
346
|
missing commits another ship or a reviewer pushed since the last sync. It
|
|
347
347
|
is quiet, not an alert, about a primary checkout that isn't cleanly on the
|
|
348
348
|
base branch (something else may be mid-edit there) — that's expected, not
|
|
349
|
-
a fault, and just means this sync is a no-op. A
|
|
350
|
-
branch
|
|
351
|
-
|
|
352
|
-
|
|
349
|
+
a fault, and just means this sync is a no-op. A DIVERGED base
|
|
350
|
+
branch (local commits the remote lacks, or one that cannot fast-forward)
|
|
351
|
+
makes `crew sync` print `DO NOT CUT A WORKTREE FROM THIS BASE` and exit
|
|
352
|
+
non-zero. That is a hard stop: do not run `git worktree add`, do not try to
|
|
353
|
+
reconcile it yourself. Post a comment on the ticket naming the repo and the
|
|
354
|
+
counts `crew sync` printed, leave the ticket as you found it, and end the
|
|
355
|
+
run — the operator reconciles the checkout. Otherwise, from
|
|
353
356
|
the primary checkout, `git worktree add` a sibling worktree for this
|
|
354
357
|
ticket, on a new branch cut from the base branch's current HEAD. The
|
|
355
358
|
digest's `worktree` and `branch` columns give this ticket's own directory
|
|
@@ -327,10 +327,13 @@ if `report_type` is either of those.
|
|
|
327
327
|
missing commits another ship or a reviewer pushed since the last sync. It
|
|
328
328
|
is quiet, not an alert, about a primary checkout that isn't cleanly on the
|
|
329
329
|
base branch (something else may be mid-edit there) — that's expected, not
|
|
330
|
-
a fault, and just means this sync is a no-op. A
|
|
331
|
-
branch
|
|
332
|
-
|
|
333
|
-
|
|
330
|
+
a fault, and just means this sync is a no-op. A DIVERGED base
|
|
331
|
+
branch (local commits the remote lacks, or one that cannot fast-forward)
|
|
332
|
+
makes `crew sync` print `DO NOT CUT A WORKTREE FROM THIS BASE` and exit
|
|
333
|
+
non-zero. That is a hard stop: do not run `git worktree add`, do not try to
|
|
334
|
+
reconcile it yourself. Post a comment on the ticket naming the repo and the
|
|
335
|
+
counts `crew sync` printed, leave the ticket as you found it, and end the
|
|
336
|
+
run — the operator reconciles the checkout. Otherwise, from
|
|
334
337
|
the primary checkout, `git worktree add` a sibling worktree for this
|
|
335
338
|
ticket, on a new branch cut from the base branch's current HEAD. The
|
|
336
339
|
digest's `worktree` and `branch` columns give this ticket's own directory
|