feat(drone): add 'drone @git tag <vX.Y.Z>' — guarded release-tag push, automate the merge playbook's last manual step
devpulse-tier (owner) verb: fetch origin, VERSION GUARD (tag X.Y.Z must match origin/main pyproject + __init__), EXISTS GUARD (refuse if tag exists), tag origin/main + push -> fires publish.yml. 'tag --list' is global tier. Removes the last user-input step from releases (Patrick request, S274). Merge playbook (merge.md) updated to use it. Verb proven live: cut v2.6.1.
This commit is contained in:
@@ -33,6 +33,7 @@ from aipass.drone.apps.handlers.git import (
|
||||
branches_handler,
|
||||
delete_branch_handler,
|
||||
close_pr_handler,
|
||||
tag_handler,
|
||||
)
|
||||
|
||||
DRONE_MODULE = {
|
||||
@@ -62,6 +63,8 @@ _COMMANDS = (
|
||||
"fix",
|
||||
"pr",
|
||||
"prune-temp",
|
||||
"tag",
|
||||
"tag-list",
|
||||
)
|
||||
|
||||
_GH_PASSTHROUGH_COMMANDS = ("issue", "run", "workflow")
|
||||
@@ -124,6 +127,8 @@ def handle_command(command: str | None = None, args: list[str] | None = None) ->
|
||||
return {"stdout": "", "stderr": "", "exit_code": 0}
|
||||
|
||||
cmd: str = command
|
||||
if cmd == "tag" and (not args or args[0] == "--list"):
|
||||
cmd = "tag-list"
|
||||
try:
|
||||
from aipass.drone.apps.plugins.devpulse_ops.auth import verify_git_access
|
||||
|
||||
@@ -170,6 +175,8 @@ def handle_command(command: str | None = None, args: list[str] | None = None) ->
|
||||
return _handle_pr(args)
|
||||
if command == "prune-temp":
|
||||
return _handle_prune_temp()
|
||||
if command == "tag":
|
||||
return _handle_tag(args)
|
||||
|
||||
available = ", ".join(_COMMANDS)
|
||||
return {
|
||||
@@ -179,6 +186,20 @@ def handle_command(command: str | None = None, args: list[str] | None = None) ->
|
||||
}
|
||||
|
||||
|
||||
def _handle_tag(args: list[str]) -> dict:
|
||||
"""Handle the tag subcommand — create/push release tags or list them."""
|
||||
if not args or args[0] == "--list":
|
||||
result = tag_handler.list_tags()
|
||||
if not result["tags"]:
|
||||
return {"stdout": result["message"], "stderr": "", "exit_code": 0}
|
||||
return {"stdout": "\n".join(result["tags"]), "stderr": "", "exit_code": 0}
|
||||
|
||||
result = tag_handler.tag_release(args[0])
|
||||
if result["success"]:
|
||||
return {"stdout": result["message"], "stderr": "", "exit_code": 0}
|
||||
return {"stdout": "", "stderr": result["message"], "exit_code": 1}
|
||||
|
||||
|
||||
def _handle_gh_passthrough(subcommand: str, args: list[str]) -> dict:
|
||||
"""Pass through to gh CLI for issue, run, and workflow subcommands."""
|
||||
cmd = ["gh", subcommand] + args
|
||||
@@ -634,6 +655,16 @@ def get_help(command: str | None = None) -> str:
|
||||
"Options:\n"
|
||||
" --dry-run Report without executing fixes.\n"
|
||||
)
|
||||
if command == "tag":
|
||||
return (
|
||||
"git tag <vX.Y.Z> — Create and push an annotated release tag [owner]\n"
|
||||
"git tag --list — List all tags (newest first) [global]\n"
|
||||
"\n"
|
||||
"Safety guards:\n"
|
||||
" Version guard Tags on origin/main only after verifying pyproject.toml\n"
|
||||
" and __init__.py both match the tag version.\n"
|
||||
" Exists guard Refuses if tag already exists locally or on remote.\n"
|
||||
)
|
||||
|
||||
return (
|
||||
"git — Tier-based git workflow (dev branch model)\n"
|
||||
@@ -644,6 +675,7 @@ def get_help(command: str | None = None) -> str:
|
||||
" lock Check lock status\n"
|
||||
" branches List remote branches\n"
|
||||
" prune-temp Delete merged citizen/* temp branches\n"
|
||||
" tag --list List all tags (newest first)\n"
|
||||
" issue [args] Passthrough to gh issue\n"
|
||||
" run [args] Passthrough to gh run\n"
|
||||
" workflow [args] Passthrough to gh workflow\n"
|
||||
@@ -658,6 +690,7 @@ def get_help(command: str | None = None) -> str:
|
||||
" sync [--autostash] Sync with origin/main (FF on dev)\n"
|
||||
" smart-sync Fetch + rebase if behind\n"
|
||||
" unlock --force Force-release the PR lock\n"
|
||||
" tag <vX.Y.Z> Create and push release tag\n"
|
||||
" fix [--dry-run] Fix broken git states\n"
|
||||
)
|
||||
|
||||
@@ -674,8 +707,8 @@ def get_introspective() -> str:
|
||||
" plugins/devpulse_ops/\n"
|
||||
" - auth.py, merge_plugin.py, sync_plugin.py, fix_plugin.py\n"
|
||||
" gh passthrough: issue, run, workflow\n"
|
||||
"Tiers: global (status,diff,log,lock,branches,prune-temp,issue,run,workflow)"
|
||||
" | owner (pr,commit,checkout,dev-pr,delete-branch,close-pr,sync,unlock,merge,smart-sync,fix)\n"
|
||||
"Tiers: global (status,diff,log,lock,branches,prune-temp,tag --list,issue,run,workflow)"
|
||||
" | owner (pr,commit,checkout,dev-pr,delete-branch,close-pr,sync,unlock,merge,smart-sync,fix,tag)\n"
|
||||
)
|
||||
|
||||
|
||||
@@ -705,7 +738,8 @@ def print_introspection() -> None:
|
||||
c.print(" - [cyan]commit_handler.py[/cyan], [cyan]checkout_handler.py[/cyan], [cyan]sync_handler.py[/cyan]")
|
||||
c.print(
|
||||
" - [cyan]dev_pr_handler.py[/cyan], [cyan]branches_handler.py[/cyan],"
|
||||
" [cyan]delete_branch_handler.py[/cyan], [cyan]close_pr_handler.py[/cyan]"
|
||||
" [cyan]delete_branch_handler.py[/cyan], [cyan]close_pr_handler.py[/cyan],"
|
||||
" [cyan]tag_handler.py[/cyan]"
|
||||
)
|
||||
c.print(" [cyan]plugins/devpulse_ops/[/cyan]")
|
||||
c.print(
|
||||
@@ -715,9 +749,9 @@ def print_introspection() -> None:
|
||||
c.print(" [dim]gh passthrough: issue, run, workflow[/dim]")
|
||||
c.print(
|
||||
"[yellow]Tiers:[/yellow] [dim]global[/dim]"
|
||||
" [dim](status,diff,log,lock,branches,prune-temp,issue,run,workflow)[/dim]"
|
||||
" [dim](status,diff,log,lock,branches,prune-temp,tag --list,issue,run,workflow)[/dim]"
|
||||
" | [dim]owner[/dim]"
|
||||
" [dim](pr,commit,checkout,dev-pr,delete-branch,close-pr,sync,unlock,merge,smart-sync,fix)[/dim]"
|
||||
" [dim](pr,commit,checkout,dev-pr,delete-branch,close-pr,sync,unlock,merge,smart-sync,fix,tag)[/dim]"
|
||||
)
|
||||
c.print()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user