@pruddiman/dispatch 1.3.0 → 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 +405 -144
- 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
|
|
@@ -1030,7 +1064,8 @@ function slugify(input3, maxLength) {
|
|
|
1030
1064
|
|
|
1031
1065
|
// src/datasources/github.ts
|
|
1032
1066
|
init_logger();
|
|
1033
|
-
|
|
1067
|
+
|
|
1068
|
+
// src/helpers/branch-validation.ts
|
|
1034
1069
|
var InvalidBranchNameError = class extends Error {
|
|
1035
1070
|
constructor(branch, reason) {
|
|
1036
1071
|
const detail = reason ? ` (${reason})` : "";
|
|
@@ -1038,14 +1073,6 @@ var InvalidBranchNameError = class extends Error {
|
|
|
1038
1073
|
this.name = "InvalidBranchNameError";
|
|
1039
1074
|
}
|
|
1040
1075
|
};
|
|
1041
|
-
async function git(args, cwd) {
|
|
1042
|
-
const { stdout } = await exec("git", args, { cwd });
|
|
1043
|
-
return stdout;
|
|
1044
|
-
}
|
|
1045
|
-
async function gh(args, cwd) {
|
|
1046
|
-
const { stdout } = await exec("gh", args, { cwd });
|
|
1047
|
-
return stdout;
|
|
1048
|
-
}
|
|
1049
1076
|
var VALID_BRANCH_NAME_RE = /^[a-zA-Z0-9._\-/]+$/;
|
|
1050
1077
|
function isValidBranchName(name) {
|
|
1051
1078
|
if (name.length === 0 || name.length > 255) return false;
|
|
@@ -1057,6 +1084,17 @@ function isValidBranchName(name) {
|
|
|
1057
1084
|
if (name.includes("//")) return false;
|
|
1058
1085
|
return true;
|
|
1059
1086
|
}
|
|
1087
|
+
|
|
1088
|
+
// src/datasources/github.ts
|
|
1089
|
+
var exec = promisify(execFile);
|
|
1090
|
+
async function git(args, cwd) {
|
|
1091
|
+
const { stdout } = await exec("git", args, { cwd, shell: process.platform === "win32" });
|
|
1092
|
+
return stdout;
|
|
1093
|
+
}
|
|
1094
|
+
async function gh(args, cwd) {
|
|
1095
|
+
const { stdout } = await exec("gh", args, { cwd, shell: process.platform === "win32" });
|
|
1096
|
+
return stdout;
|
|
1097
|
+
}
|
|
1060
1098
|
function buildBranchName(issueNumber, title, username = "unknown") {
|
|
1061
1099
|
const slug = slugify(title, 50);
|
|
1062
1100
|
return `${username}/dispatch/${issueNumber}-${slug}`;
|
|
@@ -1100,7 +1138,7 @@ var datasource = {
|
|
|
1100
1138
|
"--json",
|
|
1101
1139
|
"number,title,body,labels,state,url"
|
|
1102
1140
|
],
|
|
1103
|
-
{ cwd }
|
|
1141
|
+
{ cwd, shell: process.platform === "win32" }
|
|
1104
1142
|
);
|
|
1105
1143
|
let issues;
|
|
1106
1144
|
try {
|
|
@@ -1132,7 +1170,7 @@ var datasource = {
|
|
|
1132
1170
|
"--json",
|
|
1133
1171
|
"number,title,body,labels,state,url,comments"
|
|
1134
1172
|
],
|
|
1135
|
-
{ cwd }
|
|
1173
|
+
{ cwd, shell: process.platform === "win32" }
|
|
1136
1174
|
);
|
|
1137
1175
|
let issue;
|
|
1138
1176
|
try {
|
|
@@ -1160,18 +1198,18 @@ var datasource = {
|
|
|
1160
1198
|
},
|
|
1161
1199
|
async update(issueId, title, body, opts = {}) {
|
|
1162
1200
|
const cwd = opts.cwd || process.cwd();
|
|
1163
|
-
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" });
|
|
1164
1202
|
},
|
|
1165
1203
|
async close(issueId, opts = {}) {
|
|
1166
1204
|
const cwd = opts.cwd || process.cwd();
|
|
1167
|
-
await exec("gh", ["issue", "close", issueId], { cwd });
|
|
1205
|
+
await exec("gh", ["issue", "close", issueId], { cwd, shell: process.platform === "win32" });
|
|
1168
1206
|
},
|
|
1169
1207
|
async create(title, body, opts = {}) {
|
|
1170
1208
|
const cwd = opts.cwd || process.cwd();
|
|
1171
1209
|
const { stdout } = await exec(
|
|
1172
1210
|
"gh",
|
|
1173
1211
|
["issue", "create", "--title", title, "--body", body],
|
|
1174
|
-
{ cwd }
|
|
1212
|
+
{ cwd, shell: process.platform === "win32" }
|
|
1175
1213
|
);
|
|
1176
1214
|
const url = stdout.trim();
|
|
1177
1215
|
const match = url.match(/\/issues\/(\d+)$/);
|
|
@@ -1267,13 +1305,34 @@ import { execFile as execFile2 } from "child_process";
|
|
|
1267
1305
|
import { promisify as promisify2 } from "util";
|
|
1268
1306
|
init_logger();
|
|
1269
1307
|
var exec2 = promisify2(execFile2);
|
|
1308
|
+
var doneStateCache = /* @__PURE__ */ new Map();
|
|
1309
|
+
function mapWorkItemToIssueDetails(item, id, comments, defaults) {
|
|
1310
|
+
const fields = item.fields ?? {};
|
|
1311
|
+
return {
|
|
1312
|
+
number: String(item.id ?? id),
|
|
1313
|
+
title: fields["System.Title"] ?? defaults?.title ?? "",
|
|
1314
|
+
body: fields["System.Description"] ?? defaults?.body ?? "",
|
|
1315
|
+
labels: (fields["System.Tags"] ?? "").split(";").map((t) => t.trim()).filter(Boolean),
|
|
1316
|
+
state: fields["System.State"] ?? defaults?.state ?? "",
|
|
1317
|
+
url: item._links?.html?.href ?? item.url ?? "",
|
|
1318
|
+
comments,
|
|
1319
|
+
acceptanceCriteria: fields["Microsoft.VSTS.Common.AcceptanceCriteria"] ?? "",
|
|
1320
|
+
iterationPath: fields["System.IterationPath"] || void 0,
|
|
1321
|
+
areaPath: fields["System.AreaPath"] || void 0,
|
|
1322
|
+
assignee: fields["System.AssignedTo"]?.displayName || void 0,
|
|
1323
|
+
priority: fields["Microsoft.VSTS.Common.Priority"] ?? void 0,
|
|
1324
|
+
storyPoints: fields["Microsoft.VSTS.Scheduling.StoryPoints"] ?? fields["Microsoft.VSTS.Scheduling.Effort"] ?? fields["Microsoft.VSTS.Scheduling.Size"] ?? void 0,
|
|
1325
|
+
workItemType: fields["System.WorkItemType"] || defaults?.workItemType || void 0
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1270
1328
|
async function detectWorkItemType(opts = {}) {
|
|
1271
1329
|
try {
|
|
1272
1330
|
const args = ["boards", "work-item", "type", "list", "--output", "json"];
|
|
1273
1331
|
if (opts.project) args.push("--project", opts.project);
|
|
1274
1332
|
if (opts.org) args.push("--org", opts.org);
|
|
1275
1333
|
const { stdout } = await exec2("az", args, {
|
|
1276
|
-
cwd: opts.cwd || process.cwd()
|
|
1334
|
+
cwd: opts.cwd || process.cwd(),
|
|
1335
|
+
shell: process.platform === "win32"
|
|
1277
1336
|
});
|
|
1278
1337
|
const types = JSON.parse(stdout);
|
|
1279
1338
|
if (!Array.isArray(types) || types.length === 0) return null;
|
|
@@ -1287,6 +1346,48 @@ async function detectWorkItemType(opts = {}) {
|
|
|
1287
1346
|
return null;
|
|
1288
1347
|
}
|
|
1289
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
|
+
}
|
|
1290
1391
|
var datasource2 = {
|
|
1291
1392
|
name: "azdevops",
|
|
1292
1393
|
supportsGit() {
|
|
@@ -1295,6 +1396,7 @@ var datasource2 = {
|
|
|
1295
1396
|
async list(opts = {}) {
|
|
1296
1397
|
const conditions = [
|
|
1297
1398
|
"[System.State] <> 'Closed'",
|
|
1399
|
+
"[System.State] <> 'Done'",
|
|
1298
1400
|
"[System.State] <> 'Removed'"
|
|
1299
1401
|
];
|
|
1300
1402
|
if (opts.iteration) {
|
|
@@ -1317,7 +1419,8 @@ var datasource2 = {
|
|
|
1317
1419
|
if (opts.org) args.push("--org", opts.org);
|
|
1318
1420
|
if (opts.project) args.push("--project", opts.project);
|
|
1319
1421
|
const { stdout } = await exec2("az", args, {
|
|
1320
|
-
cwd: opts.cwd || process.cwd()
|
|
1422
|
+
cwd: opts.cwd || process.cwd(),
|
|
1423
|
+
shell: process.platform === "win32"
|
|
1321
1424
|
});
|
|
1322
1425
|
let data;
|
|
1323
1426
|
try {
|
|
@@ -1325,17 +1428,51 @@ var datasource2 = {
|
|
|
1325
1428
|
} catch {
|
|
1326
1429
|
throw new Error(`Failed to parse Azure CLI output: ${stdout.slice(0, 200)}`);
|
|
1327
1430
|
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1431
|
+
if (!Array.isArray(data)) return [];
|
|
1432
|
+
const ids = data.map((row) => String(row.id ?? row.ID ?? "")).filter(Boolean);
|
|
1433
|
+
if (ids.length === 0) return [];
|
|
1434
|
+
try {
|
|
1435
|
+
const batchArgs = [
|
|
1436
|
+
"boards",
|
|
1437
|
+
"work-item",
|
|
1438
|
+
"show",
|
|
1439
|
+
"--id",
|
|
1440
|
+
...ids,
|
|
1441
|
+
"--output",
|
|
1442
|
+
"json"
|
|
1443
|
+
];
|
|
1444
|
+
if (opts.org) batchArgs.push("--org", opts.org);
|
|
1445
|
+
if (opts.project) batchArgs.push("--project", opts.project);
|
|
1446
|
+
const { stdout: batchStdout } = await exec2("az", batchArgs, {
|
|
1447
|
+
cwd: opts.cwd || process.cwd(),
|
|
1448
|
+
shell: process.platform === "win32"
|
|
1449
|
+
});
|
|
1450
|
+
let batchItems;
|
|
1451
|
+
try {
|
|
1452
|
+
batchItems = JSON.parse(batchStdout);
|
|
1453
|
+
} catch {
|
|
1454
|
+
throw new Error(`Failed to parse Azure CLI output: ${batchStdout.slice(0, 200)}`);
|
|
1455
|
+
}
|
|
1456
|
+
const itemsArray = Array.isArray(batchItems) ? batchItems : [batchItems];
|
|
1457
|
+
const commentsArray = [];
|
|
1458
|
+
const CONCURRENCY = 5;
|
|
1459
|
+
for (let i = 0; i < itemsArray.length; i += CONCURRENCY) {
|
|
1460
|
+
const batch = itemsArray.slice(i, i + CONCURRENCY);
|
|
1461
|
+
const batchResults = await Promise.all(
|
|
1462
|
+
batch.map((item) => fetchComments(String(item.id), opts))
|
|
1463
|
+
);
|
|
1464
|
+
commentsArray.push(...batchResults);
|
|
1336
1465
|
}
|
|
1466
|
+
return itemsArray.map(
|
|
1467
|
+
(item, i) => mapWorkItemToIssueDetails(item, String(item.id), commentsArray[i])
|
|
1468
|
+
);
|
|
1469
|
+
} catch (err) {
|
|
1470
|
+
log.debug(`Batch work-item show failed, falling back to individual fetches: ${log.extractMessage(err)}`);
|
|
1471
|
+
const results = await Promise.all(
|
|
1472
|
+
ids.map((id) => datasource2.fetch(id, opts))
|
|
1473
|
+
);
|
|
1474
|
+
return results;
|
|
1337
1475
|
}
|
|
1338
|
-
return items;
|
|
1339
1476
|
},
|
|
1340
1477
|
async fetch(issueId, opts = {}) {
|
|
1341
1478
|
const args = [
|
|
@@ -1354,7 +1491,8 @@ var datasource2 = {
|
|
|
1354
1491
|
args.push("--project", opts.project);
|
|
1355
1492
|
}
|
|
1356
1493
|
const { stdout } = await exec2("az", args, {
|
|
1357
|
-
cwd: opts.cwd || process.cwd()
|
|
1494
|
+
cwd: opts.cwd || process.cwd(),
|
|
1495
|
+
shell: process.platform === "win32"
|
|
1358
1496
|
});
|
|
1359
1497
|
let item;
|
|
1360
1498
|
try {
|
|
@@ -1362,24 +1500,8 @@ var datasource2 = {
|
|
|
1362
1500
|
} catch {
|
|
1363
1501
|
throw new Error(`Failed to parse Azure CLI output: ${stdout.slice(0, 200)}`);
|
|
1364
1502
|
}
|
|
1365
|
-
const fields = item.fields ?? {};
|
|
1366
1503
|
const comments = await fetchComments(issueId, opts);
|
|
1367
|
-
return
|
|
1368
|
-
number: String(item.id ?? issueId),
|
|
1369
|
-
title: fields["System.Title"] ?? "",
|
|
1370
|
-
body: fields["System.Description"] ?? "",
|
|
1371
|
-
labels: (fields["System.Tags"] ?? "").split(";").map((t) => t.trim()).filter(Boolean),
|
|
1372
|
-
state: fields["System.State"] ?? "",
|
|
1373
|
-
url: item._links?.html?.href ?? item.url ?? "",
|
|
1374
|
-
comments,
|
|
1375
|
-
acceptanceCriteria: fields["Microsoft.VSTS.Common.AcceptanceCriteria"] ?? "",
|
|
1376
|
-
iterationPath: fields["System.IterationPath"] || void 0,
|
|
1377
|
-
areaPath: fields["System.AreaPath"] || void 0,
|
|
1378
|
-
assignee: fields["System.AssignedTo"]?.displayName || void 0,
|
|
1379
|
-
priority: fields["Microsoft.VSTS.Common.Priority"] ?? void 0,
|
|
1380
|
-
storyPoints: fields["Microsoft.VSTS.Scheduling.StoryPoints"] ?? fields["Microsoft.VSTS.Scheduling.Effort"] ?? fields["Microsoft.VSTS.Scheduling.Size"] ?? void 0,
|
|
1381
|
-
workItemType: fields["System.WorkItemType"] || void 0
|
|
1382
|
-
};
|
|
1504
|
+
return mapWorkItemToIssueDetails(item, issueId, comments);
|
|
1383
1505
|
},
|
|
1384
1506
|
async update(issueId, title, body, opts = {}) {
|
|
1385
1507
|
const args = [
|
|
@@ -1395,9 +1517,34 @@ var datasource2 = {
|
|
|
1395
1517
|
];
|
|
1396
1518
|
if (opts.org) args.push("--org", opts.org);
|
|
1397
1519
|
if (opts.project) args.push("--project", opts.project);
|
|
1398
|
-
await exec2("az", args, { cwd: opts.cwd || process.cwd() });
|
|
1520
|
+
await exec2("az", args, { cwd: opts.cwd || process.cwd(), shell: process.platform === "win32" });
|
|
1399
1521
|
},
|
|
1400
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";
|
|
1401
1548
|
const args = [
|
|
1402
1549
|
"boards",
|
|
1403
1550
|
"work-item",
|
|
@@ -1405,11 +1552,11 @@ var datasource2 = {
|
|
|
1405
1552
|
"--id",
|
|
1406
1553
|
issueId,
|
|
1407
1554
|
"--state",
|
|
1408
|
-
|
|
1555
|
+
state
|
|
1409
1556
|
];
|
|
1410
1557
|
if (opts.org) args.push("--org", opts.org);
|
|
1411
1558
|
if (opts.project) args.push("--project", opts.project);
|
|
1412
|
-
await exec2("az", args, { cwd: opts.cwd || process.cwd() });
|
|
1559
|
+
await exec2("az", args, { cwd: opts.cwd || process.cwd(), shell: process.platform === "win32" });
|
|
1413
1560
|
},
|
|
1414
1561
|
async create(title, body, opts = {}) {
|
|
1415
1562
|
const workItemType = opts.workItemType ?? await detectWorkItemType(opts);
|
|
@@ -1434,7 +1581,8 @@ var datasource2 = {
|
|
|
1434
1581
|
if (opts.org) args.push("--org", opts.org);
|
|
1435
1582
|
if (opts.project) args.push("--project", opts.project);
|
|
1436
1583
|
const { stdout } = await exec2("az", args, {
|
|
1437
|
-
cwd: opts.cwd || process.cwd()
|
|
1584
|
+
cwd: opts.cwd || process.cwd(),
|
|
1585
|
+
shell: process.platform === "win32"
|
|
1438
1586
|
});
|
|
1439
1587
|
let item;
|
|
1440
1588
|
try {
|
|
@@ -1442,32 +1590,28 @@ var datasource2 = {
|
|
|
1442
1590
|
} catch {
|
|
1443
1591
|
throw new Error(`Failed to parse Azure CLI output: ${stdout.slice(0, 200)}`);
|
|
1444
1592
|
}
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
state: fields["System.State"] ?? "New",
|
|
1452
|
-
url: item._links?.html?.href ?? item.url ?? "",
|
|
1453
|
-
comments: [],
|
|
1454
|
-
acceptanceCriteria: fields["Microsoft.VSTS.Common.AcceptanceCriteria"] ?? "",
|
|
1455
|
-
iterationPath: fields["System.IterationPath"] || void 0,
|
|
1456
|
-
areaPath: fields["System.AreaPath"] || void 0,
|
|
1457
|
-
assignee: fields["System.AssignedTo"]?.displayName || void 0,
|
|
1458
|
-
priority: fields["Microsoft.VSTS.Common.Priority"] ?? void 0,
|
|
1459
|
-
storyPoints: fields["Microsoft.VSTS.Scheduling.StoryPoints"] ?? fields["Microsoft.VSTS.Scheduling.Effort"] ?? fields["Microsoft.VSTS.Scheduling.Size"] ?? void 0,
|
|
1460
|
-
workItemType: fields["System.WorkItemType"] || workItemType
|
|
1461
|
-
};
|
|
1593
|
+
return mapWorkItemToIssueDetails(item, String(item.id), [], {
|
|
1594
|
+
title,
|
|
1595
|
+
body,
|
|
1596
|
+
state: "New",
|
|
1597
|
+
workItemType
|
|
1598
|
+
});
|
|
1462
1599
|
},
|
|
1463
1600
|
async getDefaultBranch(opts) {
|
|
1464
1601
|
try {
|
|
1465
|
-
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" });
|
|
1466
1603
|
const parts = stdout.trim().split("/");
|
|
1467
|
-
|
|
1468
|
-
|
|
1604
|
+
const branch = parts[parts.length - 1];
|
|
1605
|
+
if (!isValidBranchName(branch)) {
|
|
1606
|
+
throw new InvalidBranchNameError(branch, "from symbolic-ref output");
|
|
1607
|
+
}
|
|
1608
|
+
return branch;
|
|
1609
|
+
} catch (err) {
|
|
1610
|
+
if (err instanceof InvalidBranchNameError) {
|
|
1611
|
+
throw err;
|
|
1612
|
+
}
|
|
1469
1613
|
try {
|
|
1470
|
-
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" });
|
|
1471
1615
|
return "main";
|
|
1472
1616
|
} catch {
|
|
1473
1617
|
return "master";
|
|
@@ -1476,19 +1620,19 @@ var datasource2 = {
|
|
|
1476
1620
|
},
|
|
1477
1621
|
async getUsername(opts) {
|
|
1478
1622
|
try {
|
|
1479
|
-
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" });
|
|
1480
1624
|
const name = slugify(stdout.trim());
|
|
1481
1625
|
if (name) return name;
|
|
1482
1626
|
} catch {
|
|
1483
1627
|
}
|
|
1484
1628
|
try {
|
|
1485
|
-
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" });
|
|
1486
1630
|
const name = slugify(stdout.trim());
|
|
1487
1631
|
if (name) return name;
|
|
1488
1632
|
} catch {
|
|
1489
1633
|
}
|
|
1490
1634
|
try {
|
|
1491
|
-
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" });
|
|
1492
1636
|
const principal = stdout.trim();
|
|
1493
1637
|
const prefix = principal.split("@")[0];
|
|
1494
1638
|
const name = slugify(prefix);
|
|
@@ -1499,33 +1643,40 @@ var datasource2 = {
|
|
|
1499
1643
|
},
|
|
1500
1644
|
buildBranchName(issueNumber, title, username) {
|
|
1501
1645
|
const slug = slugify(title, 50);
|
|
1502
|
-
|
|
1646
|
+
const branch = `${username}/dispatch/${issueNumber}-${slug}`;
|
|
1647
|
+
if (!isValidBranchName(branch)) {
|
|
1648
|
+
throw new InvalidBranchNameError(branch);
|
|
1649
|
+
}
|
|
1650
|
+
return branch;
|
|
1503
1651
|
},
|
|
1504
1652
|
async createAndSwitchBranch(branchName, opts) {
|
|
1653
|
+
if (!isValidBranchName(branchName)) {
|
|
1654
|
+
throw new InvalidBranchNameError(branchName);
|
|
1655
|
+
}
|
|
1505
1656
|
try {
|
|
1506
|
-
await exec2("git", ["checkout", "-b", branchName], { cwd: opts.cwd });
|
|
1657
|
+
await exec2("git", ["checkout", "-b", branchName], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1507
1658
|
} catch (err) {
|
|
1508
1659
|
const message = log.extractMessage(err);
|
|
1509
1660
|
if (message.includes("already exists")) {
|
|
1510
|
-
await exec2("git", ["checkout", branchName], { cwd: opts.cwd });
|
|
1661
|
+
await exec2("git", ["checkout", branchName], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1511
1662
|
} else {
|
|
1512
1663
|
throw err;
|
|
1513
1664
|
}
|
|
1514
1665
|
}
|
|
1515
1666
|
},
|
|
1516
1667
|
async switchBranch(branchName, opts) {
|
|
1517
|
-
await exec2("git", ["checkout", branchName], { cwd: opts.cwd });
|
|
1668
|
+
await exec2("git", ["checkout", branchName], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1518
1669
|
},
|
|
1519
1670
|
async pushBranch(branchName, opts) {
|
|
1520
|
-
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" });
|
|
1521
1672
|
},
|
|
1522
1673
|
async commitAllChanges(message, opts) {
|
|
1523
|
-
await exec2("git", ["add", "-A"], { cwd: opts.cwd });
|
|
1524
|
-
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" });
|
|
1525
1676
|
if (!stdout.trim()) {
|
|
1526
1677
|
return;
|
|
1527
1678
|
}
|
|
1528
|
-
await exec2("git", ["commit", "-m", message], { cwd: opts.cwd });
|
|
1679
|
+
await exec2("git", ["commit", "-m", message], { cwd: opts.cwd, shell: process.platform === "win32" });
|
|
1529
1680
|
},
|
|
1530
1681
|
async createPullRequest(branchName, issueNumber, title, body, opts) {
|
|
1531
1682
|
try {
|
|
@@ -1546,7 +1697,7 @@ var datasource2 = {
|
|
|
1546
1697
|
"--output",
|
|
1547
1698
|
"json"
|
|
1548
1699
|
],
|
|
1549
|
-
{ cwd: opts.cwd }
|
|
1700
|
+
{ cwd: opts.cwd, shell: process.platform === "win32" }
|
|
1550
1701
|
);
|
|
1551
1702
|
let pr;
|
|
1552
1703
|
try {
|
|
@@ -1571,7 +1722,7 @@ var datasource2 = {
|
|
|
1571
1722
|
"--output",
|
|
1572
1723
|
"json"
|
|
1573
1724
|
],
|
|
1574
|
-
{ cwd: opts.cwd }
|
|
1725
|
+
{ cwd: opts.cwd, shell: process.platform === "win32" }
|
|
1575
1726
|
);
|
|
1576
1727
|
let prs;
|
|
1577
1728
|
try {
|
|
@@ -1607,7 +1758,8 @@ async function fetchComments(workItemId, opts) {
|
|
|
1607
1758
|
args.push("--project", opts.project);
|
|
1608
1759
|
}
|
|
1609
1760
|
const { stdout } = await exec2("az", args, {
|
|
1610
|
-
cwd: opts.cwd || process.cwd()
|
|
1761
|
+
cwd: opts.cwd || process.cwd(),
|
|
1762
|
+
shell: process.platform === "win32"
|
|
1611
1763
|
});
|
|
1612
1764
|
const data = JSON.parse(stdout);
|
|
1613
1765
|
if (data.comments && Array.isArray(data.comments)) {
|
|
@@ -1627,8 +1779,9 @@ async function fetchComments(workItemId, opts) {
|
|
|
1627
1779
|
// src/datasources/md.ts
|
|
1628
1780
|
import { execFile as execFile3 } from "child_process";
|
|
1629
1781
|
import { readFile, writeFile, readdir, mkdir, rename } from "fs/promises";
|
|
1630
|
-
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";
|
|
1631
1783
|
import { promisify as promisify3 } from "util";
|
|
1784
|
+
import { glob } from "glob";
|
|
1632
1785
|
|
|
1633
1786
|
// src/helpers/errors.ts
|
|
1634
1787
|
var UnsupportedOperationError = class extends Error {
|
|
@@ -1649,6 +1802,15 @@ function resolveDir(opts) {
|
|
|
1649
1802
|
const cwd = opts?.cwd ?? process.cwd();
|
|
1650
1803
|
return join2(cwd, DEFAULT_DIR);
|
|
1651
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
|
+
}
|
|
1652
1814
|
function extractTitle(content, filename) {
|
|
1653
1815
|
const match = content.match(/^#\s+(.+)$/m);
|
|
1654
1816
|
if (match) return match[1].trim();
|
|
@@ -1683,6 +1845,19 @@ var datasource3 = {
|
|
|
1683
1845
|
return false;
|
|
1684
1846
|
},
|
|
1685
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
|
+
}
|
|
1686
1861
|
const dir = resolveDir(opts);
|
|
1687
1862
|
let entries;
|
|
1688
1863
|
try {
|
|
@@ -1700,23 +1875,20 @@ var datasource3 = {
|
|
|
1700
1875
|
return results;
|
|
1701
1876
|
},
|
|
1702
1877
|
async fetch(issueId, opts) {
|
|
1703
|
-
const
|
|
1704
|
-
const filename = issueId.endsWith(".md") ? issueId : `${issueId}.md`;
|
|
1705
|
-
const filePath = join2(dir, filename);
|
|
1878
|
+
const filePath = resolveFilePath(issueId, opts);
|
|
1706
1879
|
const content = await readFile(filePath, "utf-8");
|
|
1880
|
+
const filename = basename(filePath);
|
|
1881
|
+
const dir = dirname2(filePath);
|
|
1707
1882
|
return toIssueDetails(filename, content, dir);
|
|
1708
1883
|
},
|
|
1709
1884
|
async update(issueId, _title, body, opts) {
|
|
1710
|
-
const
|
|
1711
|
-
const filename = issueId.endsWith(".md") ? issueId : `${issueId}.md`;
|
|
1712
|
-
const filePath = join2(dir, filename);
|
|
1885
|
+
const filePath = resolveFilePath(issueId, opts);
|
|
1713
1886
|
await writeFile(filePath, body, "utf-8");
|
|
1714
1887
|
},
|
|
1715
1888
|
async close(issueId, opts) {
|
|
1716
|
-
const
|
|
1717
|
-
const filename =
|
|
1718
|
-
const
|
|
1719
|
-
const archiveDir = join2(dir, "archive");
|
|
1889
|
+
const filePath = resolveFilePath(issueId, opts);
|
|
1890
|
+
const filename = basename(filePath);
|
|
1891
|
+
const archiveDir = join2(dirname2(filePath), "archive");
|
|
1720
1892
|
await mkdir(archiveDir, { recursive: true });
|
|
1721
1893
|
await rename(filePath, join2(archiveDir, filename));
|
|
1722
1894
|
},
|
|
@@ -1733,7 +1905,7 @@ var datasource3 = {
|
|
|
1733
1905
|
},
|
|
1734
1906
|
async getUsername(opts) {
|
|
1735
1907
|
try {
|
|
1736
|
-
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" });
|
|
1737
1909
|
const name = stdout.trim();
|
|
1738
1910
|
if (!name) return "local";
|
|
1739
1911
|
return slugify(name);
|
|
@@ -1782,7 +1954,8 @@ function getDatasource(name) {
|
|
|
1782
1954
|
async function getGitRemoteUrl(cwd) {
|
|
1783
1955
|
try {
|
|
1784
1956
|
const { stdout } = await exec4("git", ["remote", "get-url", "origin"], {
|
|
1785
|
-
cwd
|
|
1957
|
+
cwd,
|
|
1958
|
+
shell: process.platform === "win32"
|
|
1786
1959
|
});
|
|
1787
1960
|
return stdout.trim() || null;
|
|
1788
1961
|
} catch {
|
|
@@ -2057,7 +2230,7 @@ import { constants } from "fs";
|
|
|
2057
2230
|
// src/config.ts
|
|
2058
2231
|
init_providers();
|
|
2059
2232
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
|
|
2060
|
-
import { join as join4, dirname as
|
|
2233
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
2061
2234
|
|
|
2062
2235
|
// src/config-prompts.ts
|
|
2063
2236
|
init_logger();
|
|
@@ -2248,7 +2421,7 @@ async function loadConfig(configDir) {
|
|
|
2248
2421
|
}
|
|
2249
2422
|
async function saveConfig(config, configDir) {
|
|
2250
2423
|
const configPath = getConfigPath(configDir);
|
|
2251
|
-
await mkdir2(
|
|
2424
|
+
await mkdir2(dirname3(configPath), { recursive: true });
|
|
2252
2425
|
await writeFile3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2253
2426
|
}
|
|
2254
2427
|
async function handleConfigCommand(_argv, configDir) {
|
|
@@ -2323,15 +2496,16 @@ async function resolveCliConfig(args) {
|
|
|
2323
2496
|
// src/orchestrator/spec-pipeline.ts
|
|
2324
2497
|
import { join as join7 } from "path";
|
|
2325
2498
|
import { mkdir as mkdir4, readFile as readFile5, rename as rename2, unlink as unlink2 } from "fs/promises";
|
|
2326
|
-
import { glob } from "glob";
|
|
2499
|
+
import { glob as glob2 } from "glob";
|
|
2327
2500
|
init_providers();
|
|
2328
2501
|
|
|
2329
2502
|
// src/agents/spec.ts
|
|
2330
2503
|
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4, unlink } from "fs/promises";
|
|
2331
|
-
import { join as join6, resolve, sep } from "path";
|
|
2504
|
+
import { join as join6, resolve as resolve2, sep } from "path";
|
|
2332
2505
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2333
2506
|
init_logger();
|
|
2334
2507
|
init_file_logger();
|
|
2508
|
+
init_environment();
|
|
2335
2509
|
async function boot5(opts) {
|
|
2336
2510
|
const { provider } = opts;
|
|
2337
2511
|
if (!provider) {
|
|
@@ -2343,8 +2517,8 @@ async function boot5(opts) {
|
|
|
2343
2517
|
const { issue, filePath, fileContent, inlineText, cwd: workingDir, outputPath } = genOpts;
|
|
2344
2518
|
const startTime = Date.now();
|
|
2345
2519
|
try {
|
|
2346
|
-
const resolvedCwd =
|
|
2347
|
-
const resolvedOutput =
|
|
2520
|
+
const resolvedCwd = resolve2(workingDir);
|
|
2521
|
+
const resolvedOutput = resolve2(outputPath);
|
|
2348
2522
|
if (resolvedOutput !== resolvedCwd && !resolvedOutput.startsWith(resolvedCwd + sep)) {
|
|
2349
2523
|
return {
|
|
2350
2524
|
data: null,
|
|
@@ -2521,6 +2695,8 @@ function buildCommonSpecInstructions(params) {
|
|
|
2521
2695
|
``,
|
|
2522
2696
|
`\`${cwd}\``,
|
|
2523
2697
|
``,
|
|
2698
|
+
formatEnvironmentPrompt(),
|
|
2699
|
+
``,
|
|
2524
2700
|
`## Instructions`,
|
|
2525
2701
|
``,
|
|
2526
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.`,
|
|
@@ -2771,7 +2947,7 @@ function buildInlineTextItem(issues, outputDir) {
|
|
|
2771
2947
|
return [{ id: filepath, details }];
|
|
2772
2948
|
}
|
|
2773
2949
|
async function resolveFileItems(issues, specCwd, concurrency) {
|
|
2774
|
-
const files = await
|
|
2950
|
+
const files = await glob2(issues, { cwd: specCwd, absolute: true });
|
|
2775
2951
|
if (files.length === 0) {
|
|
2776
2952
|
log.error(`No files matched the pattern "${Array.isArray(issues) ? issues.join(", ") : issues}".`);
|
|
2777
2953
|
return null;
|
|
@@ -3093,6 +3269,7 @@ async function runSpecPipeline(opts) {
|
|
|
3093
3269
|
import { execFile as execFile9 } from "child_process";
|
|
3094
3270
|
import { promisify as promisify9 } from "util";
|
|
3095
3271
|
import { readFile as readFile7 } from "fs/promises";
|
|
3272
|
+
import { glob as glob3 } from "glob";
|
|
3096
3273
|
|
|
3097
3274
|
// src/parser.ts
|
|
3098
3275
|
import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
@@ -3194,6 +3371,7 @@ function groupTasksByMode(tasks) {
|
|
|
3194
3371
|
// src/agents/planner.ts
|
|
3195
3372
|
init_logger();
|
|
3196
3373
|
init_file_logger();
|
|
3374
|
+
init_environment();
|
|
3197
3375
|
async function boot6(opts) {
|
|
3198
3376
|
const { provider, cwd } = opts;
|
|
3199
3377
|
if (!provider) {
|
|
@@ -3264,6 +3442,10 @@ function buildPlannerPrompt(task, cwd, fileContext, worktreeRoot) {
|
|
|
3264
3442
|
`- All relative paths must resolve within the worktree root above.`
|
|
3265
3443
|
);
|
|
3266
3444
|
}
|
|
3445
|
+
sections.push(
|
|
3446
|
+
``,
|
|
3447
|
+
formatEnvironmentPrompt()
|
|
3448
|
+
);
|
|
3267
3449
|
sections.push(
|
|
3268
3450
|
``,
|
|
3269
3451
|
`## Instructions`,
|
|
@@ -3300,6 +3482,7 @@ function buildPlannerPrompt(task, cwd, fileContext, worktreeRoot) {
|
|
|
3300
3482
|
// src/dispatcher.ts
|
|
3301
3483
|
init_logger();
|
|
3302
3484
|
init_file_logger();
|
|
3485
|
+
init_environment();
|
|
3303
3486
|
async function dispatchTask(instance, task, cwd, plan, worktreeRoot) {
|
|
3304
3487
|
try {
|
|
3305
3488
|
log.debug(`Dispatching task: ${task.file}:${task.line} \u2014 ${task.text.slice(0, 80)}`);
|
|
@@ -3332,6 +3515,8 @@ function buildPrompt(task, cwd, worktreeRoot) {
|
|
|
3332
3515
|
`**Source file:** ${task.file}`,
|
|
3333
3516
|
`**Task (line ${task.line}):** ${task.text}`,
|
|
3334
3517
|
``,
|
|
3518
|
+
getEnvironmentBlock(),
|
|
3519
|
+
``,
|
|
3335
3520
|
`Instructions:`,
|
|
3336
3521
|
`- Complete ONLY this specific task \u2014 do not work on other tasks.`,
|
|
3337
3522
|
`- Make the minimal, correct changes needed.`,
|
|
@@ -3349,6 +3534,8 @@ function buildPlannedPrompt(task, cwd, plan, worktreeRoot) {
|
|
|
3349
3534
|
`**Source file:** ${task.file}`,
|
|
3350
3535
|
`**Task (line ${task.line}):** ${task.text}`,
|
|
3351
3536
|
``,
|
|
3537
|
+
getEnvironmentBlock(),
|
|
3538
|
+
``,
|
|
3352
3539
|
`---`,
|
|
3353
3540
|
``,
|
|
3354
3541
|
`## Execution Plan`,
|
|
@@ -3423,8 +3610,9 @@ ${err.stack}` : ""}`);
|
|
|
3423
3610
|
// src/agents/commit.ts
|
|
3424
3611
|
init_logger();
|
|
3425
3612
|
init_file_logger();
|
|
3613
|
+
init_environment();
|
|
3426
3614
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
3427
|
-
import { join as join8, resolve as
|
|
3615
|
+
import { join as join8, resolve as resolve3 } from "path";
|
|
3428
3616
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3429
3617
|
async function boot8(opts) {
|
|
3430
3618
|
const { provider } = opts;
|
|
@@ -3437,7 +3625,7 @@ async function boot8(opts) {
|
|
|
3437
3625
|
name: "commit",
|
|
3438
3626
|
async generate(genOpts) {
|
|
3439
3627
|
try {
|
|
3440
|
-
const resolvedCwd =
|
|
3628
|
+
const resolvedCwd = resolve3(genOpts.cwd);
|
|
3441
3629
|
const tmpDir = join8(resolvedCwd, ".dispatch", "tmp");
|
|
3442
3630
|
await mkdir5(tmpDir, { recursive: true });
|
|
3443
3631
|
const tmpFilename = `commit-${randomUUID4()}.md`;
|
|
@@ -3499,6 +3687,8 @@ function buildCommitPrompt(opts) {
|
|
|
3499
3687
|
const sections = [
|
|
3500
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.`,
|
|
3501
3689
|
``,
|
|
3690
|
+
formatEnvironmentPrompt(),
|
|
3691
|
+
``,
|
|
3502
3692
|
`## Conventional Commit Guidelines`,
|
|
3503
3693
|
``,
|
|
3504
3694
|
`Follow the Conventional Commits specification (https://www.conventionalcommits.org/):`,
|
|
@@ -3620,7 +3810,7 @@ init_logger();
|
|
|
3620
3810
|
init_cleanup();
|
|
3621
3811
|
|
|
3622
3812
|
// src/helpers/worktree.ts
|
|
3623
|
-
import { join as join9, basename } from "path";
|
|
3813
|
+
import { join as join9, basename as basename2 } from "path";
|
|
3624
3814
|
import { execFile as execFile7 } from "child_process";
|
|
3625
3815
|
import { promisify as promisify7 } from "util";
|
|
3626
3816
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
@@ -3628,11 +3818,11 @@ init_logger();
|
|
|
3628
3818
|
var exec7 = promisify7(execFile7);
|
|
3629
3819
|
var WORKTREE_DIR = ".dispatch/worktrees";
|
|
3630
3820
|
async function git2(args, cwd) {
|
|
3631
|
-
const { stdout } = await exec7("git", args, { cwd });
|
|
3821
|
+
const { stdout } = await exec7("git", args, { cwd, shell: process.platform === "win32" });
|
|
3632
3822
|
return stdout;
|
|
3633
3823
|
}
|
|
3634
3824
|
function worktreeName(issueFilename) {
|
|
3635
|
-
const base =
|
|
3825
|
+
const base = basename2(issueFilename);
|
|
3636
3826
|
const withoutExt = base.replace(/\.md$/i, "");
|
|
3637
3827
|
const match = withoutExt.match(/^(\d+)/);
|
|
3638
3828
|
return match ? `issue-${match[1]}` : slugify(withoutExt);
|
|
@@ -3940,14 +4130,14 @@ init_providers();
|
|
|
3940
4130
|
|
|
3941
4131
|
// src/orchestrator/datasource-helpers.ts
|
|
3942
4132
|
init_logger();
|
|
3943
|
-
import { basename as
|
|
4133
|
+
import { basename as basename3, join as join10 } from "path";
|
|
3944
4134
|
import { mkdtemp, writeFile as writeFile7 } from "fs/promises";
|
|
3945
4135
|
import { tmpdir } from "os";
|
|
3946
4136
|
import { execFile as execFile8 } from "child_process";
|
|
3947
4137
|
import { promisify as promisify8 } from "util";
|
|
3948
4138
|
var exec8 = promisify8(execFile8);
|
|
3949
4139
|
function parseIssueFilename(filePath) {
|
|
3950
|
-
const filename =
|
|
4140
|
+
const filename = basename3(filePath);
|
|
3951
4141
|
const match = /^(\d+)-(.+)\.md$/.exec(filename);
|
|
3952
4142
|
if (!match) return null;
|
|
3953
4143
|
return { issueId: match[1], slug: match[2] };
|
|
@@ -3962,7 +4152,8 @@ async function fetchItemsById(issueIds, datasource4, fetchOpts) {
|
|
|
3962
4152
|
const item = await datasource4.fetch(id, fetchOpts);
|
|
3963
4153
|
items.push(item);
|
|
3964
4154
|
} catch (err) {
|
|
3965
|
-
|
|
4155
|
+
const prefix = id.includes("/") || id.includes("\\") || id.endsWith(".md") ? "" : "#";
|
|
4156
|
+
log.warn(`Could not fetch issue ${prefix}${id}: ${log.formatErrorChain(err)}`);
|
|
3966
4157
|
}
|
|
3967
4158
|
}
|
|
3968
4159
|
return items;
|
|
@@ -3980,8 +4171,8 @@ async function writeItemsToTempDir(items) {
|
|
|
3980
4171
|
issueDetailsByFile.set(filepath, item);
|
|
3981
4172
|
}
|
|
3982
4173
|
files.sort((a, b) => {
|
|
3983
|
-
const numA = parseInt(
|
|
3984
|
-
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);
|
|
3985
4176
|
if (numA !== numB) return numA - numB;
|
|
3986
4177
|
return a.localeCompare(b);
|
|
3987
4178
|
});
|
|
@@ -3992,7 +4183,7 @@ async function getCommitSummaries(defaultBranch, cwd) {
|
|
|
3992
4183
|
const { stdout } = await exec8(
|
|
3993
4184
|
"git",
|
|
3994
4185
|
["log", `${defaultBranch}..HEAD`, "--pretty=format:%s"],
|
|
3995
|
-
{ cwd }
|
|
4186
|
+
{ cwd, shell: process.platform === "win32" }
|
|
3996
4187
|
);
|
|
3997
4188
|
return stdout.trim().split("\n").filter(Boolean);
|
|
3998
4189
|
} catch {
|
|
@@ -4004,7 +4195,7 @@ async function getBranchDiff(defaultBranch, cwd) {
|
|
|
4004
4195
|
const { stdout } = await exec8(
|
|
4005
4196
|
"git",
|
|
4006
4197
|
["diff", `${defaultBranch}..HEAD`],
|
|
4007
|
-
{ cwd, maxBuffer: 10 * 1024 * 1024 }
|
|
4198
|
+
{ cwd, maxBuffer: 10 * 1024 * 1024, shell: process.platform === "win32" }
|
|
4008
4199
|
);
|
|
4009
4200
|
return stdout;
|
|
4010
4201
|
} catch {
|
|
@@ -4015,11 +4206,11 @@ async function squashBranchCommits(defaultBranch, message, cwd) {
|
|
|
4015
4206
|
const { stdout } = await exec8(
|
|
4016
4207
|
"git",
|
|
4017
4208
|
["merge-base", defaultBranch, "HEAD"],
|
|
4018
|
-
{ cwd }
|
|
4209
|
+
{ cwd, shell: process.platform === "win32" }
|
|
4019
4210
|
);
|
|
4020
4211
|
const mergeBase = stdout.trim();
|
|
4021
|
-
await exec8("git", ["reset", "--soft", mergeBase], { cwd });
|
|
4022
|
-
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" });
|
|
4023
4214
|
}
|
|
4024
4215
|
async function buildPrBody(details, tasks, results, defaultBranch, datasourceName, cwd) {
|
|
4025
4216
|
const sections = [];
|
|
@@ -4115,6 +4306,34 @@ init_timeout();
|
|
|
4115
4306
|
import chalk7 from "chalk";
|
|
4116
4307
|
init_file_logger();
|
|
4117
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
|
+
}
|
|
4118
4337
|
var DEFAULT_PLAN_TIMEOUT_MIN = 10;
|
|
4119
4338
|
var DEFAULT_PLAN_RETRIES = 1;
|
|
4120
4339
|
async function runDispatchPipeline(opts, cwd) {
|
|
@@ -4180,7 +4399,14 @@ async function runDispatchPipeline(opts, cwd) {
|
|
|
4180
4399
|
}
|
|
4181
4400
|
const datasource4 = getDatasource(source);
|
|
4182
4401
|
const fetchOpts = { cwd, org, project, workItemType, iteration, area };
|
|
4183
|
-
|
|
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
|
+
}
|
|
4184
4410
|
if (items.length === 0) {
|
|
4185
4411
|
tui.state.phase = "done";
|
|
4186
4412
|
tui.stop();
|
|
@@ -4252,12 +4478,32 @@ async function runDispatchPipeline(opts, cwd) {
|
|
|
4252
4478
|
let featureBranchName;
|
|
4253
4479
|
let featureDefaultBranch;
|
|
4254
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
|
+
}
|
|
4255
4492
|
try {
|
|
4256
4493
|
featureDefaultBranch = await datasource4.getDefaultBranch(lifecycleOpts);
|
|
4257
4494
|
await datasource4.switchBranch(featureDefaultBranch, lifecycleOpts);
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
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
|
+
}
|
|
4261
4507
|
registerCleanup(async () => {
|
|
4262
4508
|
try {
|
|
4263
4509
|
await datasource4.switchBranch(featureDefaultBranch, lifecycleOpts);
|
|
@@ -4449,12 +4695,18 @@ ${err.stack}` : ""}`);
|
|
|
4449
4695
|
fileLogger?.info(`Execution completed successfully (${Date.now() - startTime}ms)`);
|
|
4450
4696
|
try {
|
|
4451
4697
|
const parsed = parseIssueFilename(task.file);
|
|
4698
|
+
const updatedContent = await readFile7(task.file, "utf-8");
|
|
4452
4699
|
if (parsed) {
|
|
4453
|
-
const updatedContent = await readFile7(task.file, "utf-8");
|
|
4454
4700
|
const issueDetails = issueDetailsByFile.get(task.file);
|
|
4455
4701
|
const title = issueDetails?.title ?? parsed.slug;
|
|
4456
4702
|
await datasource4.update(parsed.issueId, title, updatedContent, fetchOpts);
|
|
4457
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
|
+
}
|
|
4458
4710
|
}
|
|
4459
4711
|
} catch (err) {
|
|
4460
4712
|
log.warn(`Could not sync task completion to datasource: ${log.formatErrorChain(err)}`);
|
|
@@ -4541,13 +4793,13 @@ ${err.stack}` : ""}`);
|
|
|
4541
4793
|
}
|
|
4542
4794
|
try {
|
|
4543
4795
|
await datasource4.switchBranch(featureBranchName, lifecycleOpts);
|
|
4544
|
-
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" });
|
|
4545
4797
|
log.debug(`Merged ${branchName} into ${featureBranchName}`);
|
|
4546
4798
|
} catch (err) {
|
|
4547
4799
|
const mergeError = `Could not merge ${branchName} into feature branch: ${log.formatErrorChain(err)}`;
|
|
4548
4800
|
log.warn(mergeError);
|
|
4549
4801
|
try {
|
|
4550
|
-
await exec9("git", ["merge", "--abort"], { cwd });
|
|
4802
|
+
await exec9("git", ["merge", "--abort"], { cwd, shell: process.platform === "win32" });
|
|
4551
4803
|
} catch {
|
|
4552
4804
|
}
|
|
4553
4805
|
for (const task of fileTasks) {
|
|
@@ -4565,7 +4817,7 @@ ${err.stack}` : ""}`);
|
|
|
4565
4817
|
return;
|
|
4566
4818
|
}
|
|
4567
4819
|
try {
|
|
4568
|
-
await exec9("git", ["branch", "-d", branchName], { cwd });
|
|
4820
|
+
await exec9("git", ["branch", "-d", branchName], { cwd, shell: process.platform === "win32" });
|
|
4569
4821
|
log.debug(`Deleted local branch ${branchName}`);
|
|
4570
4822
|
} catch (err) {
|
|
4571
4823
|
log.warn(`Could not delete local branch ${branchName}: ${log.formatErrorChain(err)}`);
|
|
@@ -4721,13 +4973,20 @@ async function dryRunMode(issueIds, cwd, source, org, project, workItemType, ite
|
|
|
4721
4973
|
username = await datasource4.getUsername(lifecycleOpts);
|
|
4722
4974
|
} catch {
|
|
4723
4975
|
}
|
|
4724
|
-
|
|
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
|
+
}
|
|
4725
4984
|
if (items.length === 0) {
|
|
4726
4985
|
const label = issueIds.length > 0 ? `issue(s) ${issueIds.join(", ")}` : `datasource: ${source}`;
|
|
4727
4986
|
log.warn("No work items found from " + label);
|
|
4728
4987
|
return { total: 0, completed: 0, failed: 0, skipped: 0, results: [] };
|
|
4729
4988
|
}
|
|
4730
|
-
const { files } = await writeItemsToTempDir(items);
|
|
4989
|
+
const { files, issueDetailsByFile } = await writeItemsToTempDir(items);
|
|
4731
4990
|
const taskFiles = [];
|
|
4732
4991
|
for (const file of files) {
|
|
4733
4992
|
const tf = await parseTaskFile(file);
|
|
@@ -4744,7 +5003,7 @@ async function dryRunMode(issueIds, cwd, source, org, project, workItemType, ite
|
|
|
4744
5003
|
`);
|
|
4745
5004
|
for (const task of allTasks) {
|
|
4746
5005
|
const parsed = parseIssueFilename(task.file);
|
|
4747
|
-
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);
|
|
4748
5007
|
const branchInfo = details ? ` [branch: ${datasource4.buildBranchName(details.number, details.title, username)}]` : "";
|
|
4749
5008
|
log.task(allTasks.indexOf(task), allTasks.length, `${task.file}:${task.line} \u2014 ${task.text}${branchInfo}`);
|
|
4750
5009
|
}
|
|
@@ -4917,7 +5176,7 @@ var HELP = `
|
|
|
4917
5176
|
--no-plan Skip the planner agent, dispatch directly
|
|
4918
5177
|
--no-branch Skip branch creation, push, and PR lifecycle
|
|
4919
5178
|
--no-worktree Skip git worktree isolation for parallel issues
|
|
4920
|
-
--feature
|
|
5179
|
+
--feature [name] Group issues into a single feature branch and PR
|
|
4921
5180
|
--force Ignore prior run state and re-run all tasks
|
|
4922
5181
|
--concurrency <n> Max parallel dispatches (default: min(cpus, freeMB/500), max: ${MAX_CONCURRENCY})
|
|
4923
5182
|
--provider <name> Agent backend: ${PROVIDER_NAMES.join(", ")} (default: opencode)
|
|
@@ -4963,6 +5222,8 @@ var HELP = `
|
|
|
4963
5222
|
dispatch --respec "specs/*.md"
|
|
4964
5223
|
dispatch --spec "add dark mode toggle to settings page"
|
|
4965
5224
|
dispatch --spec "feature A should do x" --provider copilot
|
|
5225
|
+
dispatch --feature
|
|
5226
|
+
dispatch --feature my-feature
|
|
4966
5227
|
dispatch config
|
|
4967
5228
|
`.trimStart();
|
|
4968
5229
|
function parseArgs(argv) {
|
|
@@ -4972,7 +5233,7 @@ function parseArgs(argv) {
|
|
|
4972
5233
|
},
|
|
4973
5234
|
writeErr: () => {
|
|
4974
5235
|
}
|
|
4975
|
-
}).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(
|
|
4976
5237
|
new Option("--provider <name>", "Agent backend").choices(PROVIDER_NAMES)
|
|
4977
5238
|
).addOption(
|
|
4978
5239
|
new Option("--source <name>", "Issue source").choices(
|
|
@@ -5020,7 +5281,7 @@ function parseArgs(argv) {
|
|
|
5020
5281
|
if (isNaN(n) || n <= 0) throw new CommanderError(1, "commander.invalidArgument", "--test-timeout must be a positive number (minutes)");
|
|
5021
5282
|
return n;
|
|
5022
5283
|
}
|
|
5023
|
-
).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");
|
|
5024
5285
|
try {
|
|
5025
5286
|
program.parse(argv, { from: "user" });
|
|
5026
5287
|
} catch (err) {
|
|
@@ -5055,7 +5316,7 @@ function parseArgs(argv) {
|
|
|
5055
5316
|
}
|
|
5056
5317
|
}
|
|
5057
5318
|
if (opts.fixTests) args.fixTests = true;
|
|
5058
|
-
if (opts.feature) args.feature =
|
|
5319
|
+
if (opts.feature) args.feature = opts.feature;
|
|
5059
5320
|
if (opts.source !== void 0) args.issueSource = opts.source;
|
|
5060
5321
|
if (opts.concurrency !== void 0) args.concurrency = opts.concurrency;
|
|
5061
5322
|
if (opts.serverUrl !== void 0) args.serverUrl = opts.serverUrl;
|
|
@@ -5105,7 +5366,7 @@ async function main() {
|
|
|
5105
5366
|
if (rawArgv[0] === "config") {
|
|
5106
5367
|
const configProgram = new Command("dispatch-config").exitOverride().configureOutput({ writeOut: () => {
|
|
5107
5368
|
}, writeErr: () => {
|
|
5108
|
-
} }).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));
|
|
5109
5370
|
try {
|
|
5110
5371
|
configProgram.parse(rawArgv.slice(1), { from: "user" });
|
|
5111
5372
|
} catch (err) {
|
|
@@ -5136,7 +5397,7 @@ async function main() {
|
|
|
5136
5397
|
process.exit(0);
|
|
5137
5398
|
}
|
|
5138
5399
|
if (args.version) {
|
|
5139
|
-
console.log(`dispatch v${"1.
|
|
5400
|
+
console.log(`dispatch v${"1.4.0"}`);
|
|
5140
5401
|
process.exit(0);
|
|
5141
5402
|
}
|
|
5142
5403
|
const orchestrator = await boot9({ cwd: args.cwd });
|