Claude Skill

release

Prepare, validate, tag, or publish a Labtasker release. Use for version bumps, release readiness, release artifacts, tags, GitHub releases, or PyPI publication; do not use for ordinary development builds.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download luocfprime-labtasker-.agents_skills_release-19f9599.zip · 5 KB
Part of luocfprime/labtasker — 5 skills

Install

skills CLI npx skills add https://github.com/luocfprime/labtasker/tree/main/.agents/skills/release
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install luocfprime-labtasker@llmmart
Git git clone https://github.com/luocfprime/labtasker.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole luocfprime/labtasker collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Release Labtasker

Treat the Client and Server as independently installed distributions that always ship with one shared version. Separate release preparation from externally visible publication.

Establish scope and authority

Determine the requested target version and whether the user asked only to prepare, or explicitly authorized tagging, pushing, creating a GitHub release, or publishing to PyPI. Do not infer authorization for later stages.

Inspect the current branch, tags, remotes, git status, and all pending diffs. Do not include unrelated worktree changes in release commits or artifacts. Never reuse the v1 worktree's release scripts or workflow for v2.

Prepare the version

Use the deterministic helper from the repository root:

uv run python .agents/skills/release/scripts/set_version.py VERSION
uv lock
uv run python .agents/skills/release/scripts/set_version.py --check VERSION

The helper updates the workspace version, both distribution metadata files, both public __version__ values, and the Claude Code plugin/marketplace version. It maps a PEP 440 prerelease such as 2.1.0rc1 to the corresponding plugin SemVer 2.1.0-rc.1. uv lock owns lockfile updates. Review the resulting diff and update user documentation only when the release changes documented behavior. Do not create a version-only changelog file when the repository has no maintained changelog.

Validate release readiness

Run the complete ordinary gate from AGENTS.md. Build artifacts only from the exact reviewed source tree. Then smoke-test the release wheels in clean virtual environments, confirming that:

  • labtasker-client imports labtasker and provides its CLI without Server dependencies;
  • labtasker-server imports and provides its CLI without the Client package;
  • the labtasker convenience metapackage installs matching Client and Server distributions and both CLIs;
  • installed metadata and __version__ equal the target version; and
  • all three source distributions and all three wheels are present.

For a fully release-gated Linux release, also require the distributed integration suite for the exact commit, either locally with PyTorch and Accelerate installed or through the repository's distributed CI. Report that gate as pending rather than pretending it passed when it cannot be verified.

Write consistent release notes

Summarize user-visible changes since the immediately previous published release. Do not describe internal refactors, tests or CI maintenance as product changes, and do not promise compatibility that the specification does not provide. Use short imperative-present bullets such as Add, Change and Fix, name the affected Client, Server, CLI or Worker when that distinction matters, and combine closely related commits into one outcome. End every bullet with a period and order bullets by user impact, with compatibility and behavior changes before additions and fixes. Use vVERSION as the GitHub Release title.

Use this shape for every GitHub Release. The compatibility notice is conditional, and the comparison line is omitted only for the repository's first release:

> [!IMPORTANT]
> This release changes Client-Server compatibility. Upgrade the Server to
> vVERSION first, then upgrade all Clients and Workers to vVERSION. Earlier
> Clients cannot [...exact consequence...].

## Changes

- Add ...
- Change ...
- Fix ...

**Full Changelog**: [vPREVIOUS...vVERSION](https://github.com/luocfprime/labtasker/compare/vPREVIOUS...vVERSION)

Use a GitHub [!IMPORTANT] admonition only when users need to take action or could otherwise encounter a material compatibility, migration, or safety risk. Release size is not the criterion. Do not add one merely because a compatible release changes Server code, adds a feature, or expands a supported range. Changes to Client-Server interoperability, supported version combinations, API or persisted database compatibility warrant an admonition only when their practical consequence deserves prominent attention. State the exact consequence and action instead of writing only “compatibility changes.” Cover, when applicable:

  • whether upgrading both Client and Server is required or only recommended;
  • which mixed-version combinations remain supported;
  • the required upgrade order;
  • whether database migration is automatic or requires a manual step; and
  • what fails or becomes unavailable if versions are mixed.

Use required only when mixed versions are unsupported or unsafe. If the current and previous v2 Clients remain supported by the new Server, say that explicitly and use recommended for a same-version upgrade. Do not add an admonition when there is no compatibility or upgrade information worth calling out; repeated empty warnings make real warnings less visible.

When a compatible release has a material mixed-version limitation worth highlighting, use this form rather than implying that a coordinated upgrade is mandatory:

> [!IMPORTANT]
> This release includes Server changes. Upgrading the Client and Server together
> is recommended. Existing v2 Clients remain compatible.

For an incompatible release, use required, give the supported versions and upgrade order, and state any migration behavior in the same notice. Do not use vague notices such as “Server changes” without telling the reader what to do.

Keep the Full Changelog comparison as the final line even when GitHub generated it automatically. Compare the immediately previous published release tag with the new tag, including across a major-version boundary, and use the visible vPREVIOUS...vVERSION label. Do not replace the curated summary with the commit comparison and do not add a comparison link when no previous release exists. Keep the single ## Changes heading for ordinary releases rather than switching between Highlights, What's Changed, Bug fixes and generated commit groups.

Publish only with explicit authorization

Immediately before any tag, push, GitHub Release, or PyPI operation, recheck the exact commit, clean-worktree state, version alignment, artifact contents, and authorization. Use an annotated vVERSION tag unless the repository establishes a different convention.

The reviewed .github/workflows/release.yml workflow publishes all three distributions when a GitHub Release is published. It requires a vVERSION tag, the ordinary and real distributed gates for the exact commit, clean-wheel smoke tests, and protected pypi, pypi-client, and pypi-server environments. PyPI must register that workflow with environment pypi-client for labtasker-client, pypi-server for labtasker-server, and pypi for labtasker. Do not add a manual publishing trigger, improvise credentials, or copy the v1 single-package workflow.

Files (labtasker)
  • scripts
    • set_version.py 6.6 KB
      #!/usr/bin/env python3
      from __future__ import annotations
      
      import argparse
      import json
      import re
      import sys
      from pathlib import Path
      
      if sys.version_info >= (3, 11):
          import tomllib
      else:
          import tomli as tomllib
      
      ROOT = Path(__file__).resolve().parents[4]
      VERSION_PATTERN = re.compile(
          r"^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:(?:a|b|rc)(?:0|[1-9]\d*))?$"
      )
      PYPROJECTS = (
          ROOT / "pyproject.toml",
          ROOT / "packages/labtasker/pyproject.toml",
          ROOT / "packages/labtasker-client/pyproject.toml",
          ROOT / "packages/labtasker-server/pyproject.toml",
      )
      INIT_FILES = (
          ROOT / "packages/labtasker-client/src/labtasker/__init__.py",
          ROOT / "packages/labtasker-server/src/labtasker_server/__init__.py",
      )
      PLUGIN_FILES = (
          ROOT / ".claude-plugin/plugin.json",
          ROOT / ".claude-plugin/marketplace.json",
      )
      LOCK_NAMES = {
          "labtasker-workspace",
          "labtasker",
          "labtasker-client",
          "labtasker-server",
      }
      
      
      def parse_args() -> argparse.Namespace:
          parser = argparse.ArgumentParser(description="Synchronize the Labtasker workspace version.")
          parser.add_argument("version", help="PEP 440 release such as 2.1.0 or 2.1.0rc1")
          parser.add_argument(
              "--check",
              action="store_true",
              help="Verify source metadata and uv.lock without changing files.",
          )
          return parser.parse_args()
      
      
      def project_version(path: Path) -> str:
          return str(tomllib.loads(path.read_text(encoding="utf-8"))["project"]["version"])
      
      
      def init_version(path: Path) -> str:
          match = re.search(r'^__version__ = "([^"]+)"$', path.read_text(encoding="utf-8"), re.M)
          if match is None:
              raise SystemExit(f"missing __version__ assignment: {path.relative_to(ROOT)}")
          return match.group(1)
      
      
      def plugin_version(version: str) -> str:
          match = re.fullmatch(r"(\d+\.\d+\.\d+)(a|b|rc)(\d+)", version)
          if match is None:
              return version
          phase = {"a": "alpha", "b": "beta", "rc": "rc"}[match.group(2)]
          return f"{match.group(1)}-{phase}.{match.group(3)}"
      
      
      def manifest_version(path: Path) -> str:
          document = json.loads(path.read_text(encoding="utf-8"))
          if path.name == "marketplace.json":
              return str(document["plugins"][0]["version"])
          return str(document["version"])
      
      
      def replace_project_version(path: Path, version: str) -> None:
          text = path.read_text(encoding="utf-8")
          updated, count = re.subn(
              r'(?m)^(\[project\]\nname = "[^"]+"\nversion = ")[^"]+("$)',
              rf"\g<1>{version}\g<2>",
              text,
              count=1,
          )
          if count != 1:
              raise SystemExit(f"could not update project version: {path.relative_to(ROOT)}")
          path.write_text(updated, encoding="utf-8")
      
      
      def metapackage_dependency_versions() -> set[str]:
          document = tomllib.loads(
              (ROOT / "packages/labtasker/pyproject.toml").read_text(encoding="utf-8")
          )
          return {
              str(dependency).split("==", 1)[1]
              for dependency in document["project"]["dependencies"]
              if str(dependency).startswith(("labtasker-client==", "labtasker-server=="))
          }
      
      
      def replace_metapackage_dependency_versions(version: str) -> None:
          path = ROOT / "packages/labtasker/pyproject.toml"
          text = path.read_text(encoding="utf-8")
          updated, count = re.subn(
              r'(?m)^(  "labtasker-(?:client|server)==)[^"]+(",)$',
              rf"\g<1>{version}\g<2>",
              text,
          )
          if count != 2:
              raise SystemExit("could not update metapackage dependency versions")
          path.write_text(updated, encoding="utf-8")
      
      
      def replace_init_version(path: Path, version: str) -> None:
          text = path.read_text(encoding="utf-8")
          updated, count = re.subn(
              r'(?m)^__version__ = "[^"]+"$',
              f'__version__ = "{version}"',
              text,
              count=1,
          )
          if count != 1:
              raise SystemExit(f"could not update __version__: {path.relative_to(ROOT)}")
          path.write_text(updated, encoding="utf-8")
      
      
      def replace_manifest_version(path: Path, version: str) -> None:
          document = json.loads(path.read_text(encoding="utf-8"))
          if path.name == "marketplace.json":
              document["plugins"][0]["version"] = version
          else:
              document["version"] = version
          path.write_text(json.dumps(document, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
      
      
      def check_source(version: str) -> None:
          actual = {
              **{str(path.relative_to(ROOT)): project_version(path) for path in PYPROJECTS},
              **{str(path.relative_to(ROOT)): init_version(path) for path in INIT_FILES},
              **{str(path.relative_to(ROOT)): manifest_version(path) for path in PLUGIN_FILES},
          }
          expected = {
              **{str(path.relative_to(ROOT)): version for path in (*PYPROJECTS, *INIT_FILES)},
              **{str(path.relative_to(ROOT)): plugin_version(version) for path in PLUGIN_FILES},
          }
          mismatches = {path: value for path, value in actual.items() if value != expected[path]}
          if mismatches:
              details = ", ".join(
                  f"{path}={value} (expected {expected[path]})"
                  for path, value in sorted(mismatches.items())
              )
              raise SystemExit(f"version mismatch: {details}")
          if metapackage_dependency_versions() != {version}:
              raise SystemExit("metapackage dependency versions do not match the release")
      
      
      def check_lock(version: str) -> None:
          lock_path = ROOT / "uv.lock"
          lock = tomllib.loads(lock_path.read_text(encoding="utf-8"))
          actual = {
              str(package["name"]): str(package["version"])
              for package in lock["package"]
              if package["name"] in LOCK_NAMES
          }
          expected = {name: version for name in LOCK_NAMES}
          if actual != expected:
              raise SystemExit(f"uv.lock version mismatch; expected {expected}, found {actual}")
      
      
      def main() -> None:
          args = parse_args()
          version = str(args.version)
          if VERSION_PATTERN.fullmatch(version) is None:
              raise SystemExit("VERSION must look like 2.1.0 or 2.1.0rc1")
      
          if args.check:
              check_source(version)
              check_lock(version)
              print(f"Labtasker version is consistently {version}.")
              return
      
          current = {project_version(path) for path in PYPROJECTS} | {
              init_version(path) for path in INIT_FILES
          }
          if len(current) != 1:
              raise SystemExit(f"refusing to update inconsistent source versions: {sorted(current)}")
          for path in PYPROJECTS:
              replace_project_version(path, version)
          replace_metapackage_dependency_versions(version)
          for path in INIT_FILES:
              replace_init_version(path, version)
          for path in PLUGIN_FILES:
              replace_manifest_version(path, plugin_version(version))
          print(f"Updated Labtasker from {current.pop()} to {version}; run 'uv lock' next.")
      
      
      if __name__ == "__main__":
          main()
      
  • SKILL.md 6.9 KB
    ---
    name: release
    description: Prepare, validate, tag, or publish a Labtasker release. Use for version bumps, release readiness, release artifacts, tags, GitHub releases, or PyPI publication; do not use for ordinary development builds.
    ---
    
    # Release Labtasker
    
    Treat the Client and Server as independently installed distributions that always
    ship with one shared version. Separate release preparation from externally
    visible publication.
    
    ## Establish scope and authority
    
    Determine the requested target version and whether the user asked only to
    prepare, or explicitly authorized tagging, pushing, creating a GitHub release,
    or publishing to PyPI. Do not infer authorization for later stages.
    
    Inspect the current branch, tags, remotes, `git status`, and all pending diffs.
    Do not include unrelated worktree changes in release commits or artifacts. Never
    reuse the v1 worktree's release scripts or workflow for v2.
    
    ## Prepare the version
    
    Use the deterministic helper from the repository root:
    
    ```bash
    uv run python .agents/skills/release/scripts/set_version.py VERSION
    uv lock
    uv run python .agents/skills/release/scripts/set_version.py --check VERSION
    ```
    
    The helper updates the workspace version, both distribution metadata files, both
    public `__version__` values, and the Claude Code plugin/marketplace version. It
    maps a PEP 440 prerelease such as `2.1.0rc1` to the corresponding plugin SemVer
    `2.1.0-rc.1`. `uv lock` owns lockfile updates. Review the resulting diff and
    update user documentation only when the release changes documented behavior. Do
    not create a version-only changelog file when the repository has no maintained
    changelog.
    
    ## Validate release readiness
    
    Run the complete ordinary gate from `AGENTS.md`. Build artifacts only from the
    exact reviewed source tree. Then smoke-test the release wheels in clean virtual
    environments, confirming that:
    
    - `labtasker-client` imports `labtasker` and provides its CLI without Server
      dependencies;
    - `labtasker-server` imports and provides its CLI without the Client package;
    - the `labtasker` convenience metapackage installs matching Client and Server
      distributions and both CLIs;
    - installed metadata and `__version__` equal the target version; and
    - all three source distributions and all three wheels are present.
    
    For a fully release-gated Linux release, also require the distributed integration
    suite for the exact commit, either locally with PyTorch and Accelerate installed
    or through the repository's distributed CI. Report that gate as pending rather
    than pretending it passed when it cannot be verified.
    
    ## Write consistent release notes
    
    Summarize user-visible changes since the immediately previous published release.
    Do not describe internal refactors, tests or CI maintenance as product changes,
    and do not promise compatibility that the specification does not provide. Use
    short imperative-present bullets such as `Add`, `Change` and `Fix`, name the
    affected Client, Server, CLI or Worker when that distinction matters, and combine
    closely related commits into one outcome. End every bullet with a period and
    order bullets by user impact, with compatibility and behavior changes before
    additions and fixes. Use `vVERSION` as the GitHub Release title.
    
    Use this shape for every GitHub Release. The compatibility notice is conditional,
    and the comparison line is omitted only for the repository's first release:
    
    ```markdown
    > [!IMPORTANT]
    > This release changes Client-Server compatibility. Upgrade the Server to
    > vVERSION first, then upgrade all Clients and Workers to vVERSION. Earlier
    > Clients cannot [...exact consequence...].
    
    ## Changes
    
    - Add ...
    - Change ...
    - Fix ...
    
    **Full Changelog**: [vPREVIOUS...vVERSION](https://github.com/luocfprime/labtasker/compare/vPREVIOUS...vVERSION)
    ```
    
    Use a GitHub `[!IMPORTANT]` admonition only when users need to take action or
    could otherwise encounter a material compatibility, migration, or safety risk.
    Release size is not the criterion. Do not add one merely because a compatible
    release changes Server code, adds a feature, or expands a supported range.
    Changes to Client-Server interoperability, supported version combinations, API
    or persisted database compatibility warrant an admonition only when their
    practical consequence deserves prominent attention. State the exact consequence
    and action instead of writing only “compatibility changes.” Cover, when
    applicable:
    
    - whether upgrading both Client and Server is required or only recommended;
    - which mixed-version combinations remain supported;
    - the required upgrade order;
    - whether database migration is automatic or requires a manual step; and
    - what fails or becomes unavailable if versions are mixed.
    
    Use `required` only when mixed versions are unsupported or unsafe. If the current
    and previous v2 Clients remain supported by the new Server, say that explicitly
    and use `recommended` for a same-version upgrade. Do not add an admonition when
    there is no compatibility or upgrade information worth calling out; repeated
    empty warnings make real warnings less visible.
    
    When a compatible release has a material mixed-version limitation worth
    highlighting, use this form rather than implying that a coordinated upgrade is
    mandatory:
    
    ```markdown
    > [!IMPORTANT]
    > This release includes Server changes. Upgrading the Client and Server together
    > is recommended. Existing v2 Clients remain compatible.
    ```
    
    For an incompatible release, use `required`, give the supported versions and
    upgrade order, and state any migration behavior in the same notice. Do not use
    vague notices such as “Server changes” without telling the reader what to do.
    
    Keep the `Full Changelog` comparison as the final line even when GitHub generated
    it automatically. Compare the immediately previous published release tag with
    the new tag, including across a major-version boundary, and use the visible
    `vPREVIOUS...vVERSION` label. Do not replace the curated summary with the commit
    comparison and do not add a comparison link when no previous release exists.
    Keep the single `## Changes` heading for ordinary releases rather than switching
    between `Highlights`, `What's Changed`, `Bug fixes` and generated commit groups.
    
    ## Publish only with explicit authorization
    
    Immediately before any tag, push, GitHub Release, or PyPI operation, recheck the
    exact commit, clean-worktree state, version alignment, artifact contents, and
    authorization. Use an annotated `vVERSION` tag unless the repository establishes
    a different convention.
    
    The reviewed `.github/workflows/release.yml` workflow publishes all three
    distributions when a GitHub Release is published. It requires a `vVERSION` tag,
    the ordinary and real distributed gates for the exact commit, clean-wheel smoke
    tests, and protected `pypi`, `pypi-client`, and `pypi-server` environments. PyPI
    must register that workflow with environment `pypi-client` for
    `labtasker-client`, `pypi-server` for `labtasker-server`, and `pypi` for
    `labtasker`. Do not add a manual publishing trigger, improvise credentials, or
    copy the v1 single-package workflow.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related