Agent Skillsslopus/happy › release

release

GitHub

引导用户完成Happy monorepo中CLI、移动端、Web及Server组件的版本升级、构建、测试与发布流程,替代旧版交互工具。

.agents/skills/release/SKILL.md slopus/happy

Trigger Scenarios

执行发布流程 版本升级 部署上线

Install

npx skills add slopus/happy --skill release -g -y
More Options

Non-standard path

npx skills add https://github.com/slopus/happy/tree/main/.agents/skills/release -g -y

Use without installing

npx skills use slopus/happy@release

指定 Agent (Claude Code)

npx skills add slopus/happy --skill release -a claude-code -g -y

安装 repo 全部 skill

npx skills add slopus/happy --all -g -y

预览 repo 内 skill

npx skills add slopus/happy --list

SKILL.md

Frontmatter
{
    "name": "release",
    "description": "Release pipeline for CLI, mobile, web, and server. Guides through version bumping, building, testing, publishing, and deploying. Replaces the old interactive release-it flow with a Claude Code-native experience. Use when user types \/release or asks to release, publish, deploy, or ship any component."
}

Release

You are the release operator for the Happy monorepo. When invoked, walk the user through releasing the component they choose.

Step 1: Pick a target

Ask which component to release:

  • CLI — npm package happy
  • Mobile — Expo/EAS builds for iOS + Android
  • Web — Docker image + K8s deploy via TeamCity
  • Server — Docker image + K8s deploy via TeamCity
  • Docs — GitHub Pages (separate repo)

Present these as options. Wait for the user to pick.


CLI Release

Package:     packages/happy-cli
npm name:    happy
Registry:    https://registry.npmjs.org
Git tags:    cli-{version}

Tag namespace note:

  • CLI releases use cli-X.Y.Z
  • Native releases use native-<runtime-version>
  • OTA releases use ota-<ota-version>
  • Do not use a bare vX.Y.Z tag for Happy releases because multiple release streams coexist in this repo

Step 2: Gather state

Run these in parallel:

  1. npm view happy dist-tags — see current latest + beta
  2. cat packages/happy-cli/package.json | grep version — local version
  3. git status --short — check for dirty state
  4. git branch --show-current — confirm branch
  5. git log --oneline -10 — recent commits for release notes context

Present a summary:

Local version:  X.Y.Z
npm latest:     X.Y.Z
npm beta:       X.Y.Z-N
Branch:         main
Working tree:   clean / dirty

Step 3: Pick channel and version

Ask the user:

  • Channel: latest or beta
  • Bump type: For latest: patch, minor, major. For beta: prerelease (appends -N), or explicit version.

Suggest a sensible default based on the current state. For beta, the next prerelease of the current version. For latest, a patch bump.

Present as options. Wait for confirmation.

Step 4: Version bump

Edit packages/happy-cli/package.json directly — do NOT use npm version (it chokes on pnpm workspace protocol).

IMPORTANT: do this before build/test for the CLI. The build imports package.json and bakes the version into the generated bundle. If you build first and bump later, happy --version can still report the old prerelease version even though npm metadata shows the new one.

Step 4b: @slopus/happy-wire must stay bundled — do NOT move it back

packages/happy-cli/package.json keeps "@slopus/happy-wire": "workspace:*" in devDependencies, deliberately. That is not a mistake to tidy up.

pkgroll has no --external flag — its entire externals policy is derived from dependencies/peerDependencies. So the dependency section IS the bundling switch:

  • in dependencies → pkgroll emits a bare import ... from '@slopus/happy-wire' and Node resolves it from the registry at runtime
  • in devDependencies → pkgroll inlines the code into dist/, and the dep vanishes from the published package.json entirely

It must stay in devDependencies. After any build change, verify:

# must return nothing — no runtime import may survive
grep -rnE "(import|require).*@slopus/happy-wire" packages/happy-cli/dist/
# must return the definitions, not just import mentions
grep -rhoE "(function|const) (createEnvelope|stripLeadingTaskNotificationWrappers)" packages/happy-cli/dist/

(A bare "@slopus/happy-wire": "workspace:*" string does still appear in dist — that is the CLI's own package.json inlined as a JSON literal for the version string. Inert. Only an actual import/require matters.)

Why this exists. 1.2.1-beta.0 shipped declaring "@slopus/happy-wire": "0.1.0" — the only version on npm, published 2026-02-13. Local happy-wire was also labeled 0.1.0 but had 18 commits of drift, including f85b20c3 which added stripLeadingTaskNotificationWrappers and imported it from happy-cli/src/codex/utils/sessionProtocolMapper.ts. February's tarball had no such export, ESM failed at module load, and happy crashed on every invocation — dead on arrival, not degraded. 1.2.0 had survived the identical latent bug purely because none of its 15 import sites needed a post-February symbol. workspace:* publishes the local version NUMBER, never the local CODE.

No in-repo test can catch that class of bug. Inside the monorepo workspace:* resolves to local source, so prepublishOnly — build, typecheck, all 792 unit tests — always sees the correct code. It only fails against the registry. The global-install smoke check in Step 11 is the ONLY gate.

Still exposed — happy-agent and happy-server-self-host both keep happy-wire in dependencies, so they carry the original trap. Before publishing either, bundle it the same way or get happy-wire republished first.

Publish rights: @slopus/happy-wire is owned solely by steve.kite <steve@korshakov.com>. bra1ndump is an owner of happy but NOT of the @slopus scope, so publishing happy-wire 404s for them. Bundling exists partly to route around that.

Note: happy --version prints BOTH happy's own version and the Claude Code version it found:

happy version: 1.2.1-beta.1
Using Claude Code v2.1.224 from native installer
2.1.224 (Claude Code)

Do NOT pipe it through tail -2 — that cuts the happy line off and makes it look like the command only reports Claude Code's version. Read the first line.

Step 5: Build

cd packages/happy-cli
pnpm --filter happy run build

Report success/failure. Stop on failure.

Step 5b: Self-host server split

The happy npm package no longer bundles the self-host server binary or webapp. Packaged installs resolve those from the separately installed happy-server-self-host package. Do not rebuild or ship tools/server or tools/webapp as part of a CLI release.

If the CLI release depends on self-host server changes, release happy-server-self-host separately. It lives in packages/happy-server-self-host and is the publishing shell around the private packages/happy-server: pnpm --filter happy-server-self-host build bundles that package's standalone entrypoint into dist/ and copies prisma/ in (this needs bun), then pnpm --filter happy-server-self-host run bundle:webapp builds the bundled webapp. Publish from packages/happy-server-self-hostpackages/happy-server is private and is never published. The server package is a JS/TS npm package; npm handles platform specific dependencies such as Prisma and sharp normally. Do not pass --ignore-scripts when publishing it; its prepublishOnly script rebuilds the runtime, rebuilds the webapp, and runs tests before npm receives the tarball.

Before handing a server publish to the user, pre-run the full prepublishOnly chain yourself to catch failures early — the bundle:webapp step runs a multi-minute expo export, and build needs bun:

pnpm --filter happy-server-self-host --fail-if-no-match run prepublishOnly

The server typecheck, unit suite, and both Docker images are gated by .github/workflows/server.yml.

(Observed: 1332 merged a standalone.spec.ts test that only passes on Windows because the impl used POSIX path.basename; it was red on main and would have aborted the publish at the prepublishOnly test step.)

Step 6: Test (unit only)

cd packages/happy-cli
pnpm --filter happy exec vitest run --project unit

Integration tests are slow and flaky — skip them for releases. Unit tests are the gate. Expect the unit suite to take around a minute; src/utils/serverConnectionErrors.test.ts is particularly slow, so don't mistake a long run for a hang.

Report results. If failures, ask the user whether to proceed or abort.

Step 7: Publish

Mandatory human handoff for npm authentication

The agent MUST NOT run the actual npm publish command. npm authentication is interactive for this package: the maintainer must authenticate first and may be asked to authenticate again in the browser or provide an OTP during publish. After the version bump, build, bundle checks, tests, and final confirmation are complete, stop and hand the maintainer these exact commands to run in their own terminal:

cd packages/happy-cli
npm login
pnpm publish --tag {channel} --no-git-checks

Never ask the maintainer to paste an npm password, token, browser link, or OTP into chat. Wait for them to report that the command completed, then independently verify the registry in Step 8 before committing/tagging the release. If a publish was started by the agent before the handoff requirement became known, stop it and check npm view happy@{version} version before doing anything else.

  • --no-git-checks: allows dirty working tree (we already verified state)

⚠️ NEVER pass --ignore-scripts. prepublishOnly runs pnpm test (build + unit tests), and the build re-stamps the version into the bundle (Step 4). Skipping it ships whatever stale dist/ happens to be on disk. Two rationalizations look reasonable and are both WRONG:

  • "We already built + tested this session, so the scripts are redundant — skip them to go faster." That earlier build may predate the version bump (or a dependency change). The on-disk dist/ is then stamped with the OLD version, and --ignore-scripts ships it. This actually happened: 1.1.10-beta.9 was published with --ignore-scripts and shipped a bundle stamped beta.8happy --version reported beta.8 while npm metadata said beta.9. npm versions are immutable, so the only fix was bumping to beta.10 and re-releasing. A wasted version number and a broken publish, to save one ~1-minute rebuild.
  • "It makes the TLS-failure retries faster." The prepublishOnly rebuild on each retry is the price of correctness, not overhead to trim. If retries are painful, change the network (see the TLS note above) — do NOT skip scripts.

If you catch yourself reasoning toward --ignore-scripts, stop: there is no case in this repo where it is correct for a publish.

MUST use pnpm publish — never npm publish. This is a pnpm workspace; npm publish mis-resolves the workspace protocol and the bin entries and ships a broken tarball (a regression was reported for exactly this and the fix was to standardize on pnpm publish). pnpm publish is the only supported path. Do not "fall back" to npm publish if pnpm errors — diagnose the pnpm error instead.

Transient TLS upload failures are expected — retry, don't panic. The tarball is large (~160 MB, ~1000 files). The upload to registry.npmjs.org frequently dies mid-stream with:

npm error code ERR_SSL_SSL/TLS_ALERT_BAD_RECORD_MAC
npm error ... ssl3_read_bytes:ssl/tls alert bad record mac ...

This is network-layer corruption of a single TLS record on the long upload, not a code, auth, or version problem. A single bad record kills the whole stream, so each fresh attempt has an independent chance to complete. Verify that the version did NOT land (see Step 8), then ask the maintainer to re-run the same pnpm publish command in their terminal; it typically succeeds within 2–3 attempts (it took 3 on the 1.1.10-beta.4 release). npm rejects re-publishing an already-published version, which would be a misleading error. A clean success prints + happy@X.Y.Z.

Step 8: Verify

npm view happy@{version} version   # did the version actually publish?
npm view happy dist-tags           # did the channel tag move?

Check npm view happy@X.Y.Z version first — it returns the version string if the publish landed (use this between TLS retries to avoid double-publishing, and to distinguish a real failure from a cosmetic upload error).

⚠️ This metadata check is necessary but NOT sufficient. npm view ... version only confirms the tarball was accepted — it says nothing about what's inside it. A bundle stamped with the wrong version (the --ignore-scripts footgun above) passes this check cleanly. The authoritative check is the bundle itself in Step 11 (happy --version after a real install). Never report a release as done on the metadata check alone.

Then confirm the new version appears under the correct dist-tag. The tag often lags the publish by 10–40s — poll a few times before concluding it failed; npm tag propagation is not instant.

Step 9: Git tag + commit (latest only)

For latest releases only:

  1. Commit the version bump: Release version X.Y.Z
  2. Tag: git tag cli-X.Y.Z
  3. Push: git push && git push --tags

For beta releases: ask the user if they want to commit the version bump or leave it uncommitted.

If git push is rejected because origin/main advanced while releasing, fetch and rebase the release commit before retrying:

git fetch origin main
git rebase --autostash origin/main
git tag -f cli-X.Y.Z
git push && git push --tags

Use --autostash when the worktree is dirty from unrelated local changes so those edits are preserved. Recreate the tag after rebase because the release commit hash changes.

Step 10: GitHub Release (latest only)

For latest releases, create a GitHub release:

gh release create cli-X.Y.Z --generate-notes --title "cli-X.Y.Z"

Step 11: Install + verify locally

npm i -g happy@{channel}
happy --version
happy daemon status

Report the installed version and daemon status. The smoke check must confirm that happy --version matches the published version, not just npm metadata. If it reports the old version, rebuild after the version bump and cut a corrective patch release.


Mobile Release

Package:     packages/happy-app
Variants:    development, preview, production
Platform:    Expo SDK 54 / React Native 0.81.4

Build types

Always ask the user explicitly what they want to release. Present these options in order of popularity:

  1. OTA update (preview) — push JS bundle to preview channel. Most common release type.
  2. OTA update (production) — push JS bundle to production channel. Do this after preview OTA is validated.
  3. Native dev build — when native code changes. Points to dev server with bundled app.
  4. Full native release — build all profiles (dev + preview + production) to prep for a new native release.

OTA Updates

# Preview (most common)
pnpm --filter happy-app run ota

# Production
pnpm --filter happy-app run ota:production

OTA scripts require a message — stdin is not readable from Claude Code, so run the underlying eas update directly with --message:

cd packages/happy-app && APP_ENV=preview NODE_ENV=preview tsx sources/scripts/parseChangelog.ts && pnpm typecheck && eas update --branch preview --message "<message>"

Native Builds

  • Dev build — development profile, used when native code changes (points to dev server)

    cd packages/happy-app && eas build --profile development --platform all --non-interactive
    
  • TestFlight / Play Store builds — use -store profiles for distribution via TestFlight and Play Store. Always pass --auto-submit so the build goes straight to TestFlight after completion.

    # Preview (TestFlight/internal testing)
    cd packages/happy-app && eas build --profile preview-store --platform ios --non-interactive --auto-submit
    
    # Dev (TestFlight, points to dev server)
    cd packages/happy-app && eas build --profile development-store --platform ios --non-interactive --auto-submit
    
    # Production (App Store / Play Store submission)
    cd packages/happy-app && eas build --profile production --platform ios --non-interactive --auto-submit
    

IMPORTANT: Always pass --non-interactive to eas build commands. Without it, EAS prompts for Apple account login interactively which breaks in non-TTY contexts (Claude Code, CI). Remote credentials are already configured on EAS servers.

IMPORTANT: Always pass --auto-submit to -store builds. Without it, the build finishes but never reaches TestFlight — you have to manually submit with eas submit.

EAS Build Profiles

Profile              Distribution   Channel       Notes
development-store    store          development   Dev build via TestFlight
preview-store        store          preview       TestFlight / Play Store internal testing
production           store          production    App Store / Play Store submission

Internal / ad-hoc profiles (rarely used)

These install via direct link, NOT TestFlight. Almost never needed — prefer the -store profiles above.

Profile              Distribution   Channel
development          internal       development
preview              internal       preview

Version source is remote (EAS manages build numbers, auto-incremented). Runtime version "20" — bump when native code changes to invalidate OTA.

App Store Connect

Apple ID:    steve@bulkovo.com
Team ID:     466DQWDR8C

App Store Connect App IDs:
Production:   6748571505  (com.ex3ndr.happy)
Preview:      6749025570  (com.slopus.happy.preview)
Development:  6748984254  (com.slopus.happy.dev)

Web Release

Package:     packages/happy-app (same Expo app, web export)
Dockerfile:  Dockerfile.webapp
Image:       docker.korshakov.com/happy-app:{version}
K8s:         packages/happy-app/deploy/happy-app.yaml (3 replicas)

Web releases go through TeamCity (Lab_HappyWeb). The config is in the TeamCity UI, not in the repo.

Flow: expo export --platform web -> nginx:alpine static serve -> Docker build -> push -> K8s deploy.

Build args: POSTHOG_API_KEY, REVENUE_CAT_STRIPE.

Guide the user to trigger the TeamCity build, or help with manual Docker builds if needed.


Server Release

Package:     packages/happy-server
Dockerfile:  Dockerfile.server (production), Dockerfile (standalone w/ PGlite)
Image:       docker.korshakov.com/handy-server:{version}
K8s:         packages/happy-server/deploy/handy.yaml (1 replica, port 3005)

Server releases go through TeamCity (Lab_HappyServer). The config is in the TeamCity UI, not in the repo.

Build: node:20 + python3 + ffmpeg, builds happy-wire + happy-server. Secrets from Vault: handy-db, handy-master, handy-github, handy-files, handy-e2b, handy-revenuecat, handy-elevenlabs. Redis: happy-redis StatefulSet (redis:7-alpine, 1Gi persistent volume).

Guide the user to trigger the TeamCity build.


Docs Release

Site:    happy.engineering (GitHub Pages)
Repo:    github.com/slopus/slopus.github.io

Separate repo, not part of this monorepo. Guide the user to push to that repo.


Writing release notes (the in-app changelog)

CHANGELOG.md is regenerated into changelog.json and shown inside the mobile app, on a phone, right after an OTA update. Write for that reader.

  1. Investigate before writing — use subagents (Opus). Don't infer from commit titles. Spawn parallel subagents to read the actual code + git history of each candidate change and classify it: user-visible UX vs impl detail, default-on vs gated, new vs polish/fix.
  2. Default-off ⇒ exclude. A change behind a setting/experimental flag that defaults to OFF (or whose UI entry point is hidden) is a silent ship — omit it until it's on by default. Same for impl / perf-internal / refactor / type-only changes.
  3. Audience is phone users. Most never touch the CLI or desktop. Be skeptical of CLI-only / desktop-only / web-only / beta-only items — a genuinely strong feature can still be wrong for this venue; announce those in CLI release notes / docs / GitHub instead.
  4. Ask, don't assume. When announce-vs-silent-ship, default state, or scope is unclear, ask the owner and confirm the final include/exclude list before writing. Never headline-announce on your own judgment.
  5. Voice: benefit-first, terse, em-dash, one line per item, grouped as a dated themed entry like existing ones. Edit CHANGELOG.md only, then regenerate via tsx packages/happy-app/sources/scripts/parseChangelog.ts.
  6. Community Credits bullet. End each entry with a single bullet crediting community contributors whose commits ship in it: - Community Credits: [@user1](https://github.com/user1), [@user2](https://github.com/user2). Core team never appears there — Kirill (bra1ndump / kirill2003de@gmail.com), Scoteezy, and Steve (ex3ndr). To find contributors: get the previous OTA's commit via eas update:list --branch preview + eas update:view <group-id> --json (gitCommitHash), then git log <hash>..HEAD and keep non-core authors. GitHub handles come from the PR (gh pr view <n> --json author), not from the commit email. If an OTA shipped without a changelog entry, its uncredited community commits roll into the next entry's credits.

Rules

  • Release notes: investigate with subagents, exclude default-off, ask when unsure — see "Writing release notes" above.
  • Always present options — never assume which component, channel, or version.
  • Always verify before publishing — show the user what will be published and get confirmation.
  • The maintainer runs npm login and pnpm publish interactively — the agent prepares and verifies the release but never runs the publish command or handles npm credentials/OTP.
  • Do not bundle self-host server/webapp into happy — self-host runtime and the bundled webapp ship through happy-server-self-host, not the main CLI package.
  • Unit tests are the gate, not integration tests — integration tests are slow and have flaky abort/interrupt tests.
  • Use pnpm publish, not npm publish — avoids workspace protocol issues.
  • Never use --ignore-scripts for package publishing — prepublish scripts are the last guard before npm receives the tarball.
  • Never force-push tags — if a tag exists, stop and ask.

Version History

  • b824cd0 Current 2026-08-28 22:31

    文档更新:移交npm认证相关说明

  • eb980a5 2026-08-20 11:11

Same Skill Collection

.agents/skills/agent-browser/SKILL.md
.agents/skills/control-flow/SKILL.md
.agents/skills/dev/SKILL.md
.agents/skills/maintain/SKILL.md
.agents/skills/metrics-graphana/SKILL.md
.agents/skills/sessions/SKILL.md
.agents/skills/terminal-emulator/SKILL.md
.claude/skills/agent-browser/SKILL.md
.claude/skills/terminal-emulator/SKILL.md
.agents/skills/office-hours/SKILL.md

Metadata

Files
0
Version
b824cd0
Hash
cf49a9b7
Indexed
2026-08-20 11:11

- 위키
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-12 22:36
浙ICP备14020137号-1 $방문자$