Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 8 additions & 22 deletions .agents/skills/deploy/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,36 +111,22 @@ git describe --tags --abbrev=0 2>/dev/null

If no tag is found at all (first release), warn: "No version tag found. You must bump the version before deploying." Return to the version bump prompt. Otherwise, use the tag found as the release version.

If the user chose a bump level, map their response to a command:
If the user chose a bump level, map their response to a bump command and run `bump-and-tag.py`, which performs the bump, verifies the resulting tag (creating an annotated fallback if `tag.forceSignAnnotated` silently rejected a lightweight tag), and pushes both the commit and the tag. The resolved version is printed on stdout — capture it for the release step.

| User says | Run |
| User says | `--bump-cmd` |
|---|---|
| "patch" / anything mentioning patch | `make bump-patch` |
| "minor" | `make bump-minor` |
| "major" | `make bump-major` |
| A specific version e.g. "2.4.5" | `make set-version V= 2.4.5` |
| A specific version e.g. "2.4.5" | `make set-version V=2.4.5` |

These commands update the version manifest, create a git commit, and create a git tag.

After the bump command runs, verify the tag was actually created:

```bash
git tag -l "v<new-version>"
```

If the tag is missing (some git configs like `tag.forceSignAnnotated = true` reject lightweight tags silently), create an annotated tag as a fallback:

```bash
git tag -a "v<new-version>" -m "v<new-version>"
```

Push the version bump:
```bash
git push
git push --tags
python3 CodeCannon/skills/github-agile/scripts/bump-and-tag.py \
--bump-cmd "<bump-command-from-table>" \
--version-read-cmd "cat VERSION"
```

Both the version bump commit and the tag must be pushed.
If the script exits non-zero, stop and resolve the issue it reports before continuing. On success, the version printed on stdout is the new version — use it as `<new-version>` in subsequent steps.

---

Expand Down Expand Up @@ -268,4 +254,4 @@ After the command runs, note the release URL from the output.
Tell the user:

> "Released vX.Y.Z. Issues #N, #M closed automatically. GitHub Release vX.Y.Z created at `<url>`. Run `make deploy-prod` to ship to production."
<!-- generated by CodeCannon/sync.py | skill: deploy | adapter: codex | hash: f28d90c7 | DO NOT EDIT — run CodeCannon/sync.py to regenerate -->
<!-- generated by CodeCannon/sync.py | skill: deploy | adapter: codex | hash: 28d0ff7c | DO NOT EDIT — run CodeCannon/sync.py to regenerate -->
30 changes: 8 additions & 22 deletions .claude/commands/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,36 +106,22 @@ git describe --tags --abbrev=0 2>/dev/null

If no tag is found at all (first release), warn: "No version tag found. You must bump the version before deploying." Return to the version bump prompt. Otherwise, use the tag found as the release version.

If the user chose a bump level, map their response to a command:
If the user chose a bump level, map their response to a bump command and run `bump-and-tag.py`, which performs the bump, verifies the resulting tag (creating an annotated fallback if `tag.forceSignAnnotated` silently rejected a lightweight tag), and pushes both the commit and the tag. The resolved version is printed on stdout — capture it for the release step.

| User says | Run |
| User says | `--bump-cmd` |
|---|---|
| "patch" / anything mentioning patch | `make bump-patch` |
| "minor" | `make bump-minor` |
| "major" | `make bump-major` |
| A specific version e.g. "2.4.5" | `make set-version V= 2.4.5` |
| A specific version e.g. "2.4.5" | `make set-version V=2.4.5` |

These commands update the version manifest, create a git commit, and create a git tag.

After the bump command runs, verify the tag was actually created:

```bash
git tag -l "v<new-version>"
```

If the tag is missing (some git configs like `tag.forceSignAnnotated = true` reject lightweight tags silently), create an annotated tag as a fallback:

```bash
git tag -a "v<new-version>" -m "v<new-version>"
```

Push the version bump:
```bash
git push
git push --tags
python3 CodeCannon/skills/github-agile/scripts/bump-and-tag.py \
--bump-cmd "<bump-command-from-table>" \
--version-read-cmd "cat VERSION"
```

Both the version bump commit and the tag must be pushed.
If the script exits non-zero, stop and resolve the issue it reports before continuing. On success, the version printed on stdout is the new version — use it as `<new-version>` in subsequent steps.

---

Expand Down Expand Up @@ -263,4 +249,4 @@ After the command runs, note the release URL from the output.
Tell the user:

> "Released vX.Y.Z. Issues #N, #M closed automatically. GitHub Release vX.Y.Z created at `<url>`. Run `make deploy-prod` to ship to production."
<!-- generated by CodeCannon/sync.py | skill: deploy | adapter: claude | hash: 3e2e42e4 | DO NOT EDIT — run CodeCannon/sync.py to regenerate -->
<!-- generated by CodeCannon/sync.py | skill: deploy | adapter: claude | hash: a5d179b8 | DO NOT EDIT — run CodeCannon/sync.py to regenerate -->
3 changes: 3 additions & 0 deletions .codecannon.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ config:
BUMP_PATCH_CMD: make bump-patch
BUMP_MINOR_CMD: make bump-minor
BUMP_MAJOR_CMD: make bump-major
# SET_VERSION_CMD is concatenated with the version (no implicit separator),
# so include the trailing separator your command needs: `=` for kwarg-style
# (e.g. "make set-version V="), trailing space for positional (e.g. "npm version ").
SET_VERSION_CMD: "make set-version V="
REVIEW_AGENT_PROMPT: .claude/review-agent-prompt.md
DEFAULT_REVIEWERS: "@sebastientaggart"
Expand Down
30 changes: 8 additions & 22 deletions .cursor/rules/deploy.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -112,36 +112,22 @@ git describe --tags --abbrev=0 2>/dev/null

If no tag is found at all (first release), warn: "No version tag found. You must bump the version before deploying." Return to the version bump prompt. Otherwise, use the tag found as the release version.

If the user chose a bump level, map their response to a command:
If the user chose a bump level, map their response to a bump command and run `bump-and-tag.py`, which performs the bump, verifies the resulting tag (creating an annotated fallback if `tag.forceSignAnnotated` silently rejected a lightweight tag), and pushes both the commit and the tag. The resolved version is printed on stdout — capture it for the release step.

| User says | Run |
| User says | `--bump-cmd` |
|---|---|
| "patch" / anything mentioning patch | `make bump-patch` |
| "minor" | `make bump-minor` |
| "major" | `make bump-major` |
| A specific version e.g. "2.4.5" | `make set-version V= 2.4.5` |
| A specific version e.g. "2.4.5" | `make set-version V=2.4.5` |

These commands update the version manifest, create a git commit, and create a git tag.

After the bump command runs, verify the tag was actually created:

```bash
git tag -l "v<new-version>"
```

If the tag is missing (some git configs like `tag.forceSignAnnotated = true` reject lightweight tags silently), create an annotated tag as a fallback:

```bash
git tag -a "v<new-version>" -m "v<new-version>"
```

Push the version bump:
```bash
git push
git push --tags
python3 CodeCannon/skills/github-agile/scripts/bump-and-tag.py \
--bump-cmd "<bump-command-from-table>" \
--version-read-cmd "cat VERSION"
```

Both the version bump commit and the tag must be pushed.
If the script exits non-zero, stop and resolve the issue it reports before continuing. On success, the version printed on stdout is the new version — use it as `<new-version>` in subsequent steps.

---

Expand Down Expand Up @@ -269,4 +255,4 @@ After the command runs, note the release URL from the output.
Tell the user:

> "Released vX.Y.Z. Issues #N, #M closed automatically. GitHub Release vX.Y.Z created at `<url>`. Run `make deploy-prod` to ship to production."
<!-- generated by CodeCannon/sync.py | skill: deploy | adapter: cursor | hash: 9120da02 | DO NOT EDIT — run CodeCannon/sync.py to regenerate -->
<!-- generated by CodeCannon/sync.py | skill: deploy | adapter: cursor | hash: 84eeea0f | DO NOT EDIT — run CodeCannon/sync.py to regenerate -->
30 changes: 8 additions & 22 deletions .gemini/skills/deploy/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,36 +111,22 @@ git describe --tags --abbrev=0 2>/dev/null

If no tag is found at all (first release), warn: "No version tag found. You must bump the version before deploying." Return to the version bump prompt. Otherwise, use the tag found as the release version.

If the user chose a bump level, map their response to a command:
If the user chose a bump level, map their response to a bump command and run `bump-and-tag.py`, which performs the bump, verifies the resulting tag (creating an annotated fallback if `tag.forceSignAnnotated` silently rejected a lightweight tag), and pushes both the commit and the tag. The resolved version is printed on stdout — capture it for the release step.

| User says | Run |
| User says | `--bump-cmd` |
|---|---|
| "patch" / anything mentioning patch | `make bump-patch` |
| "minor" | `make bump-minor` |
| "major" | `make bump-major` |
| A specific version e.g. "2.4.5" | `make set-version V= 2.4.5` |
| A specific version e.g. "2.4.5" | `make set-version V=2.4.5` |

These commands update the version manifest, create a git commit, and create a git tag.

After the bump command runs, verify the tag was actually created:

```bash
git tag -l "v<new-version>"
```

If the tag is missing (some git configs like `tag.forceSignAnnotated = true` reject lightweight tags silently), create an annotated tag as a fallback:

```bash
git tag -a "v<new-version>" -m "v<new-version>"
```

Push the version bump:
```bash
git push
git push --tags
python3 CodeCannon/skills/github-agile/scripts/bump-and-tag.py \
--bump-cmd "<bump-command-from-table>" \
--version-read-cmd "cat VERSION"
```

Both the version bump commit and the tag must be pushed.
If the script exits non-zero, stop and resolve the issue it reports before continuing. On success, the version printed on stdout is the new version — use it as `<new-version>` in subsequent steps.

---

Expand Down Expand Up @@ -268,4 +254,4 @@ After the command runs, note the release URL from the output.
Tell the user:

> "Released vX.Y.Z. Issues #N, #M closed automatically. GitHub Release vX.Y.Z created at `<url>`. Run `make deploy-prod` to ship to production."
<!-- generated by CodeCannon/sync.py | skill: deploy | adapter: gemini | hash: 17faf3c7 | DO NOT EDIT — run CodeCannon/sync.py to regenerate -->
<!-- generated by CodeCannon/sync.py | skill: deploy | adapter: gemini | hash: 20b6c7a1 | DO NOT EDIT — run CodeCannon/sync.py to regenerate -->
2 changes: 2 additions & 0 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,6 @@ BUMP_MAJOR_CMD: make bump-major
SET_VERSION_CMD: "make set-version V=" # arbitrary version (value appended)
```

`SET_VERSION_CMD` is concatenated with the version with no implicit separator, so it must include its own trailing separator: `=` for kwarg-style (`"make set-version V="`), or a trailing space for positional-style (`"npm version "`).

These commands are expected to handle the commit and tag creation themselves. `/deploy` calls them and then pushes.
28 changes: 7 additions & 21 deletions skills/github-agile/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,36 +156,22 @@ git describe --tags --abbrev=0 2>/dev/null

If no tag is found at all (first release), warn: "No version tag found. You must bump the version before deploying." Return to the version bump prompt. Otherwise, use the tag found as the release version.

If the user chose a bump level, map their response to a command:
If the user chose a bump level, map their response to a bump command and run `bump-and-tag.py`, which performs the bump, verifies the resulting tag (creating an annotated fallback if `tag.forceSignAnnotated` silently rejected a lightweight tag), and pushes both the commit and the tag. The resolved version is printed on stdout — capture it for the release step.

| User says | Run |
| User says | `--bump-cmd` |
|---|---|
| "patch" / anything mentioning patch | `{{BUMP_PATCH_CMD}}` |
| "minor" | `{{BUMP_MINOR_CMD}}` |
| "major" | `{{BUMP_MAJOR_CMD}}` |
| A specific version e.g. "2.4.5" | `{{SET_VERSION_CMD}} 2.4.5` |
| A specific version e.g. "2.4.5" | `{{SET_VERSION_CMD}}2.4.5` |

These commands update the version manifest, create a git commit, and create a git tag.

After the bump command runs, verify the tag was actually created:

```bash
git tag -l "v<new-version>"
```

If the tag is missing (some git configs like `tag.forceSignAnnotated = true` reject lightweight tags silently), create an annotated tag as a fallback:

```bash
git tag -a "v<new-version>" -m "v<new-version>"
```

Push the version bump:
```bash
git push
git push --tags
python3 CodeCannon/skills/github-agile/scripts/bump-and-tag.py \
--bump-cmd "<bump-command-from-table>" \
--version-read-cmd "{{VERSION_READ_CMD}}"
```

Both the version bump commit and the tag must be pushed.
If the script exits non-zero, stop and resolve the issue it reports before continuing. On success, the version printed on stdout is the new version — use it as `<new-version>` in subsequent steps.

---

Expand Down
121 changes: 121 additions & 0 deletions skills/github-agile/scripts/bump-and-tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""bump-and-tag — run a version bump, verify the tag, and push commit + tag.

Wraps the bump-verify-push sequence used by /deploy Step 3 so the agent
doesn't narrate it step-by-step. The project-specific bump and version-read
commands are passed in as opaque shell strings (the same template variables
the skill already uses: {{BUMP_PATCH_CMD}}, {{VERSION_READ_CMD}}, etc.), so
this script is agnostic to whether the project uses Make, npm, uv, or a
custom script.

Defensive behaviour:
1. Run the bump command. Bail on non-zero exit.
2. Re-read the version via the version-read command.
3. Check `git tag -l "v<version>"`. If missing (e.g. tag.forceSignAnnotated
silently rejected a lightweight tag), create an annotated tag as a
fallback.
4. Push the commit and the tag. Both must succeed.

The resolved version is printed to stdout on success so the calling skill
can capture it for the release step. All diagnostics go to stderr.

Usage:
python3 .../bump-and-tag.py --bump-cmd <shell> --version-read-cmd <shell>

Exit codes:
0 success (version printed to stdout)
1 bump command failed
2 version-read command failed or produced no output
3 tag creation or push failed
4 bad arguments
"""

import argparse
import subprocess
import sys


def run_shell(cmd, *, capture=False):
"""Run a shell command string. Returns CompletedProcess."""
return subprocess.run(cmd, shell=True, text=True, capture_output=capture)


def main(argv):
parser = argparse.ArgumentParser(
prog=argv[0],
description="Run a version bump, verify the resulting tag, and push.",
)
parser.add_argument(
'--bump-cmd',
required=True,
help="Shell command that bumps the version, commits, and tags "
"(e.g. 'make bump-patch', 'npm version patch').",
)
parser.add_argument(
'--version-read-cmd',
required=True,
help="Shell command that prints the current version to stdout "
"(e.g. 'node -p \"require(\\'./package.json\\').version\"').",
)
args = parser.parse_args(argv[1:])

# 1. Run the bump.
bump = run_shell(args.bump_cmd)
if bump.returncode != 0:
print(f"Error: bump command failed: {args.bump_cmd}", file=sys.stderr)
return 1

# 2. Re-read the version.
read = run_shell(args.version_read_cmd, capture=True)
if read.returncode != 0:
print(
f"Error: version-read command failed: {args.version_read_cmd}\n"
f" stderr: {read.stderr.strip()}",
file=sys.stderr,
)
return 2
version = read.stdout.strip()
if not version:
print(
f"Error: version-read command produced no output: {args.version_read_cmd}",
file=sys.stderr,
)
return 2

tag = f"v{version}"

# 3. Verify the tag exists; fall back to an annotated tag if not.
check = subprocess.run(
['git', 'tag', '-l', tag],
capture_output=True,
text=True,
)
if check.returncode != 0:
print("Error: 'git tag -l' failed.", file=sys.stderr)
return 3
if not check.stdout.strip():
print(
f"Tag {tag} not found after bump — creating annotated tag "
"(bump command likely produced a lightweight tag that was "
"rejected by tag.forceSignAnnotated).",
file=sys.stderr,
)
fallback = subprocess.run(['git', 'tag', '-a', tag, '-m', tag])
if fallback.returncode != 0:
print(f"Error: failed to create annotated tag {tag}.", file=sys.stderr)
return 3

# 4. Push commit and tag.
for cmd in (['git', 'push'], ['git', 'push', '--tags']):
result = subprocess.run(cmd)
if result.returncode != 0:
print(f"Error: command failed: {' '.join(cmd)}", file=sys.stderr)
return 3

# Emit the resolved version so the caller can capture it.
print(version)
return 0


if __name__ == '__main__':
sys.exit(main(sys.argv))
3 changes: 3 additions & 0 deletions templates/codecannon.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ config:
BUMP_PATCH_CMD: make bump-patch
BUMP_MINOR_CMD: make bump-minor
BUMP_MAJOR_CMD: make bump-major
# SET_VERSION_CMD is concatenated with the version (no implicit separator),
# so include the trailing separator your command needs: `=` for kwarg-style
# (e.g. "make set-version V="), trailing space for positional (e.g. "npm version ").
SET_VERSION_CMD: "make set-version V="

# ── Paths ────────────────────────────────────────────────────────────────────
Expand Down
Loading