Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Deploy

ASecurity

Deploy skill for knit-statusline. Cut a release by tagging main; GoReleaser publishes the GitHub Release and the npm packages. Triggers on `/deploy` or any release/version bump/tag request.

2 stars
0 votes
0 copies
1 views
Added 9/19/2026
developmentrustgobashnodegitapi

Works with

cliapi

Security Analysis

A96/100
mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned 9/19/2026

$npx -y skills add devemberx/knit-statusline --skill deploy --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Deploy?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Deploy
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/devemberx-deploy/badge)](https://www.skillsdirectory.com/skills/devemberx-deploy)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: deploy
description: Deploy skill for knit-statusline. Cut a release by tagging main; GoReleaser publishes the GitHub Release and the npm packages. Triggers on `/deploy` or any release/version bump/tag request.
---

# Deploy Skill

Orchestrate production release. Be deliberate. Never skip hooks. Never push a tag without explicit user confirm.

**The tag is the version.** Nothing in the tree records it: the binary takes it from
`ldflags -X main.version={{.Version}}`, and `npm/scripts/prepare-packages.mjs` stamps it
into every `package.json` at build time (they sit at `0.0.0` in git on purpose). So a
release is *one* annotated tag — no bump commit, nothing to push to `main`.

**Nothing publishes without a human approval.** `publish.yml` runs in the `release`
GitHub environment. A tag push starts the run, then it waits for a required reviewer.
No npm credential exists in the repository or its secrets: the workflow authenticates over
OIDC against a trusted publisher configured per package on npmjs.com.

## Step 1 — Pre-flight checks (abort on any failure)

```bash
git status --porcelain                  # MUST be empty
git rev-parse --abbrev-ref HEAD         # MUST be 'main'
git fetch origin main --tags            # --tags so Step 2 check see remote tags
git rev-list --count HEAD..origin/main  # MUST be 0
git rev-list --count origin/main..HEAD  # MUST be 0
git config core.hooksPath               # SHOULD be '.githooks'

# --workflow not optional: CodeQL default setup also record runs against main, so
# unfiltered --limit 1 hand back its green run while ci itself red.
gh run list --branch main --workflow ci.yml --limit 1 \
  --json status,conclusion,headSha    # status 'completed', conclusion 'success'

# Environment existing prove nothing: zero protection rules = no gate.
gh api repos/:owner/:repo/environments/release \
  -q '.protection_rules[].type'       # MUST contain 'required_reviewers'

# Trusted publishing is per package, and npm attach a publisher only to a package
# already on the registry. Platform dir added since the last release is unknown
# there. Unauthenticated on purpose: pre-flight run logged out.
for dir in npm/platforms/*/ npm/knit-statusline; do
  pkg=$(node -p \
    "JSON.parse(require('node:fs').readFileSync('${dir%/}/package.json','utf8')).name")
  npm view "$pkg" name >/dev/null 2>&1 || echo "NEVER PUBLISHED: $pkg"
done                                  # MUST print nothing
```

- Dirty tree → list offending files, stop. Do NOT auto-stash.
- Branch != main → ask user to switch manually. Do NOT switch.
- Behind origin → tell user to run `git pull --ff-only`.
- Ahead of origin → tell user to open a PR for those commits. `publish.yml` refuses a tag whose
  commit is not reachable from `origin/main`, so catching it here beats failing after the tag is
  public.
- `core.hooksPath` ≠ `.githooks` → WARN, ask user to run `git config core.hooksPath .githooks`.
  Nothing in this skill commits, so it does not gate *this* run — restore it anyway as the
  repo-wide net for every later commit.
- Latest `ci` run on main not green → stop. GoReleaser reruns `go test ./...` in its
  `before.hooks`, so a broken main fails the release *after* the tag is public. A `status` other
  than `completed` (queued, in_progress) is not green either — wait for it, do not tag mid-run.
  Empty result means ci never ran for that commit: stop and say so rather than reading it as a
  pass.
- Run's `headSha` ≠ the commit about to be tagged (`git rev-parse HEAD`) → stop. A green run from
  an older commit says nothing about the one shipping.
- No `release` environment (the `gh api` call 404s) **or** `protection_rules` without
  `required_reviewers` → stop, either way. `publish.yml` names the environment, so GitHub creates
  it on first run with no protection rules at all, and an environment with no required reviewer
  approves itself — the tag would publish seven packages to npm with no human in the loop. Fix it
  at Settings → Environments → `release` → Required reviewers, then re-run the check.
- Any `NEVER PUBLISHED:` line → stop, bootstrap those packages (below), then re-run the check.
  `publish.yml` walks `npm/platforms/*/` in glob order and the launcher last, so the first
  unpublished name kills the run *there* — every package sorting after it never publishes, and
  the release stays a draft. The versions that did publish are burned: npm rejects reusing a
  version forever, `npm unpublish` included. Recovery is bootstrap-then-rerun and the skip logic
  makes that work, but it spends a second approval cycle on a tag that is already public.

### First release only (no tags yet)

The npm side has to be bootstrapped once by hand. Trusted publishing is configured **per
package** and npm requires the package to already exist on the registry, so a package that has
never been published cannot have a trusted publisher attached yet.

Confirm with the user that every one of them exists and is configured:

```bash
# Names read from the tree: a platform package added later is picked up with no
# edit here. `npm trust list` need `npm login` first -- logged out it fail on
# auth, which read like an unconfigured package.
for dir in npm/platforms/*/ npm/knit-statusline; do
  pkg=$(node -p \
    "JSON.parse(require('node:fs').readFileSync('${dir%/}/package.json','utf8')).name")
  printf '%s ' "$pkg"; npm trust list "$pkg"
done
```

One package per directory under `npm/platforms/`, plus the launcher `npm/knit-statusline` —
today that is seven: `@devemberx/knit-statusline` plus `@devemberx/knit-statusline-<target>` for
`darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-x64`, `win32-arm64` and `win32-x64`. Each
must point at repository `devemberx/knit-statusline`, workflow file `publish.yml`, environment
`release`.

Unconfigured → stop, and give the user the bootstrap procedure. `npm trust` attaches one
publisher to one package, so it runs once per package — seven times:

```bash
npm install -g npm@11                # npm trust need 11.15.0+; 11.18 run on node 22.9+
npm login                            # web auth + 2FA, token into ~/.npmrc
for dir in npm/platforms/*/ npm/knit-statusline; do
  npm publish "./${dir%/}" --access public --tag bootstrap
done
for dir in npm/platforms/*/ npm/knit-statusline; do
  pkg=$(node -p \
    "JSON.parse(require('node:fs').readFileSync('${dir%/}/package.json','utf8')).name")
  npm trust github "$pkg" --repository devemberx/knit-statusline \
    --file publish.yml --environment release --allow-publish --yes
  sleep 2                            # endpoint rate-limit
done
npm logout                           # local token dead weight, release run over OIDC
```

The workflow flag is `--file`, not `--workflow`.

`npm@11` not `npm@12` on purpose. Both carry `npm trust`, but npm 12 demands node
`^22.22.2 || ^24.15.0 || >=26.0.0` and fails `EBADENGINE` on an earlier node — a node upgrade
the operator does not need for a one-off bootstrap. `publish.yml` runs npm 12 because
`setup-node` gives it the latest 24.x.

The whole loop has to finish inside the five-minute window `npm login` opens before 2FA is asked
again.

Then verify the placeholders did not claim `latest`:

```bash
# --tag bootstrap unproven here: npm reported to set dist-tags.latest on a
# package's first publish whatever --tag say.
for dir in npm/platforms/*/ npm/knit-statusline; do
  pkg=$(node -p \
    "JSON.parse(require('node:fs').readFileSync('${dir%/}/package.json','utf8')).name")
  printf '%s ' "$pkg"; npm view "$pkg" dist-tags   # 'latest' MUST be absent
done
```

`latest` present → it points at the `0.0.0` placeholder, so `npm install
@devemberx/knit-statusline` serves a broken package until the first real release. Either drop it
with `npm dist-tag rm <pkg> latest` (once per affected package), or leave it knowingly — the
first real release publishes over it. If npm refuses to remove `latest`, take the second path and
tell the user the broken-install window is open until the release lands.

Skip the bootstrap and GoReleaser still cuts the GitHub Release, then every `npm publish` fails
on auth — a released tag with no packages behind it.

### A platform package added since the last release

Pre-flight printed `NEVER PUBLISHED:` for a package the tree carries and the registry does not —
a new target shipped between releases. Same problem as a first release, one package wide:
trusted publishing attaches per package, and only to a name npm already knows.

Bootstrap the missing ones alone. Do **not** rerun the publish loop from the first-release
section: every other package already holds `0.0.0` on the registry, and npm rejects a duplicate
version permanently. Run from a clean checkout of `main`, after the branch adding the package has
merged.

```bash
npm install -g npm@11                # same reason as above: npm 12 need a newer node
npm login
PKG_DIR=npm/platforms/<target>       # one run per missing package
PKG=$(node -p \
  "JSON.parse(require('node:fs').readFileSync('$PKG_DIR/package.json','utf8')).name")
npm publish "./$PKG_DIR" --access public --tag bootstrap
npm trust github "$PKG" --repository devemberx/knit-statusline \
  --file publish.yml --environment release --allow-publish --yes
npm view "$PKG" dist-tags            # 'latest' MUST be absent
npm logout
```

`latest` present → `npm dist-tag rm "$PKG" latest`, as in the first-release case. It points at
the `0.0.0` placeholder, so anyone following the "install directly" hint `bin/cli.js` prints for
a missing optional dependency installs a package with no binary in it until the next release.

Then re-run the Step 1 pre-flight loop and confirm it prints nothing.

## Step 2 — Determine new version (interactive)

```bash
PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
[ -n "$PREV_TAG" ] && git log "$PREV_TAG..HEAD" --oneline || git log --oneline
```

No `$PREV_TAG` → first release. Recommend **0.1.0** and say so; do not derive a bump.

Otherwise analyze commits since `$PREV_TAG`:

- `breaking` / `!:` marker → recommend **major**
- any `feat:` → recommend **minor**
- only `fix:` / `chore:` / `docs:` → recommend **patch**

Call **AskUserQuestion** with options `patch` / `minor` / `major` / `custom`, note recommendation
in question text. For `custom`, prompt exact `X.Y.Z`. Pre-1.0 the recommendation is advisory —
a `feat:` under `0.x` may still ship as a patch; let the user decide.

A prerelease version (`0.2.0-rc.1`) publishes under the npm `next` dist-tag, not `latest`, and
GoReleaser marks the GitHub Release as a prerelease. Say so if the user picks one.

Verify tag does not already exist (Step 1's `--tags` fetch catches remote tags too, not just local):

```bash
# -q --verify keep missing tag quiet; bare `git rev-parse v1.2.3` echo argument
# back and still exit non-zero, which read like a hit.
if git rev-parse -q --verify "refs/tags/v${NEW_VERSION}" >/dev/null; then
  echo "tag v${NEW_VERSION} already exists"
  exit 1
fi
```

If exists, abort, tell user pick different version. Remote tag means version already shipped
(or mid-publish) — never reuse; npm rejects republishing a version even after `npm unpublish`.
Local-only tag likely stale leftover from aborted prior run; user can delete manually
(`git tag -d v${NEW_VERSION}`) after confirming it never reached origin.

## Step 3 — Create annotated tag with curated highlights (interactive)

The tag message carries this release's curated highlights — in the tag itself, not a committed
file, so the repo never accumulates per-release notes. Line 1 is the dated marker; the rest is a
bullet list GoReleaser reads back as `{{ .TagBody }}` and renders under a `## Highlights` heading
(`release.header` in `.goreleaser.yaml`), above the change list it generates from `changelog:`.
Never hand-write that change list into the tag.

From commits since `$PREV_TAG`, draft curated summary of what *matters* to user — few
plain-language bullets (or short prose), not restatement of PR titles. Show via
AskUserQuestion (`approve` / `edit` / `skip`). English only.

On `skip`, create date-only tag (release ships the generated change list alone):

```bash
RELEASE_DATE=$(date -u +%F)  # -u so marker match UTC date GitHub stamp
git tag -a "v${NEW_VERSION}" -m "Release v${NEW_VERSION} (${RELEASE_DATE})"
```

Otherwise the message is the bullet list alone — no `## Highlights` heading in the tag,
`release.header` adds it. Create tag with `-F`:

```bash
RELEASE_DATE=$(date -u +%F)
TAG_MSG=$(mktemp)
cat > "$TAG_MSG" <<EOF
Release v${NEW_VERSION} (${RELEASE_DATE})

- <plain-language summary of a major change>
- <another>
EOF
git tag -a "v${NEW_VERSION}" -F "$TAG_MSG"
rm -f "$TAG_MSG"
```

Verify either way, and confirm the tag is annotated (`object` type `tag`, not `commit`):

```bash
git cat-file -t "v${NEW_VERSION}"   # MUST print 'tag'
git show "v${NEW_VERSION}" --no-patch
```

## Step 4 — Push tag (confirm, with irreversibility warning)

AskUserQuestion with this exact warning text: **"Pushing tag v${NEW_VERSION} starts
`.github/workflows/publish.yml`, which waits for approval in the `release` environment. Once
approved: GoReleaser (build + draft GitHub Release) → npm publish of the platform packages,
then the launcher → the release goes public. The npm step is IRREVERSIBLE (npm blocks reusing a
published version). Continue?"** Options: `push` / `cancel`.

```bash
git push origin "v${NEW_VERSION}"
```

On failure: local tag still exists; user can retry with `git push origin v${NEW_VERSION}`
or delete locally via `git tag -d v${NEW_VERSION}`.

On `cancel`: nothing shipped and nothing to clean up — the tag exists only locally. Finish
later by pushing it, or delete it and re-run.

GitHub Release created by GoReleaser inside `publish.yml`. Do NOT create one by hand;
a manual `gh release create` collides with GoReleaser's own (`release already exists`).

## Step 5 — Post-push summary

```bash
OWNER_REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
echo "Tagged v${NEW_VERSION}"
echo "Approve the run: https://github.com/${OWNER_REPO}/actions/workflows/publish.yml"
echo "Release notes:   https://github.com/${OWNER_REPO}/releases/tag/v${NEW_VERSION}"
```

Tell the user two things:

- **The run is waiting on them.** Nothing is built or published until the `release` environment
  approval goes through.
- The launcher is published last on purpose (it pins the platform packages exactly), so npm
  resolves the new version only once the whole job finishes.

## Recovering a partial publish

The job publishes seven packages in sequence. If it dies partway — network, a revoked trusted
publisher, an npm outage — some are on the registry and some are not, and the GitHub Release is
still a draft.

Do not cut a new version to escape this. The run is designed to be repeated:

```bash
# 1. See how far run got. Same order publish.yml walk: platform dirs by glob,
#    launcher last, so the first "(absent)" mark where it died.
for dir in npm/platforms/*/ npm/knit-statusline; do
  pkg=$(node -p \
    "JSON.parse(require('node:fs').readFileSync('${dir%/}/package.json','utf8')).name")
  printf '%s ' "$pkg"; npm view "${pkg}@${NEW_VERSION}" version 2>/dev/null || echo "(absent)"
done

# 2. Rerun. replace_existing_draft in .goreleaser.yaml drop stale draft, and
#    publish step skip every package already on registry.
gh run rerun <run-id> --failed
```

Do not delete the draft by hand first. `replace_existing_draft` owns that, and a manual
`gh release delete` on a tag with several stacked drafts removes whichever one GitHub
answers with.

The release only flips public after all seven succeed, so a half-finished run never fronts
packages that do not exist.

## Hard rules

- ALWAYS `git tag -a` (or `-F`). NEVER a lightweight tag — GoReleaser's `.TagBody` falls back
  to the *commit* message body, which would publish that commit's bullets as `## Highlights`.
- NEVER hand-write the categorized change list into the tag. The tag body holds the dated marker
  plus curated bullets, nothing else.
- NEVER hand-edit a `version` in `npm/**/package.json` — `prepare-packages.mjs` stamps every one
  of them from the tag, including the launcher's `optionalDependencies`, which must match exactly.
- NEVER `--no-verify`, `git push --force`, or a force-moved tag. A pushed tag has published
  artifacts and npm versions hanging off it; move it and the two disagree forever.
- NEVER create an npm token to work around a failing publish. The pipeline is tokenless by
  design; a broken trusted publisher is fixed at npmjs.com.
- NEVER auto-retry a failed push.
- NEVER run `gh release create` by hand during deploy — GoReleaser owns it.
- ALWAYS confirm before the tag push.
- ALWAYS show the tag draft to user before creating it.

Attribution

devemberxdevemberx
View sourceSee grades on GitHubMore from devemberx →
SSkills Directory ProSkills Directory

Get any skill into Claude in one click.

Download any skill as a ZIP for Claude.ai, Claude Desktop, or .claude/skills. $9/mo.

See Pro

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills Directory ProSkills Directory

Get any skill into Claude in one click.

Download any skill as a ZIP for Claude.ai, Claude Desktop, or .claude/skills. $9/mo.

See Pro

Related Skills

Clean Code

Pragmatic coding standards - concise, direct, no over-engineering, no unnecessary comments

304955 votes

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

285172 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2222 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

10341 votes
View all in development →