From b182b69cc725a5414b938142ec2279a5166d0cdf Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Feb 2026 02:14:02 +0000 Subject: [PATCH] feat(metrics): Add repository and source dimensions to skill metrics Adds `repository` (full name e.g. "owner/repo") and `source` ("cli" or "action") attributes to all Sentry metrics emitted by emitSkillMetrics. This enables slicing findings, cost, and duration by repository and by whether the run originated from a GitHub PR or the CLI. Co-Authored-By: Claude Opus 4.6 https://claude.ai/code/session_01TcAbGVYiyoCMNC7PYrJ1kS --- specs/telemetry.md | 12 ++++++------ src/cli/output/tasks.ts | 4 +++- src/sentry.ts | 17 +++++++++++++++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/specs/telemetry.md b/specs/telemetry.md index fa6532d6..a84bc88a 100644 --- a/specs/telemetry.md +++ b/specs/telemetry.md @@ -233,12 +233,12 @@ Emitted via `Sentry.metrics.*`. Each function is a no-op when Sentry is not init | Metric | Type | Attributes | |--------|------|------------| -| `skill.duration` | distribution (ms) | `skill` | -| `tokens.input` | distribution | `skill` | -| `tokens.output` | distribution | `skill` | -| `cost.usd` | distribution | `skill` | -| `findings.total` | count | `skill` | -| `findings` | count | `skill`, `severity` | +| `skill.duration` | distribution (ms) | `skill`, `repository`, `source` | +| `tokens.input` | distribution | `skill`, `repository`, `source` | +| `tokens.output` | distribution | `skill`, `repository`, `source` | +| `cost.usd` | distribution | `skill`, `repository`, `source` | +| `findings.total` | count | `skill`, `repository`, `source` | +| `findings` | count | `skill`, `repository`, `source`, `severity` | ### Extraction (`emitExtractionMetrics`) diff --git a/src/cli/output/tasks.ts b/src/cli/output/tasks.ts index 5d7577cb..a3258dce 100644 --- a/src/cli/output/tasks.ts +++ b/src/cli/output/tasks.ts @@ -432,7 +432,9 @@ export async function runSkillTask( } // Emit metrics and log completion - emitSkillMetrics(report); + emitSkillMetrics(report, { + repository: context.repository.fullName, + }); logger.info(logger.fmt`Skill execution complete: ${displayName}`, { 'finding.count': report.findings.length, 'duration_ms': report.durationMs, diff --git a/src/sentry.ts b/src/sentry.ts index 4ae912fd..d9766af7 100644 --- a/src/sentry.ts +++ b/src/sentry.ts @@ -6,6 +6,7 @@ import { getVersion } from './utils/index.js'; export type SentryContext = 'cli' | 'action'; let initialized = false; +let deploymentContext: SentryContext | undefined; export function initSentry(context: SentryContext): void { const dsn = process.env['WARDEN_SENTRY_DSN']; @@ -25,6 +26,7 @@ export function initSentry(context: SentryContext): void { ], }); + deploymentContext = context; Sentry.setTag('deployment.context', context); Sentry.setTag('service.version', getVersion()); } @@ -45,9 +47,20 @@ function safeEmit(fn: () => void): void { } } -export function emitSkillMetrics(report: SkillReport): void { +export interface SkillMetricsContext { + /** Full repository name (e.g. "owner/repo") */ + repository?: string; +} + +export function emitSkillMetrics(report: SkillReport, context?: SkillMetricsContext): void { safeEmit(() => { - const attrs = { skill: report.skill }; + const attrs: Record = { skill: report.skill }; + if (context?.repository) { + attrs['repository'] = context.repository; + } + if (deploymentContext) { + attrs['source'] = deploymentContext; + } Sentry.metrics.distribution('skill.duration', report.durationMs ?? 0, { unit: 'millisecond',