Skip to content

feat(adapters): add Cursor CLI (cursor-agent) profile - #3

Open
thecontstruct wants to merge 10 commits into
mainfrom
feat/cursor-cli-provider
Open

thecontstruct wants to merge 10 commits into
mainfrom
feat/cursor-cli-provider

Conversation

@thecontstruct

@thecontstruct thecontstruct commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Why

Cursor users need a path that needs no Node runtime, alongside the SDK provider. The cursor profile drives interactive cursor-agent through the existing tmux and hook transport, so it needs no adapter class.

What

  • New cursor-hooks-json hook dialect (adapters/profile.py, install.py). Cursor reads a project .cursor/hooks.json with a numeric top-level version and bare {"command": …} entries. Cursor 3.x loads no hooks from a project file that has no version, so merge_hooks always writes one.
  • Packaged profile data/profiles/cursor.toml:
    • stop maps to Stop, and sessionStart marks liveness. There is no SessionEnd.
    • Skills live in .cursor/skills/.
    • usage_parser = "none".
  • Workspace trust. The profile launches with --force --trust. An interactive launch in an untrusted directory blocks on a trust dialog. --force alone does not clear it, and neither does seeding Cursor's .workspace-trusted marker. Trust is granted per launch, so isolation = "worktree" works. [adapter] extra_args replaces the bypass flags, so it must keep --trust.
  • Relay command. Hooks register upstream's bmad-loop relay <Event> command (Wave 7b: register relay through the installed console script bmad-code-org/bmad-loop#825), and the cursor dialect wraps it as-is.
  • CLI name lists. cursor is added to the name lists in cli.py help and the policy.py template.
  • Docs: README, FEATURES, adapter-authoring guide, setup guide (including the uninstall list), CHANGELOG. They list the profile as supported and E2E-verified, with no token usage recorded yet.

Dropped since the first draft

  • seed_workspace_trust() and seed_workspace_trust = true. Writing the trust marker did not clear the trust dialog, so --trust replaced it.

Testing

  • Merged with upstream main at 8237188b on 2026-09-22.
  • Full suite on macOS: 10,758 passed, 165 skipped, 7 failed.
  • Known macOS failures: the 7 test failures and 2 pyright errors (os.setxattr and os.getxattr in platform_util.py) also fail on unmodified upstream main on macOS. CI runs on Linux.
  • New and updated tests:
    • A new test_install_into_cursor runs a real init --cli cursor. It fails when the cursor dialect branch is removed.
    • Cursor is added to upstream's per-dialect tests in tests/test_install.py and tests/test_probe.py.
  • Sandbox checks with a temporary HOME, spending no tokens:
    • init writes a versioned .cursor/hooks.json with bare sessionStart and stop entries.
    • Feeding the registered Stop command a Cursor payload writes one Stop event, with session_id taken from conversation_id.
    • probe-adapter cursor in scan mode, with a fake binary, reports that hooks are registered.
  • Earlier live runs:
    • Story e2e with review disabled, ending in verify and commit (2026-07-08).
    • Story e2e with review.enabled = true and trigger = always: a separate review session completed, then verify and commit (2026-07-09).
    • probe-adapter cursor --probe with 0 warnings, followed by a one-story dev loop (1 done, 0 deferred, 0 escalated) on cursor-agent 2026.09.02.

Known limits

  • There is no token accounting (usage_parser = "none"). The Stop payload names a transcript, but its token schema has not been read.
  • How the hooks behave when the network drops mid-turn has not been measured.
  • The cursor.toml header refers to the cursor-cli-headless profile, which lands in feat(adapters): add cursor-cli-headless provider (Path B) #4.

Made with Cursor

@thecontstruct thecontstruct self-assigned this Jul 9, 2026
@thecontstruct
thecontstruct marked this pull request as ready for review July 9, 2026 04:27
thecontstruct pushed a commit that referenced this pull request Aug 8, 2026
…arial-review findings (bmad-code-org#276)

A second-model adversarial review of PR bmad-code-org#277 surfaced 7 valid findings. This
lands the fixes for six of them (the 7th, a repo-wide policy-coercion
consistency issue, is deferred to a follow-up):

- #1 (High) M2 transition now outranks the M1 hash gate. A clean review that
  round-trips done->in-review->done back to byte-identical launch bytes while
  omitting its marker no longer false-refuses; the hash gate refuses only when
  no transition was observed. Safe: the dead-window false positive never records
  a transition (status never leaves `done`).
- #2 (High) The stories-mode folder+id read-back now applies the same
  launch-snapshot gate, closing the identical false completion it previously
  bypassed (it accepted on the mtime floor alone). Inert on a dev leg (no
  snapshot).
- #3 (High) The three in-place spec rewriters (append/strip/reset) now write
  atomically (temp + atomic_replace), so an interrupted/disk-full repair leaves
  the original spec intact instead of truncated.
- #4 (Med) Adds engine-level coverage of the synthesized-REVIEW repair path
  (including a follow-up review that rewrites the spec) plus regressions for
  #1/#2/#3/bmad-code-org#5/bmad-code-org#7 and a _snapshot_verdict truth-table.
- bmad-code-org#5 (Low) Snapshot/candidate identity is now by resolved filesystem path
  (_same_spec), not raw string spelling, so a `..`/symlink/case alias can't
  disable M1/M2.
- bmad-code-org#7 (Low) A spec ending in a bare `\r` no longer receives an invisible heading
  (completed to CRLF so the scan's `^` regex sees it).

#1 and #2 share a single `_snapshot_verdict` helper so the "M2 outranks M1"
rule can't drift between the two paths. All edits sit on `_DevSynthesisMixin`,
covering both the tmux and OpenCode adapters.
@thecontstruct
thecontstruct force-pushed the feat/cursor-cli-provider branch from 4be70d1 to f01b7eb Compare August 8, 2026 01:25
@thecontstruct
thecontstruct force-pushed the feat/cursor-cli-provider branch from f01b7eb to b0f345c Compare August 24, 2026 01:27
thecontstruct and others added 3 commits September 3, 2026 15:50
bmad-loop could not drive Cursor. This adds a packaged `cursor` profile and a
`cursor-hooks-json` hook dialect, so the generic adapter runs it with no Python.

Skills load from .cursor/skills/. The relay registers `sessionStart` and `stop`
in a project .cursor/hooks.json. That file is versioned and its entries are bare
{"command": ...} objects, so merge_hooks always writes a top-level `version`:
Cursor 3.x loads none of a project file's hooks without one, which reads as a
session timeout rather than as an error anyone can see.

Launch flags are `--force --trust`. Measured against cursor-agent 2026.08.04,
each in a fresh untrusted git dir launched interactively under tmux:

  bare                -> blocks on the workspace-trust dialog
  --force             -> still blocks
  --force --trust     -> runs
  seeded trust marker -> still blocks

That last row is why this drops the earlier approach of writing Cursor's
~/.cursor/projects/<slug>/.workspace-trusted marker. The marker is real, but
replaying a byte-correct copy does not satisfy the gate, so that code did not do
what it claimed. Dropping it also removes the `seed_workspace_trust` profile
field, the install-time writer, and the only code that wrote outside the
project. Because `--trust` applies per launch, isolation = "worktree" works
here, unlike antigravity.

The prompt is handed over as an argv positional, so a leading "/" never reaches
Cursor's slash menu. The template names the SKILL.md outright, as codex and
copilot do.

usage_parser stays "none". The Stop payload names a transcript, but its token
schema is unread, so nothing is claimed about it.

Marked experimental: no full dev/review loop has been run end to end. Finalize
with `probe-adapter cursor`.

Co-authored-by: Cursor <cursoragent@cursor.com>
The profile told the next reader to finalize it with `probe-adapter cursor
--probe` before trusting it unattended. That probe has now run — 0 warnings,
both hook events captured live — and a one-story dev loop then completed end
to end with a real commit, so the EXPERIMENTAL warning is stale and goes.

The probe also settled the open token question, and the answer is that
`usage_parser` can never fix it. Cursor puts the counts on the Stop hook
payload (input_tokens, output_tokens, cache_read_tokens, cache_write_tokens)
and puts no token fields in the transcript at all, so a parser handed a
transcript path has nothing to read whatever it is set to. The counts do reach
this machine and are then dropped: the shared relay narrows every payload to
ts/event/task_id/session_id/transcript_path/cwd. Wiring them up therefore
means widening a contract every provider shares, not editing this profile.
Recording that here stops the next reader retrying it as a cursor-local fix.

Also note what has NOT been measured. Completion here comes from hooks rather
than parsed stdout, so this profile does not depend on the terminal result
frame that a dropped agent stream takes away from the print/stream-json
transports. But the dev loop above ran on a healthy network path, so how these
hooks behave when the stream drops mid-turn is untested, and the note says so
rather than implying immunity.
@thecontstruct
thecontstruct force-pushed the feat/cursor-cli-provider branch from 2107b35 to c900451 Compare September 4, 2026 17:21
thecontstruct and others added 5 commits September 22, 2026 15:22
Co-authored-by: Cursor <cursoragent@cursor.com>
…mmand

Upstream bmad-code-org#825 replaced the copied bmad_loop_hook.py with an absolute
`bmad-loop relay <Event>` command. The cursor tests still built the old
command, so they never checked that Cursor's bare entries carry the new
form or that the stale-relay strip recognizes it.

- Cursor merge/strip/idempotency tests now use the installed relay form.
- Add test_install_into_cursor: real `init --cli cursor` writes a
  versioned .cursor/hooks.json whose entries hold only "command".
- Add cursor to the per-dialect tests for fresh init, legacy flat-hook
  migration, and probe hook registration.

Co-authored-by: Cursor <cursoragent@cursor.com>
Upstream now lists each CLI's hook config to edit on uninstall. Cursor
was missing, so a cursor user had no pointer to the file holding the relay.

Co-authored-by: Cursor <cursoragent@cursor.com>
The cursor rows widened three markdown tables without realigning them,
and taplo collapses the short seed_files array. Whitespace only.

Co-authored-by: Cursor <cursoragent@cursor.com>
The profile header already recorded the graduation: probe-adapter cursor --probe passed with 0 warnings and a one-story dev loop completed with a real commit on cursor-agent 2026.09.02. README, FEATURES, setup guide and CHANGELOG still called it experimental. They now say supported, E2E-verified, and note that no token usage is recorded yet.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant