@taskless-app/swarm 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/cli.js +126 -7
- package/dist/overmind.js +403 -154
- package/package.json +2 -1
package/dist/overmind.js
CHANGED
|
@@ -746,10 +746,12 @@ function hostCommandLabel(cmd) {
|
|
|
746
746
|
if (isRunScopedCommand(cmd.kind) && cmd.runSlug) return `${base3} ${cmd.runSlug}`;
|
|
747
747
|
return base3;
|
|
748
748
|
}
|
|
749
|
-
var HOST_COMMAND_KINDS, HOST_COMMAND_STATUSES, TERMINAL, HOST_COMMAND_OPEN_STATUSES, UPDATE_STEP_LABELS, HOST_COMMAND_TTL_MS, HOST_COMMAND_KEEPALIVE_MS, HOST_COMMAND_LABELS, RUN_SCOPED_COMMAND_KINDS, RUN_SCOPED, MAX_DRONES_LIMIT;
|
|
749
|
+
var SWARM_PACKAGE_NAME, MANUAL_UPDATE_COMMAND, HOST_COMMAND_KINDS, HOST_COMMAND_STATUSES, TERMINAL, HOST_COMMAND_OPEN_STATUSES, UPDATE_STEP_LABELS, HOST_COMMAND_TTL_MS, HOST_COMMAND_KEEPALIVE_MS, HOST_COMMAND_ACK_GRACE_MS, HOST_COMMAND_MUTE_REASON, HOST_COMMAND_LABELS, RUN_SCOPED_COMMAND_KINDS, RUN_SCOPED, MAX_DRONES_LIMIT;
|
|
750
750
|
var init_hostCommands = __esm({
|
|
751
751
|
"../swarm-core/src/hostCommands.ts"() {
|
|
752
752
|
"use strict";
|
|
753
|
+
SWARM_PACKAGE_NAME = "@taskless-app/swarm";
|
|
754
|
+
MANUAL_UPDATE_COMMAND = `npm i -g ${SWARM_PACKAGE_NAME}@latest`;
|
|
753
755
|
HOST_COMMAND_KINDS = ["pause", "resume", "restart", "update", "set_max_drones", "stop_run", "wake_run"];
|
|
754
756
|
HOST_COMMAND_STATUSES = ["pending", "delivered", "running", "done", "failed", "rejected", "expired"];
|
|
755
757
|
TERMINAL = /* @__PURE__ */ new Set(["done", "failed", "rejected", "expired"]);
|
|
@@ -763,6 +765,8 @@ var init_hostCommands = __esm({
|
|
|
763
765
|
};
|
|
764
766
|
HOST_COMMAND_TTL_MS = 30 * 6e4;
|
|
765
767
|
HOST_COMMAND_KEEPALIVE_MS = 10 * 6e4;
|
|
768
|
+
HOST_COMMAND_ACK_GRACE_MS = 5 * 6e4;
|
|
769
|
+
HOST_COMMAND_MUTE_REASON = `\u043C\u0430\u0448\u0438\u043D\u0430 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u043B\u0430 \u0438\u0441\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u2014 \u0432\u0435\u0440\u043E\u044F\u0442\u043D\u043E, \u0434\u0435\u043C\u043E\u043D \u0441\u0442\u0430\u0440\u0435\u0435 \u043A\u043E\u043C\u0430\u043D\u0434\u044B. \u041E\u0431\u043D\u043E\u0432\u0438 \u0432\u0440\u0443\u0447\u043D\u0443\u044E: ${MANUAL_UPDATE_COMMAND}`;
|
|
766
770
|
HOST_COMMAND_LABELS = {
|
|
767
771
|
pause: "\u043F\u0430\u0443\u0437\u0430",
|
|
768
772
|
resume: "\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C",
|
|
@@ -3263,7 +3267,7 @@ var init_droneChannel = __esm({
|
|
|
3263
3267
|
|
|
3264
3268
|
// src/attachments.ts
|
|
3265
3269
|
import { mkdir as mkdir2, rm as rm2, writeFile } from "fs/promises";
|
|
3266
|
-
import { join as
|
|
3270
|
+
import { join as join18 } from "path";
|
|
3267
3271
|
function attachmentsFromDoc(doc, origin) {
|
|
3268
3272
|
const out = [];
|
|
3269
3273
|
const walk2 = (node) => {
|
|
@@ -3354,7 +3358,7 @@ async function materializeAttachments(opts) {
|
|
|
3354
3358
|
};
|
|
3355
3359
|
const result = { files: [], skipped: [] };
|
|
3356
3360
|
if (opts.refs.length === 0) return result;
|
|
3357
|
-
const dir =
|
|
3361
|
+
const dir = join18(opts.worktree, ATTACHMENTS_DIR);
|
|
3358
3362
|
await io.removeDir(dir);
|
|
3359
3363
|
let dirReady = false;
|
|
3360
3364
|
let total = 0;
|
|
@@ -3400,7 +3404,7 @@ async function materializeAttachments(opts) {
|
|
|
3400
3404
|
await io.mkdir(dir);
|
|
3401
3405
|
dirReady = true;
|
|
3402
3406
|
}
|
|
3403
|
-
await io.writeFile(
|
|
3407
|
+
await io.writeFile(join18(dir, name), file.bytes);
|
|
3404
3408
|
} catch (e) {
|
|
3405
3409
|
result.skipped.push(`${label} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText(e)})`);
|
|
3406
3410
|
continue;
|
|
@@ -3553,7 +3557,7 @@ var init_attachments = __esm({
|
|
|
3553
3557
|
|
|
3554
3558
|
// src/shotUpload.ts
|
|
3555
3559
|
import { readFile as fsReadFile } from "fs/promises";
|
|
3556
|
-
import { basename as basename2, isAbsolute as isAbsolute3, join as
|
|
3560
|
+
import { basename as basename2, isAbsolute as isAbsolute3, join as join19 } from "path";
|
|
3557
3561
|
function shotMimetype(path) {
|
|
3558
3562
|
const dot = path.lastIndexOf(".");
|
|
3559
3563
|
if (dot < 0) return null;
|
|
@@ -3579,7 +3583,7 @@ async function readShots(paths, opts) {
|
|
|
3579
3583
|
}
|
|
3580
3584
|
let bytes;
|
|
3581
3585
|
try {
|
|
3582
|
-
bytes = await read(isAbsolute3(path) ? path :
|
|
3586
|
+
bytes = await read(isAbsolute3(path) ? path : join19(opts.worktree, path));
|
|
3583
3587
|
} catch (e) {
|
|
3584
3588
|
skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
|
|
3585
3589
|
continue;
|
|
@@ -3656,12 +3660,12 @@ var init_shotUpload = __esm({
|
|
|
3656
3660
|
});
|
|
3657
3661
|
|
|
3658
3662
|
// src/oauth.ts
|
|
3659
|
-
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
3663
|
+
import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
|
|
3660
3664
|
import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3 } from "fs";
|
|
3661
|
-
import { join as
|
|
3665
|
+
import { join as join20 } from "path";
|
|
3662
3666
|
function makePkce() {
|
|
3663
3667
|
const verifier = randomBytes2(48).toString("base64url");
|
|
3664
|
-
const challenge =
|
|
3668
|
+
const challenge = createHash2("sha256").update(verifier).digest("base64url");
|
|
3665
3669
|
return { verifier, challenge };
|
|
3666
3670
|
}
|
|
3667
3671
|
async function registerClient(args, fetchFn = fetch) {
|
|
@@ -3793,15 +3797,15 @@ var init_oauth = __esm({
|
|
|
3793
3797
|
AUTHORIZE = "/api/oauth/authorize";
|
|
3794
3798
|
TOKEN = "/api/oauth/token";
|
|
3795
3799
|
SCOPE = "openid profile email taskless:full";
|
|
3796
|
-
STORE_PATH =
|
|
3797
|
-
CLIENT_PATH =
|
|
3800
|
+
STORE_PATH = join20(SWARM_HOME, "oauth.json");
|
|
3801
|
+
CLIENT_PATH = join20(SWARM_HOME, "oauth-client.json");
|
|
3798
3802
|
base = (api) => api.replace(/\/$/, "");
|
|
3799
3803
|
}
|
|
3800
3804
|
});
|
|
3801
3805
|
|
|
3802
3806
|
// src/agentToken.ts
|
|
3803
3807
|
import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4 } from "fs";
|
|
3804
|
-
import { join as
|
|
3808
|
+
import { join as join21 } from "path";
|
|
3805
3809
|
function loadAgentToken() {
|
|
3806
3810
|
try {
|
|
3807
3811
|
const s = JSON.parse(readFileSync15(STORE_PATH2, "utf8"));
|
|
@@ -3821,6 +3825,14 @@ async function probeAgentToken(args, fetchFn = fetch) {
|
|
|
3821
3825
|
return "unreachable";
|
|
3822
3826
|
}
|
|
3823
3827
|
}
|
|
3828
|
+
function isRevokedTokenError(e) {
|
|
3829
|
+
if (e && typeof e === "object" && e.code === 401) return true;
|
|
3830
|
+
const text = e instanceof Error ? e.message : typeof e === "string" ? e : "";
|
|
3831
|
+
return /invalid_token|(?:http|status|code)[ =:]*401\b|\(401\)|401 unauthorized/i.test(text);
|
|
3832
|
+
}
|
|
3833
|
+
function revokedTokenLine() {
|
|
3834
|
+
return "[taskless] \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u043E\u0437\u0432\u0430\u043D (401 invalid_token) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0443. \u041F\u043E\u0447\u0438\u043D\u0438 \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: swarm login";
|
|
3835
|
+
}
|
|
3824
3836
|
function revokedTokenComplaint() {
|
|
3825
3837
|
return [
|
|
3826
3838
|
"",
|
|
@@ -3841,14 +3853,14 @@ var init_agentToken = __esm({
|
|
|
3841
3853
|
"src/agentToken.ts"() {
|
|
3842
3854
|
"use strict";
|
|
3843
3855
|
init_config();
|
|
3844
|
-
STORE_PATH2 =
|
|
3856
|
+
STORE_PATH2 = join21(SWARM_HOME, "agent.json");
|
|
3845
3857
|
base2 = (api) => api.replace(/\/$/, "");
|
|
3846
3858
|
}
|
|
3847
3859
|
});
|
|
3848
3860
|
|
|
3849
3861
|
// src/workspace.ts
|
|
3850
3862
|
import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync5 } from "fs";
|
|
3851
|
-
import { join as
|
|
3863
|
+
import { join as join22 } from "path";
|
|
3852
3864
|
function loadWorkspace() {
|
|
3853
3865
|
try {
|
|
3854
3866
|
const w = JSON.parse(readFileSync16(WORKSPACE_PATH, "utf8"));
|
|
@@ -3865,7 +3877,7 @@ var init_workspace = __esm({
|
|
|
3865
3877
|
"src/workspace.ts"() {
|
|
3866
3878
|
"use strict";
|
|
3867
3879
|
init_config();
|
|
3868
|
-
WORKSPACE_PATH =
|
|
3880
|
+
WORKSPACE_PATH = join22(SWARM_HOME, "workspace.json");
|
|
3869
3881
|
}
|
|
3870
3882
|
});
|
|
3871
3883
|
|
|
@@ -4220,7 +4232,9 @@ async function connectTaskless(cfg) {
|
|
|
4220
4232
|
try {
|
|
4221
4233
|
await client.connect(transport, { timeout: TASKLESS_CONNECT_TIMEOUT_MS });
|
|
4222
4234
|
} catch (e) {
|
|
4223
|
-
console.warn(
|
|
4235
|
+
console.warn(
|
|
4236
|
+
isRevokedTokenError(e) ? `${revokedTokenLine()} (\u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442 \u2014 slug-only \u0440\u0435\u0436\u0438\u043C)` : `Taskless: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${e instanceof Error ? e.message : e}) \u2192 slug-only. \u0412\u043E\u0439\u0434\u0438 \u0437\u0430\u043D\u043E\u0432\u043E.`
|
|
4237
|
+
);
|
|
4224
4238
|
return null;
|
|
4225
4239
|
}
|
|
4226
4240
|
const call = async (name, args) => await client.callTool({ name, arguments: args });
|
|
@@ -4244,7 +4258,9 @@ async function connectSwarmMcp(cfg) {
|
|
|
4244
4258
|
try {
|
|
4245
4259
|
await client.connect(transport);
|
|
4246
4260
|
} catch (e) {
|
|
4247
|
-
console.warn(
|
|
4261
|
+
console.warn(
|
|
4262
|
+
isRevokedTokenError(e) ? `${revokedTokenLine()} (\u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442 \u2014 \u0441\u043E\u0431\u044B\u0442\u0438\u044F \u043A\u043E\u043F\u044F\u0442\u0441\u044F \u0432 outbox)` : `Swarm MCP: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${e instanceof Error ? e.message : e}) \u2192 \u0431\u0443\u0444\u0435\u0440 \u0432 outbox.`
|
|
4263
|
+
);
|
|
4248
4264
|
return null;
|
|
4249
4265
|
}
|
|
4250
4266
|
return async (name, args) => await client.callTool({ name, arguments: swarmToolArgs(name, args, workspace) });
|
|
@@ -5169,7 +5185,7 @@ var init_droneStore = __esm({
|
|
|
5169
5185
|
// src/overmind.ts
|
|
5170
5186
|
import { homedir as homedir5 } from "os";
|
|
5171
5187
|
import { existsSync as existsSync16, readFileSync as readFileSync20, readdirSync as readdirSync12, renameSync as renameSync10 } from "fs";
|
|
5172
|
-
import { basename as basename4, dirname as
|
|
5188
|
+
import { basename as basename4, dirname as dirname4, join as join29 } from "path";
|
|
5173
5189
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
5174
5190
|
import { spawn as spawn3 } from "child_process";
|
|
5175
5191
|
|
|
@@ -8231,19 +8247,89 @@ init_git();
|
|
|
8231
8247
|
|
|
8232
8248
|
// src/deps.ts
|
|
8233
8249
|
import { existsSync as existsSync10, readdirSync as readdirSync11, readFileSync as readFileSync12 } from "fs";
|
|
8234
|
-
import { join as
|
|
8250
|
+
import { join as join17 } from "path";
|
|
8235
8251
|
import { execa } from "execa";
|
|
8252
|
+
|
|
8253
|
+
// src/prepareLock.ts
|
|
8254
|
+
import { createHash } from "crypto";
|
|
8255
|
+
import { closeSync as closeSync3, openSync as openSync3, rmSync as rmSync2, statSync as statSync8, writeSync } from "fs";
|
|
8256
|
+
import { join as join16 } from "path";
|
|
8257
|
+
import { tmpdir } from "os";
|
|
8258
|
+
var WAIT_TIMEOUT_MS = 5 * 6e4;
|
|
8259
|
+
var POLL_MS = 300;
|
|
8260
|
+
var STALE_MS = 20 * 6e4;
|
|
8261
|
+
function machineLockPath(key, dir = tmpdir()) {
|
|
8262
|
+
return join16(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
|
|
8263
|
+
}
|
|
8264
|
+
function defaultTryCreate(path, content) {
|
|
8265
|
+
try {
|
|
8266
|
+
const fd = openSync3(path, "wx");
|
|
8267
|
+
try {
|
|
8268
|
+
writeSync(fd, content);
|
|
8269
|
+
} finally {
|
|
8270
|
+
closeSync3(fd);
|
|
8271
|
+
}
|
|
8272
|
+
return true;
|
|
8273
|
+
} catch {
|
|
8274
|
+
return false;
|
|
8275
|
+
}
|
|
8276
|
+
}
|
|
8277
|
+
function defaultAgeMs(path) {
|
|
8278
|
+
try {
|
|
8279
|
+
return Date.now() - statSync8(path).mtimeMs;
|
|
8280
|
+
} catch {
|
|
8281
|
+
return null;
|
|
8282
|
+
}
|
|
8283
|
+
}
|
|
8284
|
+
async function withMachineLock(key, fn, io = {}) {
|
|
8285
|
+
const tryCreate = io.tryCreate ?? defaultTryCreate;
|
|
8286
|
+
const remove = io.remove ?? ((p) => {
|
|
8287
|
+
try {
|
|
8288
|
+
rmSync2(p, { force: true });
|
|
8289
|
+
} catch {
|
|
8290
|
+
}
|
|
8291
|
+
});
|
|
8292
|
+
const ageMs2 = io.ageMs ?? defaultAgeMs;
|
|
8293
|
+
const wait = io.wait ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
8294
|
+
const now = io.now ?? Date.now;
|
|
8295
|
+
const path = machineLockPath(key, io.dir ?? tmpdir());
|
|
8296
|
+
const deadline = now() + WAIT_TIMEOUT_MS;
|
|
8297
|
+
let locked = false;
|
|
8298
|
+
for (; ; ) {
|
|
8299
|
+
if (tryCreate(path, `${process.pid}
|
|
8300
|
+
`)) {
|
|
8301
|
+
locked = true;
|
|
8302
|
+
break;
|
|
8303
|
+
}
|
|
8304
|
+
const age = ageMs2(path);
|
|
8305
|
+
if (age !== null && age > STALE_MS) remove(path);
|
|
8306
|
+
if (now() >= deadline) break;
|
|
8307
|
+
await wait(POLL_MS);
|
|
8308
|
+
}
|
|
8309
|
+
try {
|
|
8310
|
+
return await fn(locked);
|
|
8311
|
+
} finally {
|
|
8312
|
+
if (locked) remove(path);
|
|
8313
|
+
}
|
|
8314
|
+
}
|
|
8315
|
+
|
|
8316
|
+
// src/deps.ts
|
|
8236
8317
|
var PREPARE_TIMEOUT_MS = 15 * 6e4;
|
|
8237
8318
|
var MAX_PLAN_COMMANDS = 12;
|
|
8238
8319
|
async function runCommand(cmd, worktree) {
|
|
8239
|
-
const
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8320
|
+
const exec = async () => {
|
|
8321
|
+
const res = await execa(cmd.run, {
|
|
8322
|
+
cwd: cmd.cwd ?? worktree,
|
|
8323
|
+
shell: true,
|
|
8324
|
+
// команда — строка целиком (у настройки/CI иначе никак), поэтому именно шелл
|
|
8325
|
+
reject: false,
|
|
8326
|
+
timeout: PREPARE_TIMEOUT_MS,
|
|
8327
|
+
...cmd.env ? { env: cmd.env } : {}
|
|
8328
|
+
// execa мержит с окружением процесса, не заменяет его
|
|
8329
|
+
});
|
|
8330
|
+
return res.exitCode === 0;
|
|
8331
|
+
};
|
|
8332
|
+
return cmd.lock ? withMachineLock(cmd.lock, exec) : exec();
|
|
8247
8333
|
}
|
|
8248
8334
|
function safeReadText(p) {
|
|
8249
8335
|
try {
|
|
@@ -8289,19 +8375,19 @@ function overridePlan(override) {
|
|
|
8289
8375
|
function detectPackageManager(worktree, io = {}) {
|
|
8290
8376
|
const exists = io.exists ?? existsSync10;
|
|
8291
8377
|
const readText = io.readText ?? safeReadText;
|
|
8292
|
-
const hasPkgJson = exists(
|
|
8378
|
+
const hasPkgJson = exists(join17(worktree, "package.json"));
|
|
8293
8379
|
if (!hasPkgJson) {
|
|
8294
|
-
if (exists(
|
|
8380
|
+
if (exists(join17(worktree, "deno.json")) || exists(join17(worktree, "deno.jsonc")) || exists(join17(worktree, "deno.lock"))) return "deno";
|
|
8295
8381
|
return null;
|
|
8296
8382
|
}
|
|
8297
|
-
const pkg = readJson(readText,
|
|
8383
|
+
const pkg = readJson(readText, join17(worktree, "package.json"));
|
|
8298
8384
|
const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
|
|
8299
8385
|
if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
|
|
8300
|
-
if (exists(
|
|
8301
|
-
if (exists(
|
|
8302
|
-
if (exists(
|
|
8303
|
-
if (exists(
|
|
8304
|
-
if (exists(
|
|
8386
|
+
if (exists(join17(worktree, "bun.lock")) || exists(join17(worktree, "bun.lockb"))) return "bun";
|
|
8387
|
+
if (exists(join17(worktree, "pnpm-lock.yaml"))) return "pnpm";
|
|
8388
|
+
if (exists(join17(worktree, "yarn.lock"))) return "yarn";
|
|
8389
|
+
if (exists(join17(worktree, "package-lock.json"))) return "npm";
|
|
8390
|
+
if (exists(join17(worktree, "deno.lock"))) return "deno";
|
|
8305
8391
|
return "npm";
|
|
8306
8392
|
}
|
|
8307
8393
|
function jsInstallCommand(pm) {
|
|
@@ -8362,20 +8448,21 @@ function detectPlan(worktree, io) {
|
|
|
8362
8448
|
// node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
|
|
8363
8449
|
// У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
|
|
8364
8450
|
// заново, но он дешёвый: тянет из того же общего кэша.
|
|
8365
|
-
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists:
|
|
8451
|
+
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join17(worktree, "node_modules"), source: "detect" }
|
|
8366
8452
|
];
|
|
8367
8453
|
for (const target of workspaceBuildTargets(worktree, io)) {
|
|
8368
8454
|
commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
|
|
8369
8455
|
}
|
|
8456
|
+
commands.push(...migrateCommands(worktree, pm, io));
|
|
8370
8457
|
return { source: "detect", ecosystem: `node/${pm}`, commands };
|
|
8371
8458
|
}
|
|
8372
8459
|
for (const marker of ECOSYSTEM_MARKERS) {
|
|
8373
|
-
const hit = marker.files.some((f) => exists(
|
|
8460
|
+
const hit = marker.files.some((f) => exists(join17(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
|
|
8374
8461
|
if (!hit) continue;
|
|
8375
8462
|
let ecosystem = marker.ecosystem;
|
|
8376
8463
|
let command = marker.command;
|
|
8377
8464
|
for (const refine of marker.refine ?? []) {
|
|
8378
|
-
if (!exists(
|
|
8465
|
+
if (!exists(join17(worktree, refine.file))) continue;
|
|
8379
8466
|
ecosystem = refine.ecosystem;
|
|
8380
8467
|
command = refine.command;
|
|
8381
8468
|
break;
|
|
@@ -8432,12 +8519,12 @@ var CI_INSTALL_PATTERNS = [
|
|
|
8432
8519
|
function ciWorkflowPlan(worktree, io) {
|
|
8433
8520
|
const readText = io.readText ?? safeReadText;
|
|
8434
8521
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
8435
|
-
const dir =
|
|
8522
|
+
const dir = join17(worktree, ".github", "workflows");
|
|
8436
8523
|
const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
8437
8524
|
const seen = /* @__PURE__ */ new Set();
|
|
8438
8525
|
const commands = [];
|
|
8439
8526
|
for (const name of files.sort()) {
|
|
8440
|
-
const yaml = readText(
|
|
8527
|
+
const yaml = readText(join17(dir, name));
|
|
8441
8528
|
if (yaml === null) continue;
|
|
8442
8529
|
for (const raw of workflowRunLines(yaml)) {
|
|
8443
8530
|
const line = raw.replace(/\s+/g, " ").trim();
|
|
@@ -8452,7 +8539,7 @@ function ciWorkflowPlan(worktree, io) {
|
|
|
8452
8539
|
return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
|
|
8453
8540
|
}
|
|
8454
8541
|
function workspaceGlobs(worktree, readText) {
|
|
8455
|
-
const yaml = readText(
|
|
8542
|
+
const yaml = readText(join17(worktree, "pnpm-workspace.yaml"));
|
|
8456
8543
|
if (yaml !== null) {
|
|
8457
8544
|
const globs = [];
|
|
8458
8545
|
let inPackages = false;
|
|
@@ -8472,7 +8559,7 @@ function workspaceGlobs(worktree, readText) {
|
|
|
8472
8559
|
}
|
|
8473
8560
|
if (globs.length) return globs;
|
|
8474
8561
|
}
|
|
8475
|
-
const pkg = readJson(readText,
|
|
8562
|
+
const pkg = readJson(readText, join17(worktree, "package.json"));
|
|
8476
8563
|
const ws = pkg?.workspaces;
|
|
8477
8564
|
const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
8478
8565
|
return list.filter((g) => typeof g === "string");
|
|
@@ -8483,14 +8570,14 @@ function workspaceDirs(worktree, globs, io) {
|
|
|
8483
8570
|
if (glob.startsWith("!")) continue;
|
|
8484
8571
|
const star = glob.indexOf("*");
|
|
8485
8572
|
if (star === -1) {
|
|
8486
|
-
const dir =
|
|
8487
|
-
if (io.exists(
|
|
8573
|
+
const dir = join17(worktree, glob);
|
|
8574
|
+
if (io.exists(join17(dir, "package.json"))) dirs.add(dir);
|
|
8488
8575
|
continue;
|
|
8489
8576
|
}
|
|
8490
|
-
const base3 =
|
|
8577
|
+
const base3 = join17(worktree, glob.slice(0, star).replace(/\/$/, ""));
|
|
8491
8578
|
for (const name of io.listDirs(base3)) {
|
|
8492
|
-
const dir =
|
|
8493
|
-
if (io.exists(
|
|
8579
|
+
const dir = join17(base3, name);
|
|
8580
|
+
if (io.exists(join17(dir, "package.json"))) dirs.add(dir);
|
|
8494
8581
|
}
|
|
8495
8582
|
}
|
|
8496
8583
|
return [...dirs];
|
|
@@ -8518,11 +8605,11 @@ function workspaceBuildTargets(worktree, io = {}) {
|
|
|
8518
8605
|
if (!globs.length) return [];
|
|
8519
8606
|
const pkgs = [];
|
|
8520
8607
|
for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
|
|
8521
|
-
const pkg = readJson(readText,
|
|
8608
|
+
const pkg = readJson(readText, join17(dir, "package.json"));
|
|
8522
8609
|
if (!pkg || typeof pkg.name !== "string") continue;
|
|
8523
8610
|
const scripts = pkg.scripts ?? {};
|
|
8524
8611
|
const entries = entryPaths(pkg);
|
|
8525
|
-
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(
|
|
8612
|
+
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join17(dir, e)));
|
|
8526
8613
|
const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
|
|
8527
8614
|
pkgs.push({ name: pkg.name, dir, deps, needsBuild });
|
|
8528
8615
|
}
|
|
@@ -8544,6 +8631,59 @@ function workspaceBuildTargets(worktree, io = {}) {
|
|
|
8544
8631
|
for (const p of targets.values()) visit(p);
|
|
8545
8632
|
return ordered;
|
|
8546
8633
|
}
|
|
8634
|
+
var LOCAL_DB_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]", "host.docker.internal"]);
|
|
8635
|
+
function devDatabaseUrl(worktree, io = {}) {
|
|
8636
|
+
const readText = io.readText ?? safeReadText;
|
|
8637
|
+
const fromEnv = (io.env ?? process.env).DATABASE_URL;
|
|
8638
|
+
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
8639
|
+
for (const file of [".env", ".env.example"]) {
|
|
8640
|
+
const raw = readText(join17(worktree, file));
|
|
8641
|
+
if (raw === null) continue;
|
|
8642
|
+
const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
|
|
8643
|
+
const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
|
|
8644
|
+
if (value) return value;
|
|
8645
|
+
}
|
|
8646
|
+
return null;
|
|
8647
|
+
}
|
|
8648
|
+
function isLocalDatabase(url) {
|
|
8649
|
+
try {
|
|
8650
|
+
return LOCAL_DB_HOSTS.has(new URL(url).hostname);
|
|
8651
|
+
} catch {
|
|
8652
|
+
return false;
|
|
8653
|
+
}
|
|
8654
|
+
}
|
|
8655
|
+
function prismaPackages(worktree, io) {
|
|
8656
|
+
const exists = io.exists ?? existsSync10;
|
|
8657
|
+
const readText = io.readText ?? safeReadText;
|
|
8658
|
+
const listDirs = io.listDirs ?? safeListDirs;
|
|
8659
|
+
const dirs = [worktree, ...workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs })];
|
|
8660
|
+
return dirs.filter((dir) => exists(join17(dir, "prisma", "schema.prisma")) && listDirs(join17(dir, "prisma", "migrations")).length > 0);
|
|
8661
|
+
}
|
|
8662
|
+
function jsExecBin(pm, command) {
|
|
8663
|
+
switch (pm) {
|
|
8664
|
+
case "npm":
|
|
8665
|
+
return `npm exec -- ${command}`;
|
|
8666
|
+
case "bun":
|
|
8667
|
+
return `bun x ${command}`;
|
|
8668
|
+
default:
|
|
8669
|
+
return `${pm} exec ${command}`;
|
|
8670
|
+
}
|
|
8671
|
+
}
|
|
8672
|
+
function migrateCommands(worktree, pm, io) {
|
|
8673
|
+
if (pm === "deno") return [];
|
|
8674
|
+
const url = devDatabaseUrl(worktree, io);
|
|
8675
|
+
if (!url || !isLocalDatabase(url)) return [];
|
|
8676
|
+
return prismaPackages(worktree, io).map((dir) => ({
|
|
8677
|
+
label: `\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0411\u0414${dir === worktree ? "" : ` (${dir.slice(worktree.length + 1)})`}`,
|
|
8678
|
+
run: jsExecBin(pm, "prisma migrate deploy"),
|
|
8679
|
+
cwd: dir,
|
|
8680
|
+
env: { DATABASE_URL: url },
|
|
8681
|
+
// Ключ мьютекса — сама база: дроны разных репозиториев с разными базами друг друга не ждут.
|
|
8682
|
+
lock: `migrate:${url}`,
|
|
8683
|
+
optional: true,
|
|
8684
|
+
source: "detect"
|
|
8685
|
+
}));
|
|
8686
|
+
}
|
|
8547
8687
|
function planPrepare(worktree, io = {}) {
|
|
8548
8688
|
const plan = overridePlan(io.override) ?? detectPlan(worktree, io) ?? ciWorkflowPlan(worktree, io);
|
|
8549
8689
|
if (!plan) return { source: "none", ecosystem: null, commands: [] };
|
|
@@ -8561,7 +8701,8 @@ async function ensureWorktreeDeps(worktree, io = {}) {
|
|
|
8561
8701
|
ran: [],
|
|
8562
8702
|
failed: [],
|
|
8563
8703
|
skipped: [],
|
|
8564
|
-
blocked: []
|
|
8704
|
+
blocked: [],
|
|
8705
|
+
warned: []
|
|
8565
8706
|
};
|
|
8566
8707
|
for (const cmd of plan.commands) {
|
|
8567
8708
|
if (cmd.skipIfExists && exists(cmd.skipIfExists)) {
|
|
@@ -8576,6 +8717,8 @@ async function ensureWorktreeDeps(worktree, io = {}) {
|
|
|
8576
8717
|
if (await exec(cmd)) {
|
|
8577
8718
|
report.ran.push(cmd.label);
|
|
8578
8719
|
report.outcome = "prepared";
|
|
8720
|
+
} else if (cmd.optional) {
|
|
8721
|
+
report.warned.push(cmd.label);
|
|
8579
8722
|
} else {
|
|
8580
8723
|
report.failed.push(cmd.label);
|
|
8581
8724
|
report.outcome = "failed";
|
|
@@ -8592,13 +8735,17 @@ function depsEvents(report) {
|
|
|
8592
8735
|
events.push(
|
|
8593
8736
|
`\u26A0 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 worktree${where} \u0423\u041F\u0410\u041B\u0410 \u043D\u0430 \xAB${report.failed.join(", ")}\xBB \u2014 \u0434\u0440\u043E\u043D \u0441\u0442\u0430\u0440\u0442\u0443\u0435\u0442 \u0411\u0415\u0417 \u0433\u043E\u0442\u043E\u0432\u043E\u0433\u043E \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F: pre-commit \u0445\u0443\u043A \u043C\u043E\u0436\u0435\u0442 \u043C\u043E\u043B\u0447\u0430 \u043D\u0435 \u0441\u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C, typecheck/\u0442\u0435\u0441\u0442\u044B \u0431\u0443\u0434\u0443\u0442 \u043A\u0440\u0430\u0441\u043D\u044B\u043C\u0438 \u043D\u0435 \u043F\u043E \u0434\u0435\u043B\u0443 (TASWM-312)`
|
|
8594
8737
|
);
|
|
8738
|
+
if (report.warned.length)
|
|
8739
|
+
events.push(
|
|
8740
|
+
`\u26A0 \u0448\u0430\u0433 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0438 \xAB${report.warned.join(", ")}\xBB \u043D\u0435 \u043F\u0440\u043E\u0448\u0451\u043B \u2014 worktree \u0433\u043E\u0442\u043E\u0432, \u043D\u043E \u0411\u0410\u0417\u0410 \u041C\u041E\u0413\u041B\u0410 \u041E\u0421\u0422\u0410\u0422\u042C\u0421\u042F \u041D\u0410 \u0421\u0422\u0410\u0420\u041E\u0419 \u0421\u0425\u0415\u041C\u0415 (\u0447\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043E \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u0430\u044F \u0431\u0430\u0437\u0430 \u043F\u0440\u043E\u0441\u0442\u043E \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442\u0430) \u2014 TASWM-362`
|
|
8741
|
+
);
|
|
8595
8742
|
if (report.blocked.length)
|
|
8596
8743
|
events.push(`\u26A0 \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0438 \u043D\u0435 \u043F\u0443\u0449\u0435\u043D\u0430 \u0433\u0435\u0439\u0442\u043E\u043C \u0434\u043E\u0432\u0435\u0440\u0438\u044F: ${report.blocked.join(", ")} \u2014 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043D\u0435 \u0433\u043E\u0442\u043E\u0432\u0438\u043B\u0438 (TASWM-312)`);
|
|
8597
8744
|
if (report.outcome === "not-detected")
|
|
8598
8745
|
events.push(
|
|
8599
8746
|
`\u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u0430: \u043F\u0440\u043E\u0435\u043A\u0442 \u043D\u0435 \u043E\u043F\u043E\u0437\u043D\u0430\u043D (\u043D\u0435\u0442 \u0437\u043D\u0430\u043A\u043E\u043C\u044B\u0445 \u043C\u0430\u0440\u043A\u0435\u0440\u043D\u044B\u0445 \u0444\u0430\u0439\u043B\u043E\u0432, \u0432 .github/workflows \u043A\u043E\u043C\u0430\u043D\u0434 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438 \u043D\u0435 \u043D\u0430\u0448\u043B\u0438) \u2014 \u0434\u0435\u043C\u043E\u043D \u041D\u0418\u0427\u0415\u0413\u041E \u043D\u0435 \u0441\u0442\u0430\u0432\u0438\u043B; \u0437\u0430\u0434\u0430\u0442\u044C \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u043C\u043E\u0436\u043D\u043E \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (swarmConfig.prepare) \u2014 TASWM-312`
|
|
8600
8747
|
);
|
|
8601
|
-
if (report.outcome === "nothing-to-do" && !report.skipped.length) {
|
|
8748
|
+
if (report.outcome === "nothing-to-do" && !report.skipped.length && !report.warned.length) {
|
|
8602
8749
|
if (report.source === "project-config") events.push(`\u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 \u043E\u0442\u043A\u043B\u044E\u0447\u0435\u043D\u0430 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u043E\u0439 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (swarmConfig.prepare \u043F\u0443\u0441\u0442) \u2014 TASWM-312`);
|
|
8603
8750
|
else if (report.ecosystem) events.push(`\u043F\u0440\u043E\u0435\u043A\u0442${where}: \u0441\u0442\u0430\u0432\u0438\u0442\u044C \u043F\u0435\u0440\u0435\u0434 \u0441\u0442\u0430\u0440\u0442\u043E\u043C \u043D\u0435\u0447\u0435\u0433\u043E, \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F \u2014 TASWM-312`);
|
|
8604
8751
|
}
|
|
@@ -8617,6 +8764,9 @@ function depsPromptNotice(report) {
|
|
|
8617
8764
|
if (problems.length) {
|
|
8618
8765
|
return `\u26A0 \u041E\u041A\u0420\u0423\u0416\u0415\u041D\u0418\u0415 WORKTREE \u041D\u0415 \u041F\u041E\u0414\u0413\u041E\u0422\u041E\u0412\u041B\u0415\u041D\u041E (TASWM-312): ${problems.join("; ")}. \u041F\u043E\u043A\u0430 \u044D\u0442\u043E \u0442\u0430\u043A, \u0413\u0415\u0419\u0422 \u041C\u041E\u0416\u0415\u0422 \u041D\u0415 \u0420\u0410\u0411\u041E\u0422\u0410\u0422\u042C: pre-commit \u0445\u0443\u043A \u043C\u043E\u043B\u0447\u0430 \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0441\u044F (\u043F\u0435\u0440\u0432\u044B\u0439 \u043A\u043E\u043C\u043C\u0438\u0442 \u0443\u0439\u0434\u0451\u0442 \u0431\u0435\u0437 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A), \u0430 typecheck/\u0442\u0435\u0441\u0442\u044B \u0431\u0443\u0434\u0443\u0442 \u043A\u0440\u0430\u0441\u043D\u044B\u043C\u0438 \u043D\u0435 \u043F\u043E \u0434\u0435\u043B\u0443. \u041F\u0435\u0440\u0432\u044B\u043C \u0434\u0435\u043B\u043E\u043C \u043F\u043E\u0447\u0438\u043D\u0438 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0441\u0430\u043C (\u043F\u043E\u0441\u0442\u0430\u0432\u044C \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 \u0442\u0430\u043A, \u043A\u0430\u043A \u044D\u0442\u043E \u0434\u0435\u043B\u0430\u0435\u0442 README/CI \u043F\u0440\u043E\u0435\u043A\u0442\u0430) \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0442\u043E\u043C \u0432\u0435\u0440\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A; \u043D\u0435 \u0441\u0434\u0430\u0432\u0430\u0439 \u0440\u0430\u0431\u043E\u0442\u0443, \u043E\u0431\u044A\u044F\u0441\u043D\u044F\u044F \u043F\u0430\u0434\u0435\u043D\u0438\u044F \xAB\u0441\u043B\u043E\u043C\u0430\u043D\u043D\u044B\u043C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435\u043C\xBB, \u043D\u0435 \u043F\u043E\u043F\u0440\u043E\u0431\u043E\u0432\u0430\u0432 \u044D\u0442\u043E.`;
|
|
8619
8766
|
}
|
|
8767
|
+
if (report.warned.length) {
|
|
8768
|
+
return `\u2139 \u0411\u0410\u0417\u0410 \u041C\u041E\u0413\u041B\u0410 \u041E\u0421\u0422\u0410\u0422\u042C\u0421\u042F \u041D\u0410 \u0421\u0422\u0410\u0420\u041E\u0419 \u0421\u0425\u0415\u041C\u0415 (TASWM-362): \u0448\u0430\u0433 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0438 \xAB${report.warned.join(", ")}\xBB \u043D\u0435 \u043F\u0440\u043E\u0448\u0451\u043B \u2014 \u043E\u0431\u044B\u0447\u043D\u043E \u043F\u043E\u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u0430\u044F \u0431\u0430\u0437\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442\u0430. \u0421\u0430\u043C worktree \u0433\u043E\u0442\u043E\u0432, typecheck \u0438 \u044E\u043D\u0438\u0442-\u0442\u0435\u0441\u0442\u044B \u0434\u043E\u0441\u0442\u043E\u0432\u0435\u0440\u043D\u044B. \u0415\u0441\u043B\u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0448\u044C \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0438 \u0443\u0432\u0438\u0434\u0438\u0448\u044C \u043E\u0448\u0438\u0431\u043A\u0443 \u043F\u0440\u043E \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0443\u044E \u043A\u043E\u043B\u043E\u043D\u043A\u0443/\u0442\u0430\u0431\u043B\u0438\u0446\u0443 \u2014 \u044D\u0442\u043E \u043E\u043D\u0430: \u043F\u043E\u0434\u043D\u0438\u043C\u0438 \u0431\u0430\u0437\u0443 \u0438 \u043D\u0430\u043A\u0430\u0442\u0438 \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0442\u0430\u043A, \u043A\u0430\u043A \u044D\u0442\u043E \u0434\u0435\u043B\u0430\u0435\u0442 dev-\u0441\u043A\u0440\u0438\u043F\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.`;
|
|
8769
|
+
}
|
|
8620
8770
|
if (report.outcome === "not-detected") {
|
|
8621
8771
|
return `\u2139 \u0414\u0415\u041C\u041E\u041D \u041D\u0415 \u0413\u041E\u0422\u041E\u0412\u0418\u041B \u041E\u041A\u0420\u0423\u0416\u0415\u041D\u0418\u0415 (TASWM-312): \u043F\u0440\u043E\u0435\u043A\u0442 \u043D\u0435 \u043E\u043F\u043E\u0437\u043D\u0430\u043D \u043F\u043E \u043C\u0430\u0440\u043A\u0435\u0440\u043D\u044B\u043C \u0444\u0430\u0439\u043B\u0430\u043C, \u043A\u043E\u043C\u0430\u043D\u0434 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438 \u0432 .github/workflows \u0442\u043E\u0436\u0435 \u043D\u0435\u0442. \u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438, \u0435\u0441\u043B\u0438 \u043E\u043D\u0438 \u043D\u0443\u0436\u043D\u044B, \u043D\u0435 \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u044B \u2014 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 README/CI \u0438 \u043F\u043E\u0441\u0442\u0430\u0432\u044C \u0438\u0445 \u0441\u0430\u043C \u041F\u0415\u0420\u0415\u0414 \u0442\u0435\u043C, \u043A\u0430\u043A \u0432\u0435\u0440\u0438\u0442\u044C typecheck/\u0442\u0435\u0441\u0442\u0430\u043C.`;
|
|
8622
8772
|
}
|
|
@@ -8976,6 +9126,15 @@ function branchFromPrPatch(currentBranch, headBranch) {
|
|
|
8976
9126
|
event: currentBranch ? `\u0432\u0435\u0442\u043A\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0432\u0437\u044F\u0442\u0430 \u0443 PR: ${currentBranch} \u2192 ${headBranch} (\u043F\u043E\u043B\u0435 \u0431\u044B\u043B\u043E \u0441\u043E\u0431\u0440\u0430\u043D\u043E \u0438\u0437 \u0441\u043B\u0430\u0433\u0430 \u2014 TASWM-356)` : `\u0432\u0435\u0442\u043A\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0432\u0437\u044F\u0442\u0430 \u0443 PR: ${headBranch} (TASWM-356)`
|
|
8977
9127
|
};
|
|
8978
9128
|
}
|
|
9129
|
+
function prLiveness(st) {
|
|
9130
|
+
if (!st) return "unknown";
|
|
9131
|
+
if (st.merged) return "merged";
|
|
9132
|
+
const state = (st.state ?? "").toUpperCase();
|
|
9133
|
+
if (state === "MERGED") return "merged";
|
|
9134
|
+
if (state === "CLOSED") return "closed";
|
|
9135
|
+
if (state === "UNKNOWN") return "unknown";
|
|
9136
|
+
return "open";
|
|
9137
|
+
}
|
|
8979
9138
|
function applyReviewTransition(db, run, prUrl) {
|
|
8980
9139
|
const { patch, ...t } = reviewTransitionPatch(run, prUrl);
|
|
8981
9140
|
db.updateRun(run.id, patch);
|
|
@@ -9486,7 +9645,7 @@ async function runDroneServer(runId) {
|
|
|
9486
9645
|
|
|
9487
9646
|
// src/droneBridge.ts
|
|
9488
9647
|
init_src();
|
|
9489
|
-
import { createHash as
|
|
9648
|
+
import { createHash as createHash3 } from "crypto";
|
|
9490
9649
|
|
|
9491
9650
|
// src/anomalies.ts
|
|
9492
9651
|
init_src();
|
|
@@ -9881,7 +10040,7 @@ function questionKey(runId, text) {
|
|
|
9881
10040
|
return `${runId} ${text.trim().replace(/\s+/g, " ")}`;
|
|
9882
10041
|
}
|
|
9883
10042
|
function noteKey(runId, text) {
|
|
9884
|
-
return `note:${runId}:${
|
|
10043
|
+
return `note:${runId}:${createHash3("sha1").update(questionKey(runId, text)).digest("hex").slice(0, 16)}`;
|
|
9885
10044
|
}
|
|
9886
10045
|
async function syncDroneChannel(opts) {
|
|
9887
10046
|
const result = { imported: [], answered: [], notes: [] };
|
|
@@ -10162,20 +10321,20 @@ function makeExclusiveReader(opts) {
|
|
|
10162
10321
|
|
|
10163
10322
|
// src/envGate.ts
|
|
10164
10323
|
import { existsSync as existsSync13 } from "fs";
|
|
10165
|
-
import { join as
|
|
10324
|
+
import { join as join24 } from "path";
|
|
10166
10325
|
import { createConnection } from "net";
|
|
10167
10326
|
import { execa as execa2 } from "execa";
|
|
10168
10327
|
|
|
10169
10328
|
// src/platform.ts
|
|
10170
10329
|
import { existsSync as existsSync12, readFileSync as readFileSync17 } from "fs";
|
|
10171
|
-
import { delimiter, join as
|
|
10330
|
+
import { delimiter, join as join23 } from "path";
|
|
10172
10331
|
function resolveCommandPath(name, env = process.env, platform = process.platform) {
|
|
10173
10332
|
if (name.includes("/") || name.includes("\\")) return existsSync12(name) ? name : null;
|
|
10174
10333
|
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
10175
10334
|
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
10176
10335
|
for (const dir of dirs) {
|
|
10177
10336
|
for (const ext of exts) {
|
|
10178
|
-
const candidate =
|
|
10337
|
+
const candidate = join23(dir, name + ext);
|
|
10179
10338
|
if (existsSync12(candidate)) return candidate;
|
|
10180
10339
|
}
|
|
10181
10340
|
}
|
|
@@ -10184,6 +10343,31 @@ function resolveCommandPath(name, env = process.env, platform = process.platform
|
|
|
10184
10343
|
function hasCommandInPath(name, env = process.env, platform = process.platform) {
|
|
10185
10344
|
return resolveCommandPath(name, env, platform) !== null;
|
|
10186
10345
|
}
|
|
10346
|
+
function detectGlobalPm(entry, io = {}) {
|
|
10347
|
+
const p = (entry ?? "").replace(/\\/g, "/");
|
|
10348
|
+
if (!p) return "unknown";
|
|
10349
|
+
if (p.endsWith(".ts") || /\/packages\/swarm-daemon\/(src|dist)\//.test(p)) return "sources";
|
|
10350
|
+
if (/\/\.bun\/install\/global\//.test(p) || /\/\.bun\/bin\//.test(p)) return "bun";
|
|
10351
|
+
if (/\/pnpm\//.test(p) || /\/\.pnpm-global\//.test(p) || /\/Library\/pnpm\//.test(p)) return "pnpm";
|
|
10352
|
+
if (/\/\.yarn\//.test(p) || /\/Yarn\/Data\/global\//.test(p) || /\/yarn\/global\//.test(p)) return "yarn";
|
|
10353
|
+
if (/\/node_modules\//.test(p)) return "npm";
|
|
10354
|
+
const env = io.env ?? process.env;
|
|
10355
|
+
const platform = io.platform ?? process.platform;
|
|
10356
|
+
const has = io.hasCommand ?? ((n) => hasCommandInPath(n, env, platform));
|
|
10357
|
+
return has("npm") ? "npm" : "unknown";
|
|
10358
|
+
}
|
|
10359
|
+
function globalInstallCommand(pm, spec) {
|
|
10360
|
+
switch (pm) {
|
|
10361
|
+
case "pnpm":
|
|
10362
|
+
return `pnpm add -g ${spec}`;
|
|
10363
|
+
case "bun":
|
|
10364
|
+
return `bun add -g ${spec}`;
|
|
10365
|
+
case "yarn":
|
|
10366
|
+
return `yarn global add ${spec}`;
|
|
10367
|
+
default:
|
|
10368
|
+
return `npm i -g ${spec}`;
|
|
10369
|
+
}
|
|
10370
|
+
}
|
|
10187
10371
|
|
|
10188
10372
|
// src/envGate.ts
|
|
10189
10373
|
var PROBE_TIMEOUT_MS = 2500;
|
|
@@ -10235,7 +10419,7 @@ var COMPOSE_MARKERS = [
|
|
|
10235
10419
|
];
|
|
10236
10420
|
function detectRequirements(repo, io = {}) {
|
|
10237
10421
|
const exists = io.exists ?? existsSync13;
|
|
10238
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
10422
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join24(repo, f)));
|
|
10239
10423
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
10240
10424
|
}
|
|
10241
10425
|
function resolveRequirements(repo, declared, io = {}) {
|
|
@@ -10371,8 +10555,8 @@ import { basename as basename3 } from "path";
|
|
|
10371
10555
|
import { serve as serve2 } from "@hono/node-server";
|
|
10372
10556
|
|
|
10373
10557
|
// src/diff.ts
|
|
10374
|
-
import { statSync as
|
|
10375
|
-
import { join as
|
|
10558
|
+
import { statSync as statSync9 } from "fs";
|
|
10559
|
+
import { join as join25 } from "path";
|
|
10376
10560
|
import { $ as $4 } from "execa";
|
|
10377
10561
|
var MAX_FILES = 200;
|
|
10378
10562
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -10515,7 +10699,7 @@ async function resolveBase(wt, baseBranch) {
|
|
|
10515
10699
|
}
|
|
10516
10700
|
function mtime(path) {
|
|
10517
10701
|
try {
|
|
10518
|
-
return
|
|
10702
|
+
return statSync9(path).mtimeMs;
|
|
10519
10703
|
} catch {
|
|
10520
10704
|
return 0;
|
|
10521
10705
|
}
|
|
@@ -10540,7 +10724,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
10540
10724
|
files.push(pf);
|
|
10541
10725
|
}
|
|
10542
10726
|
}
|
|
10543
|
-
files.sort((a, b) => mtime(
|
|
10727
|
+
files.sort((a, b) => mtime(join25(worktree, b.path)) - mtime(join25(worktree, a.path)));
|
|
10544
10728
|
const truncatedFiles = files.length > MAX_FILES;
|
|
10545
10729
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
10546
10730
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -10573,7 +10757,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
10573
10757
|
// package.json
|
|
10574
10758
|
var package_default = {
|
|
10575
10759
|
name: "@taskless-app/swarm",
|
|
10576
|
-
version: "0.6.
|
|
10760
|
+
version: "0.6.1",
|
|
10577
10761
|
type: "module",
|
|
10578
10762
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
10579
10763
|
keywords: [
|
|
@@ -10622,7 +10806,8 @@ var package_default = {
|
|
|
10622
10806
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
10623
10807
|
execa: "^10.0.0",
|
|
10624
10808
|
zod: "3.25.76"
|
|
10625
|
-
}
|
|
10809
|
+
},
|
|
10810
|
+
gitHead: "415ad928e18430894ae24ac2e4267073f5c6da14"
|
|
10626
10811
|
};
|
|
10627
10812
|
|
|
10628
10813
|
// src/version.ts
|
|
@@ -12201,48 +12386,10 @@ function closeWebServer(server) {
|
|
|
12201
12386
|
});
|
|
12202
12387
|
}
|
|
12203
12388
|
|
|
12204
|
-
// src/daemonVersion.ts
|
|
12205
|
-
init_git();
|
|
12206
|
-
import { dirname as dirname4 } from "path";
|
|
12207
|
-
import { $ as $5 } from "execa";
|
|
12208
|
-
async function captureDaemonStart(entryFile, now = () => /* @__PURE__ */ new Date()) {
|
|
12209
|
-
try {
|
|
12210
|
-
const repo = await repoRoot(dirname4(entryFile));
|
|
12211
|
-
const sha = (await $5`git -C ${repo} rev-parse HEAD`).stdout.trim();
|
|
12212
|
-
if (!sha) return null;
|
|
12213
|
-
return { repo, startedAt: now().toISOString(), startCommit: sha };
|
|
12214
|
-
} catch {
|
|
12215
|
-
return null;
|
|
12216
|
-
}
|
|
12217
|
-
}
|
|
12218
|
-
var DAEMON_CODE_PATHS = ["packages/swarm-daemon", "packages/swarm-core"];
|
|
12219
|
-
async function checkDaemonVersion(start, now = () => /* @__PURE__ */ new Date()) {
|
|
12220
|
-
const base3 = { startedAt: start.startedAt, startCommit: start.startCommit, checkedAt: now().toISOString() };
|
|
12221
|
-
try {
|
|
12222
|
-
await $5({ reject: false })`git -C ${start.repo} fetch --quiet origin`;
|
|
12223
|
-
const mainRef = await remoteMainRef(start.repo);
|
|
12224
|
-
const mainCommit = (await $5`git -C ${start.repo} rev-parse ${mainRef}`).stdout.trim();
|
|
12225
|
-
if (!mainCommit || mainCommit === start.startCommit) {
|
|
12226
|
-
return { ...base3, mainCommit: mainCommit || null, stale: false, staleFiles: [] };
|
|
12227
|
-
}
|
|
12228
|
-
const ancestor = await $5({ reject: false })`git -C ${start.repo} merge-base --is-ancestor ${start.startCommit} ${mainCommit}`;
|
|
12229
|
-
if (ancestor.exitCode !== 0) {
|
|
12230
|
-
return { ...base3, mainCommit, stale: false, staleFiles: [] };
|
|
12231
|
-
}
|
|
12232
|
-
const diff = await $5({ reject: false })(
|
|
12233
|
-
"git",
|
|
12234
|
-
["-C", start.repo, "diff", "--name-only", start.startCommit, mainCommit, "--", ...DAEMON_CODE_PATHS]
|
|
12235
|
-
);
|
|
12236
|
-
const staleFiles = diff.stdout.trim() ? diff.stdout.trim().split("\n") : [];
|
|
12237
|
-
return { ...base3, mainCommit, stale: staleFiles.length > 0, staleFiles: staleFiles.slice(0, 20) };
|
|
12238
|
-
} catch {
|
|
12239
|
-
return { ...base3, mainCommit: null, stale: false, staleFiles: [] };
|
|
12240
|
-
}
|
|
12241
|
-
}
|
|
12242
|
-
|
|
12243
12389
|
// src/release.ts
|
|
12390
|
+
init_src();
|
|
12244
12391
|
init_claudeBin();
|
|
12245
|
-
var SWARM_PACKAGE =
|
|
12392
|
+
var SWARM_PACKAGE = SWARM_PACKAGE_NAME;
|
|
12246
12393
|
var LATEST_TIMEOUT_MS = 4e3;
|
|
12247
12394
|
async function fetchLatestVersion(opts = {}) {
|
|
12248
12395
|
const fetchFn = opts.fetchFn ?? fetch;
|
|
@@ -12273,12 +12420,30 @@ function outdatedLine(current, latest, updateCommand = "swarm update") {
|
|
|
12273
12420
|
return `\u26A0 \u0432\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430: \u0443 \u0442\u0435\u0431\u044F v${current}, \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest} \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: ${updateCommand}`;
|
|
12274
12421
|
}
|
|
12275
12422
|
|
|
12423
|
+
// src/daemonVersion.ts
|
|
12424
|
+
function captureDaemonStart(now = () => /* @__PURE__ */ new Date()) {
|
|
12425
|
+
return { startedAt: now().toISOString(), version: PACKAGE_VERSION };
|
|
12426
|
+
}
|
|
12427
|
+
var DAEMON_CODE_PATHS = ["packages/swarm-daemon", "packages/swarm-core"];
|
|
12428
|
+
async function checkDaemonVersion(start, opts = {}) {
|
|
12429
|
+
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
12430
|
+
const fetchLatest = opts.latest ?? (() => fetchLatestVersion());
|
|
12431
|
+
const latestVersion = await fetchLatest().catch(() => null);
|
|
12432
|
+
return {
|
|
12433
|
+
startedAt: start.startedAt,
|
|
12434
|
+
version: start.version,
|
|
12435
|
+
latestVersion,
|
|
12436
|
+
stale: isOutdated(start.version, latestVersion),
|
|
12437
|
+
checkedAt: now().toISOString()
|
|
12438
|
+
};
|
|
12439
|
+
}
|
|
12440
|
+
|
|
12276
12441
|
// src/hostCommands.ts
|
|
12277
12442
|
init_src();
|
|
12278
12443
|
import { readFileSync as readFileSync18, renameSync as renameSync8, writeFileSync as writeFileSync13 } from "fs";
|
|
12279
|
-
import { join as
|
|
12444
|
+
import { join as join26 } from "path";
|
|
12280
12445
|
function hostCommandStatePath(home) {
|
|
12281
|
-
return
|
|
12446
|
+
return join26(home, "host-command.json");
|
|
12282
12447
|
}
|
|
12283
12448
|
function readState(path) {
|
|
12284
12449
|
try {
|
|
@@ -12421,6 +12586,11 @@ async function executeOne(cmd, opts) {
|
|
|
12421
12586
|
effects.narrate(`\u26A0\uFE0F \u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0448\u0430\u0433\u0435 \xAB${UPDATE_STEP_LABELS[res.step]}\xBB: ${res.message}`);
|
|
12422
12587
|
return false;
|
|
12423
12588
|
}
|
|
12589
|
+
if (res.restart === false) {
|
|
12590
|
+
await ack(cmd.id, "done", { step: res.step, result: res.message });
|
|
12591
|
+
effects.narrate(`\u2705 ${res.message}`);
|
|
12592
|
+
return false;
|
|
12593
|
+
}
|
|
12424
12594
|
return await doRestart(cmd, opts, res.message);
|
|
12425
12595
|
}
|
|
12426
12596
|
}
|
|
@@ -12448,11 +12618,11 @@ async function doRestart(cmd, opts, note) {
|
|
|
12448
12618
|
// src/selfUpdate.ts
|
|
12449
12619
|
init_git();
|
|
12450
12620
|
import { existsSync as existsSync14 } from "fs";
|
|
12451
|
-
import { join as
|
|
12452
|
-
import { $ as $
|
|
12621
|
+
import { join as join27 } from "path";
|
|
12622
|
+
import { $ as $5 } from "execa";
|
|
12453
12623
|
var DAEMON_PACKAGE = "@taskless-app/swarm";
|
|
12454
12624
|
var defaultRun = async (cmd, args, cwd) => {
|
|
12455
|
-
const res = await $
|
|
12625
|
+
const res = await $5({ reject: false, cwd })(cmd, args);
|
|
12456
12626
|
return { exitCode: res.exitCode ?? 1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
|
|
12457
12627
|
};
|
|
12458
12628
|
function tail(text, max = 400) {
|
|
@@ -12490,7 +12660,7 @@ async function runSelfUpdate(io) {
|
|
|
12490
12660
|
};
|
|
12491
12661
|
}
|
|
12492
12662
|
await io.onStep("install", "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438");
|
|
12493
|
-
if (!exists(
|
|
12663
|
+
if (!exists(join27(repo, "pnpm-lock.yaml"))) {
|
|
12494
12664
|
return { ok: false, step: "install", message: "\u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 pnpm-lock.yaml \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u0435\u0433\u043E \u044D\u0442\u0438\u043C \u043F\u0443\u0442\u0451\u043C \u043D\u0435 \u0431\u0435\u0440\u0443\u0441\u044C" };
|
|
12495
12665
|
}
|
|
12496
12666
|
const installed = await run("pnpm", ["install", "--frozen-lockfile"], repo);
|
|
@@ -12509,6 +12679,31 @@ async function runSelfUpdate(io) {
|
|
|
12509
12679
|
function defaultSelfUpdateIO(repo, onStep) {
|
|
12510
12680
|
return { repo, run: defaultRun, mainRef: remoteMainRef, onStep };
|
|
12511
12681
|
}
|
|
12682
|
+
async function runPackageSelfUpdate(io) {
|
|
12683
|
+
await io.onStep("fetch", "\u0441\u043C\u043E\u0442\u0440\u044E, \u0447\u0442\u043E \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043E");
|
|
12684
|
+
const latest = await io.latest();
|
|
12685
|
+
if (!latest) {
|
|
12686
|
+
return { ok: false, step: "fetch", message: "\u0434\u043E registry.npmjs.org \u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043D\u0438\u043B\u0438\u0441\u044C \u2014 \u043A\u0430\u043A\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430, \u0443\u0437\u043D\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C" };
|
|
12687
|
+
}
|
|
12688
|
+
if (!isOutdated(io.currentVersion, latest)) {
|
|
12689
|
+
return { ok: true, restart: false, step: "install", message: `\u0443\u0436\u0435 \u0441\u0432\u0435\u0436\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F: v${io.currentVersion} (\u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest})` };
|
|
12690
|
+
}
|
|
12691
|
+
await io.onStep("install", `\u0441\u0442\u0430\u0432\u043B\u044E v${latest}`);
|
|
12692
|
+
const command = io.installCommand(`${SWARM_PACKAGE}@${latest}`);
|
|
12693
|
+
const code = await io.exec(command);
|
|
12694
|
+
if (code !== 0) {
|
|
12695
|
+
return { ok: false, step: "install", message: `\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430 (\u043A\u043E\u0434 ${code}): ${command} \u2014 \u0432\u044B\u043F\u043E\u043B\u043D\u0438 \u044D\u0442\u0443 \u0441\u0442\u0440\u043E\u043A\u0443 \u0440\u0443\u043A\u0430\u043C\u0438 \u0438 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438, \u043D\u0430 \u0447\u0451\u043C \u0441\u043F\u043E\u0442\u044B\u043A\u0430\u0435\u0442\u0441\u044F` };
|
|
12696
|
+
}
|
|
12697
|
+
return { ok: true, step: "install", message: `\u043E\u0431\u043D\u043E\u0432\u0438\u043B\u0441\u044F \u0434\u043E v${latest} (\u0431\u044B\u043B\u043E v${io.currentVersion})` };
|
|
12698
|
+
}
|
|
12699
|
+
|
|
12700
|
+
// src/cliIo.ts
|
|
12701
|
+
import { createInterface } from "readline/promises";
|
|
12702
|
+
import { execa as execa3 } from "execa";
|
|
12703
|
+
async function execLoud(command) {
|
|
12704
|
+
const res = await execa3(command, { shell: true, reject: false, stdio: "inherit" });
|
|
12705
|
+
return res.exitCode ?? 1;
|
|
12706
|
+
}
|
|
12512
12707
|
|
|
12513
12708
|
// src/narration.ts
|
|
12514
12709
|
var RING_LIMIT = 20;
|
|
@@ -12584,14 +12779,14 @@ async function fetchExternalStorm(url, opts = {}) {
|
|
|
12584
12779
|
|
|
12585
12780
|
// src/anomalyState.ts
|
|
12586
12781
|
import { existsSync as existsSync15, readFileSync as readFileSync19, renameSync as renameSync9, writeFileSync as writeFileSync14 } from "fs";
|
|
12587
|
-
import { join as
|
|
12782
|
+
import { join as join28 } from "path";
|
|
12588
12783
|
var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
12589
12784
|
var MAX_RESOLVED = 200;
|
|
12590
12785
|
function emptyState() {
|
|
12591
12786
|
return { since: {}, lastFiredAt: {}, fires: {}, resolved: [], stopQuiet: null };
|
|
12592
12787
|
}
|
|
12593
12788
|
function anomalyStatePath(home) {
|
|
12594
|
-
return
|
|
12789
|
+
return join28(home, "anomaly-state.json");
|
|
12595
12790
|
}
|
|
12596
12791
|
function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
12597
12792
|
const exists = io.exists ?? existsSync15;
|
|
@@ -12871,7 +13066,7 @@ init_src();
|
|
|
12871
13066
|
init_src();
|
|
12872
13067
|
|
|
12873
13068
|
// src/gitHistory.ts
|
|
12874
|
-
import { $ as $
|
|
13069
|
+
import { $ as $6 } from "execa";
|
|
12875
13070
|
var REC = "";
|
|
12876
13071
|
var FIELD = "";
|
|
12877
13072
|
var LOG_FORMAT = `${REC}%H${FIELD}%s`;
|
|
@@ -12923,7 +13118,7 @@ function groupTaskHistory(commits, opts = {}) {
|
|
|
12923
13118
|
async function readCommitLog(repo, opts = {}) {
|
|
12924
13119
|
const max = opts.maxCommits ?? 1500;
|
|
12925
13120
|
try {
|
|
12926
|
-
const { stdout } = await $
|
|
13121
|
+
const { stdout } = await $6({ cwd: repo, reject: false })`git log --no-merges --name-only --pretty=format:${LOG_FORMAT} -n ${String(max)} ${opts.ref ?? "HEAD"}`;
|
|
12927
13122
|
return parseGitLog(stdout ?? "");
|
|
12928
13123
|
} catch {
|
|
12929
13124
|
return [];
|
|
@@ -12934,7 +13129,7 @@ async function readTaskHistory(repo, opts = {}) {
|
|
|
12934
13129
|
}
|
|
12935
13130
|
async function headSha(repo) {
|
|
12936
13131
|
try {
|
|
12937
|
-
const { stdout, exitCode } = await $
|
|
13132
|
+
const { stdout, exitCode } = await $6({ cwd: repo, reject: false })`git rev-parse HEAD`;
|
|
12938
13133
|
return exitCode === 0 ? stdout.trim() : null;
|
|
12939
13134
|
} catch {
|
|
12940
13135
|
return null;
|
|
@@ -13217,20 +13412,20 @@ function countActiveDrones(db) {
|
|
|
13217
13412
|
function countActiveReviewers(db) {
|
|
13218
13413
|
return db.listRuns().filter((r) => r.kind === "reviewer" && ACTIVE_DRONE_STATUSES.has(r.status)).length;
|
|
13219
13414
|
}
|
|
13220
|
-
async function
|
|
13221
|
-
|
|
13222
|
-
|
|
13223
|
-
|
|
13224
|
-
|
|
13225
|
-
deps.kill(rv.pid);
|
|
13226
|
-
} catch {
|
|
13227
|
-
}
|
|
13415
|
+
async function closeReviewerRun(deps, rv, reason) {
|
|
13416
|
+
if (rv.pid != null && deps.kill && deps.isAlive(rv.pid)) {
|
|
13417
|
+
try {
|
|
13418
|
+
deps.kill(rv.pid);
|
|
13419
|
+
} catch {
|
|
13228
13420
|
}
|
|
13229
|
-
if (rv.worktree_path) await deps.removeWorktree(rv.repo, rv.worktree_path).catch(() => {
|
|
13230
|
-
});
|
|
13231
|
-
deps.db.updateRun(rv.id, { status: "done" });
|
|
13232
|
-
deps.db.addEvent(rv.id, "done", "PR \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u0440\u0430\u043D\u044C\u0448\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E \u2014 \u0440\u0435\u0432\u044C\u044E \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043D\u0443\u0436\u043D\u043E");
|
|
13233
13421
|
}
|
|
13422
|
+
if (rv.worktree_path) await deps.removeWorktree(rv.repo, rv.worktree_path).catch(() => {
|
|
13423
|
+
});
|
|
13424
|
+
deps.db.updateRun(rv.id, { status: "done", stop_reason: null });
|
|
13425
|
+
deps.db.addEvent(rv.id, "done", reason);
|
|
13426
|
+
}
|
|
13427
|
+
async function closeReviewersOnDeadPr(deps, reviewRunId, reason) {
|
|
13428
|
+
for (const rv of deps.db.activeReviewersFor(reviewRunId)) await closeReviewerRun(deps, rv, reason);
|
|
13234
13429
|
}
|
|
13235
13430
|
function ciWaitStartedMs(db, runId) {
|
|
13236
13431
|
const evs = db.events(runId);
|
|
@@ -13304,7 +13499,39 @@ async function finalizeMergedReview(deps, run) {
|
|
|
13304
13499
|
deps.db.addEvent(run.id, "merged", run.pr_url ?? "");
|
|
13305
13500
|
deps.db.updateRun(run.id, { status: "done" });
|
|
13306
13501
|
deps.db.addEvent(run.id, "done");
|
|
13307
|
-
await
|
|
13502
|
+
await closeReviewersOnDeadPr(deps, run.id, "PR \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u0440\u0430\u043D\u044C\u0448\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E \u2014 \u0440\u0435\u0432\u044C\u044E \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043D\u0443\u0436\u043D\u043E");
|
|
13503
|
+
}
|
|
13504
|
+
async function finalizeClosedPrReview(deps, run, reason) {
|
|
13505
|
+
await closeReviewersOnDeadPr(deps, run.id, "PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E");
|
|
13506
|
+
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
13507
|
+
});
|
|
13508
|
+
deps.db.updateRun(run.id, { status: "done" });
|
|
13509
|
+
deps.db.addEvent(run.id, "done", reason);
|
|
13510
|
+
}
|
|
13511
|
+
var REVIEW_PR_UNKNOWN_MARK = "\u0441\u0442\u0430\u0442\u0443\u0441 PR \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D";
|
|
13512
|
+
async function prGateForReviewer(deps, run, prUrl, when) {
|
|
13513
|
+
const st = await deps.prStatus(prUrl).catch(() => null);
|
|
13514
|
+
switch (prLiveness(st)) {
|
|
13515
|
+
case "merged":
|
|
13516
|
+
await finalizeMergedReview(deps, run);
|
|
13517
|
+
narrate(deps, `PR ${droneLabel(run)} \u0441\u043C\u0435\u0440\u0436\u0435\u043D, ${when} \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u043F\u0440\u043E\u0433\u043E\u043D, \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D.`);
|
|
13518
|
+
return { kind: "finalized" };
|
|
13519
|
+
case "closed":
|
|
13520
|
+
await finalizeClosedPrReview(deps, run, `PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 (${when}) \u2014 \u0440\u0435\u0432\u044C\u044E \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E`);
|
|
13521
|
+
narrate(deps, `PR ${droneLabel(run)} \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430, ${when} \u2014 \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u043D\u0443\u0436\u043D\u043E, \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442.`);
|
|
13522
|
+
return { kind: "finalized" };
|
|
13523
|
+
case "unknown":
|
|
13524
|
+
if (!deps.db.events(run.id).some((e) => (e.text ?? "").includes(REVIEW_PR_UNKNOWN_MARK))) {
|
|
13525
|
+
deps.db.addEvent(run.id, "step", `${REVIEW_PR_UNKNOWN_MARK} (gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0438\u043B\u0438 \u0432 pr_url \u043D\u0435 PR) \u2014 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u043C, \u0441\u043F\u0440\u043E\u0441\u0438\u043C \u043D\u0430 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C \u0442\u0438\u043A\u0435`);
|
|
13526
|
+
}
|
|
13527
|
+
return { kind: "wait" };
|
|
13528
|
+
default:
|
|
13529
|
+
if (st.conflicting || st.isDraft) {
|
|
13530
|
+
deps.db.addEvent(run.id, "step", `PR \u0441\u0442\u0430\u043B \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u044E\u0449\u0438\u043C/\u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u043E\u043C, ${when} \u2014 \u0441\u043F\u0430\u0432\u043D \u043E\u0442\u043B\u043E\u0436\u0435\u043D \u0434\u043E \u0441\u043B\u0435\u0434. \u0442\u0438\u043A\u0430`);
|
|
13531
|
+
return { kind: "wait" };
|
|
13532
|
+
}
|
|
13533
|
+
return { kind: "spawn", st };
|
|
13534
|
+
}
|
|
13308
13535
|
}
|
|
13309
13536
|
function doneMergedPrForSlug(db, slug) {
|
|
13310
13537
|
const done = db.listRuns("done").find((r) => r.task_slug === slug && r.pr_url && r.kind !== "hero" && r.resolves_run_id == null && r.reviews_run_id == null);
|
|
@@ -13367,6 +13594,22 @@ async function closeMergedTaskRuns(deps) {
|
|
|
13367
13594
|
}
|
|
13368
13595
|
}
|
|
13369
13596
|
}
|
|
13597
|
+
async function closeOrphanedReviewers(deps) {
|
|
13598
|
+
for (const rv of deps.db.listRuns()) {
|
|
13599
|
+
if (rv.kind !== "reviewer" || rv.reviews_run_id == null) continue;
|
|
13600
|
+
if (!REVIEWER_ALIVE_STATUSES.has(rv.status)) continue;
|
|
13601
|
+
const parent = deps.db.getRun(rv.reviews_run_id);
|
|
13602
|
+
if (parent && !isTerminalStatus(parent.status)) continue;
|
|
13603
|
+
try {
|
|
13604
|
+
const why = parent ? `\u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442 (${STATUS_META[parent.status]?.label ?? parent.status})` : "\u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u0438\u0441\u0447\u0435\u0437";
|
|
13605
|
+
await closeReviewerRun(deps, rv, `${why} \u2014 PR \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043D\u0430 \u0440\u0435\u0432\u044C\u044E, \u0440\u0435\u0432\u044C\u044E\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E`);
|
|
13606
|
+
if (parent) deps.db.addEvent(parent.id, "step", `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 ${rv.name} (#${rv.id}) \u0437\u0430\u043A\u0440\u044B\u0442: ${why}`);
|
|
13607
|
+
narrate(deps, `${droneLabel(rv)} \u043E\u0441\u0442\u0430\u043B\u0441\u044F \u043D\u0430 \u0437\u0430\u043A\u0440\u044B\u0442\u043E\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u0435 \u2014 \u0433\u0430\u0448\u0443 \u0441\u0430\u043C, \u0441\u043B\u043E\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0451\u043D.`);
|
|
13608
|
+
} catch (e) {
|
|
13609
|
+
console.error(`[overmind] \u0437\u0430\u043A\u0440\u044B\u0442\u0438\u0435 \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 #${rv.id} \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C:`, e);
|
|
13610
|
+
}
|
|
13611
|
+
}
|
|
13612
|
+
}
|
|
13370
13613
|
var CONTINUE_NUDGE = "\u0422\u0432\u043E\u044F \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C (\u0441\u0435\u0442\u044C/\u043F\u0440\u043E\u0446\u0435\u0441\u0441) \u2014 \u0442\u044B headless-\u0434\u0440\u043E\u043D, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0448\u044C \u0422\u0423 \u0416\u0415 \u0437\u0430\u0434\u0430\u0447\u0443. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 `git status`/`git log` \u0438 \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u0441\u0432\u043E\u0438\u0445 \u0448\u0430\u0433\u043E\u0432: \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043E \u2014 \u041D\u0415 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0439, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438 \u0441 \u043C\u0435\u0441\u0442\u0430 \u043E\u0431\u0440\u044B\u0432\u0430. \u0414\u043E\u0432\u0435\u0434\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u043E \u043A\u043E\u043D\u0446\u0430, \u041A\u041E\u041C\u041C\u0418\u0422\u042C \u0418 \u041F\u0423\u0428\u042C \u043F\u043E\u0441\u043B\u0435 \u043A\u0430\u0436\u0434\u043E\u0433\u043E \u0448\u0430\u0433\u0430, \u043E\u0442\u043A\u0440\u043E\u0439 PR \u0438 \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url). \u0412\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0432 stdout \u043D\u0435 \u0437\u0430\u0434\u0430\u0432\u0430\u0439 \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0435\u0440\u0435\u0437 MCP-\u0442\u0443\u043B ask.";
|
|
13371
13614
|
function buildCiWakeNudge(summary) {
|
|
13372
13615
|
return [
|
|
@@ -13472,7 +13715,7 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
13472
13715
|
}
|
|
13473
13716
|
return parts.join("\n");
|
|
13474
13717
|
}
|
|
13475
|
-
var HERO_CHARTER_PATH =
|
|
13718
|
+
var HERO_CHARTER_PATH = join29(dirname4(THIS_FILE), "..", "docs", "HERO.md");
|
|
13476
13719
|
function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
|
|
13477
13720
|
const name = run.name || `#${run.id}`;
|
|
13478
13721
|
const charter = readFileSync20(HERO_CHARTER_PATH, "utf8");
|
|
@@ -13811,7 +14054,7 @@ function repoHasCi(deps, run) {
|
|
|
13811
14054
|
function repoHasCiWorkflows(worktreePath, repo) {
|
|
13812
14055
|
for (const base3 of [worktreePath, repo]) {
|
|
13813
14056
|
if (!base3) continue;
|
|
13814
|
-
const dir =
|
|
14057
|
+
const dir = join29(base3, ".github", "workflows");
|
|
13815
14058
|
try {
|
|
13816
14059
|
if (readdirSync12(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
|
|
13817
14060
|
} catch {
|
|
@@ -13974,10 +14217,9 @@ async function spawnFreshReviewerAfterStall(deps, run, why) {
|
|
|
13974
14217
|
if (run.review_cycle >= REVIEW_CYCLE_LIMIT || deps.db.reviewerAttemptCountFor(run.id) >= REVIEW_SPAWN_LIMIT) {
|
|
13975
14218
|
return;
|
|
13976
14219
|
}
|
|
13977
|
-
const
|
|
13978
|
-
if (
|
|
13979
|
-
|
|
13980
|
-
}
|
|
14220
|
+
const gate = await prGateForReviewer(deps, run, run.pr_url, "\u043F\u043E\u043A\u0430 \u0436\u0434\u0430\u043B CI");
|
|
14221
|
+
if (gate.kind !== "spawn") return;
|
|
14222
|
+
const st = gate.st;
|
|
13981
14223
|
try {
|
|
13982
14224
|
await deps.spawnReviewer(run, st.files ?? []);
|
|
13983
14225
|
narrate(deps, `\u0420\u0435\u0432\u044C\u044E ${droneLabel(run)} \u043F\u043E\u0442\u0435\u0440\u044F\u043B\u043E \u0441\u0435\u0441\u0441\u0438\u044E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430, \u0436\u0434\u0430\u0432 CI (${why}) \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u0441\u0440\u0430\u0437\u0443, \u043D\u0435 \u0436\u0434\u0443 \u043A\u0430\u0440\u0443\u0441\u0435\u043B\u044C.`);
|
|
@@ -14710,6 +14952,11 @@ async function tickOnce(deps) {
|
|
|
14710
14952
|
narrate(deps, `PR ${droneLabel(run)} \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u2014 ${run.task_slug} \u0437\u0430\u043A\u0440\u044B\u0442\u0430, \u0441\u043B\u043E\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D.`);
|
|
14711
14953
|
continue;
|
|
14712
14954
|
}
|
|
14955
|
+
if (prLiveness(st) === "closed") {
|
|
14956
|
+
await finalizeClosedPrReview(deps, run, "PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E, \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442");
|
|
14957
|
+
narrate(deps, `PR ${droneLabel(run)} \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 \u2014 ${run.task_slug} \u0441\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u0441 \u0440\u0435\u0432\u044C\u044E, \u0441\u043B\u043E\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D.`);
|
|
14958
|
+
continue;
|
|
14959
|
+
}
|
|
14713
14960
|
if (touchesDaemonCode(st.files ?? [])) {
|
|
14714
14961
|
const already = deps.db.events(run.id).some((e) => (e.text ?? "").includes(SELF_REFERENCE_MARK));
|
|
14715
14962
|
if (!already) {
|
|
@@ -14803,20 +15050,9 @@ async function tickOnce(deps) {
|
|
|
14803
15050
|
} else {
|
|
14804
15051
|
const reviewersCapOk = deps.reviewCap === void 0 || countActiveReviewers(deps.db) < deps.reviewCap;
|
|
14805
15052
|
if ((!deps.resourcesOk || deps.resourcesOk()) && reviewersCapOk) {
|
|
14806
|
-
const
|
|
14807
|
-
if (
|
|
14808
|
-
|
|
14809
|
-
narrate(deps, `PR ${droneLabel(run)} \u0441\u043C\u0435\u0440\u0436\u0435\u043D, \u043F\u043E\u043A\u0430 \u0436\u0434\u0430\u043B \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E, \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u043D\u0443\u0436\u043D\u043E.`);
|
|
14810
|
-
} else if (freshSt.state === "CLOSED") {
|
|
14811
|
-
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
14812
|
-
});
|
|
14813
|
-
deps.db.updateRun(run.id, { status: "done" });
|
|
14814
|
-
deps.db.addEvent(run.id, "done", "PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430, \u043F\u043E\u043A\u0430 \u0436\u0434\u0430\u043B \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E");
|
|
14815
|
-
narrate(deps, `PR ${droneLabel(run)} \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430, \u043F\u043E\u043A\u0430 \u0436\u0434\u0430\u043B \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u043D\u0443\u0436\u043D\u043E.`);
|
|
14816
|
-
} else if (freshSt.conflicting || freshSt.isDraft) {
|
|
14817
|
-
deps.db.addEvent(run.id, "step", "PR \u0441\u0442\u0430\u043B \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u044E\u0449\u0438\u043C/\u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u043E\u043C, \u043F\u043E\u043A\u0430 \u0436\u0434\u0430\u043B \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u2014 \u0441\u043F\u0430\u0432\u043D \u043E\u0442\u043B\u043E\u0436\u0435\u043D \u0434\u043E \u0441\u043B\u0435\u0434. \u0442\u0438\u043A\u0430");
|
|
14818
|
-
} else {
|
|
14819
|
-
run = syncBranchFromPr(deps.db, run, freshSt.headBranch);
|
|
15053
|
+
const gate = await prGateForReviewer(deps, run, prUrl, "\u043F\u043E\u043A\u0430 \u0436\u0434\u0430\u043B \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430");
|
|
15054
|
+
if (gate.kind === "spawn") {
|
|
15055
|
+
run = syncBranchFromPr(deps.db, run, gate.st.headBranch);
|
|
14820
15056
|
try {
|
|
14821
15057
|
await deps.spawnReviewer(run, st.files ?? []);
|
|
14822
15058
|
narrate(deps, `PR ${droneLabel(run)} \u0433\u043E\u0442\u043E\u0432 \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430.`);
|
|
@@ -14841,6 +15077,7 @@ async function tickOnce(deps) {
|
|
|
14841
15077
|
}
|
|
14842
15078
|
}
|
|
14843
15079
|
await closeMergedTaskRuns(deps);
|
|
15080
|
+
await closeOrphanedReviewers(deps);
|
|
14844
15081
|
refreshReviewPhases(deps);
|
|
14845
15082
|
let liveChildPpids;
|
|
14846
15083
|
try {
|
|
@@ -15218,7 +15455,7 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
15218
15455
|
workspace_id: reviewRun.workspace_id
|
|
15219
15456
|
// та же задача, тот же воркспейс (TASWM-288)
|
|
15220
15457
|
});
|
|
15221
|
-
const wt =
|
|
15458
|
+
const wt = join29(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
|
|
15222
15459
|
try {
|
|
15223
15460
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
15224
15461
|
await deps.git.setWorktreeIdentity(wt, resolver.name);
|
|
@@ -15270,7 +15507,7 @@ async function spawnReviewer(deps, reviewRun, files) {
|
|
|
15270
15507
|
// (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
|
|
15271
15508
|
workspace_id: reviewRun.workspace_id
|
|
15272
15509
|
});
|
|
15273
|
-
const wt =
|
|
15510
|
+
const wt = join29(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
|
|
15274
15511
|
try {
|
|
15275
15512
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
15276
15513
|
await deps.git.setWorktreeIdentity(wt, reviewer.name);
|
|
@@ -15503,7 +15740,7 @@ async function reportWorkspaceAccess(tl, db) {
|
|
|
15503
15740
|
}
|
|
15504
15741
|
async function runDaemon(opts) {
|
|
15505
15742
|
installCrashGuards();
|
|
15506
|
-
const daemonStart =
|
|
15743
|
+
const daemonStart = captureDaemonStart();
|
|
15507
15744
|
const latestRelease = fetchLatestVersion();
|
|
15508
15745
|
const worktreesDir = await migrateWorktreesAtStartup();
|
|
15509
15746
|
if (!opts.repo) {
|
|
@@ -15589,7 +15826,7 @@ async function runDaemon(opts) {
|
|
|
15589
15826
|
repoMap = { run: rebuildRepoMap, intervalMs: SWARM_REPO_MAP_MS, lastAt: Date.now() };
|
|
15590
15827
|
}
|
|
15591
15828
|
let daemonVersion;
|
|
15592
|
-
if (
|
|
15829
|
+
if (SWARM_VERSION_CHECK_MS > 0) {
|
|
15593
15830
|
let cached = null;
|
|
15594
15831
|
daemonVersion = {
|
|
15595
15832
|
run: async () => {
|
|
@@ -16200,10 +16437,21 @@ async function runDaemon(opts) {
|
|
|
16200
16437
|
return wakeRun({ db, isAlive: pidAlive, respawn: deps.respawn, narrate: narrator.narrate }, run.id);
|
|
16201
16438
|
},
|
|
16202
16439
|
update: async (onStep) => {
|
|
16203
|
-
|
|
16204
|
-
|
|
16440
|
+
const pm = detectGlobalPm(process.argv[1]);
|
|
16441
|
+
if (pm !== "sources") {
|
|
16442
|
+
return runPackageSelfUpdate({
|
|
16443
|
+
currentVersion: PACKAGE_VERSION,
|
|
16444
|
+
installCommand: (spec) => globalInstallCommand(pm, spec),
|
|
16445
|
+
latest: () => fetchLatestVersion(),
|
|
16446
|
+
exec: execLoud,
|
|
16447
|
+
onStep
|
|
16448
|
+
});
|
|
16449
|
+
}
|
|
16450
|
+
const repo = await repoRoot(dirname4(THIS_FILE)).catch(() => null);
|
|
16451
|
+
if (!repo) {
|
|
16452
|
+
return { ok: false, step: "fetch", message: "\u0431\u0435\u0433\u0443 \u0438\u0437 \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A\u043E\u0432, \u043D\u043E git-\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u043D\u0430\u0434 \u043D\u0438\u043C\u0438 \u043D\u0435\u0442 \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E" };
|
|
16205
16453
|
}
|
|
16206
|
-
return runSelfUpdate(defaultSelfUpdateIO(
|
|
16454
|
+
return runSelfUpdate(defaultSelfUpdateIO(repo, onStep));
|
|
16207
16455
|
},
|
|
16208
16456
|
narrate: narrator.narrate
|
|
16209
16457
|
};
|
|
@@ -16409,6 +16657,7 @@ export {
|
|
|
16409
16657
|
REVIEW_CYCLE_ESCALATION_MARK,
|
|
16410
16658
|
REVIEW_CYCLE_LIMIT,
|
|
16411
16659
|
REVIEW_ESCALATION_MARK,
|
|
16660
|
+
REVIEW_PR_UNKNOWN_MARK,
|
|
16412
16661
|
REVIEW_SPAWN_LIMIT,
|
|
16413
16662
|
REVIEW_UNRECOVERABLE_MARK,
|
|
16414
16663
|
SELF_REFERENCE_MARK,
|