Implement or update the full CI/CD pipeline for a lab plugin. Use when the user wants to add or update GitHub Actions workflows (ci.yaml, publish-image.yaml, release-on-main.yaml), configure pre-commit or lefthook, set up automated releases, audit an existing pipeline for drift, or sync Justfile targets with CI steps.
From plugin-labnpx claudepluginhub jmagar/claude-homelab --plugin plugin-labThis skill uses the workspace's default tool permissions.
references/ci-workflow-template.mdreferences/live-test-guard-pattern.mdProvides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
Calculates TAM/SAM/SOM using top-down, bottom-up, and value theory methodologies for market sizing, revenue estimation, and startup validation.
Implement the complete CI/CD pipeline for a lab plugin. A conforming plugin has four workflow files, not one.
Every canonical lab plugin has all four of these:
ci.yaml — Test GateRuns on every PR and push to main. Stages in order:
SKIP_LIVE_TESTS=1Stages are wired with needs: so they run sequentially. Never triggers image push.
publish-image.yaml — Image PublishingRuns on every push and workflow_dispatch. Builds and pushes the Docker image to GHCR using GHA layer cache. Tag strategy via docker/metadata-action:
type=ref,event=branch — branch name tagtype=ref,event=tag — semver tag on git tag pushtype=sha — short commit SHA tagtype=raw,value=latest,enable={{is_default_branch}} — latest on default branch onlyrelease-on-main.yaml — Automated ReleasesRuns on push to main and workflow_dispatch. Workflow:
project.version, Cargo.toml → package.version, package.json → version)v<version> already exists — fails the workflow if it does (this enforces a version bump on every main push)softprops/action-gh-release@v2, generate_release_notes: true).pre-commit-config.yaml (uses pre-commit framework, language: system)lefthook.yml (runs in parallel mode)Unless the user states otherwise, assume:
ghcr.io, image name is github.repositorySKIP_LIVE_TESTS=1 set at workflow env: levelBefore writing or updating pipeline files, collect:
python, rust, typescript)If any of these inputs are absent and cannot be inferred from the repo, ask before generating files.
Use the workflow files in ~/workspace/plugin-templates/py/, ~/workspace/plugin-templates/rs/, or ~/workspace/plugin-templates/ts/ as the base for all four files. Specialize with:
SKIP_LIVE_TESTS=1)Avoid inventing new job names or step ordering. Diverge from the canonical template only when the plugin has a documented reason.
Produce all four files plus Justfile targets:
.github/workflows/ci.yaml — lint → type-check → test gate with live test skip guard.github/workflows/publish-image.yaml — image build + push with full tag strategy and GHA cache.github/workflows/release-on-main.yaml — manifest version read → tag existence check → tag creation → GitHub release.pre-commit-config.yaml (Python) or lefthook.yml (Rust/TypeScript)lint, type-check, test, test-live, build, push, release that mirror each CI step locallyREADME.md (human-facing) and docs/secrets.md (machine-readable)Live tests require a running external service. They must never block CI when that service is unavailable. Guard them with SKIP_LIVE_TESTS=1.
Python — pytest marker:
# conftest.py
import os
import pytest
def pytest_configure(config):
config.addinivalue_line(
"markers", "live: mark test as requiring a live external service"
)
def pytest_collection_modifyitems(config, items):
if os.environ.get("SKIP_LIVE_TESTS"):
skip_live = pytest.mark.skip(reason="SKIP_LIVE_TESTS is set")
for item in items:
if "live" in item.keywords:
item.add_marker(skip_live)
# pyproject.toml — register the marker to silence PytestUnknownMarkWarning
[tool.pytest.ini_options]
markers = ["live: requires a live external service"]
# usage in tests
@pytest.mark.live
def test_actual_api_call():
...
Run without live tests (CI default): uv run pytest -m "not live"
Run with live tests (local): uv run pytest or just test-live
Rust — feature flag:
# Cargo.toml
[features]
live-tests = []
// usage in tests
#[cfg_attr(not(feature = "live-tests"), ignore)]
#[test]
fn test_actual_api_call() {
// requires running service
}
Run without live tests (CI default): cargo test
Run with live tests (local): cargo test --features live-tests or just test-live
TypeScript — environment variable check:
// vitest variant
import { describe, test, expect } from "vitest";
const skipLive = !!process.env.SKIP_LIVE_TESTS;
describe("live integration", () => {
test.skipIf(skipLive)("calls actual API", async () => {
// requires running service
});
});
// jest variant
const skipLive = !!process.env.SKIP_LIVE_TESTS;
(skipLive ? describe.skip : describe)("live integration", () => {
test("calls actual API", async () => {
// requires running service
});
});
Run without live tests (CI default): SKIP_LIVE_TESTS=1 npm test
Run with live tests (local): npm test or just test-live
In CI, set SKIP_LIVE_TESTS: "1" as a workflow-level env: variable so it applies to all test steps without repeating it per step.
Check for all four workflow files. Common drift patterns:
ci.yaml:
needs: (running in parallel instead of sequentially)publish-image.yaml:
:latest only tag strategy — must also include sha and branch/tag refscache-from/cache-to missing)release-on-main.yaml:
fetch-depth: 0 missing — git tag push will fail without full historyPre-commit / Lefthook:
parallel: true (slower than it needs to be)Justfile:
test-live target for running live tests locallyProduce a findings list organized by file before making changes.
When modifying:
README.md and docs/secrets.md if secrets were added or renamedrelease-on-main.yaml: confirm the manifest has a version field before the workflow runs — the workflow will fail immediately if it cannot find oneIf an artifact path appears in pipeline output, use the timestamp format YYYYMMDD-HHMMSS.
At minimum, all four workflow files plus supporting config:
.github/workflows/ci.yaml — lint → type-check → test with live test guard.github/workflows/publish-image.yaml — image build + push, full tag strategy, GHA cache.github/workflows/release-on-main.yaml — manifest version → tag check → tag + release.pre-commit-config.yaml (Python) or lefthook.yml (Rust/TypeScript)lint, type-check, test, test-live, build, pushREADME.md and docs/secrets.mdSKIP_LIVE_TESTS; coordinate live test marker usage with tool design