@tablation/crew 0.126.2 → 0.126.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 +135 -43
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3263,6 +3263,15 @@ function loadRepoConfig(dir) {
|
|
|
3263
3263
|
if (!file) return null;
|
|
3264
3264
|
return parseRepoConfig(readFileSync14(file, "utf8"), file);
|
|
3265
3265
|
}
|
|
3266
|
+
function loadRepoConfigTolerant(dir, refused) {
|
|
3267
|
+
try {
|
|
3268
|
+
return loadRepoConfig(dir);
|
|
3269
|
+
} catch (e) {
|
|
3270
|
+
if (!(e instanceof RepoConfigError)) throw e;
|
|
3271
|
+
refused?.(e);
|
|
3272
|
+
return null;
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3266
3275
|
function hookLabel(cfg, hook2) {
|
|
3267
3276
|
return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
|
|
3268
3277
|
}
|
|
@@ -6016,6 +6025,15 @@ function seatKeyUnreadableItem(route2, seat, storeError, nowIso = (/* @__PURE__
|
|
|
6016
6025
|
message: `${route2}: the ${seat} seat's agent key cannot be read (${storeError}) \u2014 its runs are refused until the key store is readable. Resumes automatically once it is; \`crew doctor\` names the store in use`
|
|
6017
6026
|
};
|
|
6018
6027
|
}
|
|
6028
|
+
var repoContractRefusedKey = (repo) => `repo_contract_refused:${repo}`;
|
|
6029
|
+
function repoContractRefusedItem(repo, detail, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
6030
|
+
return {
|
|
6031
|
+
kind: "repo_contract_refused",
|
|
6032
|
+
key: repoContractRefusedKey(repo),
|
|
6033
|
+
since: nowIso,
|
|
6034
|
+
message: `${repo}: its repo contract is refused and the repository is skipped \u2014 ${detail}. Resumes automatically once the file is valid on its base; \`crew doctor\` names the problem`
|
|
6035
|
+
};
|
|
6036
|
+
}
|
|
6019
6037
|
var agentLogRefusedKey = (route2) => `agent_log_refused:${route2}`;
|
|
6020
6038
|
function agentLogRefusedItem(route2, role, status2, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
6021
6039
|
return {
|
|
@@ -6129,7 +6147,7 @@ function recheckBaseStops(state, route2) {
|
|
|
6129
6147
|
for (const item of open) {
|
|
6130
6148
|
const target = reposOf(route2).find((t) => baseUnsafeKey(t.name) === item.key);
|
|
6131
6149
|
if (!target) continue;
|
|
6132
|
-
const cfg = resolveRepoConfig(
|
|
6150
|
+
const cfg = resolveRepoConfig(loadRepoConfigTolerant(target.dir), void 0, target.dir);
|
|
6133
6151
|
const outcome = refreshBaseBranch(target.dir, cfg.branch.remote, cfg.branch.base, true);
|
|
6134
6152
|
if (outcome.action === "level" || outcome.action === "would-fast-forward") {
|
|
6135
6153
|
clearShipAttention(state, item.key);
|
|
@@ -12805,7 +12823,7 @@ function syncPassengerContainers(cfg, tablationApiBaseUrl, exec = realExec2, syn
|
|
|
12805
12823
|
}
|
|
12806
12824
|
return { started, recreated, stopped };
|
|
12807
12825
|
}
|
|
12808
|
-
function passengerCheckoutSyncSpecs(cfg, workspaceId) {
|
|
12826
|
+
function passengerCheckoutSyncSpecs(cfg, workspaceId, onRefused) {
|
|
12809
12827
|
const specs = [];
|
|
12810
12828
|
for (const route2 of cfg.routes) {
|
|
12811
12829
|
if (!route2.enabled || !route2.hostPassengers || !route2.resolved?.workspaceId) continue;
|
|
@@ -12817,8 +12835,14 @@ function passengerCheckoutSyncSpecs(cfg, workspaceId) {
|
|
|
12817
12835
|
for (const target of passengerCheckoutTargets(route2, mirrorArea)) {
|
|
12818
12836
|
const ordinary = ordinaryByName.get(target.name);
|
|
12819
12837
|
const override = route2.repoOverrides[target.name];
|
|
12838
|
+
let refused = false;
|
|
12839
|
+
const declared = ordinary ? loadRepoConfigTolerant(ordinary.dir, (e) => {
|
|
12840
|
+
refused = true;
|
|
12841
|
+
onRefused?.(target.name, e);
|
|
12842
|
+
}) : null;
|
|
12843
|
+
if (refused) continue;
|
|
12820
12844
|
const effective = resolveRepoConfig(
|
|
12821
|
-
|
|
12845
|
+
declared,
|
|
12822
12846
|
{ branch: { ...route2.branch, ...override?.branch } },
|
|
12823
12847
|
ordinary?.dir ?? target.dir
|
|
12824
12848
|
);
|
|
@@ -12838,8 +12862,8 @@ function passengerCheckoutSyncSpecs(cfg, workspaceId) {
|
|
|
12838
12862
|
}
|
|
12839
12863
|
return specs;
|
|
12840
12864
|
}
|
|
12841
|
-
function syncAllPassengerCheckouts(cfg) {
|
|
12842
|
-
return passengerCheckoutSyncSpecs(cfg).filter((spec) => !spec.mirror).map((spec) => ({
|
|
12865
|
+
function syncAllPassengerCheckouts(cfg, onRefused) {
|
|
12866
|
+
return passengerCheckoutSyncSpecs(cfg, void 0, onRefused).filter((spec) => !spec.mirror).map((spec) => ({
|
|
12843
12867
|
name: spec.name,
|
|
12844
12868
|
workspaceId: spec.workspaceId,
|
|
12845
12869
|
outcome: syncPassengerCheckout(spec.dir, spec.remote, spec.gitRemoteName, spec.base, { originUrl: spec.originUrl, scheme: spec.cloneScheme, host: spec.forgeHost })
|
|
@@ -13497,7 +13521,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
13497
13521
|
}
|
|
13498
13522
|
|
|
13499
13523
|
// src/cli.ts
|
|
13500
|
-
import { resolve as resolve7, dirname as dirname34, join as
|
|
13524
|
+
import { resolve as resolve7, dirname as dirname34, join as join56, basename as basename16 } from "path";
|
|
13501
13525
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13502
13526
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
13503
13527
|
import { spawnSync as spawnSync11 } from "child_process";
|
|
@@ -16454,7 +16478,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
|
|
|
16454
16478
|
if (hit) return hit;
|
|
16455
16479
|
const target = reposOf(o.route).find((r) => r.dir === dir);
|
|
16456
16480
|
const nameOverride = target ? o.route.repoOverrides[target.name]?.worktreeName : void 0;
|
|
16457
|
-
const cfg = resolveRepoConfig(
|
|
16481
|
+
const cfg = resolveRepoConfig(loadRepoConfigTolerant(dir), nameOverride ? { worktrees: { nameOverride } } : void 0, dir);
|
|
16458
16482
|
repoCache.set(dir, cfg);
|
|
16459
16483
|
return cfg;
|
|
16460
16484
|
};
|
|
@@ -18538,8 +18562,11 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
18538
18562
|
o.emit.emit(`${changed2.split("\n")[0]} changed in this release \u2014 continuing under the merged contract`);
|
|
18539
18563
|
}
|
|
18540
18564
|
} catch (e) {
|
|
18565
|
+
if (o.contractRefusal) throw new Error(`${o.contractRefusal}; the merged contract is also refused: ${e.message}`);
|
|
18541
18566
|
o.emit.warn(`could not re-read the merged repo config: ${e.message}; releasing under the earlier one`);
|
|
18542
18567
|
}
|
|
18568
|
+
} else if (o.contractRefusal) {
|
|
18569
|
+
throw new Error(`${o.contractRefusal}; nothing merged replaces it`);
|
|
18543
18570
|
}
|
|
18544
18571
|
}
|
|
18545
18572
|
if (o.mergeOnly) {
|
|
@@ -22816,9 +22843,32 @@ function describeCause(err3) {
|
|
|
22816
22843
|
return code && !first.includes(code) ? `${first} (${code})` : first;
|
|
22817
22844
|
}
|
|
22818
22845
|
|
|
22846
|
+
// src/contract-refusal.ts
|
|
22847
|
+
import { join as join51 } from "path";
|
|
22848
|
+
function contractFixers(dir, baseRef, tickets, locate) {
|
|
22849
|
+
const out = [];
|
|
22850
|
+
for (const t of tickets) {
|
|
22851
|
+
const branch = locate(t);
|
|
22852
|
+
if (!branch) continue;
|
|
22853
|
+
const changed2 = gitOk(dir, ["diff", "--name-only", `${baseRef}...${branch}`, "--", ...REPO_CONFIG_FILENAMES]);
|
|
22854
|
+
const file = changed2?.split("\n").find((f) => f.trim() !== "");
|
|
22855
|
+
if (!file) continue;
|
|
22856
|
+
const text2 = gitOk(dir, ["show", `${branch}:${file}`]);
|
|
22857
|
+
if (text2 === null) continue;
|
|
22858
|
+
try {
|
|
22859
|
+
parseRepoConfig(text2, join51(dir, file));
|
|
22860
|
+
} catch (e) {
|
|
22861
|
+
if (e instanceof RepoConfigError) continue;
|
|
22862
|
+
throw e;
|
|
22863
|
+
}
|
|
22864
|
+
out.push({ issueId: t.issue_id, branch, file });
|
|
22865
|
+
}
|
|
22866
|
+
return out;
|
|
22867
|
+
}
|
|
22868
|
+
|
|
22819
22869
|
// src/daemon.ts
|
|
22820
22870
|
import { existsSync as existsSync48, mkdirSync as mkdirSync36, readFileSync as readFileSync44, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
|
|
22821
|
-
import { join as
|
|
22871
|
+
import { join as join52 } from "path";
|
|
22822
22872
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
22823
22873
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
22824
22874
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
@@ -22906,7 +22956,7 @@ function latestMtime(path) {
|
|
|
22906
22956
|
if (!stat2.isDirectory()) return 0;
|
|
22907
22957
|
let latest = stat2.mtimeMs;
|
|
22908
22958
|
for (const entry of readdirSync17(path, { withFileTypes: true })) {
|
|
22909
|
-
latest = Math.max(latest, latestMtime(
|
|
22959
|
+
latest = Math.max(latest, latestMtime(join52(path, entry.name)));
|
|
22910
22960
|
}
|
|
22911
22961
|
return latest;
|
|
22912
22962
|
}
|
|
@@ -22918,7 +22968,7 @@ function makeStaleChecker(paths, opts = {}) {
|
|
|
22918
22968
|
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync48(opts.reloadSentinel);
|
|
22919
22969
|
}
|
|
22920
22970
|
function reloadSentinelFor(stateDir) {
|
|
22921
|
-
return
|
|
22971
|
+
return join52(stateDir, "reload-requested");
|
|
22922
22972
|
}
|
|
22923
22973
|
function readReloadRequestedAt(stateDir) {
|
|
22924
22974
|
try {
|
|
@@ -22928,7 +22978,7 @@ function readReloadRequestedAt(stateDir) {
|
|
|
22928
22978
|
}
|
|
22929
22979
|
}
|
|
22930
22980
|
function daemonStampFor(stateDir) {
|
|
22931
|
-
return
|
|
22981
|
+
return join52(stateDir, "daemon.started");
|
|
22932
22982
|
}
|
|
22933
22983
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
22934
22984
|
mkdirSync36(stateDir, { recursive: true });
|
|
@@ -23486,7 +23536,7 @@ async function buildEnvironment(route2, ship, ticket) {
|
|
|
23486
23536
|
name: t.name,
|
|
23487
23537
|
dir: t.dir,
|
|
23488
23538
|
id,
|
|
23489
|
-
config: resolveRepoConfig(
|
|
23539
|
+
config: resolveRepoConfig(loadRepoConfigTolerant(t.dir), {
|
|
23490
23540
|
hooks: { ...route2.hooks, ...o?.hooks },
|
|
23491
23541
|
labels: { ...route2.labels, ...o?.labels },
|
|
23492
23542
|
branch: { ...route2.branch, ...o?.branch },
|
|
@@ -23515,7 +23565,7 @@ import {
|
|
|
23515
23565
|
} from "fs";
|
|
23516
23566
|
import { createConnection } from "net";
|
|
23517
23567
|
import { createServer as createServer2 } from "http";
|
|
23518
|
-
import { basename as basename14, dirname as dirname32, join as
|
|
23568
|
+
import { basename as basename14, dirname as dirname32, join as join53 } from "path";
|
|
23519
23569
|
import { execFile as execFile3, spawn as spawn12 } from "child_process";
|
|
23520
23570
|
|
|
23521
23571
|
// src/periscope-control.ts
|
|
@@ -23744,7 +23794,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
23744
23794
|
// src/control-api.ts
|
|
23745
23795
|
var PAUSABLE = ["dev", "design", "qa"];
|
|
23746
23796
|
var CONTROL_SOCKET_NAME = "control.sock";
|
|
23747
|
-
var controlSocketFor = (stateDir) =>
|
|
23797
|
+
var controlSocketFor = (stateDir) => join53(stateDir, CONTROL_SOCKET_NAME);
|
|
23748
23798
|
var MAX_SOCKET_PATH = 100;
|
|
23749
23799
|
var MAX_TAIL_BYTES = 1024 * 1024;
|
|
23750
23800
|
var MAX_WAIT_SECONDS = 30;
|
|
@@ -23831,7 +23881,7 @@ function candidates(live, rotated) {
|
|
|
23831
23881
|
const out = [live];
|
|
23832
23882
|
if (!rotated) return out;
|
|
23833
23883
|
try {
|
|
23834
|
-
for (const n of readdirSync18(dirname32(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(
|
|
23884
|
+
for (const n of readdirSync18(dirname32(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join53(dirname32(live), n));
|
|
23835
23885
|
} catch {
|
|
23836
23886
|
}
|
|
23837
23887
|
return out;
|
|
@@ -24031,7 +24081,7 @@ async function route(deps, method, url, req, abort) {
|
|
|
24031
24081
|
if (p.startsWith("/streams/") && get2) {
|
|
24032
24082
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
24033
24083
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
24034
|
-
const file =
|
|
24084
|
+
const file = join53(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
24035
24085
|
if (!existsSync49(file)) return err2(404, "no such run");
|
|
24036
24086
|
return tailReply(file, url.searchParams, false, abort);
|
|
24037
24087
|
}
|
|
@@ -24181,7 +24231,7 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
24181
24231
|
|
|
24182
24232
|
// src/periscope-mcp.ts
|
|
24183
24233
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
24184
|
-
import { basename as basename15, dirname as dirname33, isAbsolute as isAbsolute5, join as
|
|
24234
|
+
import { basename as basename15, dirname as dirname33, isAbsolute as isAbsolute5, join as join55, relative as relative2, resolve as resolve6, sep } from "path";
|
|
24185
24235
|
import { z } from "zod";
|
|
24186
24236
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24187
24237
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -24189,11 +24239,11 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
24189
24239
|
// src/periscope-endpoints.ts
|
|
24190
24240
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
24191
24241
|
import { chmodSync as chmodSync5, existsSync as existsSync50, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync45, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
|
|
24192
|
-
import { join as
|
|
24193
|
-
var secretDir = (stateDir) =>
|
|
24194
|
-
var secretFile = (stateDir, id) =>
|
|
24195
|
-
var endpointDir = (stateDir) =>
|
|
24196
|
-
var endpointFile = (stateDir, id) =>
|
|
24242
|
+
import { join as join54 } from "path";
|
|
24243
|
+
var secretDir = (stateDir) => join54(stateDir, "periscope-mcp");
|
|
24244
|
+
var secretFile = (stateDir, id) => join54(secretDir(stateDir), `${id}.json`);
|
|
24245
|
+
var endpointDir = (stateDir) => join54(stateDir, "periscope-endpoints");
|
|
24246
|
+
var endpointFile = (stateDir, id) => join54(endpointDir(stateDir), `${id}.json`);
|
|
24197
24247
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
24198
24248
|
mkdirSync37(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
24199
24249
|
const secret = randomBytes3(32).toString("base64url");
|
|
@@ -24334,13 +24384,13 @@ async function resolveInWorktree(root, requested) {
|
|
|
24334
24384
|
let abs;
|
|
24335
24385
|
if (isAbsolute5(requested)) {
|
|
24336
24386
|
abs = resolve6(requested);
|
|
24337
|
-
if (isInside(root, abs)) abs =
|
|
24387
|
+
if (isInside(root, abs)) abs = join55(realRoot, relative2(root, abs));
|
|
24338
24388
|
} else {
|
|
24339
24389
|
abs = resolve6(realRoot, requested);
|
|
24340
24390
|
}
|
|
24341
24391
|
if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
24342
24392
|
const { real, rest } = await realAncestor(abs);
|
|
24343
|
-
const final = rest ?
|
|
24393
|
+
const final = rest ? join55(real, rest) : real;
|
|
24344
24394
|
if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
24345
24395
|
if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
|
|
24346
24396
|
return final;
|
|
@@ -24408,7 +24458,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
24408
24458
|
const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
|
|
24409
24459
|
const lines2 = await Promise.all(entries.map(async (e) => {
|
|
24410
24460
|
if (e.isDirectory()) return `[DIR] ${e.name}`;
|
|
24411
|
-
const size = (await lstat(
|
|
24461
|
+
const size = (await lstat(join55(abs, e.name))).size;
|
|
24412
24462
|
return `[FILE] ${e.name} (${size} bytes)`;
|
|
24413
24463
|
}));
|
|
24414
24464
|
return text(lines2.join("\n") || "(empty)");
|
|
@@ -24474,7 +24524,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
24474
24524
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
24475
24525
|
}, async ({ path }) => {
|
|
24476
24526
|
const parent = await inTree(dirname33(path) === "" ? "." : dirname33(path));
|
|
24477
|
-
const abs =
|
|
24527
|
+
const abs = join55(parent, basename15(path));
|
|
24478
24528
|
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
24479
24529
|
const st = await lstat(abs).catch(() => void 0);
|
|
24480
24530
|
if (!st) return text(`${path} does not exist`, true);
|
|
@@ -25313,7 +25363,19 @@ if (command === "passenger-sync-daemon") {
|
|
|
25313
25363
|
return;
|
|
25314
25364
|
}
|
|
25315
25365
|
try {
|
|
25316
|
-
|
|
25366
|
+
const refusedRepos = /* @__PURE__ */ new Map();
|
|
25367
|
+
const checkouts = syncAllPassengerCheckouts(cfg, (repo, e) => refusedRepos.set(repo, e.message));
|
|
25368
|
+
for (const [repo, detail] of refusedRepos) {
|
|
25369
|
+
if (await raiseShipAttention({ state, emit }, repoContractRefusedItem(repo, detail))) {
|
|
25370
|
+
emit.warn(`passenger sync skips ${repo}: ${detail}`, { step: "passengers", data: { repo } });
|
|
25371
|
+
}
|
|
25372
|
+
}
|
|
25373
|
+
for (const item of state.shipAttention().previous()) {
|
|
25374
|
+
if (item.kind === "repo_contract_refused" && !refusedRepos.has(item.key.slice(repoContractRefusedKey("").length))) {
|
|
25375
|
+
clearShipAttention(state, item.key);
|
|
25376
|
+
}
|
|
25377
|
+
}
|
|
25378
|
+
for (const r of checkouts) {
|
|
25317
25379
|
if (r.outcome.action === "clone-failed") {
|
|
25318
25380
|
if (!r.outcome.repeat) {
|
|
25319
25381
|
emit.warn(
|
|
@@ -25536,7 +25598,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25536
25598
|
}
|
|
25537
25599
|
if (host === "linux" && hasSystemd) {
|
|
25538
25600
|
const label = labelFor(CREW_HOME, "run");
|
|
25539
|
-
const staleTimer =
|
|
25601
|
+
const staleTimer = join56(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
25540
25602
|
if (existsSync51(staleTimer)) {
|
|
25541
25603
|
process.stdout.write(
|
|
25542
25604
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
@@ -25563,7 +25625,9 @@ if (command === "passenger-sync-daemon") {
|
|
|
25563
25625
|
}
|
|
25564
25626
|
const tracker = new Tracker(c, cfg.ship);
|
|
25565
25627
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
25566
|
-
|
|
25628
|
+
const { settled: doctorRepos, errors: refusedRepos } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
|
|
25629
|
+
for (const e of refusedRepos) problems.push(`repo ${e.repo}: ${e.detail}`);
|
|
25630
|
+
for (const r of doctorRepos) {
|
|
25567
25631
|
if (!existsSync51(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
25568
25632
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
25569
25633
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
@@ -25625,7 +25689,16 @@ ${changed2.map((c) => ` - ${c}
|
|
|
25625
25689
|
`).join("")}` : "fix: nothing changed\n");
|
|
25626
25690
|
}
|
|
25627
25691
|
async function resolvedRepos(c) {
|
|
25628
|
-
|
|
25692
|
+
const { settled, errors } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
|
|
25693
|
+
for (const e of errors) {
|
|
25694
|
+
try {
|
|
25695
|
+
await raiseShipAttention({ state, emit }, repoContractRefusedItem(e.repo, e.detail));
|
|
25696
|
+
} catch {
|
|
25697
|
+
}
|
|
25698
|
+
}
|
|
25699
|
+
const refused = new Set(errors.map((e) => e.repo));
|
|
25700
|
+
for (const t of reposOf(c)) if (!refused.has(t.name)) clearShipAttention(state, repoContractRefusedKey(t.name));
|
|
25701
|
+
return settled;
|
|
25629
25702
|
}
|
|
25630
25703
|
async function sandboxReposOf(routes) {
|
|
25631
25704
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -25744,7 +25817,10 @@ ${changed2.map((c) => ` - ${c}
|
|
|
25744
25817
|
}
|
|
25745
25818
|
let boardLock;
|
|
25746
25819
|
try {
|
|
25747
|
-
|
|
25820
|
+
let contractRefusal = null;
|
|
25821
|
+
const repoFile = loadRepoConfigTolerant(target.dir, (e) => {
|
|
25822
|
+
contractRefusal = e;
|
|
25823
|
+
});
|
|
25748
25824
|
const o = c.repoOverrides[target.name];
|
|
25749
25825
|
const repo = resolveRepoConfig(repoFile, {
|
|
25750
25826
|
hooks: { ...c.hooks, ...o?.hooks },
|
|
@@ -25859,6 +25935,21 @@ ${changed2.map((c) => ` - ${c}
|
|
|
25859
25935
|
releaseModes
|
|
25860
25936
|
);
|
|
25861
25937
|
reportUnplaceable(c.route, unplaceable, tracker.contract.statuses.verified, remit);
|
|
25938
|
+
if (contractRefusal) {
|
|
25939
|
+
const refusal = contractRefusal;
|
|
25940
|
+
const baseRef = remoteBranchExists(target.dir, repo.branch.remote, repo.branch.base) ? `${repo.branch.remote}/${repo.branch.base}` : repo.branch.base;
|
|
25941
|
+
const fixers = contractFixers(
|
|
25942
|
+
target.dir,
|
|
25943
|
+
baseRef,
|
|
25944
|
+
tickets.filter((t) => t.status === tracker.contract.statuses.verified),
|
|
25945
|
+
(t) => locateBranchForTicket(target.dir, repo, t)?.branch ?? null
|
|
25946
|
+
);
|
|
25947
|
+
if (fixers.length === 0) throw refusal;
|
|
25948
|
+
remit.emit(
|
|
25949
|
+
`${target.name}: the base contract is refused (${refusal.message}); ${fixers.map((f) => f.issueId).join(", ")} replace${fixers.length === 1 ? "s" : ""} ${fixers[0].file} \u2014 merging first`,
|
|
25950
|
+
{ step: "release" }
|
|
25951
|
+
);
|
|
25952
|
+
}
|
|
25862
25953
|
const blockerInfo = blockerInfoMap(all, await tracker.ticketsByIds(missingBlockerIds(all)));
|
|
25863
25954
|
const verifiedStatus = tracker.contract.statuses.verified;
|
|
25864
25955
|
const verifiedTickets = tickets.filter((t) => t.status === verifiedStatus);
|
|
@@ -25891,6 +25982,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
25891
25982
|
emit: remit,
|
|
25892
25983
|
scope,
|
|
25893
25984
|
verifiedShas,
|
|
25985
|
+
...contractRefusal ? { contractRefusal: contractRefusal.message } : {},
|
|
25894
25986
|
state: state.release(scope),
|
|
25895
25987
|
reviewColumns: repo.release.mode === "external" ? await tracker.reviewColumns() : void 0,
|
|
25896
25988
|
dryRun,
|
|
@@ -26759,7 +26851,7 @@ ${cu.tail.trimEnd()}
|
|
|
26759
26851
|
process.exit(1);
|
|
26760
26852
|
}
|
|
26761
26853
|
requireArmed("run the daemon loop");
|
|
26762
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
26854
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join56(CREW_HOME, "src"), join56(CREW_HOME, "dist"), join56(CREW_HOME, "bin")];
|
|
26763
26855
|
const configPaths = daemonConfigPaths(cfg);
|
|
26764
26856
|
watchPaths.push(...configPaths);
|
|
26765
26857
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -28760,10 +28852,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28760
28852
|
startupSeconds: cfg.ship.intelStartupSeconds,
|
|
28761
28853
|
prepare: async (path) => {
|
|
28762
28854
|
for (const f of repoWt.config.worktrees.copy) {
|
|
28763
|
-
const src =
|
|
28764
|
-
if (!existsSync51(src) || existsSync51(
|
|
28765
|
-
mkdirSync38(dirOf2(
|
|
28766
|
-
copyFileSync2(src,
|
|
28855
|
+
const src = join56(repoWt.dir, f);
|
|
28856
|
+
if (!existsSync51(src) || existsSync51(join56(path, f))) continue;
|
|
28857
|
+
mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
|
|
28858
|
+
copyFileSync2(src, join56(path, f));
|
|
28767
28859
|
}
|
|
28768
28860
|
const setup = repoWt.config.hooks.setup;
|
|
28769
28861
|
return setup ? runScript(setup, { cwd: path, shell: shellWt }) : null;
|
|
@@ -29079,10 +29171,10 @@ ${where.join("\n")}`);
|
|
|
29079
29171
|
},
|
|
29080
29172
|
prepare: async (path) => {
|
|
29081
29173
|
for (const f of repoP.config.worktrees.copy) {
|
|
29082
|
-
const src =
|
|
29174
|
+
const src = join56(repoP.dir, f);
|
|
29083
29175
|
if (!existsSync51(src)) continue;
|
|
29084
|
-
mkdirSync38(dirOf2(
|
|
29085
|
-
copyFileSync2(src,
|
|
29176
|
+
mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
|
|
29177
|
+
copyFileSync2(src, join56(path, f));
|
|
29086
29178
|
}
|
|
29087
29179
|
const setup = repoP.config.hooks.setup;
|
|
29088
29180
|
return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
|
|
@@ -29580,12 +29672,12 @@ ${where.join("\n")}`);
|
|
|
29580
29672
|
`);
|
|
29581
29673
|
}
|
|
29582
29674
|
}
|
|
29583
|
-
if (existsSync51(
|
|
29675
|
+
if (existsSync51(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
|
|
29584
29676
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
29585
29677
|
break;
|
|
29586
29678
|
}
|
|
29587
29679
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
29588
|
-
const fresh = !existsSync51(
|
|
29680
|
+
const fresh = !existsSync51(join56(dir, ".git"));
|
|
29589
29681
|
if (existsSync51(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
29590
29682
|
if (dryRun) {
|
|
29591
29683
|
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`);
|
|
@@ -29593,8 +29685,8 @@ ${where.join("\n")}`);
|
|
|
29593
29685
|
}
|
|
29594
29686
|
if (fresh) {
|
|
29595
29687
|
mkdirSync38(dir, { recursive: true });
|
|
29596
|
-
writeFileSync38(
|
|
29597
|
-
writeFileSync38(
|
|
29688
|
+
writeFileSync38(join56(dir, "README.md"), SAMPLE_README);
|
|
29689
|
+
writeFileSync38(join56(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
29598
29690
|
const g = (...a) => {
|
|
29599
29691
|
const r = spawnSync11("git", a, { cwd: dir, encoding: "utf8" });
|
|
29600
29692
|
if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
package/package.json
CHANGED