Dokumentation
Auf dieser Seite
  1. Agent Quick Context
  2. Version Policy
  3. One-Time Setup
  4. Automated Daily Driver
  5. Independent morning dispatch
  6. Hosted release policy
  7. Changelog Classification (Contributor-Facing)
  8. Local Release Parity Checklist
  9. Reviewed Delivery Closeout
  10. GitHub Workflow
  11. Verified Coverage Uploads
  12. Post-Release Verification
  13. Failure Handling
  14. Silent skip debugging

Releasing @unbrained/pm-cli

This page is for maintainers cutting npm and GitHub releases. It assumes release work is tracked with pm. For local progressive-disclosure routing, install guide-shell with pm install guide-shell --project, then use pm guide release.

Agent Quick Context

  • Release versioning is calendar SemVer-compatible: one production version (YYYY.M.D) per UTC day. Older history can contain YYYY.M.D-N ordinals, but release preparation no longer creates them.
  • Daily release preparation is owned by the GitHub Actions auto-release workflow.
  • Publishing is owned by the tag-driven GitHub Actions release workflow.
  • Do not run manual npm publish.
  • Run local parity gates before pushing release tags.
  • Treat pnpm sdk:surface:check as a release compatibility gate; never regenerate an unexplained public API diff.
  • Use pm guide release --json for machine-readable release docs routing after guide-shell is installed.

Tracked documentation work: pm-u9d0, pm-4s24d2, pm-39cqqx, stable peer compatibility pm-csuce0, and artifact budgets pm-998juj, plus exact-tag recovery pm-lwnifd, and SDK-bound reliability classification pm-dqtzva. The local/hosted gate selection contract is tracked by pm-ei6x66. Release-candidate analyzer provenance is tracked by pm-u1baah, and authoritative blocker-recovery run selection by pm-db8onn, and queued automatic same-day recovery by pm-dm2vfz. Bounded Sentry request latency is tracked by pm-b9g2cs, and exact-tag changelog stabilization by pm-e63v1x. Dual-phase package sizing around Sentry injection is tracked by pm-llbnua.

Version Policy

Examples:

  • first release on 2026-05-01: 2026.5.1
  • the next production release after 2026.5.1 is the next UTC day's calendar version; do not create 2026.5.1-2

Inspect the next SemVer-compatible calendar version for diagnostics:

pnpm version:next

This diagnostic preserves validation compatibility with historical ordinal tags, but never proposes one. When today's stable release already exists it fails with immutable-tag recovery guidance, because an ordinal would be a SemVer prerelease excluded from ordinary stable package peer ranges. An explicit pipeline --version must equal the current UTC calendar date; past, future, malformed, and ordinal targets fail before Git inspection.

Validate the current package version:

pnpm version:check

One-Time Setup

  • Use npm trusted publishing for .github/workflows/release.yml so GitHub-hosted release jobs exchange their OIDC identity for a short-lived npm publish credential and publish signed packages. Keep id-token: write, the release environment, Node 24 or newer, npm 11.5.1 or newer, and npm publish --access public --provenance. The npm Trusted Publisher must match repository unbraind/pm-cli, workflow release.yml, and environment release. The release job performs a tokenless npm publish --dry-run OIDC exchange before the long release gates and fails early with npm's sanitized exchange diagnostic when the registry refuses the configured identity. Do not pass registry-url to actions/setup-node: its generated token-placeholder npmrc can suppress npm's OIDC exchange when no token is present. Keep @unbrained/pm-cli public; trusted publishing authorizes npm publish, not npm access, so restore package visibility manually before retrying if an anonymous metadata probe fails. Do not expose NODE_AUTH_TOKEN or NPM_TOKEN to the publish step; if private dependencies ever require a read token, scope it only to the install step.
  • Add RELEASE_PAT to the release environment from a maintainer token with contents:write and branch-protection bypass rights. Auto Release creates a checked version/changelog commit and tag on main; the default GITHUB_TOKEN has contents:write but cannot satisfy protected-branch required status checks for that freshly-created commit. The workflow does not persist this elevated token during checkout or dependency installation; run-release-pipeline.mjs scopes it to the git push process.
  • Add SENTRY_AUTH_TOKEN as an optional GitHub Environment or repository secret when Sentry release creation and sourcemap upload should run. Add SENTRY_PERSONAL_ADMIN_TOKEN for the GitHub-hosted Sentry issue-threshold gate; CI-scoped release tokens may not have issue-read scope. The release workflow skips Sentry upload cleanly when SENTRY_AUTH_TOKEN is absent, but fails the reliability threshold gate when SENTRY_PERSONAL_ADMIN_TOKEN is absent; local maintainers should still run the token-backed Sentry gate before release.
  • Keep any release environment compatible with free GitHub features. This repository is public, so environment secrets and tag/branch deployment rules are compatible with the free GitHub path; do not add paid-only release gates.
  • Ensure GITHUB_TOKEN has contents: write for GitHub Release creation.
  • Keep package.json repository, homepage, and bugs URLs aligned with https://github.com/unbraind/pm-cli.
  • Keep npm publishing compatible with provenance. The release workflow must keep id-token: write, a GitHub-hosted runner, Node 24 or newer, npm 11.5.1 or newer, tokenless public metadata probes, and npm publish --access public --provenance through the configured Trusted Publisher.

Automated Daily Driver

.github/workflows/auto-release.yml runs once per day and can also be dispatched manually.

Independent morning dispatch

Tracked by pm-9url9h. An independent operator-managed timer can invoke scripts/release/dispatch-daily-release.mjs at 04:45 Europe/Vienna (02:45 UTC in summer, 03:45 UTC in winter). The aim is completion before 06:30–07:00 local time; host availability, GitHub runner capacity and release gates can prevent that target. The host must be awake, online and able to authenticate with gh unattended.

The existing 02:35 UTC GitHub cron remains enabled as a fallback. Either clock can arrive first. The dispatcher checks both release workflows for active runs and skips an existing exact UTC-day tag. Tag existence is explicitly not publication proof: incomplete publication still requires the normal immutable-tag recovery procedure. Hosted concurrency and the one-release-per-UTC-day guard remain the final protection if a native run races the external request.

The standalone dispatcher requires an absolute private state directory:

node scripts/release/dispatch-daily-release.mjs --state-dir /absolute/private/state --check

--check reads prerequisites without dispatching or writing state, including outside the morning window. Without it, the dispatcher accepts only 04:45–05:15 Vienna time and sends workflow_dispatch to auto-release.yml on main with explicit push=true, dry_run=false, and telemetry_mode=off, matching scheduled production policy. It never builds from the operator's working branch, changes release secrets or bypasses hosted gates.

An exclusive daily intent file is written before sending the request. A timeout can mean GitHub accepted the request, so an uncertain attempt is retained and never automatically resent that UTC day. Inspect hosted run history before any manual recovery. Pre-dispatch read failures leave no intent; a timer may try again within the morning window. Late persistent-timer catch-up is a no-op. Install a reviewed copy outside development checkouts so branch edits cannot silently change an unattended service.

Keep dispatcher output in the service journal: dispatch_accepted proves only API acceptance. Verify the hosted run, GitHub Release and exact npm version separately. These runs remain workflow_dispatch; they do not improve or erase the native schedule denominator in Release Reliability (repository-only, excluded from npm). Disable the independent timer to roll back; the original hosted schedule and publication path continue unchanged. Host-specific unit files and authentication configuration belong in private operator documentation.

Hosted release policy

Pinned tool downloads retain their existing versions and mandatory acceptance checks. Codecov CLI v11.3.1 uses its immutable GitHub release asset API endpoint, with the reviewed SHA-256 verified before execution. The Release workflow first requires uncached pinned Bun bootstrap and exact version checks on native macOS and Windows runners. Both must pass before the Build, Test, Publish job can run; a failed download or mismatched runtime therefore blocks npm publication. The later installed-package acceptance remains a separate check of the published artifact. macOS Bun acceptance uses the official pinned asset URL with ?download=1 after repeated HTTP 504s on the ordinary route; binary caching stays disabled. A failed download still fails the job. Recover an incomplete release against its existing tag after reviewed transport changes land; a download failure does not authorize another automatic version. A completed, explicitly skipped publication job with an empty step list remains eligible for first-publication recovery. Missing job evidence is still refused, and accepted publication in any other attempt still blocks a second publication. Tracked by pm-gh1694 (repository-only tracker record, excluded from npm).

  • release only when commits exist after the latest release tag
  • ignore tracker-governance-only commits for publish eligibility: .agents/pm/** and the mechanically generated CHANGELOG.md projection do not create a package release by themselves, while any product, test, documentation, workflow, or other changed path remains release-relevant
  • create at most one production tag and npm version per UTC day; if no tag was created, a non-github-actions[bot] closure of the exact bot-created Auto Release blocked issue on the same UTC day triggers one preparation retry
  • if today's tag already exists, a queued schedule or blocker closure bypasses candidate preparation and accepts the immutable result only after independent public-distribution evidence confirms the exact release; incomplete publication continues through bounded recovery instead of being accepted from tag presence alone
  • release preparation must pass all quality and compatibility gates before commit+tag push
  • before dependency installation or build, auto-release verifies that the candidate has exact-commit analyzer results or an immutable tree-identical reviewed PR head with successful DeepScan and CodeFactor results
  • after the automatic version commit lands, main CI accepts its parent's analyzer evidence only when the commit is the exact immutable-tagged release projection: the canonical commit message, every expected distribution manifest changed by version substitution alone, and only the generated changelog heading changed. Any other path or content still fails closed
  • CHANGELOG.md is generated by the latest npm pm-changelog package (pm install npm:pm-changelog --project, then pm changelog generate --mode replace --all-release-tags) from closed tracker items across git release tag windows and checked in CI; do not edit it by hand
  • Release preparation passes --date-from-version with the target calendar version so the generated heading already has its UTC release date before the tag exists. This keeps the preparation commit consistent with canonical post-tag generation and the normal main changelog check.
  • protected-branch pushes require RELEASE_PAT; Auto Release fails fast before the expensive release gates when push=true, dry_run=false, and that secret is not configured
  • release reliability gating requires SENTRY_PERSONAL_ADMIN_TOKEN for issue-threshold checks; Auto Release fails before creating the version commit/tag when the token is missing and push=true, while sourcemap upload remains optional through SENTRY_AUTH_TOKEN
  • after creating and pushing a new tag, auto-release waits for the tag-push .github/workflows/release.yml run to finish instead of dispatching a second publish workflow
  • native scheduled and declared morning-dispatcher failures open or update the same daily blocker with trigger origin, preflight state (push, dry_run, release_pat_configured, and sentry_personal_admin_token_configured) and detected cause. Successful automatic recovery closes it only after exact publication verification; operator dispatch does not manage this lifecycle. This behavior is tracked by pm-automatic-release-alerts.
  • when either automatic origin fails across multiple UTC days, auto-release supersedes a stale open blocker with a fresh current-day blocker so same-day retry detection follows the latest automatic failure
  • closing the exact bot-created Auto Release blocked: scheduled run failed issue as a maintainer or agent records a same-day retry marker, then either retries preparation when no tag exists or watches an active exact-tag Release workflow. If only failed or missing evidence exists, it dispatches the reviewed current release.yml from main with the immutable tag input; it never reruns historical workflow code. It comments with the recovered tag on success and reopens the same issue on failure. A second close on the same UTC day is refused before release mutation and reported as retry_already_attempted, and workflow cleanup closures by github-actions[bot] are ignored.
  • after a scheduled run publishes a tag and the downstream release workflow succeeds, auto-release closes any open Auto Release blocked issue so the GitHub tracker reflects current release health
  • workflow concurrency can leave a delayed schedule queued behind another production run. The queued schedule resolves the current UTC-day immutable result before candidate provenance, proves publication, and exits without evaluating the generated release commit as a new candidate

Dependency advisory diagnostics are tracked by pm-z20n3s. Both Auto Release and Release retain the original .cache/dependency-security/receipt.json after the gate, including failures, as dependency-advisory-<run-id>-<attempt> for seven days. Only that public receipt is uploaded, with explicit hidden-file inclusion. It records registry and OSV findings or unavailable evidence without raw stderr, credentials, telemetry, or sibling caches. Missing receipts warn rather than masking the original failure. Retention never changes the gate verdict.

Pipeline entrypoint:

node scripts/release/run-release-pipeline.mjs

Tracked by pm-prrlce: hosted preparation uses --push --defer-push to write an exact producer binding. Auto Release uploads release-producer-binding-<attempt> before verifying and atomically pushing its prepared refs. Publication independently fetches the verified run's artifact and compares the tag name, target commit, tag object and declaration. Missing, expired, ambiguous or mismatched evidence fails closed; artifact retention is 90 days. A branch advance after sealing refuses publication rather than rebasing into a different target. Local --push refuses before Git inspection or asset mutation; local preparation without pushing remains supported. Historical admission remains limited to the reviewed exact-object inventory.

The pipeline performs:

  1. change detection + one-release-per-day guard
  2. a single YYYY.M.D version bump; ordinal targets and the removed --allow-same-day-release override fail closed
  3. latest pm-changelog install and main changelog refresh through package-owned full-history generation; the release pipeline passes --release-version, --all-release-tags, and the canonical --exclude-tag changelog-exclude policy so the pending release section matches post-tag CI checks
  4. build, clone-local merge-driver installation, then the remaining strict gates (typecheck, docs/skills freshness, coverage, static quality, compatibility, security, smoke checks, reliability gate); this ordering makes the checkout-owned CLI available before bootstrap, matches CI, and prevents fresh-clone tracker measurements from observing undeclared merge-driver repairs
  5. release note generation from changelog + pm evidence
  6. commit and tag creation (plus optional push)

The generated changelog includes clickable pm item links to the tracked .toon files. Missing release evidence should be fixed in pm item history, not by hand-editing CHANGELOG.md.

Changelog Classification (Contributor-Facing)

pnpm changelog:pm routes closed pm items into keep-a-changelog sections using pm-changelog classification logic. Use explicit type/tag metadata when you want deterministic section routing.

Signal tiers:

  • Strong signals: item type + tags
  • Weak signal: item title (after stripping CLI-flag-like tokens such as --add)

Category precedence (first matching bucket wins):

Priority Category Trigger terms (from strong + weak signals unless noted)
1 Security security, cve, vulnerability
2 Deprecated deprecated, deprecation
3 Removed removed, remove, deleted, delete
4 Fixed fix, fixed, bug, bugfix, hotfix, regression
5 Added feature, feat, added, add, new
6 Changed strong-signal change, changed, refactor, update, updated, improve; for non-bug-like types only, title fallback is also used
7 Other no classifier match

Bug-like default:

  • Items with type Issue, Bug, Bugfix, or Defect default to Fixed unless a higher-priority category already matched.
  • This bug-like default runs before title-only Changed fallback to avoid misrouting command-name issue titles (for example "pm update ..." issue reports).

Practical examples:

  • Issue + title pm update --add-tags fails -> Fixed
  • Task + tag refactor -> Changed
  • Feature + tag security -> Security
  • Issue + tag feature -> Added (explicit stronger signal beats default)

If a changelog routing rule appears incorrect, fix classifier behavior in the pm-changelog package/repo and then consume the updated package here. Do not patch generator internals in this repository.

Local Release Parity Checklist

  1. Confirm the UTC calendar date. Do not use an ordinal diagnostic as a production target.
pnpm version:next
  1. Verify previous-version tracker compatibility in a temporary project before release asset edits.

Create representative data with the latest published package and then read, mutate, run linked tests, validate, and health-check the same temp PM_PATH with the current build. The temp run must use isolated PM_PATH and PM_GLOBAL_PATH; never point compatibility tests at the repository's real tracker data.

Minimum coverage:

  • parent and dependency links
  • comments, notes, learnings, body, reminders, events
  • linked files, docs, and tests
  • legacy markdown item files (including external YAML wrappers before JSON front matter) migrating cleanly to TOON
  • closed issue metadata and history drift checks
  • current-build write mutation and item-count preservation
  1. Review private reliability signals.

Use maintainer-only local workflows for reliability checks and incident triage. Keep operational details, infrastructure topology, and raw diagnostics out of tracked release documentation and release notes.

Run the public Sentry/telemetry threshold gate through the package script alias:

pnpm sentry:telemetry:gate -- --telemetry-mode best-effort

The Sentry threshold gate reads the latest event for each issue and classifies expected handled failures from the SDK error catalog. An event is ignored only when it is handled, its pm.error_code resolves to a declared canonical code, its pm.exit_code exactly matches that code's transport contract, and the semantic class is usage, not_found, or conflict. Message and title prose never participate. Unknown codes, missing or mismatched exits, unhandled events, and every generic_failure or dependency_failed remain blocking. This keeps rewording independent from release policy and makes stale or broad message allowlists impossible. The gate skips issues whose events are all test traffic: environment=test (opted-in CI tests) or pm.source_context=test (a test child that delivers with another environment such as ci). An issue with any other event counts whenever any of its events falls inside the activity window. Activity and environment are separate Sentry queries, because Sentry applies lastSeen after an environment filter: a combined query would also skip an issue whose production event predates the window while a test event falls inside it. When the listing of issues with non-test events may be truncated, nothing is skipped, and a failed listing fails the gate. Both listings follow Sentry's next-page links up to --sentry-limit (default 200); when active issues remain beyond it, the gate fails, because it cannot judge issues it never read.

Sentry API requests use a 120-second deadline by default. Operators can set --sentry-request-timeout-ms between 1 and 300000 when reproducing provider latency, while the emitted gate receipt records the effective value. The release workflow pins 120000; query timeouts remain fail-closed and must not be treated as an empty issue set.

If private reliability checks identify repeated user friction, either confirm the current release already contains the remediation with regression coverage or fix it before continuing.

The build writes dist/cli-bundle/bundle-manifest.json atomically with SHA-256 digests for every emitted bundle file. At startup, pm reports bundle_integrity_torn_install only when a module-loader failure is accompanied by manifest proof that an upgrade or rebuild changed, removed, or corrupted the active bundle. Reinstall @unbrained/pm-cli and retry after that diagnostic. Ordinary ERR_MODULE_NOT_FOUND and export failures with an intact manifest remain unexpected failures and must continue to block reliability gates.

  1. Run the same release pipeline locally.

Runtime install reproducibility is tracked by pm-gh1417. The committed runtime-dependencies.json records only the exact production closure from the tested pnpm-lock.yaml, including integrity, nested conflicts and optional edges. After changing dependencies, run pnpm install, then node scripts/release/runtime-lock.mjs apply. Static and publication gates reject drift; never generate the ledger by resolving fresh registry ranges.

pnpm pack:distribution builds an isolated publication tree, copies the exact installed runtime closure into physical package directories and sets npm's bundleDependencies. It excludes pnpm's development store and source maps. The release workflow publishes this staged tarball with provenance; CI and packed smoke tests use the same staging command. Application distribution and bundled runtime have separate committed size and file-count limits, so runtime payload cannot hide application growth. The ledger ships with the tarball for installed-version verification. pm-iceees keeps selected optional-peer subtrees complete: npm deliberately skips peer edges when gathering bundled files, so staged runtime manifests record the exact tested peer bindings as ordinary or optional dependency entries. Package code, source and installed manifests, and peer declarations remain intact. pm-2csj4k (repository-only tracker record excluded from npm) adds one root entry per required peer: Bun installs resolve a package's dependencies from the registry instead of its bundleDependencies, so the published root manifest pins every non-optional peer target that the ledger installs at the root to its tested version and bundles it. A peer resolved only in a nested node_modules is not pinned, because the root manifest may name only root-installed packages, which the installed-runtime verifier enforces. Without the pin, @sentry/node's @opentelemetry/api ^1.9.1 resolved to 1.10.0 and Bun warned against the bundled OpenTelemetry packages' <1.10.0 peer ranges. Optional-only peers and direct dependencies are unchanged, and the packed smoke runs a verbose bun add and fails on any peer-range warning. A real archive regression resolves the peer's descendant from an extracted package and the installed-runtime verifier still refuses any missing locked package. npm 12 ignores published shrinkwraps; shipping a shrinkwrap alone therefore cannot enforce this contract. SDK consumers supply the bounded optional Node types peer themselves.

Physical entrypoint verification is tracked by pm-tl7vlu (repository-only tracker record excluded from npm). The dependency advisory CLI resolves both its invocation and module to physical paths, so directory aliases and Node's symlink-preserving main-module mode still execute verification and return a receipt or failure status. Module imports expose the collector without starting verification.

Push the final implementation commit through a reviewed pull request first, wait for DeepScan and CodeFactor to finish on that reviewed SHA, then run the canonical registry-owned preflight:

pnpm verify:preflight

The registry supplies the ordered executable plan, command arguments, environment, capture policy, and explicit skip policy. Its receipt distinguishes passed checks from declared skips. The same registry maps named PR, nightly, and release workflow gates; hosted-only entries must explain why no faithful local equivalent exists. Analyzer provenance is the first executable step, so an unreleasable candidate fails before build, coverage, or package work.

The preflight includes the mandatory local hosted-analysis proof:

pnpm quality:hosted-analysis

The gate accepts only DeepScan's explicit zero-new-issue status and CodeFactor's explicit no-issues result. Both contexts are required by main branch protection, and the release pipeline reruns the same immutable-tree proof. The gate reads branch protection through REST first; if that transport is unavailable or its core API quota is exhausted, it queries the equivalent GraphQL rules across every cursor-paginated page and still requires one exact main rule with strict updates plus both analyzer contexts. Malformed, truncated, missing, or ambiguous admin policy data fails closed. GitHub Actions' ephemeral token cannot request the repository Administration: read permission required by both of those policy surfaces. When both admin transports reject that token, the gate uses the contents-readable GET /branches/main summary and requires that the exact candidate is the current protected branch head, protection is enabled and enforced, and CodeFactor plus DeepScan are effective required contexts. That summary intentionally cannot attest the admin-only strict-update setting: its receipt reports strict: null, strict_verified: false, and verification_scope: effective_required_checks instead of inventing a positive result. Exact, immutable identical-tree, or fully validated deterministic-release analyzer provenance remains mandatory and is emitted beside the policy receipt, so the restricted-token recovery cannot admit an unanalyzed or different-tree candidate. A maintainer token with administration-read access still verifies and reports strict: true through REST or paginated GraphQL. It reads the release commit first. When GitHub does not copy app results onto a merge commit, the gate may reuse a reviewed merge-parent or squash-PR head only when its immutable Git tree SHA exactly matches the release commit. Squash provenance additionally requires one unambiguous GitHub association to a closed PR merged into main with the release commit as its merge commit. Missing, ambiguous, or different-tree provenance fails closed.

The automatic release commit is the sole non-identical-tree derivation. The gate requires one parent, the canonical release subject and body, an immutable v<version> tag resolving to the candidate, the complete expected manifest inventory, exact old-version to new-version substitutions in every manifest, and an exact Unreleased changelog-heading replacement. Fixed distribution paths come from the same inventory as version synchronization and staging. Plugin runtime package manifests are mandatory when present in the immutable parent tree; historical parents that predate those files remain verifiable. The package-generated replacement may be ## <version> or the historical ## <version> - <YYYY-MM-DD> form, with the date matching the calendar version. The complete heading must match and every other changelog byte must remain unchanged. The gate then resolves the parent through the same exact or reviewed identical-tree analyzer contract. Added, deleted, renamed, missing, or otherwise modified paths are refused, as are malformed manifests, changelog edits, untagged commits, and parents without successful analyzer evidence. The receipt reports analysis_source: deterministic_release_transform and names both the release parent and the parent's analyzer source; it never describes the changed release tree as identical to the analyzed tree.

The supported default-branch arrival path is a reviewed pull request. A direct push to main is releasable only if both analyzers attached successful evidence to that exact commit or it is the narrowly validated automatic release projection above; when neither condition holds, the gate explicitly refuses the candidate instead of letting the next scheduled release discover the missing precondition after expensive work. CI runs Release analyzer readiness (main) on every product-relevant push to main, providing continuous visibility between release attempts. If GitHub's commit-association endpoint has not yet published a new merge, the gate reads the pull-request number from GitHub's standard merge message and accepts it only after the immutable PR resource confirms that exact merge SHA, main base, and reviewed head. Remediate a refused direct-main candidate by landing the next tree-changing fix through a reviewed PR; never copy, synthesize, or bypass analyzer evidence.

Reviewed Delivery Closeout

The reviewed delivery includes its PM evidence, item closure, and generated changelog. Complete those mutations on the implementation branch before merge whenever their evidence is already available. A pull request is not complete while known tracker or changelog closeout remains queued for a direct push.

When a repository update genuinely depends on the merge SHA or a post-merge hosted result, create a normal follow-up branch from main and land that update through another reviewed pull request. Do not push .agents/pm/** or CHANGELOG.md closeout directly to main. Although tracker-only commits do not create package-release eligibility by themselves, the direct commit still becomes the default-branch head and lacks the reviewed analyzer evidence needed by the next release-relevant candidate. Release analyzer readiness (main) must fail closed in that state; its failure is a provenance control, not an analyzer approval. Restore readiness only with a tree-changing, main-based, reviewed pull request—never by copying statuses, weakening the gate, or synthesizing a pull-request association.

# Read-only parity check
pnpm release:pipeline:dry-run

# Full local preparation (version/changelog mutation + local commit/tag)
pnpm release:pipeline

The static phase includes pnpm sdk:surface:check, pnpm benchmark:sdk-entrypoints:check, and pnpm benchmark:transport:check. The packaging phase runs the package-artifact preflight step (scripts/release/package-artifact-gate.mjs, the gate behind pnpm quality:package-artifact) before any version commit or tag exists. It evaluates npm's actual packlist against the committed unpacked-size, file-count, required-runtime-file, and forbidden source-map budgets, so an over-budget candidate never receives an immutable tag. The package-artifact-sentry-injected step then runs the same gate with --profile=sentry-injected --simulate-injection. It runs the installed sentry-cli on a disposable copy of dist/ and adds each packed file's measured growth to the packlist, so it checks the bytes the release will publish without rewriting the checkout. An incremental rebuild does not re-emit unchanged modules, so injecting the checkout itself would leave injected files behind. Release additionally reruns the packlist gate immediately after Sentry debug-ID injection and before any Sentry upload or finalization, using the derived sentry-injected profile: the base ceiling plus max_bytes_per_injected_file for every packed dist/**/*.js file. sentry-cli sourcemaps inject dist/ adds one fixed-length snippet to each such file (358 bytes with @sentry/cli 3.8.2), so a fixed allowance stopped covering the transform once the file count grew and blocked the already-tagged 2026.11.5 (pm-998juj). tests/unit/scripts/release/sentry-injection-allowance.spec.ts runs the installed sentry-cli against a fixture, so an upgrade that grows the snippet fails in the pull request that upgrades it, not on a tag. CI constrains the base build, publication constrains the exact mutated artifact users install, and a rejected package cannot leave finalized observability artifacts. Additive SDK exports require a reviewed snapshot refresh. A removal or semantic signature change fails until the maintainer supplies pnpm sdk:surface:update -- --acknowledge-breaking "<release rationale>"; pair that acknowledgement with migration guidance, compatible extension version bounds where applicable, and the next eligible date-based release.

Build finalization removes redundant indentation from emitted .d.ts files (pm-chw71s). String and template type contents, comments, JSDoc, symbols and module boundaries retain their original tokens. Source declarations remain readable, and packed TypeScript consumers verify the emitted surface before publication. Artifact ceilings remain unchanged. Whitespace after explicit declaration delimiters is also compacted, while newlines that may supply automatic semicolons are retained. Literal contents and documentation remain intact; the publication-weight owner is pm-998juj and this delimiter-compaction tranche is pm-5aze0q (repository-only tracker records excluded from npm).

The generated error-code catalog ships as compact rows (pm-998juj, a repository-only tracker record excluded from npm). scripts/generate-error-code-catalog.mjs omits every derivable field: the meaning sentence, the class implied by the exit code, the shared recovery guidance, provisional stability, every-command emitters, a self canonical code and empty aliases. expandPmErrorCodeCatalogRows restores them at load time, so PM_ERROR_CODE_CATALOG and every serialized projection stay byte-identical. The catalog appears once in the tsc emit and once in each CLI bundle, and the compact rows removed about 320 KB from the packed distribution.

The publication tree ships only the emitted declarations a consumer can reach (pm-998juj). exportedDeclarationClosure in scripts/release/package-distribution.mjs walks every exported types entry through its relative from and import() specifiers. The exports map refuses unexported subpaths, so a consumer cannot import any other dist/**/*.d.ts: the CLI internals behind ./cli, the MCP servers, and internal SDK and core modules. Packing leaves those out, and the checkout keeps every declaration. A manifest without an exports map ships every declaration, because any subpath then stays importable. The walk fails closed. An exported or referenced declaration missing from the packlist, or a relative specifier that is not a .js module path, refuses the pack instead of publishing a dangling type reference. The packed smoke's strict TypeScript consumer imports every exported types entry, so it compiles the complete shipped closure. The cut dropped 212 declarations (266,135 bytes) from the 2026.11.6 distribution. Artifact ceilings remain unchanged. The tsc .js emit still ships, because the pm and MCP bins and the ./cli export import it directly.

  1. Land the implementation through a reviewed pull request, then use hosted preparation for publication.

Local preparation without --push is a diagnostic preview. Its local producer declaration cannot authorize publication. Once the reviewed implementation is on main, dispatch the existing Auto Release workflow from that default branch:

gh workflow run auto-release.yml --ref main \
  -f push=true -f dry_run=false -f telemetry_mode=off -f trigger_origin=operator

Hosted preparation runs the mandatory gates, creates the version commit and annotated tag, and seals their exact producer binding in an immutable artifact before verifying and atomically pushing either ref. It waits for the single tag-driven Release workflow to publish and accept that exact package. Do not push locally prepared tags or invoke npm publish separately.

If today's immutable tag already exists, use the exact-tag recovery procedure below. A local diagnostic tag does not prove hosted preparation or publication; never move a published tag or mint a second automatic version for the same UTC day.

GitHub Workflow

.github/workflows/release.yml runs on v*.*.* tags and handles:

  • full-history checkout
  • manual workflow_dispatch by tag for recovery. An isolated anonymous exact-version probe keeps already-published recovery on the reviewed dispatch-time main source; when the immutable tag exists but npm publication never completed, recovery preserves the reviewed package gate and budget in runner-temporary storage before checking out that exact tagged source. The controls inspect the untouched tagged package without becoming part of it, so recovery applies the current fail-closed distribution policy while retaining the original version guard and immutable package bytes.
  • pnpm install with frozen lockfile
  • version policy and tag guard
  • secret scan
  • build, clone-local merge-driver installation, typecheck, test, and coverage
  • generated changelog verification and pm-changelog installation before the tracker-bearing static gate, so a clean checkout does not misclassify the managed extension's linked files as missing. Every exact-tag source, including an ordinary tag push and recovery of an unpublished immutable tag, regenerates only CHANGELOG.md with the tagged checkout's canonical package script and fails if that operation changes any other tracked source path (apart from managed-extension install metadata). Reviewed main recovery sources retain the non-mutating drift check.
  • static quality gate (shared complexity, duplication, dead/orphan module, file/folder hygiene, source/exported docstring coverage profile)
  • temporary-project compatibility gate against latest published tracker data
  • reliability threshold gate (Sentry severity threshold, bounded to a recent-activity window via --sentry-window-days (default 14, 0 = unbounded) so a stale benign unresolved issue cannot block every scheduled release; Sentry requests use the fail-closed bounded --sentry-request-timeout-ms contract (default 120000, maximum 300000); --telemetry-mode gate policy: off | best-effort | required). Scheduled auto-release.yml failures open/update an Auto Release blocked GitHub issue so blocked daily releases are never silently skipped.
  • extension grammar acceptance (pm-gh1466, pm-gh1467): the existing flag parity gate includes a synthetic extension population, and packed smoke runs the same independent echo oracle on Node and Bun. Immediately before publication, extension-flag-acceptance.mjs extracts the exact tarball passed to npm publish, resolves its public SDK exports and manifest executable, and checks help, machine contracts, handler options, remaining arguments and refusals. Any failed or missing case stops publication. The retained receipt records executed case identities, runtime, resolved entrypoints, fixture digest and tarball SHA-256. This proves candidate behavior; published-installed acceptance remains a separate claim. Exact-tag recovery preserves both the reviewed runner and its fixture outside the tagged checkout.
  • diagnostic privacy acceptance (pm-geywoc): the same candidate runner requires the independent synthetic diagnostic fixture through the installed public SDK. A disposable loopback collector verifies real serialized error events, streamed spans, and finalized logs, including scope attributes. Quoted keys, Unicode escapes in keys/delimiters/separators, nested serialized JSON, containers and incomplete values must lose their sensitive markers; safe text, numeric/boolean attributes and caller scope must remain intact. Opaque encoded boundaries redact the remaining text, and a long backslash control bounds scanning cost. Missing, failed or duplicate case receipts block acceptance. Recovery preserves the diagnostic fixture with the runner. This local export proof is separate from hosted Sentry ingestion and does not change user consent.
  • sandboxed pm coverage
  • a base npm pack dry run, optional Sentry debug-ID injection, a second sentry-injected packlist budget over the exact publishable bytes, and only then optional Sentry release metadata/upload/finalization and the npx tarball smoke test. Sentry release metadata and sourcemaps are uploaded only from tag sources. Recovery of an already-published version from reviewed main preserves that version's existing Sentry commits and sourcemaps; local debug-ID injection still validates the candidate artifact budget without changing the published release's symbolication data.
  • generated release notes from changelog plus sanitized tracker metadata. The tracker read projects only release-relevant fields and explicitly removes row and token limits. The generator validates the completeness receipt before summarizing; partial, unreadable, or malformed input produces a visible warning instead of claiming no items closed. The regression suite exercises a real workspace exceeding the default JSON budget. Tracked by pm-svxdsx.
  • artifact uploads
  • npm publish --access public --provenance --tag latest, skipped on retry only when the exact version is anonymously visible from a fresh npm cache. If the package is public but the target version is absent, the workflow publishes immediately without attempting a package-access mutation. A dispatch may do so only when its source-selection preflight pinned the checkout to the requested immutable tag; reviewed-main recovery continues to refuse publication of a missing target. Only when neither the target nor package metadata is anonymously visible, the same-tag recovery path fails closed and instructs the maintainer to restore public package visibility outside the workflow. Trusted publishing does not authorize package-access mutations. Permission, authentication, and registry failures stop the workflow instead of risking an immutable-version overwrite. The checked-out tag's package.json supplies the canonical package identity to both the publish guard and the post-publish npm/npx/bunx verifier so those identities cannot drift. The explicit stable dist-tag also preserves correct latest behavior when rerunning historical ordinal tags.
  • post-publish npm/npx/bunx verification in the separate publication job, which needs the publishing job and owns a 110-minute limit, so a slow build phase can no longer cut the registry wait short. npm's publish-time malware scan holds a new version for about five minutes and sometimes far longer (2026.11.8 became visible 55m22s after publish); the verifier polls for about ninety minutes, and an exhausted wait names the version, the scan and the publication-only re-run (pm-0jzl5t). The job checks out the exact commit the release job published (recorded after exact-tag source selection), so the verifiers read the same package.json identity as the publish guard; previous-release selection keeps the reviewed dispatch-source selector. The verifier runs scripts/release/verify-published-release.mjs, using isolated empty npm and Bun caches plus an empty npm user config so maintainer credentials and cached metadata cannot mask a public-registry outage. The verifier dispatches a real pm contracts command through both explicit-bin and package-default invocations, performs stateless JSON-RPC server/discover against the symlink-resolved pm-mcp bin under both npx and bunx, and launches the exact public pm-mcp-http bin under both executors on an isolated loopback port for a real Streamable HTTP server/discover exchange. Both transports require canonical 2026-07-28 metadata/result envelopes. HTTP startup is bounded to two 20-second attempts per executor so the complete retry budget remains below the hosted step timeout. Signal-aware process-group cleanup escalates from SIGTERM to SIGKILL after a bounded grace period, including when the outer evaluator times out or an intermediate executor exits before its server descendant. Direct executor exit is not treated as process-group cleanup. The verifier derives bin coverage from package.json, and proves missing-bin and missing-command controls fail.
  • exact-package installed acceptance through scripts/release/verify-installed-agent-session.mjs. Separate npm and Bun install roots must contain the resolved executable, then each drives the cold-start init -> context -> create -> claim -> annotate -> files -> close -> validate -> get -> context loop. The structured report identifies the failing step and records per-step output ceilings and estimated token cost.
  • GitHub Release creation
  • GitHub Release metadata verification through the same local verification script

The Auto Release parent has a 290-minute orchestration limit: 60 minutes for preparation and gates, 30 for aggregate runner queue delays, the serial 60-minute build-and-publish, 110-minute publication verification, 15-minute acceptance and 5-minute advertisement job limits, and 10 minutes for orchestration overhead. Runner queue time consumes the waiting parent's wall time even when no child job is executing. The reserve accommodates delays without extending a child job or changing a quality gate. Extreme queue delays can still exhaust the parent; recover the existing immutable tag after checking its publication evidence rather than creating another daily version. Tracked by pm-29bwud.

Monitor:

gh run list --workflow Release --limit 5
gh run watch <run-id> --exit-status

Verified Coverage Uploads

Tracked by pm-2x67z9. CI downloads the official Codecov CLI from an immutable GitHub release and checks the reviewed SHA-256 before making it executable. Both coverage and test-result uploads require that verifier to succeed and retain mandatory upload failures plus the exact PR-head identity. The pinned action's supported url input selects the official https://codecov.io Cloud host for the current coverage and test-result APIs; certificate validation remains required for every request. Both uploads disable verbose logging because debug output can expose signed storage upload URLs in public Actions logs. Normal upload-result diagnostics remain available, and upload errors still fail the required gate. Failed tests can still upload diagnostic reports after successful verification. Update the release URL and digest together; verify the official artifact and its adoption age before changing those pins. The shell regression executes the real checksum with approved and corrupt download fixtures; a failed check must prevent executable permission and uploads.

Post-Release Verification

Use the rolling reliability report (repository-only, excluded from npm) as well as the latest release result. Its first-attempt denominator, explicit outcome receipts, and UTC dispatch policy preserve failures even after a recovery succeeds.

npm view @unbrained/pm-cli@<version> version dist.integrity dist.unpackedSize --json
npx --yes --package @unbrained/pm-cli@<version> -- pm --json --no-extensions contracts --summary
npx --yes @unbrained/pm-cli@<version> pm --json --no-extensions contracts --summary
bunx --silent --bun --package @unbrained/pm-cli@<version> pm --json --no-extensions contracts --summary
bunx --silent --bun @unbrained/pm-cli@<version> pm --json --no-extensions contracts --summary
pnpm release:verify-installed-agent -- --version <version> --manager both --json
gh release view v<version> --json tagName,name,isDraft,isPrerelease,url
pnpm release:verify-published -- --version <version>

The executable remains pm even though the npm package is scoped.

Use the npm registry package for maintainer global updates. Do not use npm install -g https://github.com/unbraind/pm-cli.git as the normal update path; npm can leave a stale shim while replacing git-sourced global installs. If a workstation is already in that state, run bash scripts/install.sh --repair or npm uninstall -g @unbrained/pm-cli && npm install -g @unbrained/pm-cli@latest.

Failure Handling

  • If local gates fail, fix and rerun before tagging.
  • Treat failed scheduled Nightly Validation jobs as release-health blockers until triaged. The nightly workflow opens or updates a GitHub issue for each failing scheduled OS/Node matrix entry, with the run URL and commit SHA, so cross-platform regressions do not rely on someone manually scanning the Actions tab.
  • A time-bounded waiver that expires fails repository-defect-evidence-required at the next release gate; the failure record names the item or boundary and its expiry. The nightly Waiver notice window step fails about two weeks earlier, so renew or retire the waivers it names through a reviewed pull request first. See Waiver Notice Window.
  • Treat a green manual Auto Release dry-run (push=false or dry_run=true) as gate confidence only. It does not prove the protected-branch publish path; scheduled production runs still require RELEASE_PAT and SENTRY_PERSONAL_ADMIN_TOKEN to be configured in the release environment.
  • If release preparation fails before creating a tag, fix the cause and retry preparation on the same UTC day.
  • Once a tag exists, never move or replace it and never create a same-day ordinal recovery version. Rerun .github/workflows/release.yml with workflow_dispatch and tag=v<version> (or close the current bot-created blocker once to trigger the guarded exact-run recovery). The workflow skips npm publication for a version visible to its isolated anonymous probe, keeping reviewed dispatch-time main. A definitive missing-version response can pin source to the immutable tag only after admission reads complete workflow and job inventories, validates totals and unique identities, and checks matching attempts. Prior successful Publish to npm evidence refuses publication while metadata is unavailable: acceptance can precede visibility. Unavailable, malformed, incomplete or inconsistent evidence also refuses it. Only the executing attempt is excluded; its earlier attempts remain checked. This admission check also runs before first publication, using reviewed controls retained outside recovery checkout. Push and dispatch publishers share exact-tag concurrency. See pm-publication-acceptance-fence. A never-published tag then reapplies the version guard, installs the managed changelog extension, regenerates the package changelog with the tagged checkout's canonical policy under a tracked-path mutation guard, and permits first publication only from that tagged source. Ordinary tag-push releases use the same guarded regeneration because their tag did not exist when the release commit's changelog was prepared. Other registry failures stop before source selection or publication.
  • If the publication job fails because npm has not yet made a version that Publish to npm accepted visible, the package is held by npm's publish-time scan, not lost. Do not dispatch a recovery or touch the tag. Once npm view @unbrained/pm-cli@<version> version succeeds, run gh run rerun <run-id> --failed: only the publication job and the skipped acceptance and advertisement jobs run again, without rebuilding or republishing. The failure message prints the exact command (pm-0jzl5t). Close the bot-created Auto Release blocked issue by hand after that run succeeds; the parent run that recorded the failure stays red.
  • If an immutable published package contains a defect that cannot be repaired by rerunning the same tag workflow, document the incident and ship the code fix in the next UTC day's release.
  • A manual exact-tag workflow_dispatch recovery uses isolated anonymous registry probes and never mutates package access. A visible package with a missing target version proceeds directly to exact-tag OIDC publication. When neither the package nor target version is anonymously visible, recovery fails closed until a maintainer restores public visibility outside the workflow. An already-visible immutable version is still verified and never republished. Recovery starts from the dispatch-time commit SHA and fails unless the dispatch ref is the repository default branch (main). It remains on that reviewed source when the exact npm version exists. When the version is definitively absent, it switches to the resolved commit behind RELEASE_TAG, requires package.json to match the tag, installs the clone-local merge driver and managed changelog extension, and may publish that exact source after every gate passes.
  • Record failure evidence and remediation in the release pm item.

Silent skip debugging

When auto-release exits green but does not cut a version, inspect the pipeline's JSON skip reason from scripts/release/run-release-pipeline.mjs (or rerun locally with pnpm release:pipeline:dry-run -- --json):

  • tracker-only skip family: tracker_only_changes_since_last_tag (all changed paths are .agents/pm/** and/or the generated CHANGELOG.md projection; a product-visible path is the required negative control)
  • changelog-empty skip family: empty_generated_changelog_section_for_target_version (generated release section exists but has no non-empty entries)

pm-changelog is maintained in a separate repository/package. Classifier or release-window bugs must be fixed and released there first, then consumed here via the latest npm package (pm install npm:pm-changelog --project) before rerunning release generation.

Before local changelog regeneration diagnostics, always refresh tags first:

git fetch --tags --force

Without a forced tag refresh, local tag windows can drift from origin and produce misleading changelog or skip diagnostics.

Aus docs/RELEASING.md in pm 2026.11.13. Auf GitHub ansehen