@unbrained/pm-cli 2026.5.1 → 2026.5.3-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/AGENTS.md +8 -1
- package/CHANGELOG.md +73 -4
- package/CONTRIBUTING.md +11 -5
- package/PRD.md +17 -1
- package/README.md +55 -1099
- package/SECURITY.md +6 -11
- package/dist/cli/bootstrap-args.d.ts +18 -0
- package/dist/cli/bootstrap-args.js +242 -0
- package/dist/cli/bootstrap-args.js.map +1 -0
- package/dist/cli/commander-usage.d.ts +17 -0
- package/dist/cli/commander-usage.js +178 -0
- package/dist/cli/commander-usage.js.map +1 -0
- package/dist/cli/commands/activity.d.ts +10 -0
- package/dist/cli/commands/activity.js +14 -10
- package/dist/cli/commands/activity.js.map +1 -1
- package/dist/cli/commands/aggregate.js.map +1 -1
- package/dist/cli/commands/append.js.map +1 -1
- package/dist/cli/commands/calendar.js +19 -34
- package/dist/cli/commands/calendar.js.map +1 -1
- package/dist/cli/commands/claim.js.map +1 -1
- package/dist/cli/commands/close.js.map +1 -1
- package/dist/cli/commands/comments-audit.js.map +1 -1
- package/dist/cli/commands/comments.js +1 -9
- package/dist/cli/commands/comments.js.map +1 -1
- package/dist/cli/commands/completion.js.map +1 -1
- package/dist/cli/commands/config.d.ts +21 -3
- package/dist/cli/commands/config.js +118 -2
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/context.d.ts +90 -1
- package/dist/cli/commands/context.js +496 -12
- package/dist/cli/commands/context.js.map +1 -1
- package/dist/cli/commands/contracts.js.map +1 -1
- package/dist/cli/commands/create.js +2 -2
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/dedupe-audit.js +2 -11
- package/dist/cli/commands/dedupe-audit.js.map +1 -1
- package/dist/cli/commands/delete.js.map +1 -1
- package/dist/cli/commands/deps.js.map +1 -1
- package/dist/cli/commands/docs.js.map +1 -1
- package/dist/cli/commands/extension.js.map +1 -1
- package/dist/cli/commands/files.js +14 -2
- package/dist/cli/commands/files.js.map +1 -1
- package/dist/cli/commands/gc.js.map +1 -1
- package/dist/cli/commands/get.js.map +1 -1
- package/dist/cli/commands/health.js +16 -12
- package/dist/cli/commands/health.js.map +1 -1
- package/dist/cli/commands/history.js +1 -9
- package/dist/cli/commands/history.js.map +1 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/learnings.js +1 -9
- package/dist/cli/commands/learnings.js.map +1 -1
- package/dist/cli/commands/list.d.ts +1 -0
- package/dist/cli/commands/list.js +13 -31
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/normalize.js +14 -23
- package/dist/cli/commands/normalize.js.map +1 -1
- package/dist/cli/commands/notes.js +1 -9
- package/dist/cli/commands/notes.js.map +1 -1
- package/dist/cli/commands/reindex.js +2 -7
- package/dist/cli/commands/reindex.js.map +1 -1
- package/dist/cli/commands/restore.js.map +1 -1
- package/dist/cli/commands/search.js +4 -35
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/stats.js.map +1 -1
- package/dist/cli/commands/templates.js.map +1 -1
- package/dist/cli/commands/test-all.js.map +1 -1
- package/dist/cli/commands/test-runs.js +1 -11
- package/dist/cli/commands/test-runs.js.map +1 -1
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/commands/update-many.js +1 -6
- package/dist/cli/commands/update-many.js.map +1 -1
- package/dist/cli/commands/update.js +2 -2
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/commands/validate.js +23 -18
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/error-guidance.d.ts +13 -0
- package/dist/cli/error-guidance.js +56 -6
- package/dist/cli/error-guidance.js.map +1 -1
- package/dist/cli/extension-command-help.d.ts +48 -0
- package/dist/cli/extension-command-help.js +389 -0
- package/dist/cli/extension-command-help.js.map +1 -0
- package/dist/cli/extension-command-options.js.map +1 -1
- package/dist/cli/help-content.js +9 -3
- package/dist/cli/help-content.js.map +1 -1
- package/dist/cli/help-json-payload.d.ts +25 -0
- package/dist/cli/help-json-payload.js +265 -0
- package/dist/cli/help-json-payload.js.map +1 -0
- package/dist/cli/main.js +1000 -4456
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/migration-gates.d.ts +22 -0
- package/dist/cli/migration-gates.js +146 -0
- package/dist/cli/migration-gates.js.map +1 -0
- package/dist/cli/register-list-query.d.ts +2 -0
- package/dist/cli/register-list-query.js +317 -0
- package/dist/cli/register-list-query.js.map +1 -0
- package/dist/cli/register-mutation.d.ts +2 -0
- package/dist/cli/register-mutation.js +795 -0
- package/dist/cli/register-mutation.js.map +1 -0
- package/dist/cli/register-operations.d.ts +2 -0
- package/dist/cli/register-operations.js +610 -0
- package/dist/cli/register-operations.js.map +1 -0
- package/dist/cli/register-setup.d.ts +2 -0
- package/dist/cli/register-setup.js +334 -0
- package/dist/cli/register-setup.js.map +1 -0
- package/dist/cli/registration-helpers.d.ts +53 -0
- package/dist/cli/registration-helpers.js +669 -0
- package/dist/cli/registration-helpers.js.map +1 -0
- package/dist/cli/shared-parsers.d.ts +6 -0
- package/dist/cli/shared-parsers.js +40 -0
- package/dist/cli/shared-parsers.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/core/extensions/extension-types.d.ts +605 -0
- package/dist/core/extensions/extension-types.js +22 -0
- package/dist/core/extensions/extension-types.js.map +1 -0
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/item-fields.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +2 -586
- package/dist/core/extensions/loader.js +3 -21
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runtime-registrations.js.map +1 -1
- package/dist/core/fs/fs-utils.js.map +1 -1
- package/dist/core/fs/index.js.map +1 -1
- package/dist/core/history/history-stream-policy.js.map +1 -1
- package/dist/core/history/history.js.map +1 -1
- package/dist/core/history/index.js.map +1 -1
- package/dist/core/item/id.js.map +1 -1
- package/dist/core/item/index.js.map +1 -1
- package/dist/core/item/item-format.js.map +1 -1
- package/dist/core/item/parent-reference-policy.js.map +1 -1
- package/dist/core/item/parse.js +6 -0
- package/dist/core/item/parse.js.map +1 -1
- package/dist/core/item/sprint-release-format.js.map +1 -1
- package/dist/core/item/status.js.map +1 -1
- package/dist/core/item/type-registry.js.map +1 -1
- package/dist/core/lock/index.js.map +1 -1
- package/dist/core/lock/lock.js +1 -6
- package/dist/core/lock/lock.js.map +1 -1
- package/dist/core/output/command-aware.js.map +1 -1
- package/dist/core/output/output.js.map +1 -1
- package/dist/core/schema/runtime-field-filters.js.map +1 -1
- package/dist/core/schema/runtime-field-values.js.map +1 -1
- package/dist/core/schema/runtime-schema.js.map +1 -1
- package/dist/core/search/cache.js +1 -7
- package/dist/core/search/cache.js.map +1 -1
- package/dist/core/search/embedding-batches.js +4 -0
- package/dist/core/search/embedding-batches.js.map +1 -1
- package/dist/core/search/http-client.d.ts +29 -0
- package/dist/core/search/http-client.js +64 -0
- package/dist/core/search/http-client.js.map +1 -0
- package/dist/core/search/providers.d.ts +3 -13
- package/dist/core/search/providers.js +19 -88
- package/dist/core/search/providers.js.map +1 -1
- package/dist/core/search/semantic-defaults.js +2 -7
- package/dist/core/search/semantic-defaults.js.map +1 -1
- package/dist/core/search/vector-stores.d.ts +4 -13
- package/dist/core/search/vector-stores.js +40 -93
- package/dist/core/search/vector-stores.js.map +1 -1
- package/dist/core/sentry/helpers.d.ts +27 -0
- package/dist/core/sentry/helpers.js +171 -0
- package/dist/core/sentry/helpers.js.map +1 -0
- package/dist/core/sentry/instrument.d.ts +25 -0
- package/dist/core/sentry/instrument.js +204 -0
- package/dist/core/sentry/instrument.js.map +1 -0
- package/dist/core/shared/command-types.js.map +1 -1
- package/dist/core/shared/conflict-markers.js.map +1 -1
- package/dist/core/shared/constants.d.ts +3 -0
- package/dist/core/shared/constants.js +58 -1
- package/dist/core/shared/constants.js.map +1 -1
- package/dist/core/shared/errors.js.map +1 -1
- package/dist/core/shared/index.d.ts +1 -0
- package/dist/core/shared/index.js +1 -0
- package/dist/core/shared/index.js.map +1 -1
- package/dist/core/shared/primitives.d.ts +13 -0
- package/dist/core/shared/primitives.js +33 -0
- package/dist/core/shared/primitives.js.map +1 -0
- package/dist/core/shared/serialization.js.map +1 -1
- package/dist/core/shared/text-normalization.js.map +1 -1
- package/dist/core/shared/time.js.map +1 -1
- package/dist/core/store/front-matter-cache.d.ts +6 -0
- package/dist/core/store/front-matter-cache.js +150 -0
- package/dist/core/store/front-matter-cache.js.map +1 -0
- package/dist/core/store/index.js.map +1 -1
- package/dist/core/store/item-format-migration.js.map +1 -1
- package/dist/core/store/item-store.js +46 -36
- package/dist/core/store/item-store.js.map +1 -1
- package/dist/core/store/paths.js.map +1 -1
- package/dist/core/store/settings.js +36 -0
- package/dist/core/store/settings.js.map +1 -1
- package/dist/core/telemetry/consent.js.map +1 -1
- package/dist/core/telemetry/observability.d.ts +24 -0
- package/dist/core/telemetry/observability.js +185 -0
- package/dist/core/telemetry/observability.js.map +1 -0
- package/dist/core/telemetry/runtime.d.ts +29 -3
- package/dist/core/telemetry/runtime.js +337 -25
- package/dist/core/telemetry/runtime.js.map +1 -1
- package/dist/core/test/background-runs.js.map +1 -1
- package/dist/core/test/item-test-run-tracking.js.map +1 -1
- package/dist/sdk/cli-contracts.js +28 -0
- package/dist/sdk/cli-contracts.js.map +1 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -1
- package/docs/AGENT_GUIDE.md +125 -0
- package/docs/ARCHITECTURE.md +201 -478
- package/docs/COMMANDS.md +209 -0
- package/docs/CONFIGURATION.md +146 -0
- package/docs/EXTENSIONS.md +146 -645
- package/docs/QUICKSTART.md +108 -0
- package/docs/README.md +70 -0
- package/docs/RELEASING.md +92 -50
- package/docs/SDK.md +127 -68
- package/docs/TESTING.md +125 -0
- package/docs/examples/starter-extension/README.md +39 -25
- package/package.json +24 -11
- package/dist/command-types.d.ts +0 -1
- package/dist/command-types.js +0 -2
- package/dist/command-types.js.map +0 -1
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +0 -2
- package/dist/constants.js.map +0 -1
- package/dist/errors.d.ts +0 -1
- package/dist/errors.js +0 -2
- package/dist/errors.js.map +0 -1
- package/dist/fs-utils.d.ts +0 -1
- package/dist/fs-utils.js +0 -2
- package/dist/fs-utils.js.map +0 -1
- package/dist/history.d.ts +0 -1
- package/dist/history.js +0 -2
- package/dist/history.js.map +0 -1
- package/dist/id.d.ts +0 -1
- package/dist/id.js +0 -2
- package/dist/id.js.map +0 -1
- package/dist/item-format.d.ts +0 -1
- package/dist/item-format.js +0 -2
- package/dist/item-format.js.map +0 -1
- package/dist/item-store.d.ts +0 -1
- package/dist/item-store.js +0 -2
- package/dist/item-store.js.map +0 -1
- package/dist/lock.d.ts +0 -1
- package/dist/lock.js +0 -2
- package/dist/lock.js.map +0 -1
- package/dist/output.d.ts +0 -1
- package/dist/output.js +0 -2
- package/dist/output.js.map +0 -1
- package/dist/parse.d.ts +0 -1
- package/dist/parse.js +0 -2
- package/dist/parse.js.map +0 -1
- package/dist/paths.d.ts +0 -1
- package/dist/paths.js +0 -2
- package/dist/paths.js.map +0 -1
- package/dist/serialization.d.ts +0 -1
- package/dist/serialization.js +0 -2
- package/dist/serialization.js.map +0 -1
- package/dist/settings.d.ts +0 -1
- package/dist/settings.js +0 -2
- package/dist/settings.js.map +0 -1
- package/dist/time.d.ts +0 -1
- package/dist/time.js +0 -2
- package/dist/time.js.map +0 -1
|
@@ -2,16 +2,24 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { appendLineAtomic, readFileIfExists, writeFileAtomic } from "../fs/fs-utils.js";
|
|
5
|
+
import { resolveTelemetryErrorCategory } from "../shared/constants.js";
|
|
6
|
+
import { nowIso } from "../shared/time.js";
|
|
5
7
|
import { resolveGlobalPmRoot } from "../store/paths.js";
|
|
6
8
|
import { readSettings, writeSettings } from "../store/settings.js";
|
|
9
|
+
import { deriveTelemetryCommandResolution, deriveTelemetryCommandTaxonomy, inferTelemetryErrorCode, } from "./observability.js";
|
|
7
10
|
const TELEMETRY_QUEUE_RELATIVE_PATH = path.join("runtime", "telemetry", "events.jsonl");
|
|
8
11
|
const TELEMETRY_STATE_RELATIVE_PATH = path.join("runtime", "telemetry", "state.json");
|
|
9
12
|
const TELEMETRY_SCHEMA_VERSION = 1;
|
|
10
13
|
const TELEMETRY_FLUSH_BATCH_SIZE = 100;
|
|
11
14
|
const TELEMETRY_MAX_RETRY_DELAY_MS = 3_600_000;
|
|
12
15
|
const TELEMETRY_RETRY_BASE_DELAY_MS = 30_000;
|
|
13
|
-
const TELEMETRY_HTTP_TIMEOUT_MS =
|
|
16
|
+
const TELEMETRY_HTTP_TIMEOUT_MS = 5_000;
|
|
14
17
|
const MILLISECONDS_PER_DAY = 86_400_000;
|
|
18
|
+
const TELEMETRY_MAX_EVENT_BYTES = 65_536;
|
|
19
|
+
const TELEMETRY_SANITIZE_MAX_DEPTH = 6;
|
|
20
|
+
const TELEMETRY_SANITIZE_MAX_ARRAY_ITEMS = 20;
|
|
21
|
+
const TELEMETRY_MAX_QUEUE_ENTRY_ATTEMPTS = 15;
|
|
22
|
+
const TELEMETRY_RESULT_PREVIEW_MAX_BYTES = 8_192;
|
|
15
23
|
const OTEL_TRACES_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT";
|
|
16
24
|
const OTEL_BASE_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_ENDPOINT";
|
|
17
25
|
const OTEL_SERVICE_NAME_ENV = "OTEL_SERVICE_NAME";
|
|
@@ -21,6 +29,11 @@ const PM_TELEMETRY_OTEL_DISABLED_ENV = "PM_TELEMETRY_OTEL_DISABLED";
|
|
|
21
29
|
const PM_TELEMETRY_OTEL_DISABLED_VALUES = new Set(["1", "true", "yes", "on"]);
|
|
22
30
|
const PM_TELEMETRY_SOURCE_CONTEXT_ENV = "PM_TELEMETRY_SOURCE_CONTEXT";
|
|
23
31
|
const PM_TELEMETRY_SOURCE_CONTEXT_VALUES = ["user", "automation", "test", "dogfood", "audit_smoke"];
|
|
32
|
+
let _lastFlushPromise = Promise.resolve();
|
|
33
|
+
/** Wait for the most recent background flush to settle. Test-only helper. */
|
|
34
|
+
export function waitForPendingFlush() {
|
|
35
|
+
return _lastFlushPromise;
|
|
36
|
+
}
|
|
24
37
|
const PM_TELEMETRY_SOURCE_CONTEXT_SET = new Set(PM_TELEMETRY_SOURCE_CONTEXT_VALUES);
|
|
25
38
|
const BOOLEAN_TRUE_VALUES = new Set(["1", "true", "yes", "on"]);
|
|
26
39
|
const PROCESS_SESSION_ID = crypto.randomUUID();
|
|
@@ -41,9 +54,9 @@ const SENSITIVE_INLINE_KEY_PATTERN = "(?:token|secret|password|passwd|api[_-]?ke
|
|
|
41
54
|
const INLINE_SENSITIVE_ASSIGNMENT_PATTERN = new RegExp(`\\b(${SENSITIVE_INLINE_KEY_PATTERN})\\s*([:=])\\s*([^\\s,;]+)`, "giu");
|
|
42
55
|
const INLINE_SENSITIVE_FLAG_PATTERN = new RegExp(`(--${SENSITIVE_INLINE_KEY_PATTERN})(=|\\s+)([^\\s,;]+)`, "giu");
|
|
43
56
|
const ABSOLUTE_PATH_TOKEN_PATTERN = /(^|[\s"'`(=])\/(?:[^\s"'`),;]+)/g;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
const EMAIL_PATTERN = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/giu;
|
|
58
|
+
const BEARER_TOKEN_PATTERN = /bearer\s+[a-z0-9._=-]+/giu;
|
|
59
|
+
const PRIVATE_IP_PATTERN = /\b(?:10\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)|172\.(?:1[6-9]|2\d|3[01])\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)|192\.168\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d))\b/g;
|
|
47
60
|
function queuePath(globalPmRoot) {
|
|
48
61
|
return path.join(globalPmRoot, TELEMETRY_QUEUE_RELATIVE_PATH);
|
|
49
62
|
}
|
|
@@ -94,10 +107,14 @@ function redactInlineSensitiveAssignments(input) {
|
|
|
94
107
|
function redactAbsolutePathTokens(input) {
|
|
95
108
|
return input.replaceAll(ABSOLUTE_PATH_TOKEN_PATTERN, (_match, prefix) => `${prefix}[redacted_path]`);
|
|
96
109
|
}
|
|
110
|
+
function sanitizeCommonSensitiveTokens(input) {
|
|
111
|
+
const withoutEmails = input.replaceAll(EMAIL_PATTERN, "[redacted_email]");
|
|
112
|
+
const withoutBearer = withoutEmails.replaceAll(BEARER_TOKEN_PATTERN, "bearer [redacted_token]");
|
|
113
|
+
return withoutBearer.replaceAll(PRIVATE_IP_PATTERN, "[redacted_ip]");
|
|
114
|
+
}
|
|
97
115
|
function sanitizeStringRedacted(input) {
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const withoutInlineSecrets = redactInlineSensitiveAssignments(withoutBearer);
|
|
116
|
+
const withoutCommonSensitiveTokens = sanitizeCommonSensitiveTokens(input);
|
|
117
|
+
const withoutInlineSecrets = redactInlineSensitiveAssignments(withoutCommonSensitiveTokens);
|
|
101
118
|
const withoutAbsolutePaths = redactAbsolutePathTokens(withoutInlineSecrets);
|
|
102
119
|
const trimmed = withoutAbsolutePaths.trim();
|
|
103
120
|
if (trimmed.startsWith("/") && trimmed.length > 1) {
|
|
@@ -109,12 +126,17 @@ function sanitizeStringRedacted(input) {
|
|
|
109
126
|
return withoutAbsolutePaths;
|
|
110
127
|
}
|
|
111
128
|
function sanitizeStringMax(input) {
|
|
112
|
-
const
|
|
113
|
-
const withoutInlineSecrets = redactInlineSensitiveAssignments(
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
const withoutCommonSensitiveTokens = sanitizeCommonSensitiveTokens(input);
|
|
130
|
+
const withoutInlineSecrets = redactInlineSensitiveAssignments(withoutCommonSensitiveTokens);
|
|
131
|
+
const withoutAbsolutePaths = redactAbsolutePathTokens(withoutInlineSecrets);
|
|
132
|
+
const trimmed = withoutAbsolutePaths.trim();
|
|
133
|
+
if (trimmed.startsWith("/") && trimmed.length > 1) {
|
|
134
|
+
return "[redacted_path]";
|
|
116
135
|
}
|
|
117
|
-
|
|
136
|
+
if (withoutAbsolutePaths.length > 2048) {
|
|
137
|
+
return `${withoutAbsolutePaths.slice(0, 2045)}...`;
|
|
138
|
+
}
|
|
139
|
+
return withoutAbsolutePaths;
|
|
118
140
|
}
|
|
119
141
|
function sanitizeString(input, captureLevel = "redacted") {
|
|
120
142
|
if (captureLevel === "max") {
|
|
@@ -122,7 +144,7 @@ function sanitizeString(input, captureLevel = "redacted") {
|
|
|
122
144
|
}
|
|
123
145
|
return sanitizeStringRedacted(input);
|
|
124
146
|
}
|
|
125
|
-
function sanitizeValue(value, keyHint, captureLevel = "redacted") {
|
|
147
|
+
function sanitizeValue(value, keyHint, captureLevel = "redacted", depth = 0) {
|
|
126
148
|
if (keyHint && isSensitiveKey(keyHint)) {
|
|
127
149
|
return "[redacted]";
|
|
128
150
|
}
|
|
@@ -135,14 +157,17 @@ function sanitizeValue(value, keyHint, captureLevel = "redacted") {
|
|
|
135
157
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
136
158
|
return value;
|
|
137
159
|
}
|
|
160
|
+
if (depth >= TELEMETRY_SANITIZE_MAX_DEPTH) {
|
|
161
|
+
return "[depth_truncated]";
|
|
162
|
+
}
|
|
138
163
|
if (Array.isArray(value)) {
|
|
139
|
-
return value.map((entry) => sanitizeValue(entry, undefined, captureLevel));
|
|
164
|
+
return value.slice(0, TELEMETRY_SANITIZE_MAX_ARRAY_ITEMS).map((entry) => sanitizeValue(entry, undefined, captureLevel, depth + 1));
|
|
140
165
|
}
|
|
141
166
|
if (typeof value === "object") {
|
|
142
167
|
const record = value;
|
|
143
168
|
const sanitized = {};
|
|
144
169
|
for (const [key, nested] of Object.entries(record)) {
|
|
145
|
-
sanitized[key] = sanitizeValue(nested, key, captureLevel);
|
|
170
|
+
sanitized[key] = sanitizeValue(nested, key, captureLevel, depth + 1);
|
|
146
171
|
}
|
|
147
172
|
return sanitized;
|
|
148
173
|
}
|
|
@@ -196,6 +221,28 @@ function normalizePmVersion(value) {
|
|
|
196
221
|
const trimmed = value?.trim() ?? "";
|
|
197
222
|
return trimmed.length > 0 ? trimmed : "0.0.0";
|
|
198
223
|
}
|
|
224
|
+
function normalizeTelemetryErrorCode(value) {
|
|
225
|
+
const normalized = value?.trim();
|
|
226
|
+
return normalized && normalized.length > 0 ? normalized : undefined;
|
|
227
|
+
}
|
|
228
|
+
function normalizeTelemetryExitCode(exitCode, ok) {
|
|
229
|
+
if (Number.isFinite(exitCode)) {
|
|
230
|
+
return Math.max(0, Math.trunc(exitCode ?? 0));
|
|
231
|
+
}
|
|
232
|
+
return ok ? 0 : 1;
|
|
233
|
+
}
|
|
234
|
+
function normalizeTelemetryErrorCategory(params) {
|
|
235
|
+
if (params.ok) {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
if (typeof params.errorCategory === "string" && params.errorCategory.trim().length > 0) {
|
|
239
|
+
return params.errorCategory;
|
|
240
|
+
}
|
|
241
|
+
if (typeof params.errorCode === "string" && params.errorCode.trim().length > 0) {
|
|
242
|
+
return resolveTelemetryErrorCategory(params.errorCode);
|
|
243
|
+
}
|
|
244
|
+
return "unknown";
|
|
245
|
+
}
|
|
199
246
|
function resolveTelemetrySourceContext(globalOptions) {
|
|
200
247
|
const override = (process.env[PM_TELEMETRY_SOURCE_CONTEXT_ENV] ?? "").trim().toLowerCase();
|
|
201
248
|
if (PM_TELEMETRY_SOURCE_CONTEXT_SET.has(override)) {
|
|
@@ -228,6 +275,44 @@ function resolveTelemetrySourceContext(globalOptions) {
|
|
|
228
275
|
function hashWithInstallationId(installationId, value) {
|
|
229
276
|
return crypto.createHash("sha256").update(`${installationId}:${value}`).digest("hex");
|
|
230
277
|
}
|
|
278
|
+
function normalizeForHash(value, depth = 0) {
|
|
279
|
+
if (value === null || value === undefined) {
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
283
|
+
return value;
|
|
284
|
+
}
|
|
285
|
+
if (depth >= TELEMETRY_SANITIZE_MAX_DEPTH) {
|
|
286
|
+
return "[depth_truncated]";
|
|
287
|
+
}
|
|
288
|
+
if (Array.isArray(value)) {
|
|
289
|
+
return value.slice(0, TELEMETRY_SANITIZE_MAX_ARRAY_ITEMS).map((entry) => normalizeForHash(entry, depth + 1));
|
|
290
|
+
}
|
|
291
|
+
if (typeof value === "object") {
|
|
292
|
+
const record = value;
|
|
293
|
+
const normalized = {};
|
|
294
|
+
const sortedKeys = Object.keys(record).sort((left, right) => left.localeCompare(right));
|
|
295
|
+
for (const key of sortedKeys) {
|
|
296
|
+
normalized[key] = normalizeForHash(record[key], depth + 1);
|
|
297
|
+
}
|
|
298
|
+
return normalized;
|
|
299
|
+
}
|
|
300
|
+
return String(value);
|
|
301
|
+
}
|
|
302
|
+
function hashTelemetryValue(installationId, value) {
|
|
303
|
+
return hashWithInstallationId(installationId, JSON.stringify(normalizeForHash(value)));
|
|
304
|
+
}
|
|
305
|
+
function hashCommandArgs(installationId, args) {
|
|
306
|
+
return {
|
|
307
|
+
hashes: args.map((arg) => hashWithInstallationId(installationId, arg)),
|
|
308
|
+
digest: hashWithInstallationId(installationId, args.join("\u0000")),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function hashTelemetryErrorFingerprint(installationId, command, errorCode, errorMessage) {
|
|
312
|
+
const normalizedMessage = sanitizeString(errorMessage ?? "", "redacted");
|
|
313
|
+
const fingerprintSource = `${command}\u0000${errorCode ?? "unknown_error"}\u0000${normalizedMessage}`;
|
|
314
|
+
return hashWithInstallationId(installationId, fingerprintSource);
|
|
315
|
+
}
|
|
231
316
|
function telemetryDisabledByEnvironment() {
|
|
232
317
|
return PM_TELEMETRY_DISABLED_VALUES.has((process.env[PM_TELEMETRY_DISABLED_ENV] ?? "").trim().toLowerCase());
|
|
233
318
|
}
|
|
@@ -294,6 +379,13 @@ async function exportLocalOtelSpan(activeCommand, outcome, finishedAtIso, durati
|
|
|
294
379
|
}
|
|
295
380
|
const serviceNameCandidate = sanitizeString((process.env[OTEL_SERVICE_NAME_ENV] ?? "").trim());
|
|
296
381
|
const serviceName = serviceNameCandidate.length > 0 ? serviceNameCandidate : "pm-cli";
|
|
382
|
+
const normalizedExitCode = normalizeTelemetryExitCode(outcome.exit_code, outcome.ok);
|
|
383
|
+
const normalizedErrorCode = normalizeTelemetryErrorCode(outcome.error_code);
|
|
384
|
+
const normalizedErrorCategory = normalizeTelemetryErrorCategory({
|
|
385
|
+
ok: outcome.ok,
|
|
386
|
+
errorCode: normalizedErrorCode,
|
|
387
|
+
errorCategory: outcome.error_category,
|
|
388
|
+
});
|
|
297
389
|
const attributes = [
|
|
298
390
|
otlpStringAttribute("pm.command", sanitizeString(activeCommand.command)),
|
|
299
391
|
otlpStringAttribute("pm.version", activeCommand.pm_version),
|
|
@@ -304,8 +396,15 @@ async function exportLocalOtelSpan(activeCommand, outcome, finishedAtIso, durati
|
|
|
304
396
|
otlpStringAttribute("pm.pm_root_hash", activeCommand.pm_root_hash),
|
|
305
397
|
otlpStringAttribute("pm.cwd_hash", activeCommand.cwd_hash),
|
|
306
398
|
otlpBoolAttribute("pm.ok", outcome.ok),
|
|
399
|
+
otlpIntAttribute("pm.exit_code", normalizedExitCode),
|
|
307
400
|
otlpIntAttribute("pm.duration_ms", durationMs),
|
|
308
401
|
];
|
|
402
|
+
if (typeof normalizedErrorCode === "string") {
|
|
403
|
+
attributes.push(otlpStringAttribute("pm.error_code", normalizedErrorCode));
|
|
404
|
+
}
|
|
405
|
+
if (typeof normalizedErrorCategory === "string") {
|
|
406
|
+
attributes.push(otlpStringAttribute("pm.error_category", normalizedErrorCategory));
|
|
407
|
+
}
|
|
309
408
|
if (typeof outcome.error === "string" && outcome.error.trim().length > 0) {
|
|
310
409
|
attributes.push(otlpStringAttribute("pm.error", sanitizeString(outcome.error)));
|
|
311
410
|
}
|
|
@@ -374,8 +473,16 @@ function summarizeResult(result, captureLevel = "redacted") {
|
|
|
374
473
|
const record = result;
|
|
375
474
|
const keys = Object.keys(record).sort((left, right) => left.localeCompare(right));
|
|
376
475
|
const sanitized = {};
|
|
476
|
+
let previewBytes = 0;
|
|
377
477
|
for (const key of keys.slice(0, 25)) {
|
|
378
|
-
|
|
478
|
+
const sanitizedValue = sanitizeValue(record[key], key, captureLevel);
|
|
479
|
+
const entrySize = JSON.stringify(sanitizedValue)?.length ?? 0;
|
|
480
|
+
if (previewBytes + entrySize > TELEMETRY_RESULT_PREVIEW_MAX_BYTES) {
|
|
481
|
+
sanitized[key] = "[preview_truncated]";
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
previewBytes += entrySize;
|
|
485
|
+
sanitized[key] = sanitizedValue;
|
|
379
486
|
}
|
|
380
487
|
return {
|
|
381
488
|
type: "object",
|
|
@@ -388,21 +495,37 @@ function summarizeResult(result, captureLevel = "redacted") {
|
|
|
388
495
|
}
|
|
389
496
|
function buildCommandStartPayload(params) {
|
|
390
497
|
const { captureLevel, context, pmVersion, sourceContext, pmRootHash, cwdHash, installationId } = params;
|
|
498
|
+
const commandTaxonomy = deriveTelemetryCommandTaxonomy(context.command);
|
|
499
|
+
const hashedArgs = hashCommandArgs(installationId, context.args);
|
|
500
|
+
const commandInvocationDigest = hashWithInstallationId(installationId, `${context.command}\u0000${context.args.join("\u0000")}`);
|
|
501
|
+
const commandOptionsDigest = hashTelemetryValue(installationId, context.options);
|
|
502
|
+
const globalOptionsDigest = hashTelemetryValue(installationId, context.global);
|
|
391
503
|
if (captureLevel === "minimal") {
|
|
392
504
|
return {
|
|
393
505
|
capture_level: captureLevel,
|
|
394
506
|
pm_version: pmVersion,
|
|
395
507
|
source_context: sourceContext.source_context,
|
|
396
508
|
source_context_source: sourceContext.source_context_source,
|
|
509
|
+
command_taxonomy: commandTaxonomy,
|
|
510
|
+
command_args_digest: hashedArgs.digest,
|
|
511
|
+
command_invocation_digest: commandInvocationDigest,
|
|
512
|
+
command_options_digest: commandOptionsDigest,
|
|
513
|
+
global_options_digest: globalOptionsDigest,
|
|
397
514
|
};
|
|
398
515
|
}
|
|
399
516
|
return {
|
|
400
517
|
pm_version: pmVersion,
|
|
401
518
|
source_context: sourceContext.source_context,
|
|
402
519
|
source_context_source: sourceContext.source_context_source,
|
|
520
|
+
command_taxonomy: commandTaxonomy,
|
|
403
521
|
command_args: sanitizeCommandArgs(context.args, captureLevel),
|
|
522
|
+
command_args_hashes: hashedArgs.hashes,
|
|
523
|
+
command_args_digest: hashedArgs.digest,
|
|
524
|
+
command_invocation_digest: commandInvocationDigest,
|
|
404
525
|
command_options: sanitizeValue(context.options, undefined, captureLevel),
|
|
526
|
+
command_options_digest: commandOptionsDigest,
|
|
405
527
|
global_options: sanitizeValue(context.global, undefined, captureLevel),
|
|
528
|
+
global_options_digest: globalOptionsDigest,
|
|
406
529
|
pm_root_hash: pmRootHash,
|
|
407
530
|
cwd_hash: cwdHash,
|
|
408
531
|
capture_level: captureLevel,
|
|
@@ -417,15 +540,25 @@ function buildCommandStartPayload(params) {
|
|
|
417
540
|
};
|
|
418
541
|
}
|
|
419
542
|
function buildCommandFinishPayload(params) {
|
|
420
|
-
const { captureLevel, pmVersion, sourceContext, outcome, durationMs, startedAt } = params;
|
|
543
|
+
const { captureLevel, pmVersion, sourceContext, outcome, durationMs, startedAt, command, installationId, commandTaxonomy, exitCode, errorCode, errorCategory, commandResolution, resolutionStage, } = params;
|
|
544
|
+
const errorFingerprint = outcome.ok === false
|
|
545
|
+
? hashTelemetryErrorFingerprint(installationId, command, errorCode, outcome.error)
|
|
546
|
+
: undefined;
|
|
421
547
|
if (captureLevel === "minimal") {
|
|
422
548
|
return {
|
|
423
549
|
capture_level: captureLevel,
|
|
424
550
|
pm_version: pmVersion,
|
|
425
551
|
source_context: sourceContext.source_context,
|
|
426
552
|
source_context_source: sourceContext.source_context_source,
|
|
553
|
+
command_taxonomy: commandTaxonomy,
|
|
554
|
+
command_resolution: commandResolution,
|
|
555
|
+
resolution_stage: resolutionStage,
|
|
427
556
|
ok: outcome.ok,
|
|
557
|
+
exit_code: exitCode,
|
|
558
|
+
error_code: errorCode,
|
|
559
|
+
error_category: errorCategory,
|
|
428
560
|
error: outcome.error ? sanitizeString(outcome.error, "redacted") : undefined,
|
|
561
|
+
error_fingerprint: errorFingerprint,
|
|
429
562
|
duration_ms: durationMs,
|
|
430
563
|
};
|
|
431
564
|
}
|
|
@@ -434,13 +567,77 @@ function buildCommandFinishPayload(params) {
|
|
|
434
567
|
pm_version: pmVersion,
|
|
435
568
|
source_context: sourceContext.source_context,
|
|
436
569
|
source_context_source: sourceContext.source_context_source,
|
|
570
|
+
command_taxonomy: commandTaxonomy,
|
|
571
|
+
command_resolution: commandResolution,
|
|
572
|
+
resolution_stage: resolutionStage,
|
|
437
573
|
ok: outcome.ok,
|
|
574
|
+
exit_code: exitCode,
|
|
575
|
+
error_code: errorCode,
|
|
576
|
+
error_category: errorCategory,
|
|
438
577
|
error: outcome.error ? sanitizeString(outcome.error, captureLevel) : undefined,
|
|
578
|
+
error_fingerprint: errorFingerprint,
|
|
439
579
|
duration_ms: durationMs,
|
|
440
580
|
started_at: startedAt,
|
|
441
581
|
result_summary: summarizeResult(outcome.result, captureLevel),
|
|
442
582
|
};
|
|
443
583
|
}
|
|
584
|
+
function buildCommandErrorPayload(params) {
|
|
585
|
+
const { captureLevel, pmVersion, sourceContext, command, commandTaxonomy, commandResolution, resolutionStage, args, options, pmRootHash, cwdHash, installationId, errorCode, errorMessage, errorCategory, exitCode, } = params;
|
|
586
|
+
const attemptedArgHashes = hashCommandArgs(installationId, args);
|
|
587
|
+
const attemptedCommandDigest = hashWithInstallationId(installationId, command);
|
|
588
|
+
const attemptedOptionsDigest = hashTelemetryValue(installationId, options);
|
|
589
|
+
const errorFingerprint = hashTelemetryErrorFingerprint(installationId, command, errorCode, errorMessage);
|
|
590
|
+
if (captureLevel === "minimal") {
|
|
591
|
+
return {
|
|
592
|
+
capture_level: captureLevel,
|
|
593
|
+
pm_version: pmVersion,
|
|
594
|
+
source_context: sourceContext.source_context,
|
|
595
|
+
source_context_source: sourceContext.source_context_source,
|
|
596
|
+
command_taxonomy: commandTaxonomy,
|
|
597
|
+
command_resolution: commandResolution,
|
|
598
|
+
resolution_stage: resolutionStage,
|
|
599
|
+
attempted_command_digest: attemptedCommandDigest,
|
|
600
|
+
attempted_args_digest: attemptedArgHashes.digest,
|
|
601
|
+
attempted_options_digest: attemptedOptionsDigest,
|
|
602
|
+
error_code: errorCode,
|
|
603
|
+
error_category: errorCategory,
|
|
604
|
+
exit_code: exitCode,
|
|
605
|
+
error: sanitizeString(errorMessage, "redacted"),
|
|
606
|
+
error_fingerprint: errorFingerprint,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return {
|
|
610
|
+
capture_level: captureLevel,
|
|
611
|
+
pm_version: pmVersion,
|
|
612
|
+
source_context: sourceContext.source_context,
|
|
613
|
+
source_context_source: sourceContext.source_context_source,
|
|
614
|
+
command_taxonomy: commandTaxonomy,
|
|
615
|
+
command_resolution: commandResolution,
|
|
616
|
+
resolution_stage: resolutionStage,
|
|
617
|
+
attempted_command: sanitizeString(command, captureLevel),
|
|
618
|
+
attempted_command_digest: attemptedCommandDigest,
|
|
619
|
+
attempted_args: sanitizeCommandArgs(args, captureLevel),
|
|
620
|
+
attempted_args_hashes: attemptedArgHashes.hashes,
|
|
621
|
+
attempted_args_digest: attemptedArgHashes.digest,
|
|
622
|
+
attempted_options: sanitizeValue(options, undefined, captureLevel),
|
|
623
|
+
attempted_options_digest: attemptedOptionsDigest,
|
|
624
|
+
error_code: errorCode,
|
|
625
|
+
error_category: errorCategory,
|
|
626
|
+
exit_code: exitCode,
|
|
627
|
+
error: sanitizeString(errorMessage, captureLevel),
|
|
628
|
+
error_fingerprint: errorFingerprint,
|
|
629
|
+
pm_root_hash: pmRootHash,
|
|
630
|
+
cwd_hash: cwdHash,
|
|
631
|
+
runtime: {
|
|
632
|
+
node: process.version,
|
|
633
|
+
platform: process.platform,
|
|
634
|
+
arch: process.arch,
|
|
635
|
+
hostname_hash: hashWithInstallationId(installationId, os.hostname()),
|
|
636
|
+
stdin_tty: process.stdin.isTTY === true,
|
|
637
|
+
stdout_tty: process.stdout.isTTY === true,
|
|
638
|
+
},
|
|
639
|
+
};
|
|
640
|
+
}
|
|
444
641
|
async function ensureInstallationId(globalPmRoot) {
|
|
445
642
|
const settings = await readSettings(globalPmRoot);
|
|
446
643
|
let changed = false;
|
|
@@ -462,7 +659,13 @@ async function enqueueTelemetryEvent(globalPmRoot, event) {
|
|
|
462
659
|
event,
|
|
463
660
|
attempts: 0,
|
|
464
661
|
};
|
|
465
|
-
|
|
662
|
+
let serialized = JSON.stringify(queued);
|
|
663
|
+
if (serialized.length > TELEMETRY_MAX_EVENT_BYTES) {
|
|
664
|
+
const trimmed = { ...event, payload: { ...event.payload, result_summary: { truncated: true, reason: "payload_size_exceeded", original_bytes: serialized.length } } };
|
|
665
|
+
const trimmedQueued = { event: trimmed, attempts: 0 };
|
|
666
|
+
serialized = JSON.stringify(trimmedQueued);
|
|
667
|
+
}
|
|
668
|
+
await appendLineAtomic(queuePath(globalPmRoot), serialized);
|
|
466
669
|
}
|
|
467
670
|
function parseQueueLines(raw) {
|
|
468
671
|
const entries = [];
|
|
@@ -521,7 +724,9 @@ function pruneExpiredQueueEntries(entries, retentionDays) {
|
|
|
521
724
|
const retained = [];
|
|
522
725
|
let prunedCount = 0;
|
|
523
726
|
for (const entry of entries) {
|
|
524
|
-
|
|
727
|
+
const serializedSize = JSON.stringify(entry).length;
|
|
728
|
+
const oversized = serializedSize > TELEMETRY_MAX_EVENT_BYTES;
|
|
729
|
+
if (oversized || isExpiredQueueEntry(entry, cutoffMs) || entry.attempts >= TELEMETRY_MAX_QUEUE_ENTRY_ATTEMPTS) {
|
|
525
730
|
prunedCount += 1;
|
|
526
731
|
continue;
|
|
527
732
|
}
|
|
@@ -578,12 +783,17 @@ async function flushQueue(globalPmRoot, endpoint, retentionDays) {
|
|
|
578
783
|
}
|
|
579
784
|
const dueIds = new Set(dueEntries.map((entry) => entry.event.event_id));
|
|
580
785
|
const attemptTime = nowIso();
|
|
786
|
+
const requestHeaders = {
|
|
787
|
+
"content-type": "application/json",
|
|
788
|
+
};
|
|
789
|
+
const ingestKey = process.env.PM_TELEMETRY_INGEST_KEY?.trim();
|
|
790
|
+
if (ingestKey) {
|
|
791
|
+
requestHeaders["x-pm-telemetry-key"] = ingestKey;
|
|
792
|
+
}
|
|
581
793
|
try {
|
|
582
794
|
const response = await fetch(normalizedEndpoint, {
|
|
583
795
|
method: "POST",
|
|
584
|
-
headers:
|
|
585
|
-
"content-type": "application/json",
|
|
586
|
-
},
|
|
796
|
+
headers: requestHeaders,
|
|
587
797
|
body: JSON.stringify({
|
|
588
798
|
schema_version: TELEMETRY_SCHEMA_VERSION,
|
|
589
799
|
events: dueEntries.map((entry) => entry.event),
|
|
@@ -670,11 +880,13 @@ export async function startTelemetryCommand(context) {
|
|
|
670
880
|
}),
|
|
671
881
|
};
|
|
672
882
|
await enqueueTelemetryEvent(globalPmRoot, event);
|
|
673
|
-
|
|
883
|
+
_lastFlushPromise = flushQueue(globalPmRoot, endpoint, retentionDays).catch(() => { });
|
|
884
|
+
const commandTaxonomy = deriveTelemetryCommandTaxonomy(context.command);
|
|
674
885
|
return {
|
|
675
886
|
started_at: occurredAt,
|
|
676
887
|
started_at_ms: Date.now(),
|
|
677
888
|
command: context.command,
|
|
889
|
+
command_taxonomy: commandTaxonomy,
|
|
678
890
|
pm_version: pmVersion,
|
|
679
891
|
source_context: sourceContext.source_context,
|
|
680
892
|
source_context_source: sourceContext.source_context_source,
|
|
@@ -702,6 +914,25 @@ export async function finishTelemetryCommand(activeCommand, outcome) {
|
|
|
702
914
|
try {
|
|
703
915
|
const finishedAt = nowIso();
|
|
704
916
|
const durationMs = Math.max(0, Date.now() - activeCommand.started_at_ms);
|
|
917
|
+
const normalizedErrorCode = normalizeTelemetryErrorCode(inferTelemetryErrorCode({
|
|
918
|
+
ok: outcome.ok,
|
|
919
|
+
errorCode: outcome.error_code,
|
|
920
|
+
errorMessage: outcome.error,
|
|
921
|
+
exitCode: outcome.exit_code,
|
|
922
|
+
}));
|
|
923
|
+
const normalizedErrorCategory = normalizeTelemetryErrorCategory({
|
|
924
|
+
ok: outcome.ok,
|
|
925
|
+
errorCode: normalizedErrorCode,
|
|
926
|
+
errorCategory: outcome.error_category,
|
|
927
|
+
});
|
|
928
|
+
const normalizedExitCode = normalizeTelemetryExitCode(outcome.exit_code, outcome.ok);
|
|
929
|
+
const commandResolution = outcome.command_resolution ??
|
|
930
|
+
deriveTelemetryCommandResolution({
|
|
931
|
+
ok: outcome.ok,
|
|
932
|
+
errorCode: normalizedErrorCode,
|
|
933
|
+
errorCategory: normalizedErrorCategory,
|
|
934
|
+
});
|
|
935
|
+
const resolutionStage = outcome.resolution_stage ?? "execute";
|
|
705
936
|
const event = {
|
|
706
937
|
schema_version: TELEMETRY_SCHEMA_VERSION,
|
|
707
938
|
event_id: crypto.randomUUID(),
|
|
@@ -720,11 +951,92 @@ export async function finishTelemetryCommand(activeCommand, outcome) {
|
|
|
720
951
|
outcome,
|
|
721
952
|
durationMs,
|
|
722
953
|
startedAt: activeCommand.started_at,
|
|
954
|
+
command: activeCommand.command,
|
|
955
|
+
installationId: activeCommand.installation_id,
|
|
956
|
+
commandTaxonomy: activeCommand.command_taxonomy,
|
|
957
|
+
exitCode: normalizedExitCode,
|
|
958
|
+
errorCode: normalizedErrorCode,
|
|
959
|
+
errorCategory: normalizedErrorCategory,
|
|
960
|
+
commandResolution,
|
|
961
|
+
resolutionStage,
|
|
723
962
|
}),
|
|
724
963
|
};
|
|
725
964
|
await enqueueTelemetryEvent(activeCommand.global_pm_root, event);
|
|
726
|
-
|
|
727
|
-
|
|
965
|
+
_lastFlushPromise = flushQueue(activeCommand.global_pm_root, activeCommand.endpoint, activeCommand.retention_days).catch(() => { });
|
|
966
|
+
void exportLocalOtelSpan(activeCommand, {
|
|
967
|
+
...outcome,
|
|
968
|
+
exit_code: normalizedExitCode,
|
|
969
|
+
error_code: normalizedErrorCode,
|
|
970
|
+
error_category: normalizedErrorCategory,
|
|
971
|
+
}, finishedAt, durationMs).catch(() => { });
|
|
972
|
+
}
|
|
973
|
+
catch {
|
|
974
|
+
// Telemetry must never block command execution.
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
export async function emitTelemetryErrorEvent(context) {
|
|
978
|
+
if (telemetryDisabledByEnvironment()) {
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
try {
|
|
982
|
+
const globalPmRoot = resolveGlobalPmRoot(process.cwd());
|
|
983
|
+
const settings = await readSettings(globalPmRoot);
|
|
984
|
+
if (!settings.telemetry.enabled) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const captureLevel = normalizeCaptureLevel(settings.telemetry.capture_level);
|
|
988
|
+
const { installationId, endpoint, retentionDays } = await ensureInstallationId(globalPmRoot);
|
|
989
|
+
const pmVersion = normalizePmVersion(context.pm_version);
|
|
990
|
+
const sourceContext = resolveTelemetrySourceContext(context.global);
|
|
991
|
+
const pmRootHash = hashWithInstallationId(installationId, context.pm_root);
|
|
992
|
+
const cwdHash = hashWithInstallationId(installationId, process.cwd());
|
|
993
|
+
const occurredAt = nowIso();
|
|
994
|
+
const normalizedErrorCode = normalizeTelemetryErrorCode(inferTelemetryErrorCode({
|
|
995
|
+
ok: false,
|
|
996
|
+
errorCode: context.error_code,
|
|
997
|
+
errorMessage: context.error_message,
|
|
998
|
+
exitCode: context.exit_code,
|
|
999
|
+
})) ?? "unknown_error";
|
|
1000
|
+
const normalizedErrorCategory = context.error_category ?? resolveTelemetryErrorCategory(normalizedErrorCode);
|
|
1001
|
+
const normalizedExitCode = normalizeTelemetryExitCode(context.exit_code, false);
|
|
1002
|
+
const normalizedCommand = context.command.trim().length > 0 ? context.command : "<unknown>";
|
|
1003
|
+
const commandTaxonomy = deriveTelemetryCommandTaxonomy(normalizedCommand);
|
|
1004
|
+
const commandResolution = context.command_resolution ??
|
|
1005
|
+
deriveTelemetryCommandResolution({
|
|
1006
|
+
ok: false,
|
|
1007
|
+
errorCode: normalizedErrorCode,
|
|
1008
|
+
errorCategory: normalizedErrorCategory,
|
|
1009
|
+
});
|
|
1010
|
+
const resolutionStage = context.resolution_stage ?? "unknown";
|
|
1011
|
+
const event = {
|
|
1012
|
+
schema_version: TELEMETRY_SCHEMA_VERSION,
|
|
1013
|
+
event_id: crypto.randomUUID(),
|
|
1014
|
+
event_type: "command_error",
|
|
1015
|
+
occurred_at: occurredAt,
|
|
1016
|
+
installation_id: installationId,
|
|
1017
|
+
session_id: PROCESS_SESSION_ID,
|
|
1018
|
+
command: sanitizeString(normalizedCommand, "redacted"),
|
|
1019
|
+
payload: buildCommandErrorPayload({
|
|
1020
|
+
captureLevel,
|
|
1021
|
+
pmVersion,
|
|
1022
|
+
sourceContext,
|
|
1023
|
+
command: normalizedCommand,
|
|
1024
|
+
commandTaxonomy,
|
|
1025
|
+
commandResolution,
|
|
1026
|
+
resolutionStage,
|
|
1027
|
+
args: context.args,
|
|
1028
|
+
options: context.options,
|
|
1029
|
+
pmRootHash,
|
|
1030
|
+
cwdHash,
|
|
1031
|
+
installationId,
|
|
1032
|
+
errorCode: normalizedErrorCode,
|
|
1033
|
+
errorCategory: normalizedErrorCategory,
|
|
1034
|
+
exitCode: normalizedExitCode,
|
|
1035
|
+
errorMessage: context.error_message,
|
|
1036
|
+
}),
|
|
1037
|
+
};
|
|
1038
|
+
await enqueueTelemetryEvent(globalPmRoot, event);
|
|
1039
|
+
_lastFlushPromise = flushQueue(globalPmRoot, endpoint, retentionDays).catch(() => { });
|
|
728
1040
|
}
|
|
729
1041
|
catch {
|
|
730
1042
|
// Telemetry must never block command execution.
|