diff --git a/README.md b/README.md index ecc0e64..d97d3a9 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,24 @@ skips blockers, and reports overlapping source that needs one integration PR. ## Install -Install the reviewed `v0.1.3` source commit directly from GitHub: +Install the reviewed `v0.1.4` source commit directly from GitHub: ```bash python3 -m pip install \ - 'deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@7fe247bd1d1994fafcea21817351b3da7e29f611' + 'deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@f18dce436bae2d6b7009d9aa76b4bfc8d0aac5a8' deploybot init ``` +Invoke the bundled `$deploybot` skill to inspect or operate the queue. Typical +requests include “show the DeployBot queue,” “why is PR 42 blocked?”, and +“deploy this PR.” Queue-only status is read-only: + +```bash +deploybot status +deploybot status --json +deploybot inspect 42 --json +``` + For development from the Astro source tree, install `./packages/agent-merge-queue[mcp]` instead. @@ -82,8 +92,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - # v0.1.3; keep the full commit so privileged workflows are immutable. - - uses: Forward-Future/DeployBot@7fe247bd1d1994fafcea21817351b3da7e29f611 + # v0.1.4; keep the full commit so privileged workflows are immutable. + - uses: Forward-Future/DeployBot@f18dce436bae2d6b7009d9aa76b4bfc8d0aac5a8 ``` Keep this workflow on the default branch. Never check out or execute code from @@ -144,6 +154,7 @@ The `mergeq` and `mergeq-mcp` command aliases remain for compatibility. ## Commands ```text +deploybot status --json deploybot plan --json deploybot inspect --json deploybot enqueue @@ -155,6 +166,9 @@ deploybot dequeue --reason "..." deploybot merge --batch ``` +`status` is a read-only alias for `plan`. It reports queue order, readiness, +blockers, dependencies, and overlap groups without freezing a batch. + `drain` merges only independent, green, exact-head-reviewed PRs. Overlapping source is returned as `integration_required` for an agent to resolve once. It waits briefly while GitHub recomputes mergeability after a merge, then records diff --git a/adapters/claude-code/.claude-plugin/plugin.json b/adapters/claude-code/.claude-plugin/plugin.json index c75e476..956ffb9 100644 --- a/adapters/claude-code/.claude-plugin/plugin.json +++ b/adapters/claude-code/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "deploybot", - "version": "0.1.3", + "version": "0.1.4", "description": "DeployBot: a provider-neutral GitHub merge queue for coding agents", "author": { "name": "DeployBot contributors" diff --git a/adapters/claude-code/.mcp.json b/adapters/claude-code/.mcp.json index dbaf6e6..5d2d1cb 100644 --- a/adapters/claude-code/.mcp.json +++ b/adapters/claude-code/.mcp.json @@ -4,7 +4,7 @@ "command": "uvx", "args": [ "--from", - "deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@7fe247bd1d1994fafcea21817351b3da7e29f611", + "deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@f18dce436bae2d6b7009d9aa76b4bfc8d0aac5a8", "deploybot-mcp" ] } diff --git a/adapters/claude-code/skills/deploybot/SKILL.md b/adapters/claude-code/skills/deploybot/SKILL.md new file mode 100644 index 0000000..6283f85 --- /dev/null +++ b/adapters/claude-code/skills/deploybot/SKILL.md @@ -0,0 +1,54 @@ +--- +name: deploybot +description: Inspect and operate a DeployBot GitHub merge queue. Use when the user invokes DeployBot, asks for queue or PR status, wants to know what is ready, waiting, blocked, next, dependent, or overlapping, explicitly says deploy, or asks the queue coordinator to freeze or drain authorized pull requests. +--- + +# DeployBot + +Read `.mergequeue.toml` before acting. Prefer the DeployBot MCP tools; fall back +to the `deploybot` CLI. Treat GitHub as the durable source of truth. + +## Read Status + +Keep status checks read-only: + +- For the full queue, call `queue_plan` or run `deploybot status --json`. +- For one pull request, call `inspect_pull_request` or run + `deploybot inspect --json`. +- Never call `freeze_queue` merely to view status because freezing writes a + durable batch marker. + +Report the queue in order. For each pull request, include its state, exact head, +checks or reviews still waiting, blocker, dependencies, and overlap group. Say +plainly when the queue is empty. + +Use these state meanings: + +- `ready`: every configured merge gate passes. +- `waiting`: a check, review, or GitHub mergeability result is still pending. +- `blocked`: a failure, stale head, conflict, revoked authorization, or owner + action prevents merging. +- `integration required`: queued pull requests share hand-edited source and + must be combined before merging. + +## Change Queue State + +Require the user's exact `deploy` instruction before calling +`enqueue_pull_request` or `deploybot enqueue` for that conversation's pull +request. Do not infer permission from readiness or review completion. + +Use `block_pull_request`, `unblock_pull_request`, or `dequeue_pull_request` only +for the requested pull request and preserve a concrete reason. Never merge an +unlabeled pull request or treat a wake-up event as trusted queue state. + +## Coordinate Merges + +Only the designated coordinator may call `freeze_queue`, `drain_queue`, or the +matching CLI commands. Re-read the queue, freeze one exact batch, preserve +first-in order unless dependencies require otherwise, and skip blocked items +that do not block independent work. + +Merge independent ready pull requests back-to-back. Route source-overlap groups +through one integration pull request instead of resolving the same conflict +several times. Finish by following the newest cumulative base-branch CI and the +repository-owned deployment. diff --git a/adapters/codex/agent-merge-queue/.codex-plugin/plugin.json b/adapters/codex/agent-merge-queue/.codex-plugin/plugin.json index e176ce1..594fb42 100644 --- a/adapters/codex/agent-merge-queue/.codex-plugin/plugin.json +++ b/adapters/codex/agent-merge-queue/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "deploybot", - "version": "0.1.3", + "version": "0.1.4", "description": "Review, queue, and land pull requests through a shared GitHub merge queue", "author": { "name": "DeployBot contributors" diff --git a/adapters/codex/agent-merge-queue/.mcp.json b/adapters/codex/agent-merge-queue/.mcp.json index dbaf6e6..5d2d1cb 100644 --- a/adapters/codex/agent-merge-queue/.mcp.json +++ b/adapters/codex/agent-merge-queue/.mcp.json @@ -4,7 +4,7 @@ "command": "uvx", "args": [ "--from", - "deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@7fe247bd1d1994fafcea21817351b3da7e29f611", + "deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@f18dce436bae2d6b7009d9aa76b4bfc8d0aac5a8", "deploybot-mcp" ] } diff --git a/adapters/codex/agent-merge-queue/skills/deploybot/SKILL.md b/adapters/codex/agent-merge-queue/skills/deploybot/SKILL.md new file mode 100644 index 0000000..6283f85 --- /dev/null +++ b/adapters/codex/agent-merge-queue/skills/deploybot/SKILL.md @@ -0,0 +1,54 @@ +--- +name: deploybot +description: Inspect and operate a DeployBot GitHub merge queue. Use when the user invokes DeployBot, asks for queue or PR status, wants to know what is ready, waiting, blocked, next, dependent, or overlapping, explicitly says deploy, or asks the queue coordinator to freeze or drain authorized pull requests. +--- + +# DeployBot + +Read `.mergequeue.toml` before acting. Prefer the DeployBot MCP tools; fall back +to the `deploybot` CLI. Treat GitHub as the durable source of truth. + +## Read Status + +Keep status checks read-only: + +- For the full queue, call `queue_plan` or run `deploybot status --json`. +- For one pull request, call `inspect_pull_request` or run + `deploybot inspect --json`. +- Never call `freeze_queue` merely to view status because freezing writes a + durable batch marker. + +Report the queue in order. For each pull request, include its state, exact head, +checks or reviews still waiting, blocker, dependencies, and overlap group. Say +plainly when the queue is empty. + +Use these state meanings: + +- `ready`: every configured merge gate passes. +- `waiting`: a check, review, or GitHub mergeability result is still pending. +- `blocked`: a failure, stale head, conflict, revoked authorization, or owner + action prevents merging. +- `integration required`: queued pull requests share hand-edited source and + must be combined before merging. + +## Change Queue State + +Require the user's exact `deploy` instruction before calling +`enqueue_pull_request` or `deploybot enqueue` for that conversation's pull +request. Do not infer permission from readiness or review completion. + +Use `block_pull_request`, `unblock_pull_request`, or `dequeue_pull_request` only +for the requested pull request and preserve a concrete reason. Never merge an +unlabeled pull request or treat a wake-up event as trusted queue state. + +## Coordinate Merges + +Only the designated coordinator may call `freeze_queue`, `drain_queue`, or the +matching CLI commands. Re-read the queue, freeze one exact batch, preserve +first-in order unless dependencies require otherwise, and skip blocked items +that do not block independent work. + +Merge independent ready pull requests back-to-back. Route source-overlap groups +through one integration pull request instead of resolving the same conflict +several times. Finish by following the newest cumulative base-branch CI and the +repository-owned deployment. diff --git a/adapters/codex/agent-merge-queue/skills/deploybot/agents/openai.yaml b/adapters/codex/agent-merge-queue/skills/deploybot/agents/openai.yaml new file mode 100644 index 0000000..085d42a --- /dev/null +++ b/adapters/codex/agent-merge-queue/skills/deploybot/agents/openai.yaml @@ -0,0 +1,9 @@ +interface: + display_name: "DeployBot" + short_description: "Inspect and manage agent merge queues" + default_prompt: "Use $deploybot to show the current merge queue and explain what is ready, waiting, or blocked." +dependencies: + tools: + - type: "mcp" + value: "deploybot" + description: "Durable GitHub merge queue operations" diff --git a/adapters/cursor/.cursor/mcp.json b/adapters/cursor/.cursor/mcp.json index dbaf6e6..5d2d1cb 100644 --- a/adapters/cursor/.cursor/mcp.json +++ b/adapters/cursor/.cursor/mcp.json @@ -4,7 +4,7 @@ "command": "uvx", "args": [ "--from", - "deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@7fe247bd1d1994fafcea21817351b3da7e29f611", + "deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@f18dce436bae2d6b7009d9aa76b4bfc8d0aac5a8", "deploybot-mcp" ] } diff --git a/adapters/cursor/.cursor/rules/deploybot.mdc b/adapters/cursor/.cursor/rules/deploybot.mdc new file mode 100644 index 0000000..16a5541 --- /dev/null +++ b/adapters/cursor/.cursor/rules/deploybot.mdc @@ -0,0 +1,13 @@ +--- +description: Inspect DeployBot queue status, blockers, dependencies, and overlap groups +alwaysApply: false +--- + +Read `.mergequeue.toml` and use the `deploybot` MCP tools. For queue status, +call `queue_plan`; for one pull request, call `inspect_pull_request`. Status +requests must stay read-only: never freeze a queue merely to inspect it. + +Report queue order, readiness, exact heads, pending checks or reviews, blockers, +dependencies, and source overlaps. Only the user's exact `deploy` instruction +authorizes enqueueing that conversation's pull request. Only the designated +coordinator may freeze or drain the queue. diff --git a/adapters/cursor/AGENTS.md b/adapters/cursor/AGENTS.md index c380561..503d924 100644 --- a/adapters/cursor/AGENTS.md +++ b/adapters/cursor/AGENTS.md @@ -4,6 +4,10 @@ Read `.mergequeue.toml` and use the `deploybot` MCP tools. Keep changing pull requests draft and make the final ready head immutable. Address valid feedback from the configured review providers. +For read-only status, use `queue_plan` for the full queue and +`inspect_pull_request` for one pull request. Report order, readiness, blockers, +dependencies, and overlap groups; never freeze the queue just to inspect it. + Only the user's exact `deploy` instruction authorizes enqueueing the current thread's pull request. Never poll, merge an unlabeled pull request, or absorb unrelated work. Merge independent ready work in order, skip blockers, honor diff --git a/examples/github-workflow.yml b/examples/github-workflow.yml index 12acf04..d067e23 100644 --- a/examples/github-workflow.yml +++ b/examples/github-workflow.yml @@ -19,5 +19,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - # v0.1.3; keep the full commit so privileged workflows are immutable. - - uses: Forward-Future/DeployBot@7fe247bd1d1994fafcea21817351b3da7e29f611 + # v0.1.4; keep the full commit so privileged workflows are immutable. + - uses: Forward-Future/DeployBot@f18dce436bae2d6b7009d9aa76b4bfc8d0aac5a8 diff --git a/pyproject.toml b/pyproject.toml index 8663605..256bb42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "deploybot-merge-queue" -version = "0.1.3" +version = "0.1.4" description = "DeployBot: a provider-neutral GitHub merge queue for coding agents" readme = "README.md" license = "MIT" diff --git a/skills/deploybot/SKILL.md b/skills/deploybot/SKILL.md new file mode 100644 index 0000000..6283f85 --- /dev/null +++ b/skills/deploybot/SKILL.md @@ -0,0 +1,54 @@ +--- +name: deploybot +description: Inspect and operate a DeployBot GitHub merge queue. Use when the user invokes DeployBot, asks for queue or PR status, wants to know what is ready, waiting, blocked, next, dependent, or overlapping, explicitly says deploy, or asks the queue coordinator to freeze or drain authorized pull requests. +--- + +# DeployBot + +Read `.mergequeue.toml` before acting. Prefer the DeployBot MCP tools; fall back +to the `deploybot` CLI. Treat GitHub as the durable source of truth. + +## Read Status + +Keep status checks read-only: + +- For the full queue, call `queue_plan` or run `deploybot status --json`. +- For one pull request, call `inspect_pull_request` or run + `deploybot inspect --json`. +- Never call `freeze_queue` merely to view status because freezing writes a + durable batch marker. + +Report the queue in order. For each pull request, include its state, exact head, +checks or reviews still waiting, blocker, dependencies, and overlap group. Say +plainly when the queue is empty. + +Use these state meanings: + +- `ready`: every configured merge gate passes. +- `waiting`: a check, review, or GitHub mergeability result is still pending. +- `blocked`: a failure, stale head, conflict, revoked authorization, or owner + action prevents merging. +- `integration required`: queued pull requests share hand-edited source and + must be combined before merging. + +## Change Queue State + +Require the user's exact `deploy` instruction before calling +`enqueue_pull_request` or `deploybot enqueue` for that conversation's pull +request. Do not infer permission from readiness or review completion. + +Use `block_pull_request`, `unblock_pull_request`, or `dequeue_pull_request` only +for the requested pull request and preserve a concrete reason. Never merge an +unlabeled pull request or treat a wake-up event as trusted queue state. + +## Coordinate Merges + +Only the designated coordinator may call `freeze_queue`, `drain_queue`, or the +matching CLI commands. Re-read the queue, freeze one exact batch, preserve +first-in order unless dependencies require otherwise, and skip blocked items +that do not block independent work. + +Merge independent ready pull requests back-to-back. Route source-overlap groups +through one integration pull request instead of resolving the same conflict +several times. Finish by following the newest cumulative base-branch CI and the +repository-owned deployment. diff --git a/skills/deploybot/agents/openai.yaml b/skills/deploybot/agents/openai.yaml new file mode 100644 index 0000000..97d5ca9 --- /dev/null +++ b/skills/deploybot/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "DeployBot" + short_description: "Inspect and manage agent merge queues" + default_prompt: "Use $deploybot to show the current merge queue and explain what is ready, waiting, or blocked." diff --git a/src/agent_merge_queue/__init__.py b/src/agent_merge_queue/__init__.py index 8a1af4e..519f4da 100644 --- a/src/agent_merge_queue/__init__.py +++ b/src/agent_merge_queue/__init__.py @@ -1,3 +1,3 @@ """DeployBot: a provider-neutral GitHub merge queue for coding agents.""" -__version__ = "0.1.3" +__version__ = "0.1.4" diff --git a/src/agent_merge_queue/cli.py b/src/agent_merge_queue/cli.py index 188e9e2..5a88c84 100755 --- a/src/agent_merge_queue/cli.py +++ b/src/agent_merge_queue/cli.py @@ -1492,6 +1492,10 @@ def build_parser() -> argparse.ArgumentParser: inspect.add_argument("--json", action="store_true", dest="json_output") plan = subparsers.add_parser("plan", help="show the current ordered queue") plan.add_argument("--json", action="store_true", dest="json_output") + status = subparsers.add_parser( + "status", help="show queue readiness, blockers, and overlaps" + ) + status.add_argument("--json", action="store_true", dest="json_output") freeze = subparsers.add_parser("freeze", help="persist one exact queue pass") freeze.add_argument("--json", action="store_true", dest="json_output") drain = subparsers.add_parser( @@ -1535,7 +1539,7 @@ def main(argv: list[str] | None = None) -> int: print("merge queue labels are ready") elif arguments.command == "inspect": command_inspect(client, arguments.pr, json_output=arguments.json_output) - elif arguments.command == "plan": + elif arguments.command in {"plan", "status"}: print_plan(client.queue(), json_output=arguments.json_output) elif arguments.command == "freeze": command_freeze(client, json_output=arguments.json_output) diff --git a/tests/test_cli.py b/tests/test_cli.py index 5f02c52..2a06721 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -26,6 +26,7 @@ latest_batch_marker, latest_marker, marker_queued_at, + main, new_batch, overlap_groups, queue_state_body, @@ -74,6 +75,19 @@ def entry(number: int, *paths: str, state: str = "ready") -> QueueEntry: class QueueCoreTest(unittest.TestCase): + def test_status_is_a_read_only_plan_alias(self) -> None: + queued = [entry(1, "src/app.py")] + with ( + patch("agent_merge_queue.cli.load_config", return_value=CONFIG), + patch("agent_merge_queue.cli.GitHub") as github, + patch("agent_merge_queue.cli.print_plan") as print_plan, + ): + github.return_value.queue.return_value = queued + result = main(["status", "--json"]) + + self.assertEqual(result, 0) + print_plan.assert_called_once_with(queued, json_output=True) + def test_dependency_directive_is_configurable(self) -> None: body = "Queue-after: #12, #14\nBlocked by #99" self.assertEqual(structured_dependencies(body, "Queue-after"), [12, 14]) diff --git a/tests/test_skill.py b/tests/test_skill.py new file mode 100644 index 0000000..0d8ba28 --- /dev/null +++ b/tests/test_skill.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +CANONICAL = ROOT / "skills" / "deploybot" / "SKILL.md" +RELEASE_COMMIT = "f18dce436bae2d6b7009d9aa76b4bfc8d0aac5a8" + + +class DeployBotSkillTest(unittest.TestCase): + def test_skill_is_packaged_for_codex_and_claude(self) -> None: + expected = CANONICAL.read_text(encoding="utf-8") + copies = [ + ROOT + / "adapters" + / "codex" + / "agent-merge-queue" + / "skills" + / "deploybot" + / "SKILL.md", + ROOT + / "adapters" + / "claude-code" + / "skills" + / "deploybot" + / "SKILL.md", + ] + for path in copies: + with self.subTest(path=path): + self.assertEqual(path.read_text(encoding="utf-8"), expected) + + def test_status_guidance_is_read_only(self) -> None: + skill = CANONICAL.read_text(encoding="utf-8") + self.assertIn("deploybot status --json", skill) + self.assertIn("Never call `freeze_queue` merely to view status", skill) + self.assertIn("exact `deploy` instruction", skill) + + def test_cursor_adapter_exposes_status_workflow(self) -> None: + rule = ( + ROOT + / "adapters" + / "cursor" + / ".cursor" + / "rules" + / "deploybot.mdc" + ).read_text(encoding="utf-8") + self.assertIn("queue_plan", rule) + self.assertIn("never freeze a queue merely to inspect it", rule) + + def test_clients_pin_the_immutable_status_release(self) -> None: + paths = [ + ROOT / "adapters" / "codex" / "agent-merge-queue" / ".mcp.json", + ROOT / "adapters" / "claude-code" / ".mcp.json", + ROOT / "adapters" / "cursor" / ".cursor" / "mcp.json", + ROOT / "examples" / "github-workflow.yml", + ] + for path in paths: + with self.subTest(path=path): + self.assertIn(RELEASE_COMMIT, path.read_text(encoding="utf-8")) + + +if __name__ == "__main__": + unittest.main()