releasing-to-pypi — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited releasing-to-pypi (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A release to PyPI in this project requires all steps completed atomically before pushing. The publish workflow triggers on GitHub Release creation and checks out the tag's commit — if that commit has wrong versions, stale lint, or missing changelog, the release fails and cannot be re-uploaded to PyPI (immutable filenames).
Core principle: The tag must point to a commit where every file is release-ready. Work backwards from that constraint.
vMANDATORY: Execute every step in order. Do not skip. Do not reorder.
ruff check protocol/ core/ adapters/If lint fails, fix it. Never commit code that fails lint — fixing lint after commit requires either amend (breaks protected branches) or an extra commit (splits changes, tag placement becomes error-prone).
# Protocol tests
pytest protocol/tests/ --timeout=30
# Core tests (excluding on-chain)
pytest core/tests/ --timeout=30 -m "not devnet and not localnet and not mainnet"
# Adapter tests
cd adapters/mcp && pytest tests/ --timeout=30 && cd ../..
cd adapters/client_mcp && pytest tests/ --timeout=30 && cd ../..All tests must pass. Do not commit with known failures.
Determine the next version. Check existing tags:
git tag -l 'v*' --sort=-v:refname | head -5Update all 10 files in a single commit:
| # | File | Field | Notes |
|---|---|---|---|
| 1 | protocol/pyproject.toml | version = "X.Y.Z" | |
| 2 | protocol/open402/__init__.py | __version__ = "X.Y.Z" | ⚠️ Easy to forget |
| 3 | core/pyproject.toml | version = "X.Y.Z" | |
| 4 | core/ag402_core/__init__.py | __version__ = "X.Y.Z" | ⚠️ Easy to forget |
| 5 | adapters/mcp/pyproject.toml | version = "X.Y.Z" | |
| 6 | adapters/mcp/ag402_mcp/__init__.py | __version__ = "X.Y.Z" | ⚠️ Easy to forget |
| 7 | adapters/client_mcp/pyproject.toml | version = "X.Y.Z" | |
| 8 | adapters/client_mcp/ag402_client_mcp/__init__.py | __version__ = "X.Y.Z" | ⚠️ Easy to forget |
| 9 | adapters/client_mcp/tests/test_server.py | assert __version__ == "X.Y.Z" | ⚠️ Test will fail in CI if missed |
| 10 | CHANGELOG.md | Add ## [X.Y.Z] section at top |
Verification command — run this after editing to catch misses:
grep -r '"0\.1\.' protocol/pyproject.toml protocol/open402/__init__.py \
core/pyproject.toml core/ag402_core/__init__.py \
adapters/mcp/pyproject.toml adapters/mcp/ag402_mcp/__init__.py \
adapters/client_mcp/pyproject.toml adapters/client_mcp/ag402_client_mcp/__init__.py \
adapters/client_mcp/tests/test_server.pyAll lines must show the same version. If any differ → STOP and fix.
Never split version bump and code changes into separate commits before tagging. The tag must land on a commit that has both the code changes AND the correct version numbers.
Check and update test counts / feature descriptions in:
| File | What to check |
|---|---|
README.md | Test count badge, config env var table, feature descriptions |
SECURITY.md | Test totals, security feature list |
CHANGELOG.md | New version section with correct date and changes |
CRITICAL: Never commit internal planning docs or secrets.
# Verify .gitignore excludes internal docs
grep '开源发布行动计划' .gitignore # Must be present
# Verify no secrets in staged files
git diff --cached --name-only | xargs grep -l 'PRIVATE_KEY\|ghp_\|sk-' 2>/dev/null
# Must return emptygit tag -a vX.Y.Z -m "vX.Y.Z: short description"The tag MUST point to the commit containing the version bump. Verify:
git log --oneline -1 vX.Y.Z
# Must show the version bump commit, NOT an earlier commitgit push -u origin main && git push origin vX.Y.ZThis is the step that actually publishes to PyPI. The publish.yml workflow triggers on release: types: [published], NOT on tag push. Pushing a tag alone does NOT publish to PyPI.
gh release create vX.Y.Z --title "vX.Y.Z — Short description" --notes "$(cat <<'EOF'
## What's Changed
- Feature 1
- Feature 2
- ...
**Full Changelog**: https://github.com/AetherCore-Dev/ag402/blob/main/CHANGELOG.md
EOF
)"After creating the release, verify the workflow triggered:
gh run list --workflow=publish.yml --limit=1| Mistake | Consequence | Prevention |
|---|---|---|
| Commit code, then fix lint separately | Extra commit; tag placed on wrong commit | Step 1: lint before commit |
Forget __init__.py versions | __version__ mismatches pyproject.toml; import shows wrong version; test fails in CI | Step 3: all 10 files + verification command |
| Forget test assertion version | test_server.py::test_package_version fails in CI → release blocked | Step 3: file #9 in the checklist |
| Tag on code commit, version bump in later commit | CI checks out tag → builds old version → PyPI rejects duplicate filename | Step 3+6: version bump + tag on same commit |
Push tag but skip gh release create | PyPI publish never triggers — publish.yml fires on release: [published], not on tag push | Step 8: always create GitHub Release |
| Commit internal docs (开源发布行动计划.md) | Internal planning docs leak to public repo | Step 5: verify .gitignore + check staged files |
| Forget CHANGELOG.md | Release notes incomplete | Step 3: CHANGELOG in same commit |
| Forget to update README/SECURITY.md | Stale test counts, missing env vars | Step 4: documentation checklist |
git push --force on protected branch | Push rejected; requires extra fixup commits | Never amend pushed commits; get it right first time |
| Tag points to wrong commit | CI builds wrong version; PyPI upload fails with "File already exists" | Step 6: verify tag target |
git commit but haven't run ruff check → STOPgit tag but version bump is in a different commit → STOPgrep verification shows mixed version numbers → STOPgit push --force on main → STOP (protected branch)gh release create but git log --oneline -1 vX.Y.Z doesn't show version bump → STOPgit commit --amend on an already-pushed commit → STOPgit diff --cached shows 开源发布行动计划.md or other internal files → STOPgh release create → STOP (PyPI won't publish)# 1. Lint
ruff check protocol/ core/ adapters/
# 2. Test (all 4 packages)
pytest protocol/tests/ --timeout=30
pytest core/tests/ --timeout=30 -m "not devnet and not localnet and not mainnet"
(cd adapters/mcp && pytest tests/ --timeout=30)
(cd adapters/client_mcp && pytest tests/ --timeout=30)
# 3. Bump versions (all 10 files) + update docs + update CHANGELOG
# Then verify:
grep -r '"X\.Y\.Z"' protocol/pyproject.toml protocol/open402/__init__.py \
core/pyproject.toml core/ag402_core/__init__.py \
adapters/mcp/pyproject.toml adapters/mcp/ag402_mcp/__init__.py \
adapters/client_mcp/pyproject.toml adapters/client_mcp/ag402_client_mcp/__init__.py \
adapters/client_mcp/tests/test_server.py
# 4. Check no internal files staged
git diff --cached --name-only # Should NOT include 开源发布行动计划.md
# 5. Commit
git add <specific files> && git commit -m "chore: release vX.Y.Z — description"
# 6. Tag the version-bumped commit
git tag -a vX.Y.Z -m "vX.Y.Z: description"
# 7. Push together
git push -u origin main && git push origin vX.Y.Z
# 8. Create GitHub Release (THIS triggers PyPI publish)
gh release create vX.Y.Z --title "vX.Y.Z — Description" --notes "..."
# 9. Verify publish workflow triggered
gh run list --workflow=publish.yml --limit=1When pushing to GitHub, use the authenticated HTTPS URL:
git remote set-url origin https://<PAT>@github.com/AetherCore-Dev/ag402.git
git config user.name "AetherCoreDev"
git config user.email "[email protected]"~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.