agentweaver 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,7 +12,7 @@ The package is designed to run as an npm CLI and includes an interactive termina
12
12
 
13
13
  - Fetches a Jira issue by key or browse URL
14
14
  - Generates workflow artifacts such as design, implementation plan, QA plan, bug analysis, reviews, and summaries
15
- - For bug-analysis flows, structured JSON artifacts are the machine-readable source of truth, while Markdown artifacts are for human inspection
15
+ - Machine-readable JSON artifacts are stored under `.agentweaver-<TASK>/.artifacts/` and act as the source of truth between workflow steps; Markdown artifacts remain for human inspection
16
16
  - Runs workflow stages like `bug-analyze`, `bug-fix`, `mr-description`, `plan`, `task-describe`, `implement`, `review`, `review-fix`, `test`, and `auto`
17
17
  - Persists compact `auto` pipeline state on disk so runs can resume without storing large agent outputs
18
18
  - Uses Docker runtime services for isolated Codex execution and build verification
package/dist/artifacts.js CHANGED
@@ -11,61 +11,89 @@ export function taskWorkspaceDir(taskKey) {
11
11
  export function ensureTaskWorkspaceDir(taskKey) {
12
12
  const workspaceDir = taskWorkspaceDir(taskKey);
13
13
  mkdirSync(workspaceDir, { recursive: true });
14
+ mkdirSync(taskArtifactsDir(taskKey), { recursive: true });
14
15
  return workspaceDir;
15
16
  }
16
17
  export function taskWorkspaceFile(taskKey, fileName) {
17
18
  return path.join(taskWorkspaceDir(taskKey), fileName);
18
19
  }
20
+ export function taskArtifactsDir(taskKey) {
21
+ return path.join(taskWorkspaceDir(taskKey), ".artifacts");
22
+ }
23
+ export function taskArtifactsFile(taskKey, fileName) {
24
+ return path.join(taskArtifactsDir(taskKey), fileName);
25
+ }
19
26
  export function artifactFile(prefix, taskKey, iteration) {
20
27
  return taskWorkspaceFile(taskKey, `${prefix}-${taskKey}-${iteration}.md`);
21
28
  }
29
+ export function artifactJsonFile(prefix, taskKey, iteration) {
30
+ return taskArtifactsFile(taskKey, `${prefix}-${taskKey}-${iteration}.json`);
31
+ }
22
32
  export function designFile(taskKey) {
23
33
  return artifactFile("design", taskKey, 1);
24
34
  }
35
+ export function designJsonFile(taskKey) {
36
+ return artifactJsonFile("design", taskKey, 1);
37
+ }
25
38
  export function planFile(taskKey) {
26
39
  return artifactFile("plan", taskKey, 1);
27
40
  }
41
+ export function planJsonFile(taskKey) {
42
+ return artifactJsonFile("plan", taskKey, 1);
43
+ }
28
44
  export function bugAnalyzeFile(taskKey) {
29
45
  return taskWorkspaceFile(taskKey, `bug-analyze-${taskKey}.md`);
30
46
  }
31
47
  export function bugAnalyzeJsonFile(taskKey) {
32
- return taskWorkspaceFile(taskKey, `bug-analyze-${taskKey}.json`);
48
+ return taskArtifactsFile(taskKey, `bug-analyze-${taskKey}.json`);
33
49
  }
34
50
  export function bugFixDesignFile(taskKey) {
35
51
  return taskWorkspaceFile(taskKey, `bug-fix-design-${taskKey}.md`);
36
52
  }
37
53
  export function bugFixDesignJsonFile(taskKey) {
38
- return taskWorkspaceFile(taskKey, `bug-fix-design-${taskKey}.json`);
54
+ return taskArtifactsFile(taskKey, `bug-fix-design-${taskKey}.json`);
39
55
  }
40
56
  export function bugFixPlanFile(taskKey) {
41
57
  return taskWorkspaceFile(taskKey, `bug-fix-plan-${taskKey}.md`);
42
58
  }
43
59
  export function bugFixPlanJsonFile(taskKey) {
44
- return taskWorkspaceFile(taskKey, `bug-fix-plan-${taskKey}.json`);
60
+ return taskArtifactsFile(taskKey, `bug-fix-plan-${taskKey}.json`);
45
61
  }
46
62
  export function qaFile(taskKey) {
47
63
  return artifactFile("qa", taskKey, 1);
48
64
  }
65
+ export function qaJsonFile(taskKey) {
66
+ return artifactJsonFile("qa", taskKey, 1);
67
+ }
49
68
  export function taskSummaryFile(taskKey) {
50
69
  return artifactFile("task", taskKey, 1);
51
70
  }
71
+ export function taskSummaryJsonFile(taskKey) {
72
+ return artifactJsonFile("task", taskKey, 1);
73
+ }
52
74
  export function readyToMergeFile(taskKey) {
53
75
  return taskWorkspaceFile(taskKey, READY_TO_MERGE_FILE);
54
76
  }
55
77
  export function jiraTaskFile(taskKey) {
56
- return taskWorkspaceFile(taskKey, `${taskKey}.json`);
78
+ return taskArtifactsFile(taskKey, `${taskKey}.json`);
57
79
  }
58
80
  export function jiraDescriptionFile(taskKey) {
59
81
  return taskWorkspaceFile(taskKey, `jira-${taskKey}-description.md`);
60
82
  }
83
+ export function jiraDescriptionJsonFile(taskKey) {
84
+ return taskArtifactsFile(taskKey, `jira-${taskKey}-description.json`);
85
+ }
61
86
  export function mrDescriptionFile(taskKey) {
62
87
  return taskWorkspaceFile(taskKey, `mr-description-${taskKey}.md`);
63
88
  }
89
+ export function mrDescriptionJsonFile(taskKey) {
90
+ return taskArtifactsFile(taskKey, `mr-description-${taskKey}.json`);
91
+ }
64
92
  export function autoStateFile(taskKey) {
65
- return taskWorkspaceFile(taskKey, `.agentweaver-state-${taskKey}.json`);
93
+ return taskArtifactsFile(taskKey, `.agentweaver-state-${taskKey}.json`);
66
94
  }
67
95
  export function planArtifacts(taskKey) {
68
- return [designFile(taskKey), planFile(taskKey), qaFile(taskKey)];
96
+ return [designFile(taskKey), designJsonFile(taskKey), planFile(taskKey), planJsonFile(taskKey), qaFile(taskKey), qaJsonFile(taskKey)];
69
97
  }
70
98
  export function bugAnalyzeArtifacts(taskKey) {
71
99
  return [
@@ -77,6 +105,27 @@ export function bugAnalyzeArtifacts(taskKey) {
77
105
  bugFixPlanJsonFile(taskKey),
78
106
  ];
79
107
  }
108
+ export function reviewFile(taskKey, iteration) {
109
+ return artifactFile("review", taskKey, iteration);
110
+ }
111
+ export function reviewJsonFile(taskKey, iteration) {
112
+ return artifactJsonFile("review", taskKey, iteration);
113
+ }
114
+ export function reviewReplyFile(taskKey, iteration) {
115
+ return artifactFile("review-reply", taskKey, iteration);
116
+ }
117
+ export function reviewReplyJsonFile(taskKey, iteration) {
118
+ return artifactJsonFile("review-reply", taskKey, iteration);
119
+ }
120
+ export function reviewFixFile(taskKey, iteration) {
121
+ return artifactFile("review-fix", taskKey, iteration);
122
+ }
123
+ export function reviewFixJsonFile(taskKey, iteration) {
124
+ return artifactJsonFile("review-fix", taskKey, iteration);
125
+ }
126
+ export function reviewFixSelectionJsonFile(taskKey, iteration) {
127
+ return artifactJsonFile("review-fix-selection", taskKey, iteration);
128
+ }
80
129
  export function requireArtifacts(paths, message) {
81
130
  const missing = paths.filter((filePath) => !existsSync(filePath));
82
131
  if (missing.length > 0) {
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "no
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
5
  import { fileURLToPath } from "node:url";
6
- import { REVIEW_FILE_RE, REVIEW_REPLY_FILE_RE, autoStateFile, bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, ensureTaskWorkspaceDir, jiraTaskFile, planArtifacts, readyToMergeFile, requireArtifacts, taskWorkspaceDir, taskSummaryFile, } from "./artifacts.js";
6
+ import { REVIEW_FILE_RE, REVIEW_REPLY_FILE_RE, autoStateFile, bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designJsonFile, ensureTaskWorkspaceDir, jiraTaskFile, planJsonFile, planArtifacts, qaJsonFile, readyToMergeFile, requireArtifacts, reviewReplyJsonFile, reviewFixSelectionJsonFile, reviewJsonFile, taskWorkspaceDir, taskSummaryFile, } from "./artifacts.js";
7
7
  import { TaskRunnerError } from "./errors.js";
8
8
  import { buildJiraApiUrl, buildJiraBrowseUrl, extractIssueKey, requireJiraTaskFile } from "./jira.js";
9
9
  import { validateStructuredArtifacts } from "./structured-artifacts.js";
@@ -17,7 +17,8 @@ import { resolveCmd, resolveDockerComposeCmd } from "./runtime/command-resolutio
17
17
  import { defaultDockerComposeFile, dockerRuntimeEnv } from "./runtime/docker-runtime.js";
18
18
  import { runCommand } from "./runtime/process-runner.js";
19
19
  import { InteractiveUi } from "./interactive-ui.js";
20
- import { bye, printError, printInfo, printPanel, printSummary, setFlowExecutionState } from "./tui.js";
20
+ import { bye, printError, printInfo, printPanel, printSummary, setFlowExecutionState, stripAnsi, } from "./tui.js";
21
+ import { requestUserInputInTerminal } from "./user-input.js";
21
22
  const COMMANDS = [
22
23
  "bug-analyze",
23
24
  "bug-fix",
@@ -45,6 +46,37 @@ const runtimeServices = {
45
46
  dockerRuntimeEnv: () => dockerRuntimeEnv(PACKAGE_ROOT),
46
47
  runCommand,
47
48
  };
49
+ function buildFailureOutputPreview(output) {
50
+ const normalized = stripAnsi(output).replace(/\r\n/g, "\n").trim();
51
+ if (!normalized) {
52
+ return "";
53
+ }
54
+ const lines = normalized
55
+ .split("\n")
56
+ .map((line) => line.trimEnd())
57
+ .filter((line) => line.trim().length > 0);
58
+ if (lines.length === 0) {
59
+ return "";
60
+ }
61
+ const previewLines = lines.slice(-8);
62
+ let preview = previewLines.join("\n");
63
+ const maxLength = 1200;
64
+ if (preview.length > maxLength) {
65
+ preview = `...${preview.slice(-(maxLength - 3))}`;
66
+ }
67
+ return preview;
68
+ }
69
+ function formatProcessFailure(error) {
70
+ const returnCode = Number(error.returnCode);
71
+ const baseMessage = !Number.isNaN(returnCode)
72
+ ? `Command failed with exit code ${returnCode}`
73
+ : error.message?.trim() || "Command failed";
74
+ const preview = buildFailureOutputPreview(String(error.output ?? ""));
75
+ if (!preview) {
76
+ return baseMessage;
77
+ }
78
+ return `${baseMessage}\nПричина:\n${preview}`;
79
+ }
48
80
  function usage() {
49
81
  return `Usage:
50
82
  agentweaver <jira-browse-url|jira-issue-key>
@@ -420,11 +452,31 @@ function autoFlowParams(config) {
420
452
  reviewFixPoints: config.reviewFixPoints,
421
453
  };
422
454
  }
455
+ const FLOW_DESCRIPTIONS = {
456
+ auto: "Полный пайплайн задачи: планирование, реализация, проверки, ревью, ответы на ревью и повторные итерации до готовности к merge.",
457
+ "bug-analyze": "Анализирует баг по Jira и создаёт структурированные артефакты: гипотезу причины, дизайн исправления и план работ.",
458
+ "bug-fix": "Берёт результаты bug-analyze как source of truth и реализует исправление бага в коде.",
459
+ "mr-description": "Готовит краткое intent-описание для merge request на основе задачи и текущих изменений.",
460
+ plan: "Загружает задачу из Jira и создаёт дизайн, план реализации и QA-план в structured JSON и markdown.",
461
+ "task-describe": "Строит короткое резюме задачи на основе Jira-артефакта для быстрого ознакомления.",
462
+ implement: "Реализует задачу по утверждённым design/plan артефактам и при необходимости запускает post-verify сборки.",
463
+ review: "Запускает Claude-код-ревью текущих изменений, валидирует structured findings, затем готовит ответ на замечания через Codex.",
464
+ "review-fix": "Исправляет замечания после review-reply, обновляет код и прогоняет обязательные проверки после правок.",
465
+ test: "Запускает verify/build-проверку в контейнере и показывает результат с краткой сводкой при падении.",
466
+ "test-fix": "Прогоняет тесты, исправляет найденные проблемы и готовит код к следующей успешной проверке.",
467
+ "test-linter-fix": "Прогоняет линтер и генерацию, затем исправляет замечания для чистого прогона.",
468
+ "run-tests-loop": "Циклически запускает `./run_tests.sh`, анализирует последнюю ошибку и правит код до успешного прохождения или исчерпания попыток.",
469
+ "run-linter-loop": "Циклически запускает `./run_linter.sh`, исправляет проблемы линтера или генерации и повторяет попытки до успеха.",
470
+ };
471
+ function flowDescription(id) {
472
+ return FLOW_DESCRIPTIONS[id] ?? "Описание для этого flow пока не задано.";
473
+ }
423
474
  function declarativeFlowDefinition(id, label, fileName) {
424
475
  const flow = loadDeclarativeFlow(fileName);
425
476
  return {
426
477
  id,
427
478
  label,
479
+ description: flowDescription(id),
428
480
  phases: flow.phases.map((phase) => ({
429
481
  id: phase.id,
430
482
  repeatVars: Object.fromEntries(Object.entries(phase.repeatVars).map(([key, value]) => [key, value])),
@@ -439,6 +491,7 @@ function autoFlowDefinition() {
439
491
  return {
440
492
  id: "auto",
441
493
  label: "auto",
494
+ description: flowDescription("auto"),
442
495
  phases: flow.phases.map((phase) => ({
443
496
  id: phase.id,
444
497
  repeatVars: Object.fromEntries(Object.entries(phase.repeatVars).map(([key, value]) => [key, value])),
@@ -469,13 +522,14 @@ function interactiveFlowDefinitions() {
469
522
  function publishFlowState(flowId, executionState) {
470
523
  setFlowExecutionState(flowId, stripExecutionStatePayload(executionState));
471
524
  }
472
- async function runDeclarativeFlowBySpecFile(fileName, config, flowParams) {
525
+ async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, requestUserInput = requestUserInputInTerminal) {
473
526
  const context = createPipelineContext({
474
527
  issueKey: config.taskKey,
475
528
  jiraRef: config.jiraRef,
476
529
  dryRun: config.dryRun,
477
530
  verbose: config.verbose,
478
531
  runtime: runtimeServices,
532
+ requestUserInput,
479
533
  });
480
534
  const flow = loadDeclarativeFlow(fileName);
481
535
  const executionState = {
@@ -503,6 +557,7 @@ async function runAutoPhaseViaSpec(config, phaseId, executionState, state) {
503
557
  dryRun: config.dryRun,
504
558
  verbose: config.verbose,
505
559
  runtime: runtimeServices,
560
+ requestUserInput: requestUserInputInTerminal,
506
561
  });
507
562
  const autoFlow = loadAutoFlow();
508
563
  const phase = findPhaseById(autoFlow.phases, phaseId);
@@ -577,9 +632,10 @@ async function summarizeBuildFailure(output) {
577
632
  dryRun: false,
578
633
  verbose: false,
579
634
  runtime: runtimeServices,
635
+ requestUserInput: requestUserInputInTerminal,
580
636
  }), output);
581
637
  }
582
- async function executeCommand(config, runFollowupVerify = true) {
638
+ async function executeCommand(config, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal) {
583
639
  if (config.command === "auto") {
584
640
  await runAutoPipeline(config);
585
641
  return false;
@@ -612,7 +668,7 @@ async function executeCommand(config, runFollowupVerify = true) {
612
668
  jiraApiUrl: config.jiraApiUrl,
613
669
  taskKey: config.taskKey,
614
670
  extraPrompt: config.extraPrompt,
615
- });
671
+ }, requestUserInput);
616
672
  return false;
617
673
  }
618
674
  if (config.command === "bug-analyze") {
@@ -625,7 +681,7 @@ async function executeCommand(config, runFollowupVerify = true) {
625
681
  jiraApiUrl: config.jiraApiUrl,
626
682
  taskKey: config.taskKey,
627
683
  extraPrompt: config.extraPrompt,
628
- });
684
+ }, requestUserInput);
629
685
  return false;
630
686
  }
631
687
  if (config.command === "bug-fix") {
@@ -639,7 +695,7 @@ async function executeCommand(config, runFollowupVerify = true) {
639
695
  await runDeclarativeFlowBySpecFile("bug-fix.json", config, {
640
696
  taskKey: config.taskKey,
641
697
  extraPrompt: config.extraPrompt,
642
- });
698
+ }, requestUserInput);
643
699
  return false;
644
700
  }
645
701
  if (config.command === "mr-description") {
@@ -647,7 +703,7 @@ async function executeCommand(config, runFollowupVerify = true) {
647
703
  await runDeclarativeFlowBySpecFile("mr-description.json", config, {
648
704
  taskKey: config.taskKey,
649
705
  extraPrompt: config.extraPrompt,
650
- });
706
+ }, requestUserInput);
651
707
  return false;
652
708
  }
653
709
  if (config.command === "task-describe") {
@@ -655,19 +711,24 @@ async function executeCommand(config, runFollowupVerify = true) {
655
711
  await runDeclarativeFlowBySpecFile("task-describe.json", config, {
656
712
  taskKey: config.taskKey,
657
713
  extraPrompt: config.extraPrompt,
658
- });
714
+ }, requestUserInput);
659
715
  return false;
660
716
  }
661
717
  if (config.command === "implement") {
662
718
  requireJiraTaskFile(config.jiraTaskFile);
663
719
  requireArtifacts(planArtifacts(config.taskKey), "Implement mode requires plan artifacts from the planning phase.");
720
+ validateStructuredArtifacts([
721
+ { path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
722
+ { path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
723
+ { path: qaJsonFile(config.taskKey), schemaId: "qa-plan/v1" },
724
+ ], "Implement mode requires valid structured plan artifacts from the planning phase.");
664
725
  try {
665
726
  await runDeclarativeFlowBySpecFile("implement.json", config, {
666
727
  taskKey: config.taskKey,
667
728
  dockerComposeFile: config.dockerComposeFile,
668
729
  extraPrompt: config.extraPrompt,
669
730
  runFollowupVerify,
670
- });
731
+ }, requestUserInput);
671
732
  }
672
733
  catch (error) {
673
734
  if (!config.dryRun) {
@@ -683,25 +744,38 @@ async function executeCommand(config, runFollowupVerify = true) {
683
744
  }
684
745
  if (config.command === "review") {
685
746
  requireJiraTaskFile(config.jiraTaskFile);
747
+ validateStructuredArtifacts([
748
+ { path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
749
+ { path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
750
+ ], "Review mode requires valid structured plan artifacts from the planning phase.");
686
751
  const iteration = nextReviewIterationForTask(config.taskKey);
687
752
  await runDeclarativeFlowBySpecFile("review.json", config, {
688
753
  taskKey: config.taskKey,
689
754
  iteration,
690
755
  extraPrompt: config.extraPrompt,
691
- });
756
+ }, requestUserInput);
692
757
  return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
693
758
  }
694
759
  if (config.command === "review-fix") {
695
760
  requireJiraTaskFile(config.jiraTaskFile);
761
+ const latestIteration = latestReviewReplyIteration(config.taskKey);
762
+ if (latestIteration === null) {
763
+ throw new TaskRunnerError("Review-fix mode requires at least one review-reply artifact.");
764
+ }
765
+ validateStructuredArtifacts([
766
+ { path: reviewJsonFile(config.taskKey, latestIteration), schemaId: "review-findings/v1" },
767
+ { path: reviewReplyJsonFile(config.taskKey, latestIteration), schemaId: "review-reply/v1" },
768
+ ], "Review-fix mode requires valid structured review artifacts.");
696
769
  try {
697
770
  await runDeclarativeFlowBySpecFile("review-fix.json", config, {
698
771
  taskKey: config.taskKey,
699
772
  dockerComposeFile: config.dockerComposeFile,
700
- latestIteration: latestReviewReplyIteration(config.taskKey),
773
+ latestIteration,
774
+ reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, latestIteration),
701
775
  runFollowupVerify,
702
776
  extraPrompt: config.extraPrompt,
703
777
  reviewFixPoints: config.reviewFixPoints,
704
- });
778
+ }, requestUserInput);
705
779
  }
706
780
  catch (error) {
707
781
  if (!config.dryRun) {
@@ -721,7 +795,7 @@ async function executeCommand(config, runFollowupVerify = true) {
721
795
  await runDeclarativeFlowBySpecFile("test.json", config, {
722
796
  taskKey: config.taskKey,
723
797
  dockerComposeFile: config.dockerComposeFile,
724
- });
798
+ }, requestUserInput);
725
799
  }
726
800
  catch (error) {
727
801
  if (!config.dryRun) {
@@ -740,7 +814,7 @@ async function executeCommand(config, runFollowupVerify = true) {
740
814
  await runDeclarativeFlowBySpecFile(config.command === "test-fix" ? "test-fix.json" : "test-linter-fix.json", config, {
741
815
  taskKey: config.taskKey,
742
816
  extraPrompt: config.extraPrompt,
743
- });
817
+ }, requestUserInput);
744
818
  return false;
745
819
  }
746
820
  if (config.command === "run-tests-loop" || config.command === "run-linter-loop") {
@@ -748,7 +822,7 @@ async function executeCommand(config, runFollowupVerify = true) {
748
822
  taskKey: config.taskKey,
749
823
  dockerComposeFile: config.dockerComposeFile,
750
824
  extraPrompt: config.extraPrompt,
751
- });
825
+ }, requestUserInput);
752
826
  return false;
753
827
  }
754
828
  throw new TaskRunnerError(`Unsupported command: ${config.command}`);
@@ -933,7 +1007,7 @@ async function runInteractive(jiraRef, forceRefresh = false) {
933
1007
  onRun: async (flowId) => {
934
1008
  try {
935
1009
  const command = buildConfig(flowId, jiraRef);
936
- await executeCommand(command);
1010
+ await executeCommand(command, true, (form) => ui.requestUserInput(form));
937
1011
  }
938
1012
  catch (error) {
939
1013
  if (error instanceof TaskRunnerError) {
@@ -944,7 +1018,7 @@ async function runInteractive(jiraRef, forceRefresh = false) {
944
1018
  const returnCode = Number(error.returnCode);
945
1019
  if (!Number.isNaN(returnCode)) {
946
1020
  ui.setFlowFailed(flowId);
947
- printError(`Command failed with exit code ${returnCode}`);
1021
+ printError(formatProcessFailure(error));
948
1022
  return;
949
1023
  }
950
1024
  throw error;
@@ -968,6 +1042,7 @@ async function runInteractive(jiraRef, forceRefresh = false) {
968
1042
  setSummary: (markdown) => {
969
1043
  ui.setSummary(markdown);
970
1044
  },
1045
+ requestUserInput: (form) => ui.requestUserInput(form),
971
1046
  }), {
972
1047
  jiraApiUrl: config.jiraApiUrl,
973
1048
  jiraTaskFile: config.jiraTaskFile,
@@ -1041,7 +1116,7 @@ export async function main(argv = process.argv.slice(2)) {
1041
1116
  }
1042
1117
  const returnCode = Number(error.returnCode);
1043
1118
  if (!Number.isNaN(returnCode)) {
1044
- printError(`Command failed with exit code ${returnCode}`);
1119
+ printError(formatProcessFailure(error));
1045
1120
  return returnCode || 1;
1046
1121
  }
1047
1122
  throw error;