automata-cli 0.2.0-develop.26 → 0.2.0-develop.40
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/index.js +322 -33
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -233,9 +233,59 @@ var configCommand = new Command("config").description("Configure automata settin
|
|
|
233
233
|
import { Command as Command2 } from "commander";
|
|
234
234
|
|
|
235
235
|
// src/git/gitService.ts
|
|
236
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
237
|
+
|
|
238
|
+
// src/config/azdoService.ts
|
|
236
239
|
import { spawnSync } from "child_process";
|
|
237
240
|
function run(cmd, args) {
|
|
238
241
|
const result = spawnSync(cmd, args, { encoding: "utf8" });
|
|
242
|
+
if (result.error) {
|
|
243
|
+
const err = result.error;
|
|
244
|
+
if (err.code === "ENOENT") {
|
|
245
|
+
throw new Error("`azdo` CLI is not installed or not on PATH.");
|
|
246
|
+
}
|
|
247
|
+
throw new Error(err.message);
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
stdout: result.stdout ?? "",
|
|
251
|
+
stderr: result.stderr ?? "",
|
|
252
|
+
status: result.status ?? 1
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function mapStatus(azdoStatus) {
|
|
256
|
+
switch (azdoStatus) {
|
|
257
|
+
case "active":
|
|
258
|
+
return "OPEN";
|
|
259
|
+
case "completed":
|
|
260
|
+
return "MERGED";
|
|
261
|
+
case "abandoned":
|
|
262
|
+
return "CLOSED";
|
|
263
|
+
default:
|
|
264
|
+
return azdoStatus.toUpperCase();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function getPrInfo() {
|
|
268
|
+
const { stdout, stderr, status } = run("azdo", ["pr", "status", "--json"]);
|
|
269
|
+
if (status !== 0) {
|
|
270
|
+
throw new Error(stderr.trim() || "Failed to query Azure DevOps PR status. Is `azdo` installed and authenticated?");
|
|
271
|
+
}
|
|
272
|
+
const parsed = JSON.parse(stdout);
|
|
273
|
+
if (parsed.pullRequests.length === 0) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
const pr = parsed.pullRequests[0];
|
|
277
|
+
return {
|
|
278
|
+
number: pr.id,
|
|
279
|
+
title: pr.title,
|
|
280
|
+
state: mapStatus(pr.status),
|
|
281
|
+
url: pr.url,
|
|
282
|
+
checks: []
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/git/gitService.ts
|
|
287
|
+
function run2(cmd, args) {
|
|
288
|
+
const result = spawnSync2(cmd, args, { encoding: "utf8" });
|
|
239
289
|
return {
|
|
240
290
|
stdout: result.stdout ?? "",
|
|
241
291
|
stderr: result.stderr ?? "",
|
|
@@ -243,19 +293,55 @@ function run(cmd, args) {
|
|
|
243
293
|
};
|
|
244
294
|
}
|
|
245
295
|
function getCurrentBranch() {
|
|
246
|
-
const { stdout, status } =
|
|
296
|
+
const { stdout, status } = run2("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
247
297
|
if (status !== 0) {
|
|
248
298
|
throw new Error("Failed to determine current branch. Are you inside a git repository?");
|
|
249
299
|
}
|
|
250
300
|
return stdout.trim();
|
|
251
301
|
}
|
|
252
|
-
function
|
|
253
|
-
const { stdout,
|
|
302
|
+
function parseOwnerRepo() {
|
|
303
|
+
const { stdout, status } = run2("git", ["remote", "get-url", "origin"]);
|
|
304
|
+
if (status !== 0) return null;
|
|
305
|
+
const url = stdout.trim();
|
|
306
|
+
const https = url.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
307
|
+
if (https) return https[1];
|
|
308
|
+
const ssh = url.match(/github\.com:([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
309
|
+
if (ssh) return ssh[1];
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
function extractLastMarkdownUrl(markdown) {
|
|
313
|
+
const matches = [...markdown.matchAll(/\]\((https?:\/\/[^)]+)\)/g)];
|
|
314
|
+
return matches.length > 0 ? matches[matches.length - 1][1] ?? null : null;
|
|
315
|
+
}
|
|
316
|
+
function fetchCheckRunOutputs(ownerRepo, sha) {
|
|
317
|
+
const { stdout, status } = run2("gh", [
|
|
318
|
+
"api",
|
|
319
|
+
`repos/${ownerRepo}/commits/${sha}/check-runs`,
|
|
320
|
+
"--jq",
|
|
321
|
+
".check_runs[] | {name, html_url, details_url, output}"
|
|
322
|
+
]);
|
|
323
|
+
const map = /* @__PURE__ */ new Map();
|
|
324
|
+
if (status !== 0) return map;
|
|
325
|
+
for (const line of stdout.trim().split("\n")) {
|
|
326
|
+
if (!line) continue;
|
|
327
|
+
try {
|
|
328
|
+
const item = JSON.parse(line);
|
|
329
|
+
const title = item.output?.title ?? "";
|
|
330
|
+
const summaryUrl = item.output?.summary ? extractLastMarkdownUrl(item.output.summary) : null;
|
|
331
|
+
const detailsUrl = summaryUrl ?? (item.details_url !== item.html_url ? item.details_url : "") ?? item.html_url;
|
|
332
|
+
map.set(item.name, { title, detailsUrl });
|
|
333
|
+
} catch {
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return map;
|
|
337
|
+
}
|
|
338
|
+
function getPrInfoGh(branch) {
|
|
339
|
+
const { stdout, stderr, status } = run2("gh", [
|
|
254
340
|
"pr",
|
|
255
341
|
"view",
|
|
256
342
|
branch,
|
|
257
343
|
"--json",
|
|
258
|
-
"number,title,state,url,statusCheckRollup"
|
|
344
|
+
"number,title,state,url,headRefOid,statusCheckRollup"
|
|
259
345
|
]);
|
|
260
346
|
if (status !== 0) {
|
|
261
347
|
if (stderr.includes("no pull requests found") || stderr.includes("Could not resolve")) {
|
|
@@ -264,45 +350,127 @@ function getPrInfo(branch) {
|
|
|
264
350
|
throw new Error(stderr.trim() || "Failed to query GitHub. Is `gh` installed and authenticated?");
|
|
265
351
|
}
|
|
266
352
|
const raw = JSON.parse(stdout);
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
353
|
+
const failedChecks = (raw.statusCheckRollup ?? []).filter(
|
|
354
|
+
(c) => c.conclusion !== null && ["FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "CANCELLED"].includes(c.conclusion)
|
|
355
|
+
);
|
|
356
|
+
const ownerRepo = failedChecks.length > 0 ? parseOwnerRepo() : null;
|
|
357
|
+
const checkOutputs = ownerRepo ? fetchCheckRunOutputs(ownerRepo, raw.headRefOid) : /* @__PURE__ */ new Map();
|
|
358
|
+
const checks = (raw.statusCheckRollup ?? []).map((c) => {
|
|
359
|
+
const enriched = checkOutputs.get(c.name);
|
|
360
|
+
return {
|
|
361
|
+
name: c.name,
|
|
362
|
+
status: c.status,
|
|
363
|
+
conclusion: c.conclusion,
|
|
364
|
+
description: enriched?.title || c.description || "",
|
|
365
|
+
detailsUrl: enriched?.detailsUrl || c.detailsUrl || ""
|
|
366
|
+
};
|
|
367
|
+
});
|
|
274
368
|
return { number: raw.number, title: raw.title, state: raw.state, url: raw.url, checks };
|
|
275
369
|
}
|
|
370
|
+
function getPrInfo2(branch) {
|
|
371
|
+
const config = readConfig();
|
|
372
|
+
if (config.remoteType === "azdo") {
|
|
373
|
+
return getPrInfo();
|
|
374
|
+
}
|
|
375
|
+
return getPrInfoGh(branch);
|
|
376
|
+
}
|
|
276
377
|
function isUpstreamGone(branch) {
|
|
277
|
-
const { status } =
|
|
378
|
+
const { status } = run2("git", ["ls-remote", "--exit-code", "--heads", "origin", branch]);
|
|
278
379
|
return status !== 0;
|
|
279
380
|
}
|
|
280
381
|
function hasUncommittedChanges() {
|
|
281
|
-
const { stdout } =
|
|
382
|
+
const { stdout } = run2("git", ["status", "--porcelain"]);
|
|
282
383
|
return stdout.trim().length > 0;
|
|
283
384
|
}
|
|
284
385
|
function checkoutAndPull(targetBranch) {
|
|
285
|
-
const checkout =
|
|
386
|
+
const checkout = run2("git", ["checkout", targetBranch]);
|
|
286
387
|
if (checkout.status !== 0) {
|
|
287
388
|
throw new Error(`Failed to checkout ${targetBranch}: ${checkout.stderr.trim()}`);
|
|
288
389
|
}
|
|
289
|
-
const pull =
|
|
390
|
+
const pull = run2("git", ["pull"]);
|
|
290
391
|
if (pull.status !== 0) {
|
|
291
392
|
throw new Error(`Failed to pull ${targetBranch}: ${pull.stderr.trim()}`);
|
|
292
393
|
}
|
|
293
394
|
}
|
|
294
395
|
function fetchPrune() {
|
|
295
|
-
const result =
|
|
396
|
+
const result = run2("git", ["fetch", "--prune"]);
|
|
296
397
|
if (result.status !== 0) {
|
|
297
398
|
throw new Error(`Failed to fetch --prune: ${result.stderr.trim()}`);
|
|
298
399
|
}
|
|
299
400
|
}
|
|
300
401
|
function deleteLocalBranch(branch) {
|
|
301
|
-
const result =
|
|
402
|
+
const result = run2("git", ["branch", "-D", branch]);
|
|
302
403
|
if (result.status !== 0) {
|
|
303
404
|
throw new Error(`Failed to delete branch ${branch}: ${result.stderr.trim()}`);
|
|
304
405
|
}
|
|
305
406
|
}
|
|
407
|
+
var REVIEW_THREADS_QUERY = `
|
|
408
|
+
query($owner:String!,$repo:String!,$prNumber:Int!){
|
|
409
|
+
repository(owner:$owner,name:$repo){
|
|
410
|
+
pullRequest(number:$prNumber){
|
|
411
|
+
reviewThreads(first:100){
|
|
412
|
+
nodes{
|
|
413
|
+
isResolved
|
|
414
|
+
isOutdated
|
|
415
|
+
comments(first:1){
|
|
416
|
+
nodes{ author{login} body path line createdAt }
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}`.trim();
|
|
423
|
+
function getPrCommentsGh(branch) {
|
|
424
|
+
const prView = run2("gh", ["pr", "view", branch, "--json", "number"]);
|
|
425
|
+
if (prView.status !== 0) {
|
|
426
|
+
if (prView.stderr.includes("no pull requests found") || prView.stderr.includes("Could not resolve")) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
throw new Error(prView.stderr.trim() || "Failed to query GitHub. Is `gh` installed and authenticated?");
|
|
430
|
+
}
|
|
431
|
+
const { number: prNumber } = JSON.parse(prView.stdout);
|
|
432
|
+
const ownerRepo = parseOwnerRepo();
|
|
433
|
+
if (!ownerRepo) {
|
|
434
|
+
throw new Error("Could not determine GitHub owner/repo from git remote. Is 'origin' set to a GitHub URL?");
|
|
435
|
+
}
|
|
436
|
+
const slashIdx = ownerRepo.indexOf("/");
|
|
437
|
+
const owner = ownerRepo.slice(0, slashIdx);
|
|
438
|
+
const repo = ownerRepo.slice(slashIdx + 1);
|
|
439
|
+
const gql = run2("gh", [
|
|
440
|
+
"api",
|
|
441
|
+
"graphql",
|
|
442
|
+
"-f",
|
|
443
|
+
`query=${REVIEW_THREADS_QUERY}`,
|
|
444
|
+
"-f",
|
|
445
|
+
`owner=${owner}`,
|
|
446
|
+
"-f",
|
|
447
|
+
`repo=${repo}`,
|
|
448
|
+
"-F",
|
|
449
|
+
`prNumber=${String(prNumber)}`
|
|
450
|
+
]);
|
|
451
|
+
if (gql.status !== 0) {
|
|
452
|
+
throw new Error(gql.stderr.trim() || "Failed to query GitHub GraphQL API.");
|
|
453
|
+
}
|
|
454
|
+
const response = JSON.parse(gql.stdout);
|
|
455
|
+
const threads = response.data.repository.pullRequest.reviewThreads.nodes;
|
|
456
|
+
return threads.filter((t) => !t.isResolved && t.comments.nodes.length > 0).map((t) => {
|
|
457
|
+
const c = t.comments.nodes[0];
|
|
458
|
+
return {
|
|
459
|
+
author: c.author.login,
|
|
460
|
+
body: c.body,
|
|
461
|
+
path: c.path,
|
|
462
|
+
line: c.line ?? null,
|
|
463
|
+
createdAt: c.createdAt
|
|
464
|
+
};
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
function getPrComments(branch) {
|
|
468
|
+
const config = readConfig();
|
|
469
|
+
if (config.remoteType === "azdo") {
|
|
470
|
+
return "unsupported";
|
|
471
|
+
}
|
|
472
|
+
return getPrCommentsGh(branch);
|
|
473
|
+
}
|
|
306
474
|
|
|
307
475
|
// src/commands/git.ts
|
|
308
476
|
var FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "CANCELLED"]);
|
|
@@ -317,7 +485,7 @@ function checkSymbol(check) {
|
|
|
317
485
|
function formatCheckSummary(checks) {
|
|
318
486
|
const running = checks.some((c) => c.status !== "COMPLETED");
|
|
319
487
|
const failed = checks.filter((c) => c.conclusion !== null && FAIL_CONCLUSIONS.has(c.conclusion));
|
|
320
|
-
const errors = failed.length === 0 ? "none" : failed.map((c) => `${c.name}: ${c.description.trim() || "no details available"}`).join("; ");
|
|
488
|
+
const errors = failed.length === 0 ? "none" : failed.map((c) => `${c.name}: ${c.description.trim() || c.detailsUrl || "no details available"}`).join("; ");
|
|
321
489
|
return `Checks Running: ${String(running)}
|
|
322
490
|
Check Errors: ${errors}
|
|
323
491
|
`;
|
|
@@ -330,13 +498,32 @@ function formatChecks(checks) {
|
|
|
330
498
|
const pending = check.status !== "COMPLETED" ? " (pending)" : "";
|
|
331
499
|
lines.push(` ${sym} ${check.name}${pending}`);
|
|
332
500
|
if (check.conclusion !== null && FAIL_CONCLUSIONS.has(check.conclusion)) {
|
|
333
|
-
const
|
|
334
|
-
|
|
501
|
+
const desc = check.description.trim();
|
|
502
|
+
const url = check.detailsUrl.trim();
|
|
503
|
+
if (desc) lines.push(` Details: ${desc}`);
|
|
504
|
+
if (url) lines.push(` URL: ${url}`);
|
|
505
|
+
if (!desc && !url) lines.push(` Details: (no details available)`);
|
|
335
506
|
}
|
|
336
507
|
}
|
|
337
508
|
return lines.join("\n") + "\n";
|
|
338
509
|
}
|
|
339
|
-
|
|
510
|
+
function sleep(ms) {
|
|
511
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
512
|
+
}
|
|
513
|
+
function formatFailedChecks(failed) {
|
|
514
|
+
const lines = [];
|
|
515
|
+
for (const check of failed) {
|
|
516
|
+
lines.push(` \u2717 ${check.name}`);
|
|
517
|
+
const desc = check.description.trim();
|
|
518
|
+
const url = check.detailsUrl.trim();
|
|
519
|
+
if (desc) lines.push(` Details: ${desc}`);
|
|
520
|
+
if (url) lines.push(` URL: ${url}`);
|
|
521
|
+
if (!desc && !url) lines.push(` Details: (no details available)`);
|
|
522
|
+
}
|
|
523
|
+
return lines.join("\n") + "\n";
|
|
524
|
+
}
|
|
525
|
+
var POLL_INTERVAL_MS = 1e4;
|
|
526
|
+
var getPrInfoCmd = new Command2("get-pr-info").description("Show pull request info for the current branch").option("--json", "Output as JSON").option("--wait-finish-checks", "Poll until all checks complete, then report pass/fail (exit 1 on failure)").addHelpText(
|
|
340
527
|
"after",
|
|
341
528
|
`
|
|
342
529
|
Check status symbols:
|
|
@@ -347,7 +534,7 @@ Check status symbols:
|
|
|
347
534
|
|
|
348
535
|
Failure details are printed beneath each \u2717 check.
|
|
349
536
|
See docs/git.md for full output reference.`
|
|
350
|
-
).action((options) => {
|
|
537
|
+
).action(async (options) => {
|
|
351
538
|
let branch;
|
|
352
539
|
try {
|
|
353
540
|
branch = getCurrentBranch();
|
|
@@ -356,9 +543,51 @@ See docs/git.md for full output reference.`
|
|
|
356
543
|
`);
|
|
357
544
|
process.exit(1);
|
|
358
545
|
}
|
|
546
|
+
if (options.waitFinishChecks) {
|
|
547
|
+
let pr2;
|
|
548
|
+
while (true) {
|
|
549
|
+
try {
|
|
550
|
+
pr2 = getPrInfo2(branch);
|
|
551
|
+
} catch (err) {
|
|
552
|
+
process.stderr.write(`Error: ${err.message}
|
|
553
|
+
`);
|
|
554
|
+
process.exit(1);
|
|
555
|
+
}
|
|
556
|
+
if (pr2 === null) {
|
|
557
|
+
process.stderr.write(`Error: No pull request found for branch: ${branch}
|
|
558
|
+
`);
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}
|
|
561
|
+
const running = pr2.checks.filter((c) => c.status !== "COMPLETED");
|
|
562
|
+
if (running.length === 0) break;
|
|
563
|
+
process.stdout.write(`Waiting for ${running.length} check(s) to complete...
|
|
564
|
+
`);
|
|
565
|
+
await sleep(POLL_INTERVAL_MS);
|
|
566
|
+
}
|
|
567
|
+
const failed = pr2.checks.filter((c) => c.conclusion !== null && FAIL_CONCLUSIONS.has(c.conclusion));
|
|
568
|
+
if (failed.length === 0) {
|
|
569
|
+
if (options.json) {
|
|
570
|
+
process.stdout.write(JSON.stringify({ result: "passed" }, null, 2) + "\n");
|
|
571
|
+
} else {
|
|
572
|
+
process.stdout.write(`All checks passed. \u2713
|
|
573
|
+
`);
|
|
574
|
+
}
|
|
575
|
+
process.exit(0);
|
|
576
|
+
} else {
|
|
577
|
+
if (options.json) {
|
|
578
|
+
process.stdout.write(JSON.stringify({ result: "failed", failed }, null, 2) + "\n");
|
|
579
|
+
} else {
|
|
580
|
+
process.stdout.write(`${failed.length} check(s) failed:
|
|
581
|
+
`);
|
|
582
|
+
process.stdout.write(formatFailedChecks(failed));
|
|
583
|
+
}
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
359
588
|
let pr;
|
|
360
589
|
try {
|
|
361
|
-
pr =
|
|
590
|
+
pr = getPrInfo2(branch);
|
|
362
591
|
} catch (err) {
|
|
363
592
|
process.stderr.write(`Error: ${err.message}
|
|
364
593
|
`);
|
|
@@ -381,6 +610,64 @@ URL: ${pr.url}
|
|
|
381
610
|
process.stdout.write(formatChecks(pr.checks));
|
|
382
611
|
}
|
|
383
612
|
});
|
|
613
|
+
var ANSI_ESCAPE_RE = new RegExp("\x1B(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])", "g");
|
|
614
|
+
var CONTROL_CHARS_RE = new RegExp("[\0-\b\v\f-\x7F]", "g");
|
|
615
|
+
function sanitizeText(text) {
|
|
616
|
+
return text.replace(ANSI_ESCAPE_RE, "").replace(CONTROL_CHARS_RE, "");
|
|
617
|
+
}
|
|
618
|
+
var getPrCommentsCmd = new Command2("get-pr-comments").description("List open (unresolved) review comments on the pull request for the current branch (GitHub only)").option("--json", "Output as JSON array").addHelpText(
|
|
619
|
+
"after",
|
|
620
|
+
`
|
|
621
|
+
Only GitHub (remoteType: gh) is supported. Azure DevOps is not supported.
|
|
622
|
+
See docs/azdo-gap.md for details.`
|
|
623
|
+
).action((options) => {
|
|
624
|
+
let branch;
|
|
625
|
+
try {
|
|
626
|
+
branch = getCurrentBranch();
|
|
627
|
+
} catch (err) {
|
|
628
|
+
process.stderr.write(`Error: ${err.message}
|
|
629
|
+
`);
|
|
630
|
+
process.exit(1);
|
|
631
|
+
}
|
|
632
|
+
let comments;
|
|
633
|
+
try {
|
|
634
|
+
comments = getPrComments(branch);
|
|
635
|
+
} catch (err) {
|
|
636
|
+
process.stderr.write(`Error: ${err.message}
|
|
637
|
+
`);
|
|
638
|
+
process.exit(1);
|
|
639
|
+
}
|
|
640
|
+
if (comments === "unsupported") {
|
|
641
|
+
process.stderr.write(
|
|
642
|
+
`Error: get-pr-comments is not supported for Azure DevOps. See docs/azdo-gap.md for details.
|
|
643
|
+
`
|
|
644
|
+
);
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
if (comments === null) {
|
|
648
|
+
process.stderr.write(`Error: No pull request found for branch: ${branch}
|
|
649
|
+
`);
|
|
650
|
+
process.exit(1);
|
|
651
|
+
}
|
|
652
|
+
if (options.json) {
|
|
653
|
+
process.stdout.write(JSON.stringify(comments, null, 2) + "\n");
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
if (comments.length === 0) {
|
|
657
|
+
process.stdout.write(`No open comments.
|
|
658
|
+
`);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const lines = [];
|
|
662
|
+
for (const c of comments) {
|
|
663
|
+
const loc = c.line !== null ? `${c.path}:${String(c.line)}` : `${c.path}:(file)`;
|
|
664
|
+
const safeBody = sanitizeText(c.body);
|
|
665
|
+
const safeAuthor = sanitizeText(c.author);
|
|
666
|
+
lines.push(`[${safeAuthor}] on ${loc}
|
|
667
|
+
${safeBody}`);
|
|
668
|
+
}
|
|
669
|
+
process.stdout.write(lines.join("\n\n") + "\n");
|
|
670
|
+
});
|
|
384
671
|
var finishFeatureCmd = new Command2("finish-feature").description("Clean up a merged feature branch: checkout develop, pull, and delete local branch").action(() => {
|
|
385
672
|
let branch;
|
|
386
673
|
try {
|
|
@@ -402,7 +689,7 @@ var finishFeatureCmd = new Command2("finish-feature").description("Clean up a me
|
|
|
402
689
|
}
|
|
403
690
|
let pr;
|
|
404
691
|
try {
|
|
405
|
-
pr =
|
|
692
|
+
pr = getPrInfo2(branch);
|
|
406
693
|
} catch (err) {
|
|
407
694
|
process.stderr.write(`Error: ${err.message}
|
|
408
695
|
`);
|
|
@@ -450,18 +737,18 @@ var finishFeatureCmd = new Command2("finish-feature").description("Clean up a me
|
|
|
450
737
|
process.exit(1);
|
|
451
738
|
}
|
|
452
739
|
});
|
|
453
|
-
var gitCommand = new Command2("git").description("Git workflow commands (requires gh CLI)").addCommand(getPrInfoCmd).addCommand(finishFeatureCmd);
|
|
740
|
+
var gitCommand = new Command2("git").description("Git workflow commands (requires gh CLI)").addCommand(getPrInfoCmd).addCommand(getPrCommentsCmd).addCommand(finishFeatureCmd);
|
|
454
741
|
|
|
455
742
|
// src/commands/getReady.ts
|
|
456
743
|
import { Command as Command3 } from "commander";
|
|
457
|
-
import { spawnSync as
|
|
744
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
458
745
|
import { existsSync } from "fs";
|
|
459
746
|
import { delimiter, join as join2 } from "path";
|
|
460
747
|
|
|
461
748
|
// src/config/githubService.ts
|
|
462
|
-
import { spawnSync as
|
|
463
|
-
function
|
|
464
|
-
const result =
|
|
749
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
750
|
+
function run3(cmd, args) {
|
|
751
|
+
const result = spawnSync3(cmd, args, { encoding: "utf8" });
|
|
465
752
|
if (result.error) {
|
|
466
753
|
const err = result.error;
|
|
467
754
|
if (err.code === "ENOENT") {
|
|
@@ -502,7 +789,7 @@ function listIssues(technique, value) {
|
|
|
502
789
|
filterArgs = ["--search", `${value} in:title`];
|
|
503
790
|
break;
|
|
504
791
|
}
|
|
505
|
-
const { stdout, stderr, status } =
|
|
792
|
+
const { stdout, stderr, status } = run3("gh", [...baseArgs, ...filterArgs]);
|
|
506
793
|
if (status !== 0) {
|
|
507
794
|
throw new Error(stderr.trim() || "Failed to query GitHub issues. Is `gh` installed and authenticated?");
|
|
508
795
|
}
|
|
@@ -513,7 +800,7 @@ function listIssues(technique, value) {
|
|
|
513
800
|
return issues[0];
|
|
514
801
|
}
|
|
515
802
|
function postComment(issueNumber, body) {
|
|
516
|
-
const { stderr, status } =
|
|
803
|
+
const { stderr, status } = run3("gh", ["issue", "comment", String(issueNumber), "--body", body]);
|
|
517
804
|
if (status !== 0) {
|
|
518
805
|
throw new Error(stderr.trim() || `Failed to post comment on issue #${issueNumber}.`);
|
|
519
806
|
}
|
|
@@ -533,7 +820,7 @@ function invokeClaudeCode(issue, systemPrompt) {
|
|
|
533
820
|
|
|
534
821
|
${issue.body}` : issue.body;
|
|
535
822
|
const claudeBin = resolveCommand("claude");
|
|
536
|
-
const result =
|
|
823
|
+
const result = spawnSync4(claudeBin, ["-p", prompt], { encoding: "utf8", stdio: "inherit" });
|
|
537
824
|
if (result.error) {
|
|
538
825
|
const err = result.error;
|
|
539
826
|
if (err.code === "ENOENT") {
|
|
@@ -553,7 +840,9 @@ ${issue.body}` : issue.body;
|
|
|
553
840
|
var getReadyCommand = new Command3("get-ready").description("Find the next open GitHub issue matching the configured filter, claim it, and invoke Claude Code").option("--json", "Output issue details as JSON").option("--no-claude", "Skip Claude Code invocation after claiming the issue").action((options) => {
|
|
554
841
|
const config = readConfig();
|
|
555
842
|
if (config.remoteType !== "gh") {
|
|
556
|
-
process.stderr.write(
|
|
843
|
+
process.stderr.write(
|
|
844
|
+
"Error: get-ready is not supported in Azure DevOps mode. Work item discovery is not available in azdo-cli. See docs/azdo-gap.md for details.\n"
|
|
845
|
+
);
|
|
557
846
|
process.exit(1);
|
|
558
847
|
}
|
|
559
848
|
if (!config.issueDiscoveryTechnique) {
|