@tablation/crew 0.122.0 → 0.123.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +343 -292
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5850,6 +5850,78 @@ function cloneFailureMarker(dir) {
|
|
|
5850
5850
|
return `${dir}.clone-failed`;
|
|
5851
5851
|
}
|
|
5852
5852
|
|
|
5853
|
+
// src/repo-map.ts
|
|
5854
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
5855
|
+
import { join as join20 } from "path";
|
|
5856
|
+
var CHARS_PER_TOKEN = 4;
|
|
5857
|
+
var MAP_TIMEOUT_MS = 6e4;
|
|
5858
|
+
function budgetMap(text2, tokenBudget) {
|
|
5859
|
+
const body = text2.replace(/\s+$/, "");
|
|
5860
|
+
const limit = tokenBudget * CHARS_PER_TOKEN;
|
|
5861
|
+
if (body.length <= limit) return body;
|
|
5862
|
+
const lines2 = body.split("\n");
|
|
5863
|
+
const noteFor = (kept2) => `\u2026 ${lines2.length - kept2} of ${lines2.length} lines cut to fit ship.mapTokenBudget (${tokenBudget} tokens)`;
|
|
5864
|
+
let kept = lines2.length;
|
|
5865
|
+
let size = body.length;
|
|
5866
|
+
while (kept > 0 && size + 1 + noteFor(kept).length > limit) {
|
|
5867
|
+
kept--;
|
|
5868
|
+
size -= lines2[kept].length + 1;
|
|
5869
|
+
}
|
|
5870
|
+
return [...lines2.slice(0, kept), noteFor(kept)].join("\n");
|
|
5871
|
+
}
|
|
5872
|
+
function mapFilePath(value2) {
|
|
5873
|
+
if (!value2) return null;
|
|
5874
|
+
const v = value2.trim();
|
|
5875
|
+
if (!/^[A-Za-z0-9_.+@-]+(?:\/[A-Za-z0-9_.+@-]+)*\.md$/i.test(v)) return null;
|
|
5876
|
+
if (v.split("/").some((seg) => seg === ".." || seg === ".")) return null;
|
|
5877
|
+
return v;
|
|
5878
|
+
}
|
|
5879
|
+
function inspectMapFile(dir, path) {
|
|
5880
|
+
try {
|
|
5881
|
+
const text2 = readFileSync18(join20(dir, path), "utf8").replace(/\s+$/, "");
|
|
5882
|
+
return { path, exists: true, lines: text2 ? text2.split("\n").length : 0, tokens: Math.ceil(text2.length / CHARS_PER_TOKEN) };
|
|
5883
|
+
} catch {
|
|
5884
|
+
return { path, exists: false, lines: 0, tokens: 0 };
|
|
5885
|
+
}
|
|
5886
|
+
}
|
|
5887
|
+
function loadRepoMap(repo, tokenBudget, shellFallback) {
|
|
5888
|
+
const script = repo.config.hooks.map;
|
|
5889
|
+
if (!script) return Promise.resolve({ skipped: "" });
|
|
5890
|
+
const file = mapFilePath(script);
|
|
5891
|
+
if (file) {
|
|
5892
|
+
const m = inspectMapFile(repo.dir, file);
|
|
5893
|
+
if (!m.exists) return Promise.resolve({ skipped: `the \`map\` file ${file} for ${repo.name} is missing or unreadable, so the brief has no repository map` });
|
|
5894
|
+
let text2 = "";
|
|
5895
|
+
try {
|
|
5896
|
+
text2 = readFileSync18(join20(repo.dir, file), "utf8");
|
|
5897
|
+
} catch {
|
|
5898
|
+
}
|
|
5899
|
+
if (!text2.trim()) return Promise.resolve({ skipped: `the \`map\` file ${file} for ${repo.name} is empty, so the brief has no repository map` });
|
|
5900
|
+
return Promise.resolve({ text: budgetMap(text2, tokenBudget) });
|
|
5901
|
+
}
|
|
5902
|
+
return runScript(script, {
|
|
5903
|
+
cwd: repo.dir,
|
|
5904
|
+
shell: resolveShell(repo.config.shell ?? shellFallback),
|
|
5905
|
+
env: { CREW_REPO: repo.name },
|
|
5906
|
+
timeoutMs: MAP_TIMEOUT_MS
|
|
5907
|
+
}).then(
|
|
5908
|
+
(r) => {
|
|
5909
|
+
if (r.code !== 0) return { skipped: `the \`map\` hook for ${repo.name} exited ${r.code}, so the brief has no repository map` };
|
|
5910
|
+
if (!r.stdout.trim()) return { skipped: `the \`map\` hook for ${repo.name} printed nothing, so the brief has no repository map` };
|
|
5911
|
+
return { text: budgetMap(r.stdout, tokenBudget) };
|
|
5912
|
+
},
|
|
5913
|
+
(e) => ({ skipped: `the \`map\` hook for ${repo.name} could not run (${e instanceof Error ? e.message : String(e)}), so the brief has no repository map` })
|
|
5914
|
+
);
|
|
5915
|
+
}
|
|
5916
|
+
async function withRepoMaps(repos, tokenBudget, shellFallback) {
|
|
5917
|
+
return Promise.all(repos.map(async (r) => {
|
|
5918
|
+
if (!r.config.hooks.map) return r;
|
|
5919
|
+
const out = await loadRepoMap(r, tokenBudget, shellFallback);
|
|
5920
|
+
if ("skipped" in out) return out.skipped ? { ...r, mapSkipped: out.skipped } : r;
|
|
5921
|
+
return { ...r, map: out.text };
|
|
5922
|
+
}));
|
|
5923
|
+
}
|
|
5924
|
+
|
|
5853
5925
|
// src/ship-attention.ts
|
|
5854
5926
|
var RELEASE_STALE_AFTER_INTERVALS = 3;
|
|
5855
5927
|
function upsert(items, item) {
|
|
@@ -6091,6 +6163,7 @@ function clearShipAttention(state, key) {
|
|
|
6091
6163
|
if (next.length !== previous.length) store.persist(next);
|
|
6092
6164
|
}
|
|
6093
6165
|
function hookCommand(script) {
|
|
6166
|
+
if (mapFilePath(script)) return null;
|
|
6094
6167
|
for (const raw of script.split("\n")) {
|
|
6095
6168
|
const line = raw.trim();
|
|
6096
6169
|
if (!line || line.startsWith("#")) continue;
|
|
@@ -6170,6 +6243,18 @@ function repoFindings(o) {
|
|
|
6170
6243
|
remedy: "add the directory holding it to ship.extraPath in crew.yaml"
|
|
6171
6244
|
});
|
|
6172
6245
|
}
|
|
6246
|
+
if (o.mapFile) {
|
|
6247
|
+
if (!o.mapFile.exists) {
|
|
6248
|
+
problems.push({
|
|
6249
|
+
severity: "attention",
|
|
6250
|
+
kind: "map_file_missing",
|
|
6251
|
+
...at,
|
|
6252
|
+
hook: "map",
|
|
6253
|
+
message: `${o.route}/${o.repo}: hooks.map names ${o.mapFile.path}, which is not in the checkout, so briefs carry no repository map`,
|
|
6254
|
+
remedy: `commit ${o.mapFile.path}, or correct hooks.map in the repository's .crew.yaml`
|
|
6255
|
+
});
|
|
6256
|
+
} else passed++;
|
|
6257
|
+
}
|
|
6173
6258
|
if (o.intelSyntax) {
|
|
6174
6259
|
if (o.intelSyntax.status === "error") {
|
|
6175
6260
|
problems.push({
|
|
@@ -6205,8 +6290,8 @@ function attentionProblems(items, already) {
|
|
|
6205
6290
|
import { createHash as createHash6 } from "crypto";
|
|
6206
6291
|
import { spawn as spawn5 } from "child_process";
|
|
6207
6292
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
6208
|
-
import { mkdirSync as mkdirSync19, readFileSync as
|
|
6209
|
-
import { dirname as dirname14, join as
|
|
6293
|
+
import { mkdirSync as mkdirSync19, readFileSync as readFileSync27, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
|
|
6294
|
+
import { dirname as dirname14, join as join32 } from "path";
|
|
6210
6295
|
import { tmpdir as tmpdir3 } from "os";
|
|
6211
6296
|
|
|
6212
6297
|
// src/passenger-containers.ts
|
|
@@ -6222,20 +6307,20 @@ import { spawnSync as spawnSync7 } from "child_process";
|
|
|
6222
6307
|
import { existsSync as existsSync29, readdirSync as readdirSync11 } from "fs";
|
|
6223
6308
|
|
|
6224
6309
|
// src/interrupted.ts
|
|
6225
|
-
import { existsSync as existsSync27, readdirSync as readdirSync9, readFileSync as
|
|
6226
|
-
import { join as
|
|
6310
|
+
import { existsSync as existsSync27, readdirSync as readdirSync9, readFileSync as readFileSync26 } from "fs";
|
|
6311
|
+
import { join as join30 } from "path";
|
|
6227
6312
|
|
|
6228
6313
|
// src/agent.ts
|
|
6229
6314
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
6230
|
-
import { readFileSync as
|
|
6315
|
+
import { readFileSync as readFileSync25, existsSync as existsSync26, statSync as statSync8, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, createWriteStream, openSync as openSync4, closeSync as closeSync4, readSync } from "fs";
|
|
6231
6316
|
import { StringDecoder } from "string_decoder";
|
|
6232
6317
|
import { finished } from "stream/promises";
|
|
6233
|
-
import { join as
|
|
6318
|
+
import { join as join29, dirname as dirname13, basename as basename9 } from "path";
|
|
6234
6319
|
import { createHash as createHash4 } from "crypto";
|
|
6235
6320
|
|
|
6236
6321
|
// src/state.ts
|
|
6237
|
-
import { readFileSync as
|
|
6238
|
-
import { join as
|
|
6322
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync14, existsSync as existsSync19, mkdirSync as mkdirSync13, rmSync as rmSync10, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
|
|
6323
|
+
import { join as join21 } from "path";
|
|
6239
6324
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
6240
6325
|
var State = class _State {
|
|
6241
6326
|
dir;
|
|
@@ -6244,7 +6329,7 @@ var State = class _State {
|
|
|
6244
6329
|
mkdirSync13(dir, { recursive: true });
|
|
6245
6330
|
}
|
|
6246
6331
|
path(...parts) {
|
|
6247
|
-
return
|
|
6332
|
+
return join21(this.dir, ...parts);
|
|
6248
6333
|
}
|
|
6249
6334
|
/**
|
|
6250
6335
|
* The timestamp every "new since last poll" check is measured against.
|
|
@@ -6255,7 +6340,7 @@ var State = class _State {
|
|
|
6255
6340
|
*/
|
|
6256
6341
|
watermark() {
|
|
6257
6342
|
try {
|
|
6258
|
-
const v =
|
|
6343
|
+
const v = readFileSync19(this.path(".poll-watermark"), "utf8").trim();
|
|
6259
6344
|
return v || EPOCH;
|
|
6260
6345
|
} catch {
|
|
6261
6346
|
return EPOCH;
|
|
@@ -6341,7 +6426,7 @@ var State = class _State {
|
|
|
6341
6426
|
const name = _State.safe(rawName);
|
|
6342
6427
|
const file = this.path(`.${name}.lock`);
|
|
6343
6428
|
try {
|
|
6344
|
-
const existing = Number.parseInt(
|
|
6429
|
+
const existing = Number.parseInt(readFileSync19(file, "utf8").trim(), 10);
|
|
6345
6430
|
if (Number.isFinite(existing) && existing > 0) {
|
|
6346
6431
|
try {
|
|
6347
6432
|
process.kill(existing, 0);
|
|
@@ -6379,7 +6464,7 @@ var State = class _State {
|
|
|
6379
6464
|
if (!/^\.release-.+\.lock$/.test(n)) continue;
|
|
6380
6465
|
let pid = 0;
|
|
6381
6466
|
try {
|
|
6382
|
-
pid = Number.parseInt(
|
|
6467
|
+
pid = Number.parseInt(readFileSync19(this.path(n), "utf8").trim(), 10);
|
|
6383
6468
|
} catch {
|
|
6384
6469
|
continue;
|
|
6385
6470
|
}
|
|
@@ -6465,7 +6550,7 @@ var State = class _State {
|
|
|
6465
6550
|
ticketInFlight(recordId) {
|
|
6466
6551
|
const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
|
|
6467
6552
|
try {
|
|
6468
|
-
const pid = Number.parseInt(
|
|
6553
|
+
const pid = Number.parseInt(readFileSync19(file, "utf8").trim(), 10);
|
|
6469
6554
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
6470
6555
|
process.kill(pid, 0);
|
|
6471
6556
|
return true;
|
|
@@ -6499,7 +6584,7 @@ var State = class _State {
|
|
|
6499
6584
|
const heldReported = `.release-held-${suffix}`;
|
|
6500
6585
|
const read = (f) => {
|
|
6501
6586
|
try {
|
|
6502
|
-
return
|
|
6587
|
+
return readFileSync19(this.path(f), "utf8").trim() || null;
|
|
6503
6588
|
} catch {
|
|
6504
6589
|
return null;
|
|
6505
6590
|
}
|
|
@@ -6549,7 +6634,7 @@ var State = class _State {
|
|
|
6549
6634
|
/** Whether the board was already told about this head's red gate. */
|
|
6550
6635
|
heldReported: () => {
|
|
6551
6636
|
try {
|
|
6552
|
-
return
|
|
6637
|
+
return readFileSync19(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
|
|
6553
6638
|
} catch {
|
|
6554
6639
|
return null;
|
|
6555
6640
|
}
|
|
@@ -6586,7 +6671,7 @@ var State = class _State {
|
|
|
6586
6671
|
const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
|
|
6587
6672
|
const read = (f) => {
|
|
6588
6673
|
try {
|
|
6589
|
-
return
|
|
6674
|
+
return readFileSync19(this.path(f), "utf8").trim() || null;
|
|
6590
6675
|
} catch {
|
|
6591
6676
|
return null;
|
|
6592
6677
|
}
|
|
@@ -6651,7 +6736,7 @@ var State = class _State {
|
|
|
6651
6736
|
return {
|
|
6652
6737
|
previous: () => {
|
|
6653
6738
|
try {
|
|
6654
|
-
const parsed = JSON.parse(
|
|
6739
|
+
const parsed = JSON.parse(readFileSync19(this.path(file), "utf8"));
|
|
6655
6740
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
6656
6741
|
} catch {
|
|
6657
6742
|
return {};
|
|
@@ -6674,7 +6759,7 @@ var State = class _State {
|
|
|
6674
6759
|
return {
|
|
6675
6760
|
previous: () => {
|
|
6676
6761
|
try {
|
|
6677
|
-
const parsed = JSON.parse(
|
|
6762
|
+
const parsed = JSON.parse(readFileSync19(this.path(file), "utf8"));
|
|
6678
6763
|
return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
|
|
6679
6764
|
} catch {
|
|
6680
6765
|
return [];
|
|
@@ -6695,7 +6780,7 @@ var State = class _State {
|
|
|
6695
6780
|
return {
|
|
6696
6781
|
previous: () => {
|
|
6697
6782
|
try {
|
|
6698
|
-
const parsed = JSON.parse(
|
|
6783
|
+
const parsed = JSON.parse(readFileSync19(this.path(file), "utf8"));
|
|
6699
6784
|
return Array.isArray(parsed) ? parsed : [];
|
|
6700
6785
|
} catch {
|
|
6701
6786
|
return [];
|
|
@@ -6728,7 +6813,7 @@ var State = class _State {
|
|
|
6728
6813
|
}
|
|
6729
6814
|
readInt(file) {
|
|
6730
6815
|
try {
|
|
6731
|
-
return Number.parseInt(
|
|
6816
|
+
return Number.parseInt(readFileSync19(this.path(file), "utf8").trim(), 10) || 0;
|
|
6732
6817
|
} catch {
|
|
6733
6818
|
return 0;
|
|
6734
6819
|
}
|
|
@@ -7117,8 +7202,8 @@ function renderEnvironment(i) {
|
|
|
7117
7202
|
}
|
|
7118
7203
|
|
|
7119
7204
|
// src/intel-bridge.ts
|
|
7120
|
-
import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as
|
|
7121
|
-
import { dirname as dirname10, join as
|
|
7205
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync20, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
|
|
7206
|
+
import { dirname as dirname10, join as join22 } from "path";
|
|
7122
7207
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7123
7208
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7124
7209
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -7132,9 +7217,9 @@ var STATUS_WAITING = "The symbol service is not available yet: the worktree has
|
|
|
7132
7217
|
var STATUS_LOST = "The symbol service stopped answering, so its tools are unavailable. They return if the service comes back; until then use text search.";
|
|
7133
7218
|
function intelDir(stateDir, route2, ticket) {
|
|
7134
7219
|
const safe = (s) => s.replace(/[^A-Za-z0-9._-]+/g, "_");
|
|
7135
|
-
return
|
|
7220
|
+
return join22(stateDir, "intel", safe(route2), safe(ticket));
|
|
7136
7221
|
}
|
|
7137
|
-
var attachFilePath = (stateDir, route2, ticket) =>
|
|
7222
|
+
var attachFilePath = (stateDir, route2, ticket) => join22(intelDir(stateDir, route2, ticket), "url");
|
|
7138
7223
|
function writeAttachFile(file, url) {
|
|
7139
7224
|
mkdirSync14(dirname10(file), { recursive: true });
|
|
7140
7225
|
writeFileSync15(file, `${url}
|
|
@@ -7142,7 +7227,7 @@ function writeAttachFile(file, url) {
|
|
|
7142
7227
|
}
|
|
7143
7228
|
function readAttachFile(file) {
|
|
7144
7229
|
try {
|
|
7145
|
-
const url =
|
|
7230
|
+
const url = readFileSync20(file, "utf8").trim();
|
|
7146
7231
|
return /^https?:\/\/\S+$/i.test(url) ? url : void 0;
|
|
7147
7232
|
} catch {
|
|
7148
7233
|
return void 0;
|
|
@@ -7482,8 +7567,8 @@ var BeforeFirstEditTracker = class {
|
|
|
7482
7567
|
};
|
|
7483
7568
|
|
|
7484
7569
|
// src/agent-log.ts
|
|
7485
|
-
import { existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync6, readFileSync as
|
|
7486
|
-
import { join as
|
|
7570
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync6, readFileSync as readFileSync21, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
|
|
7571
|
+
import { join as join23 } from "path";
|
|
7487
7572
|
|
|
7488
7573
|
// src/pending-backoff.ts
|
|
7489
7574
|
var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
|
|
@@ -7686,20 +7771,20 @@ async function addRunToTicket(target, t, recordId, run3) {
|
|
|
7686
7771
|
};
|
|
7687
7772
|
}
|
|
7688
7773
|
}
|
|
7689
|
-
var queueDir = (stateDir) =>
|
|
7774
|
+
var queueDir = (stateDir) => join23(stateDir, "pending-agent-logs");
|
|
7690
7775
|
function queueAgentRun(stateDir, name, q2) {
|
|
7691
7776
|
mkdirSync15(queueDir(stateDir), { recursive: true });
|
|
7692
|
-
writeFileSync16(
|
|
7777
|
+
writeFileSync16(join23(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
7693
7778
|
}
|
|
7694
7779
|
async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
7695
7780
|
const dir = queueDir(stateDir);
|
|
7696
7781
|
if (!existsSync21(dir)) return 0;
|
|
7697
7782
|
let sent = 0;
|
|
7698
7783
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
7699
|
-
const path =
|
|
7784
|
+
const path = join23(dir, f);
|
|
7700
7785
|
let q2;
|
|
7701
7786
|
try {
|
|
7702
|
-
q2 = JSON.parse(
|
|
7787
|
+
q2 = JSON.parse(readFileSync21(path, "utf8"));
|
|
7703
7788
|
} catch {
|
|
7704
7789
|
continue;
|
|
7705
7790
|
}
|
|
@@ -7739,20 +7824,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
7739
7824
|
}
|
|
7740
7825
|
return sent;
|
|
7741
7826
|
}
|
|
7742
|
-
var countersDir = (stateDir) =>
|
|
7827
|
+
var countersDir = (stateDir) => join23(stateDir, "pending-ticket-counters");
|
|
7743
7828
|
function queueTicketCounters(stateDir, name, q2) {
|
|
7744
7829
|
mkdirSync15(countersDir(stateDir), { recursive: true });
|
|
7745
|
-
writeFileSync16(
|
|
7830
|
+
writeFileSync16(join23(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
7746
7831
|
}
|
|
7747
7832
|
async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
7748
7833
|
const dir = countersDir(stateDir);
|
|
7749
7834
|
if (!existsSync21(dir)) return 0;
|
|
7750
7835
|
let sent = 0;
|
|
7751
7836
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
7752
|
-
const path =
|
|
7837
|
+
const path = join23(dir, f);
|
|
7753
7838
|
let q2;
|
|
7754
7839
|
try {
|
|
7755
|
-
q2 = JSON.parse(
|
|
7840
|
+
q2 = JSON.parse(readFileSync21(path, "utf8"));
|
|
7756
7841
|
} catch {
|
|
7757
7842
|
continue;
|
|
7758
7843
|
}
|
|
@@ -7974,8 +8059,8 @@ var HandoffTranscriptTracker = class {
|
|
|
7974
8059
|
};
|
|
7975
8060
|
|
|
7976
8061
|
// src/pending-handoff.ts
|
|
7977
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync16, readdirSync as readdirSync7, readFileSync as
|
|
7978
|
-
import { join as
|
|
8062
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync16, readdirSync as readdirSync7, readFileSync as readFileSync22, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "fs";
|
|
8063
|
+
import { join as join25 } from "path";
|
|
7979
8064
|
|
|
7980
8065
|
// src/ticket-branch.ts
|
|
7981
8066
|
import { basename as basename7 } from "path";
|
|
@@ -9142,7 +9227,7 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
9142
9227
|
|
|
9143
9228
|
// src/run-worktree.ts
|
|
9144
9229
|
import { existsSync as existsSync22 } from "fs";
|
|
9145
|
-
import { dirname as dirname11, join as
|
|
9230
|
+
import { dirname as dirname11, join as join24 } from "path";
|
|
9146
9231
|
var describeRunWorktreePlan = (p) => {
|
|
9147
9232
|
switch (p.action) {
|
|
9148
9233
|
case "cut-remote":
|
|
@@ -9199,7 +9284,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
9199
9284
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
9200
9285
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
9201
9286
|
}
|
|
9202
|
-
const path =
|
|
9287
|
+
const path = join24(dirname11(dir), worktreeForTicket(dir, cfg, t, role));
|
|
9203
9288
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
9204
9289
|
return { action: "cut-remote", path, branch: located.branch };
|
|
9205
9290
|
}
|
|
@@ -9656,12 +9741,12 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
|
|
|
9656
9741
|
|
|
9657
9742
|
// src/pending-handoff.ts
|
|
9658
9743
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
9659
|
-
var pendingDirFor = (stateDir) =>
|
|
9660
|
-
var stateDirOfStream = (streamPath) =>
|
|
9744
|
+
var pendingDirFor = (stateDir) => join25(stateDir, "pending-handoffs");
|
|
9745
|
+
var stateDirOfStream = (streamPath) => join25(streamPath, "..", "..");
|
|
9661
9746
|
function writePendingHandoff(stateDir, rec) {
|
|
9662
9747
|
const dir = pendingDirFor(stateDir);
|
|
9663
9748
|
mkdirSync16(dir, { recursive: true });
|
|
9664
|
-
writeFileSync17(
|
|
9749
|
+
writeFileSync17(join25(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
|
|
9665
9750
|
}
|
|
9666
9751
|
function listPendingHandoffs(stateDir, route2) {
|
|
9667
9752
|
const dir = pendingDirFor(stateDir);
|
|
@@ -9669,7 +9754,7 @@ function listPendingHandoffs(stateDir, route2) {
|
|
|
9669
9754
|
const out = [];
|
|
9670
9755
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
9671
9756
|
try {
|
|
9672
|
-
const rec = JSON.parse(
|
|
9757
|
+
const rec = JSON.parse(readFileSync22(join25(dir, f), "utf8"));
|
|
9673
9758
|
if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
|
|
9674
9759
|
} catch {
|
|
9675
9760
|
}
|
|
@@ -9677,7 +9762,7 @@ function listPendingHandoffs(stateDir, route2) {
|
|
|
9677
9762
|
return out;
|
|
9678
9763
|
}
|
|
9679
9764
|
function removePendingHandoff(stateDir, ticketRecordId) {
|
|
9680
|
-
rmSync13(
|
|
9765
|
+
rmSync13(join25(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
|
|
9681
9766
|
}
|
|
9682
9767
|
function pendingHandoffFor(stateDir, ticketRecordId) {
|
|
9683
9768
|
return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
|
|
@@ -9787,8 +9872,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
|
|
|
9787
9872
|
}
|
|
9788
9873
|
|
|
9789
9874
|
// src/skills.ts
|
|
9790
|
-
import { readFileSync as
|
|
9791
|
-
import { join as
|
|
9875
|
+
import { readFileSync as readFileSync24, readdirSync as readdirSync8, existsSync as existsSync25 } from "fs";
|
|
9876
|
+
import { join as join27 } from "path";
|
|
9792
9877
|
import { parse as parseYaml } from "yaml";
|
|
9793
9878
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
9794
9879
|
|
|
@@ -9804,8 +9889,8 @@ ${additions}`;
|
|
|
9804
9889
|
}
|
|
9805
9890
|
|
|
9806
9891
|
// src/agents.ts
|
|
9807
|
-
import { readFileSync as
|
|
9808
|
-
import { join as
|
|
9892
|
+
import { readFileSync as readFileSync23, existsSync as existsSync24 } from "fs";
|
|
9893
|
+
import { join as join26 } from "path";
|
|
9809
9894
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
9810
9895
|
var PERSONA_NAME = {
|
|
9811
9896
|
dev: "Developer",
|
|
@@ -9819,12 +9904,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
9819
9904
|
var AgentsSyncError = class extends Error {
|
|
9820
9905
|
};
|
|
9821
9906
|
function personaDefaultPrompt(promptsDir, role) {
|
|
9822
|
-
const briefPath =
|
|
9907
|
+
const briefPath = join26(promptsDir, "personas", `lane-${role}.md`);
|
|
9823
9908
|
if (!existsSync24(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
9824
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
9825
|
-
const commonPath =
|
|
9826
|
-
if (!existsSync24(commonPath)) return
|
|
9827
|
-
return `${
|
|
9909
|
+
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync23(briefPath, "utf8");
|
|
9910
|
+
const commonPath = join26(promptsDir, "personas", "common.md");
|
|
9911
|
+
if (!existsSync24(commonPath)) return readFileSync23(briefPath, "utf8");
|
|
9912
|
+
return `${readFileSync23(commonPath, "utf8")}${readFileSync23(briefPath, "utf8")}`;
|
|
9828
9913
|
}
|
|
9829
9914
|
async function resolveAgentId(route2, role, opts) {
|
|
9830
9915
|
const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -10271,7 +10356,7 @@ function describeCrewLink(o) {
|
|
|
10271
10356
|
var SkillSyncError = class extends Error {
|
|
10272
10357
|
};
|
|
10273
10358
|
function parseSkillFile(path) {
|
|
10274
|
-
const text2 =
|
|
10359
|
+
const text2 = readFileSync24(path, "utf8");
|
|
10275
10360
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
|
|
10276
10361
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
10277
10362
|
const frontmatter = match[1];
|
|
@@ -10284,9 +10369,9 @@ function parseSkillFile(path) {
|
|
|
10284
10369
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
10285
10370
|
}
|
|
10286
10371
|
function listLocalSkills(promptsDir) {
|
|
10287
|
-
const dir =
|
|
10372
|
+
const dir = join27(promptsDir, "skills");
|
|
10288
10373
|
if (!existsSync25(dir)) return [];
|
|
10289
|
-
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(
|
|
10374
|
+
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join27(dir, f)));
|
|
10290
10375
|
}
|
|
10291
10376
|
async function syncSkills(route2, opts) {
|
|
10292
10377
|
if (!route2.resolved) {
|
|
@@ -10473,7 +10558,7 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
10473
10558
|
import { spawn as spawn3, execFile, execFileSync as execFileSync6 } from "child_process";
|
|
10474
10559
|
import { mkdtempSync, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, rmSync as rmSync14 } from "fs";
|
|
10475
10560
|
import { tmpdir as tmpdir2 } from "os";
|
|
10476
|
-
import { basename as basename8, dirname as dirname12, join as
|
|
10561
|
+
import { basename as basename8, dirname as dirname12, join as join28 } from "path";
|
|
10477
10562
|
import { promisify } from "util";
|
|
10478
10563
|
|
|
10479
10564
|
// src/run-limit.ts
|
|
@@ -10646,9 +10731,9 @@ var SandboxRunner = class {
|
|
|
10646
10731
|
const harness = cfg.agentBin ?? basename8(plan.bin);
|
|
10647
10732
|
const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
|
|
10648
10733
|
if (viaUpload) {
|
|
10649
|
-
const tmp = mkdtempSync(
|
|
10734
|
+
const tmp = mkdtempSync(join28(tmpdir2(), "crew-prompt-"));
|
|
10650
10735
|
try {
|
|
10651
|
-
const file =
|
|
10736
|
+
const file = join28(tmp, "prompt.txt");
|
|
10652
10737
|
writeFileSync18(file, plan.prompt, { mode: 384 });
|
|
10653
10738
|
await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
|
|
10654
10739
|
} finally {
|
|
@@ -10802,7 +10887,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
|
|
|
10802
10887
|
function streamShowsAllowanceRefusal(streamPath) {
|
|
10803
10888
|
let raw;
|
|
10804
10889
|
try {
|
|
10805
|
-
raw =
|
|
10890
|
+
raw = readFileSync25(streamPath, "utf8");
|
|
10806
10891
|
} catch {
|
|
10807
10892
|
return false;
|
|
10808
10893
|
}
|
|
@@ -10825,7 +10910,7 @@ function streamShowsAllowanceRefusal(streamPath) {
|
|
|
10825
10910
|
}
|
|
10826
10911
|
return false;
|
|
10827
10912
|
}
|
|
10828
|
-
var digestPathFor = (stateDir, routeLabel, role, runId) =>
|
|
10913
|
+
var digestPathFor = (stateDir, routeLabel, role, runId) => join29(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
10829
10914
|
function codeBridgeFor(o, harness) {
|
|
10830
10915
|
if (o.ship.runner === "sandbox" || !o.ticket) return void 0;
|
|
10831
10916
|
const number = /(\d+)$/.exec(o.ticket)?.[1];
|
|
@@ -10841,7 +10926,7 @@ function codeBridgeFor(o, harness) {
|
|
|
10841
10926
|
const args = [...process.execArgv, process.argv[1] ?? "crew", "intel", "bridge", o.route.route, number, "--state-dir", o.stateDir];
|
|
10842
10927
|
const bridge = { command: process.execPath, args };
|
|
10843
10928
|
if (harness === "gemini") {
|
|
10844
|
-
const file =
|
|
10929
|
+
const file = join29(intelDir(o.stateDir, o.route.route, number), "gemini-settings.json");
|
|
10845
10930
|
mkdirSync18(dirname13(file), { recursive: true });
|
|
10846
10931
|
writeFileSync19(file, `${JSON.stringify({ mcpServers: { [INTEL_SERVER_NAME]: { command: bridge.command, args } } }, null, 2)}
|
|
10847
10932
|
`);
|
|
@@ -10850,11 +10935,11 @@ function codeBridgeFor(o, harness) {
|
|
|
10850
10935
|
return bridge;
|
|
10851
10936
|
}
|
|
10852
10937
|
function planAgentRun(o) {
|
|
10853
|
-
const briefPath =
|
|
10938
|
+
const briefPath = join29(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
10854
10939
|
if (!existsSync26(briefPath)) {
|
|
10855
10940
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
10856
10941
|
}
|
|
10857
|
-
const commonPath =
|
|
10942
|
+
const commonPath = join29(o.route.promptsDir, "personas", "common.md");
|
|
10858
10943
|
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync26(commonPath);
|
|
10859
10944
|
const now = o.now ?? (() => Date.now());
|
|
10860
10945
|
const routeLabel = routeSlug(o.route.route);
|
|
@@ -10864,10 +10949,10 @@ function planAgentRun(o) {
|
|
|
10864
10949
|
let ageSeconds;
|
|
10865
10950
|
if (existsSync26(digestPath2)) {
|
|
10866
10951
|
ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
|
|
10867
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
10952
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync25(digestPath2, "utf8");
|
|
10868
10953
|
}
|
|
10869
|
-
const common = usesCommon ?
|
|
10870
|
-
const brief =
|
|
10954
|
+
const common = usesCommon ? readFileSync25(commonPath, "utf8") : "";
|
|
10955
|
+
const brief = readFileSync25(briefPath, "utf8");
|
|
10871
10956
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
10872
10957
|
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
10873
10958
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -10890,7 +10975,7 @@ function planAgentRun(o) {
|
|
|
10890
10975
|
digest
|
|
10891
10976
|
});
|
|
10892
10977
|
const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
|
|
10893
|
-
const streamsDir =
|
|
10978
|
+
const streamsDir = join29(o.stateDir, "streams");
|
|
10894
10979
|
const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
|
|
10895
10980
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
10896
10981
|
const { model } = seat;
|
|
@@ -10983,8 +11068,8 @@ function planAgentRun(o) {
|
|
|
10983
11068
|
digestAttached: digest !== void 0,
|
|
10984
11069
|
digestAgeSeconds: ageSeconds,
|
|
10985
11070
|
model,
|
|
10986
|
-
streamPath:
|
|
10987
|
-
eventsPath:
|
|
11071
|
+
streamPath: join29(streamsDir, `${base}.jsonl`),
|
|
11072
|
+
eventsPath: join29(streamsDir, `${base}.events.jsonl`),
|
|
10988
11073
|
ticket: o.ticket,
|
|
10989
11074
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
10990
11075
|
repo: o.repo ?? null,
|
|
@@ -11015,7 +11100,7 @@ function describePlan(p) {
|
|
|
11015
11100
|
var pidPathFor = (streamPath) => `${streamPath}.pid`;
|
|
11016
11101
|
function readRunSidecar(streamPath) {
|
|
11017
11102
|
try {
|
|
11018
|
-
const sc = JSON.parse(
|
|
11103
|
+
const sc = JSON.parse(readFileSync25(pidPathFor(streamPath), "utf8"));
|
|
11019
11104
|
return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
|
|
11020
11105
|
} catch {
|
|
11021
11106
|
return null;
|
|
@@ -11588,19 +11673,19 @@ function runFinished(streamPath) {
|
|
|
11588
11673
|
const path = `${streamPath}.meta.json`;
|
|
11589
11674
|
if (!existsSync27(path)) return false;
|
|
11590
11675
|
try {
|
|
11591
|
-
return JSON.parse(
|
|
11676
|
+
return JSON.parse(readFileSync26(path, "utf8")).running !== true;
|
|
11592
11677
|
} catch {
|
|
11593
11678
|
return true;
|
|
11594
11679
|
}
|
|
11595
11680
|
}
|
|
11596
11681
|
function orphanedStreams(o) {
|
|
11597
|
-
const dir =
|
|
11682
|
+
const dir = join30(o.stateDir, "streams");
|
|
11598
11683
|
if (!existsSync27(dir)) return [];
|
|
11599
11684
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
11600
11685
|
const out = [];
|
|
11601
11686
|
for (const name of readdirSync9(dir)) {
|
|
11602
11687
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11603
|
-
const streamPath =
|
|
11688
|
+
const streamPath = join30(dir, name);
|
|
11604
11689
|
if (runFinished(streamPath)) continue;
|
|
11605
11690
|
const sidecar = readRunSidecar(streamPath);
|
|
11606
11691
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -11654,14 +11739,14 @@ async function reconcileRuns(o) {
|
|
|
11654
11739
|
|
|
11655
11740
|
// src/live-runs.ts
|
|
11656
11741
|
import { existsSync as existsSync28, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
|
|
11657
|
-
import { join as
|
|
11742
|
+
import { join as join31 } from "path";
|
|
11658
11743
|
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
11659
|
-
const dir =
|
|
11744
|
+
const dir = join31(stateDir, "streams");
|
|
11660
11745
|
if (!existsSync28(dir)) return [];
|
|
11661
11746
|
const out = [];
|
|
11662
11747
|
for (const name of readdirSync10(dir)) {
|
|
11663
11748
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11664
|
-
const streamPath =
|
|
11749
|
+
const streamPath = join31(dir, name);
|
|
11665
11750
|
if (runFinished(streamPath)) continue;
|
|
11666
11751
|
const sidecar = readRunSidecar(streamPath);
|
|
11667
11752
|
if (!sidecar || !isAlive(sidecar)) continue;
|
|
@@ -11671,12 +11756,12 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
|
11671
11756
|
}
|
|
11672
11757
|
function runActivity(stateDir, isAlive = runIsAlive) {
|
|
11673
11758
|
const activeRuns = liveRuns(stateDir, isAlive).length;
|
|
11674
|
-
const dir =
|
|
11759
|
+
const dir = join31(stateDir, "streams");
|
|
11675
11760
|
let newest = 0;
|
|
11676
11761
|
if (existsSync28(dir)) {
|
|
11677
11762
|
for (const name of readdirSync10(dir)) {
|
|
11678
11763
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11679
|
-
const streamPath =
|
|
11764
|
+
const streamPath = join31(dir, name);
|
|
11680
11765
|
if (!runFinished(streamPath)) continue;
|
|
11681
11766
|
try {
|
|
11682
11767
|
newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
|
|
@@ -12716,10 +12801,10 @@ function planSandbox(repo, decl, warm = {}) {
|
|
|
12716
12801
|
const layer = repoLayer(repo, decl, crew, warm);
|
|
12717
12802
|
return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
|
|
12718
12803
|
}
|
|
12719
|
-
var sandboxStatePath = (stateDir) =>
|
|
12804
|
+
var sandboxStatePath = (stateDir) => join32(stateDir, "sandbox-images.json");
|
|
12720
12805
|
function readSandboxState(stateDir) {
|
|
12721
12806
|
try {
|
|
12722
|
-
const v = JSON.parse(
|
|
12807
|
+
const v = JSON.parse(readFileSync27(sandboxStatePath(stateDir), "utf8"));
|
|
12723
12808
|
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
12724
12809
|
} catch {
|
|
12725
12810
|
return {};
|
|
@@ -12770,7 +12855,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
|
|
|
12770
12855
|
} catch {
|
|
12771
12856
|
}
|
|
12772
12857
|
};
|
|
12773
|
-
var sandboxLogDir = (stateDir) =>
|
|
12858
|
+
var sandboxLogDir = (stateDir) => join32(stateDir, "sandbox-logs");
|
|
12774
12859
|
async function buildSandboxImage(plan, deps) {
|
|
12775
12860
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
12776
12861
|
const state = readSandboxState(deps.stateDir);
|
|
@@ -12780,19 +12865,19 @@ async function buildSandboxImage(plan, deps) {
|
|
|
12780
12865
|
}
|
|
12781
12866
|
const logDir = sandboxLogDir(deps.stateDir);
|
|
12782
12867
|
mkdirSync19(logDir, { recursive: true });
|
|
12783
|
-
const logPath =
|
|
12868
|
+
const logPath = join32(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
12784
12869
|
const builtLayers = [];
|
|
12785
12870
|
for (const layer of plan.layers) {
|
|
12786
12871
|
if (deps.hasImage(layer.tag)) continue;
|
|
12787
|
-
const ctx =
|
|
12872
|
+
const ctx = join32(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
|
|
12788
12873
|
mkdirSync19(ctx, { recursive: true });
|
|
12789
12874
|
try {
|
|
12790
|
-
writeFileSync20(
|
|
12875
|
+
writeFileSync20(join32(ctx, "Dockerfile"), layer.dockerfile);
|
|
12791
12876
|
for (const [name, content] of Object.entries(layer.files)) {
|
|
12792
|
-
mkdirSync19(dirname14(
|
|
12793
|
-
writeFileSync20(
|
|
12877
|
+
mkdirSync19(dirname14(join32(ctx, name)), { recursive: true });
|
|
12878
|
+
writeFileSync20(join32(ctx, name), content);
|
|
12794
12879
|
}
|
|
12795
|
-
const code = await deps.run(["build", "--tag", layer.tag, "--file",
|
|
12880
|
+
const code = await deps.run(["build", "--tag", layer.tag, "--file", join32(ctx, "Dockerfile"), ctx], logPath);
|
|
12796
12881
|
if (code !== 0) {
|
|
12797
12882
|
state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
|
|
12798
12883
|
writeSandboxState(deps.stateDir, state);
|
|
@@ -12981,9 +13066,9 @@ function managedRuntimeWarnings(kind, ship) {
|
|
|
12981
13066
|
|
|
12982
13067
|
// src/selftest.ts
|
|
12983
13068
|
import { spawn as spawn6 } from "child_process";
|
|
12984
|
-
import { existsSync as existsSync30, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, readFileSync as
|
|
13069
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync28, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
|
|
12985
13070
|
import { tmpdir as tmpdir4 } from "os";
|
|
12986
|
-
import { join as
|
|
13071
|
+
import { join as join33 } from "path";
|
|
12987
13072
|
var SELFTEST_TIMEOUT_MS = 12e4;
|
|
12988
13073
|
var KEY_VAR = "CREW_API_KEY";
|
|
12989
13074
|
var SELFTEST_PROMPT = "This is a read-only connectivity check. Use the tracker MCP tool to read this workspace (the workspaces find-one tool, using the workspace id below) and reply with the workspace's name alone, on one line. Do not write anything, do not create files, and do not use any other tool.\n\nWorkspace id: ";
|
|
@@ -13144,7 +13229,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
13144
13229
|
...adapter.runEnv(spec),
|
|
13145
13230
|
[KEY_VAR]: i.apiKey
|
|
13146
13231
|
};
|
|
13147
|
-
const cwd = mkdtempSync2(
|
|
13232
|
+
const cwd = mkdtempSync2(join33(tmpdir4(), "crew-selftest-"));
|
|
13148
13233
|
try {
|
|
13149
13234
|
const run3 = await deps.run({
|
|
13150
13235
|
bin: invocation.bin,
|
|
@@ -13177,11 +13262,11 @@ function formatResult(r) {
|
|
|
13177
13262
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
13178
13263
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
13179
13264
|
}
|
|
13180
|
-
var fileFor2 = (stateDir) =>
|
|
13265
|
+
var fileFor2 = (stateDir) => join33(stateDir, "selftest.json");
|
|
13181
13266
|
function readSelftest(stateDir) {
|
|
13182
13267
|
try {
|
|
13183
13268
|
const p = fileFor2(stateDir);
|
|
13184
|
-
return existsSync30(p) ? JSON.parse(
|
|
13269
|
+
return existsSync30(p) ? JSON.parse(readFileSync28(p, "utf8")) : {};
|
|
13185
13270
|
} catch {
|
|
13186
13271
|
return {};
|
|
13187
13272
|
}
|
|
@@ -13202,7 +13287,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
13202
13287
|
}
|
|
13203
13288
|
|
|
13204
13289
|
// src/cli.ts
|
|
13205
|
-
import { resolve as resolve7, dirname as dirname33, join as
|
|
13290
|
+
import { resolve as resolve7, dirname as dirname33, join as join55, basename as basename16 } from "path";
|
|
13206
13291
|
import { fileURLToPath } from "url";
|
|
13207
13292
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
13208
13293
|
import { spawnSync as spawnSync11 } from "child_process";
|
|
@@ -13400,14 +13485,14 @@ function rosterMarkdown(roster, meId) {
|
|
|
13400
13485
|
}
|
|
13401
13486
|
|
|
13402
13487
|
// src/connect-resume.ts
|
|
13403
|
-
import { mkdirSync as mkdirSync21, readFileSync as
|
|
13404
|
-
import { dirname as dirname15, join as
|
|
13488
|
+
import { mkdirSync as mkdirSync21, readFileSync as readFileSync29, rmSync as rmSync17, writeFileSync as writeFileSync22 } from "fs";
|
|
13489
|
+
import { dirname as dirname15, join as join34 } from "path";
|
|
13405
13490
|
function lastSessionFile(stateDir) {
|
|
13406
|
-
return
|
|
13491
|
+
return join34(stateDir, "connect-session.json");
|
|
13407
13492
|
}
|
|
13408
13493
|
function readPointers(stateDir) {
|
|
13409
13494
|
try {
|
|
13410
|
-
const parsed = JSON.parse(
|
|
13495
|
+
const parsed = JSON.parse(readFileSync29(lastSessionFile(stateDir), "utf8"));
|
|
13411
13496
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
13412
13497
|
const out = {};
|
|
13413
13498
|
for (const [host, v] of Object.entries(parsed)) {
|
|
@@ -13507,15 +13592,15 @@ async function signOutSessions(o) {
|
|
|
13507
13592
|
}
|
|
13508
13593
|
|
|
13509
13594
|
// src/reset.ts
|
|
13510
|
-
import { existsSync as existsSync31, mkdirSync as mkdirSync22, readFileSync as
|
|
13511
|
-
import { dirname as dirname16, join as
|
|
13595
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync22, readFileSync as readFileSync30, readdirSync as readdirSync12, rmSync as rmSync18, statSync as statSync10, writeFileSync as writeFileSync23 } from "fs";
|
|
13596
|
+
import { dirname as dirname16, join as join35 } from "path";
|
|
13512
13597
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
13513
13598
|
function clonedCheckoutsFile(stateDir) {
|
|
13514
|
-
return
|
|
13599
|
+
return join35(stateDir, "cloned-checkouts.json");
|
|
13515
13600
|
}
|
|
13516
13601
|
function readClonedCheckouts(stateDir) {
|
|
13517
13602
|
try {
|
|
13518
|
-
const parsed = JSON.parse(
|
|
13603
|
+
const parsed = JSON.parse(readFileSync30(clonedCheckoutsFile(stateDir), "utf8"));
|
|
13519
13604
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
13520
13605
|
} catch {
|
|
13521
13606
|
return [];
|
|
@@ -13602,10 +13687,10 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
13602
13687
|
}
|
|
13603
13688
|
function removeState(stateDir, dryRun) {
|
|
13604
13689
|
const out = [];
|
|
13605
|
-
const targets = [
|
|
13690
|
+
const targets = [join35(stateDir, "resolved"), join35(stateDir, "keys"), clonedCheckoutsFile(stateDir), join35(stateDir, "connect-session.json"), join35(stateDir, "reload-requested"), join35(stateDir, "daemon.started")];
|
|
13606
13691
|
try {
|
|
13607
13692
|
for (const name of readdirSync12(stateDir)) {
|
|
13608
|
-
if (name.startsWith(".") && statSync10(
|
|
13693
|
+
if (name.startsWith(".") && statSync10(join35(stateDir, name)).isFile()) targets.push(join35(stateDir, name));
|
|
13609
13694
|
}
|
|
13610
13695
|
} catch {
|
|
13611
13696
|
}
|
|
@@ -13625,7 +13710,7 @@ function wipeStateDir(stateDir, dryRun) {
|
|
|
13625
13710
|
return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
|
|
13626
13711
|
}
|
|
13627
13712
|
if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
|
|
13628
|
-
if (!dryRun) for (const n of names) rmSync18(
|
|
13713
|
+
if (!dryRun) for (const n of names) rmSync18(join35(stateDir, n), { recursive: true, force: true });
|
|
13629
13714
|
return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
|
|
13630
13715
|
}
|
|
13631
13716
|
function describeResetStep(s) {
|
|
@@ -13706,9 +13791,9 @@ async function runDeprovision(o, fx, deps) {
|
|
|
13706
13791
|
}
|
|
13707
13792
|
|
|
13708
13793
|
// src/pool-member.ts
|
|
13709
|
-
import { mkdirSync as mkdirSync23, readFileSync as
|
|
13794
|
+
import { mkdirSync as mkdirSync23, readFileSync as readFileSync31, renameSync as renameSync4, writeFileSync as writeFileSync24 } from "fs";
|
|
13710
13795
|
import { homedir as homedir8 } from "os";
|
|
13711
|
-
import { dirname as dirname17, join as
|
|
13796
|
+
import { dirname as dirname17, join as join36 } from "path";
|
|
13712
13797
|
var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
|
|
13713
13798
|
function backoffMs(attempt) {
|
|
13714
13799
|
return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
|
|
@@ -13939,14 +14024,14 @@ async function runPoolMember(deps) {
|
|
|
13939
14024
|
}
|
|
13940
14025
|
}
|
|
13941
14026
|
function defaultMemberDir(env = process.env) {
|
|
13942
|
-
return env.CREW_POOL_DIR ??
|
|
14027
|
+
return env.CREW_POOL_DIR ?? join36(env.XDG_STATE_HOME ?? join36(homedir8(), ".local", "state"), "crew-pool-member");
|
|
13943
14028
|
}
|
|
13944
14029
|
function fileMemberStore(dir) {
|
|
13945
|
-
const file =
|
|
14030
|
+
const file = join36(dir, "member.json");
|
|
13946
14031
|
return {
|
|
13947
14032
|
read() {
|
|
13948
14033
|
try {
|
|
13949
|
-
return JSON.parse(
|
|
14034
|
+
return JSON.parse(readFileSync31(file, "utf8"));
|
|
13950
14035
|
} catch {
|
|
13951
14036
|
return {};
|
|
13952
14037
|
}
|
|
@@ -14012,10 +14097,10 @@ import { hostname as hostname3 } from "os";
|
|
|
14012
14097
|
// src/machine-id.ts
|
|
14013
14098
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
14014
14099
|
import { createHash as createHash7 } from "crypto";
|
|
14015
|
-
import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as
|
|
14016
|
-
import { dirname as dirname18, join as
|
|
14100
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync32, writeFileSync as writeFileSync25 } from "fs";
|
|
14101
|
+
import { dirname as dirname18, join as join37 } from "path";
|
|
14017
14102
|
function machineIdPathFor(stateDir) {
|
|
14018
|
-
return
|
|
14103
|
+
return join37(stateDir, "machine-id");
|
|
14019
14104
|
}
|
|
14020
14105
|
var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
|
|
14021
14106
|
function readRawMachineId(platform2 = process.platform) {
|
|
@@ -14029,7 +14114,7 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
14029
14114
|
}
|
|
14030
14115
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
14031
14116
|
if (existsSync32(p)) {
|
|
14032
|
-
const id =
|
|
14117
|
+
const id = readFileSync32(p, "utf8").trim();
|
|
14033
14118
|
if (id) return id;
|
|
14034
14119
|
}
|
|
14035
14120
|
}
|
|
@@ -14041,7 +14126,7 @@ function ensureMachineId(stateDir, opts = {}) {
|
|
|
14041
14126
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
14042
14127
|
const path = machineIdPathFor(stateDir);
|
|
14043
14128
|
if (existsSync32(path)) {
|
|
14044
|
-
const cached =
|
|
14129
|
+
const cached = readFileSync32(path, "utf8").trim();
|
|
14045
14130
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
14046
14131
|
}
|
|
14047
14132
|
const raw = (opts.read ?? readRawMachineId)();
|
|
@@ -14061,7 +14146,7 @@ function matchShipRows(ships, shipName2, machineId) {
|
|
|
14061
14146
|
|
|
14062
14147
|
// src/provision.ts
|
|
14063
14148
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
14064
|
-
import { existsSync as existsSync33, readFileSync as
|
|
14149
|
+
import { existsSync as existsSync33, readFileSync as readFileSync33, writeFileSync as writeFileSync26 } from "fs";
|
|
14065
14150
|
import { hostname as hostname2 } from "os";
|
|
14066
14151
|
import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
|
|
14067
14152
|
|
|
@@ -14070,7 +14155,7 @@ import { parseDocument as parseDocument2 } from "yaml";
|
|
|
14070
14155
|
|
|
14071
14156
|
// src/repos-cmd.ts
|
|
14072
14157
|
import { homedir as homedir9 } from "os";
|
|
14073
|
-
import { basename as basename10, join as
|
|
14158
|
+
import { basename as basename10, join as join38, resolve as resolve4 } from "path";
|
|
14074
14159
|
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
14075
14160
|
var ReposError = class extends Error {
|
|
14076
14161
|
};
|
|
@@ -14348,7 +14433,7 @@ function planRepoClone(i) {
|
|
|
14348
14433
|
}
|
|
14349
14434
|
const ship = doc.get("ship", true);
|
|
14350
14435
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
14351
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
14436
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join38(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
14352
14437
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
14353
14438
|
const forgeHost = i.resolved?.repoForgeHosts?.[id];
|
|
14354
14439
|
return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
|
|
@@ -14663,7 +14748,7 @@ function realProvisionDeps(crewHome) {
|
|
|
14663
14748
|
enableRoute(route2) {
|
|
14664
14749
|
const file = configFile();
|
|
14665
14750
|
if (!file) return false;
|
|
14666
|
-
const text2 =
|
|
14751
|
+
const text2 = readFileSync33(file, "utf8");
|
|
14667
14752
|
const doc = parseDocument3(text2);
|
|
14668
14753
|
const routes = doc.get("routes");
|
|
14669
14754
|
const entry = routes?.items?.find((r) => r.get("route") === route2);
|
|
@@ -14689,7 +14774,7 @@ function realProvisionDeps(crewHome) {
|
|
|
14689
14774
|
continue;
|
|
14690
14775
|
}
|
|
14691
14776
|
for (const p of plan.unitPaths) {
|
|
14692
|
-
if (!existsSync33(p) ||
|
|
14777
|
+
if (!existsSync33(p) || readFileSync33(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
|
|
14693
14778
|
}
|
|
14694
14779
|
}
|
|
14695
14780
|
const label = labelFor(crewHome, "run");
|
|
@@ -14713,7 +14798,7 @@ function realProvisionDeps(crewHome) {
|
|
|
14713
14798
|
setShipSetting(key, value2) {
|
|
14714
14799
|
const file = configFile();
|
|
14715
14800
|
if (!file) return false;
|
|
14716
|
-
const doc = parseDocument3(
|
|
14801
|
+
const doc = parseDocument3(readFileSync33(file, "utf8"));
|
|
14717
14802
|
if (doc.getIn(["ship", key]) === value2) return false;
|
|
14718
14803
|
doc.setIn(["ship", key], value2);
|
|
14719
14804
|
writeFileSync26(file, doc.toString());
|
|
@@ -14722,7 +14807,7 @@ function realProvisionDeps(crewHome) {
|
|
|
14722
14807
|
applyManagedDefaults() {
|
|
14723
14808
|
const file = configFile();
|
|
14724
14809
|
if (!file) return [];
|
|
14725
|
-
const doc = parseDocument3(
|
|
14810
|
+
const doc = parseDocument3(readFileSync33(file, "utf8"));
|
|
14726
14811
|
const wrote = applyManagedDefaultsToDoc(doc);
|
|
14727
14812
|
if (wrote.length) writeFileSync26(file, doc.toString());
|
|
14728
14813
|
return wrote;
|
|
@@ -14741,9 +14826,9 @@ host=${host}
|
|
|
14741
14826
|
|
|
14742
14827
|
// src/tunnel.ts
|
|
14743
14828
|
import { spawn as spawn7 } from "child_process";
|
|
14744
|
-
import { existsSync as existsSync34, mkdirSync as mkdirSync25, readFileSync as
|
|
14829
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync25, readFileSync as readFileSync34, writeFileSync as writeFileSync27, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
|
|
14745
14830
|
import { randomInt } from "crypto";
|
|
14746
|
-
import { join as
|
|
14831
|
+
import { join as join39 } from "path";
|
|
14747
14832
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
14748
14833
|
var defaultSpawn = (cmd, args) => spawn7(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
14749
14834
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -14815,20 +14900,20 @@ function startTunnel(opts) {
|
|
|
14815
14900
|
};
|
|
14816
14901
|
}
|
|
14817
14902
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
14818
|
-
return
|
|
14903
|
+
return join39(stateDir, "passengers", `${workspaceId}.json`);
|
|
14819
14904
|
}
|
|
14820
14905
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
14821
14906
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
14822
14907
|
if (!existsSync34(path)) return void 0;
|
|
14823
14908
|
try {
|
|
14824
|
-
return JSON.parse(
|
|
14909
|
+
return JSON.parse(readFileSync34(path, "utf8"));
|
|
14825
14910
|
} catch {
|
|
14826
14911
|
return void 0;
|
|
14827
14912
|
}
|
|
14828
14913
|
}
|
|
14829
14914
|
function writePersistedTunnel(stateDir, state) {
|
|
14830
14915
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
14831
|
-
mkdirSync25(
|
|
14916
|
+
mkdirSync25(join39(stateDir, "passengers"), { recursive: true });
|
|
14832
14917
|
writeFileSync27(path, `${JSON.stringify(state, null, 2)}
|
|
14833
14918
|
`);
|
|
14834
14919
|
}
|
|
@@ -14931,7 +15016,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
14931
15016
|
}
|
|
14932
15017
|
}
|
|
14933
15018
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
14934
|
-
const dir =
|
|
15019
|
+
const dir = join39(stateDir, "passengers");
|
|
14935
15020
|
if (!existsSync34(dir)) return [];
|
|
14936
15021
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
14937
15022
|
}
|
|
@@ -15834,7 +15919,7 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
15834
15919
|
|
|
15835
15920
|
// src/poll.ts
|
|
15836
15921
|
import { writeFileSync as writeFileSync28 } from "fs";
|
|
15837
|
-
import { join as
|
|
15922
|
+
import { join as join40 } from "path";
|
|
15838
15923
|
|
|
15839
15924
|
// src/stalled.ts
|
|
15840
15925
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -16141,7 +16226,7 @@ async function decideCycle(o) {
|
|
|
16141
16226
|
}
|
|
16142
16227
|
return decision;
|
|
16143
16228
|
}
|
|
16144
|
-
var digestPath = (stateDir, route2, role, runId) =>
|
|
16229
|
+
var digestPath = (stateDir, route2, role, runId) => join40(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
16145
16230
|
function otherRunsRemoved(tickets, own) {
|
|
16146
16231
|
if (!own) return tickets;
|
|
16147
16232
|
return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
|
|
@@ -16327,7 +16412,7 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
16327
16412
|
}
|
|
16328
16413
|
|
|
16329
16414
|
// src/repo-refresh.ts
|
|
16330
|
-
import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as
|
|
16415
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync35, writeFileSync as writeFileSync29 } from "fs";
|
|
16331
16416
|
import { dirname as dirname20 } from "path";
|
|
16332
16417
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
16333
16418
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
@@ -16358,7 +16443,7 @@ function applyRepoRows(resolved, rows) {
|
|
|
16358
16443
|
function persistRepoRows(stateDir, routeName, rows) {
|
|
16359
16444
|
const path = resolvedPathFor(stateDir, routeName);
|
|
16360
16445
|
if (!existsSync36(path)) return;
|
|
16361
|
-
const raw = JSON.parse(
|
|
16446
|
+
const raw = JSON.parse(readFileSync35(path, "utf8"));
|
|
16362
16447
|
applyRepoRows(raw, rows);
|
|
16363
16448
|
mkdirSync26(dirname20(path), { recursive: true });
|
|
16364
16449
|
writeFileSync29(path, `${JSON.stringify(raw, null, 2)}
|
|
@@ -16811,7 +16896,7 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
16811
16896
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
16812
16897
|
|
|
16813
16898
|
// src/connect-wizard.ts
|
|
16814
|
-
import { delimiter, dirname as dirname21, join as
|
|
16899
|
+
import { delimiter, dirname as dirname21, join as join41 } from "path";
|
|
16815
16900
|
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync37, mkdirSync as mkdirSync27, writeFileSync as writeFileSync30 } from "fs";
|
|
16816
16901
|
import { stringify as stringify3 } from "yaml";
|
|
16817
16902
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
@@ -16834,7 +16919,7 @@ function defaultIsExecutable(p) {
|
|
|
16834
16919
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
16835
16920
|
for (const dir of pathEnv.split(delimiter)) {
|
|
16836
16921
|
if (!dir) continue;
|
|
16837
|
-
const candidate =
|
|
16922
|
+
const candidate = join41(dir, name);
|
|
16838
16923
|
if (isExecutable(candidate)) return candidate;
|
|
16839
16924
|
}
|
|
16840
16925
|
return void 0;
|
|
@@ -16931,7 +17016,7 @@ Wrote ${o.configPath}.
|
|
|
16931
17016
|
// src/connect-json.ts
|
|
16932
17017
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
16933
17018
|
import { homedir as homedir10 } from "os";
|
|
16934
|
-
import { dirname as dirname22, join as
|
|
17019
|
+
import { dirname as dirname22, join as join42 } from "path";
|
|
16935
17020
|
import { delimiter as delimiter2 } from "path";
|
|
16936
17021
|
var CHOICE_EXIT_CODE = 3;
|
|
16937
17022
|
function createEmitter(json2, write) {
|
|
@@ -16970,12 +17055,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
16970
17055
|
const dirs = [
|
|
16971
17056
|
...pathEnv.split(delimiter2),
|
|
16972
17057
|
...(extraPath ?? "").split(delimiter2),
|
|
16973
|
-
|
|
16974
|
-
|
|
17058
|
+
join42(home, ".local/bin"),
|
|
17059
|
+
join42(home, ".claude/local"),
|
|
16975
17060
|
"/opt/homebrew/bin",
|
|
16976
17061
|
"/usr/local/bin",
|
|
16977
|
-
|
|
16978
|
-
|
|
17062
|
+
join42(home, ".npm-global/bin"),
|
|
17063
|
+
join42(home, ".volta/bin")
|
|
16979
17064
|
];
|
|
16980
17065
|
return [...new Set(dirs.filter(Boolean))];
|
|
16981
17066
|
}
|
|
@@ -17011,8 +17096,8 @@ function flagWizardAnswers(f, env) {
|
|
|
17011
17096
|
}
|
|
17012
17097
|
|
|
17013
17098
|
// src/release-run.ts
|
|
17014
|
-
import { readFileSync as
|
|
17015
|
-
import { dirname as dirname23, join as
|
|
17099
|
+
import { readFileSync as readFileSync36, writeFileSync as writeFileSync31, existsSync as existsSync38, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
|
|
17100
|
+
import { dirname as dirname23, join as join43 } from "path";
|
|
17016
17101
|
import { tmpdir as tmpdir5 } from "os";
|
|
17017
17102
|
|
|
17018
17103
|
// src/release.ts
|
|
@@ -17753,13 +17838,13 @@ async function bump(o, size) {
|
|
|
17753
17838
|
}
|
|
17754
17839
|
return printed;
|
|
17755
17840
|
}
|
|
17756
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
17841
|
+
const files = o.repo.release.versionFiles.map((f) => join43(o.cwd, f));
|
|
17757
17842
|
const first = files[0];
|
|
17758
17843
|
if (!existsSync38(first)) {
|
|
17759
17844
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
17760
17845
|
return null;
|
|
17761
17846
|
}
|
|
17762
|
-
const current = JSON.parse(
|
|
17847
|
+
const current = JSON.parse(readFileSync36(first, "utf8")).version;
|
|
17763
17848
|
if (!current) {
|
|
17764
17849
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
17765
17850
|
return null;
|
|
@@ -17772,7 +17857,7 @@ async function bump(o, size) {
|
|
|
17772
17857
|
return version;
|
|
17773
17858
|
}
|
|
17774
17859
|
for (const f of files) {
|
|
17775
|
-
const src =
|
|
17860
|
+
const src = readFileSync36(f, "utf8");
|
|
17776
17861
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
17777
17862
|
if (out === src) {
|
|
17778
17863
|
o.emit.error(`no version field in ${f}`);
|
|
@@ -17785,13 +17870,13 @@ async function bump(o, size) {
|
|
|
17785
17870
|
function writeChangelog(o, version, entries) {
|
|
17786
17871
|
const file = o.repo.release.changelog;
|
|
17787
17872
|
if (!file || entries.length === 0) return;
|
|
17788
|
-
const path =
|
|
17873
|
+
const path = join43(o.cwd, file);
|
|
17789
17874
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
17790
17875
|
if (o.dryRun) {
|
|
17791
17876
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
17792
17877
|
return;
|
|
17793
17878
|
}
|
|
17794
|
-
const existing = existsSync38(path) ?
|
|
17879
|
+
const existing = existsSync38(path) ? readFileSync36(path, "utf8") : "# Changelog\n";
|
|
17795
17880
|
writeFileSync31(path, insertChangelogSection(existing, section));
|
|
17796
17881
|
}
|
|
17797
17882
|
async function confirm(o, expected) {
|
|
@@ -17885,8 +17970,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
|
|
|
17885
17970
|
function openReleaseCheckout(o) {
|
|
17886
17971
|
const { remote, base } = o.repo.branch;
|
|
17887
17972
|
const branch = `crew/release-${Date.now()}`;
|
|
17888
|
-
const root = mkdtempSync3(
|
|
17889
|
-
const dir =
|
|
17973
|
+
const root = mkdtempSync3(join43(tmpdir5(), "crew-release-"));
|
|
17974
|
+
const dir = join43(root, "checkout");
|
|
17890
17975
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
17891
17976
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
17892
17977
|
try {
|
|
@@ -17896,10 +17981,10 @@ function openReleaseCheckout(o) {
|
|
|
17896
17981
|
throw e;
|
|
17897
17982
|
}
|
|
17898
17983
|
for (const f of o.repo.worktrees.copy) {
|
|
17899
|
-
const src =
|
|
17984
|
+
const src = join43(o.cwd, f);
|
|
17900
17985
|
if (!existsSync38(src)) continue;
|
|
17901
|
-
mkdirSync28(dirname23(
|
|
17902
|
-
copyFileSync(src,
|
|
17986
|
+
mkdirSync28(dirname23(join43(dir, f)), { recursive: true });
|
|
17987
|
+
copyFileSync(src, join43(dir, f));
|
|
17903
17988
|
}
|
|
17904
17989
|
return { dir, branch, close };
|
|
17905
17990
|
}
|
|
@@ -18489,49 +18574,6 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
18489
18574
|
return { merged, conflicts, unbuildable, version, tag, deployed, integrated, confirmed, decision };
|
|
18490
18575
|
}
|
|
18491
18576
|
|
|
18492
|
-
// src/repo-map.ts
|
|
18493
|
-
var CHARS_PER_TOKEN = 4;
|
|
18494
|
-
var MAP_TIMEOUT_MS = 6e4;
|
|
18495
|
-
function budgetMap(text2, tokenBudget) {
|
|
18496
|
-
const body = text2.replace(/\s+$/, "");
|
|
18497
|
-
const limit = tokenBudget * CHARS_PER_TOKEN;
|
|
18498
|
-
if (body.length <= limit) return body;
|
|
18499
|
-
const lines2 = body.split("\n");
|
|
18500
|
-
const noteFor = (kept2) => `\u2026 ${lines2.length - kept2} of ${lines2.length} lines cut to fit ship.mapTokenBudget (${tokenBudget} tokens)`;
|
|
18501
|
-
let kept = lines2.length;
|
|
18502
|
-
let size = body.length;
|
|
18503
|
-
while (kept > 0 && size + 1 + noteFor(kept).length > limit) {
|
|
18504
|
-
kept--;
|
|
18505
|
-
size -= lines2[kept].length + 1;
|
|
18506
|
-
}
|
|
18507
|
-
return [...lines2.slice(0, kept), noteFor(kept)].join("\n");
|
|
18508
|
-
}
|
|
18509
|
-
function loadRepoMap(repo, tokenBudget, shellFallback) {
|
|
18510
|
-
const script = repo.config.hooks.map;
|
|
18511
|
-
if (!script) return Promise.resolve({ skipped: "" });
|
|
18512
|
-
return runScript(script, {
|
|
18513
|
-
cwd: repo.dir,
|
|
18514
|
-
shell: resolveShell(repo.config.shell ?? shellFallback),
|
|
18515
|
-
env: { CREW_REPO: repo.name },
|
|
18516
|
-
timeoutMs: MAP_TIMEOUT_MS
|
|
18517
|
-
}).then(
|
|
18518
|
-
(r) => {
|
|
18519
|
-
if (r.code !== 0) return { skipped: `the \`map\` hook for ${repo.name} exited ${r.code}, so the brief has no repository map` };
|
|
18520
|
-
if (!r.stdout.trim()) return { skipped: `the \`map\` hook for ${repo.name} printed nothing, so the brief has no repository map` };
|
|
18521
|
-
return { text: budgetMap(r.stdout, tokenBudget) };
|
|
18522
|
-
},
|
|
18523
|
-
(e) => ({ skipped: `the \`map\` hook for ${repo.name} could not run (${e instanceof Error ? e.message : String(e)}), so the brief has no repository map` })
|
|
18524
|
-
);
|
|
18525
|
-
}
|
|
18526
|
-
async function withRepoMaps(repos, tokenBudget, shellFallback) {
|
|
18527
|
-
return Promise.all(repos.map(async (r) => {
|
|
18528
|
-
if (!r.config.hooks.map) return r;
|
|
18529
|
-
const out = await loadRepoMap(r, tokenBudget, shellFallback);
|
|
18530
|
-
if ("skipped" in out) return out.skipped ? { ...r, mapSkipped: out.skipped } : r;
|
|
18531
|
-
return { ...r, map: out.text };
|
|
18532
|
-
}));
|
|
18533
|
-
}
|
|
18534
|
-
|
|
18535
18577
|
// src/failure-alert.ts
|
|
18536
18578
|
var FAILURE_ALERT_MARKER = calloutHeader("CAUTION", "Failure Alert");
|
|
18537
18579
|
function findOpenFailureAlert(tickets, headline) {
|
|
@@ -19265,7 +19307,7 @@ function nextRoles(decision, ran) {
|
|
|
19265
19307
|
}
|
|
19266
19308
|
|
|
19267
19309
|
// src/ship-name.ts
|
|
19268
|
-
import { existsSync as existsSync40, mkdirSync as mkdirSync29, readFileSync as
|
|
19310
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync29, readFileSync as readFileSync37, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
|
|
19269
19311
|
import { dirname as dirname24 } from "path";
|
|
19270
19312
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
19271
19313
|
var ShipNameError = class extends Error {
|
|
@@ -19329,7 +19371,7 @@ async function syncRouteName(i) {
|
|
|
19329
19371
|
function readSyncedName(resolvedFile) {
|
|
19330
19372
|
if (!existsSync40(resolvedFile)) return void 0;
|
|
19331
19373
|
try {
|
|
19332
|
-
const v = JSON.parse(
|
|
19374
|
+
const v = JSON.parse(readFileSync37(resolvedFile, "utf8")).shipName;
|
|
19333
19375
|
return typeof v === "string" ? v : void 0;
|
|
19334
19376
|
} catch {
|
|
19335
19377
|
return void 0;
|
|
@@ -19339,7 +19381,7 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
19339
19381
|
if (!existsSync40(resolvedFile)) return;
|
|
19340
19382
|
let raw;
|
|
19341
19383
|
try {
|
|
19342
|
-
raw = JSON.parse(
|
|
19384
|
+
raw = JSON.parse(readFileSync37(resolvedFile, "utf8"));
|
|
19343
19385
|
} catch {
|
|
19344
19386
|
return;
|
|
19345
19387
|
}
|
|
@@ -19354,7 +19396,7 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
19354
19396
|
async function reconcileShipName(i) {
|
|
19355
19397
|
let local = i.ship.name;
|
|
19356
19398
|
try {
|
|
19357
|
-
local = shipNameOfYaml(
|
|
19399
|
+
local = shipNameOfYaml(readFileSync37(i.configFile, "utf8")) ?? local;
|
|
19358
19400
|
} catch {
|
|
19359
19401
|
}
|
|
19360
19402
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -19378,7 +19420,7 @@ async function reconcileShipName(i) {
|
|
|
19378
19420
|
}
|
|
19379
19421
|
if (result.status !== "ok") return result;
|
|
19380
19422
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
19381
|
-
const text2 =
|
|
19423
|
+
const text2 = readFileSync37(i.configFile, "utf8");
|
|
19382
19424
|
writeFileSync32(i.configFile, setShipNameInYaml(text2, result.name));
|
|
19383
19425
|
i.ship.name = result.name;
|
|
19384
19426
|
if (result.action === "row-wins") {
|
|
@@ -20038,7 +20080,7 @@ function describeCapability(i) {
|
|
|
20038
20080
|
}
|
|
20039
20081
|
|
|
20040
20082
|
// src/agents-sync-run.ts
|
|
20041
|
-
import { existsSync as existsSync41, mkdirSync as mkdirSync30, readFileSync as
|
|
20083
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync30, readFileSync as readFileSync38, writeFileSync as writeFileSync33 } from "fs";
|
|
20042
20084
|
import { dirname as dirname25 } from "path";
|
|
20043
20085
|
async function runAgentsSync(target, o) {
|
|
20044
20086
|
const dryRun = o.dryRun ?? false;
|
|
@@ -20072,7 +20114,7 @@ async function runAgentsSync(target, o) {
|
|
|
20072
20114
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
20073
20115
|
if (!dryRun) {
|
|
20074
20116
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
20075
|
-
const raw = existsSync41(resolvedPath) ? JSON.parse(
|
|
20117
|
+
const raw = existsSync41(resolvedPath) ? JSON.parse(readFileSync38(resolvedPath, "utf8")) : {};
|
|
20076
20118
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
20077
20119
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
20078
20120
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
@@ -20194,7 +20236,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
20194
20236
|
|
|
20195
20237
|
// src/repos-check.ts
|
|
20196
20238
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
20197
|
-
import { readFileSync as
|
|
20239
|
+
import { readFileSync as readFileSync39 } from "fs";
|
|
20198
20240
|
import { basename as basename11 } from "path";
|
|
20199
20241
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
20200
20242
|
function checkHookSyntax(script, shell) {
|
|
@@ -20228,14 +20270,14 @@ function renderSamples(cfg, dir) {
|
|
|
20228
20270
|
});
|
|
20229
20271
|
}
|
|
20230
20272
|
function checkRepoSpec(dir, opts = {}) {
|
|
20231
|
-
const empty = { parsed: null, effective: null, readiness: [], hooks: [], shadowed: [], samples: [] };
|
|
20273
|
+
const empty = { parsed: null, effective: null, readiness: [], hooks: [], shadowed: [], mapFile: null, samples: [] };
|
|
20232
20274
|
const file = findRepoConfig(dir);
|
|
20233
20275
|
if (!file) {
|
|
20234
20276
|
return { ...empty, file: null, error: `no .crew.yaml, .crew.yml or .crew.json in ${dir}`, exitCode: 1 };
|
|
20235
20277
|
}
|
|
20236
20278
|
let parsed;
|
|
20237
20279
|
try {
|
|
20238
|
-
parsed = parseRepoConfig(
|
|
20280
|
+
parsed = parseRepoConfig(readFileSync39(file, "utf8"), file);
|
|
20239
20281
|
} catch (e) {
|
|
20240
20282
|
if (!(e instanceof RepoConfigError)) throw e;
|
|
20241
20283
|
return { ...empty, file, error: e.message, exitCode: 1 };
|
|
@@ -20246,14 +20288,16 @@ function checkRepoSpec(dir, opts = {}) {
|
|
|
20246
20288
|
const engine = effective.hooks.setup ? containerEngineUse(effective.hooks.setup) : null;
|
|
20247
20289
|
if (engine) readiness.push(`hooks.setup runs \`${engine}\`, and a sandbox has no container engine \u2014 start the service as a process instead (REPO_SPEC.md, "Services inside the sandbox")`);
|
|
20248
20290
|
}
|
|
20249
|
-
const
|
|
20291
|
+
const mapPath = mapFilePath(effective.hooks.map);
|
|
20292
|
+
const mapFile = mapPath ? { ...inspectMapFile(dir, mapPath), budget: opts.ship?.mapTokenBudget ?? DEFAULT_MAP_TOKEN_BUDGET } : null;
|
|
20293
|
+
const hooks = Object.keys(effective.hooks).filter((name) => !(name === "map" && mapPath)).map((name) => ({
|
|
20250
20294
|
name,
|
|
20251
20295
|
label: hookLabel(effective, name),
|
|
20252
20296
|
// CREW-2914: a flavor name is not shell, so it is not syntax-checked.
|
|
20253
20297
|
syntax: name === "intel" && intelFlavor(effective.hooks.intel) ? { status: "ok" } : checkHookSyntax(effective.hooks[name], effective.shell),
|
|
20254
20298
|
...name === "intel" && intelFlavor(effective.hooks.intel) ? { flavor: intelFlavor(effective.hooks.intel) } : {}
|
|
20255
20299
|
}));
|
|
20256
|
-
const failed = hooks.some((h) => h.syntax.status === "error");
|
|
20300
|
+
const failed = hooks.some((h) => h.syntax.status === "error") || mapFile !== null && !mapFile.exists;
|
|
20257
20301
|
return {
|
|
20258
20302
|
file,
|
|
20259
20303
|
parsed,
|
|
@@ -20261,6 +20305,7 @@ function checkRepoSpec(dir, opts = {}) {
|
|
|
20261
20305
|
readiness,
|
|
20262
20306
|
hooks,
|
|
20263
20307
|
shadowed: effective.shadowed,
|
|
20308
|
+
mapFile,
|
|
20264
20309
|
samples: renderSamples(effective, dir),
|
|
20265
20310
|
error: null,
|
|
20266
20311
|
exitCode: failed || opts.strict && readiness.length > 0 ? 1 : 0
|
|
@@ -20280,6 +20325,10 @@ function renderRepoCheck(r) {
|
|
|
20280
20325
|
` branch.push ${e.branch.push}`
|
|
20281
20326
|
];
|
|
20282
20327
|
for (const s of r.samples) lines2.push(` ${s.case}: branch ${s.branch}, worktree ../${s.worktree}`);
|
|
20328
|
+
if (r.mapFile) {
|
|
20329
|
+
const m = r.mapFile;
|
|
20330
|
+
lines2.push(m.exists ? ` map: file ${m.path} (${m.lines} lines, about ${m.tokens} tokens; ship budget ${m.budget})` : ` map: file ${m.path} NOT FOUND \u2014 the brief will carry an absence note`);
|
|
20331
|
+
}
|
|
20283
20332
|
lines2.push(r.hooks.length ? " hooks" : " hooks none");
|
|
20284
20333
|
for (const h of r.hooks) {
|
|
20285
20334
|
const named = h.label !== e.hooks[h.name] ? ` (${h.label})` : "";
|
|
@@ -20692,7 +20741,7 @@ ${run3.err}` });
|
|
|
20692
20741
|
}
|
|
20693
20742
|
|
|
20694
20743
|
// src/sample-ticket.ts
|
|
20695
|
-
import { basename as basename12, dirname as dirname26, join as
|
|
20744
|
+
import { basename as basename12, dirname as dirname26, join as join44 } from "path";
|
|
20696
20745
|
var SAMPLE_REPO = "crew-sample";
|
|
20697
20746
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
20698
20747
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -20715,8 +20764,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
20715
20764
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
20716
20765
|
`;
|
|
20717
20766
|
function sampleDir(existing, reposBase, workspace) {
|
|
20718
|
-
if (existing.length > 0) return
|
|
20719
|
-
return
|
|
20767
|
+
if (existing.length > 0) return join44(dirname26(existing[0]), SAMPLE_REPO);
|
|
20768
|
+
return join44(reposBase, workspace, SAMPLE_REPO);
|
|
20720
20769
|
}
|
|
20721
20770
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
20722
20771
|
function sampleDescription() {
|
|
@@ -20760,8 +20809,8 @@ function planSampleTicket(i) {
|
|
|
20760
20809
|
}
|
|
20761
20810
|
|
|
20762
20811
|
// src/worktree-sweep.ts
|
|
20763
|
-
import { existsSync as existsSync42, readFileSync as
|
|
20764
|
-
import { basename as basename13, dirname as dirname27, join as
|
|
20812
|
+
import { existsSync as existsSync42, readFileSync as readFileSync40, readdirSync as readdirSync14 } from "fs";
|
|
20813
|
+
import { basename as basename13, dirname as dirname27, join as join45, resolve as resolve5 } from "path";
|
|
20765
20814
|
function planWorktreeSweep(target, tickets, contract) {
|
|
20766
20815
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
20767
20816
|
const verified = contract.statuses.verified;
|
|
@@ -20838,7 +20887,7 @@ function siblingWorktreeDirs(target) {
|
|
|
20838
20887
|
for (const name of names) {
|
|
20839
20888
|
if (!name.startsWith(stem)) continue;
|
|
20840
20889
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
20841
|
-
const path =
|
|
20890
|
+
const path = join45(parent, name);
|
|
20842
20891
|
if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
|
|
20843
20892
|
}
|
|
20844
20893
|
return out;
|
|
@@ -20941,7 +20990,7 @@ function isHandBackMerge(path, base) {
|
|
|
20941
20990
|
if (!msgPath) return false;
|
|
20942
20991
|
let first;
|
|
20943
20992
|
try {
|
|
20944
|
-
first =
|
|
20993
|
+
first = readFileSync40(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
20945
20994
|
} catch {
|
|
20946
20995
|
return false;
|
|
20947
20996
|
}
|
|
@@ -21047,13 +21096,13 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
21047
21096
|
|
|
21048
21097
|
// src/stream-sweep.ts
|
|
21049
21098
|
import { existsSync as existsSync43, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
|
|
21050
|
-
import { join as
|
|
21099
|
+
import { join as join46 } from "path";
|
|
21051
21100
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
21052
21101
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
21053
|
-
const dir =
|
|
21102
|
+
const dir = join46(stateDir, "streams");
|
|
21054
21103
|
if (!existsSync43(dir)) return [];
|
|
21055
21104
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
21056
|
-
return readdirSync15(dir).map((name) =>
|
|
21105
|
+
return readdirSync15(dir).map((name) => join46(dir, name)).filter((path) => {
|
|
21057
21106
|
try {
|
|
21058
21107
|
return statSync12(path).mtimeMs < cutoff;
|
|
21059
21108
|
} catch {
|
|
@@ -21081,7 +21130,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
21081
21130
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
21082
21131
|
if (!existsSync43(stateDir)) return [];
|
|
21083
21132
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
21084
|
-
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) =>
|
|
21133
|
+
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join46(stateDir, name)).filter((path) => {
|
|
21085
21134
|
try {
|
|
21086
21135
|
return statSync12(path).mtimeMs < cutoff;
|
|
21087
21136
|
} catch {
|
|
@@ -21091,12 +21140,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
21091
21140
|
}
|
|
21092
21141
|
|
|
21093
21142
|
// src/cli.ts
|
|
21094
|
-
import { realpathSync as realpathSync3, readFileSync as
|
|
21143
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync46, existsSync as existsSync50, 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";
|
|
21095
21144
|
import { createInterface } from "readline/promises";
|
|
21096
21145
|
|
|
21097
21146
|
// src/periscope-cmd.ts
|
|
21098
|
-
import { existsSync as existsSync44, mkdirSync as mkdirSync31, readFileSync as
|
|
21099
|
-
import { join as
|
|
21147
|
+
import { existsSync as existsSync44, mkdirSync as mkdirSync31, readFileSync as readFileSync41, rmSync as rmSync21, writeFileSync as writeFileSync34 } from "fs";
|
|
21148
|
+
import { join as join47 } from "path";
|
|
21100
21149
|
var PeriscopeError = class extends Error {
|
|
21101
21150
|
};
|
|
21102
21151
|
var LivePeriscopeError = class extends PeriscopeError {
|
|
@@ -21106,8 +21155,8 @@ var LivePeriscopeError = class extends PeriscopeError {
|
|
|
21106
21155
|
this.data = { refused: "live_periscope_exists", ...live };
|
|
21107
21156
|
}
|
|
21108
21157
|
};
|
|
21109
|
-
var priorDir = (stateDir) =>
|
|
21110
|
-
var priorPath = (stateDir, ticketRecordId) =>
|
|
21158
|
+
var priorDir = (stateDir) => join47(stateDir, "periscope-priors");
|
|
21159
|
+
var priorPath = (stateDir, ticketRecordId) => join47(priorDir(stateDir), `${ticketRecordId}.json`);
|
|
21111
21160
|
function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
21112
21161
|
rmSync21(priorPath(stateDir, ticketRecordId), { force: true });
|
|
21113
21162
|
}
|
|
@@ -21121,7 +21170,7 @@ function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
|
21121
21170
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
21122
21171
|
try {
|
|
21123
21172
|
const p = priorPath(stateDir, ticketRecordId);
|
|
21124
|
-
return existsSync44(p) ? JSON.parse(
|
|
21173
|
+
return existsSync44(p) ? JSON.parse(readFileSync41(p, "utf8")) : void 0;
|
|
21125
21174
|
} catch {
|
|
21126
21175
|
return void 0;
|
|
21127
21176
|
}
|
|
@@ -21575,7 +21624,7 @@ ${tail2(preview.output)}`);
|
|
|
21575
21624
|
// src/periscope-idle.ts
|
|
21576
21625
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
21577
21626
|
import { statSync as statSync13 } from "fs";
|
|
21578
|
-
import { join as
|
|
21627
|
+
import { join as join48 } from "path";
|
|
21579
21628
|
var MAX_SCANNED_FILES = 2e4;
|
|
21580
21629
|
function newestWorktreeMtime(root) {
|
|
21581
21630
|
const a = newestTrackedMtime(root);
|
|
@@ -21600,7 +21649,7 @@ function newestTrackedMtime(root) {
|
|
|
21600
21649
|
if (!rel) continue;
|
|
21601
21650
|
if (++scanned > MAX_SCANNED_FILES) break;
|
|
21602
21651
|
try {
|
|
21603
|
-
const m = statSync13(
|
|
21652
|
+
const m = statSync13(join48(root, rel)).mtimeMs;
|
|
21604
21653
|
if (newest === null || m > newest) newest = m;
|
|
21605
21654
|
} catch {
|
|
21606
21655
|
}
|
|
@@ -21676,12 +21725,12 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
|
21676
21725
|
|
|
21677
21726
|
// src/ssh-keys.ts
|
|
21678
21727
|
import { execFileSync as execFileSync13 } from "child_process";
|
|
21679
|
-
import { existsSync as existsSync45, mkdirSync as mkdirSync32, readFileSync as
|
|
21680
|
-
import { dirname as dirname28, join as
|
|
21728
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync32, readFileSync as readFileSync42, chmodSync as chmodSync3 } from "fs";
|
|
21729
|
+
import { dirname as dirname28, join as join49 } from "path";
|
|
21681
21730
|
var SshKeyError = class extends Error {
|
|
21682
21731
|
};
|
|
21683
21732
|
function shipSshKeyPathFor(stateDir) {
|
|
21684
|
-
return
|
|
21733
|
+
return join49(stateDir, "ssh", "id_ed25519");
|
|
21685
21734
|
}
|
|
21686
21735
|
function ensureShipSshKeypair(stateDir) {
|
|
21687
21736
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
@@ -21710,7 +21759,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
21710
21759
|
if (!existsSync45(publicKeyPath)) {
|
|
21711
21760
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
21712
21761
|
}
|
|
21713
|
-
const publicKey =
|
|
21762
|
+
const publicKey = readFileSync42(publicKeyPath, "utf8").trim();
|
|
21714
21763
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
21715
21764
|
}
|
|
21716
21765
|
function sshKeygenAvailable() {
|
|
@@ -21874,8 +21923,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
21874
21923
|
import { createServer as createHttpServer } from "http";
|
|
21875
21924
|
import { spawn as spawn9 } from "child_process";
|
|
21876
21925
|
import { randomBytes } from "crypto";
|
|
21877
|
-
import { existsSync as existsSync46, mkdirSync as mkdirSync34, readFileSync as
|
|
21878
|
-
import { join as
|
|
21926
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync34, readFileSync as readFileSync43, writeFileSync as writeFileSync35, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
|
|
21927
|
+
import { join as join50 } from "path";
|
|
21879
21928
|
|
|
21880
21929
|
// src/passenger-mirror.ts
|
|
21881
21930
|
import { mkdirSync as mkdirSync33 } from "fs";
|
|
@@ -22005,20 +22054,20 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
22005
22054
|
return child;
|
|
22006
22055
|
};
|
|
22007
22056
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
22008
|
-
return
|
|
22057
|
+
return join50(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
22009
22058
|
}
|
|
22010
22059
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
22011
22060
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
22012
22061
|
if (!existsSync46(path)) return void 0;
|
|
22013
22062
|
try {
|
|
22014
|
-
return JSON.parse(
|
|
22063
|
+
return JSON.parse(readFileSync43(path, "utf8"));
|
|
22015
22064
|
} catch {
|
|
22016
22065
|
return void 0;
|
|
22017
22066
|
}
|
|
22018
22067
|
}
|
|
22019
22068
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
22020
22069
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
22021
|
-
mkdirSync34(
|
|
22070
|
+
mkdirSync34(join50(stateDir, "passengers"), { recursive: true });
|
|
22022
22071
|
writeFileSync35(path, `${JSON.stringify(state, null, 2)}
|
|
22023
22072
|
`);
|
|
22024
22073
|
}
|
|
@@ -22039,7 +22088,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
22039
22088
|
}
|
|
22040
22089
|
}
|
|
22041
22090
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
22042
|
-
const dir =
|
|
22091
|
+
const dir = join50(stateDir, "passengers");
|
|
22043
22092
|
if (!existsSync46(dir)) return [];
|
|
22044
22093
|
const suffix = "-sync.json";
|
|
22045
22094
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
@@ -22537,8 +22586,8 @@ function describeCause(err3) {
|
|
|
22537
22586
|
}
|
|
22538
22587
|
|
|
22539
22588
|
// src/daemon.ts
|
|
22540
|
-
import { existsSync as existsSync47, mkdirSync as mkdirSync36, readFileSync as
|
|
22541
|
-
import { join as
|
|
22589
|
+
import { existsSync as existsSync47, mkdirSync as mkdirSync36, readFileSync as readFileSync44, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
|
|
22590
|
+
import { join as join51 } from "path";
|
|
22542
22591
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
22543
22592
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
22544
22593
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
@@ -22626,7 +22675,7 @@ function latestMtime(path) {
|
|
|
22626
22675
|
if (!stat2.isDirectory()) return 0;
|
|
22627
22676
|
let latest = stat2.mtimeMs;
|
|
22628
22677
|
for (const entry of readdirSync17(path, { withFileTypes: true })) {
|
|
22629
|
-
latest = Math.max(latest, latestMtime(
|
|
22678
|
+
latest = Math.max(latest, latestMtime(join51(path, entry.name)));
|
|
22630
22679
|
}
|
|
22631
22680
|
return latest;
|
|
22632
22681
|
}
|
|
@@ -22638,7 +22687,7 @@ function makeStaleChecker(paths, opts = {}) {
|
|
|
22638
22687
|
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync47(opts.reloadSentinel);
|
|
22639
22688
|
}
|
|
22640
22689
|
function reloadSentinelFor(stateDir) {
|
|
22641
|
-
return
|
|
22690
|
+
return join51(stateDir, "reload-requested");
|
|
22642
22691
|
}
|
|
22643
22692
|
function readReloadRequestedAt(stateDir) {
|
|
22644
22693
|
try {
|
|
@@ -22648,7 +22697,7 @@ function readReloadRequestedAt(stateDir) {
|
|
|
22648
22697
|
}
|
|
22649
22698
|
}
|
|
22650
22699
|
function daemonStampFor(stateDir) {
|
|
22651
|
-
return
|
|
22700
|
+
return join51(stateDir, "daemon.started");
|
|
22652
22701
|
}
|
|
22653
22702
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
22654
22703
|
mkdirSync36(stateDir, { recursive: true });
|
|
@@ -22658,7 +22707,7 @@ function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
|
22658
22707
|
}
|
|
22659
22708
|
function readDaemonStarted(stateDir) {
|
|
22660
22709
|
try {
|
|
22661
|
-
const v = JSON.parse(
|
|
22710
|
+
const v = JSON.parse(readFileSync44(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
22662
22711
|
return typeof v === "number" ? v : null;
|
|
22663
22712
|
} catch {
|
|
22664
22713
|
return null;
|
|
@@ -23235,7 +23284,7 @@ import {
|
|
|
23235
23284
|
} from "fs";
|
|
23236
23285
|
import { createConnection } from "net";
|
|
23237
23286
|
import { createServer as createServer2 } from "http";
|
|
23238
|
-
import { basename as basename14, dirname as dirname31, join as
|
|
23287
|
+
import { basename as basename14, dirname as dirname31, join as join52 } from "path";
|
|
23239
23288
|
import { execFile as execFile3, spawn as spawn12 } from "child_process";
|
|
23240
23289
|
|
|
23241
23290
|
// src/periscope-control.ts
|
|
@@ -23464,7 +23513,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
23464
23513
|
// src/control-api.ts
|
|
23465
23514
|
var PAUSABLE = ["dev", "design", "qa"];
|
|
23466
23515
|
var CONTROL_SOCKET_NAME = "control.sock";
|
|
23467
|
-
var controlSocketFor = (stateDir) =>
|
|
23516
|
+
var controlSocketFor = (stateDir) => join52(stateDir, CONTROL_SOCKET_NAME);
|
|
23468
23517
|
var MAX_SOCKET_PATH = 100;
|
|
23469
23518
|
var MAX_TAIL_BYTES = 1024 * 1024;
|
|
23470
23519
|
var MAX_WAIT_SECONDS = 30;
|
|
@@ -23551,7 +23600,7 @@ function candidates(live, rotated) {
|
|
|
23551
23600
|
const out = [live];
|
|
23552
23601
|
if (!rotated) return out;
|
|
23553
23602
|
try {
|
|
23554
|
-
for (const n of readdirSync18(dirname31(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(
|
|
23603
|
+
for (const n of readdirSync18(dirname31(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join52(dirname31(live), n));
|
|
23555
23604
|
} catch {
|
|
23556
23605
|
}
|
|
23557
23606
|
return out;
|
|
@@ -23751,7 +23800,7 @@ async function route(deps, method, url, req, abort) {
|
|
|
23751
23800
|
if (p.startsWith("/streams/") && get2) {
|
|
23752
23801
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
23753
23802
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
23754
|
-
const file =
|
|
23803
|
+
const file = join52(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
23755
23804
|
if (!existsSync48(file)) return err2(404, "no such run");
|
|
23756
23805
|
return tailReply(file, url.searchParams, false, abort);
|
|
23757
23806
|
}
|
|
@@ -23901,19 +23950,19 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
23901
23950
|
|
|
23902
23951
|
// src/periscope-mcp.ts
|
|
23903
23952
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
23904
|
-
import { basename as basename15, dirname as dirname32, isAbsolute as isAbsolute5, join as
|
|
23953
|
+
import { basename as basename15, dirname as dirname32, isAbsolute as isAbsolute5, join as join54, relative as relative2, resolve as resolve6, sep } from "path";
|
|
23905
23954
|
import { z } from "zod";
|
|
23906
23955
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23907
23956
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
23908
23957
|
|
|
23909
23958
|
// src/periscope-endpoints.ts
|
|
23910
23959
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
23911
|
-
import { chmodSync as chmodSync5, existsSync as existsSync49, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as
|
|
23912
|
-
import { join as
|
|
23913
|
-
var secretDir = (stateDir) =>
|
|
23914
|
-
var secretFile = (stateDir, id) =>
|
|
23915
|
-
var endpointDir = (stateDir) =>
|
|
23916
|
-
var endpointFile = (stateDir, id) =>
|
|
23960
|
+
import { chmodSync as chmodSync5, existsSync as existsSync49, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync45, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
|
|
23961
|
+
import { join as join53 } from "path";
|
|
23962
|
+
var secretDir = (stateDir) => join53(stateDir, "periscope-mcp");
|
|
23963
|
+
var secretFile = (stateDir, id) => join53(secretDir(stateDir), `${id}.json`);
|
|
23964
|
+
var endpointDir = (stateDir) => join53(stateDir, "periscope-endpoints");
|
|
23965
|
+
var endpointFile = (stateDir, id) => join53(endpointDir(stateDir), `${id}.json`);
|
|
23917
23966
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
23918
23967
|
mkdirSync37(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
23919
23968
|
const secret = randomBytes3(32).toString("base64url");
|
|
@@ -23925,7 +23974,7 @@ function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
|
23925
23974
|
}
|
|
23926
23975
|
function readPeriscopeSecret(stateDir, ticketRecordId) {
|
|
23927
23976
|
try {
|
|
23928
|
-
const v = JSON.parse(
|
|
23977
|
+
const v = JSON.parse(readFileSync45(secretFile(stateDir, ticketRecordId), "utf8"));
|
|
23929
23978
|
return typeof v.secret === "string" && v.secret ? v.secret : void 0;
|
|
23930
23979
|
} catch {
|
|
23931
23980
|
return void 0;
|
|
@@ -23936,7 +23985,7 @@ function removePeriscopeSecret(stateDir, ticketRecordId) {
|
|
|
23936
23985
|
}
|
|
23937
23986
|
function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
23938
23987
|
try {
|
|
23939
|
-
return JSON.parse(
|
|
23988
|
+
return JSON.parse(readFileSync45(endpointFile(stateDir, ticketRecordId), "utf8"));
|
|
23940
23989
|
} catch {
|
|
23941
23990
|
return void 0;
|
|
23942
23991
|
}
|
|
@@ -24054,13 +24103,13 @@ async function resolveInWorktree(root, requested) {
|
|
|
24054
24103
|
let abs;
|
|
24055
24104
|
if (isAbsolute5(requested)) {
|
|
24056
24105
|
abs = resolve6(requested);
|
|
24057
|
-
if (isInside(root, abs)) abs =
|
|
24106
|
+
if (isInside(root, abs)) abs = join54(realRoot, relative2(root, abs));
|
|
24058
24107
|
} else {
|
|
24059
24108
|
abs = resolve6(realRoot, requested);
|
|
24060
24109
|
}
|
|
24061
24110
|
if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
24062
24111
|
const { real, rest } = await realAncestor(abs);
|
|
24063
|
-
const final = rest ?
|
|
24112
|
+
const final = rest ? join54(real, rest) : real;
|
|
24064
24113
|
if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
24065
24114
|
if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
|
|
24066
24115
|
return final;
|
|
@@ -24128,7 +24177,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
24128
24177
|
const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
|
|
24129
24178
|
const lines2 = await Promise.all(entries.map(async (e) => {
|
|
24130
24179
|
if (e.isDirectory()) return `[DIR] ${e.name}`;
|
|
24131
|
-
const size = (await lstat(
|
|
24180
|
+
const size = (await lstat(join54(abs, e.name))).size;
|
|
24132
24181
|
return `[FILE] ${e.name} (${size} bytes)`;
|
|
24133
24182
|
}));
|
|
24134
24183
|
return text(lines2.join("\n") || "(empty)");
|
|
@@ -24194,7 +24243,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
24194
24243
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
24195
24244
|
}, async ({ path }) => {
|
|
24196
24245
|
const parent = await inTree(dirname32(path) === "" ? "." : dirname32(path));
|
|
24197
|
-
const abs =
|
|
24246
|
+
const abs = join54(parent, basename15(path));
|
|
24198
24247
|
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
24199
24248
|
const st = await lstat(abs).catch(() => void 0);
|
|
24200
24249
|
if (!st) return text(`${path} does not exist`, true);
|
|
@@ -24685,7 +24734,7 @@ if (command === "provision") {
|
|
|
24685
24734
|
process.exit(outcome.code);
|
|
24686
24735
|
}
|
|
24687
24736
|
if (command === "pool-member") {
|
|
24688
|
-
const pool = poolConfigFrom(argv, process.env, () =>
|
|
24737
|
+
const pool = poolConfigFrom(argv, process.env, () => readFileSync46(0, "utf8"));
|
|
24689
24738
|
if (typeof pool === "string") {
|
|
24690
24739
|
process.stderr.write(`crew pool-member: ${pool}
|
|
24691
24740
|
`);
|
|
@@ -24943,7 +24992,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24943
24992
|
throw e;
|
|
24944
24993
|
}
|
|
24945
24994
|
if (command === "credential") {
|
|
24946
|
-
const r = runCredential(positional[1],
|
|
24995
|
+
const r = runCredential(positional[1], readFileSync46(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
|
|
24947
24996
|
process.stdout.write(r.out);
|
|
24948
24997
|
process.stderr.write(r.err);
|
|
24949
24998
|
process.exit(r.code);
|
|
@@ -24955,9 +25004,9 @@ if (command === "passenger-sync-daemon") {
|
|
|
24955
25004
|
forges: cfg.ship.forges,
|
|
24956
25005
|
stateDir: cfg.ship.stateDir,
|
|
24957
25006
|
configFile: cfg.configFile,
|
|
24958
|
-
readFile: (p) =>
|
|
25007
|
+
readFile: (p) => readFileSync46(p, "utf8"),
|
|
24959
25008
|
writeFile: (p, t) => writeFileSync38(p, t),
|
|
24960
|
-
stdin: () =>
|
|
25009
|
+
stdin: () => readFileSync46(0, "utf8"),
|
|
24961
25010
|
env: process.env
|
|
24962
25011
|
}
|
|
24963
25012
|
);
|
|
@@ -25250,7 +25299,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25250
25299
|
}
|
|
25251
25300
|
if (host === "linux" && hasSystemd) {
|
|
25252
25301
|
const label = labelFor(CREW_HOME, "run");
|
|
25253
|
-
const staleTimer =
|
|
25302
|
+
const staleTimer = join55(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
25254
25303
|
if (existsSync50(staleTimer)) {
|
|
25255
25304
|
process.stdout.write(
|
|
25256
25305
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
@@ -25322,7 +25371,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25322
25371
|
for (const a of plan.actions) {
|
|
25323
25372
|
if (a.kind === "enable") {
|
|
25324
25373
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
25325
|
-
writeFileSync38(cfg.configFile, planEnable(
|
|
25374
|
+
writeFileSync38(cfg.configFile, planEnable(readFileSync46(cfg.configFile, "utf8"), a.route));
|
|
25326
25375
|
enabledSound.add(a.route);
|
|
25327
25376
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
25328
25377
|
} else {
|
|
@@ -26461,7 +26510,7 @@ ${cu.tail.trimEnd()}
|
|
|
26461
26510
|
process.exit(1);
|
|
26462
26511
|
}
|
|
26463
26512
|
requireArmed("run the daemon loop");
|
|
26464
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
26513
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join55(CREW_HOME, "src"), join55(CREW_HOME, "dist"), join55(CREW_HOME, "bin")];
|
|
26465
26514
|
const configPaths = daemonConfigPaths(cfg);
|
|
26466
26515
|
watchPaths.push(...configPaths);
|
|
26467
26516
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -26766,7 +26815,7 @@ ${cu.tail.trimEnd()}
|
|
|
26766
26815
|
`);
|
|
26767
26816
|
if (!dryRun) {
|
|
26768
26817
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
26769
|
-
const raw = existsSync50(resolvedPath) ? JSON.parse(
|
|
26818
|
+
const raw = existsSync50(resolvedPath) ? JSON.parse(readFileSync46(resolvedPath, "utf8")) : {};
|
|
26770
26819
|
raw.agentSkills = result.agentSkills;
|
|
26771
26820
|
mkdirSync38(dirOf2(resolvedPath), { recursive: true });
|
|
26772
26821
|
writeFileSync38(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
@@ -26792,7 +26841,7 @@ ${cu.tail.trimEnd()}
|
|
|
26792
26841
|
throw e;
|
|
26793
26842
|
}
|
|
26794
26843
|
const oldName = cfg.ship.name;
|
|
26795
|
-
writeFileSync38(cfg.configFile, setShipNameInYaml(
|
|
26844
|
+
writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync46(cfg.configFile, "utf8"), newName));
|
|
26796
26845
|
cfg.ship.name = newName;
|
|
26797
26846
|
let renameMachineId;
|
|
26798
26847
|
try {
|
|
@@ -26839,7 +26888,7 @@ ${cu.tail.trimEnd()}
|
|
|
26839
26888
|
process.exit(2);
|
|
26840
26889
|
};
|
|
26841
26890
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
|
|
26842
|
-
const configText =
|
|
26891
|
+
const configText = readFileSync46(cfg.configFile, "utf8");
|
|
26843
26892
|
if (sub === "check") {
|
|
26844
26893
|
try {
|
|
26845
26894
|
const routeArg = value("route") ?? positional[2];
|
|
@@ -26854,7 +26903,8 @@ ${cu.tail.trimEnd()}
|
|
|
26854
26903
|
labels: { ...found.labels, ...o?.labels },
|
|
26855
26904
|
release: mergeRouteRelease(found.release, o?.release),
|
|
26856
26905
|
branch: { ...found.branch, ...o?.branch },
|
|
26857
|
-
worktrees: { prefix: shipWorktreePrefixFor(found), nameOverride: o?.worktreeName }
|
|
26906
|
+
worktrees: { prefix: shipWorktreePrefixFor(found), nameOverride: o?.worktreeName },
|
|
26907
|
+
mapTokenBudget: cfg.ship.mapTokenBudget
|
|
26858
26908
|
});
|
|
26859
26909
|
} catch (e) {
|
|
26860
26910
|
if (e instanceof ReposError) {
|
|
@@ -26879,7 +26929,7 @@ ${cu.tail.trimEnd()}
|
|
|
26879
26929
|
}
|
|
26880
26930
|
}, read: (r) => {
|
|
26881
26931
|
try {
|
|
26882
|
-
return
|
|
26932
|
+
return readFileSync46(resolvePath(dir, r), "utf8");
|
|
26883
26933
|
} catch {
|
|
26884
26934
|
return void 0;
|
|
26885
26935
|
}
|
|
@@ -27030,7 +27080,7 @@ ${cu.tail.trimEnd()}
|
|
|
27030
27080
|
let resolvedRemotes;
|
|
27031
27081
|
if (existsSync50(resolvedFile)) {
|
|
27032
27082
|
try {
|
|
27033
|
-
resolvedRemotes = JSON.parse(
|
|
27083
|
+
resolvedRemotes = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
27034
27084
|
} catch {
|
|
27035
27085
|
}
|
|
27036
27086
|
}
|
|
@@ -27051,7 +27101,7 @@ ${cu.tail.trimEnd()}
|
|
|
27051
27101
|
isCheckout,
|
|
27052
27102
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
27053
27103
|
try {
|
|
27054
|
-
return Object.keys(parseRepoConfig(
|
|
27104
|
+
return Object.keys(parseRepoConfig(readFileSync46(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
27055
27105
|
} catch {
|
|
27056
27106
|
return false;
|
|
27057
27107
|
}
|
|
@@ -27160,7 +27210,7 @@ ${cu.tail.trimEnd()}
|
|
|
27160
27210
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
27161
27211
|
`);
|
|
27162
27212
|
if (existsSync50(resolvedFile)) {
|
|
27163
|
-
const raw = JSON.parse(
|
|
27213
|
+
const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
27164
27214
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
27165
27215
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
27166
27216
|
writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
@@ -27538,7 +27588,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27538
27588
|
process.exit(2);
|
|
27539
27589
|
}
|
|
27540
27590
|
if (found.shipNameAdopted && !dryRun) {
|
|
27541
|
-
writeFileSync38(cfg.configFile, setShipNameInYaml(
|
|
27591
|
+
writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync46(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
27542
27592
|
cfg.ship.name = found.shipNameAdopted;
|
|
27543
27593
|
}
|
|
27544
27594
|
if (found.shipRow) {
|
|
@@ -27584,7 +27634,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27584
27634
|
const hadPreviousFile = existsSync50(resolvedPath);
|
|
27585
27635
|
if (hadPreviousFile) {
|
|
27586
27636
|
try {
|
|
27587
|
-
previous = JSON.parse(
|
|
27637
|
+
previous = JSON.parse(readFileSync46(resolvedPath, "utf8"));
|
|
27588
27638
|
} catch {
|
|
27589
27639
|
}
|
|
27590
27640
|
}
|
|
@@ -28458,10 +28508,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28458
28508
|
startupSeconds: cfg.ship.intelStartupSeconds,
|
|
28459
28509
|
prepare: async (path) => {
|
|
28460
28510
|
for (const f of repoWt.config.worktrees.copy) {
|
|
28461
|
-
const src =
|
|
28462
|
-
if (!existsSync50(src) || existsSync50(
|
|
28463
|
-
mkdirSync38(dirOf2(
|
|
28464
|
-
copyFileSync2(src,
|
|
28511
|
+
const src = join55(repoWt.dir, f);
|
|
28512
|
+
if (!existsSync50(src) || existsSync50(join55(path, f))) continue;
|
|
28513
|
+
mkdirSync38(dirOf2(join55(path, f)), { recursive: true });
|
|
28514
|
+
copyFileSync2(src, join55(path, f));
|
|
28465
28515
|
}
|
|
28466
28516
|
const setup = repoWt.config.hooks.setup;
|
|
28467
28517
|
return setup ? runScript(setup, { cwd: path, shell: shellWt }) : null;
|
|
@@ -28768,10 +28818,10 @@ ${where.join("\n")}`);
|
|
|
28768
28818
|
},
|
|
28769
28819
|
prepare: async (path) => {
|
|
28770
28820
|
for (const f of repoP.config.worktrees.copy) {
|
|
28771
|
-
const src =
|
|
28821
|
+
const src = join55(repoP.dir, f);
|
|
28772
28822
|
if (!existsSync50(src)) continue;
|
|
28773
|
-
mkdirSync38(dirOf2(
|
|
28774
|
-
copyFileSync2(src,
|
|
28823
|
+
mkdirSync38(dirOf2(join55(path, f)), { recursive: true });
|
|
28824
|
+
copyFileSync2(src, join55(path, f));
|
|
28775
28825
|
}
|
|
28776
28826
|
const setup = repoP.config.hooks.setup;
|
|
28777
28827
|
return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
|
|
@@ -29081,7 +29131,7 @@ ${where.join("\n")}`);
|
|
|
29081
29131
|
};
|
|
29082
29132
|
const droppedRoutes = [];
|
|
29083
29133
|
try {
|
|
29084
|
-
const rawCfg = parseYaml2(
|
|
29134
|
+
const rawCfg = parseYaml2(readFileSync46(cfg.configFile, "utf8"));
|
|
29085
29135
|
for (const r of rawCfg?.routes ?? []) {
|
|
29086
29136
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
29087
29137
|
try {
|
|
@@ -29200,7 +29250,7 @@ ${where.join("\n")}`);
|
|
|
29200
29250
|
}
|
|
29201
29251
|
case "log": {
|
|
29202
29252
|
try {
|
|
29203
|
-
const text2 =
|
|
29253
|
+
const text2 = readFileSync46(cfg.ship.logFile, "utf8").split("\n");
|
|
29204
29254
|
process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
29205
29255
|
`);
|
|
29206
29256
|
} catch {
|
|
@@ -29223,7 +29273,7 @@ ${where.join("\n")}`);
|
|
|
29223
29273
|
};
|
|
29224
29274
|
if (!route2.resolved) fail2(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
29225
29275
|
const tracker = new Tracker(route2, cfg.ship);
|
|
29226
|
-
const configText =
|
|
29276
|
+
const configText = readFileSync46(cfg.configFile, "utf8");
|
|
29227
29277
|
const cfgBase = dirname33(cfg.configFile);
|
|
29228
29278
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
29229
29279
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
@@ -29261,19 +29311,19 @@ ${where.join("\n")}`);
|
|
|
29261
29311
|
if (row) {
|
|
29262
29312
|
await tracker.deleteRepoRow(row.id);
|
|
29263
29313
|
if (existsSync50(resolvedFile)) {
|
|
29264
|
-
const raw = JSON.parse(
|
|
29314
|
+
const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
29265
29315
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
29266
29316
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
29267
29317
|
writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
29268
29318
|
`);
|
|
29269
29319
|
}
|
|
29270
29320
|
}
|
|
29271
|
-
if (existsSync50(
|
|
29321
|
+
if (existsSync50(join55(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
|
|
29272
29322
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
29273
29323
|
break;
|
|
29274
29324
|
}
|
|
29275
29325
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
29276
|
-
const fresh = !existsSync50(
|
|
29326
|
+
const fresh = !existsSync50(join55(dir, ".git"));
|
|
29277
29327
|
if (existsSync50(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
29278
29328
|
if (dryRun) {
|
|
29279
29329
|
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`);
|
|
@@ -29281,8 +29331,8 @@ ${where.join("\n")}`);
|
|
|
29281
29331
|
}
|
|
29282
29332
|
if (fresh) {
|
|
29283
29333
|
mkdirSync38(dir, { recursive: true });
|
|
29284
|
-
writeFileSync38(
|
|
29285
|
-
writeFileSync38(
|
|
29334
|
+
writeFileSync38(join55(dir, "README.md"), SAMPLE_README);
|
|
29335
|
+
writeFileSync38(join55(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
29286
29336
|
const g = (...a) => {
|
|
29287
29337
|
const r = spawnSync11("git", a, { cwd: dir, encoding: "utf8" });
|
|
29288
29338
|
if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -29301,7 +29351,7 @@ ${where.join("\n")}`);
|
|
|
29301
29351
|
});
|
|
29302
29352
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
29303
29353
|
if (existsSync50(resolvedFile)) {
|
|
29304
|
-
const raw = JSON.parse(
|
|
29354
|
+
const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
29305
29355
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
29306
29356
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
29307
29357
|
writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
@@ -29695,6 +29745,7 @@ watermark: ${state.watermark()}
|
|
|
29695
29745
|
base: doctorBaseReport(r, r.config.branch.base, baseHealth),
|
|
29696
29746
|
hooks: r.config.hooks,
|
|
29697
29747
|
onPath: (cmd) => !!findOnPath(cmd, doctorSchedulerPath),
|
|
29748
|
+
mapFile: mapFilePath(r.config.hooks.map) ? inspectMapFile(r.dir, mapFilePath(r.config.hooks.map)) : void 0,
|
|
29698
29749
|
intelSyntax: r.config.hooks.intel && !intelFlavor(r.config.hooks.intel) ? checkHookSyntax(r.config.hooks.intel, r.config.shell) : void 0,
|
|
29699
29750
|
intelFlavor: intelFlavor(r.config.hooks.intel) ? { name: intelFlavor(r.config.hooks.intel), missing: [
|
|
29700
29751
|
...missingPrerequisites(intelFlavor(r.config.hooks.intel), (bin) => !!findOnPath(bin, doctorSchedulerPath)),
|
|
@@ -29762,7 +29813,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
29762
29813
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
|
|
29763
29814
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
29764
29815
|
try {
|
|
29765
|
-
return
|
|
29816
|
+
return readFileSync46(p, "utf8");
|
|
29766
29817
|
} catch {
|
|
29767
29818
|
return null;
|
|
29768
29819
|
}
|