Skip to content

Commit 101c2c2

Browse files
chore(tools): add development and release skills
Three AI agent skills were previously local-only and not version-controlled, making them unavailable to other contributors. - Add tools/skills/ with generate_commit_message, pin_upstream_images (renamed from tag_release_images), and prepare_release (renamed from bump-version) including reference docs and scripts - Add tools/skills/README.md with release workflow overview - Document all skills in CONTRIBUTING.md with triggers, descriptions, and instructions for adding new skills Skills are now tracked in the repo and shareable across the team.
1 parent 7ec567d commit 101c2c2

File tree

8 files changed

+592
-0
lines changed

8 files changed

+592
-0
lines changed

CONTRIBUTING.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ pkg/
8585
testutil/ # Shared test infrastructure (envtest, Kind helpers)
8686
tools/
8787
observer/ # Standalone cluster health monitoring tool
88+
skills/ # AI agent skills for development and release workflows
8889
```
8990

9091
## Making Changes
@@ -131,3 +132,32 @@ fix(drain): prevent fallthrough on topo unavailability
131132
docs(readme): update backup architecture section
132133
chore(deps): update internal Go module dependencies
133134
```
135+
136+
## AI Agent Skills
137+
138+
This project includes structured AI agent skills under `tools/skills/` and `tools/observer/skills/`. These are prompt-based workflows that teach AI coding agents (e.g., Claude Code) how to perform specific tasks consistently.
139+
140+
### Available Skills
141+
142+
**Operator Development** ([tools/skills/](tools/skills/)):
143+
144+
| Skill | Trigger | Description |
145+
|:---|:---|:---|
146+
| `generate_commit_message` | `/generate_commit_message` | Generate semantic Conventional Commits messages from staged changes. |
147+
| `pin_upstream_images` | `/pin_upstream_images` | Pin multigres container image SHA tags and review upstream code changes. |
148+
| `prepare_release` | `/prepare_release` | Full release preparation: changelog, version inference, and doc audit. |
149+
150+
**Observer** ([tools/observer/skills/](tools/observer/skills/)):
151+
152+
| Skill | Trigger | Description |
153+
|:---|:---|:---|
154+
| `exercise_cluster` | `/exercise_cluster` | Deploy fixtures, run mutation scenarios, validate health. |
155+
| `diagnose_with_observer` | `/diagnose_with_observer` | Triage observer findings and produce bug reports. |
156+
157+
### Adding a New Skill
158+
159+
1. Create a directory under `tools/skills/<skill_name>/` with a `SKILL.md` file
160+
2. Use the frontmatter format (`name`, `description`) — see existing skills for examples
161+
3. Add reference files under `references/` or scripts under `scripts/` as needed
162+
4. Create a stub in `.agents/skills/<skill_name>/SKILL.md` pointing to the repo location
163+
5. Update the [tools/skills/README.md](tools/skills/README.md) table

tools/skills/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Operator Development Skills
2+
3+
AI agent skills for operator development workflows. Each skill is a structured prompt that teaches an agent how to perform a specific task with consistency and precision.
4+
5+
## Available Skills
6+
7+
| Skill | Category | Description |
8+
|-------|----------|-------------|
9+
| [generate_commit_message](generate_commit_message/SKILL.md) | Development | Generate semantic Conventional Commits messages from staged changes. |
10+
| [pin_upstream_images](pin_upstream_images/SKILL.md) | Release | Pin multigres container image SHA tags and review upstream code changes between versions. |
11+
| [prepare_release](prepare_release/SKILL.md) | Release | Full release preparation: changelog generation, version inference, and documentation audit. |
12+
13+
## Release Workflow
14+
15+
The release skills are designed to be used together:
16+
17+
```
18+
pin_upstream_images prepare_release
19+
─────────────────── ───────────────
20+
1. Fetch latest SHA tags 1. Gather commits since last tag
21+
2. Update image_defaults.go 2. Categorize changes
22+
3. Review upstream code changes ──→ 3. Update CHANGELOG.md
23+
4. Cross-reference operator impact 4. Audit all documentation
24+
5. Present impact analysis 5. Apply doc fixes
25+
```
26+
27+
**pin_upstream_images** upgrades the multigres container images and reviews what changed upstream. **prepare_release** then prepares the changelog and audits docs for the full release. Both use **generate_commit_message** for their final commit.
28+
29+
## Usage
30+
31+
These skills are loaded automatically by Claude Code when their trigger phrases are used. You can also invoke them directly:
32+
33+
- `/generate_commit_message` -- after staging changes
34+
- `/pin_upstream_images` -- when upgrading multigres images
35+
- `/prepare_release` -- when cutting a new operator release
36+
37+
## See Also
38+
39+
- [Observer Skills](../observer/skills/README.md) -- Skills for cluster health diagnostics (exercise_cluster, diagnose_with_observer)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
name: generate_commit_message
3+
description: generate semantic git commit messages
4+
---
5+
6+
# Generate Commit Message Skill
7+
8+
### ROLE
9+
You are a Senior DevOps Engineer and Lead Maintainer. Your goal is to generate semantically correct, clean, and highly informative git commit messages based on the provided code diffs.
10+
11+
### RULES (STRICT)
12+
1. **Format:** Use the Conventional Commits standard: `type(scope): summary`.
13+
2. **Sanitization:** NEVER include `cci:` identifiers, absolute file paths, URI schemes, or Markdown links. Use only relative filenames (e.g., `pkg/auth/...`).
14+
3. **Length:** Keep the first line under 50 characters if possible, never over 72. Wrap body text at 72 characters.
15+
4. **Mood:** Use the imperative mood for the subject line (e.g., "fix: remove handler" NOT "fixed: removed handler").
16+
17+
### CONTENT STRUCTURE
18+
Construct the commit body using these three specific sections (you do not need to label them, but the content must flow in this order):
19+
20+
1. **The Change (What):** A bulleted list of specific technical changes (files, functions, logic).
21+
2. **The Context (Why):** A brief explanation of the problem, bug, or missing feature that necessitated this change.
22+
3. **The Value (Advantage):** What is the benefit? (e.g., "Improves query performance by 20%", "Prevents race condition during shutdown", "Enables 100% test coverage"). If there a bunch of test changes and implementation code, focus on the actual implementation because the test code changes are likely related to that, rather than the main thing! The test work should be a mention at the end in this case.
23+
24+
### OUTPUT TEMPLATE
25+
```text
26+
<type>(<scope>): <short summary>
27+
28+
<The Why: 1-2 sentences on the problem context>
29+
30+
<The What: Bullet points of changes>
31+
- Refactored [Component] to use [Method]
32+
- Removed [Deprecated Function] in favor of [New Logic]
33+
34+
<The Advantage: 1 sentence on the impact>
35+
```
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
---
2+
name: pin_upstream_images
3+
description: Pin multigres container image tags in image_defaults.go for operator releases. Compares upstream multigres code changes between the current and new SHA, highlights breaking changes and new features, then updates the tags. Triggered by user requests like "prepare images for release", "pin image tags", "pin upstream images", or "upgrade multigres images".
4+
---
5+
6+
# Pin Upstream Images
7+
8+
Upgrade multigres container image tags in `api/v1alpha1/image_defaults.go` to the latest SHA tags, with an upstream code review between the old and new versions.
9+
10+
## Workflow
11+
12+
### 1. Fetch Latest SHA Tags
13+
14+
Run the fetch script to get the latest SHA tags:
15+
16+
```bash
17+
python3 tools/skills/pin_upstream_images/scripts/fetch_latest_tags.py
18+
```
19+
20+
This outputs `KEY_TAG=sha-XXXXXXX` for each image. The script resolves which `sha-*` tag currently points to the same digest as `main` for each of:
21+
- `ghcr.io/multigres/multigres` -> used by DefaultMultiAdminImage, DefaultMultiOrchImage, DefaultMultiPoolerImage, DefaultMultiGatewayImage
22+
- `ghcr.io/multigres/pgctld` -> used by DefaultPostgresImage
23+
- `ghcr.io/multigres/multiadmin-web` -> used by DefaultMultiAdminWebImage
24+
25+
### 2. Update Image Tags
26+
27+
Immediately update `api/v1alpha1/image_defaults.go` by replacing the old `sha-XXXXXXX` tags with the new ones:
28+
29+
| Constant | Image | Tag source |
30+
|----------------------------|------------------------------------|-----------------|
31+
| DefaultPostgresImage | ghcr.io/multigres/pgctld | PGCTLD_TAG |
32+
| DefaultMultiAdminImage | ghcr.io/multigres/multigres | MULTIGRES_TAG |
33+
| DefaultMultiAdminWebImage | ghcr.io/multigres/multiadmin-web | MULTIADMIN_WEB_TAG |
34+
| DefaultMultiOrchImage | ghcr.io/multigres/multigres | MULTIGRES_TAG |
35+
| DefaultMultiPoolerImage | ghcr.io/multigres/multigres | MULTIGRES_TAG |
36+
| DefaultMultiGatewayImage | ghcr.io/multigres/multigres | MULTIGRES_TAG |
37+
38+
Do NOT modify DefaultEtcdImage -- it uses a separate versioned release.
39+
40+
If old and new SHAs are identical for all images, inform the user that images are already up to date and stop.
41+
42+
### 3. Upstream Code Comparison
43+
44+
All multigres images (`multigres`, `pgctld`, `multiadmin-web`) are built from the same monorepo: `https://github.com/multigres/multigres`. Perform the comparison there.
45+
46+
1. Clone or pull the upstream multigres repo to `/tmp/multigres`:
47+
```bash
48+
if [ -d /tmp/multigres ]; then
49+
cd /tmp/multigres && git fetch --all && git checkout main && git pull
50+
else
51+
git clone https://github.com/multigres/multigres /tmp/multigres
52+
fi
53+
```
54+
55+
2. Identify the unique old and new SHA values from Step 2. Since `multigres/multigres` and `multigres/pgctld` may have different SHA tags, compare each distinct old->new pair. Typically:
56+
- **MULTIGRES_TAG** old SHA vs new SHA (covers multiadmin, multiorch, multipooler, multigateway, and pgctld if they share the same SHA)
57+
- **MULTIADMIN_WEB_TAG** old SHA vs new SHA (if different from the above)
58+
59+
3. For each distinct old->new SHA pair, run:
60+
```bash
61+
cd /tmp/multigres
62+
git log --oneline <old-sha>..<new-sha>
63+
git diff --stat <old-sha>..<new-sha>
64+
```
65+
Then selectively review the full diff for files that look relevant to the operator (e.g., changes to CLI flags, configuration, proto definitions, RPC interfaces, container entrypoints, health checks, pooler behavior, orchestrator logic, gateway behavior, backup/restore, topology management).
66+
67+
4. If the old and new SHAs are the same for an image group, note that no changes occurred and skip the diff.
68+
69+
### 4. Cross-Reference with Operator Code
70+
71+
This is the critical step. Do NOT make hypothetical recommendations. For each upstream change identified in Step 3, **search the operator codebase** to determine concrete impact.
72+
73+
For every potentially impactful upstream change:
74+
75+
1. **Search the operator code** using `grep_search`, `view_file_outline`, and `view_code_item` to find whether the operator references, uses, or depends on the changed upstream construct (proto field, gRPC service, CLI flag, topology record field, behavior, etc.).
76+
77+
2. **Classify each change with evidence:**
78+
- **Action required** -- The operator code directly references or depends on something that changed. Cite the exact file and line in the operator. Describe what needs to change and why.
79+
- **New feature opportunity** -- The upstream change introduces a capability the operator *could* support but currently does not. Cite what the upstream change adds and confirm the operator has no existing support for it.
80+
- **No impact** -- The upstream change does not touch anything the operator interacts with. Confirm this by showing the search came up empty.
81+
82+
3. **Do not speculate.** Every recommendation must be backed by a concrete search result (or confirmed absence) in the operator codebase. No "if the operator does X" phrasing -- either it does or it doesn't.
83+
84+
### 5. Present Results
85+
86+
Present a structured summary to the user:
87+
88+
**Commits between old and new:**
89+
- List of commit messages (from `git log --oneline`)
90+
91+
**Impact Analysis:**
92+
For each upstream change, include:
93+
- What changed upstream (one-liner)
94+
- Operator files affected (with file paths and line references) or "none found"
95+
- Verdict: **Action required** / **New feature opportunity** / **No impact**
96+
- For action-required items: specific description of what needs to change in the operator
97+
98+
### 6. Finalize
99+
100+
1. Display the `image_defaults.go` diff to the user.
101+
102+
2. Suggest a branch name and generate a commit message using the `generate_commit_message` skill:
103+
- Branch name: `release/pin-image-tags-YYYY-MM-DD`
104+
- The commit message should reference upgrading multigres images and include the old->new SHA transitions
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/usr/bin/env python3
2+
"""Fetch the latest SHA tag for each multigres GHCR image that shares the 'main' tag.
3+
4+
Uses the GHCR OCI Distribution API. No authentication needed for public packages.
5+
Runs digest lookups in parallel for speed.
6+
7+
Output: KEY=VALUE pairs, one per line:
8+
MULTIGRES_TAG=sha-XXXXXXX
9+
PGCTLD_TAG=sha-XXXXXXX
10+
MULTIADMIN_WEB_TAG=sha-XXXXXXX
11+
"""
12+
13+
import json
14+
import sys
15+
import urllib.request
16+
from concurrent.futures import ThreadPoolExecutor, as_completed
17+
18+
REGISTRY = "ghcr.io"
19+
20+
IMAGES = {
21+
"MULTIGRES": "multigres/multigres",
22+
"PGCTLD": "multigres/pgctld",
23+
"MULTIADMIN_WEB": "multigres/multiadmin-web",
24+
}
25+
26+
ACCEPT = ",".join([
27+
"application/vnd.oci.image.index.v1+json",
28+
"application/vnd.docker.distribution.manifest.list.v2+json",
29+
"application/vnd.docker.distribution.manifest.v2+json",
30+
])
31+
32+
33+
def get_token(image: str) -> str:
34+
url = f"https://{REGISTRY}/token?scope=repository:{image}:pull"
35+
with urllib.request.urlopen(url) as resp:
36+
return json.loads(resp.read())["token"]
37+
38+
39+
def get_digest(image: str, token: str, ref: str) -> str:
40+
url = f"https://{REGISTRY}/v2/{image}/manifests/{ref}"
41+
req = urllib.request.Request(url, method="HEAD", headers={
42+
"Authorization": f"Bearer {token}",
43+
"Accept": ACCEPT,
44+
})
45+
with urllib.request.urlopen(req) as resp:
46+
return resp.headers.get("Docker-Content-Digest", "")
47+
48+
49+
def get_all_tags(image: str, token: str) -> list[str]:
50+
url = f"https://{REGISTRY}/v2/{image}/tags/list?n=10000"
51+
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
52+
with urllib.request.urlopen(req) as resp:
53+
return json.loads(resp.read()).get("tags", [])
54+
55+
56+
def find_sha_tag(image: str, token: str, target_digest: str, sha_tags: list[str]) -> str | None:
57+
"""Find the sha-* tag matching target_digest using parallel lookups."""
58+
with ThreadPoolExecutor(max_workers=20) as pool:
59+
future_to_tag = {
60+
pool.submit(get_digest, image, token, tag): tag
61+
for tag in sha_tags
62+
}
63+
for future in as_completed(future_to_tag):
64+
tag = future_to_tag[future]
65+
if future.result() == target_digest:
66+
return tag
67+
return None
68+
69+
70+
def main():
71+
results = {}
72+
errors = []
73+
74+
for var_name, image in IMAGES.items():
75+
token = get_token(image)
76+
main_digest = get_digest(image, token, "main")
77+
if not main_digest:
78+
errors.append(f"Could not resolve digest for {image}:main")
79+
continue
80+
81+
all_tags = get_all_tags(image, token)
82+
sha_tags = [t for t in all_tags if t.startswith("sha-")]
83+
84+
found = find_sha_tag(image, token, main_digest, sha_tags)
85+
if not found:
86+
errors.append(
87+
f"No sha-* tag matching main for {image} "
88+
f"(digest={main_digest}, checked {len(sha_tags)} tags)"
89+
)
90+
continue
91+
92+
results[var_name] = found
93+
94+
# Print results in a stable order
95+
for var_name in IMAGES:
96+
if var_name in results:
97+
print(f"{var_name}_TAG={results[var_name]}")
98+
99+
if errors:
100+
for e in errors:
101+
print(f"ERROR: {e}", file=sys.stderr)
102+
sys.exit(1)
103+
104+
105+
if __name__ == "__main__":
106+
main()

0 commit comments

Comments
 (0)