From c534b3fb351e03a9b86e1150840c5eeff7d3611f Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 5 Jul 2026 12:50:14 -0700 Subject: [PATCH 1/6] inject agent version only in release builds --- packages/agent/src/adapters/claude/claude-agent.ts | 3 ++- packages/agent/src/adapters/codex/codex-agent.ts | 3 ++- packages/agent/src/posthog-api.ts | 4 ++-- packages/agent/src/server/agent-server.ts | 12 +++++------- packages/agent/src/version.ts | 6 ++++++ packages/agent/tsup.config.ts | 14 ++++++++++++++ packages/ui/src/utils/agentVersion.ts | 5 +++-- 7 files changed, 34 insertions(+), 13 deletions(-) create mode 100644 packages/agent/src/version.ts diff --git a/packages/agent/src/adapters/claude/claude-agent.ts b/packages/agent/src/adapters/claude/claude-agent.ts index e20e9d520a..a6bbf67822 100644 --- a/packages/agent/src/adapters/claude/claude-agent.ts +++ b/packages/agent/src/adapters/claude/claude-agent.ts @@ -71,6 +71,7 @@ import { import { resolveGithubToken } from "../../utils/github-token"; import { Logger } from "../../utils/logger"; import { Pushable } from "../../utils/streams"; +import { AGENT_VERSION } from "../../version"; import { BaseAcpAgent } from "../base-acp-agent"; import { LOCAL_TOOLS_MCP_NAME } from "../local-tools"; import { resolveTaskId } from "../session-meta"; @@ -311,7 +312,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent { agentInfo: { name: packageJson.name, title: "Claude Agent", - version: packageJson.version, + version: AGENT_VERSION, }, authMethods: [], }; diff --git a/packages/agent/src/adapters/codex/codex-agent.ts b/packages/agent/src/adapters/codex/codex-agent.ts index 11eac60acb..671759b34f 100644 --- a/packages/agent/src/adapters/codex/codex-agent.ts +++ b/packages/agent/src/adapters/codex/codex-agent.ts @@ -65,6 +65,7 @@ import { nodeReadableToWebReadable, nodeWritableToWebWritable, } from "../../utils/streams"; +import { AGENT_VERSION } from "../../version"; import { BaseAcpAgent, type BaseSession } from "../base-acp-agent"; import { buildBreakdown, @@ -482,7 +483,7 @@ export class CodexAcpAgent extends BaseAcpAgent { agentInfo: { name: packageJson.name, title: "Codex Agent", - version: packageJson.version, + version: AGENT_VERSION, }, }; } diff --git a/packages/agent/src/posthog-api.ts b/packages/agent/src/posthog-api.ts index fdeb360188..2b6d8cd070 100644 --- a/packages/agent/src/posthog-api.ts +++ b/packages/agent/src/posthog-api.ts @@ -1,4 +1,3 @@ -import packageJson from "../package.json" with { type: "json" }; import type { ArtifactType, PostHogAPIConfig, @@ -12,6 +11,7 @@ import { getGatewayUsageUrl, getLlmGatewayUrl, } from "./utils/gateway"; +import { AGENT_VERSION } from "./version"; export { getGatewayInvalidatePlanCacheUrl, @@ -19,7 +19,7 @@ export { getLlmGatewayUrl, }; -const DEFAULT_USER_AGENT = `posthog/agent.hog.dev; version: ${packageJson.version}`; +const DEFAULT_USER_AGENT = `posthog/agent.hog.dev; version: ${AGENT_VERSION}`; export interface TaskArtifactUploadPayload { name: string; diff --git a/packages/agent/src/server/agent-server.ts b/packages/agent/src/server/agent-server.ts index 02ebfba7a1..7503fdecf3 100644 --- a/packages/agent/src/server/agent-server.ts +++ b/packages/agent/src/server/agent-server.ts @@ -19,7 +19,6 @@ import { getCurrentBranch } from "@posthog/git/queries"; import { unzipSync } from "fflate"; import { Hono } from "hono"; import { z } from "zod"; -import packageJson from "../../package.json" with { type: "json" }; import { POSTHOG_METHODS, POSTHOG_NOTIFICATIONS } from "../acp-extensions"; import { createAcpConnection, @@ -68,6 +67,7 @@ import { resolveLlmGatewayUrl, } from "../utils/gateway"; import { Logger } from "../utils/logger"; +import { AGENT_VERSION } from "../version"; import { logAgentshRuntimeInfo } from "./agentsh-runtime"; import { normalizeCloudPromptContent, @@ -377,7 +377,7 @@ export class AgentServer { apiUrl: config.apiUrl, projectId: config.projectId, getApiKey: () => config.apiKey, - userAgent: `posthog/cloud.hog.dev; version: ${config.version ?? packageJson.version}`, + userAgent: `posthog/cloud.hog.dev; version: ${config.version ?? AGENT_VERSION}`, }); if (config.eventIngestToken) { this.eventStreamSender = new TaskRunEventStreamSender({ @@ -1174,7 +1174,7 @@ export class AgentServer { apiUrl: this.config.apiUrl, projectId: this.config.projectId, getApiKey: () => this.config.apiKey, - userAgent: `posthog/cloud.hog.dev; version: ${this.config.version ?? packageJson.version}`, + userAgent: `posthog/cloud.hog.dev; version: ${this.config.version ?? AGENT_VERSION}`, }); const logWriter = new SessionLogWriter({ @@ -1355,9 +1355,7 @@ export class AgentServer { bootMs: this.sessionReadyBootMs, sessionInitMs: this.sessionInitMs, }); - this.logger.debug( - `Agent version: ${this.config.version ?? packageJson.version}`, - ); + this.logger.debug(`Agent version: ${this.config.version ?? AGENT_VERSION}`); await logAgentshRuntimeInfo(this.logger); this.logger.debug(`Initial permission mode: ${initialPermissionMode}`); @@ -1372,7 +1370,7 @@ export class AgentServer { sessionId: acpSessionId, runId: payload.run_id, taskId: payload.task_id, - agentVersion: this.config.version ?? packageJson.version, + agentVersion: this.config.version ?? AGENT_VERSION, }, }; this.broadcastEvent({ diff --git a/packages/agent/src/version.ts b/packages/agent/src/version.ts new file mode 100644 index 0000000000..1aaf3389bb --- /dev/null +++ b/packages/agent/src/version.ts @@ -0,0 +1,6 @@ +declare const __AGENT_VERSION__: string | undefined; + +// tsup injects the real version only for npm release builds (AGENT_RELEASE_BUILD=1); +// source builds (vite, vitest, dev tsup) keep the "latest source" sentinel. +export const AGENT_VERSION: string = + typeof __AGENT_VERSION__ === "string" ? __AGENT_VERSION__ : "0.0.0-dev"; diff --git a/packages/agent/tsup.config.ts b/packages/agent/tsup.config.ts index bb5335eb1f..cf6868ab77 100644 --- a/packages/agent/tsup.config.ts +++ b/packages/agent/tsup.config.ts @@ -4,6 +4,7 @@ import { cpSync, existsSync, mkdirSync, + readFileSync, writeFileSync, } from "node:fs"; import { builtinModules } from "node:module"; @@ -78,11 +79,24 @@ function copyAssets() { ); } +// Release builds (AGENT_RELEASE_BUILD=1, set only by the npm release workflow) bake in +// the real package version; every other build keeps the 0.0.0-dev sentinel that +// packages/ui/src/utils/agentVersion.ts treats as "latest source, satisfies any range". +const agentVersion = + process.env.AGENT_RELEASE_BUILD === "1" + ? JSON.parse( + readFileSync(resolve(import.meta.dirname, "package.json"), "utf8"), + ).version + : "0.0.0-dev"; + const sharedOptions = { sourcemap: true, splitting: false, outDir: "dist", target: "node20", + define: { + __AGENT_VERSION__: JSON.stringify(agentVersion), + }, noExternal: [ "@posthog/shared", "@posthog/git", diff --git a/packages/ui/src/utils/agentVersion.ts b/packages/ui/src/utils/agentVersion.ts index 30c7103c0d..304cb50862 100644 --- a/packages/ui/src/utils/agentVersion.ts +++ b/packages/ui/src/utils/agentVersion.ts @@ -1,7 +1,8 @@ import semver from "semver"; -/** Sentinel version used by unbuilt dev builds (matches the placeholder in - * `packages/agent/package.json`). Real release builds inject a real semver. */ +/** Sentinel version reported by source builds (matches the default in + * `packages/agent/src/version.ts`). Only npm release builds of the agent + * inject a real semver, via tsup with AGENT_RELEASE_BUILD=1. */ const DEV_VERSION = "0.0.0-dev"; /** From 76db086a2dcbeb2c593749ed648e37379909eefd Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 5 Jul 2026 12:50:26 -0700 Subject: [PATCH 2/6] add changesets for agent releases --- .changeset/README.md | 15 + .changeset/config.json | 16 + apps/code/package.json | 1 + package.json | 1 + packages/electron-trpc/package.json | 1 + packages/enricher/package.json | 1 + packages/git/package.json | 1 + packages/platform/package.json | 1 + packages/shared/package.json | 1 + packages/ui/package.json | 2 +- pnpm-lock.yaml | 435 ++++++++++++++++++++++++++-- 11 files changed, 443 insertions(+), 32 deletions(-) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000000..2c18995cf9 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,15 @@ +# Changesets + +This folder drives releases of `@posthog/agent`, the only npm-published package +in this repo. A merged changeset triggers the release pipeline in +`.github/workflows/agent-release.yml`, which prepares and verifies a release +candidate and then waits for human approval before publishing. + +To request a release, run `pnpm changeset` at the repo root, select +`@posthog/agent`, pick a semver bump and describe the change. Changes to +`packages/shared`, `packages/git` or `packages/enricher` also ship through +`@posthog/agent` (they are bundled into it), so changesets for those changes +must target `@posthog/agent` too. + +See `packages/agent/RELEASING.md` for the full release process and +[changesets docs](https://github.com/changesets/changesets) for the format. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000000..799e294bba --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "bumpVersionsWithWorkspaceProtocolOnly": true, + "privatePackages": { + "version": false, + "tag": false + }, + "ignore": [] +} diff --git a/apps/code/package.json b/apps/code/package.json index 2f573e9504..733428ed86 100644 --- a/apps/code/package.json +++ b/apps/code/package.json @@ -2,6 +2,7 @@ "name": "@posthog/code", "version": "0.0.0-dev", "description": "PostHog Code - desktop task manager", + "private": true, "homepage": "https://posthog.com", "main": ".vite/build/bootstrap.js", "versionHash": "dynamic", diff --git a/package.json b/package.json index dbbff8f14f..2626bb9298 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "license": "MIT", "devDependencies": { "@biomejs/biome": "2.2.4", + "@changesets/cli": "^2.31.0", "@posthog/cli": "^0.7.3", "fflate": "^0.8.2", "husky": "^9.1.7", diff --git a/packages/electron-trpc/package.json b/packages/electron-trpc/package.json index 7713c8cf30..bf973fdb23 100644 --- a/packages/electron-trpc/package.json +++ b/packages/electron-trpc/package.json @@ -1,6 +1,7 @@ { "name": "@posthog/electron-trpc", "description": "Electron support for tRPC (forked with HMR fix)", + "private": true, "version": "0.1.0", "type": "module", "exports": { diff --git a/packages/enricher/package.json b/packages/enricher/package.json index 4f859e981e..72556981ef 100644 --- a/packages/enricher/package.json +++ b/packages/enricher/package.json @@ -2,6 +2,7 @@ "name": "@posthog/enricher", "version": "1.0.0", "description": "Detect and enrich PostHog SDK usage in source code", + "private": true, "type": "module", "exports": { ".": { diff --git a/packages/git/package.json b/packages/git/package.json index 6f76e486c3..df1606da79 100644 --- a/packages/git/package.json +++ b/packages/git/package.json @@ -2,6 +2,7 @@ "name": "@posthog/git", "version": "1.0.0", "description": "Git utilities for PostHog Code", + "private": true, "type": "module", "exports": { "./*": { diff --git a/packages/platform/package.json b/packages/platform/package.json index 326da8093b..e8194b5d1c 100644 --- a/packages/platform/package.json +++ b/packages/platform/package.json @@ -2,6 +2,7 @@ "name": "@posthog/platform", "version": "1.0.0", "description": "Host-agnostic platform port interfaces. Zero runtime deps; implemented by per-host adapters (Electron, Node server, React Native, web).", + "private": true, "type": "module", "exports": { "./url-launcher": { diff --git a/packages/shared/package.json b/packages/shared/package.json index 7a1ee01192..db92aa7e51 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -2,6 +2,7 @@ "name": "@posthog/shared", "version": "1.0.0", "description": "Shared utilities for PostHog Code", + "private": true, "type": "module", "exports": { ".": { diff --git a/packages/ui/package.json b/packages/ui/package.json index a93ec5c3a5..7faa7fc28a 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,7 +1,7 @@ { "name": "@posthog/ui", "version": "1.0.0", - "description": "React UI layer. Components, stores, hooks. Pure rendering and UI state \u2014 no I/O, no business logic. Built on @posthog/quill. Consumed by every host app (desktop, web, mobile-web).", + "description": "React UI layer. Components, stores, hooks. Pure rendering and UI state — no I/O, no business logic. Built on @posthog/quill. Consumed by every host app (desktop, web, mobile-web).", "private": true, "type": "module", "exports": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1cb2f3bb09..dbc51d9532 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -94,6 +94,9 @@ importers: '@biomejs/biome': specifier: 2.2.4 version: 2.2.4 + '@changesets/cli': + specifier: ^2.31.0 + version: 2.31.0(@types/node@25.2.0) '@posthog/cli': specifier: ^0.7.3 version: 0.7.5 @@ -2348,6 +2351,61 @@ packages: '@borewit/text-codec@0.2.2': resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} + '@changesets/apply-release-plan@7.1.1': + resolution: {integrity: sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==} + + '@changesets/assemble-release-plan@6.0.10': + resolution: {integrity: sha512-rSDcqdJ9KbVyjpBIuCidhvZNIiVt1XaIYp73ycVQRIA5n/j6wQaEk0ChRLMUQ1vkxZe51PTQ9OIhbg6HQMW45A==} + + '@changesets/changelog-git@0.2.1': + resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} + + '@changesets/cli@2.31.0': + resolution: {integrity: sha512-AhI4enNTgHu2IZr6K4WZyf0EPch4XVMn1yOMFmCD9gsfBGqMYaHXls5HyDv6/CL5axVQABz68eG30eCtbr2wFg==} + hasBin: true + + '@changesets/config@3.1.4': + resolution: {integrity: sha512-pf0bvD/v6WI2cRlZ6hzpjtZdSlXDXMAJ+Iz7xfFzV4ZxJ8OGGAON+1qYc99ZPrijnt4xp3VGG7eNvAOGS24V1Q==} + + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + + '@changesets/get-dependents-graph@2.1.4': + resolution: {integrity: sha512-ZsS00x6WvmHq3sQv8oCMwL0f/z3wbXCVuSVTJwCnnmbC/iBdNJGFx1EcbMG4PC6sXRyH69liM4A2WKXzn/kRPg==} + + '@changesets/get-release-plan@4.0.16': + resolution: {integrity: sha512-2K5Om6CrMPm45rtvckfzWo7e9jOVCKLCnXia5eUPaURH7/LWzri7pK1TycdzAuAtehLkW7VPbWLCSExTHmiI6g==} + + '@changesets/get-version-range-type@0.4.0': + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + + '@changesets/git@3.0.4': + resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} + + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + + '@changesets/parse@0.4.3': + resolution: {integrity: sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==} + + '@changesets/pre@2.0.2': + resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} + + '@changesets/read@0.6.7': + resolution: {integrity: sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==} + + '@changesets/should-skip-package@0.1.2': + resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} + + '@changesets/types@4.1.0': + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + + '@changesets/types@6.1.0': + resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} + + '@changesets/write@0.4.0': + resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@codemirror/autocomplete@6.20.0': resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} @@ -3408,6 +3466,15 @@ packages: '@types/node': optional: true + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/figures@1.0.15': resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} engines: {node: '>=18'} @@ -3846,6 +3913,12 @@ packages: resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} engines: {node: '>= 10.0.0'} + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} @@ -7210,6 +7283,9 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@types/node@16.9.1': resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==} @@ -7578,6 +7654,10 @@ packages: anser@1.4.10: resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -7664,6 +7744,10 @@ packages: array-timsort@1.0.3: resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -7824,6 +7908,10 @@ packages: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} engines: {node: '>=12.0.0'} + better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + better-sqlite3@12.10.1: resolution: {integrity: sha512-HfFtzCqnSfwB3+HroF6PSKzyh+7RfNMGPCzHFUZXRlvrPCb4P3cvxKZNN43Sr7IrkofqQZM+gIvffGpA8VvqgA==} engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x} @@ -8030,6 +8118,9 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} + check-error@2.1.3: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} @@ -8534,6 +8625,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + detect-libc@1.0.3: resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} engines: {node: '>=0.10'} @@ -8562,6 +8657,10 @@ packages: dir-compare@4.2.0: resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -8906,6 +9005,10 @@ packages: resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} engines: {node: '>=10.13.0'} + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -9419,6 +9522,9 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -9765,6 +9871,10 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -9910,6 +10020,10 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + human-id@4.2.0: + resolution: {integrity: sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA==} + hasBin: true + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -10150,6 +10264,10 @@ packages: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} + is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + is-typed-array@1.1.15: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} @@ -10166,6 +10284,10 @@ packages: resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} engines: {node: '>=18'} + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -10706,6 +10828,9 @@ packages: lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + lodash.throttle@4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} @@ -11176,6 +11301,10 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -11457,6 +11586,9 @@ packages: orderedmap@2.1.1: resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} @@ -11501,6 +11633,10 @@ packages: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -11517,6 +11653,10 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + p-map@7.0.4: resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} engines: {node: '>=18'} @@ -11528,6 +11668,9 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} @@ -11676,6 +11819,10 @@ packages: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -12032,6 +12179,9 @@ packages: resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} engines: {node: '>=0.6'} + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + query-selector-shadow-dom@1.0.1: resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} @@ -12323,6 +12473,10 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -12827,6 +12981,9 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + split-on-first@1.1.0: resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} engines: {node: '>=6'} @@ -13105,6 +13262,10 @@ packages: resolution: {integrity: sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==} engines: {node: '>=6.0.0'} + term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + terminal-link@2.1.1: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} @@ -15005,6 +15166,149 @@ snapshots: '@borewit/text-codec@0.2.2': {} + '@changesets/apply-release-plan@7.1.1': + dependencies: + '@changesets/config': 3.1.4 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.8.4 + + '@changesets/assemble-release-plan@6.0.10': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.8.4 + + '@changesets/changelog-git@0.2.1': + dependencies: + '@changesets/types': 6.1.0 + + '@changesets/cli@2.31.0(@types/node@25.2.0)': + dependencies: + '@changesets/apply-release-plan': 7.1.1 + '@changesets/assemble-release-plan': 6.0.10 + '@changesets/changelog-git': 0.2.1 + '@changesets/config': 3.1.4 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.4 + '@changesets/get-release-plan': 4.0.16 + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.7 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@changesets/write': 0.4.0 + '@inquirer/external-editor': 1.0.3(@types/node@25.2.0) + '@manypkg/get-packages': 1.1.3 + ansi-colors: 4.1.3 + enquirer: 2.4.1 + fs-extra: 7.0.1 + mri: 1.2.0 + package-manager-detector: 0.2.11 + picocolors: 1.1.1 + resolve-from: 5.0.0 + semver: 7.8.4 + spawndamnit: 3.0.1 + term-size: 2.2.1 + transitivePeerDependencies: + - '@types/node' + + '@changesets/config@3.1.4': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.4 + '@changesets/logger': 0.1.1 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.4': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.1 + semver: 7.8.4 + + '@changesets/get-release-plan@4.0.16': + dependencies: + '@changesets/assemble-release-plan': 6.0.10 + '@changesets/config': 3.1.4 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.7 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.1 + + '@changesets/parse@0.4.3': + dependencies: + '@changesets/types': 6.1.0 + js-yaml: 4.1.1 + + '@changesets/pre@2.0.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.7': + dependencies: + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.3 + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/should-skip-package@0.1.2': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.1.0': {} + + '@changesets/write@0.4.0': + dependencies: + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + human-id: 4.2.0 + prettier: 2.8.8 + '@codemirror/autocomplete@6.20.0': dependencies: '@codemirror/language': 6.12.2 @@ -16200,6 +16504,13 @@ snapshots: optionalDependencies: '@types/node': 25.2.0 + '@inquirer/external-editor@1.0.3(@types/node@25.2.0)': + dependencies: + chardet: 2.2.0 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 25.2.0 + '@inquirer/figures@1.0.15': {} '@inquirer/type@3.0.10(@types/node@20.19.41)': @@ -16839,6 +17150,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.29.2 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.29.2 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + '@marijn/find-cluster-break@1.0.2': {} '@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.6)': @@ -19874,6 +20201,8 @@ snapshots: '@types/ms@2.1.0': {} + '@types/node@12.20.55': {} + '@types/node@16.9.1': {} '@types/node@20.19.41': @@ -20146,7 +20475,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(@vitest/ui@4.1.8)(jsdom@26.1.0)(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) + vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.8)(jsdom@26.1.0)(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/utils@3.2.4': dependencies: @@ -20383,6 +20712,8 @@ snapshots: anser@1.4.10: {} + ansi-colors@4.1.3: {} + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -20498,6 +20829,8 @@ snapshots: array-timsort@1.0.3: {} + array-union@2.1.0: {} + asap@2.0.6: {} asn1js@3.0.10: @@ -20723,6 +21056,10 @@ snapshots: dependencies: open: 8.4.2 + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + better-sqlite3@12.10.1: dependencies: bindings: 1.5.0 @@ -20951,6 +21288,8 @@ snapshots: character-reference-invalid@2.0.1: {} + chardet@2.2.0: {} + check-error@2.1.3: {} chokidar@3.6.0: @@ -21414,6 +21753,8 @@ snapshots: destroy@1.2.0: {} + detect-indent@6.1.0: {} + detect-libc@1.0.3: {} detect-libc@2.1.2: {} @@ -21436,6 +21777,10 @@ snapshots: minimatch: 3.1.2 p-limit: 3.1.0 + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + dlv@1.1.3: {} dmg-builder@26.15.3(electron-builder-squirrel-windows@26.15.3): @@ -21673,6 +22018,11 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + entities@4.5.0: {} entities@6.0.1: {} @@ -22374,6 +22724,8 @@ snapshots: extend@3.0.2: {} + extendable-error@0.1.7: {} + fast-deep-equal@3.1.3: {} fast-equals@5.4.0: {} @@ -22735,6 +23087,15 @@ snapshots: gopd: 1.2.0 optional: true + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + globrex@0.1.2: {} goober@2.1.19(csstype@3.2.3): @@ -22945,6 +23306,8 @@ snapshots: transitivePeerDependencies: - supports-color + human-id@4.2.0: {} + human-signals@2.1.0: {} human-signals@5.0.0: {} @@ -23135,6 +23498,10 @@ snapshots: is-stream@4.0.1: {} + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + is-typed-array@1.1.15: dependencies: which-typed-array: 1.1.20 @@ -23145,6 +23512,8 @@ snapshots: is-what@5.5.0: {} + is-windows@1.0.2: {} + is-wsl@2.2.0: dependencies: is-docker: 2.2.1 @@ -23694,6 +24063,8 @@ snapshots: lodash.once@4.1.1: {} + lodash.startcase@4.4.0: {} + lodash.throttle@4.1.1: {} lodash@4.17.23: {} @@ -24456,6 +24827,8 @@ snapshots: mprocs@0.7.3: {} + mri@1.2.0: {} + mrmime@2.0.1: {} ms@2.0.0: {} @@ -24821,6 +25194,8 @@ snapshots: orderedmap@2.1.1: {} + outdent@0.5.0: {} + outvariant@1.4.3: {} oxc-parser@0.127.0: @@ -24998,6 +25373,10 @@ snapshots: p-cancelable@2.1.1: {} + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -25014,12 +25393,18 @@ snapshots: dependencies: p-limit: 3.1.0 + p-map@2.1.0: {} + p-map@7.0.4: {} p-try@2.2.0: {} package-json-from-dist@1.0.1: {} + package-manager-detector@0.2.11: + dependencies: + quansync: 0.2.11 + package-manager-detector@1.6.0: {} pako@1.0.11: {} @@ -25141,6 +25526,8 @@ snapshots: pify@2.3.0: {} + pify@4.0.1: {} + pirates@4.0.7: {} pixelmatch@5.3.0: @@ -25546,6 +25933,8 @@ snapshots: dependencies: side-channel: 1.1.0 + quansync@0.2.11: {} + query-selector-shadow-dom@1.0.1: {} query-string@7.1.3: @@ -26004,6 +26393,13 @@ snapshots: dependencies: pify: 2.3.0 + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.2 + pify: 4.0.1 + strip-bom: 3.0.0 + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -26676,6 +27072,11 @@ snapshots: space-separated-tokens@2.0.2: {} + spawndamnit@3.0.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + split-on-first@1.1.0: {} sprintf-js@1.0.3: {} @@ -26977,6 +27378,8 @@ snapshots: mkdirp: 0.5.6 rimraf: 2.6.3 + term-size@2.2.1: {} + terminal-link@2.1.1: dependencies: ansi-escapes: 4.3.2 @@ -27661,36 +28064,6 @@ snapshots: transitivePeerDependencies: - msw - vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(@vitest/ui@4.1.8)(jsdom@26.1.0)(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.8 - '@vitest/runner': 4.1.8 - '@vitest/snapshot': 4.1.8 - '@vitest/spy': 4.1.8 - '@vitest/utils': 4.1.8 - es-module-lexer: 2.0.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.1.0 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@types/node': 24.12.0 - '@vitest/ui': 4.1.8(vitest@4.1.8) - jsdom: 26.1.0 - transitivePeerDependencies: - - msw - vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.2.0)(@vitest/ui@4.1.8)(jsdom@26.1.0)(msw@2.12.8(@types/node@25.2.0)(typescript@5.9.3))(vite@7.3.5(@types/node@25.2.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.8 From 10d5dc493cd93162344bbd884647b60cb4e1b092 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 5 Jul 2026 12:50:27 -0700 Subject: [PATCH 3/6] gate agent npm publish behind human approval --- .github/CODEOWNERS | 7 + .github/pull_request_template.md | 1 + .github/workflows/agent-release.yml | 458 +++++++++++++++++++-- .github/workflows/agent-tag.yml | 68 --- .github/workflows/code-discord-release.yml | 2 + AGENTS.md | 2 + packages/agent/RELEASING.md | 99 +++++ packages/agent/package.json | 2 +- 8 files changed, 546 insertions(+), 93 deletions(-) create mode 100644 .github/CODEOWNERS delete mode 100644 .github/workflows/agent-tag.yml create mode 100644 packages/agent/RELEASING.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..655dfd99dd --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# Release-critical files: edits here can reshape or bypass the npm publish +# approval gate, so they require review by the release owners. +# TODO(ops): replace @PostHog/team-array with the team that owns agent releases. +/.github/CODEOWNERS @PostHog/team-array +/.github/workflows/agent-release.yml @PostHog/team-array +/.changeset/config.json @PostHog/team-array +/packages/agent/package.json @PostHog/team-array diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index fde54cfafc..f9cb00c46f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -18,3 +18,4 @@ - [ ] Publish to changelog? - [ ] Alert Sales and Marketing teams? +- [ ] Added a changeset? (required to ship `packages/agent`, `shared`, `git` or `enricher` changes to npm; run `pnpm changeset`) diff --git a/.github/workflows/agent-release.yml b/.github/workflows/agent-release.yml index 7437c879ba..5260970182 100644 --- a/.github/workflows/agent-release.yml +++ b/.github/workflows/agent-release.yml @@ -1,25 +1,141 @@ name: Release Agent +# Hardened release pipeline for @posthog/agent, modeled on +# https://posthog.com/handbook/engineering/sdks/releases and posthog-php's release.yml. +# A merged changeset produces a verified release candidate that a human must approve +# via the protected "Release" environment before anything is committed, tagged or +# published to npm. See packages/agent/RELEASING.md. + on: push: - tags: - - "agent-v*" + branches: + - main + paths: + - ".changeset/*.md" + workflow_dispatch: + +permissions: + contents: read concurrency: group: agent-release cancel-in-progress: false jobs: - release: - name: Publish agent to npm + check-changesets: + name: Check for changesets runs-on: ubuntu-latest permissions: contents: read - id-token: write + outputs: + has-changesets: ${{ steps.check.outputs.has-changesets }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.sha }} + persist-credentials: false + + - name: Check for changesets + id: check + run: | + changeset_count=$(find .changeset -name '*.md' ! -name 'README.md' 2>/dev/null | wc -l) + if [ "$changeset_count" -gt 0 ]; then + echo "has-changesets=true" >> "$GITHUB_OUTPUT" + echo "Found $changeset_count changeset(s), ready to release" + else + echo "has-changesets=false" >> "$GITHUB_OUTPUT" + echo "No changesets to release" + fi + + prepare-release-candidate: + name: Prepare release candidate + needs: check-changesets + if: needs.check-changesets.outputs.has-changesets == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + new-version: ${{ steps.candidate.outputs.new-version }} + patch-sha256: ${{ steps.candidate.outputs.patch-sha256 }} + source-sha: ${{ steps.candidate.outputs.source-sha }} steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Setup pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + + - name: Set up Node 24 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 + cache: "pnpm" + + - name: Install release tooling dependencies + run: pnpm install --frozen-lockfile --ignore-scripts + + - name: Apply changesets and compute release candidate + id: candidate + run: | + set -euo pipefail + + source_sha=$(git rev-parse HEAD) + pnpm exec changeset version + new_version=$(jq -r '.version' packages/agent/package.json) + + git add -A + if git diff --cached --quiet; then + echo "No release candidate changes were generated" >&2 + exit 1 + fi + + changed_files=$(git diff --cached --name-only) + while IFS= read -r file; do + case "$file" in + packages/agent/package.json|packages/agent/CHANGELOG.md|.changeset/*.md) ;; + *) + echo "Unexpected release candidate change: $file" >&2 + exit 1 + ;; + esac + done <<< "$changed_files" + + git diff --cached --binary > release.patch + patch_sha256=$(sha256sum release.patch | awk '{print $1}') + + { + echo "source-sha=$source_sha" + echo "new-version=$new_version" + echo "patch-sha256=$patch_sha256" + } >> "$GITHUB_OUTPUT" + echo "Release candidate $new_version prepared from $source_sha with patch sha256 $patch_sha256" + + - name: Upload release candidate patch + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: release-candidate-${{ steps.candidate.outputs.source-sha }} + path: release.patch + if-no-files-found: error + retention-days: 14 + + verify-release-candidate: + name: Verify release candidate + needs: [check-changesets, prepare-release-candidate] + if: needs.check-changesets.outputs.has-changesets == 'true' + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + steps: + - name: Checkout source revision + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.prepare-release-candidate.outputs.source-sha }} persist-credentials: false - name: Setup pnpm @@ -31,40 +147,334 @@ jobs: node-version: 24 cache: "pnpm" - - name: Extract version from tag - id: version + - name: Download release candidate patch + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: release-candidate-${{ needs.prepare-release-candidate.outputs.source-sha }} + path: release-candidate + + - name: Apply and validate release candidate patch + env: + EXPECTED_PATCH_SHA256: ${{ needs.prepare-release-candidate.outputs.patch-sha256 }} + EXPECTED_VERSION: ${{ needs.prepare-release-candidate.outputs.new-version }} run: | - TAG_VERSION="${GITHUB_REF#refs/tags/agent-v}" - echo "Version: $TAG_VERSION" - echo "version=$TAG_VERSION" >> "$GITHUB_OUTPUT" + set -euo pipefail - - name: Set version in package.json + actual_patch_sha256=$(sha256sum release-candidate/release.patch | awk '{print $1}') + if [ "$actual_patch_sha256" != "$EXPECTED_PATCH_SHA256" ]; then + echo "Release candidate patch sha256 mismatch: expected $EXPECTED_PATCH_SHA256, got $actual_patch_sha256" >&2 + exit 1 + fi + + git apply --index release-candidate/release.patch + + package_version=$(jq -r '.version' packages/agent/package.json) + if [ "$EXPECTED_VERSION" != "$package_version" ]; then + echo "Version mismatch: expected=$EXPECTED_VERSION packages/agent/package.json=$package_version" >&2 + exit 1 + fi + + rm -rf release-candidate + + - name: Check tag and release do not already exist env: - APP_VERSION: ${{ steps.version.outputs.version }} + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: agent-v${{ needs.prepare-release-candidate.outputs.new-version }} + REPOSITORY: ${{ github.repository }} run: | - jq --arg v "$APP_VERSION" '.version = $v' packages/agent/package.json > tmp.json && mv tmp.json packages/agent/package.json - echo "Set packages/agent/package.json version to $APP_VERSION" + set -euo pipefail + + check_missing() { + local description="$1" + shift + local output + local status + + if output=$("$@" 2>&1); then + echo "$description already exists for $RELEASE_TAG" >&2 + exit 1 + else + status=$? + fi + + if grep -q "HTTP 404" <<< "$output" || grep -q "Not Found" <<< "$output"; then + return 0 + fi + + echo "Could not check whether $description exists: $output" >&2 + exit "$status" + } + + check_missing "GitHub release" gh api "repos/$REPOSITORY/releases/tags/$RELEASE_TAG" + check_missing "git tag" gh api "repos/$REPOSITORY/git/ref/tags/$RELEASE_TAG" - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build shared (agent dependency) - run: pnpm --filter @posthog/shared run build + - name: Build and test the release candidate + env: + AGENT_RELEASE_BUILD: "1" + run: | + pnpm --filter @posthog/shared run build + pnpm --filter @posthog/git run build + pnpm --filter @posthog/enricher run build + pnpm --filter agent run build + pnpm --filter agent run test - - name: Build git (agent dependency) - run: pnpm --filter @posthog/git run build + notify-approval-needed: + name: Notify Slack - Approval Needed + needs: [check-changesets, prepare-release-candidate, verify-release-candidate] + if: needs.check-changesets.outputs.has-changesets == 'true' && needs.prepare-release-candidate.result == 'success' && needs.verify-release-candidate.result == 'success' + uses: posthog/.github/.github/workflows/notify-approval-needed.yml@cb0979b67dcd585828b61ac45927eef4da8f6287 # main + with: + slack_channel_id: ${{ vars.SLACK_APPROVALS_CODE_CHANNEL_ID }} + slack_user_group_id: ${{ vars.GROUP_CODE_SLACK_GROUP_ID }} + # This repo has two tag namespaces (v* desktop, agent-v* npm). The compare + # link must resolve against the latest agent-v* tag, which requires the + # non-checkout code path of the reusable workflow. + checkout_repository: false + tag_pattern: "agent-v*" + tag_sort: "version" + secrets: + slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }} + posthog_project_api_key: ${{ secrets.POSTHOG_PROJECT_API_KEY }} - - name: Build enricher (agent dependency) - run: pnpm --filter @posthog/enricher run build + release: + name: Publish release + needs: + [ + check-changesets, + prepare-release-candidate, + verify-release-candidate, + notify-approval-needed, + ] + runs-on: ubuntu-latest + # Slack notification failure (e.g. secrets not provisioned yet) must not block the + # release: the security gate is the environment approval, not the Slack ping. + if: always() && needs.check-changesets.outputs.has-changesets == 'true' && needs.verify-release-candidate.result == 'success' && (needs.notify-approval-needed.result == 'success' || needs.notify-approval-needed.result == 'failure') + environment: Release + permissions: + actions: read + contents: write + id-token: write + env: + AGENT_RELEASE_BUILD: "1" + steps: + - name: Notify Slack - Approved + if: needs.notify-approval-needed.outputs.slack_ts != '' + continue-on-error: true + uses: posthog/.github/.github/actions/slack-thread-reply@cb0979b67dcd585828b61ac45927eef4da8f6287 # main + with: + slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }} + slack_channel_id: ${{ vars.SLACK_APPROVALS_CODE_CHANNEL_ID }} + thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }} + message: "✅ Release approved! Publishing @posthog/agent@${{ needs.prepare-release-candidate.outputs.new-version }}..." + emoji_reaction: "white_check_mark" + + - name: Checkout approved source revision + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.prepare-release-candidate.outputs.source-sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Setup pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + + - name: Set up Node 24 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 + cache: "pnpm" - - name: Build the package - run: pnpm --filter agent run build + - name: Download verified release candidate patch + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: release-candidate-${{ needs.prepare-release-candidate.outputs.source-sha }} + path: release-candidate + + - name: Apply verified release candidate patch + id: guard + env: + GH_TOKEN: ${{ github.token }} + EXPECTED_PATCH_SHA256: ${{ needs.prepare-release-candidate.outputs.patch-sha256 }} + EXPECTED_SOURCE_SHA: ${{ needs.prepare-release-candidate.outputs.source-sha }} + EXPECTED_VERSION: ${{ needs.prepare-release-candidate.outputs.new-version }} + RELEASE_TAG: agent-v${{ needs.prepare-release-candidate.outputs.new-version }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + + actual_patch_sha256=$(sha256sum release-candidate/release.patch | awk '{print $1}') + if [ "$actual_patch_sha256" != "$EXPECTED_PATCH_SHA256" ]; then + echo "Release candidate patch sha256 mismatch: expected $EXPECTED_PATCH_SHA256, got $actual_patch_sha256" >&2 + exit 1 + fi + + # A pre-existing tag means a prior approved attempt already committed the + # version bump (and possibly failed later, e.g. at npm publish). Resume by + # rebuilding the same content instead of failing on "main moved". + if gh api "repos/$REPOSITORY/git/ref/tags/$RELEASE_TAG" --jq '.object.sha' > /dev/null 2>&1; then + echo "Tag $RELEASE_TAG already exists; resuming a previously approved release" + echo "resume=true" >> "$GITHUB_OUTPUT" + else + current_main_sha=$(gh api "repos/$REPOSITORY/git/ref/heads/main" --jq '.object.sha') + if [ "$current_main_sha" != "$EXPECTED_SOURCE_SHA" ]; then + echo "main moved since the release candidate was verified: expected $EXPECTED_SOURCE_SHA, got $current_main_sha. Re-run the workflow so a fresh candidate is prepared and verified." >&2 + exit 1 + fi + echo "resume=false" >> "$GITHUB_OUTPUT" + fi + + git apply --index release-candidate/release.patch + + package_version=$(jq -r '.version' packages/agent/package.json) + if [ "$EXPECTED_VERSION" != "$package_version" ]; then + echo "Version mismatch: expected=$EXPECTED_VERSION packages/agent/package.json=$package_version" >&2 + exit 1 + fi - - name: Run tests - run: pnpm --filter agent run test + rm -rf release-candidate + + - name: Get GitHub App token + id: releaser + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ secrets.GH_APP_POSTHOG_AGENT_RELEASER_APP_ID }} + private-key: ${{ secrets.GH_APP_POSTHOG_AGENT_RELEASER_PRIVATE_KEY }} + + - name: Commit version bump + id: commit-version-bump + if: steps.guard.outputs.resume != 'true' + uses: planetscale/ghcommit-action@a6b150b81dca5dd027baa898604418eec9e11465 # v0.2.22 + with: + commit_message: "chore(agent): release agent-v${{ needs.prepare-release-candidate.outputs.new-version }} [version bump] [skip ci]" + repo: ${{ github.repository }} + branch: main + file_pattern: "packages/agent/package.json packages/agent/CHANGELOG.md .changeset" + env: + GITHUB_TOKEN: ${{ steps.releaser.outputs.token }} + + - name: Create release tag + if: steps.guard.outputs.resume != 'true' && steps.commit-version-bump.outputs.commit-hash != '' + env: + GH_TOKEN: ${{ steps.releaser.outputs.token }} + RELEASE_TAG: agent-v${{ needs.prepare-release-candidate.outputs.new-version }} + COMMIT_SHA: ${{ steps.commit-version-bump.outputs.commit-hash }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + gh api "repos/$REPOSITORY/git/refs" -f ref="refs/tags/$RELEASE_TAG" -f sha="$COMMIT_SHA" + echo "Created tag $RELEASE_TAG at $COMMIT_SHA" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build and test the approved release + run: | + pnpm --filter @posthog/shared run build + pnpm --filter @posthog/git run build + pnpm --filter @posthog/enricher run build + pnpm --filter agent run build + pnpm --filter agent run test + + - name: Check whether version is already on npm + id: npm-check + env: + NEW_VERSION: ${{ needs.prepare-release-candidate.outputs.new-version }} + run: | + set -euo pipefail + already_published=$(npm view "@posthog/agent" versions --json | jq --arg v "$NEW_VERSION" 'index($v) != null') + echo "already-published=$already_published" >> "$GITHUB_OUTPUT" + echo "@posthog/agent@$NEW_VERSION already published: $already_published" - name: Publish the package to npm registry + if: steps.npm-check.outputs.already-published != 'true' working-directory: packages/agent run: pnpm publish --access public --no-git-checks env: NPM_CONFIG_PROVENANCE: true + + - name: Create GitHub release + env: + GH_TOKEN: ${{ steps.releaser.outputs.token }} + RELEASE_TAG: agent-v${{ needs.prepare-release-candidate.outputs.new-version }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + + if gh api "repos/$REPOSITORY/releases/tags/$RELEASE_TAG" > /dev/null 2>&1; then + echo "GitHub release for $RELEASE_TAG already exists" + exit 0 + fi + + CHANGELOG_ENTRY=$(awk -v defText="See packages/agent/CHANGELOG.md" '/^## /{if (flag) exit; flag=1} flag; END{if (!flag) print defText}' packages/agent/CHANGELOG.md) + gh release create "$RELEASE_TAG" --title "$RELEASE_TAG" --notes "$CHANGELOG_ENTRY" + + notify-released: + name: Notify Slack - Released + needs: [prepare-release-candidate, notify-approval-needed, release] + runs-on: ubuntu-latest + if: always() && needs.release.result == 'success' && needs.notify-approval-needed.outputs.slack_ts != '' + steps: + - name: Notify Slack - Released + uses: posthog/.github/.github/actions/slack-thread-reply@cb0979b67dcd585828b61ac45927eef4da8f6287 # main + with: + slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }} + slack_channel_id: ${{ vars.SLACK_APPROVALS_CODE_CHANNEL_ID }} + thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }} + message: "🚀 @posthog/agent@${{ needs.prepare-release-candidate.outputs.new-version }} released successfully!" + emoji_reaction: "rocket" + + notify-release-failed: + name: Notify Slack - Release Failed + needs: [prepare-release-candidate, notify-approval-needed, release] + runs-on: ubuntu-latest + if: always() && (needs.release.result == 'failure' || needs.release.result == 'cancelled') && needs.notify-approval-needed.outputs.slack_ts != '' + permissions: + actions: read + contents: read + steps: + - name: Check whether release was rejected + id: check-failure + env: + GH_TOKEN: ${{ github.token }} + run: | + RESPONSE=$(gh api /repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/approvals) + REJECTED=$(echo "$RESPONSE" | jq '[.[] | select(.state == "rejected")] | length') + if [ "$REJECTED" -gt 0 ]; then + echo "was_rejected=true" >> "$GITHUB_OUTPUT" + COMMENT=$(echo "$RESPONSE" | jq -r '.[] | select(.state == "rejected") | .comment // empty' | head -1) + if [ -n "$COMMENT" ]; then + { + echo 'message<> "$GITHUB_OUTPUT" + else + echo "message=🚫 Release was rejected." >> "$GITHUB_OUTPUT" + fi + else + echo "was_rejected=false" >> "$GITHUB_OUTPUT" + fi + + - name: Notify Slack - Failed + if: steps.check-failure.outputs.was_rejected != 'true' + uses: posthog/.github/.github/actions/slack-thread-reply@cb0979b67dcd585828b61ac45927eef4da8f6287 # main + with: + slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }} + slack_channel_id: ${{ vars.SLACK_APPROVALS_CODE_CHANNEL_ID }} + thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }} + message: "❌ Release `@posthog/agent@${{ needs.prepare-release-candidate.outputs.new-version }}` failed or was cancelled! " + emoji_reaction: "x" + + - name: Notify Slack - Rejected + if: steps.check-failure.outputs.was_rejected == 'true' + continue-on-error: true + uses: posthog/.github/.github/actions/slack-thread-reply@cb0979b67dcd585828b61ac45927eef4da8f6287 # main + with: + slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }} + slack_channel_id: ${{ vars.SLACK_APPROVALS_CODE_CHANNEL_ID }} + thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }} + message: "${{ steps.check-failure.outputs.message }}" + emoji_reaction: "no_entry_sign" diff --git a/.github/workflows/agent-tag.yml b/.github/workflows/agent-tag.yml deleted file mode 100644 index 1cacd3cc05..0000000000 --- a/.github/workflows/agent-tag.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: Tag Agent Release - -on: - push: - branches: - - main - paths: - - "packages/agent/**" - - ".github/workflows/agent-tag.yml" - - ".github/workflows/agent-release.yml" - -concurrency: - group: agent-tag - cancel-in-progress: false - -jobs: - tag: - permissions: - contents: write - runs-on: ubuntu-latest - steps: - - name: Get app token - id: app-token - uses: getsentry/action-github-app-token@5c1e90706fe007857338ac1bfbd7a4177db2f789 # v4.0.0 - with: - app_id: ${{ secrets.GH_APP_ARRAY_RELEASER_APP_ID }} - private_key: ${{ secrets.GH_APP_ARRAY_RELEASER_PRIVATE_KEY }} - - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - token: ${{ steps.app-token.outputs.token }} - - - name: Compute version and create tag - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - REPOSITORY: ${{ github.repository }} - run: | - # Find the latest agent base version tag (agent-vX.Y or agent-vX.Y.0 format) - LATEST_TAG=$(git tag --list 'agent-v[0-9]*.[0-9]*' --sort=-v:refname | grep -E '^agent-v[0-9]+\.[0-9]+(\.0)?$' | head -1) - - if [ -z "$LATEST_TAG" ]; then - echo "No agent version tag found. Create one with: git tag agent-v2.1 && git push origin agent-v2.1" - exit 1 - fi - - # Extract major.minor from tag - VERSION_PART=${LATEST_TAG#agent-v} - MAJOR=$(echo "$VERSION_PART" | cut -d. -f1) - MINOR=$(echo "$VERSION_PART" | cut -d. -f2) - - # Count commits since the base tag - PATCH=$(git rev-list "$LATEST_TAG"..HEAD --count) - - if [ "$PATCH" -eq 0 ]; then - echo "No commits since $LATEST_TAG. Nothing to release." - exit 0 - fi - - NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" - TAG="agent-v$NEW_VERSION" - echo "Creating tag: $TAG (from base tag $LATEST_TAG + $PATCH commits)" - - git config user.email "github-actions[bot]@users.noreply.github.com" - git config user.name "github-actions[bot]" - git tag -a "$TAG" -m "Agent release $TAG" - git push "https://x-access-token:${GH_TOKEN}@github.com/$REPOSITORY" "$TAG" diff --git a/.github/workflows/code-discord-release.yml b/.github/workflows/code-discord-release.yml index 7b078597ec..0d7ec66655 100644 --- a/.github/workflows/code-discord-release.yml +++ b/.github/workflows/code-discord-release.yml @@ -9,6 +9,8 @@ permissions: {} jobs: notify: + # Desktop releases only; agent-v* npm releases are announced via Slack instead + if: ${{ !startsWith(github.event.release.tag_name, 'agent-v') }} runs-on: ubuntu-latest steps: - uses: SethCohen/github-releases-to-discord@37afa88c8c9302a9307244b5a0d4e782d528a4b5 # v1.15.1 diff --git a/AGENTS.md b/AGENTS.md index c0343c6fa9..9769b13d3e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -198,6 +198,7 @@ await boot(container); - `pnpm test:e2e`: run Playwright tests. - `pnpm --filter typecheck|test|build`: run a scoped task. - `pnpm --filter code package|make`: package the Electron app. +- `pnpm changeset`: request an npm release of `@posthog/agent` (see [packages/agent/RELEASING.md](./packages/agent/RELEASING.md)). - `node scripts/check-host-boundaries.mjs`: verify host boundary allowlist. ## Code Style @@ -251,3 +252,4 @@ See [docs/testing.md](./docs/testing.md). - [docs/LOCAL-DEVELOPMENT.md](./docs/LOCAL-DEVELOPMENT.md) - [docs/UPDATES.md](./docs/UPDATES.md) - [docs/TROUBLESHOOTING.md](./docs/TROUBLESHOOTING.md) +- [packages/agent/RELEASING.md](./packages/agent/RELEASING.md) diff --git a/packages/agent/RELEASING.md b/packages/agent/RELEASING.md new file mode 100644 index 0000000000..77d6d20020 --- /dev/null +++ b/packages/agent/RELEASING.md @@ -0,0 +1,99 @@ +# Releasing @posthog/agent + +`@posthog/agent` is the only npm-published package in this repo. It ships to +PostHog cloud sandboxes, so releases are intentional and human-approved, +following the [PostHog SDK release process](https://posthog.com/handbook/engineering/sdks/releases). +There is no auto-publish on merge. + +## When to add a changeset + +Add a changeset in any PR whose changes should ship to npm: + +- Changes under `packages/agent`. +- Changes under `packages/shared`, `packages/git` or `packages/enricher` that + affect the agent. These packages never publish on their own; tsup bundles + them into `@posthog/agent`, so they only reach npm through an agent release. + +Always target `@posthog/agent` in the changeset. The other packages are +private and cannot be versioned. + +```bash +pnpm changeset +``` + +Pick a bump (patch/minor/major) and write a summary; it becomes the +`packages/agent/CHANGELOG.md` entry and the GitHub release notes. PRs without +a changeset merge normally and release nothing. + +## What happens after merge + +Merging a changeset to `main` triggers `.github/workflows/agent-release.yml`: + +1. `prepare-release-candidate` runs `changeset version`, asserts that only + `packages/agent/package.json`, `packages/agent/CHANGELOG.md` and consumed + `.changeset/*.md` files changed, and uploads the diff as a sha256-pinned + patch artifact. +2. `verify-release-candidate` re-applies the patch on a clean checkout, checks + the version is consistent and not already tagged, released or published, + then builds and tests the exact candidate. +3. Slack pings the approvals channel with a compare link against the previous + `agent-v*` tag. +4. The `release` job waits for approval on the protected `Release` + environment. Reviewers approve or reject from the workflow run page. +5. After approval, the job re-verifies the patch hash and that `main` has not + moved, commits the version bump via the release GitHub App (signed API + commit, `[skip ci]`), creates the `agent-v` tag, rebuilds, tests, + publishes to npm with OIDC provenance and creates the GitHub release. + +Several changesets merged before the run starts batch into one release. Runs +queue behind each other (`concurrency: agent-release`); each queued release +needs its own approval. + +## Approving or rejecting + +Required reviewers are configured on the `Release` environment (repo Settings +-> Environments). Review the Slack compare link and the green verify build, +then approve or reject on the run's review prompt. A rejection blocks the +release job before it starts: nothing is committed, tagged or published, and +the Slack thread is updated with the rejection comment. + +## Failures and re-runs + +The release job is idempotent; every side effect is preceded by an existence +check: + +- Publish failed after the version bump commit landed: `main` says version X, + npm does not have it. Re-run the release job (or dispatch the workflow). It + detects the existing `agent-v` tag, skips commit and tag creation + and retries build, test, publish and the GitHub release. +- "main moved since the release candidate was verified": an unrelated commit + landed between verification and approval. Re-run the whole workflow + (`workflow_dispatch` on the Actions page) so a fresh candidate is prepared + against the new `main`. +- Already-published versions are never re-published; the publish step is + skipped when npm already has the version. + +Version reporting: source builds (dev, desktop, vitest) report the sentinel +`0.0.0-dev`, which the UI treats as "latest, satisfies any feature gate" +(`packages/ui/src/utils/agentVersion.ts`). Only the release workflow builds +with `AGENT_RELEASE_BUILD=1`, which makes tsup inject the real version from +`packages/agent/package.json` (see `packages/agent/src/version.ts`). + +## Operational setup + +The pipeline depends on GitHub and npm settings that live outside this repo's +code. If any of these drift, releases fail closed: + +- `Release` environment: required reviewers, prevent self-review on, admin + bypass off, deployments restricted to `main`. +- GitHub App (`GH_APP_POSTHOG_AGENT_RELEASER_*` secrets, scoped to the + `Release` environment): Contents read/write on this repo only. Bypasses the + `agent-v*` tag ruleset and CodeQL/PR rulesets. +- Tag ruleset for `agent-v*`: create, update and delete restricted to the App. +- npm trusted publisher for `@posthog/agent`: org `PostHog` (case-sensitive), + workflow `agent-release.yml`, environment `Release` (exact case). No npm + token exists; publishing uses OIDC with provenance. +- Slack: org secret `SLACK_CLIENT_LIBRARIES_BOT_TOKEN` and + `POSTHOG_PROJECT_API_KEY` granted to this repo, plus repo vars + `SLACK_APPROVALS_CODE_CHANNEL_ID` and `GROUP_CODE_SLACK_GROUP_ID`. Slack + failures never block a release; the environment approval is the gate. diff --git a/packages/agent/package.json b/packages/agent/package.json index f72b4cb5e9..21d624518b 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@posthog/agent", - "version": "0.0.0-dev", + "version": "2.3.1250", "repository": "https://github.com/PostHog/code", "description": "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog", "exports": { From e7a5b76458015402d9a5bc9642a8bf790f1f4bc9 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 5 Jul 2026 12:50:28 -0700 Subject: [PATCH 4/6] harden desktop release publish workflow --- .github/workflows/code-release.yml | 6 ++++++ .github/workflows/code-tag.yml | 3 +++ docs/UPDATES.md | 12 ++++++++++++ 3 files changed, 21 insertions(+) diff --git a/.github/workflows/code-release.yml b/.github/workflows/code-release.yml index 990e9bc766..be812030dd 100644 --- a/.github/workflows/code-release.yml +++ b/.github/workflows/code-release.yml @@ -494,6 +494,11 @@ jobs: # the default `needs` gate skips this job (leaving the GitHub release a draft) if # any of the macOS, Windows or Linux publish jobs fails. runs-on: ubuntu-latest + # Flipping the draft public is the irreversible step (auto-updaters pick it up), + # so it runs in a protected environment. The gate activates once required + # reviewers are configured on "Desktop Release" in repo settings; the + # environment's tag policy must allow v* tags since this workflow runs on tags. + environment: Desktop Release permissions: contents: write steps: @@ -517,6 +522,7 @@ jobs: apps/code/scripts/merge-mac-manifests.mjs apps/code/scripts/generate-release-download-tables.mjs sparse-checkout-cone-mode: false + persist-credentials: false - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 diff --git a/.github/workflows/code-tag.yml b/.github/workflows/code-tag.yml index 9339939b6a..f1713aba94 100644 --- a/.github/workflows/code-tag.yml +++ b/.github/workflows/code-tag.yml @@ -36,11 +36,14 @@ jobs: app_id: ${{ secrets.GH_APP_ARRAY_RELEASER_APP_ID }} private_key: ${{ secrets.GH_APP_ARRAY_RELEASER_PRIVATE_KEY }} + # The tag push below authenticates via an explicit token URL, so the checkout + # credential does not need to persist in .git/config for the rest of the job. - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} + persist-credentials: false - name: Check quiet period id: quiet diff --git a/docs/UPDATES.md b/docs/UPDATES.md index 969fff5ade..95b51b26c0 100644 --- a/docs/UPDATES.md +++ b/docs/UPDATES.md @@ -29,6 +29,18 @@ electron-builder generates and uploads a `latest-mac.yml` (macOS) or `latest.yml 2. Each push to `main` triggers a release with version `0.15.N` where N = commits since `v0.15.0` 3. No manual `package.json` updates needed for patch releases +## Release Approval + +Builds and asset uploads run automatically into a draft release, which +auto-updaters never see. The `finalize-release` job, which flips the draft +public, runs in the `Desktop Release` GitHub environment. Once required +reviewers are configured on that environment, publishing waits for a human +approval on the workflow run; with no reviewers configured it behaves as +before. Approve within 24 hours: the nightly draft cleanup deletes drafts +older than a day, after which the run must be re-triggered by re-pushing the +tag or dispatching the workflow. The npm release process for `@posthog/agent` +is separate, see [packages/agent/RELEASING.md](../packages/agent/RELEASING.md). + ## Releasing a Patch (Automatic) Just push to `main`. The workflow computes the version automatically: From 18f552ff7564b1b918a89e35a31b745c75968dfc Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 5 Jul 2026 19:00:56 -0700 Subject: [PATCH 5/6] set real team in CODEOWNERS --- .github/CODEOWNERS | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 655dfd99dd..85fc323e74 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,7 +1,6 @@ # Release-critical files: edits here can reshape or bypass the npm publish # approval gate, so they require review by the release owners. -# TODO(ops): replace @PostHog/team-array with the team that owns agent releases. -/.github/CODEOWNERS @PostHog/team-array -/.github/workflows/agent-release.yml @PostHog/team-array -/.changeset/config.json @PostHog/team-array -/packages/agent/package.json @PostHog/team-array +/.github/CODEOWNERS @PostHog/team-posthog-code +/.github/workflows/agent-release.yml @PostHog/team-posthog-code +/.changeset/config.json @PostHog/team-posthog-code +/packages/agent/package.json @PostHog/team-posthog-code From 9362a78371c21ede11cff03849d092ee81996209 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 5 Jul 2026 19:01:00 -0700 Subject: [PATCH 6/6] use random heredoc delimiter for rejections --- .github/workflows/agent-release.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/agent-release.yml b/.github/workflows/agent-release.yml index 5260970182..ebeb38e2ac 100644 --- a/.github/workflows/agent-release.yml +++ b/.github/workflows/agent-release.yml @@ -444,12 +444,15 @@ jobs: REJECTED=$(echo "$RESPONSE" | jq '[.[] | select(.state == "rejected")] | length') if [ "$REJECTED" -gt 0 ]; then echo "was_rejected=true" >> "$GITHUB_OUTPUT" - COMMENT=$(echo "$RESPONSE" | jq -r '.[] | select(.state == "rejected") | .comment // empty' | head -1) + COMMENT=$(echo "$RESPONSE" | jq -r '[.[] | select(.state == "rejected") | .comment // empty] | first // empty') if [ -n "$COMMENT" ]; then + # Random delimiter so reviewer-controlled comment text can never + # terminate the heredoc early and truncate the output. + delimiter="msg_$(openssl rand -hex 16)" { - echo 'message<> "$GITHUB_OUTPUT" else echo "message=🚫 Release was rejected." >> "$GITHUB_OUTPUT"