automata-cli 0.2.0-develop.26 → 0.2.0-develop.31
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 +196 -32
- 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,41 +350,56 @@ 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
|
}
|
|
@@ -317,7 +418,7 @@ function checkSymbol(check) {
|
|
|
317
418
|
function formatCheckSummary(checks) {
|
|
318
419
|
const running = checks.some((c) => c.status !== "COMPLETED");
|
|
319
420
|
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("; ");
|
|
421
|
+
const errors = failed.length === 0 ? "none" : failed.map((c) => `${c.name}: ${c.description.trim() || c.detailsUrl || "no details available"}`).join("; ");
|
|
321
422
|
return `Checks Running: ${String(running)}
|
|
322
423
|
Check Errors: ${errors}
|
|
323
424
|
`;
|
|
@@ -330,13 +431,32 @@ function formatChecks(checks) {
|
|
|
330
431
|
const pending = check.status !== "COMPLETED" ? " (pending)" : "";
|
|
331
432
|
lines.push(` ${sym} ${check.name}${pending}`);
|
|
332
433
|
if (check.conclusion !== null && FAIL_CONCLUSIONS.has(check.conclusion)) {
|
|
333
|
-
const
|
|
334
|
-
|
|
434
|
+
const desc = check.description.trim();
|
|
435
|
+
const url = check.detailsUrl.trim();
|
|
436
|
+
if (desc) lines.push(` Details: ${desc}`);
|
|
437
|
+
if (url) lines.push(` URL: ${url}`);
|
|
438
|
+
if (!desc && !url) lines.push(` Details: (no details available)`);
|
|
335
439
|
}
|
|
336
440
|
}
|
|
337
441
|
return lines.join("\n") + "\n";
|
|
338
442
|
}
|
|
339
|
-
|
|
443
|
+
function sleep(ms) {
|
|
444
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
445
|
+
}
|
|
446
|
+
function formatFailedChecks(failed) {
|
|
447
|
+
const lines = [];
|
|
448
|
+
for (const check of failed) {
|
|
449
|
+
lines.push(` \u2717 ${check.name}`);
|
|
450
|
+
const desc = check.description.trim();
|
|
451
|
+
const url = check.detailsUrl.trim();
|
|
452
|
+
if (desc) lines.push(` Details: ${desc}`);
|
|
453
|
+
if (url) lines.push(` URL: ${url}`);
|
|
454
|
+
if (!desc && !url) lines.push(` Details: (no details available)`);
|
|
455
|
+
}
|
|
456
|
+
return lines.join("\n") + "\n";
|
|
457
|
+
}
|
|
458
|
+
var POLL_INTERVAL_MS = 1e4;
|
|
459
|
+
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
460
|
"after",
|
|
341
461
|
`
|
|
342
462
|
Check status symbols:
|
|
@@ -347,7 +467,7 @@ Check status symbols:
|
|
|
347
467
|
|
|
348
468
|
Failure details are printed beneath each \u2717 check.
|
|
349
469
|
See docs/git.md for full output reference.`
|
|
350
|
-
).action((options) => {
|
|
470
|
+
).action(async (options) => {
|
|
351
471
|
let branch;
|
|
352
472
|
try {
|
|
353
473
|
branch = getCurrentBranch();
|
|
@@ -356,9 +476,51 @@ See docs/git.md for full output reference.`
|
|
|
356
476
|
`);
|
|
357
477
|
process.exit(1);
|
|
358
478
|
}
|
|
479
|
+
if (options.waitFinishChecks) {
|
|
480
|
+
let pr2;
|
|
481
|
+
while (true) {
|
|
482
|
+
try {
|
|
483
|
+
pr2 = getPrInfo2(branch);
|
|
484
|
+
} catch (err) {
|
|
485
|
+
process.stderr.write(`Error: ${err.message}
|
|
486
|
+
`);
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
if (pr2 === null) {
|
|
490
|
+
process.stderr.write(`Error: No pull request found for branch: ${branch}
|
|
491
|
+
`);
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
494
|
+
const running = pr2.checks.filter((c) => c.status !== "COMPLETED");
|
|
495
|
+
if (running.length === 0) break;
|
|
496
|
+
process.stdout.write(`Waiting for ${running.length} check(s) to complete...
|
|
497
|
+
`);
|
|
498
|
+
await sleep(POLL_INTERVAL_MS);
|
|
499
|
+
}
|
|
500
|
+
const failed = pr2.checks.filter((c) => c.conclusion !== null && FAIL_CONCLUSIONS.has(c.conclusion));
|
|
501
|
+
if (failed.length === 0) {
|
|
502
|
+
if (options.json) {
|
|
503
|
+
process.stdout.write(JSON.stringify({ result: "passed" }, null, 2) + "\n");
|
|
504
|
+
} else {
|
|
505
|
+
process.stdout.write(`All checks passed. \u2713
|
|
506
|
+
`);
|
|
507
|
+
}
|
|
508
|
+
process.exit(0);
|
|
509
|
+
} else {
|
|
510
|
+
if (options.json) {
|
|
511
|
+
process.stdout.write(JSON.stringify({ result: "failed", failed }, null, 2) + "\n");
|
|
512
|
+
} else {
|
|
513
|
+
process.stdout.write(`${failed.length} check(s) failed:
|
|
514
|
+
`);
|
|
515
|
+
process.stdout.write(formatFailedChecks(failed));
|
|
516
|
+
}
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
359
521
|
let pr;
|
|
360
522
|
try {
|
|
361
|
-
pr =
|
|
523
|
+
pr = getPrInfo2(branch);
|
|
362
524
|
} catch (err) {
|
|
363
525
|
process.stderr.write(`Error: ${err.message}
|
|
364
526
|
`);
|
|
@@ -402,7 +564,7 @@ var finishFeatureCmd = new Command2("finish-feature").description("Clean up a me
|
|
|
402
564
|
}
|
|
403
565
|
let pr;
|
|
404
566
|
try {
|
|
405
|
-
pr =
|
|
567
|
+
pr = getPrInfo2(branch);
|
|
406
568
|
} catch (err) {
|
|
407
569
|
process.stderr.write(`Error: ${err.message}
|
|
408
570
|
`);
|
|
@@ -454,14 +616,14 @@ var gitCommand = new Command2("git").description("Git workflow commands (require
|
|
|
454
616
|
|
|
455
617
|
// src/commands/getReady.ts
|
|
456
618
|
import { Command as Command3 } from "commander";
|
|
457
|
-
import { spawnSync as
|
|
619
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
458
620
|
import { existsSync } from "fs";
|
|
459
621
|
import { delimiter, join as join2 } from "path";
|
|
460
622
|
|
|
461
623
|
// src/config/githubService.ts
|
|
462
|
-
import { spawnSync as
|
|
463
|
-
function
|
|
464
|
-
const result =
|
|
624
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
625
|
+
function run3(cmd, args) {
|
|
626
|
+
const result = spawnSync3(cmd, args, { encoding: "utf8" });
|
|
465
627
|
if (result.error) {
|
|
466
628
|
const err = result.error;
|
|
467
629
|
if (err.code === "ENOENT") {
|
|
@@ -502,7 +664,7 @@ function listIssues(technique, value) {
|
|
|
502
664
|
filterArgs = ["--search", `${value} in:title`];
|
|
503
665
|
break;
|
|
504
666
|
}
|
|
505
|
-
const { stdout, stderr, status } =
|
|
667
|
+
const { stdout, stderr, status } = run3("gh", [...baseArgs, ...filterArgs]);
|
|
506
668
|
if (status !== 0) {
|
|
507
669
|
throw new Error(stderr.trim() || "Failed to query GitHub issues. Is `gh` installed and authenticated?");
|
|
508
670
|
}
|
|
@@ -513,7 +675,7 @@ function listIssues(technique, value) {
|
|
|
513
675
|
return issues[0];
|
|
514
676
|
}
|
|
515
677
|
function postComment(issueNumber, body) {
|
|
516
|
-
const { stderr, status } =
|
|
678
|
+
const { stderr, status } = run3("gh", ["issue", "comment", String(issueNumber), "--body", body]);
|
|
517
679
|
if (status !== 0) {
|
|
518
680
|
throw new Error(stderr.trim() || `Failed to post comment on issue #${issueNumber}.`);
|
|
519
681
|
}
|
|
@@ -533,7 +695,7 @@ function invokeClaudeCode(issue, systemPrompt) {
|
|
|
533
695
|
|
|
534
696
|
${issue.body}` : issue.body;
|
|
535
697
|
const claudeBin = resolveCommand("claude");
|
|
536
|
-
const result =
|
|
698
|
+
const result = spawnSync4(claudeBin, ["-p", prompt], { encoding: "utf8", stdio: "inherit" });
|
|
537
699
|
if (result.error) {
|
|
538
700
|
const err = result.error;
|
|
539
701
|
if (err.code === "ENOENT") {
|
|
@@ -553,7 +715,9 @@ ${issue.body}` : issue.body;
|
|
|
553
715
|
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
716
|
const config = readConfig();
|
|
555
717
|
if (config.remoteType !== "gh") {
|
|
556
|
-
process.stderr.write(
|
|
718
|
+
process.stderr.write(
|
|
719
|
+
"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"
|
|
720
|
+
);
|
|
557
721
|
process.exit(1);
|
|
558
722
|
}
|
|
559
723
|
if (!config.issueDiscoveryTechnique) {
|