Writing Skills
writing-skills
效率与协作技能,来自 obra/superpowers,查看原文获取完整用途和操作说明。
mlops-automation【部署与运维】Automate an MLOps project with mise tasks, lefthook hooks, Docker images, GitHub Actions, and MLflow tracking on a SQL backend. Use when adding a task runner, git hooks, CI/CD, or experiment tracking to a working package.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
To elevate the codebase to production standards by adding Task Automation (mise), Git Hooks (lefthook), Containerization (docker), CI/CD (github-actions), and Experiment Tracking (mlflow).
uvPreserve existing work and repository visibility. Prepare local changes first; commit, push, change remote settings, publish, or deploy only with user authorization.
Expose a single, shared task vocabulary with mise (replaces just/make).
mise — pins the toolchain and defines tasks in mise.toml.install, format, check, test, build, watch. Run everything via mise run <task> so hooks and CI reuse the same entrypoints.format: Format code and config (ruff format, dprint fmt).check: Static checks in parallel (ruff check, ty, pip-audit, gitleaks, trivy, actionlint + zizmor).test: Run pytest.build: Build the wheel (uv build).all = ["mise run format", "mise run check", "mise run test", "mise run build"]. This is the one command a developer, a hook, an agent, or CI runs; nothing else is allowed to define "ready".[tools], run mise lock, and commit mise.lock — it records the exact version, URL, and checksum per platform, so uv.lock pins the libraries and mise.lock pins the binaries.[settings.task] run_auto_install = false so a task fails loudly on a missing tool instead of downloading one mid-run.Catch issues locally with lefthook (replaces pre-commit).
Framework: lefthook with thin hooks — every command delegates to a mise run task so hooks and CI stay identical.
Explicit Priorities: lefthook orders commands alphabetically, so state the order yourself — formatters at 10, the staged secret scan at 20, the whole-tree checks at 30. Without priorities, check can read files that the formatter has not rewritten yet.
Staged Files: pass {staged_files} to the formatters and set stage_fixed: true, so a reformat is restaged into the commit being made rather than left dirty in the working tree.
Secret Gate: run mise run check:leaks --staged before the checks. A history scan does not look at the commit you are about to create; --staged does.
pre-push: Run mise run test.
Example:
# lefthook.yml
pre-commit:
parallel: false
commands:
format:dprint:
priority: 10
glob: "*.{json,md,toml,yaml,yml}"
run: mise run format:dprint {staged_files}
stage_fixed: true
format:python:
priority: 10
glob: "*.py"
run: mise run format:python {staged_files}
stage_fixed: true
check:leaks:
priority: 20
run: mise run check:leaks --staged
check:
priority: 30
run: mise run check
pre-push:
commands:
test:
run: mise run test
Commits: Enforce Conventional Commits (e.g., feat: add new model) so git-cliff can generate the changelog.
Reproducibility anywhere.
docker.python:3.14-slim for a minimal footprint; copy a pinned uv binary into the build stage (ghcr.io/astral-sh/uv:0.13.0, never :latest — a floating tag is invisible to Dependabot).uv sync --frozen --no-install-project --no-dev before copying src/; then install the package with uv sync --frozen --no-dev --no-editable..venv to an identical Python runtime image, or install the wheel and its locked runtime dependencies in the final stage.USER 10001:10001) and copy with --chown=10001:10001, so ownership is stable across rebuilds and readable by a host that does not share the image's /etc/passwd.check:dockerfile = "hadolint Dockerfile" to check; the Dockerfile is code and deserves the same gate.ghcr.io for GitHub.Automate verification and release with GitHub Actions.
Platform: ask for the company CI/CD platform, or use github-actions for GitHub.
One Step: CI runs mise run all and nothing else. A workflow that lists format, then check, then test as separate steps will drift from the local gate; a workflow with one step cannot.
Hardening: pin the runner (ubuntu-24.04), set timeout-minutes, keep permissions: contents: read, and check out with persist-credentials: false so no step inherits a push token it does not need.
Prove Cleanliness: end with test -z "$(git status --porcelain)". Because mise run all starts by formatting, a dirty tree at the end means a contributor committed unformatted files.
Example:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
check:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false # no step pushes back to the repository
- name: Install mise system
uses: jdx/mise-action@08ae2a091c038e1e05ec6f5115dc5c69d5a2a6b8 # v5.1.2
with:
cache: true # reuse the tools pinned by mise.lock
- name: Run canonical gate
run: mise run all
- name: Verify no changes
run: test -z "$(git status --porcelain)"
Audit the Workflows: add check:actions = ["actionlint", "zizmor --offline .github/workflows/"] to check. actionlint catches invalid syntax and shell bugs; zizmor catches workflow security issues (script injection, over-broad permissions, credential persistence). Pin actions to full commit SHAs with readable version comments; the default pinning rule can then enforce the policy without a relaxation file.
Scheduled Security Pass: push CI only scans recent commits, so add .github/workflows/security.yml on a weekly schedule (plus workflow_dispatch) that checks out with fetch-depth: 0 and runs the full-history gitleaks git --redact=100 and mise run check:scan using the committed Trivy policy. Keep fetch-depth: 0 out of ci.yml: a full clone on every push costs time and buys nothing the scheduled job does not already cover.
Release: cd.yml on Release builds the image and publishes docs via the official GitHub Pages Actions (configure-pages, upload-pages-artifact, deploy-pages).
Dependencies: add .github/dependabot.yml for every ecosystem in use (uv, github-actions, docker), grouping minor and patch updates into one pull request per ecosystem so majors are still reviewed alone.
Manage the ML lifecycle with MLflow 3.
MLflow (3.16.1+).sqlite:///mlflow.db is a real SQLAlchemy backend that supports the model registry and shares its shape with a production Postgres, so the client API can stay consistent. A shared deployment also needs artifact access, authentication, migration, and backup ownership. The legacy file store is in maintenance mode and disabled by default. MLFLOW_ALLOW_FILE_STORE=true is a migration escape hatch; use SQL for current features and a shared production service.mlflow server --backend-store-uri=sqlite:///mlflow.db --artifacts-destination=./mlartifacts. Add mlflow.db to .gitignore — it is local state, and committing it puts a binary database in the history.mlflow.autolog(); log metrics, params, and artifacts.name= (e.g., mlflow.pyfunc.log_model(name=...)).mlflow.validate_evaluation_results against explicit metric thresholds.@Champion for a deployment pointer, matching its case exactly. Evaluate and promote an immutable candidate version first; aliases can move. Never rely on moving versions (e.g., v1 -> v2).Write flexible code.
ModelFactory).Automation only helps if the next contributor finds it.
AGENTS.md: the commands, the definition of done, the conventions, and the layout — for AI assistants.README.md: the same ground truth for humans, without the agent-facing rules.AGENTS.md is worse than no command at all.mise run all (and format/check/test/build) work?mise.lock and uv.lock committed?{staged_files}, and check:leaks --staged?python:3.14-slim, non-root, with a pinned uv, and does hadolint pass?ci.yml a single mise run all step with persist-credentials: false and the porcelain assertion?actionlint and zizmor pass, and does security.yml rescan the full history weekly?