From d679862a88df831685e142fb0cb40db16225d5c8 Mon Sep 17 00:00:00 2001 From: Marc Seiler Date: Mon, 30 Mar 2026 17:37:05 -0400 Subject: [PATCH 1/6] feat(otel): add OTLP HTTP exporter support --- CHANGELOG.md | 6 ++++++ README.md | 19 +++++++++++++++++-- bun.lock | 7 +++++++ package.json | 3 +++ src/config.ts | 3 +++ src/index.ts | 4 +++- src/otel.ts | 26 +++++++++++++++++++++++--- tests/config.test.ts | 12 ++++++++++++ tests/probe.test.ts | 15 +++++++++++++++ 9 files changed, 89 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9a30b2..ffb1dd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and --- +## [Unreleased] + +### Features + +* **otel:** add OTLP HTTP/protobuf exporter support via `OPENCODE_OTLP_PROTOCOL` + ## [0.6.0](https://github.com/DEVtheOPS/opencode-plugin-otel/compare/v0.5.0...v0.6.0) (2026-03-26) diff --git a/README.md b/README.md index 89811a6..1689749 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Build status](https://img.shields.io/github/actions/workflow/status/DEVtheOPS/opencode-plugin-otel/release-please.yml?branch=main)](https://github.com/DEVtheOPS/opencode-plugin-otel/actions/workflows/release-please.yml) [![License](https://img.shields.io/npm/l/@devtheops/opencode-plugin-otel.svg)](https://github.com/DEVtheOPS/opencode-plugin-otel/blob/main/LICENSE) -An [opencode](https://opencode.ai) plugin that exports telemetry via OpenTelemetry (OTLP/gRPC), mirroring the same signals as [Claude Code's monitoring](https://code.claude.com/docs/en/monitoring-usage). +An [opencode](https://opencode.ai) plugin that exports telemetry via OpenTelemetry (OTLP over gRPC or HTTP/protobuf), mirroring the same signals as [Claude Code's monitoring](https://code.claude.com/docs/en/monitoring-usage). - [What it instruments](#what-it-instruments) - [Metrics](#metrics) @@ -82,7 +82,8 @@ All configuration is via environment variables. Set them in your shell profile ( | Variable | Default | Description | |----------|---------|-------------| | `OPENCODE_ENABLE_TELEMETRY` | _(unset)_ | Set to any non-empty value to enable the plugin | -| `OPENCODE_OTLP_ENDPOINT` | `http://localhost:4317` | gRPC OTLP collector endpoint | +| `OPENCODE_OTLP_ENDPOINT` | `http://localhost:4317` | OTLP collector endpoint. For `grpc`, use the collector host/port. For `http/protobuf`, use the base URL and the plugin will append `/v1/traces`, `/v1/metrics`, and `/v1/logs`. | +| `OPENCODE_OTLP_PROTOCOL` | `grpc` | OTLP transport protocol: `grpc` or `http/protobuf` | | `OPENCODE_OTLP_METRICS_INTERVAL` | `60000` | Metrics export interval in milliseconds | | `OPENCODE_OTLP_LOGS_INTERVAL` | `5000` | Logs export interval in milliseconds | | `OPENCODE_METRIC_PREFIX` | `opencode.` | Prefix for all metric names (e.g. set to `claude_code.` for Claude Code dashboard compatibility) | @@ -95,9 +96,12 @@ All configuration is via environment variables. Set them in your shell profile ( ```bash export OPENCODE_ENABLE_TELEMETRY=1 export OPENCODE_OTLP_ENDPOINT=http://localhost:4317 +export OPENCODE_OTLP_PROTOCOL=grpc opencode ``` +For `OPENCODE_OTLP_PROTOCOL=http/protobuf`, set `OPENCODE_OTLP_ENDPOINT` to the collector base URL rather than a per-signal path. The plugin expands it to `/v1/traces`, `/v1/metrics`, and `/v1/logs` automatically. + ### Headers and resource attributes ```bash @@ -147,6 +151,7 @@ export OPENCODE_DISABLE_METRICS="cache.count,session.duration,session.token.tota ```bash export OPENCODE_ENABLE_TELEMETRY=1 export OPENCODE_OTLP_ENDPOINT=https://api.datadoghq.com +export OPENCODE_OTLP_PROTOCOL=http/protobuf ``` ### Honeycomb example @@ -154,6 +159,16 @@ export OPENCODE_OTLP_ENDPOINT=https://api.datadoghq.com ```bash export OPENCODE_ENABLE_TELEMETRY=1 export OPENCODE_OTLP_ENDPOINT=https://api.honeycomb.io +export OPENCODE_OTLP_PROTOCOL=http/protobuf +``` + +### Grafana Cloud example + +```bash +export OPENCODE_ENABLE_TELEMETRY=1 +export OPENCODE_OTLP_ENDPOINT=https://otlp-gateway-prod-us-central-0.grafana.net/otlp +export OPENCODE_OTLP_PROTOCOL=http/protobuf +export OPENCODE_OTLP_HEADERS="Authorization=Basic " ``` ### Claude Code dashboard compatibility diff --git a/bun.lock b/bun.lock index cefa465..6e723a0 100644 --- a/bun.lock +++ b/bun.lock @@ -9,8 +9,11 @@ "@opencode-ai/sdk": "^1.2.23", "@opentelemetry/api": "^1.9.0", "@opentelemetry/exporter-logs-otlp-grpc": "^0.213.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.213.0", "@opentelemetry/exporter-metrics-otlp-grpc": "^0.213.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.213.0", "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.213.0", "@opentelemetry/resources": "^2.6.0", "@opentelemetry/sdk-logs": "^0.213.0", "@opentelemetry/sdk-metrics": "^2.6.0", @@ -42,12 +45,16 @@ "@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.213.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-grpc-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/sdk-logs": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-QiRZzvayEOFnenSXi85Eorgy5WTqyNQ+E7gjl6P6r+W3IUIwAIH8A9/BgMWfP056LwmdrBL6+qvnwaIEmug6Yg=="], + "@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.213.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.213.0", "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/sdk-logs": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-vqDVSpLp09ZzcFIdb7QZrEFPxUlO3GzdhBKLstq3jhYB5ow3+ZtV5V0ngSdi/0BZs+J5WPiN1+UDV4X5zD/GzA=="], + "@opentelemetry/exporter-metrics-otlp-grpc": ["@opentelemetry/exporter-metrics-otlp-grpc@0.213.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.0", "@opentelemetry/exporter-metrics-otlp-http": "0.213.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-grpc-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-metrics": "2.6.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Z8gYKUAU48qwm+a1tjnGv9xbE7a5lukVIwgF6Z5i3VPXPVMe4Sjra0nN3zU7m277h+V+ZpsPGZJ2Xf0OTkL7/w=="], "@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-metrics": "2.6.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-yw3fTIw4KQIRXC/ZyYQq5gtA3Ogfdfz/g5HVgleobQAcjUUE8Nj3spGMx8iQPp+S+u6/js7BixufRkXhzLmpJA=="], "@opentelemetry/exporter-trace-otlp-grpc": ["@opentelemetry/exporter-trace-otlp-grpc@0.213.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-grpc-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-trace-base": "2.6.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-L8y6piP4jBIIx1Nv7/9hkx25ql6/Cro/kQrs+f9e8bPF0Ar5Dm991v7PnbtubKz6Q4fT872H56QXUWVnz/Cs4Q=="], + "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-trace-base": "2.6.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-tnRmJD39aWrE/Sp7F6AbRNAjKHToDkAqBi6i0lESpGWz3G+f4bhVAV6mgSXH2o18lrDVJXo6jf9bAywQw43wRA=="], + "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.213.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-transformer": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg=="], "@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.213.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.0", "@opentelemetry/otlp-exporter-base": "0.213.0", "@opentelemetry/otlp-transformer": "0.213.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-XgRGuLE9usFNlnw2lgMIM4HTwpcIyjdU/xPoJ8v3LbBLBfjaDkIugjc9HoWa7ZSJ/9Bhzgvm/aD0bGdYUFgnTw=="], diff --git a/package.json b/package.json index 18ae35a..aa8693f 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,11 @@ "@opencode-ai/sdk": "^1.2.23", "@opentelemetry/api": "^1.9.0", "@opentelemetry/exporter-logs-otlp-grpc": "^0.213.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.213.0", "@opentelemetry/exporter-metrics-otlp-grpc": "^0.213.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.213.0", "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.213.0", "@opentelemetry/resources": "^2.6.0", "@opentelemetry/sdk-logs": "^0.213.0", "@opentelemetry/sdk-metrics": "^2.6.0", diff --git a/src/config.ts b/src/config.ts index 4c06247..687676a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,6 +4,7 @@ import { LEVELS, type Level } from "./types.ts" export type PluginConfig = { enabled: boolean endpoint: string + protocol: "grpc" | "http/protobuf" metricsInterval: number logsInterval: number metricPrefix: string @@ -31,6 +32,7 @@ export function parseEnvInt(key: string, fallback: number): number { export function loadConfig(): PluginConfig { const otlpHeaders = process.env["OPENCODE_OTLP_HEADERS"] const resourceAttributes = process.env["OPENCODE_RESOURCE_ATTRIBUTES"] + const protocol = process.env["OPENCODE_OTLP_PROTOCOL"] if (otlpHeaders) process.env["OTEL_EXPORTER_OTLP_HEADERS"] = otlpHeaders if (resourceAttributes) process.env["OTEL_RESOURCE_ATTRIBUTES"] = resourceAttributes @@ -52,6 +54,7 @@ export function loadConfig(): PluginConfig { return { enabled: !!process.env["OPENCODE_ENABLE_TELEMETRY"], endpoint: process.env["OPENCODE_OTLP_ENDPOINT"] ?? "http://localhost:4317", + protocol: protocol === "http/protobuf" ? "http/protobuf" : "grpc", metricsInterval: parseEnvInt("OPENCODE_OTLP_METRICS_INTERVAL", 60000), logsInterval: parseEnvInt("OPENCODE_OTLP_LOGS_INTERVAL", 5000), metricPrefix: process.env["OPENCODE_METRIC_PREFIX"] ?? "opencode.", diff --git a/src/index.ts b/src/index.ts index 8675d16..225842d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,7 +27,7 @@ import { handleSessionDiff, handleCommandExecuted } from "./handlers/activity.ts const PLUGIN_VERSION: string = (pkg as { version?: string }).version ?? "unknown" /** - * OpenCode plugin that exports session telemetry via OpenTelemetry (OTLP/gRPC). + * OpenCode plugin that exports session telemetry via OpenTelemetry (OTLP over gRPC or HTTP/protobuf). * Instruments metrics (sessions, tokens, cost, lines of code, commits, tool durations) * and structured log events. All instrumentation is gated on `OPENCODE_ENABLE_TELEMETRY`. */ @@ -48,6 +48,7 @@ export const OtelPlugin: Plugin = async ({ project, client }) => { await log("info", "starting up", { version: PLUGIN_VERSION, endpoint: config.endpoint, + protocol: config.protocol, metricsInterval: config.metricsInterval, logsInterval: config.logsInterval, metricPrefix: config.metricPrefix, @@ -70,6 +71,7 @@ export const OtelPlugin: Plugin = async ({ project, client }) => { const { meterProvider, loggerProvider, tracerProvider } = setupOtel( config.endpoint, + config.protocol, config.metricsInterval, config.logsInterval, PLUGIN_VERSION, diff --git a/src/otel.ts b/src/otel.ts index 661f820..98721c5 100644 --- a/src/otel.ts +++ b/src/otel.ts @@ -6,6 +6,9 @@ import { BasicTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trac import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-grpc" import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-grpc" import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc" +import { OTLPLogExporter as OTLPHttpLogExporter } from "@opentelemetry/exporter-logs-otlp-http" +import { OTLPMetricExporter as OTLPHttpMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http" +import { OTLPTraceExporter as OTLPHttpTraceExporter } from "@opentelemetry/exporter-trace-otlp-http" import { resourceFromAttributes } from "@opentelemetry/resources" import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions" import { ATTR_HOST_ARCH } from "@opentelemetry/semantic-conventions/incubating" @@ -44,6 +47,13 @@ export type OtelProviders = { tracerProvider: BasicTracerProvider } +export function buildHttpSignalUrl(endpoint: string, signal: "traces" | "metrics" | "logs") { + const url = new URL(endpoint) + const normalizedPath = url.pathname.endsWith("/") ? url.pathname.slice(0, -1) : url.pathname + url.pathname = `${normalizedPath}/v1/${signal}` + return url.toString() +} + /** * Initialises the OTel SDK — creates a `MeterProvider`, `LoggerProvider`, and * `BasicTracerProvider` backed by OTLP/gRPC exporters pointed at `endpoint`, and @@ -51,17 +61,27 @@ export type OtelProviders = { */ export function setupOtel( endpoint: string, + protocol: "grpc" | "http/protobuf", metricsInterval: number, logsInterval: number, version: string, ): OtelProviders { const resource = buildResource(version) + const metricExporter = protocol === "http/protobuf" + ? new OTLPHttpMetricExporter({ url: buildHttpSignalUrl(endpoint, "metrics") }) + : new OTLPMetricExporter({ url: endpoint }) + const logExporter = protocol === "http/protobuf" + ? new OTLPHttpLogExporter({ url: buildHttpSignalUrl(endpoint, "logs") }) + : new OTLPLogExporter({ url: endpoint }) + const traceExporter = protocol === "http/protobuf" + ? new OTLPHttpTraceExporter({ url: buildHttpSignalUrl(endpoint, "traces") }) + : new OTLPTraceExporter({ url: endpoint }) const meterProvider = new MeterProvider({ resource, readers: [ new PeriodicExportingMetricReader({ - exporter: new OTLPMetricExporter({ url: endpoint }), + exporter: metricExporter, exportIntervalMillis: metricsInterval, }), ], @@ -71,7 +91,7 @@ export function setupOtel( const loggerProvider = new LoggerProvider({ resource, processors: [ - new BatchLogRecordProcessor(new OTLPLogExporter({ url: endpoint }), { + new BatchLogRecordProcessor(logExporter, { scheduledDelayMillis: logsInterval, }), ], @@ -80,7 +100,7 @@ export function setupOtel( const tracerProvider = new BasicTracerProvider({ resource, - spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter({ url: endpoint }))], + spanProcessors: [new BatchSpanProcessor(traceExporter)], }) trace.setGlobalTracerProvider(tracerProvider) diff --git a/tests/config.test.ts b/tests/config.test.ts index 6ac4b79..378a43c 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -44,6 +44,7 @@ describe("loadConfig", () => { const vars = [ "OPENCODE_ENABLE_TELEMETRY", "OPENCODE_OTLP_ENDPOINT", + "OPENCODE_OTLP_PROTOCOL", "OPENCODE_OTLP_METRICS_INTERVAL", "OPENCODE_OTLP_LOGS_INTERVAL", "OPENCODE_OTLP_HEADERS", @@ -60,6 +61,7 @@ describe("loadConfig", () => { const cfg = loadConfig() expect(cfg.enabled).toBe(false) expect(cfg.endpoint).toBe("http://localhost:4317") + expect(cfg.protocol).toBe("grpc") expect(cfg.metricsInterval).toBe(60000) expect(cfg.logsInterval).toBe(5000) }) @@ -74,6 +76,16 @@ describe("loadConfig", () => { expect(loadConfig().endpoint).toBe("http://collector:4317") }) + test("reads HTTP/protobuf protocol", () => { + process.env["OPENCODE_OTLP_PROTOCOL"] = "http/protobuf" + expect(loadConfig().protocol).toBe("http/protobuf") + }) + + test("falls back to grpc for unknown protocol", () => { + process.env["OPENCODE_OTLP_PROTOCOL"] = "http" + expect(loadConfig().protocol).toBe("grpc") + }) + test("reads custom intervals", () => { process.env["OPENCODE_OTLP_METRICS_INTERVAL"] = "30000" process.env["OPENCODE_OTLP_LOGS_INTERVAL"] = "2000" diff --git a/tests/probe.test.ts b/tests/probe.test.ts index 7c57b97..66e94cb 100644 --- a/tests/probe.test.ts +++ b/tests/probe.test.ts @@ -1,5 +1,6 @@ import { describe, test, expect } from "bun:test" import { probeEndpoint, parseEndpoint } from "../src/probe.ts" +import { buildHttpSignalUrl } from "../src/otel.ts" describe("parseEndpoint", () => { test("uses port 80 for http:// URLs without explicit port", () => { @@ -56,3 +57,17 @@ describe("probeEndpoint", () => { } }) }) + +describe("buildHttpSignalUrl", () => { + test("appends signal path to a bare host", () => { + expect(buildHttpSignalUrl("https://otlp.example.com", "traces")).toBe("https://otlp.example.com/v1/traces") + }) + + test("appends signal path to a base OTLP path", () => { + expect(buildHttpSignalUrl("https://otlp.example.com/otlp", "metrics")).toBe("https://otlp.example.com/otlp/v1/metrics") + }) + + test("normalizes a trailing slash before appending", () => { + expect(buildHttpSignalUrl("https://otlp.example.com/otlp/", "logs")).toBe("https://otlp.example.com/otlp/v1/logs") + }) +}) From 0dda12ab0e53ac8328f182c9a5387cc61e8f031a Mon Sep 17 00:00:00 2001 From: Marc Seiler Date: Mon, 30 Mar 2026 17:43:34 -0400 Subject: [PATCH 2/6] docs(otel): update setup docstring for HTTP support --- src/otel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/otel.ts b/src/otel.ts index 98721c5..3b9071c 100644 --- a/src/otel.ts +++ b/src/otel.ts @@ -56,8 +56,8 @@ export function buildHttpSignalUrl(endpoint: string, signal: "traces" | "metrics /** * Initialises the OTel SDK — creates a `MeterProvider`, `LoggerProvider`, and - * `BasicTracerProvider` backed by OTLP/gRPC exporters pointed at `endpoint`, and - * registers them as the global providers. + * `BasicTracerProvider` backed by OTLP exporters (gRPC or HTTP/protobuf) + * pointed at `endpoint`, and registers them as the global providers. */ export function setupOtel( endpoint: string, From fb8990a67c5107c743d884a95a3f10da32c2f5d1 Mon Sep 17 00:00:00 2001 From: Marc Seiler Date: Mon, 30 Mar 2026 18:08:16 -0400 Subject: [PATCH 3/6] ci(jsdoc): enforce local documentation quality checks --- .coderabbit.yaml | 27 ++++ .github/PULL_REQUEST_TEMPLATE.md | 4 +- .github/workflows/ci.yml | 6 + .pre-commit-config.yaml | 14 ++ CONTRIBUTING.md | 6 +- bun.lock | 219 +++++++++++++++++++++++++++++++ eslint.config.mjs | 30 +++++ package.json | 9 +- scripts/check-jsdoc-coverage.mjs | 50 +++++++ tests/jsdoc-coverage.test.ts | 14 ++ tsconfig.json | 3 +- 11 files changed, 376 insertions(+), 6 deletions(-) create mode 100644 .coderabbit.yaml create mode 100644 eslint.config.mjs create mode 100644 scripts/check-jsdoc-coverage.mjs create mode 100644 tests/jsdoc-coverage.test.ts diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..d25fcb3 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,27 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +language: en-US + +reviews: + profile: chill + high_level_summary: true + review_status: true + poem: false + auto_review: + enabled: true + drafts: false + path_filters: + - "!**/*.lock" + - "!dist/**" + - "!coverage/**" + - "!**/*.snap" + pre_merge_checks: + docstrings: + mode: warning + threshold: 80 + description: + mode: warning + issue_assessment: + mode: warning + +chat: + auto_reply: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 291b4b2..6c9e1c9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -17,6 +17,8 @@ - [ ] I have read the [CONTRIBUTING.md](../CONTRIBUTING.md) document - [ ] My code follows the style guidelines of this project +- [ ] `bun run lint` passes with no errors +- [ ] `bun run check:jsdoc-coverage` passes with no errors - [ ] `bun run typecheck` passes with no errors - [ ] `bun test` passes with no errors - [ ] I have added tests that prove my fix is effective or that my feature works @@ -29,4 +31,4 @@ ## Additional context - \ No newline at end of file + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59d8830..a3f6402 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,12 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile + - name: Lint + run: bun run lint + + - name: JSDoc coverage + run: bun run check:jsdoc-coverage + - name: Typecheck run: bun run typecheck diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d786f6c..cb20973 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,6 +23,20 @@ repos: - repo: local hooks: + - id: eslint + name: ESLint + language: system + entry: bun run lint + pass_filenames: false + types: [ts] + + - id: jsdoc-coverage + name: JSDoc coverage + language: system + entry: bun run check:jsdoc-coverage + pass_filenames: false + types: [ts] + - id: typecheck name: TypeScript typecheck language: system diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ffbd622..8322058 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,6 +30,8 @@ opencode loads TypeScript natively via Bun, so there is no build step required d | Command | Description | |---------|-------------| +| `bun run lint` | Run ESLint, including JSDoc formatting checks | +| `bun run check:jsdoc-coverage` | Enforce minimum JSDoc coverage for exported API declarations | | `bun run typecheck` | Type-check all sources without emitting | | `bun test` | Run the test suite | @@ -109,9 +111,9 @@ chore(deps): bump @opentelemetry/api to 1.10.0 ## Submitting changes 1. Fork the repo and create a branch from `main`: `git checkout -b feat/my-feature` -2. Make your changes and ensure `bun run typecheck` and `bun test` pass +2. Make your changes and ensure `bun run lint`, `bun run check:jsdoc-coverage`, `bun run typecheck`, and `bun test` pass 3. Commit using Conventional Commits format -4. Open a pull request — the title should also follow Conventional Commits format +4. Open a pull request with a clear, human-readable title and link any related issues in the description ## Releasing diff --git a/bun.lock b/bun.lock index 6e723a0..fed699d 100644 --- a/bun.lock +++ b/bun.lock @@ -23,14 +23,45 @@ }, "devDependencies": { "@types/bun": "latest", + "@typescript-eslint/parser": "^8.30.1", + "eslint": "^9.24.0", + "eslint-plugin-jsdoc": "^50.6.11", }, }, }, "packages": { + "@es-joy/jsdoccomment": ["@es-joy/jsdoccomment@0.50.2", "", { "dependencies": { "@types/estree": "^1.0.6", "@typescript-eslint/types": "^8.11.0", "comment-parser": "1.4.1", "esquery": "^1.6.0", "jsdoc-type-pratt-parser": "~4.1.0" } }, "sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="], + + "@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + "@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="], "@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="], + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], "@opencode-ai/plugin": ["@opencode-ai/plugin@1.2.23", "", { "dependencies": { "@opencode-ai/sdk": "1.2.23", "zod": "4.1.8" } }, "sha512-5DRhitKrMK02u0AKrF+jIK+gUj0GyzN34bXkABXgNMTYDPwiMPkh4UPDJjjRrlofRgWQgmSLFc0nARHX4f92qA=="], @@ -93,44 +124,220 @@ "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.0", "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.0", "@typescript-eslint/tsconfig-utils": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], + + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "are-docs-informative": ["are-docs-informative@0.0.2", "", {}, "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], + "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "comment-parser": ["comment-parser@1.4.1", "", {}, "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="], + + "eslint-plugin-jsdoc": ["eslint-plugin-jsdoc@50.8.0", "", { "dependencies": { "@es-joy/jsdoccomment": "~0.50.2", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", "debug": "^4.4.1", "escape-string-regexp": "^4.0.0", "espree": "^10.3.0", "esquery": "^1.6.0", "parse-imports-exports": "^0.2.4", "semver": "^7.7.2", "spdx-expression-parse": "^4.0.0" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, "sha512-UyGb5755LMFWPrZTEqqvTJ3urLz1iqj+bYOHFNag+sw3NvaMWP9K2z+uIn37XfNALmQLQyrBlJ5mkiVPL7ADEg=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "jsdoc-type-pratt-parser": ["jsdoc-type-pratt-parser@4.1.0", "", {}, "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-imports-exports": ["parse-imports-exports@0.2.4", "", { "dependencies": { "parse-statements": "1.0.11" } }, "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ=="], + + "parse-statements": ["parse-statements@1.0.11", "", {}, "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@4.0.0", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.23", "", {}, "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw=="], + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], @@ -139,6 +346,18 @@ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], } } diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..313068c --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,30 @@ +import jsdoc from "eslint-plugin-jsdoc" +import tsParser from "@typescript-eslint/parser" + +/** @type {import("eslint").Linter.Config[]} */ +const config = [ + { + files: ["src/**/*.ts", "tests/**/*.ts", "scripts/**/*.mjs"], + languageOptions: { + parser: tsParser, + sourceType: "module", + ecmaVersion: "latest", + }, + plugins: { + jsdoc, + }, + rules: { + "jsdoc/check-alignment": "error", + "jsdoc/check-indentation": "error", + "jsdoc/no-bad-blocks": "error", + "jsdoc/require-asterisk-prefix": "error", + }, + settings: { + jsdoc: { + mode: "typescript", + }, + }, + }, +] + +export default config diff --git a/package.json b/package.json index aa8693f..1d4c37d 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,9 @@ "access": "public" }, "scripts": { - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "bun --bun eslint .", + "check:jsdoc-coverage": "bun scripts/check-jsdoc-coverage.mjs" }, "dependencies": { "@opencode-ai/plugin": "^1.2.23", @@ -53,6 +55,9 @@ "typescript": "^5.9.3" }, "devDependencies": { - "@types/bun": "latest" + "@types/bun": "latest", + "@typescript-eslint/parser": "^8.30.1", + "eslint": "^9.24.0", + "eslint-plugin-jsdoc": "^50.6.11" } } diff --git a/scripts/check-jsdoc-coverage.mjs b/scripts/check-jsdoc-coverage.mjs new file mode 100644 index 0000000..6ff60be --- /dev/null +++ b/scripts/check-jsdoc-coverage.mjs @@ -0,0 +1,50 @@ +import ts from "typescript" + +const THRESHOLD = 0.8 +const SOURCE_DIR = new URL("../src/", import.meta.url) + +function hasJSDoc(node, sourceFile) { + const ranges = ts.getLeadingCommentRanges(sourceFile.getFullText(), node.getFullStart()) ?? [] + return ranges.some((range) => sourceFile.getFullText().slice(range.pos, range.end).startsWith("/**")) +} + +function visit(node, sourceFile, counts) { + if (ts.isFunctionDeclaration(node) && node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) { + counts.total += 1 + if (hasJSDoc(node, sourceFile)) counts.documented += 1 + } + + if (ts.isVariableStatement(node) && node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) { + counts.total += 1 + if (hasJSDoc(node, sourceFile)) counts.documented += 1 + } + + if ((ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) && node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) { + counts.total += 1 + if (hasJSDoc(node, sourceFile)) counts.documented += 1 + } + + ts.forEachChild(node, (child) => visit(child, sourceFile, counts)) +} + +async function getSourceFiles(dirUrl) { + const cwd = Bun.fileURLToPath(dirUrl) + return Array.fromAsync(new Bun.Glob("**/*.ts").scan({ cwd, absolute: true })) +} + +const files = await getSourceFiles(SOURCE_DIR) +const counts = { total: 0, documented: 0 } + +for (const file of files) { + const sourceText = await Bun.file(file).text() + const sourceFile = ts.createSourceFile(file, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS) + visit(sourceFile, sourceFile, counts) +} + +const coverage = counts.total === 0 ? 1 : counts.documented / counts.total +if (coverage < THRESHOLD) { + console.error(`JSDoc coverage ${(coverage * 100).toFixed(2)}% is below required ${(THRESHOLD * 100).toFixed(2)}% (${counts.documented}/${counts.total})`) + process.exit(1) +} + +console.log(`JSDoc coverage ${(coverage * 100).toFixed(2)}% (${counts.documented}/${counts.total})`) diff --git a/tests/jsdoc-coverage.test.ts b/tests/jsdoc-coverage.test.ts new file mode 100644 index 0000000..52dedde --- /dev/null +++ b/tests/jsdoc-coverage.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, test } from "bun:test" + +describe("JSDoc coverage baseline", () => { + test("exported API coverage stays at or above 80%", async () => { + const proc = Bun.spawn(["bun", "run", "check:jsdoc-coverage"], { + cwd: "/Users/digitalfiz/Development/github/devtheops/opencode-plugin-otel", + stdout: "pipe", + stderr: "pipe", + }) + const exitCode = await proc.exited + const stderr = await new Response(proc.stderr).text() + expect(exitCode, stderr).toBe(0) + }) +}) diff --git a/tsconfig.json b/tsconfig.json index 32ebfb5..e192db2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,5 +26,6 @@ "noUnusedLocals": false, "noUnusedParameters": false, "noPropertyAccessFromIndexSignature": false - } + }, + "exclude": ["eslint.config.mjs"] } From 82cc3f217df1ba491769604add62aa3ec4ba7a25 Mon Sep 17 00:00:00 2001 From: Marc Seiler Date: Mon, 30 Mar 2026 18:10:56 -0400 Subject: [PATCH 4/6] style(repo): apply pre-commit formatting --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/workflows/opencode.yml | 2 +- CHANGELOG.md | 5 -- SECURITY.md | 4 +- package.json | 70 +++++++++++----------- 7 files changed, 41 insertions(+), 46 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 36d5f39..1d3b948 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -71,4 +71,4 @@ body: - label: I have searched existing issues to ensure this hasn't been reported required: true - label: I have redacted any sensitive information (API keys, tokens, etc.) - required: true \ No newline at end of file + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 4f430c3..56b3d02 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -5,4 +5,4 @@ contact_links: about: Share and discuss ideas for new features - name: Questions & Help url: https://github.com/DEVtheOPS/opencode-plugin-otel/discussions/categories/q-a - about: Ask the community for help \ No newline at end of file + about: Ask the community for help diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 140d628..72eacd2 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -52,4 +52,4 @@ body: - label: I have searched existing issues and discussions for similar feature requests required: true - label: I have checked the changelog to see if this already exists - required: true \ No newline at end of file + required: true diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml index 07d33d7..4155d12 100644 --- a/.github/workflows/opencode.yml +++ b/.github/workflows/opencode.yml @@ -30,4 +30,4 @@ jobs: env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} with: - model: opencode/big-pickle \ No newline at end of file + model: opencode/big-pickle diff --git a/CHANGELOG.md b/CHANGELOG.md index ffb1dd7..66b8d1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,28 +14,24 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and ## [0.6.0](https://github.com/DEVtheOPS/opencode-plugin-otel/compare/v0.5.0...v0.6.0) (2026-03-26) - ### Features * **config:** add OPENCODE_DISABLE_TRACES for per-type trace suppression ([89cb9b9](https://github.com/DEVtheOPS/opencode-plugin-otel/commit/89cb9b9b9b1f79559f3930a2017ca16f513785b3)) * **tracing:** add OpenTelemetry traces with gen_ai.* and tool spans ([0a00b43](https://github.com/DEVtheOPS/opencode-plugin-otel/commit/0a00b43c714c45146ac93b9077c478127727e6ce)) * **tracing:** add OpenTelemetry traces with gen_ai.* and tool spans ([6c848a7](https://github.com/DEVtheOPS/opencode-plugin-otel/commit/6c848a7bca237ab60e7035244d4889dae44560ca)), closes [#19](https://github.com/DEVtheOPS/opencode-plugin-otel/issues/19) - ### Bug Fixes * **traces:** apply metricPrefix to opencode span names and fix out-of-order parentage ([65f1e70](https://github.com/DEVtheOPS/opencode-plugin-otel/commit/65f1e70ec592a571dd5fd410769920cc5c6e1142)) ## [0.5.0](https://github.com/DEVtheOPS/opencode-plugin-otel/compare/v0.4.1...v0.5.0) (2026-03-21) - ### Features * **handlers:** add agent usage metrics and sub-agent tracking ([2d12f88](https://github.com/DEVtheOPS/opencode-plugin-otel/commit/2d12f8846425075c4d8aac1573ac6e488bf868c3)) ## [0.4.1](https://github.com/DEVtheOPS/opencode-plugin-otel/compare/v0.4.0...v0.4.1) (2026-03-16) - ### Bug Fixes * Normalize token and cost units for Claude compatibility ([a8b35dc](https://github.com/DEVtheOPS/opencode-plugin-otel/commit/a8b35dc65e84c646b40abccc534afb6110ba2f26)) @@ -44,7 +40,6 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and ## [0.4.0](https://github.com/DEVtheOPS/opencode-plugin-otel/compare/v0.3.0...v0.4.0) (2026-03-15) - ### Features * **config:** add OPENCODE_DISABLE_METRICS to suppress individual metrics ([8ec7c48](https://github.com/DEVtheOPS/opencode-plugin-otel/commit/8ec7c486d102921829a26d1f377df6aa20d988ad)) diff --git a/SECURITY.md b/SECURITY.md index 59c6a45..564735b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,7 +12,7 @@ If you discover a security vulnerability, please report it responsibly: 1. **Do not** open a public issue -2. Email the maintainer at security@devtheops.com +2. Email the maintainer at 3. Include: - Description of the vulnerability - Steps to reproduce @@ -30,4 +30,4 @@ If you discover a security vulnerability, please report it responsibly: English is preferred, but we will do our best to accommodate other languages. -Thank you for helping keep this project secure. \ No newline at end of file +Thank you for helping keep this project secure. diff --git a/package.json b/package.json index 1d4c37d..793ba09 100644 --- a/package.json +++ b/package.json @@ -1,42 +1,8 @@ { - "name": "@devtheops/opencode-plugin-otel", - "version": "0.6.0", - "description": "OpenTelemetry telemetry plugin for opencode CLI", - "keywords": [ - "opentelemetry", - "otel", - "telemetry", - "opencode", - "plugin", - "observability" - ], - "license": "MPL-2.0", "author": "DEVtheOPS", - "homepage": "https://github.com/DEVtheOPS/opencode-plugin-otel#readme", "bugs": { "url": "https://github.com/DEVtheOPS/opencode-plugin-otel/issues" }, - "repository": { - "type": "git", - "url": "https://github.com/DEVtheOPS/opencode-plugin-otel.git" - }, - "type": "module", - "main": "src/index.ts", - "module": "src/index.ts", - "exports": { - ".": "./src/index.ts" - }, - "files": [ - "src/" - ], - "publishConfig": { - "access": "public" - }, - "scripts": { - "typecheck": "tsc --noEmit", - "lint": "bun --bun eslint .", - "check:jsdoc-coverage": "bun scripts/check-jsdoc-coverage.mjs" - }, "dependencies": { "@opencode-ai/plugin": "^1.2.23", "@opencode-ai/sdk": "^1.2.23", @@ -54,10 +20,44 @@ "@opentelemetry/semantic-conventions": "^1.40.0", "typescript": "^5.9.3" }, + "description": "OpenTelemetry telemetry plugin for opencode CLI", "devDependencies": { "@types/bun": "latest", "@typescript-eslint/parser": "^8.30.1", "eslint": "^9.24.0", "eslint-plugin-jsdoc": "^50.6.11" - } + }, + "exports": { + ".": "./src/index.ts" + }, + "files": [ + "src/" + ], + "homepage": "https://github.com/DEVtheOPS/opencode-plugin-otel#readme", + "keywords": [ + "opentelemetry", + "otel", + "telemetry", + "opencode", + "plugin", + "observability" + ], + "license": "MPL-2.0", + "main": "src/index.ts", + "module": "src/index.ts", + "name": "@devtheops/opencode-plugin-otel", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/DEVtheOPS/opencode-plugin-otel.git" + }, + "scripts": { + "check:jsdoc-coverage": "bun scripts/check-jsdoc-coverage.mjs", + "lint": "bun --bun eslint .", + "typecheck": "tsc --noEmit" + }, + "type": "module", + "version": "0.6.0" } From ea465ad05ad216c09014995614264659982bb435 Mon Sep 17 00:00:00 2001 From: Marc Seiler Date: Mon, 30 Mar 2026 18:23:09 -0400 Subject: [PATCH 5/6] test(jsdoc): use current Bun executable in coverage check --- tests/jsdoc-coverage.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/jsdoc-coverage.test.ts b/tests/jsdoc-coverage.test.ts index 52dedde..57b4ef9 100644 --- a/tests/jsdoc-coverage.test.ts +++ b/tests/jsdoc-coverage.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test" describe("JSDoc coverage baseline", () => { test("exported API coverage stays at or above 80%", async () => { - const proc = Bun.spawn(["bun", "run", "check:jsdoc-coverage"], { + const proc = Bun.spawn([process.execPath, "run", "check:jsdoc-coverage"], { cwd: "/Users/digitalfiz/Development/github/devtheops/opencode-plugin-otel", stdout: "pipe", stderr: "pipe", From 96a3788e9627b1a35f15dd41eaa3e62bb21ac2ff Mon Sep 17 00:00:00 2001 From: Marc Seiler Date: Mon, 30 Mar 2026 18:27:11 -0400 Subject: [PATCH 6/6] test(jsdoc): run coverage check in process --- scripts/check-jsdoc-coverage.mjs | 35 ++++++++++++++++++++------------ tests/jsdoc-coverage.test.ts | 11 +++------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/scripts/check-jsdoc-coverage.mjs b/scripts/check-jsdoc-coverage.mjs index 6ff60be..815310f 100644 --- a/scripts/check-jsdoc-coverage.mjs +++ b/scripts/check-jsdoc-coverage.mjs @@ -1,6 +1,6 @@ import ts from "typescript" -const THRESHOLD = 0.8 +export const JSDOC_COVERAGE_THRESHOLD = 0.8 const SOURCE_DIR = new URL("../src/", import.meta.url) function hasJSDoc(node, sourceFile) { @@ -32,19 +32,28 @@ async function getSourceFiles(dirUrl) { return Array.fromAsync(new Bun.Glob("**/*.ts").scan({ cwd, absolute: true })) } -const files = await getSourceFiles(SOURCE_DIR) -const counts = { total: 0, documented: 0 } +export async function getJSDocCoverage(dirUrl = SOURCE_DIR) { + const files = await getSourceFiles(dirUrl) + const counts = { total: 0, documented: 0 } -for (const file of files) { - const sourceText = await Bun.file(file).text() - const sourceFile = ts.createSourceFile(file, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS) - visit(sourceFile, sourceFile, counts) -} + for (const file of files) { + const sourceText = await Bun.file(file).text() + const sourceFile = ts.createSourceFile(file, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS) + visit(sourceFile, sourceFile, counts) + } -const coverage = counts.total === 0 ? 1 : counts.documented / counts.total -if (coverage < THRESHOLD) { - console.error(`JSDoc coverage ${(coverage * 100).toFixed(2)}% is below required ${(THRESHOLD * 100).toFixed(2)}% (${counts.documented}/${counts.total})`) - process.exit(1) + return { + ...counts, + coverage: counts.total === 0 ? 1 : counts.documented / counts.total, + } } -console.log(`JSDoc coverage ${(coverage * 100).toFixed(2)}% (${counts.documented}/${counts.total})`) +if (import.meta.main) { + const result = await getJSDocCoverage() + if (result.coverage < JSDOC_COVERAGE_THRESHOLD) { + console.error(`JSDoc coverage ${(result.coverage * 100).toFixed(2)}% is below required ${(JSDOC_COVERAGE_THRESHOLD * 100).toFixed(2)}% (${result.documented}/${result.total})`) + process.exit(1) + } + + console.log(`JSDoc coverage ${(result.coverage * 100).toFixed(2)}% (${result.documented}/${result.total})`) +} diff --git a/tests/jsdoc-coverage.test.ts b/tests/jsdoc-coverage.test.ts index 57b4ef9..716ee03 100644 --- a/tests/jsdoc-coverage.test.ts +++ b/tests/jsdoc-coverage.test.ts @@ -1,14 +1,9 @@ import { describe, expect, test } from "bun:test" +import { getJSDocCoverage, JSDOC_COVERAGE_THRESHOLD } from "../scripts/check-jsdoc-coverage.mjs" describe("JSDoc coverage baseline", () => { test("exported API coverage stays at or above 80%", async () => { - const proc = Bun.spawn([process.execPath, "run", "check:jsdoc-coverage"], { - cwd: "/Users/digitalfiz/Development/github/devtheops/opencode-plugin-otel", - stdout: "pipe", - stderr: "pipe", - }) - const exitCode = await proc.exited - const stderr = await new Response(proc.stderr).text() - expect(exitCode, stderr).toBe(0) + const result = await getJSDocCoverage() + expect(result.coverage).toBeGreaterThanOrEqual(JSDOC_COVERAGE_THRESHOLD) }) })