@unbrained/pm-cli 2026.5.3 → 2026.5.4

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/CHANGELOG.md CHANGED
@@ -7,6 +7,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2026.5.4] - 2026-05-04
11
+
12
+ ### Changed
13
+ - Relaxed release workflow npx/bunx post-publish checks to emit warnings instead of hard-failing the pipeline when registry/executor convergence lags, while keeping npm publication metadata as a blocking verification gate.
14
+
15
+ ## [2026.5.3-8] - 2026-05-03
16
+
17
+ ### Changed
18
+ - Updated release workflow npx verification to run the explicit package binary command (`npx @unbrained/pm-cli@<version> pm --version`) to avoid npm exec binary resolution drift on GitHub-hosted runners.
19
+
20
+ ## [2026.5.3-7] - 2026-05-03
21
+
22
+ ### Changed
23
+ - Updated release post-publish verification commands to execute the package `pm` binary explicitly (`npm exec --package ... -- pm --version` and `bunx ... pm --version`) and parse terminal line output robustly.
24
+
25
+ ## [2026.5.3-6] - 2026-05-03
26
+
27
+ ### Changed
28
+ - Added npm/npx/bunx propagation retries to release publication verification so post-publish checks wait for registry availability instead of failing immediately on transient 404 windows.
29
+
30
+ ## [2026.5.3-5] - 2026-05-03
31
+
32
+ ### Changed
33
+ - Hardened release workflow reliability gating so Sentry threshold checks are skipped when the runner does not provide the `sentry` CLI binary, preventing false-negative publish blocks on GitHub-hosted runners.
34
+
35
+ ## [2026.5.3-4] - 2026-05-03
36
+
37
+ ### Changed
38
+ - Relaxed tag release version policy guard in `.github/workflows/release.yml` to validate tag/version consistency without blocking same-day retry tags when a previous tag run failed before npm publication.
39
+ - Hardened `scripts/release/run-release-pipeline.mjs` same-day retry version resolution so local retry cuts always advance beyond the currently checked-out package version when npm has not yet observed failed prior tags.
40
+
41
+ ## [2026.5.3-3] - 2026-05-03
42
+
43
+ ### Fixed
44
+ - Relaxed release compatibility health evaluation so compatibility gating only blocks on failing health checks, not warning-only health states, preventing false negatives on GitHub-hosted release runs.
45
+
46
+ ## [2026.5.3-2] - 2026-05-03
47
+
48
+ ### Added
49
+ - Added a scheduled auto-release workflow (`.github/workflows/auto-release.yml`) with one-release-per-UTC-day defaults, manual same-day override controls, and a shared release pipeline driver.
50
+ - Added release automation scripts under `scripts/release/` for changelog promotion, strict static quality checks, temporary-project backward-compatibility validation, Sentry/telemetry threshold gating, unified release gate execution, and full local pipeline orchestration.
51
+ - Added release automation contract coverage in `tests/integration/release-automation-contract.spec.ts`.
52
+
53
+ ### Changed
54
+ - Hardened CI, nightly, and release workflows with explicit static quality and compatibility migration gates.
55
+ - Release workflow now verifies published package availability via npm, npx, and bunx, and verifies GitHub release metadata after publication.
56
+ - Expanded release-readiness runtime checks to cover new release scripts, package commands, and auto-release workflow presence.
57
+
10
58
  ## [2026.5.3] - 2026-05-03
11
59
 
12
60
  ### Added
package/README.md CHANGED
@@ -17,7 +17,7 @@
17
17
  | Settings, storage, search, and output | [Configuration](docs/CONFIGURATION.md) |
18
18
  | Safe test execution and linked tests | [Testing](docs/TESTING.md) |
19
19
  | Extension authoring | [Extensions](docs/EXTENSIONS.md) and [SDK](docs/SDK.md) |
20
- | Maintainer release process | [Releasing](docs/RELEASING.md) |
20
+ | Maintainer release process (daily auto-release + local parity) | [Releasing](docs/RELEASING.md) |
21
21
  | Contributor internals | [Architecture](docs/ARCHITECTURE.md) |
22
22
 
23
23
  Full documentation starts at [docs/README.md](docs/README.md).
@@ -76,6 +76,14 @@ pm list-in-progress --limit 20
76
76
 
77
77
  If no relevant item exists, create a parent lineage before child work, claim the child item, link changed files/docs/tests, and leave evidence comments before closing. The full workflow is in the [Agent Guide](docs/AGENT_GUIDE.md).
78
78
 
79
+ ## Release Automation
80
+
81
+ - Daily release preparation runs in `.github/workflows/auto-release.yml`.
82
+ - Tag-driven publishing remains in `.github/workflows/release.yml`.
83
+ - Local parity commands:
84
+ - `pnpm release:pipeline:dry-run`
85
+ - `pnpm release:pipeline -- --telemetry-mode required`
86
+
79
87
  ## Core Model
80
88
 
81
89
  - Items live under `.agents/pm/` as TOON by default, with JSON-front-matter markdown also supported.
package/dist/cli/main.js CHANGED
@@ -12,6 +12,7 @@ import { PmCliError } from "../core/shared/errors.js";
12
12
  import { printError, printResult, writeStdout } from "../core/output/output.js";
13
13
  import { maybeRunFirstUseTelemetryPrompt } from "../core/telemetry/consent.js";
14
14
  import { emitTelemetryErrorEvent, finishTelemetryCommand, startTelemetryCommand, } from "../core/telemetry/runtime.js";
15
+ import { deriveTelemetryCommandResolution, } from "../core/telemetry/observability.js";
15
16
  import { sentryCaptureCliError, sentryFinishCommandSpan, sentryFlush, sentryLogCliUsageError, sentrySetCommandContext, sentryStartCommandSpan, } from "../core/sentry/helpers.js";
16
17
  import { getSettingsPath, resolvePmRoot } from "../core/store/paths.js";
17
18
  import { readSettings } from "../core/store/settings.js";
@@ -38,6 +39,20 @@ if (typeof process.env[PM_PACKAGE_ROOT_ENV] !== "string" || process.env[PM_PACKA
38
39
  }
39
40
  let activeExtensionHookContext = null;
40
41
  let activeTelemetryCommandContext = null;
42
+ const TELEMETRY_COMMAND_RESOLUTION_SET = new Set([
43
+ "success",
44
+ "nonexistent_command",
45
+ "invalid_option",
46
+ "missing_required_option",
47
+ "missing_required_argument",
48
+ "invalid_usage",
49
+ "validation_failed",
50
+ "conflict",
51
+ "runtime_failed",
52
+ "unknown_failed",
53
+ ]);
54
+ const TELEMETRY_RESOLUTION_STAGE_SET = new Set(["parse", "preflight", "execute", "unknown"]);
55
+ const TELEMETRY_ERROR_CATEGORY_SET = new Set(["usage", "validation", "conflict", "runtime", "unknown"]);
41
56
  let runtimeExtensionSnapshotCache = null;
42
57
  let activeRuntimeExtensionCommandDescriptors = new Map();
43
58
  function describeUnknownError(error) {
@@ -49,6 +64,163 @@ function describeUnknownError(error) {
49
64
  }
50
65
  return "Unknown failure";
51
66
  }
67
+ function asRecord(value) {
68
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
69
+ return null;
70
+ }
71
+ return value;
72
+ }
73
+ function readRecordString(record, ...keys) {
74
+ if (!record) {
75
+ return undefined;
76
+ }
77
+ for (const key of keys) {
78
+ const candidate = record[key];
79
+ if (typeof candidate === "string") {
80
+ const normalized = candidate.trim();
81
+ if (normalized.length > 0) {
82
+ return normalized;
83
+ }
84
+ }
85
+ }
86
+ return undefined;
87
+ }
88
+ function readRecordBoolean(record, ...keys) {
89
+ if (!record) {
90
+ return undefined;
91
+ }
92
+ for (const key of keys) {
93
+ const candidate = record[key];
94
+ if (typeof candidate === "boolean") {
95
+ return candidate;
96
+ }
97
+ }
98
+ return undefined;
99
+ }
100
+ function readRecordNumber(record, ...keys) {
101
+ if (!record) {
102
+ return undefined;
103
+ }
104
+ for (const key of keys) {
105
+ const candidate = record[key];
106
+ if (typeof candidate === "number" && Number.isFinite(candidate)) {
107
+ return Math.max(0, Math.trunc(candidate));
108
+ }
109
+ }
110
+ return undefined;
111
+ }
112
+ function normalizeTelemetryCommandResolution(value) {
113
+ if (!value) {
114
+ return undefined;
115
+ }
116
+ const normalized = value.trim().toLowerCase();
117
+ if (!TELEMETRY_COMMAND_RESOLUTION_SET.has(normalized)) {
118
+ return undefined;
119
+ }
120
+ return normalized;
121
+ }
122
+ function normalizeTelemetryResolutionStage(value) {
123
+ if (!value) {
124
+ return undefined;
125
+ }
126
+ const normalized = value.trim().toLowerCase();
127
+ if (!TELEMETRY_RESOLUTION_STAGE_SET.has(normalized)) {
128
+ return undefined;
129
+ }
130
+ return normalized;
131
+ }
132
+ function normalizeTelemetryErrorCategory(value) {
133
+ if (!value) {
134
+ return undefined;
135
+ }
136
+ const normalized = value.trim().toLowerCase();
137
+ if (!TELEMETRY_ERROR_CATEGORY_SET.has(normalized)) {
138
+ return undefined;
139
+ }
140
+ return normalized;
141
+ }
142
+ function inferPostActionFailureMessage(result) {
143
+ const explicit = readRecordString(result, "error", "message");
144
+ if (explicit) {
145
+ return explicit;
146
+ }
147
+ const warnings = result?.warnings;
148
+ if (Array.isArray(warnings)) {
149
+ const firstWarning = warnings.find((value) => typeof value === "string" && value.trim().length > 0);
150
+ if (typeof firstWarning === "string") {
151
+ return firstWarning.trim();
152
+ }
153
+ }
154
+ const skippedTriggered = readRecordBoolean(result, "fail_on_skipped_triggered", "failOnSkippedTriggered");
155
+ if (skippedTriggered) {
156
+ return "linked_test_fail_on_skipped_triggered";
157
+ }
158
+ const failedCount = readRecordNumber(result, "failed");
159
+ if (typeof failedCount === "number" && failedCount > 0) {
160
+ return `failed_runs:${failedCount}`;
161
+ }
162
+ const runResults = result?.run_results;
163
+ if (Array.isArray(runResults)) {
164
+ const failedRuns = runResults.filter((entry) => {
165
+ const row = asRecord(entry);
166
+ return row?.status === "failed";
167
+ }).length;
168
+ if (failedRuns > 0) {
169
+ return `failed_runs:${failedRuns}`;
170
+ }
171
+ }
172
+ return undefined;
173
+ }
174
+ function inferPostActionErrorCode(ok, exitCode) {
175
+ if (ok) {
176
+ return undefined;
177
+ }
178
+ if (exitCode === EXIT_CODE.USAGE) {
179
+ return "invalid_command_usage";
180
+ }
181
+ if (exitCode === EXIT_CODE.NOT_FOUND) {
182
+ return "item_not_found";
183
+ }
184
+ if (exitCode === EXIT_CODE.CONFLICT) {
185
+ return "lock_conflict";
186
+ }
187
+ if (exitCode === EXIT_CODE.DEPENDENCY_FAILED) {
188
+ return "dependency_failed";
189
+ }
190
+ return "command_failed";
191
+ }
192
+ function buildPostActionTelemetryOutcome() {
193
+ const result = asRecord(getActiveCommandResult());
194
+ const processExitCode = typeof process.exitCode === "number" && Number.isFinite(process.exitCode)
195
+ ? Math.max(0, Math.trunc(process.exitCode))
196
+ : undefined;
197
+ const resultExitCode = readRecordNumber(result, "exit_code", "exitCode");
198
+ const exitCode = processExitCode ?? resultExitCode ?? EXIT_CODE.SUCCESS;
199
+ const resultOk = readRecordBoolean(result, "ok");
200
+ const ok = resultOk ?? exitCode === EXIT_CODE.SUCCESS;
201
+ const errorCode = readRecordString(result, "error_code", "errorCode") ?? inferPostActionErrorCode(ok, exitCode);
202
+ const errorCategory = normalizeTelemetryErrorCategory(readRecordString(result, "error_category", "errorCategory")) ??
203
+ (!ok ? resolveTelemetryErrorCategory(errorCode) : undefined);
204
+ const errorMessage = !ok
205
+ ? inferPostActionFailureMessage(result) ?? `command_exit_${exitCode}`
206
+ : undefined;
207
+ const commandResolution = normalizeTelemetryCommandResolution(readRecordString(result, "command_resolution", "commandResolution")) ??
208
+ deriveTelemetryCommandResolution({
209
+ ok,
210
+ errorCode,
211
+ errorCategory,
212
+ });
213
+ const resolutionStage = normalizeTelemetryResolutionStage(readRecordString(result, "resolution_stage", "resolutionStage")) ?? "execute";
214
+ return {
215
+ ok,
216
+ error: errorMessage,
217
+ exit_code: exitCode,
218
+ error_code: errorCode,
219
+ error_category: errorCategory,
220
+ command_resolution: commandResolution,
221
+ resolution_stage: resolutionStage,
222
+ };
223
+ }
52
224
  async function runAndClearAfterCommandHooks(outcome) {
53
225
  const telemetryRuntime = activeTelemetryCommandContext;
54
226
  activeTelemetryCommandContext = null;
@@ -59,6 +231,8 @@ async function runAndClearAfterCommandHooks(outcome) {
59
231
  exit_code: outcome.exit_code,
60
232
  error_code: outcome.error_code,
61
233
  error_category: outcome.error_category,
234
+ command_resolution: outcome.command_resolution,
235
+ resolution_stage: outcome.resolution_stage,
62
236
  });
63
237
  const runtime = activeExtensionHookContext;
64
238
  activeExtensionHookContext = null;
@@ -640,7 +814,10 @@ program.hook("preAction", async (_thisCommand, actionCommand) => {
640
814
  global: globalOptions,
641
815
  pm_root: fallbackPmRoot,
642
816
  });
643
- sentrySetCommandContext(commandPath, commandArgs, commandOptions);
817
+ sentrySetCommandContext(commandPath, commandArgs, commandOptions, {
818
+ source_context: activeTelemetryCommandContext?.source_context,
819
+ source_context_source: activeTelemetryCommandContext?.source_context_source,
820
+ });
644
821
  sentryStartCommandSpan(commandPath);
645
822
  await enforceItemFormatWriteGateAndPreflightMigration(commandPath, commandOptions, fallbackPmRoot, defaultPreflightDecision());
646
823
  return;
@@ -716,7 +893,10 @@ program.hook("preAction", async (_thisCommand, actionCommand) => {
716
893
  global: globalOptions,
717
894
  pm_root: runtimeExtensions.pmRoot,
718
895
  });
719
- sentrySetCommandContext(commandPath, commandArgs, commandOptions);
896
+ sentrySetCommandContext(commandPath, commandArgs, commandOptions, {
897
+ source_context: activeTelemetryCommandContext?.source_context,
898
+ source_context_source: activeTelemetryCommandContext?.source_context_source,
899
+ });
720
900
  sentryStartCommandSpan(commandPath);
721
901
  const hookWarnings = await runBeforeCommandHooks(runtimeExtensions.hooks, {
722
902
  command: commandPath,
@@ -733,8 +913,15 @@ program.hook("preAction", async (_thisCommand, actionCommand) => {
733
913
  }
734
914
  });
735
915
  program.hook("postAction", async () => {
736
- sentryFinishCommandSpan(true);
737
- await runAndClearAfterCommandHooks({ ok: true, exit_code: EXIT_CODE.SUCCESS });
916
+ const outcome = buildPostActionTelemetryOutcome();
917
+ sentryFinishCommandSpan(outcome.ok, outcome.error, {
918
+ error_code: outcome.error_code,
919
+ error_category: outcome.error_category,
920
+ exit_code: outcome.exit_code,
921
+ command_resolution: outcome.command_resolution,
922
+ resolution_stage: outcome.resolution_stage,
923
+ });
924
+ await runAndClearAfterCommandHooks(outcome);
738
925
  });
739
926
  registerSetupCommands(program);
740
927
  registerListQueryCommands(program);
@@ -762,6 +949,11 @@ async function main() {
762
949
  const attemptedCommand = parseBootstrapCommandName(invocationArgv) ?? "<unknown>";
763
950
  const emitTelemetryCommandError = async (params) => {
764
951
  const errorCategory = resolveTelemetryErrorCategory(params.errorCode);
952
+ const commandResolution = deriveTelemetryCommandResolution({
953
+ ok: false,
954
+ errorCode: params.errorCode,
955
+ errorCategory,
956
+ });
765
957
  await emitTelemetryErrorEvent({
766
958
  command: params.command,
767
959
  args: invocationArgv,
@@ -773,8 +965,13 @@ async function main() {
773
965
  error_message: params.errorMessage,
774
966
  exit_code: params.exitCode,
775
967
  error_category: errorCategory,
968
+ command_resolution: commandResolution,
969
+ resolution_stage: params.resolutionStage,
776
970
  });
777
- return errorCategory;
971
+ return {
972
+ errorCategory,
973
+ commandResolution,
974
+ };
778
975
  };
779
976
  if (!bootstrapGlobal.noExtensions) {
780
977
  const bootstrapSnapshot = await loadRuntimeExtensionSnapshot(bootstrapPmRoot);
@@ -782,7 +979,7 @@ async function main() {
782
979
  }
783
980
  if (error instanceof PmCliError) {
784
981
  const classification = classifyPmCliError(error.message, error.context);
785
- const errorCategory = await emitTelemetryCommandError({
982
+ const { errorCategory, commandResolution } = await emitTelemetryCommandError({
786
983
  command: attemptedCommand,
787
984
  errorCode: classification.code,
788
985
  errorMessage: classification.detail,
@@ -790,6 +987,7 @@ async function main() {
790
987
  options: {
791
988
  bootstrap_global_options: bootstrapGlobal,
792
989
  },
990
+ resolutionStage: "execute",
793
991
  });
794
992
  sentryLogCliUsageError({
795
993
  command: attemptedCommand,
@@ -797,14 +995,25 @@ async function main() {
797
995
  error_category: errorCategory,
798
996
  exit_code: error.exitCode,
799
997
  error_message: classification.detail,
998
+ command_resolution: commandResolution,
999
+ resolution_stage: "execute",
1000
+ source_context: activeTelemetryCommandContext?.source_context,
1001
+ });
1002
+ sentryFinishCommandSpan(false, error.message, {
1003
+ error_code: classification.code,
1004
+ error_category: errorCategory,
1005
+ exit_code: error.exitCode,
1006
+ command_resolution: commandResolution,
1007
+ resolution_stage: "execute",
800
1008
  });
801
- sentryFinishCommandSpan(false, error.message);
802
1009
  await runAndClearAfterCommandHooks({
803
1010
  ok: false,
804
1011
  error: error.message,
805
1012
  exit_code: error.exitCode,
806
1013
  error_code: classification.code,
807
1014
  error_category: errorCategory,
1015
+ command_resolution: commandResolution,
1016
+ resolution_stage: "execute",
808
1017
  });
809
1018
  sentryCaptureCliError(error);
810
1019
  if (jsonErrors) {
@@ -831,7 +1040,7 @@ async function main() {
831
1040
  unknownCommandExamples: usageContext.unknownCommandExamples,
832
1041
  unknownCommandNextSteps: usageContext.unknownCommandNextSteps,
833
1042
  });
834
- const errorCategory = await emitTelemetryCommandError({
1043
+ const { errorCategory, commandResolution } = await emitTelemetryCommandError({
835
1044
  command: unknownToken,
836
1045
  errorCode: classification.code,
837
1046
  errorMessage: classification.detail,
@@ -840,6 +1049,7 @@ async function main() {
840
1049
  bootstrap_global_options: bootstrapGlobal,
841
1050
  commander_code: code ?? "commander.helpDisplayed",
842
1051
  },
1052
+ resolutionStage: "parse",
843
1053
  });
844
1054
  sentryLogCliUsageError({
845
1055
  command: unknownToken,
@@ -847,17 +1057,28 @@ async function main() {
847
1057
  error_category: errorCategory,
848
1058
  exit_code: EXIT_CODE.USAGE,
849
1059
  error_message: classification.detail,
1060
+ command_resolution: commandResolution,
1061
+ resolution_stage: "parse",
1062
+ source_context: activeTelemetryCommandContext?.source_context,
850
1063
  });
851
1064
  const renderedUsage = jsonErrors
852
1065
  ? await formatCommanderUsageJson({ message: unknownMessage }, program, activeRuntimeExtensionCommandDescriptors)
853
1066
  : await formatCommanderUsageMessage({ message: unknownMessage }, program, activeRuntimeExtensionCommandDescriptors);
854
- sentryFinishCommandSpan(false, unknownMessage);
1067
+ sentryFinishCommandSpan(false, unknownMessage, {
1068
+ error_code: classification.code,
1069
+ error_category: errorCategory,
1070
+ exit_code: EXIT_CODE.USAGE,
1071
+ command_resolution: commandResolution,
1072
+ resolution_stage: "parse",
1073
+ });
855
1074
  await runAndClearAfterCommandHooks({
856
1075
  ok: false,
857
1076
  error: unknownMessage,
858
1077
  exit_code: EXIT_CODE.USAGE,
859
1078
  error_code: classification.code,
860
1079
  error_category: errorCategory,
1080
+ command_resolution: commandResolution,
1081
+ resolution_stage: "parse",
861
1082
  });
862
1083
  if (jsonErrors) {
863
1084
  printError(renderedUsage);
@@ -869,19 +1090,31 @@ async function main() {
869
1090
  process.exitCode = EXIT_CODE.USAGE;
870
1091
  return;
871
1092
  }
872
- sentryFinishCommandSpan(true);
1093
+ sentryFinishCommandSpan(true, undefined, {
1094
+ exit_code: EXIT_CODE.SUCCESS,
1095
+ command_resolution: "success",
1096
+ resolution_stage: "parse",
1097
+ });
873
1098
  await runAndClearAfterCommandHooks({
874
1099
  ok: true,
875
1100
  exit_code: EXIT_CODE.SUCCESS,
1101
+ command_resolution: "success",
1102
+ resolution_stage: "parse",
876
1103
  });
877
1104
  process.exitCode = EXIT_CODE.SUCCESS;
878
1105
  return;
879
1106
  }
880
1107
  if (code === "commander.version") {
881
- sentryFinishCommandSpan(true);
1108
+ sentryFinishCommandSpan(true, undefined, {
1109
+ exit_code: EXIT_CODE.SUCCESS,
1110
+ command_resolution: "success",
1111
+ resolution_stage: "parse",
1112
+ });
882
1113
  await runAndClearAfterCommandHooks({
883
1114
  ok: true,
884
1115
  exit_code: EXIT_CODE.SUCCESS,
1116
+ command_resolution: "success",
1117
+ resolution_stage: "parse",
885
1118
  });
886
1119
  process.exitCode = EXIT_CODE.SUCCESS;
887
1120
  return;
@@ -892,7 +1125,7 @@ async function main() {
892
1125
  unknownCommandExamples: usageContext.unknownCommandExamples,
893
1126
  unknownCommandNextSteps: usageContext.unknownCommandNextSteps,
894
1127
  });
895
- const errorCategory = await emitTelemetryCommandError({
1128
+ const { errorCategory, commandResolution } = await emitTelemetryCommandError({
896
1129
  command: attemptedCommand,
897
1130
  errorCode: classification.code,
898
1131
  errorMessage: classification.detail,
@@ -901,6 +1134,7 @@ async function main() {
901
1134
  bootstrap_global_options: bootstrapGlobal,
902
1135
  commander_code: code,
903
1136
  },
1137
+ resolutionStage: "parse",
904
1138
  });
905
1139
  sentryLogCliUsageError({
906
1140
  command: attemptedCommand,
@@ -908,17 +1142,28 @@ async function main() {
908
1142
  error_category: errorCategory,
909
1143
  exit_code: EXIT_CODE.USAGE,
910
1144
  error_message: classification.detail,
1145
+ command_resolution: commandResolution,
1146
+ resolution_stage: "parse",
1147
+ source_context: activeTelemetryCommandContext?.source_context,
911
1148
  });
912
1149
  const renderedUsage = jsonErrors
913
1150
  ? await formatCommanderUsageJson(error, program, activeRuntimeExtensionCommandDescriptors)
914
1151
  : await formatCommanderUsageMessage(error, program, activeRuntimeExtensionCommandDescriptors);
915
- sentryFinishCommandSpan(false, usageContext.message);
1152
+ sentryFinishCommandSpan(false, usageContext.message, {
1153
+ error_code: classification.code,
1154
+ error_category: errorCategory,
1155
+ exit_code: EXIT_CODE.USAGE,
1156
+ command_resolution: commandResolution,
1157
+ resolution_stage: "parse",
1158
+ });
916
1159
  await runAndClearAfterCommandHooks({
917
1160
  ok: false,
918
1161
  error: usageContext.message,
919
1162
  exit_code: EXIT_CODE.USAGE,
920
1163
  error_code: classification.code,
921
1164
  error_category: errorCategory,
1165
+ command_resolution: commandResolution,
1166
+ resolution_stage: "parse",
922
1167
  });
923
1168
  if (jsonErrors) {
924
1169
  printError(renderedUsage);
@@ -934,7 +1179,7 @@ async function main() {
934
1179
  sentryCaptureCliError(error);
935
1180
  const message = describeUnknownError(error);
936
1181
  const classification = classifyUnknownError(message);
937
- const errorCategory = await emitTelemetryCommandError({
1182
+ const { errorCategory, commandResolution } = await emitTelemetryCommandError({
938
1183
  command: attemptedCommand,
939
1184
  errorCode: classification.code,
940
1185
  errorMessage: classification.detail,
@@ -942,14 +1187,23 @@ async function main() {
942
1187
  options: {
943
1188
  bootstrap_global_options: bootstrapGlobal,
944
1189
  },
1190
+ resolutionStage: "execute",
1191
+ });
1192
+ sentryFinishCommandSpan(false, message, {
1193
+ error_code: classification.code,
1194
+ error_category: errorCategory,
1195
+ exit_code: EXIT_CODE.GENERIC_FAILURE,
1196
+ command_resolution: commandResolution,
1197
+ resolution_stage: "execute",
945
1198
  });
946
- sentryFinishCommandSpan(false, message);
947
1199
  await runAndClearAfterCommandHooks({
948
1200
  ok: false,
949
1201
  error: message,
950
1202
  exit_code: EXIT_CODE.GENERIC_FAILURE,
951
1203
  error_code: classification.code,
952
1204
  error_category: errorCategory,
1205
+ command_resolution: commandResolution,
1206
+ resolution_stage: "execute",
953
1207
  });
954
1208
  if (jsonErrors) {
955
1209
  printError(JSON.stringify(formatUnknownErrorForJson(message, EXIT_CODE.GENERIC_FAILURE), null, 2));