Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
068a458
Separate Cursor Cloud Agent and Grok Bot identities
cursoragent Sep 5, 2026
bf446eb
Fix CI regressions from provider separation
cursoragent Sep 5, 2026
cbd363b
Merge remote-tracking branch 'origin/main' into cursor/provider-regis…
cursoragent Sep 5, 2026
fbb780f
Merge remote-tracking branch 'origin/main' into cursor/provider-regis…
cursoragent Sep 5, 2026
655958a
Address Codex audit findings: reject reviewer-only providers, remove …
cursoragent Sep 5, 2026
43ab467
Fix Cursor Cloud Agent setup documentation
cursoragent Sep 5, 2026
ac3aeb5
WIP: Update tests for cursor_bugbot validation
cursoragent Sep 5, 2026
74375c7
Update tests for cursor_bugbot capability validation
cursoragent Sep 5, 2026
e712763
Fix remaining test issues
cursoragent Sep 5, 2026
d142fde
Fix documentation and improve capability guard
cursoragent Sep 5, 2026
3149dec
Fix all test failures: update tests to use cursor_cloud_agent for bui…
cursoragent Sep 5, 2026
ae453f4
Fix ruff violations and duplicate assertion
cursoragent Sep 5, 2026
516e423
Fix campaign_discovery tests and complete Gitar cleanups
cursoragent Sep 5, 2026
583e153
Merge remote-tracking branch 'origin/main' into cursor/provider-regis…
cursoragent Sep 5, 2026
a148c33
Merge remote-tracking branch 'origin/main' into cursor/provider-regis…
jeffhuber Sep 5, 2026
97ae596
Update package-source fixtures for Cursor identity split
jeffhuber Sep 5, 2026
1c738b4
Sync campaign provider defaults across all entry points
cursoragent Sep 5, 2026
2446405
Synchronize Cursor campaign defaults
jeffhuber Sep 5, 2026
6a9c94a
Merge remote-tracking branch 'origin/cursor/provider-registry-separat…
jeffhuber Sep 5, 2026
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
35 changes: 23 additions & 12 deletions docs/builders-grok-cursor.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
# Grok Bot And Cursor Cloud Agents
# Cursor Cloud Agent And Grok Bot Separation

Code Mower treats hosted coding agents as **builders**, not reviewer lanes,
until they produce an actual pull request. Reviewer lanes still run after the
PR exists.
Code Mower distinguishes between Cursor's hosted builder and review capabilities:

- `cursor_cloud_agent` is the hosted async builder/executor identity that can take work orders and execute package-install qualification campaigns.
- `cursor_bugbot` (also known as Grok Bot or BugBot) is Cursor's review service and remains informational/manual; it cannot execute work orders.
- `grok_build` is the local Grok Build CLI reviewer lane.

This distinction matters:

- `grok_build` is the local Grok Build CLI reviewer lane.
- `grok_bot` is a hosted/manual builder or orchestrator identity.
- `cursor_cloud_agent` is a hosted async builder/executor identity.
- `cursor_bugbot` is Cursor's reviewer service and remains informational/manual.
- `cursor_cloud_agent` has `role: builder` and `capability: work_order_execution`, qualifying it for release campaigns.
- `cursor_bugbot` has `role: reviewer` and `capability: code_review`, excluding it from builder tasks.

## Provider Aliases and Compatibility

The provider alias map routes spellings to canonical identities:

- `cursor` → `cursor_cloud_agent` (builder)
- `cursor_cloud_agent` → `cursor_cloud_agent` (builder)
- `cursor_bugbot` → `cursor_bugbot` (reviewer)
- `cursor_grok_bot` → `cursor_bugbot` (reviewer)
- `grok_bot` → `cursor_bugbot` (reviewer)

Historical `cursor_bugbot` campaigns created before v1.0.8 remain valid but are not accepted for new release qualification work. The separation preserves existing stored evidence through an explicit compatibility path without silently reinterpreting old data.

## Recommended Current-Beta Flow
## Recommended Current Flow

Use GitHub Issues as the source of truth:

Expand All @@ -26,12 +38,11 @@ code-mower work-order draft \
--output .code-mower/work-orders/example.md
```

Give the work order to Grok Bot, Cursor Cloud Agents, or another hosted
builder. After that builder opens a PR, record source-free provenance:
Give the work order to Cursor Cloud Agent. After it opens a PR, record source-free provenance:

```bash
code-mower builder record \
--provider grok_bot \
--provider cursor_cloud_agent \
--executor cursor_cloud_agent \
--work-order .code-mower/work-orders/example.md \
--pr OWNER/REPO#124 \
Expand Down
21 changes: 19 additions & 2 deletions docs/provider-diversity-provenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,22 @@ This document tracks the tool/model/version provenance metadata for v1.0 provide
- **Bot Authors**: gitar-ai[bot], gitar-bot, gitar-bot[bot]
- **Status**: informational adapter-based lane, opt-in required

### Cursor BugBot
### Cursor Cloud Agent (Builder)
- **Provider**: cursor_cloud_agent
- **Driver**: hosted_bridge
- **Schema**: codeMower.cursorCloudAgentAudit.v1
- **Result Source**: trailer_comment
- **Model Source**: vendor_hidden
- **Version Detection**: vendor_hidden
- **Merge Authority**: true (enabled_by_default=false, manual trigger)
- **Spend Policy**: paid
- **Role**: builder
- **Capability**: work_order_execution
- **Bot Authors**: cursor[bot], cursor
- **Trigger Comments**: "@cursor run", "cursor run"
- **Status**: hosted async builder with release qualification capability; can execute work orders and package-install campaigns

### Cursor BugBot (Reviewer)
- **Provider**: cursor_bugbot
- **Driver**: saas_event
- **Schema**: codeMower.cursorBugbotAudit.v1 (fixture schema)
Expand All @@ -47,9 +62,11 @@ This document tracks the tool/model/version provenance metadata for v1.0 provide
- **Version Detection**: vendor_hidden
- **Merge Authority**: false (informational)
- **Spend Policy**: paid
- **Role**: reviewer
- **Capability**: code_review
- **Bot Authors**: cursor[bot], cursor
- **Trigger Comments**: "bugbot run", "@cursor review"
- **Status**: manual informational lane, calibration-only until output shape captured
- **Status**: manual informational review lane; Grok Bot and BugBot are review surfaces only and cannot execute package-install campaigns

### Antigravity CLI
- **Provider**: antigravity
Expand Down
78 changes: 64 additions & 14 deletions docs/release-qualification.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ No local paths, secrets, commands, or raw output.

## Release Campaigns

The `code-mower release campaign` command coordinates multi-provider qualification across Claude, Codex, Antigravity, Muse, Cursor/Grok Bot, and Devin:
The `code-mower release campaign` command coordinates multi-provider qualification across Claude, Codex, Antigravity, Muse, Cursor Cloud Agent, and Devin:

```bash
code-mower release campaign \
Expand Down Expand Up @@ -167,7 +167,7 @@ other package-index install; see
[Cache Bypass And Propagation Triage](pypi-release.md#cache-bypass-and-propagation-triage).

- **Dry-run by default, applied once and for all:** Omit `--apply` for a safe preview. Add `--apply` for live local execution, GitHub comment dispatch, or paid runs. `applied` is a *monotonic* transition: a dry-run campaign becomes applied the first time it is dispatched with `--apply`, and nothing moves it back. A later `resume` or `--status` poll that simply omits `--apply` is not a claim that the dispatches and attempts already made never happened, so it leaves the campaign (and each dispatched provider's `dispatch_mode`) applied -- in stored state, in the rendered text, and on the Board. Previously such a poll relabelled real evidence as a dry-run preview and regressed the aggregate status to "run with --apply to dispatch providers" for providers that had already been dispatched. A poll still never dispatches anything: only `--apply` does that. The aggregate headline of a preview is held to the same standard as the individual providers it summarizes: `queued` / "run with --apply to dispatch providers" is only reported while at least one provider is genuinely dispatchable. When every provider that is not already complete is `unavailable` -- for a missing `--issue`, a missing `--repo-slug`, missing credentials, or an unconfigured adapter alike -- the campaign reports `unavailable` and the actionable "configure prerequisites for unavailable providers: ..." next action instead of pointing at an `--apply` run that could dispatch nothing. A mixed preview stays `queued`, and its detail line still counts the queued and unavailable providers separately.
- **Provider diversity:** Tracks Claude, Codex, Antigravity, Muse, Cursor/Grok Bot, and Devin. Missing tools, tokens, or adapters -- and a `code-mower.yml` that configures one lane under two spellings -- degrade gracefully to `unavailable` without failing the campaign. Each provider may appear at most once: `--providers` is canonicalized before any participant is built, so naming the same provider twice -- directly, or through two aliases of one lane such as `cursor` and `grok_bot` -- is rejected with an explicit error instead of creating two participants that share a single idempotency key and result path (which would let one provider's evidence count twice).
- **Provider diversity:** Tracks Claude, Codex, Antigravity, Muse, Cursor Cloud Agent, and Devin. Missing tools, tokens, or adapters -- and a `code-mower.yml` that configures one lane under two spellings -- degrade gracefully to `unavailable` without failing the campaign. Each provider may appear at most once: `--providers` is canonicalized before any participant is built, so naming the same provider twice -- directly, or through two aliases of one lane such as `cursor` and `cursor_cloud_agent` -- is rejected with an explicit error instead of creating two participants that share a single idempotency key and result path (which would let one provider's evidence count twice).
- **Idempotent resume:** Pass `--resume` to re-poll running providers or advance queued participants without duplicating dispatch or re-invoking an adapter that already completed. Once a provider's applied dispatch/adapter has been attempted (even if it failed or its outcome was uncertain), ordinary resume never repeats it automatically -- pass `--retry-provider <provider>` to explicitly retry that one provider. `--retry-provider` is rejected unless the named provider is already part of the campaign.
- **A hosted dispatch is checkpointed as pollable before it is posted:** Posting the dispatch comment is an external side effect that the campaign cannot undo and cannot re-observe, so everything a later resume needs is persisted *first*: the attempt (`attempted_at`), the `running` state, the issue the comment is addressed to (in `dispatch_ref`), the applied dispatch mode, and the matching campaign status and next action. A process killed anywhere around the post therefore leaves a campaign that an ordinary `--resume` polls to a conclusion against the original issue -- accepting the trusted, identity-bound result if the comment did get posted and answered -- and that never reposts on its own. Previously the campaign recorded only `attempted_at` and stayed `queued` until the post returned, which resume neither polled (not running) nor redispatched (already attempted): the provider stalled until an explicit retry posted a second comment for a dispatch that may well have succeeded. The checkpoint never claims the post succeeded -- `dispatched_at` is stamped and `dispatch_ref` is replaced with the returned dispatch metadata only when the post returns successfully; a dispatch that fails in-process records the usual `github_dispatch_failed` unavailable result. If nothing was ever posted, resume simply keeps polling and finds nothing, and dispatching again still requires an explicit `--retry-provider` (which, being explicit, may post a second comment). A retry that cannot dispatch for a prerequisite reason -- no `--issue`, missing credentials -- leaves an outstanding dispatch `running` and pollable rather than demoting it to `unavailable`, since refusing to dispatch reveals nothing about the comment already posted.
- **Never reinitialized:** An existing campaign is never replaced by a fresh queued one. Repeating the same invocation (same `--release-tag` or `--campaign-id`, with or without `--resume`) advances the stored campaign under resume semantics, so a repeated `--apply` never reruns a local adapter, reposts a hosted dispatch, or discards recorded provider state and evidence. The explicit `create` action fails when that campaign already exists, and `resume`/`dispatch` fail when it does not -- neither falls through to creating one. A request that asks for two actions at once is refused rather than resolved to one of them: an action may be spelled with the equivalent legacy flag (`status` with `--status`, `resume`/`dispatch` with `--resume`), but combining an action with a flag naming a *different* action -- `create --resume` above all -- exits non-zero with a bounded conflict message before any campaign lookup, directory creation, lock, state write, adapter run, dispatch, or poll, so the rejected request leaves nothing behind. Previously `create --resume` reached the command body with both intents live and was answered by whichever branch tested its flag first, reporting "no existing campaign to resume" for an explicit `create`. Creation arguments that describe a different campaign (`--package-spec`, `--providers`, `--qualification-context`, `--starting-version`, `--package-source`, or a `--campaign-id`/`--release-tag` pair that disagree) are rejected explicitly rather than silently ignored. `--qualification-context` and `--package-source` are each compared whenever supplied, including an explicit `--qualification-context cold_install` or `--package-source pypi` against a stored campaign with the same value: neither flag has a default value of its own, so an omitted flag (which advances the stored campaign under its own context/source, and creates a `cold_install`/`pypi` campaign when there is none) is distinguishable from an explicitly requested default, and an explicit default is never silently ignored. A campaign created without `--package-source` (or an explicit `pypi`) stays `pypi`; resuming it with `--package-source testpypi` is rejected as an identity conflict, not a resume, exactly like a conflicting `--qualification-context`. A campaign stored before this field existed reads back as `pypi`, its documented default. `--repo-slug` is the one field an existing campaign can still be *completed* with: a campaign created without a repository slug has nowhere to dispatch, so supplying `--repo-slug` on a later `resume`/`dispatch` fills the empty stored value and persists it before any hosted dispatch uses it. A `--repo-slug` that disagrees with a non-empty stored slug is rejected like any other identity change -- an in-flight campaign is never repointed at a different repository.
Expand Down Expand Up @@ -248,16 +248,16 @@ Hosted / SaaS providers (`hosted_bridge`/`saas_event` driver: Devin, Cursor BugB
- The provider's reply comment must embed a `CODE_MOWER_ADOPTION_RESULT` marker as a single-line HTML comment on a line of its own (`<!-- CODE_MOWER_ADOPTION_RESULT: {...} -->`), wrapping schema `code_mower.releaseCampaignResult.v1` with `campaign_id`, `provider`, `release_tag`, and `idempotency_key` matching the original dispatch, plus a validated `adoption_result`. A bare or unbound result is ignored so a stale or unrelated comment can never be replayed as evidence. The embedded `adoption_result`'s own `qualification_context` and `starting_version` must also match the campaign's exactly, independent of the wrapper's idempotency key -- a cold-install result cannot complete an upgrade campaign, and an upgrade result from one starting version cannot complete a same-tag upgrade campaign from a different starting version. The marker line is matched end to end and its JSON is captured through the object's own final brace, so a literal `-->` inside a permitted string value cannot truncate an otherwise valid trusted result; a marker whose JSON is genuinely malformed is still ignored (fail-closed), never guessed at.
- These identity fields are visible in the public dispatch comment, so binding alone does not prove authorship -- anyone could reply with a matching marker. A result marker is only ever accepted from a GitHub comment author present in the lane's `provider_config.bot_authors` list (and, if configured, the comma-separated login list in the environment variable named by `provider_config.bot_authors_env`). A lane with no trusted authors configured trusts nobody; an untrusted or spoofed author's comment is ignored and the provider keeps running.

#### Cursor BugBot Setup
#### Cursor Cloud Agent Setup

Cursor BugBot (also known as Cursor/Grok Bot, or Cursor Cloud Agents) is a hosted SaaS provider using the `saas_event` driver.
Cursor Cloud Agent is a hosted async builder using the `hosted_bridge` driver.

**Prerequisites:**
- GitHub App authorization for Cursor in your repository
- `CURSOR_BUGBOT_AUDIT_LABEL_TOKEN` (or `GITHUB_TOKEN` as fallback) for applying audit labels
- `CURSOR_CLOUD_AGENT_AUDIT_LABEL_TOKEN` (or `GITHUB_TOKEN` as fallback) for applying audit labels
- `GITHUB_TOKEN` for posting dispatch comments
- After verifying that the installed App answers campaign issue comments, set
`CODE_MOWER_CURSOR_BUGBOT_CAMPAIGN_TRANSPORT_READY=1`. Without it, doctor and
`CODE_MOWER_CURSOR_CLOUD_AGENT_CAMPAIGN_TRANSPORT_READY=1`. Without it, doctor and
Board report the transport as unverified, but an explicit `--apply` may still
dispatch it under the response deadline below. Token presence alone proves
comment permission, not that the App supports this transport.
Expand All @@ -267,29 +267,79 @@ Cursor BugBot (also known as Cursor/Grok Bot, or Cursor Cloud Agents) is a hoste
- `cursor`

**Environment override:**
Set `CURSOR_BUGBOT_BOT_AUTHORS` to a comma-separated list of additional trusted GitHub logins. This extends (does not replace) the default trusted authors, allowing self-hosted or alternative Cursor integrations to be trusted.
Set `CURSOR_CLOUD_AGENT_BOT_AUTHORS` to a comma-separated list of additional trusted GitHub logins. This extends (does not replace) the default trusted authors, allowing self-hosted or alternative Cursor integrations to be trusted.

**Trigger comments:**
- `bugbot run`
- `@cursor review`
- `@cursor run`
- `cursor run`

**Role and capability:**
- `role: builder`
- `capability: work_order_execution`
- Can execute work orders and package-install campaigns

**Response timeout:**
- 3600 seconds (1 hour)

After the dispatch comment is posted, one of these trigger commands is posted as a separate comment to actually start the BugBot qualification run.
Code Mower binds the hidden dispatch and trigger markers to separate locally persisted random nonces. The trigger nonce is never exposed by the earlier dispatch, so an interrupted resume can reconcile its own comments without trusting forgeable public fields or starting the provider twice. Reconciliation is read-only; retrying a missing trigger requires `--resume --apply`.
**Campaign dispatch:**
The campaign posts a GitHub issue comment with schema `code_mower.releaseCampaignDispatch.v1` containing:
- `campaign_id`
- `provider`: `cursor_cloud_agent`
- `release_tag`
- `package_spec`
- `qualification_context` (cold_install/upgrade/unknown)
- `starting_version` (for upgrade campaigns only)
- `idempotency_key`

Cursor Cloud Agent replies with a comment containing `<!-- CODE_MOWER_ADOPTION_RESULT: {...} -->` wrapping schema `code_mower.releaseCampaignResult.v1`. The embedded `adoption_result` must match the campaign's provider, release tag, package identity, qualification context, and (for upgrades) starting version.

**Trusted authors:**
- `cursor[bot]`, `cursor` (registry defaults)
- Override via `CURSOR_CLOUD_AGENT_BOT_AUTHORS` environment variable

**Example dispatch:**
```bash
code-mower release campaign \
--release-tag v1.0.0 \
--package-spec code-mower==1.0.0 \
--providers cursor_bugbot \
--providers cursor_cloud_agent \
--issue 123 \
--repo-slug owner/repo \
--apply
```

**Aliases:** `cursor`, `cursor_bugbot`, `cursor_grok_bot`, `cursor_cloud_agent`, `grok_bot` all resolve to the canonical `cursor_bugbot` provider.
**Aliases:**
- `cursor` → `cursor_cloud_agent`
- `cursor_cloud_agent` → `cursor_cloud_agent`

**Note:** Cursor Cloud Agent is an opt-in paid provider (`enabled_by_default: false`, `trigger_policy: manual`, `spend_policy: paid`). It must be explicitly requested via `--providers cursor_cloud_agent` or `--providers cursor`.

#### Cursor BugBot / Grok Bot Setup

Cursor BugBot and Grok Bot are **review-only** hosted providers using the `saas_event` driver. They provide code review and cannot execute package-install qualification campaigns.

**Prerequisites:**
- `CURSOR_BUGBOT_AUDIT_LABEL_TOKEN` (or `GITHUB_TOKEN` as fallback) for applying audit labels
- `GITHUB_TOKEN` for posting review trigger comments

**Trigger comments** (for review, not campaigns):
- `bugbot run`
- `@cursor review`

**Trusted authors:**
- `cursor[bot]`, `cursor` (registry defaults)
- Override via `CURSOR_BUGBOT_BOT_AUTHORS` environment variable

**Aliases:**
- `cursor_bugbot` → `cursor_bugbot`
- `cursor_grok_bot` → `cursor_bugbot`
- `grok_bot` → `cursor_bugbot`

**Note:** Cursor BugBot is an opt-in paid review provider. It is review-only with `role: reviewer` and `capability: code_review`. It cannot participate in release qualification campaigns.

#### Historical Note

**Note:** Cursor BugBot is an opt-in paid provider (`enabled_by_default: false`, `trigger_policy: manual`, `spend_policy: paid`). It must be explicitly requested via `--providers cursor_bugbot` and is not included in the default provider set.
Before v1.0.8, `cursor_bugbot` was used for both builder and review capabilities. As of v1.0.8, `cursor_cloud_agent` is the builder identity and `cursor_bugbot` is review-only.

#### Devin Setup

Expand Down
2 changes: 1 addition & 1 deletion src/code_mower/doctor_checks/adoption.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ def check_adoption_setup(
"codex",
"antigravity",
"muse",
"cursor_bugbot",
"cursor_cloud_agent",
"devin",
)

Expand Down
Loading
Loading