@pruddiman/dispatch 1.3.1 → 1.4.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 +310 -92
- package/dist/cli.js.map +1 -1
- package/package.json +2 -4
package/dist/cli.js
CHANGED
|
@@ -422,7 +422,7 @@ var init_opencode = __esm({
|
|
|
422
422
|
|
|
423
423
|
// src/helpers/timeout.ts
|
|
424
424
|
function withTimeout(promise, ms, label) {
|
|
425
|
-
const p = new Promise((
|
|
425
|
+
const p = new Promise((resolve5, reject) => {
|
|
426
426
|
let settled = false;
|
|
427
427
|
const timer = setTimeout(() => {
|
|
428
428
|
if (settled) return;
|
|
@@ -434,7 +434,7 @@ function withTimeout(promise, ms, label) {
|
|
|
434
434
|
if (settled) return;
|
|
435
435
|
settled = true;
|
|
436
436
|
clearTimeout(timer);
|
|
437
|
-
|
|
437
|
+
resolve5(value);
|
|
438
438
|
},
|
|
439
439
|
(err) => {
|
|
440
440
|
if (settled) return;
|
|
@@ -542,9 +542,9 @@ async function boot2(opts) {
|
|
|
542
542
|
let unsubErr;
|
|
543
543
|
try {
|
|
544
544
|
await withTimeout(
|
|
545
|
-
new Promise((
|
|
545
|
+
new Promise((resolve5, reject) => {
|
|
546
546
|
unsubIdle = session.on("session.idle", () => {
|
|
547
|
-
|
|
547
|
+
resolve5();
|
|
548
548
|
});
|
|
549
549
|
unsubErr = session.on("session.error", (event) => {
|
|
550
550
|
reject(new Error(`Copilot session error: ${event.data.message}`));
|
|
@@ -761,18 +761,20 @@ import { promisify as promisify6 } from "util";
|
|
|
761
761
|
async function checkProviderInstalled(name) {
|
|
762
762
|
try {
|
|
763
763
|
await exec6(PROVIDER_BINARIES[name], ["--version"], {
|
|
764
|
-
shell: process.platform === "win32"
|
|
764
|
+
shell: process.platform === "win32",
|
|
765
|
+
timeout: DETECTION_TIMEOUT_MS
|
|
765
766
|
});
|
|
766
767
|
return true;
|
|
767
768
|
} catch {
|
|
768
769
|
return false;
|
|
769
770
|
}
|
|
770
771
|
}
|
|
771
|
-
var exec6, PROVIDER_BINARIES;
|
|
772
|
+
var exec6, DETECTION_TIMEOUT_MS, PROVIDER_BINARIES;
|
|
772
773
|
var init_detect = __esm({
|
|
773
774
|
"src/providers/detect.ts"() {
|
|
774
775
|
"use strict";
|
|
775
776
|
exec6 = promisify6(execFile6);
|
|
777
|
+
DETECTION_TIMEOUT_MS = 5e3;
|
|
776
778
|
PROVIDER_BINARIES = {
|
|
777
779
|
opencode: "opencode",
|
|
778
780
|
copilot: "copilot",
|
|
@@ -826,6 +828,35 @@ var init_providers = __esm({
|
|
|
826
828
|
}
|
|
827
829
|
});
|
|
828
830
|
|
|
831
|
+
// src/helpers/environment.ts
|
|
832
|
+
function getEnvironmentInfo() {
|
|
833
|
+
const platform = process.platform;
|
|
834
|
+
switch (platform) {
|
|
835
|
+
case "win32":
|
|
836
|
+
return { platform, os: "Windows", shell: "cmd.exe/PowerShell" };
|
|
837
|
+
case "darwin":
|
|
838
|
+
return { platform, os: "macOS", shell: "zsh/bash" };
|
|
839
|
+
default:
|
|
840
|
+
return { platform, os: "Linux", shell: "bash" };
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
function formatEnvironmentPrompt() {
|
|
844
|
+
const env = getEnvironmentInfo();
|
|
845
|
+
return [
|
|
846
|
+
`## Environment`,
|
|
847
|
+
`- **Operating System:** ${env.os}`,
|
|
848
|
+
`- **Default Shell:** ${env.shell}`,
|
|
849
|
+
`- Always run commands directly in the shell. Do NOT write intermediate scripts (e.g. .bat, .ps1, .py files) unless the task explicitly requires creating a script.`
|
|
850
|
+
].join("\n");
|
|
851
|
+
}
|
|
852
|
+
var getEnvironmentBlock;
|
|
853
|
+
var init_environment = __esm({
|
|
854
|
+
"src/helpers/environment.ts"() {
|
|
855
|
+
"use strict";
|
|
856
|
+
getEnvironmentBlock = formatEnvironmentPrompt;
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
|
|
829
860
|
// src/helpers/cleanup.ts
|
|
830
861
|
function registerCleanup(fn) {
|
|
831
862
|
cleanups.push(fn);
|
|
@@ -880,15 +911,15 @@ async function detectTestCommand(cwd) {
|
|
|
880
911
|
}
|
|
881
912
|
}
|
|
882
913
|
function runTestCommand(command, cwd) {
|
|
883
|
-
return new Promise((
|
|
914
|
+
return new Promise((resolve5) => {
|
|
884
915
|
const [cmd, ...args] = command.split(" ");
|
|
885
916
|
execFileCb(
|
|
886
917
|
cmd,
|
|
887
918
|
args,
|
|
888
|
-
{ cwd, maxBuffer: 10 * 1024 * 1024 },
|
|
919
|
+
{ cwd, maxBuffer: 10 * 1024 * 1024, shell: process.platform === "win32" },
|
|
889
920
|
(error, stdout, stderr) => {
|
|
890
921
|
const exitCode = error && "code" in error ? error.code ?? 1 : error ? 1 : 0;
|
|
891
|
-
|
|
922
|
+
resolve5({ exitCode, stdout, stderr, command });
|
|
892
923
|
}
|
|
893
924
|
);
|
|
894
925
|
});
|
|
@@ -902,6 +933,8 @@ function buildFixTestsPrompt(testResult, cwd) {
|
|
|
902
933
|
`**Test command:** ${testResult.command}`,
|
|
903
934
|
`**Exit code:** ${testResult.exitCode}`,
|
|
904
935
|
``,
|
|
936
|
+
formatEnvironmentPrompt(),
|
|
937
|
+
``,
|
|
905
938
|
`## Test Output`,
|
|
906
939
|
``,
|
|
907
940
|
"```",
|
|
@@ -1003,11 +1036,12 @@ var init_fix_tests_pipeline = __esm({
|
|
|
1003
1036
|
init_cleanup();
|
|
1004
1037
|
init_logger();
|
|
1005
1038
|
init_file_logger();
|
|
1039
|
+
init_environment();
|
|
1006
1040
|
}
|
|
1007
1041
|
});
|
|
1008
1042
|
|
|
1009
1043
|
// src/cli.ts
|
|
1010
|
-
import { resolve as
|
|
1044
|
+
import { resolve as resolve4, join as join12 } from "path";
|
|
1011
1045
|
import { Command, Option, CommanderError } from "commander";
|
|
1012
1046
|
|
|
1013
1047
|
// src/spec-generator.ts
|
|
@@ -1054,11 +1088,11 @@ function isValidBranchName(name) {
|
|
|
1054
1088
|
// src/datasources/github.ts
|
|
1055
1089
|
var exec = promisify(execFile);
|
|
1056
1090
|
async function git(args, cwd) {
|
|
1057
|
-
const { stdout } = await exec("git", args, { cwd });
|
|
1091
|
+
const { stdout } = await exec("git", args, { cwd, shell: process.platform === "win32" });
|
|
1058
1092
|
return stdout;
|
|
1059
1093
|
}
|
|
1060
1094
|
async function gh(args, cwd) {
|
|
1061
|
-
const { stdout } = await exec("gh", args, { cwd });
|
|
1095
|
+
const { stdout } = await exec("gh", args, { cwd, shell: process.platform === "win32" });
|
|
1062
1096
|
return stdout;
|
|
1063
1097
|
}
|
|
1064
1098
|
function buildBranchName(issueNumber, title, username = "unknown") {
|
|
@@ -1104,7 +1138,7 @@ var datasource = {
|
|
|
1104
1138
|
"--json",
|
|
1105
1139
|
"number,title,body,labels,state,url"
|
|
1106
1140
|
],
|
|
1107
|
-
{ cwd }
|
|
1141
|
+
{ cwd, shell: process.platform === "win32" }
|
|
1108
1142
|
);
|
|
1109
1143
|
let issues;
|
|
1110
1144
|
try {
|
|
@@ -1136,7 +1170,7 @@ var datasource = {
|
|
|
1136
1170
|
"--json",
|
|
1137
1171
|
"number,title,body,labels,state,url,comments"
|
|
1138
1172
|
],
|
|
1139
|
-
{ cwd }
|
|
1173
|
+
{ cwd, shell: process.platform === "win32" }
|
|
1140
1174
|
);
|
|
1141
1175
|
let issue;
|
|
1142
1176
|
try {
|
|
@@ -1164,18 +1198,18 @@ var datasource = {
|
|
|
1164
1198
|
},
|
|
1165
1199
|
async update(issueId, title, body, opts = {}) {
|
|
1166
1200
|
const cwd = opts.cwd || process.cwd();
|
|
1167
|
-
await exec("gh", ["issue", "edit", issueId, "--title", title, "--body", body], { cwd });
|
|
1201
|
+
await exec("gh", ["issue", "edit", issueId, "--title", title, "--body", body], { cwd, shell: process.platform === "win32" });
|
|
1168
1202
|
},
|
|
1169
1203
|
async close(issueId, opts = {}) {
|
|
1170
1204
|
const cwd = opts.cwd || process.cwd();
|
|
1171
|
-
await exec("gh", ["issue", "close", issueId], { cwd });
|
|
1205
|
+
await exec("gh", ["issue", "close", issueId], { cwd, shell: process.platform === "win32" });
|
|
1172
1206
|
},
|
|
1173
1207
|
async create(title, body, opts = {}) {
|
|
1174
1208
|
const cwd = opts.cwd || process.cwd();
|
|
1175
1209
|
const { stdout } = await exec(
|
|
1176
1210
|
"gh",
|
|
1177
1211
|
["issue", "create", "--title", title, "--body", body],
|
|
1178
|
-
{ cwd }
|
|
1212
|
+
{ cwd, shell: process.platform === "win32" }
|
|
1179
1213
|
);
|
|
1180
1214
|
const url = stdout.trim();
|
|
1181
1215
|
const match = url.match(/\/issues\/(\d+)$/);
|
|
@@ -1271,6 +1305,7 @@ import { execFile as execFile2 } from "child_process";
|
|
|
1271
1305
|
import { promisify as promisify2 } from "util";
|
|
1272
1306
|
init_logger();
|
|
1273
1307
|
var exec2 = promisify2(execFile2);
|
|
1308
|
+
var doneStateCache = /* @__PURE__ */ new Map();
|
|
1274
1309
|
function mapWorkItemToIssueDetails(item, id, comments, defaults) {
|
|
1275
1310
|
const fields = item.fields ?? {};
|
|
1276
1311
|
return {
|
|
@@ -1296,7 +1331,8 @@ async function detectWorkItemType(opts = {}) {
|
|
|
1296
1331
|
if (opts.project) args.push("--project", opts.project);
|
|
1297
1332
|
if (opts.org) args.push("--org", opts.org);
|
|
1298
1333
|
const { stdout } = await exec2("az", args, {
|
|
1299
|
-
cwd: opts.cwd || process.cwd()
|
|
1334
|
+
cwd: opts.cwd || process.cwd(),
|
|
1335
|
+
shell: process.platform === "win32"
|
|
1300
1336
|
});
|
|
1301
1337
|
const types = JSON.parse(stdout);
|
|
1302
1338
|
if (!Array.isArray(types) || types.length === 0) return null;
|
|
@@ -1310,6 +1346,48 @@ async function detectWorkItemType(opts = {}) {
|
|
|
1310
1346
|
return null;
|
|
1311
1347
|
}
|
|
1312
1348
|
}
|
|
1349
|
+
async function detectDoneState(workItemType, opts = {}) {
|
|
1350
|
+
const cacheKey = `${opts.org ?? ""}|${opts.project ?? ""}|${workItemType}`;
|
|
1351
|
+
const cached = doneStateCache.get(cacheKey);
|
|
1352
|
+
if (cached) return cached;
|
|
1353
|
+
try {
|
|
1354
|
+
const args = [
|
|
1355
|
+
"boards",
|
|
1356
|
+
"work-item",
|
|
1357
|
+
"type",
|
|
1358
|
+
"state",
|
|
1359
|
+
"list",
|
|
1360
|
+
"--type",
|
|
1361
|
+
workItemType,
|
|
1362
|
+
"--output",
|
|
1363
|
+
"json"
|
|
1364
|
+
];
|
|
1365
|
+
if (opts.project) args.push("--project", opts.project);
|
|
1366
|
+
if (opts.org) args.push("--org", opts.org);
|
|
1367
|
+
const { stdout } = await exec2("az", args, {
|
|
1368
|
+
cwd: opts.cwd || process.cwd(),
|
|
1369
|
+
shell: process.platform === "win32"
|
|
1370
|
+
});
|
|
1371
|
+
const states = JSON.parse(stdout);
|
|
1372
|
+
if (Array.isArray(states)) {
|
|
1373
|
+
const completed = states.find((s) => s.category === "Completed");
|
|
1374
|
+
if (completed) {
|
|
1375
|
+
doneStateCache.set(cacheKey, completed.name);
|
|
1376
|
+
return completed.name;
|
|
1377
|
+
}
|
|
1378
|
+
const names = states.map((s) => s.name);
|
|
1379
|
+
const fallbacks = ["Done", "Closed", "Resolved", "Completed"];
|
|
1380
|
+
for (const f of fallbacks) {
|
|
1381
|
+
if (names.includes(f)) {
|
|
1382
|
+
doneStateCache.set(cacheKey, f);
|
|
1383
|
+
return f;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
} catch {
|
|
1388
|
+
}
|
|
1389
|
+
return "Closed";
|
|
1390
|
+
}
|
|
1313
1391
|
var datasource2 = {
|
|
1314
1392
|
name: "azdevops",
|
|
1315
1393
|
supportsGit() {
|
|
@@ -1318,6 +1396,7 @@ var datasource2 = {
|
|
|
1318
1396
|
async list(opts = {}) {
|
|
1319
1397
|
const conditions = [
|
|
1320
1398
|
"[System.State] <> 'Closed'",
|
|
1399
|
+
"[System.State] <> 'Done'",
|
|
1321
1400
|
"[System.State] <> 'Removed'"
|
|
1322
1401
|
];
|
|
1323
1402
|
if (opts.iteration) {
|
|
@@ -1340,7 +1419,8 @@ var datasource2 = {
|
|
|
1340
1419
|
if (opts.org) args.push("--org", opts.org);
|
|
1341
1420
|
if (opts.project) args.push("--project", opts.project);
|
|
1342
1421
|
const { stdout } = await exec2("az", args, {
|
|
1343
|
-
cwd: opts.cwd || process.cwd()
|
|
1422
|
+
cwd: opts.cwd || process.cwd(),
|
|
1423
|
+
shell: process.platform === "win32"
|
|
1344
1424
|
});
|
|
1345
1425
|
let data;
|
|
1346
1426
|
try {
|
|
@@ -1364,7 +1444,8 @@ var datasource2 = {
|
|
|
1364
1444
|
if (opts.org) batchArgs.push("--org", opts.org);
|
|
1365
1445
|
if (opts.project) batchArgs.push("--project", opts.project);
|
|
1366
1446
|
const { stdout: batchStdout } = await exec2("az", batchArgs, {
|
|
1367
|
-
cwd: opts.cwd || process.cwd()
|
|
1447
|
+
cwd: opts.cwd || process.cwd(),
|
|
1448
|
+
shell: process.platform === "win32"
|
|
1368
1449
|
});
|
|
1369
1450
|
let batchItems;
|
|
1370
1451
|
try {
|
|
@@ -1410,7 +1491,8 @@ var datasource2 = {
|
|
|
1410
1491
|
args.push("--project", opts.project);
|
|
1411
1492
|
}
|
|
1412
1493
|
const { stdout } = await exec2("az", args, {
|
|
1413
|
-
cwd: opts.cwd || process.cwd()
|
|
1494
|
+
cwd: opts.cwd || process.cwd(),
|
|
1495
|
+
shell: process.platform === "win32"
|
|
1414
1496
|
});
|
|
1415
1497
|
let item;
|
|
1416
1498
|
try {
|
|
@@ -1435,9 +1517,34 @@ var datasource2 = {
|
|
|
1435
1517
|
];
|
|
1436
1518
|
if (opts.org) args.push("--org", opts.org);
|
|
1437
1519
|
if (opts.project) args.push("--project", opts.project);
|
|
1438
|
-
await exec2("az", args, { cwd: opts.cwd || process.cwd() });
|
|
1520
|
+
await exec2("az", args, { cwd: opts.cwd || process.cwd(), shell: process.platform === "win32" });
|
|
1439
1521
|
},
|
|
1440
1522
|
async close(issueId, opts = {}) {
|
|
1523
|
+
let workItemType = opts.workItemType;
|
|
1524
|
+
if (!workItemType) {
|
|
1525
|
+
const showArgs = [
|
|
1526
|
+
"boards",
|
|
1527
|
+
"work-item",
|
|
1528
|
+
"show",
|
|
1529
|
+
"--id",
|
|
1530
|
+
issueId,
|
|
1531
|
+
"--output",
|
|
1532
|
+
"json"
|
|
1533
|
+
];
|
|
1534
|
+
if (opts.org) showArgs.push("--org", opts.org);
|
|
1535
|
+
if (opts.project) showArgs.push("--project", opts.project);
|
|
1536
|
+
const { stdout } = await exec2("az", showArgs, {
|
|
1537
|
+
cwd: opts.cwd || process.cwd(),
|
|
1538
|
+
shell: process.platform === "win32"
|
|
1539
|
+
});
|
|
1540
|
+
try {
|
|
1541
|
+
const item = JSON.parse(stdout);
|
|
1542
|
+
workItemType = item.fields?.["System.WorkItemType"] ?? void 0;
|
|
1543
|
+
} catch {
|
|
1544
|
+
workItemType = void 0;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
const state = workItemType ? await detectDoneState(workItemType, opts) : "Closed";
|
|
1441
1548
|
const args = [
|
|
1442
1549
|
"boards",
|
|
1443
1550
|
"work-item",
|
|
@@ -1445,11 +1552,11 @@ var datasource2 = {
|
|
|
1445
1552
|
"--id",
|
|
1446
1553
|
issueId,
|
|
1447
1554
|
"--state",
|
|
1448
|
-
|
|
1555
|
+
state
|
|
1449
1556
|
];
|
|
1450
1557
|
if (opts.org) args.push("--org", opts.org);
|
|
1451
1558
|
if (opts.project) args.push("--project", opts.project);
|
|
1452
|
-
await exec2("az", args, { cwd: opts.cwd || process.cwd() });
|
|
1559
|
+
await exec2("az", args, { cwd: opts.cwd || process.cwd(), shell: process.platform === "win32" });
|
|
1453
1560
|
},
|
|
1454
1561
|
async create(title, body, opts = {}) {
|
|
1455
1562
|
const workItemType = opts.workItemType ?? await detectWorkItemType(opts);
|
|
@@ -1474,7 +1581,8 @@ var datasource2 = {
|
|
|
1474
1581
|
if (opts.org) args.push("--org", opts.org);
|
|
1475
1582
|
if (opts.project) args.push("--project", opts.project);
|
|
1476
1583
|
const { stdout } = await exec2("az", args, {
|
|
1477
|
-
cwd: opts.cwd || process.cwd()
|
|
1584
|
+
cwd: opts.cwd || process.cwd(),
|
|
1585
|
+
shell: process.platform === "win32"
|
|
1478
1586
|
});
|
|
1479
1587
|
let item;
|
|
1480
1588
|
try {
|
|
@@ -1491,7 +1599,7 @@ var datasource2 = {
|
|
|
1491
1599
|
},
|
|
1492
1600
|
async getDefaultBranch(opts) {
|
|
1493
1601
|
try {
|
|
1494
|
-
const { stdout } = await exec2("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], { cwd: opts.cwd });
|
|
1602
|
+
const { stdout } = await exec2("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1495
1603
|
const parts = stdout.trim().split("/");
|
|
1496
1604
|
const branch = parts[parts.length - 1];
|
|
1497
1605
|
if (!isValidBranchName(branch)) {
|
|
@@ -1503,7 +1611,7 @@ var datasource2 = {
|
|
|
1503
1611
|
throw err;
|
|
1504
1612
|
}
|
|
1505
1613
|
try {
|
|
1506
|
-
await exec2("git", ["rev-parse", "--verify", "main"], { cwd: opts.cwd });
|
|
1614
|
+
await exec2("git", ["rev-parse", "--verify", "main"], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1507
1615
|
return "main";
|
|
1508
1616
|
} catch {
|
|
1509
1617
|
return "master";
|
|
@@ -1512,19 +1620,19 @@ var datasource2 = {
|
|
|
1512
1620
|
},
|
|
1513
1621
|
async getUsername(opts) {
|
|
1514
1622
|
try {
|
|
1515
|
-
const { stdout } = await exec2("git", ["config", "user.name"], { cwd: opts.cwd });
|
|
1623
|
+
const { stdout } = await exec2("git", ["config", "user.name"], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1516
1624
|
const name = slugify(stdout.trim());
|
|
1517
1625
|
if (name) return name;
|
|
1518
1626
|
} catch {
|
|
1519
1627
|
}
|
|
1520
1628
|
try {
|
|
1521
|
-
const { stdout } = await exec2("az", ["account", "show", "--query", "user.name", "-o", "tsv"], { cwd: opts.cwd });
|
|
1629
|
+
const { stdout } = await exec2("az", ["account", "show", "--query", "user.name", "-o", "tsv"], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1522
1630
|
const name = slugify(stdout.trim());
|
|
1523
1631
|
if (name) return name;
|
|
1524
1632
|
} catch {
|
|
1525
1633
|
}
|
|
1526
1634
|
try {
|
|
1527
|
-
const { stdout } = await exec2("az", ["account", "show", "--query", "user.principalName", "-o", "tsv"], { cwd: opts.cwd });
|
|
1635
|
+
const { stdout } = await exec2("az", ["account", "show", "--query", "user.principalName", "-o", "tsv"], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1528
1636
|
const principal = stdout.trim();
|
|
1529
1637
|
const prefix = principal.split("@")[0];
|
|
1530
1638
|
const name = slugify(prefix);
|
|
@@ -1546,29 +1654,29 @@ var datasource2 = {
|
|
|
1546
1654
|
throw new InvalidBranchNameError(branchName);
|
|
1547
1655
|
}
|
|
1548
1656
|
try {
|
|
1549
|
-
await exec2("git", ["checkout", "-b", branchName], { cwd: opts.cwd });
|
|
1657
|
+
await exec2("git", ["checkout", "-b", branchName], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1550
1658
|
} catch (err) {
|
|
1551
1659
|
const message = log.extractMessage(err);
|
|
1552
1660
|
if (message.includes("already exists")) {
|
|
1553
|
-
await exec2("git", ["checkout", branchName], { cwd: opts.cwd });
|
|
1661
|
+
await exec2("git", ["checkout", branchName], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1554
1662
|
} else {
|
|
1555
1663
|
throw err;
|
|
1556
1664
|
}
|
|
1557
1665
|
}
|
|
1558
1666
|
},
|
|
1559
1667
|
async switchBranch(branchName, opts) {
|
|
1560
|
-
await exec2("git", ["checkout", branchName], { cwd: opts.cwd });
|
|
1668
|
+
await exec2("git", ["checkout", branchName], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1561
1669
|
},
|
|
1562
1670
|
async pushBranch(branchName, opts) {
|
|
1563
|
-
await exec2("git", ["push", "--set-upstream", "origin", branchName], { cwd: opts.cwd });
|
|
1671
|
+
await exec2("git", ["push", "--set-upstream", "origin", branchName], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1564
1672
|
},
|
|
1565
1673
|
async commitAllChanges(message, opts) {
|
|
1566
|
-
await exec2("git", ["add", "-A"], { cwd: opts.cwd });
|
|
1567
|
-
const { stdout } = await exec2("git", ["diff", "--cached", "--stat"], { cwd: opts.cwd });
|
|
1674
|
+
await exec2("git", ["add", "-A"], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1675
|
+
const { stdout } = await exec2("git", ["diff", "--cached", "--stat"], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1568
1676
|
if (!stdout.trim()) {
|
|
1569
1677
|
return;
|
|
1570
1678
|
}
|
|
1571
|
-
await exec2("git", ["commit", "-m", message], { cwd: opts.cwd });
|
|
1679
|
+
await exec2("git", ["commit", "-m", message], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1572
1680
|
},
|
|
1573
1681
|
async createPullRequest(branchName, issueNumber, title, body, opts) {
|
|
1574
1682
|
try {
|
|
@@ -1589,7 +1697,7 @@ var datasource2 = {
|
|
|
1589
1697
|
"--output",
|
|
1590
1698
|
"json"
|
|
1591
1699
|
],
|
|
1592
|
-
{ cwd: opts.cwd }
|
|
1700
|
+
{ cwd: opts.cwd, shell: process.platform === "win32" }
|
|
1593
1701
|
);
|
|
1594
1702
|
let pr;
|
|
1595
1703
|
try {
|
|
@@ -1614,7 +1722,7 @@ var datasource2 = {
|
|
|
1614
1722
|
"--output",
|
|
1615
1723
|
"json"
|
|
1616
1724
|
],
|
|
1617
|
-
{ cwd: opts.cwd }
|
|
1725
|
+
{ cwd: opts.cwd, shell: process.platform === "win32" }
|
|
1618
1726
|
);
|
|
1619
1727
|
let prs;
|
|
1620
1728
|
try {
|
|
@@ -1650,7 +1758,8 @@ async function fetchComments(workItemId, opts) {
|
|
|
1650
1758
|
args.push("--project", opts.project);
|
|
1651
1759
|
}
|
|
1652
1760
|
const { stdout } = await exec2("az", args, {
|
|
1653
|
-
cwd: opts.cwd || process.cwd()
|
|
1761
|
+
cwd: opts.cwd || process.cwd(),
|
|
1762
|
+
shell: process.platform === "win32"
|
|
1654
1763
|
});
|
|
1655
1764
|
const data = JSON.parse(stdout);
|
|
1656
1765
|
if (data.comments && Array.isArray(data.comments)) {
|
|
@@ -1670,8 +1779,9 @@ async function fetchComments(workItemId, opts) {
|
|
|
1670
1779
|
// src/datasources/md.ts
|
|
1671
1780
|
import { execFile as execFile3 } from "child_process";
|
|
1672
1781
|
import { readFile, writeFile, readdir, mkdir, rename } from "fs/promises";
|
|
1673
|
-
import { join as join2, parse as parsePath } from "path";
|
|
1782
|
+
import { basename, dirname as dirname2, isAbsolute, join as join2, parse as parsePath, resolve } from "path";
|
|
1674
1783
|
import { promisify as promisify3 } from "util";
|
|
1784
|
+
import { glob } from "glob";
|
|
1675
1785
|
|
|
1676
1786
|
// src/helpers/errors.ts
|
|
1677
1787
|
var UnsupportedOperationError = class extends Error {
|
|
@@ -1692,6 +1802,15 @@ function resolveDir(opts) {
|
|
|
1692
1802
|
const cwd = opts?.cwd ?? process.cwd();
|
|
1693
1803
|
return join2(cwd, DEFAULT_DIR);
|
|
1694
1804
|
}
|
|
1805
|
+
function resolveFilePath(issueId, opts) {
|
|
1806
|
+
const filename = issueId.endsWith(".md") ? issueId : `${issueId}.md`;
|
|
1807
|
+
if (isAbsolute(filename)) return filename;
|
|
1808
|
+
if (/[/\\]/.test(filename)) {
|
|
1809
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
1810
|
+
return resolve(cwd, filename);
|
|
1811
|
+
}
|
|
1812
|
+
return join2(resolveDir(opts), filename);
|
|
1813
|
+
}
|
|
1695
1814
|
function extractTitle(content, filename) {
|
|
1696
1815
|
const match = content.match(/^#\s+(.+)$/m);
|
|
1697
1816
|
if (match) return match[1].trim();
|
|
@@ -1726,6 +1845,19 @@ var datasource3 = {
|
|
|
1726
1845
|
return false;
|
|
1727
1846
|
},
|
|
1728
1847
|
async list(opts) {
|
|
1848
|
+
if (opts?.pattern) {
|
|
1849
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
1850
|
+
const files = await glob(opts.pattern, { cwd, absolute: true });
|
|
1851
|
+
const mdFiles2 = files.filter((f) => f.endsWith(".md")).sort();
|
|
1852
|
+
const results2 = [];
|
|
1853
|
+
for (const filePath of mdFiles2) {
|
|
1854
|
+
const content = await readFile(filePath, "utf-8");
|
|
1855
|
+
const filename = basename(filePath);
|
|
1856
|
+
const dir2 = dirname2(filePath);
|
|
1857
|
+
results2.push(toIssueDetails(filename, content, dir2));
|
|
1858
|
+
}
|
|
1859
|
+
return results2;
|
|
1860
|
+
}
|
|
1729
1861
|
const dir = resolveDir(opts);
|
|
1730
1862
|
let entries;
|
|
1731
1863
|
try {
|
|
@@ -1743,23 +1875,20 @@ var datasource3 = {
|
|
|
1743
1875
|
return results;
|
|
1744
1876
|
},
|
|
1745
1877
|
async fetch(issueId, opts) {
|
|
1746
|
-
const
|
|
1747
|
-
const filename = issueId.endsWith(".md") ? issueId : `${issueId}.md`;
|
|
1748
|
-
const filePath = join2(dir, filename);
|
|
1878
|
+
const filePath = resolveFilePath(issueId, opts);
|
|
1749
1879
|
const content = await readFile(filePath, "utf-8");
|
|
1880
|
+
const filename = basename(filePath);
|
|
1881
|
+
const dir = dirname2(filePath);
|
|
1750
1882
|
return toIssueDetails(filename, content, dir);
|
|
1751
1883
|
},
|
|
1752
1884
|
async update(issueId, _title, body, opts) {
|
|
1753
|
-
const
|
|
1754
|
-
const filename = issueId.endsWith(".md") ? issueId : `${issueId}.md`;
|
|
1755
|
-
const filePath = join2(dir, filename);
|
|
1885
|
+
const filePath = resolveFilePath(issueId, opts);
|
|
1756
1886
|
await writeFile(filePath, body, "utf-8");
|
|
1757
1887
|
},
|
|
1758
1888
|
async close(issueId, opts) {
|
|
1759
|
-
const
|
|
1760
|
-
const filename =
|
|
1761
|
-
const
|
|
1762
|
-
const archiveDir = join2(dir, "archive");
|
|
1889
|
+
const filePath = resolveFilePath(issueId, opts);
|
|
1890
|
+
const filename = basename(filePath);
|
|
1891
|
+
const archiveDir = join2(dirname2(filePath), "archive");
|
|
1763
1892
|
await mkdir(archiveDir, { recursive: true });
|
|
1764
1893
|
await rename(filePath, join2(archiveDir, filename));
|
|
1765
1894
|
},
|
|
@@ -1776,7 +1905,7 @@ var datasource3 = {
|
|
|
1776
1905
|
},
|
|
1777
1906
|
async getUsername(opts) {
|
|
1778
1907
|
try {
|
|
1779
|
-
const { stdout } = await exec3("git", ["config", "user.name"], { cwd: opts.cwd });
|
|
1908
|
+
const { stdout } = await exec3("git", ["config", "user.name"], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1780
1909
|
const name = stdout.trim();
|
|
1781
1910
|
if (!name) return "local";
|
|
1782
1911
|
return slugify(name);
|
|
@@ -1825,7 +1954,8 @@ function getDatasource(name) {
|
|
|
1825
1954
|
async function getGitRemoteUrl(cwd) {
|
|
1826
1955
|
try {
|
|
1827
1956
|
const { stdout } = await exec4("git", ["remote", "get-url", "origin"], {
|
|
1828
|
-
cwd
|
|
1957
|
+
cwd,
|
|
1958
|
+
shell: process.platform === "win32"
|
|
1829
1959
|
});
|
|
1830
1960
|
return stdout.trim() || null;
|
|
1831
1961
|
} catch {
|
|
@@ -2100,7 +2230,7 @@ import { constants } from "fs";
|
|
|
2100
2230
|
// src/config.ts
|
|
2101
2231
|
init_providers();
|
|
2102
2232
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
|
|
2103
|
-
import { join as join4, dirname as
|
|
2233
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
2104
2234
|
|
|
2105
2235
|
// src/config-prompts.ts
|
|
2106
2236
|
init_logger();
|
|
@@ -2291,7 +2421,7 @@ async function loadConfig(configDir) {
|
|
|
2291
2421
|
}
|
|
2292
2422
|
async function saveConfig(config, configDir) {
|
|
2293
2423
|
const configPath = getConfigPath(configDir);
|
|
2294
|
-
await mkdir2(
|
|
2424
|
+
await mkdir2(dirname3(configPath), { recursive: true });
|
|
2295
2425
|
await writeFile3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2296
2426
|
}
|
|
2297
2427
|
async function handleConfigCommand(_argv, configDir) {
|
|
@@ -2366,15 +2496,16 @@ async function resolveCliConfig(args) {
|
|
|
2366
2496
|
// src/orchestrator/spec-pipeline.ts
|
|
2367
2497
|
import { join as join7 } from "path";
|
|
2368
2498
|
import { mkdir as mkdir4, readFile as readFile5, rename as rename2, unlink as unlink2 } from "fs/promises";
|
|
2369
|
-
import { glob } from "glob";
|
|
2499
|
+
import { glob as glob2 } from "glob";
|
|
2370
2500
|
init_providers();
|
|
2371
2501
|
|
|
2372
2502
|
// src/agents/spec.ts
|
|
2373
2503
|
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4, unlink } from "fs/promises";
|
|
2374
|
-
import { join as join6, resolve, sep } from "path";
|
|
2504
|
+
import { join as join6, resolve as resolve2, sep } from "path";
|
|
2375
2505
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2376
2506
|
init_logger();
|
|
2377
2507
|
init_file_logger();
|
|
2508
|
+
init_environment();
|
|
2378
2509
|
async function boot5(opts) {
|
|
2379
2510
|
const { provider } = opts;
|
|
2380
2511
|
if (!provider) {
|
|
@@ -2386,8 +2517,8 @@ async function boot5(opts) {
|
|
|
2386
2517
|
const { issue, filePath, fileContent, inlineText, cwd: workingDir, outputPath } = genOpts;
|
|
2387
2518
|
const startTime = Date.now();
|
|
2388
2519
|
try {
|
|
2389
|
-
const resolvedCwd =
|
|
2390
|
-
const resolvedOutput =
|
|
2520
|
+
const resolvedCwd = resolve2(workingDir);
|
|
2521
|
+
const resolvedOutput = resolve2(outputPath);
|
|
2391
2522
|
if (resolvedOutput !== resolvedCwd && !resolvedOutput.startsWith(resolvedCwd + sep)) {
|
|
2392
2523
|
return {
|
|
2393
2524
|
data: null,
|
|
@@ -2564,6 +2695,8 @@ function buildCommonSpecInstructions(params) {
|
|
|
2564
2695
|
``,
|
|
2565
2696
|
`\`${cwd}\``,
|
|
2566
2697
|
``,
|
|
2698
|
+
formatEnvironmentPrompt(),
|
|
2699
|
+
``,
|
|
2567
2700
|
`## Instructions`,
|
|
2568
2701
|
``,
|
|
2569
2702
|
`1. **Explore the codebase** \u2014 read relevant files, search for symbols, understand the project structure, language, frameworks, conventions, and patterns. Identify the tech stack (languages, package managers, frameworks, test runners) so your spec aligns with the project's actual standards.`,
|
|
@@ -2814,7 +2947,7 @@ function buildInlineTextItem(issues, outputDir) {
|
|
|
2814
2947
|
return [{ id: filepath, details }];
|
|
2815
2948
|
}
|
|
2816
2949
|
async function resolveFileItems(issues, specCwd, concurrency) {
|
|
2817
|
-
const files = await
|
|
2950
|
+
const files = await glob2(issues, { cwd: specCwd, absolute: true });
|
|
2818
2951
|
if (files.length === 0) {
|
|
2819
2952
|
log.error(`No files matched the pattern "${Array.isArray(issues) ? issues.join(", ") : issues}".`);
|
|
2820
2953
|
return null;
|
|
@@ -3136,6 +3269,7 @@ async function runSpecPipeline(opts) {
|
|
|
3136
3269
|
import { execFile as execFile9 } from "child_process";
|
|
3137
3270
|
import { promisify as promisify9 } from "util";
|
|
3138
3271
|
import { readFile as readFile7 } from "fs/promises";
|
|
3272
|
+
import { glob as glob3 } from "glob";
|
|
3139
3273
|
|
|
3140
3274
|
// src/parser.ts
|
|
3141
3275
|
import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
@@ -3237,6 +3371,7 @@ function groupTasksByMode(tasks) {
|
|
|
3237
3371
|
// src/agents/planner.ts
|
|
3238
3372
|
init_logger();
|
|
3239
3373
|
init_file_logger();
|
|
3374
|
+
init_environment();
|
|
3240
3375
|
async function boot6(opts) {
|
|
3241
3376
|
const { provider, cwd } = opts;
|
|
3242
3377
|
if (!provider) {
|
|
@@ -3307,6 +3442,10 @@ function buildPlannerPrompt(task, cwd, fileContext, worktreeRoot) {
|
|
|
3307
3442
|
`- All relative paths must resolve within the worktree root above.`
|
|
3308
3443
|
);
|
|
3309
3444
|
}
|
|
3445
|
+
sections.push(
|
|
3446
|
+
``,
|
|
3447
|
+
formatEnvironmentPrompt()
|
|
3448
|
+
);
|
|
3310
3449
|
sections.push(
|
|
3311
3450
|
``,
|
|
3312
3451
|
`## Instructions`,
|
|
@@ -3343,6 +3482,7 @@ function buildPlannerPrompt(task, cwd, fileContext, worktreeRoot) {
|
|
|
3343
3482
|
// src/dispatcher.ts
|
|
3344
3483
|
init_logger();
|
|
3345
3484
|
init_file_logger();
|
|
3485
|
+
init_environment();
|
|
3346
3486
|
async function dispatchTask(instance, task, cwd, plan, worktreeRoot) {
|
|
3347
3487
|
try {
|
|
3348
3488
|
log.debug(`Dispatching task: ${task.file}:${task.line} \u2014 ${task.text.slice(0, 80)}`);
|
|
@@ -3375,6 +3515,8 @@ function buildPrompt(task, cwd, worktreeRoot) {
|
|
|
3375
3515
|
`**Source file:** ${task.file}`,
|
|
3376
3516
|
`**Task (line ${task.line}):** ${task.text}`,
|
|
3377
3517
|
``,
|
|
3518
|
+
getEnvironmentBlock(),
|
|
3519
|
+
``,
|
|
3378
3520
|
`Instructions:`,
|
|
3379
3521
|
`- Complete ONLY this specific task \u2014 do not work on other tasks.`,
|
|
3380
3522
|
`- Make the minimal, correct changes needed.`,
|
|
@@ -3392,6 +3534,8 @@ function buildPlannedPrompt(task, cwd, plan, worktreeRoot) {
|
|
|
3392
3534
|
`**Source file:** ${task.file}`,
|
|
3393
3535
|
`**Task (line ${task.line}):** ${task.text}`,
|
|
3394
3536
|
``,
|
|
3537
|
+
getEnvironmentBlock(),
|
|
3538
|
+
``,
|
|
3395
3539
|
`---`,
|
|
3396
3540
|
``,
|
|
3397
3541
|
`## Execution Plan`,
|
|
@@ -3466,8 +3610,9 @@ ${err.stack}` : ""}`);
|
|
|
3466
3610
|
// src/agents/commit.ts
|
|
3467
3611
|
init_logger();
|
|
3468
3612
|
init_file_logger();
|
|
3613
|
+
init_environment();
|
|
3469
3614
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
3470
|
-
import { join as join8, resolve as
|
|
3615
|
+
import { join as join8, resolve as resolve3 } from "path";
|
|
3471
3616
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3472
3617
|
async function boot8(opts) {
|
|
3473
3618
|
const { provider } = opts;
|
|
@@ -3480,7 +3625,7 @@ async function boot8(opts) {
|
|
|
3480
3625
|
name: "commit",
|
|
3481
3626
|
async generate(genOpts) {
|
|
3482
3627
|
try {
|
|
3483
|
-
const resolvedCwd =
|
|
3628
|
+
const resolvedCwd = resolve3(genOpts.cwd);
|
|
3484
3629
|
const tmpDir = join8(resolvedCwd, ".dispatch", "tmp");
|
|
3485
3630
|
await mkdir5(tmpDir, { recursive: true });
|
|
3486
3631
|
const tmpFilename = `commit-${randomUUID4()}.md`;
|
|
@@ -3542,6 +3687,8 @@ function buildCommitPrompt(opts) {
|
|
|
3542
3687
|
const sections = [
|
|
3543
3688
|
`You are a **commit message agent**. Your job is to analyze the git diff below and generate a meaningful, conventional-commit-compliant commit message, a PR title, and a PR description.`,
|
|
3544
3689
|
``,
|
|
3690
|
+
formatEnvironmentPrompt(),
|
|
3691
|
+
``,
|
|
3545
3692
|
`## Conventional Commit Guidelines`,
|
|
3546
3693
|
``,
|
|
3547
3694
|
`Follow the Conventional Commits specification (https://www.conventionalcommits.org/):`,
|
|
@@ -3663,7 +3810,7 @@ init_logger();
|
|
|
3663
3810
|
init_cleanup();
|
|
3664
3811
|
|
|
3665
3812
|
// src/helpers/worktree.ts
|
|
3666
|
-
import { join as join9, basename } from "path";
|
|
3813
|
+
import { join as join9, basename as basename2 } from "path";
|
|
3667
3814
|
import { execFile as execFile7 } from "child_process";
|
|
3668
3815
|
import { promisify as promisify7 } from "util";
|
|
3669
3816
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
@@ -3671,11 +3818,11 @@ init_logger();
|
|
|
3671
3818
|
var exec7 = promisify7(execFile7);
|
|
3672
3819
|
var WORKTREE_DIR = ".dispatch/worktrees";
|
|
3673
3820
|
async function git2(args, cwd) {
|
|
3674
|
-
const { stdout } = await exec7("git", args, { cwd });
|
|
3821
|
+
const { stdout } = await exec7("git", args, { cwd, shell: process.platform === "win32" });
|
|
3675
3822
|
return stdout;
|
|
3676
3823
|
}
|
|
3677
3824
|
function worktreeName(issueFilename) {
|
|
3678
|
-
const base =
|
|
3825
|
+
const base = basename2(issueFilename);
|
|
3679
3826
|
const withoutExt = base.replace(/\.md$/i, "");
|
|
3680
3827
|
const match = withoutExt.match(/^(\d+)/);
|
|
3681
3828
|
return match ? `issue-${match[1]}` : slugify(withoutExt);
|
|
@@ -3983,14 +4130,14 @@ init_providers();
|
|
|
3983
4130
|
|
|
3984
4131
|
// src/orchestrator/datasource-helpers.ts
|
|
3985
4132
|
init_logger();
|
|
3986
|
-
import { basename as
|
|
4133
|
+
import { basename as basename3, join as join10 } from "path";
|
|
3987
4134
|
import { mkdtemp, writeFile as writeFile7 } from "fs/promises";
|
|
3988
4135
|
import { tmpdir } from "os";
|
|
3989
4136
|
import { execFile as execFile8 } from "child_process";
|
|
3990
4137
|
import { promisify as promisify8 } from "util";
|
|
3991
4138
|
var exec8 = promisify8(execFile8);
|
|
3992
4139
|
function parseIssueFilename(filePath) {
|
|
3993
|
-
const filename =
|
|
4140
|
+
const filename = basename3(filePath);
|
|
3994
4141
|
const match = /^(\d+)-(.+)\.md$/.exec(filename);
|
|
3995
4142
|
if (!match) return null;
|
|
3996
4143
|
return { issueId: match[1], slug: match[2] };
|
|
@@ -4005,7 +4152,8 @@ async function fetchItemsById(issueIds, datasource4, fetchOpts) {
|
|
|
4005
4152
|
const item = await datasource4.fetch(id, fetchOpts);
|
|
4006
4153
|
items.push(item);
|
|
4007
4154
|
} catch (err) {
|
|
4008
|
-
|
|
4155
|
+
const prefix = id.includes("/") || id.includes("\\") || id.endsWith(".md") ? "" : "#";
|
|
4156
|
+
log.warn(`Could not fetch issue ${prefix}${id}: ${log.formatErrorChain(err)}`);
|
|
4009
4157
|
}
|
|
4010
4158
|
}
|
|
4011
4159
|
return items;
|
|
@@ -4023,8 +4171,8 @@ async function writeItemsToTempDir(items) {
|
|
|
4023
4171
|
issueDetailsByFile.set(filepath, item);
|
|
4024
4172
|
}
|
|
4025
4173
|
files.sort((a, b) => {
|
|
4026
|
-
const numA = parseInt(
|
|
4027
|
-
const numB = parseInt(
|
|
4174
|
+
const numA = parseInt(basename3(a).match(/^(\d+)/)?.[1] ?? "0", 10);
|
|
4175
|
+
const numB = parseInt(basename3(b).match(/^(\d+)/)?.[1] ?? "0", 10);
|
|
4028
4176
|
if (numA !== numB) return numA - numB;
|
|
4029
4177
|
return a.localeCompare(b);
|
|
4030
4178
|
});
|
|
@@ -4035,7 +4183,7 @@ async function getCommitSummaries(defaultBranch, cwd) {
|
|
|
4035
4183
|
const { stdout } = await exec8(
|
|
4036
4184
|
"git",
|
|
4037
4185
|
["log", `${defaultBranch}..HEAD`, "--pretty=format:%s"],
|
|
4038
|
-
{ cwd }
|
|
4186
|
+
{ cwd, shell: process.platform === "win32" }
|
|
4039
4187
|
);
|
|
4040
4188
|
return stdout.trim().split("\n").filter(Boolean);
|
|
4041
4189
|
} catch {
|
|
@@ -4047,7 +4195,7 @@ async function getBranchDiff(defaultBranch, cwd) {
|
|
|
4047
4195
|
const { stdout } = await exec8(
|
|
4048
4196
|
"git",
|
|
4049
4197
|
["diff", `${defaultBranch}..HEAD`],
|
|
4050
|
-
{ cwd, maxBuffer: 10 * 1024 * 1024 }
|
|
4198
|
+
{ cwd, maxBuffer: 10 * 1024 * 1024, shell: process.platform === "win32" }
|
|
4051
4199
|
);
|
|
4052
4200
|
return stdout;
|
|
4053
4201
|
} catch {
|
|
@@ -4058,11 +4206,11 @@ async function squashBranchCommits(defaultBranch, message, cwd) {
|
|
|
4058
4206
|
const { stdout } = await exec8(
|
|
4059
4207
|
"git",
|
|
4060
4208
|
["merge-base", defaultBranch, "HEAD"],
|
|
4061
|
-
{ cwd }
|
|
4209
|
+
{ cwd, shell: process.platform === "win32" }
|
|
4062
4210
|
);
|
|
4063
4211
|
const mergeBase = stdout.trim();
|
|
4064
|
-
await exec8("git", ["reset", "--soft", mergeBase], { cwd });
|
|
4065
|
-
await exec8("git", ["commit", "-m", message], { cwd });
|
|
4212
|
+
await exec8("git", ["reset", "--soft", mergeBase], { cwd, shell: process.platform === "win32" });
|
|
4213
|
+
await exec8("git", ["commit", "-m", message], { cwd, shell: process.platform === "win32" });
|
|
4066
4214
|
}
|
|
4067
4215
|
async function buildPrBody(details, tasks, results, defaultBranch, datasourceName, cwd) {
|
|
4068
4216
|
const sections = [];
|
|
@@ -4158,6 +4306,34 @@ init_timeout();
|
|
|
4158
4306
|
import chalk7 from "chalk";
|
|
4159
4307
|
init_file_logger();
|
|
4160
4308
|
var exec9 = promisify9(execFile9);
|
|
4309
|
+
async function resolveGlobItems(patterns, cwd) {
|
|
4310
|
+
const files = await glob3(patterns, { cwd, absolute: true });
|
|
4311
|
+
if (files.length === 0) {
|
|
4312
|
+
log.warn(`No files matched the pattern(s): ${patterns.join(", ")}`);
|
|
4313
|
+
return [];
|
|
4314
|
+
}
|
|
4315
|
+
log.info(`Matched ${files.length} file(s) from glob pattern(s)`);
|
|
4316
|
+
const items = [];
|
|
4317
|
+
for (const filePath of files) {
|
|
4318
|
+
try {
|
|
4319
|
+
const content = await readFile7(filePath, "utf-8");
|
|
4320
|
+
const title = extractTitle(content, filePath);
|
|
4321
|
+
items.push({
|
|
4322
|
+
number: filePath,
|
|
4323
|
+
title,
|
|
4324
|
+
body: content,
|
|
4325
|
+
labels: [],
|
|
4326
|
+
state: "open",
|
|
4327
|
+
url: filePath,
|
|
4328
|
+
comments: [],
|
|
4329
|
+
acceptanceCriteria: ""
|
|
4330
|
+
});
|
|
4331
|
+
} catch (err) {
|
|
4332
|
+
log.warn(`Could not read file ${filePath}: ${log.formatErrorChain(err)}`);
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
return items;
|
|
4336
|
+
}
|
|
4161
4337
|
var DEFAULT_PLAN_TIMEOUT_MIN = 10;
|
|
4162
4338
|
var DEFAULT_PLAN_RETRIES = 1;
|
|
4163
4339
|
async function runDispatchPipeline(opts, cwd) {
|
|
@@ -4223,7 +4399,14 @@ async function runDispatchPipeline(opts, cwd) {
|
|
|
4223
4399
|
}
|
|
4224
4400
|
const datasource4 = getDatasource(source);
|
|
4225
4401
|
const fetchOpts = { cwd, org, project, workItemType, iteration, area };
|
|
4226
|
-
|
|
4402
|
+
let items;
|
|
4403
|
+
if (issueIds.length > 0 && source === "md" && issueIds.some((id) => isGlobOrFilePath(id))) {
|
|
4404
|
+
items = await resolveGlobItems(issueIds, cwd);
|
|
4405
|
+
} else if (issueIds.length > 0) {
|
|
4406
|
+
items = await fetchItemsById(issueIds, datasource4, fetchOpts);
|
|
4407
|
+
} else {
|
|
4408
|
+
items = await datasource4.list(fetchOpts);
|
|
4409
|
+
}
|
|
4227
4410
|
if (items.length === 0) {
|
|
4228
4411
|
tui.state.phase = "done";
|
|
4229
4412
|
tui.stop();
|
|
@@ -4295,12 +4478,32 @@ async function runDispatchPipeline(opts, cwd) {
|
|
|
4295
4478
|
let featureBranchName;
|
|
4296
4479
|
let featureDefaultBranch;
|
|
4297
4480
|
if (feature) {
|
|
4481
|
+
if (typeof feature === "string") {
|
|
4482
|
+
if (!isValidBranchName(feature)) {
|
|
4483
|
+
log.error(`Invalid feature branch name: "${feature}"`);
|
|
4484
|
+
tui.state.phase = "done";
|
|
4485
|
+
tui.stop();
|
|
4486
|
+
return { total: allTasks.length, completed: 0, failed: allTasks.length, skipped: 0, results: [] };
|
|
4487
|
+
}
|
|
4488
|
+
featureBranchName = feature.includes("/") ? feature : `dispatch/${feature}`;
|
|
4489
|
+
} else {
|
|
4490
|
+
featureBranchName = generateFeatureBranchName();
|
|
4491
|
+
}
|
|
4298
4492
|
try {
|
|
4299
4493
|
featureDefaultBranch = await datasource4.getDefaultBranch(lifecycleOpts);
|
|
4300
4494
|
await datasource4.switchBranch(featureDefaultBranch, lifecycleOpts);
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4495
|
+
try {
|
|
4496
|
+
await datasource4.createAndSwitchBranch(featureBranchName, lifecycleOpts);
|
|
4497
|
+
log.debug(`Created feature branch ${featureBranchName} from ${featureDefaultBranch}`);
|
|
4498
|
+
} catch (createErr) {
|
|
4499
|
+
const message = log.extractMessage(createErr);
|
|
4500
|
+
if (message.includes("already exists")) {
|
|
4501
|
+
await datasource4.switchBranch(featureBranchName, lifecycleOpts);
|
|
4502
|
+
log.debug(`Switched to existing feature branch ${featureBranchName}`);
|
|
4503
|
+
} else {
|
|
4504
|
+
throw createErr;
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4304
4507
|
registerCleanup(async () => {
|
|
4305
4508
|
try {
|
|
4306
4509
|
await datasource4.switchBranch(featureDefaultBranch, lifecycleOpts);
|
|
@@ -4492,12 +4695,18 @@ ${err.stack}` : ""}`);
|
|
|
4492
4695
|
fileLogger?.info(`Execution completed successfully (${Date.now() - startTime}ms)`);
|
|
4493
4696
|
try {
|
|
4494
4697
|
const parsed = parseIssueFilename(task.file);
|
|
4698
|
+
const updatedContent = await readFile7(task.file, "utf-8");
|
|
4495
4699
|
if (parsed) {
|
|
4496
|
-
const updatedContent = await readFile7(task.file, "utf-8");
|
|
4497
4700
|
const issueDetails = issueDetailsByFile.get(task.file);
|
|
4498
4701
|
const title = issueDetails?.title ?? parsed.slug;
|
|
4499
4702
|
await datasource4.update(parsed.issueId, title, updatedContent, fetchOpts);
|
|
4500
4703
|
log.success(`Synced task completion to issue #${parsed.issueId}`);
|
|
4704
|
+
} else {
|
|
4705
|
+
const issueDetails = issueDetailsByFile.get(task.file);
|
|
4706
|
+
if (issueDetails) {
|
|
4707
|
+
await datasource4.update(issueDetails.number, issueDetails.title, updatedContent, fetchOpts);
|
|
4708
|
+
log.success(`Synced task completion to issue #${issueDetails.number}`);
|
|
4709
|
+
}
|
|
4501
4710
|
}
|
|
4502
4711
|
} catch (err) {
|
|
4503
4712
|
log.warn(`Could not sync task completion to datasource: ${log.formatErrorChain(err)}`);
|
|
@@ -4584,13 +4793,13 @@ ${err.stack}` : ""}`);
|
|
|
4584
4793
|
}
|
|
4585
4794
|
try {
|
|
4586
4795
|
await datasource4.switchBranch(featureBranchName, lifecycleOpts);
|
|
4587
|
-
await exec9("git", ["merge", branchName, "--no-ff", "-m", `merge: issue #${details.number}`], { cwd });
|
|
4796
|
+
await exec9("git", ["merge", branchName, "--no-ff", "-m", `merge: issue #${details.number}`], { cwd, shell: process.platform === "win32" });
|
|
4588
4797
|
log.debug(`Merged ${branchName} into ${featureBranchName}`);
|
|
4589
4798
|
} catch (err) {
|
|
4590
4799
|
const mergeError = `Could not merge ${branchName} into feature branch: ${log.formatErrorChain(err)}`;
|
|
4591
4800
|
log.warn(mergeError);
|
|
4592
4801
|
try {
|
|
4593
|
-
await exec9("git", ["merge", "--abort"], { cwd });
|
|
4802
|
+
await exec9("git", ["merge", "--abort"], { cwd, shell: process.platform === "win32" });
|
|
4594
4803
|
} catch {
|
|
4595
4804
|
}
|
|
4596
4805
|
for (const task of fileTasks) {
|
|
@@ -4608,7 +4817,7 @@ ${err.stack}` : ""}`);
|
|
|
4608
4817
|
return;
|
|
4609
4818
|
}
|
|
4610
4819
|
try {
|
|
4611
|
-
await exec9("git", ["branch", "-d", branchName], { cwd });
|
|
4820
|
+
await exec9("git", ["branch", "-d", branchName], { cwd, shell: process.platform === "win32" });
|
|
4612
4821
|
log.debug(`Deleted local branch ${branchName}`);
|
|
4613
4822
|
} catch (err) {
|
|
4614
4823
|
log.warn(`Could not delete local branch ${branchName}: ${log.formatErrorChain(err)}`);
|
|
@@ -4764,13 +4973,20 @@ async function dryRunMode(issueIds, cwd, source, org, project, workItemType, ite
|
|
|
4764
4973
|
username = await datasource4.getUsername(lifecycleOpts);
|
|
4765
4974
|
} catch {
|
|
4766
4975
|
}
|
|
4767
|
-
|
|
4976
|
+
let items;
|
|
4977
|
+
if (issueIds.length > 0 && source === "md" && issueIds.some((id) => isGlobOrFilePath(id))) {
|
|
4978
|
+
items = await resolveGlobItems(issueIds, cwd);
|
|
4979
|
+
} else if (issueIds.length > 0) {
|
|
4980
|
+
items = await fetchItemsById(issueIds, datasource4, fetchOpts);
|
|
4981
|
+
} else {
|
|
4982
|
+
items = await datasource4.list(fetchOpts);
|
|
4983
|
+
}
|
|
4768
4984
|
if (items.length === 0) {
|
|
4769
4985
|
const label = issueIds.length > 0 ? `issue(s) ${issueIds.join(", ")}` : `datasource: ${source}`;
|
|
4770
4986
|
log.warn("No work items found from " + label);
|
|
4771
4987
|
return { total: 0, completed: 0, failed: 0, skipped: 0, results: [] };
|
|
4772
4988
|
}
|
|
4773
|
-
const { files } = await writeItemsToTempDir(items);
|
|
4989
|
+
const { files, issueDetailsByFile } = await writeItemsToTempDir(items);
|
|
4774
4990
|
const taskFiles = [];
|
|
4775
4991
|
for (const file of files) {
|
|
4776
4992
|
const tf = await parseTaskFile(file);
|
|
@@ -4787,7 +5003,7 @@ async function dryRunMode(issueIds, cwd, source, org, project, workItemType, ite
|
|
|
4787
5003
|
`);
|
|
4788
5004
|
for (const task of allTasks) {
|
|
4789
5005
|
const parsed = parseIssueFilename(task.file);
|
|
4790
|
-
const details = parsed ? items.find((item) => item.number === parsed.issueId) :
|
|
5006
|
+
const details = parsed ? items.find((item) => item.number === parsed.issueId) : issueDetailsByFile.get(task.file);
|
|
4791
5007
|
const branchInfo = details ? ` [branch: ${datasource4.buildBranchName(details.number, details.title, username)}]` : "";
|
|
4792
5008
|
log.task(allTasks.indexOf(task), allTasks.length, `${task.file}:${task.line} \u2014 ${task.text}${branchInfo}`);
|
|
4793
5009
|
}
|
|
@@ -4960,7 +5176,7 @@ var HELP = `
|
|
|
4960
5176
|
--no-plan Skip the planner agent, dispatch directly
|
|
4961
5177
|
--no-branch Skip branch creation, push, and PR lifecycle
|
|
4962
5178
|
--no-worktree Skip git worktree isolation for parallel issues
|
|
4963
|
-
--feature
|
|
5179
|
+
--feature [name] Group issues into a single feature branch and PR
|
|
4964
5180
|
--force Ignore prior run state and re-run all tasks
|
|
4965
5181
|
--concurrency <n> Max parallel dispatches (default: min(cpus, freeMB/500), max: ${MAX_CONCURRENCY})
|
|
4966
5182
|
--provider <name> Agent backend: ${PROVIDER_NAMES.join(", ")} (default: opencode)
|
|
@@ -5006,6 +5222,8 @@ var HELP = `
|
|
|
5006
5222
|
dispatch --respec "specs/*.md"
|
|
5007
5223
|
dispatch --spec "add dark mode toggle to settings page"
|
|
5008
5224
|
dispatch --spec "feature A should do x" --provider copilot
|
|
5225
|
+
dispatch --feature
|
|
5226
|
+
dispatch --feature my-feature
|
|
5009
5227
|
dispatch config
|
|
5010
5228
|
`.trimStart();
|
|
5011
5229
|
function parseArgs(argv) {
|
|
@@ -5015,7 +5233,7 @@ function parseArgs(argv) {
|
|
|
5015
5233
|
},
|
|
5016
5234
|
writeErr: () => {
|
|
5017
5235
|
}
|
|
5018
|
-
}).helpOption(false).argument("[issueIds...]").option("-h, --help", "Show help").option("-v, --version", "Show version").option("--dry-run", "List tasks without dispatching").option("--no-plan", "Skip the planner agent").option("--no-branch", "Skip branch creation").option("--no-worktree", "Skip git worktree isolation").option("--feature", "Group issues into a single feature branch").option("--force", "Ignore prior run state").option("--verbose", "Show detailed debug output").option("--fix-tests", "Run tests and fix failures").option("--spec <values...>", "Spec mode: issue numbers, glob, or text").option("--respec [values...]", "Regenerate specs").addOption(
|
|
5236
|
+
}).helpOption(false).argument("[issueIds...]").option("-h, --help", "Show help").option("-v, --version", "Show version").option("--dry-run", "List tasks without dispatching").option("--no-plan", "Skip the planner agent").option("--no-branch", "Skip branch creation").option("--no-worktree", "Skip git worktree isolation").option("--feature [name]", "Group issues into a single feature branch").option("--force", "Ignore prior run state").option("--verbose", "Show detailed debug output").option("--fix-tests", "Run tests and fix failures").option("--spec <values...>", "Spec mode: issue numbers, glob, or text").option("--respec [values...]", "Regenerate specs").addOption(
|
|
5019
5237
|
new Option("--provider <name>", "Agent backend").choices(PROVIDER_NAMES)
|
|
5020
5238
|
).addOption(
|
|
5021
5239
|
new Option("--source <name>", "Issue source").choices(
|
|
@@ -5063,7 +5281,7 @@ function parseArgs(argv) {
|
|
|
5063
5281
|
if (isNaN(n) || n <= 0) throw new CommanderError(1, "commander.invalidArgument", "--test-timeout must be a positive number (minutes)");
|
|
5064
5282
|
return n;
|
|
5065
5283
|
}
|
|
5066
|
-
).option("--cwd <dir>", "Working directory", (val) =>
|
|
5284
|
+
).option("--cwd <dir>", "Working directory", (val) => resolve4(val)).option("--output-dir <dir>", "Output directory", (val) => resolve4(val)).option("--org <url>", "Azure DevOps organization URL").option("--project <name>", "Azure DevOps project name").option("--server-url <url>", "Provider server URL");
|
|
5067
5285
|
try {
|
|
5068
5286
|
program.parse(argv, { from: "user" });
|
|
5069
5287
|
} catch (err) {
|
|
@@ -5098,7 +5316,7 @@ function parseArgs(argv) {
|
|
|
5098
5316
|
}
|
|
5099
5317
|
}
|
|
5100
5318
|
if (opts.fixTests) args.fixTests = true;
|
|
5101
|
-
if (opts.feature) args.feature =
|
|
5319
|
+
if (opts.feature) args.feature = opts.feature;
|
|
5102
5320
|
if (opts.source !== void 0) args.issueSource = opts.source;
|
|
5103
5321
|
if (opts.concurrency !== void 0) args.concurrency = opts.concurrency;
|
|
5104
5322
|
if (opts.serverUrl !== void 0) args.serverUrl = opts.serverUrl;
|
|
@@ -5148,7 +5366,7 @@ async function main() {
|
|
|
5148
5366
|
if (rawArgv[0] === "config") {
|
|
5149
5367
|
const configProgram = new Command("dispatch-config").exitOverride().configureOutput({ writeOut: () => {
|
|
5150
5368
|
}, writeErr: () => {
|
|
5151
|
-
} }).helpOption(false).allowUnknownOption(true).allowExcessArguments(true).option("--cwd <dir>", "Working directory", (v) =>
|
|
5369
|
+
} }).helpOption(false).allowUnknownOption(true).allowExcessArguments(true).option("--cwd <dir>", "Working directory", (v) => resolve4(v));
|
|
5152
5370
|
try {
|
|
5153
5371
|
configProgram.parse(rawArgv.slice(1), { from: "user" });
|
|
5154
5372
|
} catch (err) {
|
|
@@ -5179,7 +5397,7 @@ async function main() {
|
|
|
5179
5397
|
process.exit(0);
|
|
5180
5398
|
}
|
|
5181
5399
|
if (args.version) {
|
|
5182
|
-
console.log(`dispatch v${"1.
|
|
5400
|
+
console.log(`dispatch v${"1.4.0"}`);
|
|
5183
5401
|
process.exit(0);
|
|
5184
5402
|
}
|
|
5185
5403
|
const orchestrator = await boot9({ cwd: args.cwd });
|