diff --git a/AGENTS.md b/AGENTS.md index d031a54..26b8e88 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,14 +4,14 @@ This file is the contract any agent (Claude Code, Cursor, Codex, …) must follo ## What this repo is -A Claude Code plugin that delegates coding tasks from Claude to the Cursor CLI (`cursor-agent`). Nine slash commands under the `cursor:` namespace plus a `cursor-runner` subagent. Source of truth lives under `plugins/cursor/`. +A Claude Code plugin that delegates coding tasks from Claude to the Cursor CLI (`cursor-agent`). Eleven slash commands under the `cursor:` namespace, a `cursor-runner` subagent, and a `composer-prompting` skill. Source of truth lives under `plugins/cursor/`. ## Hard rules 1. **Zero runtime dependencies.** The plugin ships as plain ESM `.mjs` and must execute directly after `/plugin install` with zero `npm install` in the user's plugin cache. If you are about to add `execa`, `zod`, `nanoid`, a HTTP client, a command parser, or any other third-party runtime package — stop. Write a small inline helper instead. See `plugins/cursor/scripts/lib/run.mjs` as the reference pattern (it replaced `execa` in ~80 lines). 2. **No build step.** No TypeScript, no bundler, no `dist/`. `scripts/*.mjs` IS the ship artefact. If you find yourself wanting one, something has gone wrong with the approach. -3. **Slash command scripts live under `plugins/cursor/scripts/.mjs`.** Their wrappers at `plugins/cursor/commands/.md` must use `node "${CLAUDE_PLUGIN_ROOT}/scripts/.mjs" -- "$ARGUMENTS"` with quoted `$ARGUMENTS` — unquoted breaks under zsh on any prompt containing `?`, `*`, or `@`. -4. **`Bash(node:*)` is the only permission pattern used in `allowed-tools`.** Do not invent path-based patterns — Claude Code does not expand `${CLAUDE_PLUGIN_ROOT}` inside `allowed-tools`. +3. **Slash command scripts live under `plugins/cursor/scripts/.mjs`.** Their wrappers at `plugins/cursor/commands/.md` must use `node "${CLAUDE_PLUGIN_ROOT}/scripts/.mjs" -- "$ARGUMENTS"` with quoted `$ARGUMENTS` — unquoted breaks under zsh on any prompt containing `?`, `*`, or `@`. Exception: `review.md` and `adversarial-review.md` are model-orchestrated (they estimate the diff and ask wait-vs-background before running), so they give Claude the `node …` command in a fenced block rather than an auto-executing `!` line, and `adversarial-review` reuses `review.mjs --adversarial` instead of shipping its own script. +4. **`Bash(node:*)` is the only permission pattern used in `allowed-tools`.** Do not invent path-based patterns — Claude Code does not expand `${CLAUDE_PLUGIN_ROOT}` inside `allowed-tools`. Exception: the two estimate-first review commands additionally list `Bash(git:*)`, `AskUserQuestion`, and `Read, Glob, Grep` for the size-estimate/ask step — those are tool-name patterns, not path-based ones, so they are fine. 5. **Jobs are persisted under `~/.cursor-plugin-cc/jobs//`.** Never break that layout; users point scripts at those files when reporting bugs. 6. **Language: everything in this repo is English.** Code, comments, commit messages, docs, PR bodies, issue titles. The plugin does not impose a language policy on target repos — `cursor-runner` reads target-repo conventions — but this repo itself is English-only. 7. **Do not impose conventions on target repos.** The `cursor-runner` subagent reads `AGENTS.md` / `.cursor/rules` / existing code in whatever repo the user is working in and tells Cursor to match THAT style. When editing the subagent, do not hardcode English / Prettier / whatever. @@ -46,10 +46,11 @@ Plus a **Constraints** block that forbids: touching files outside the list, rena ## Where things live -- `plugins/cursor/scripts/.mjs` — command entrypoints (9). -- `plugins/cursor/scripts/lib/*.mjs` — shared helpers (run, id, args, paths, jobs, parse, cursor, git, invoked, plan). +- `plugins/cursor/scripts/.mjs` — command entrypoints (10; `adversarial-review` has no script of its own — it reuses `review.mjs --adversarial`). +- `plugins/cursor/scripts/lib/*.mjs` — shared helpers (run, id, args, paths, jobs, parse, cursor, git, invoked, plan, hints, md). - `plugins/cursor/commands/*.md` — slash command wrappers. - `plugins/cursor/agents/cursor-runner.md` — the handoff subagent prompt. +- `plugins/cursor/skills/composer-prompting/SKILL.md` — Cursor prompt-shaping guidance the `cursor-runner` subagent references via its `skills:` frontmatter. - `plugins/cursor/tests/*.test.mjs` — vitest specs + fixtures. - `.claude-plugin/marketplace.json` — what Claude Code's `/plugin install` reads. diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e74218..8430ce2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.4.0 — /cursor:adversarial-review + estimate-first reviews + composer-prompting skill + +Ported from upstream [`openai/codex-plugin-cc`](https://github.com/openai/codex-plugin-cc) (whose `/codex:adversarial-review`, estimate-first review flow, and `gpt-5-4-prompting` skill this release mirrors), adapted to the Cursor CLI. + +### Added + +- **`/cursor:adversarial-review`** — a first-class steerable review command that challenges the chosen implementation and design (assumptions, tradeoffs, failure modes, whether a different approach would be simpler or safer), not just implementation defects. It reuses the existing review runtime (`scripts/review.mjs --adversarial`), so it supports `--base `, `--scope`, `--model`, `--wait`/`--background`, and free-form focus text, and is tracked as a normal job (`/cursor:status`, `/cursor:result`, `/cursor:cancel` all apply). Promotes what used to be only the `--adversarial` flag on `/cursor:review` into a discoverable command with sharper framing. +- **`composer-prompting` skill** — the Cursor/Composer prompt-shaping guidance (repo grounding, the five-section prompt anatomy + guardrails, chunking heuristics, model selection, resume-vs-fresh) now lives in `plugins/cursor/skills/composer-prompting/SKILL.md`. The `cursor-runner` subagent references it via a new `skills:` frontmatter entry instead of restating the mechanics inline, and the main thread can consult it when hand-crafting `/cursor:delegate` prompts. Mirrors codex's internal `gpt-5-4-prompting` skill. + +### Changed + +- **`/cursor:review` and `/cursor:adversarial-review` estimate the diff before running.** When neither `--wait` nor `--background` is passed, the command inspects `git status` / `git diff --shortstat` to gauge review size, then asks once (via `AskUserQuestion`) whether to wait or run in the background — recommending background for anything beyond a tiny 1–2 file change. Explicit `--wait` / `--background` skip the question. The commands moved from an auto-executing one-liner wrapper to a model-orchestrated flow; their `allowed-tools` now include `Bash(git:*)` and `AskUserQuestion` for the estimate step. Background runs still use the plugin's own detached worker (the script returns a job id immediately), not a Claude background task. +- **`cursor-runner` subagent slimmed** — the prompt-anatomy, chunking, model, and resume/fresh sections were extracted into the `composer-prompting` skill; the agent now points at the skill and keeps only its operational spine (ground → invoke `/cursor:delegate` → return verbatim) and guardrails. + ## 0.3.2 — clearer "job not found" hint ### Fixed diff --git a/README.md b/README.md index 8455079..dda6498 100644 --- a/README.md +++ b/README.md @@ -117,11 +117,12 @@ What lives where after a run: ## What you get -Ten slash commands under the `cursor:` namespace: +Eleven slash commands under the `cursor:` namespace: - **`/cursor:delegate`** — hand a coding task to Cursor, foreground or background. - **`/cursor:from-plan`** — turn a Claude Code plan (from plan mode) into a `tasks/.md` and hand it off to Cursor. - **`/cursor:review`** — read-only code review of your git diff by a Cursor model. Reports findings; never edits files. +- **`/cursor:adversarial-review`** — steerable review that challenges the design and approach (assumptions, tradeoffs, failure modes), not just implementation defects. Read-only. - **`/cursor:browser`** — verify a URL / flow in a real browser via Cursor's `chrome-devtools` MCP. - **`/cursor:status`** — list recent jobs or inspect a specific one. - **`/cursor:result`** — print the final output of a finished job. @@ -130,7 +131,7 @@ Ten slash commands under the `cursor:` namespace: - **`/cursor:sessions`** — list Cursor's own chat sessions for this repo. - **`/cursor:setup`** — health-check the CLI, list models + configured MCPs, or guide installation. -Plus a `cursor-runner` subagent you can invoke from inside Claude to delegate well-scoped tasks automatically. +Plus a `cursor-runner` subagent you can invoke from inside Claude to delegate well-scoped tasks automatically, and a `composer-prompting` skill it uses to shape well-specified tasks into tight Cursor prompts. ## Why this plugin @@ -205,11 +206,13 @@ Read-only code review of your git diff by a Cursor model. The plugin collects th By default it picks the target automatically: a dirty working tree is reviewed as-is; a clean tree falls back to a branch diff against the detected default branch. Any trailing text is passed as a reviewer **focus**. +**Wait or background?** If you don't pass `--wait` or `--background`, the command first estimates the diff size (`git status` / `git diff --shortstat`) and asks once whether to wait for the result or run it in the background — recommending background for anything beyond a tiny 1–2 file change, since a multi-file review can take a while. Pass `--wait` or `--background` explicitly to skip the question. + | Flag | Default | Effect | | ------------------------------------ | ----------------- | ------------------------------------------------------------------------------------------------- | | `--base ` | auto | Review the branch diff `...HEAD` (merge-base) instead of the working tree. | | `--scope auto\|working-tree\|branch` | `auto` | Force the target. `working-tree` = uncommitted changes; `branch` = vs the detected default branch. | -| `--adversarial` | off | Challenge the design and assumptions, not just implementation defects. | +| `--adversarial` | off | Challenge the design and assumptions, not just implementation defects. Prefer the dedicated [`/cursor:adversarial-review`](#cursoradversarial-review-flags-focus) command; this flag is kept for backward compatibility. | | `--model ` | `auto` | Same aliases as `/cursor:delegate`. Use `gpt`/`opus`/`gemini` for a deeper review. | | `--background` | off | Detach; returns a job id immediately. Read it later with `/cursor:result`. | | `--wait` | on | Block until the review finishes (default unless `--background`). | @@ -228,6 +231,21 @@ Examples: This is a **second opinion**, not a replacement for Claude reviewing the diff in-session. Reach for it when you want a different model's eyes on the change, or to offload a large review while Claude keeps orchestrating. +### `/cursor:adversarial-review [flags] [focus...]` + +A **steerable** sibling of `/cursor:review` that questions the chosen implementation and design rather than only hunting implementation defects. Use it to pressure-test assumptions, tradeoffs, failure modes, and whether a different approach would have been simpler or safer — for example before shipping a change you are not fully sure about. + +It uses the **same review-target selection** as `/cursor:review` (working tree by default, `--base ` for a branch diff, `--scope`, `--model`, `--wait`/`--background`) and, like `/cursor:review`, estimates the diff and asks wait-vs-background when you don't pass an explicit mode. Any trailing text is the reviewer **focus** — use it to point the challenge at a specific risk area. It is read-only and never edits files; the same post-flight check applies. + +``` +/cursor:adversarial-review # challenge the working-tree diff +/cursor:adversarial-review --base main # challenge this branch vs main +/cursor:adversarial-review "is the retry/backoff design sound under load?" +/cursor:adversarial-review --background --model opus look for race conditions and question the approach +``` + +Under the hood it is `/cursor:review --adversarial`, so it shows up as a normal job in `/cursor:status`, `/cursor:result`, and `/cursor:cancel`. + ### `/cursor:browser ` Verify a page or a flow in a **real browser** via Cursor's `chrome-devtools` MCP. This is read-only by design — Cursor navigates, interacts, checks console/network and reports back; it will not modify your source files. diff --git a/plugins/cursor/agents/cursor-runner.md b/plugins/cursor/agents/cursor-runner.md index fc11ea3..96e9408 100644 --- a/plugins/cursor/agents/cursor-runner.md +++ b/plugins/cursor/agents/cursor-runner.md @@ -2,6 +2,8 @@ name: cursor-runner description: Hand off a well-specified coding task to the Cursor CLI (`cursor-agent`) via `/cursor:delegate`. Use for small-to-medium, well-scoped changes where speed matters (default model `composer-2.5-fast`). Do NOT use this agent for code review, design decisions, or large refactors — those stay with the main Claude conversation. tools: [Bash, Read] +skills: + - composer-prompting --- You are the **cursor-runner** subagent. Your single job is to delegate a concrete coding task to Cursor CLI and then report the outcome back to the main Claude conversation. You are a forwarder, not an implementer. @@ -19,61 +21,19 @@ Your job is step 2 only. Never do steps 1, 3, or 4 yourself. ## What you must do -### 1. Read the target repo's conventions before writing the prompt +### 1. Shape the prompt with the `composer-prompting` skill -Cursor has no conversation context — whatever the target repo expects, you must bake into the prompt you send. **Use `Read` (only) to check for:** +Use the **`composer-prompting`** skill to turn the main thread's spec into a tight Cursor prompt. It is the source of truth for: -- `AGENTS.md`, `CLAUDE.md`, `.cursor/rules/**`, `.github/copilot-instructions.md`, `CONTRIBUTING.md` — convention files. -- `package.json` / `Taskfile.yml` / `Makefile` / `justfile` — to learn which commands build and test the project. -- `README.md` — for the overall project goal (one sentence is enough). +- **Grounding** — read the target repo's `AGENTS.md` / `CLAUDE.md` / `.cursor/rules` / conventions and verify commands with `Read` (only) before writing the prompt, and match the repo's own language and style. +- **Prompt anatomy** — the five required sections (Goal, Repo context, Acceptance criteria, Files to touch, How to verify) plus the guardrails block. +- **Chunking** — refuse a monolithic blob; split anything bigger than ~5 steps / ~10 files / 2 layers into one slice per `/cursor:delegate` call. +- **Model choice** — default `composer-2.5-fast`; escalate only with a reason. +- **Resume vs fresh** — continue the same thread or start clean. -**Language and style follow the target repo, not this plugin.** If the target repo's commits, comments, or UI strings are in Czech / German / any other language, Cursor must match — do not force English. If the repo is mixed (e.g. code in English, user-facing copy in Czech), say so explicitly in the prompt. When in doubt, tell Cursor: "match the existing style of surrounding files." +Use the skill only to shape the forwarded prompt. Do not use it to review the diff, draft a solution, or do independent work of your own. -### 2. Write a tight, self-contained prompt for Cursor - -Every prompt you send **must** have these sections, in this order: - -1. **Goal** — one or two sentences. What is the outcome? What is this a step of, if anything? -2. **Repo context** — 1–2 lines: stack / framework, and "follow conventions in `AGENTS.md` / `.cursor/rules` / whichever you actually found." -3. **Acceptance criteria** — 1–5 bullet points, concrete and verifiable. -4. **Files to touch** — an explicit list. Unless the task inherently cannot predict this, Cursor must not wander outside it. -5. **How to verify** — the exact commands that prove the task is done (e.g. `npm test`, `task typecheck && task test`, `pnpm lint`). This is not optional — without it Cursor will declare "done" on unverified work. -6. **Guardrails** — short and blunt: - - Do not delete files outside the list. - - Do not rename public APIs unless asked. - - Do not touch lockfiles (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`) unless the task is explicitly about dependencies. - - If a pre-existing test is already failing, report it — do not "fix" it as a side task. - -### 3. Chunk oversized plans before delegating - -`cursor-agent --force` will YOLO through anything you hand it. That is the point — and also the risk. **Refuse to delegate a single monolithic blob of work.** Heuristics: - -- If the plan has **more than ~5 discrete steps**, split into one `/cursor:delegate` call per step (or per coherent slice). -- If the plan touches **more than ~10 files** or crosses **more than 2 architectural layers**, ask the main Claude to narrow the slice first. -- If you cannot name the acceptance criteria in ≤ 5 bullets, the slice is still too big. - -Small slices give Cursor a tight scope, make the diff reviewable, and make failures cheap to retry. - -### 4. Pick a model - -Default is `composer-2.5-fast` — Cursor's own current default and the fastest Composer variant. Escalate only when the task warrants it: - -- `composer-2.5` (non-fast) — quality matters slightly more than latency, but the task is still well-scoped. -- `sonnet` (`claude-4.6-sonnet-medium`) — more than ~5 files touched, or moderate architecture changes. -- `opus` (`claude-opus-4-7-high`) — cross-cutting refactor, subtle correctness, or a prior `composer` run failed. -- `gpt` / `codex` (`gpt-5.3-codex`) — only when the user explicitly asks for it. - -Unknown aliases are forwarded as-is, so `--model ` always works. - -### 5. Decide: resume or fresh - -- **`--resume`** (default when not specified): continue the latest Cursor chat for this repo. Use it when you are **iterating on the same task** — e.g. "also cover the 429 path", "rename the helper you just added". Cheap, preserves Cursor's mental model. -- **`--resume=`**: same as above but target a specific prior chat — use when `/cursor:status` or the user pointed you at one explicitly. -- **`--fresh`**: start a brand-new Cursor session. Use it when **the new task has nothing to do with the previous one**, or when the previous run went off the rails and resuming would just carry the confusion forward. - -When in doubt: fresh if the task topic changed, resume if it's the same thread of work. - -### 6. Invoke `/cursor:delegate` via a single `Bash` call +### 2. Invoke `/cursor:delegate` via a single `Bash` call ```bash node "${CLAUDE_PLUGIN_ROOT}/scripts/delegate.mjs" \ @@ -82,7 +42,7 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/delegate.mjs" \ Use `--background` only if the user explicitly asked for it, or the task obviously exceeds ~5 minutes. -### 7. Return Cursor's output verbatim +### 3. Return Cursor's output verbatim Do not paraphrase the summary, do not rewrite the file list, do not hide the chat id. The main Claude will read the diff and decide what comes next. diff --git a/plugins/cursor/commands/adversarial-review.md b/plugins/cursor/commands/adversarial-review.md new file mode 100644 index 0000000..5173c06 --- /dev/null +++ b/plugins/cursor/commands/adversarial-review.md @@ -0,0 +1,65 @@ +--- +description: Steerable code review that challenges the design and approach, not just implementation defects. Read-only; never edits files. +argument-hint: '[--wait|--background] [--base ] [--scope auto|working-tree|branch] [--model ] [focus...]' +disable-model-invocation: true +allowed-tools: Read, Glob, Grep, Bash(node:*), Bash(git:*), AskUserQuestion +--- + +Run an **adversarial** Cursor review through the shared review runtime (`scripts/review.mjs --adversarial`). +Position it as a challenge review that questions the chosen implementation, design choices, tradeoffs, and assumptions — it is not just a stricter pass over implementation defects. + +Raw slash-command arguments: +`$ARGUMENTS` + +Core constraint: + +- This command is **review-only**. +- Do not fix issues, apply patches, or suggest that you are about to make changes. +- Your only job is to run the review and return Cursor's output verbatim to the user. +- Keep the framing focused on whether the current approach is the right one, what assumptions it depends on, and where the design could fail under real-world load, concurrency, or edge cases. + +Execution mode rules: + +- If the raw arguments include `--wait`, do not ask. Run in the foreground. +- If the raw arguments include `--background`, do not ask. Run in the background. +- Otherwise, estimate the review size before asking: + - For working-tree review, start with `git status --short --untracked-files=all`. + - For working-tree review, also inspect both `git diff --shortstat --cached` and `git diff --shortstat`. + - For base-branch review, use `git diff --shortstat ...HEAD`. + - Treat untracked files or directories as reviewable work for auto or working-tree review even when `git diff --shortstat` is empty. + - Only conclude there is nothing to review when the relevant scope is actually empty. + - Recommend waiting only when the scoped review is clearly tiny, roughly 1-2 files total and no sign of a broader directory-sized change. + - In every other case, including unclear size, recommend background. + - When in doubt, run the review instead of declaring that there is nothing to review. +- Then use `AskUserQuestion` exactly once with two options, putting the recommended option first and suffixing its label with `(Recommended)`: + - `Wait for results` + - `Run in background` + +Argument handling: + +- Preserve the user's arguments exactly. +- Do not strip `--wait` or `--background` yourself — pass them through untouched; the script reads them. +- Do not weaken the adversarial framing or rewrite the user's focus text. +- Uses the same review-target selection as `/cursor:review`: working-tree review, branch review, and `--base `. +- Unlike `/cursor:review`, it takes extra free-form focus text after the flags (e.g. "question the retry/backoff design"). + +Foreground flow: + +- Run: + +```bash +node "${CLAUDE_PLUGIN_ROOT}/scripts/review.mjs" --adversarial --wait -- "$ARGUMENTS" +``` + +- Return the command stdout verbatim, exactly as-is — it is a code review. Do not paraphrase, summarize, or add commentary before or after it. +- Do not fix any issues mentioned in the review output. + +Background flow: + +- Run the SAME command with `--background` instead of `--wait`, as a normal (foreground) `Bash` call — `review.mjs` detaches the worker itself and returns a job id immediately, so do **not** set `run_in_background`: + +```bash +node "${CLAUDE_PLUGIN_ROOT}/scripts/review.mjs" --adversarial --background -- "$ARGUMENTS" +``` + +- Present the returned job id and the `/cursor:status` / `/cursor:result` hints exactly as printed. Do not poll or wait for completion in this turn. diff --git a/plugins/cursor/commands/review.md b/plugins/cursor/commands/review.md index 8227e07..9b35b46 100644 --- a/plugins/cursor/commands/review.md +++ b/plugins/cursor/commands/review.md @@ -1,9 +1,62 @@ --- description: Read-only code review of your git diff by a Cursor model. Reports findings; never edits files. -argument-hint: '[--background] [--wait] [--adversarial] [--base ] [--scope auto|working-tree|branch] [--model ] [--timeout ] [focus...]' -allowed-tools: Bash(node:*) +argument-hint: '[--wait|--background] [--base ] [--scope auto|working-tree|branch] [--model ] [--adversarial] [focus...]' +disable-model-invocation: true +allowed-tools: Read, Glob, Grep, Bash(node:*), Bash(git:*), AskUserQuestion --- -!`node "${CLAUDE_PLUGIN_ROOT}/scripts/review.mjs" -- "$ARGUMENTS"` +Run a read-only Cursor code review of the git diff through the shared review runtime (`scripts/review.mjs`). -Render the tool output to the user verbatim — it is a code review, do not paraphrase or summarise it, and do not act on the findings yourself. If the job ran in the foreground, present the **Review** section as-is. If it was started in the background, show the returned job id and the `/cursor:status` / `/cursor:result` hints. This command is review-only: never apply the fixes it suggests unless the user explicitly asks in a follow-up. +Raw slash-command arguments: +`$ARGUMENTS` + +Core constraint: + +- This command is **review-only**. +- Do not fix issues, apply patches, or suggest that you are about to make changes. +- Your only job is to run the review and return Cursor's output verbatim to the user. + +Execution mode rules: + +- If the raw arguments include `--wait`, do not ask. Run the review in the foreground. +- If the raw arguments include `--background`, do not ask. Run the review in the background. +- Otherwise, estimate the review size before asking: + - For working-tree review, start with `git status --short --untracked-files=all`. + - For working-tree review, also inspect both `git diff --shortstat --cached` and `git diff --shortstat`. + - For base-branch review, use `git diff --shortstat ...HEAD`. + - Treat untracked files or directories as reviewable work for auto or working-tree review even when `git diff --shortstat` is empty. + - Only conclude there is nothing to review when the relevant scope is actually empty. + - Recommend waiting only when the review is clearly tiny, roughly 1-2 files total and no sign of a broader directory-sized change. + - In every other case, including unclear size, recommend background. + - When in doubt, run the review instead of declaring that there is nothing to review. +- Then use `AskUserQuestion` exactly once with two options, putting the recommended option first and suffixing its label with `(Recommended)`: + - `Wait for results` + - `Run in background` + +Argument handling: + +- Preserve the user's arguments exactly. +- Do not strip `--wait` or `--background` yourself — pass them through untouched; the script reads them. +- Do not add extra review instructions or rewrite the user's intent. +- For a steerable review that challenges the design and approach — not just implementation defects — use [`/cursor:adversarial-review`](adversarial-review.md) instead. (The `--adversarial` flag on this command still works for backward compatibility.) + +Foreground flow: + +- Run: + +```bash +node "${CLAUDE_PLUGIN_ROOT}/scripts/review.mjs" --wait -- "$ARGUMENTS" +``` + +- Return the command stdout verbatim, exactly as-is — it is a code review. Do not paraphrase, summarize, or add commentary before or after it. If the run touched the working tree, the script surfaces a post-flight warning — keep it. +- Do not fix any issues mentioned in the review output. + +Background flow: + +- Run the SAME command with `--background` instead of `--wait`, as a normal (foreground) `Bash` call — `review.mjs` detaches the worker itself and returns a job id immediately, so do **not** set `run_in_background`: + +```bash +node "${CLAUDE_PLUGIN_ROOT}/scripts/review.mjs" --background -- "$ARGUMENTS" +``` + +- Present the returned job id and the `/cursor:status` / `/cursor:result` hints exactly as printed. Do not poll or wait for completion in this turn. diff --git a/plugins/cursor/package.json b/plugins/cursor/package.json index 71ca2a8..49b4d2a 100644 --- a/plugins/cursor/package.json +++ b/plugins/cursor/package.json @@ -1,6 +1,6 @@ { "name": "cursor-plugin-cc", - "version": "0.3.2", + "version": "0.4.0", "description": "Use Cursor CLI from Claude Code to delegate coding tasks to Composer and other Cursor models.", "type": "module", "license": "MIT", @@ -31,7 +31,8 @@ "plugin.json", "scripts", "commands", - "agents" + "agents", + "skills" ], "scripts": { "test": "vitest run", diff --git a/plugins/cursor/plugin.json b/plugins/cursor/plugin.json index cbb5825..688a6c0 100644 --- a/plugins/cursor/plugin.json +++ b/plugins/cursor/plugin.json @@ -1,6 +1,6 @@ { "name": "cursor", - "version": "0.3.2", + "version": "0.4.0", "description": "Hand off tasks from Claude Code to cursor-agent. Composer-optimised.", "author": { "name": "Tomas Grasl", diff --git a/plugins/cursor/scripts/review.mjs b/plugins/cursor/scripts/review.mjs index b4681b4..b850f8f 100644 --- a/plugins/cursor/scripts/review.mjs +++ b/plugins/cursor/scripts/review.mjs @@ -39,9 +39,9 @@ function parseFlags(argv) { return { focus, model, background, wait, adversarial, base, scope, timeout, noGitCheck, worker }; } -function buildReviewPrompt({ label, body, focus, adversarial }) { +export function buildReviewPrompt({ label, body, focus, adversarial }) { const role = adversarial - ? 'You are a skeptical senior engineer running an ADVERSARIAL design review. Pressure-test the approach itself, not only the implementation defects.' + ? 'You are a skeptical senior engineer running an ADVERSARIAL review. Challenge whether the chosen implementation and design are the right approach — question assumptions, tradeoffs, and failure modes, not only the implementation defects.' : 'You are a senior code reviewer.'; const lines = [role, '', `**Review target:** ${label}`]; if (focus) lines.push('', `**Reviewer focus (prioritise this):** ${focus}`); @@ -60,6 +60,7 @@ function buildReviewPrompt({ label, body, focus, adversarial }) { if (adversarial) { lines.push( '- Challenge the design: is this the right approach? What assumptions does it rest on? Where does it break under real-world load, concurrency, or edge cases?', + '- Would a different approach be simpler, safer, or cheaper to maintain? If so, say what and why — but stay grounded in the diff, do not invent requirements.', ); } lines.push( diff --git a/plugins/cursor/skills/composer-prompting/SKILL.md b/plugins/cursor/skills/composer-prompting/SKILL.md new file mode 100644 index 0000000..48b347f --- /dev/null +++ b/plugins/cursor/skills/composer-prompting/SKILL.md @@ -0,0 +1,76 @@ +--- +name: composer-prompting +description: Internal guidance for shaping a well-specified coding task into a tight Cursor/Composer prompt before delegating it via /cursor:delegate +user-invocable: false +--- + +# Composer Prompting + +Use this skill when the `cursor-runner` subagent (or the main Claude thread) needs to turn a well-specified coding task into a prompt for the Cursor CLI (`cursor-agent`, Composer by default). + +Cursor has **no conversation context** — whatever the target repo expects, you must bake into the prompt you send. Prompt Composer like a fast executor with a precise contract, not a collaborator you can clarify with mid-run. State the goal, the exact end state, the files it may touch, and how "done" is verified. + +## Ground the prompt in the target repo first + +Before writing the prompt, use `Read` (only) to check the target repo for: + +- `AGENTS.md`, `CLAUDE.md`, `.cursor/rules/**`, `.github/copilot-instructions.md`, `CONTRIBUTING.md` — convention files. +- `package.json` / `Taskfile.yml` / `Makefile` / `justfile` — to learn which commands build and test the project. +- `README.md` — for the overall project goal (one sentence is enough). + +**Language and style follow the target repo, not this plugin.** If the repo's commits, comments, or UI strings are in Czech / German / any other language, Composer must match — do not force English. If the repo is mixed (code in English, user copy in Czech), say so explicitly. When in doubt, tell Cursor: "match the existing style of surrounding files." + +## Prompt anatomy — the five sections + +Every prompt you send **must** have these sections, in this order: + +1. **Goal** — one or two sentences. What is the outcome? What is this a step of, if anything? +2. **Repo context** — 1–2 lines: stack / framework, and "follow conventions in `AGENTS.md` / `.cursor/rules` / whichever you actually found." +3. **Acceptance criteria** — 1–5 bullet points, concrete and verifiable. +4. **Files to touch** — an explicit list. Unless the task inherently cannot predict this, Composer must not wander outside it. +5. **How to verify** — the exact commands that prove the task is done (e.g. `npm test`, `task typecheck && task test`, `pnpm lint`). Not optional — without it Composer will declare "done" on unverified work. + +Then a **Guardrails** block, short and blunt: + +- Do not delete files outside the list. +- Do not rename public APIs unless asked. +- Do not touch lockfiles (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`) unless the task is explicitly about dependencies. +- If a pre-existing test is already failing, report it — do not "fix" it as a side task. + +## Chunk oversized plans before delegating + +`cursor-agent --force` will YOLO through anything you hand it. That is the point — and also the risk. **Refuse to delegate a single monolithic blob of work.** Heuristics: + +- More than **~5 discrete steps** → split into one `/cursor:delegate` call per step (or per coherent slice). +- More than **~10 files** or crossing **more than 2 architectural layers** → ask the main Claude to narrow the slice first. +- If you cannot name the acceptance criteria in ≤ 5 bullets, the slice is still too big. + +Small slices give Composer a tight scope, make the diff reviewable, and make failures cheap to retry. + +## Pick a model + +Default is `composer-2.5-fast` — Cursor's own current default and the fastest Composer variant. Escalate only when the task warrants it: + +- `composer-2.5` (non-fast) — quality matters slightly more than latency, but the task is still well-scoped. +- `sonnet` (`claude-4.6-sonnet-medium`) — more than ~5 files touched, or moderate architecture changes. +- `opus` (`claude-opus-4-7-high`) — cross-cutting refactor, subtle correctness, or a prior `composer` run failed. +- `gpt` / `codex` (`gpt-5.3-codex`) — only when the user explicitly asks for it. + +Unknown aliases are forwarded as-is, so `--model ` always works. Do not escalate without a reason — `composer-2.5-fast` is the default for speed and cost. + +## Resume or fresh + +- **`--resume`** (default when not specified): continue the latest Cursor chat for this repo. Use it when **iterating on the same task** — "also cover the 429 path", "rename the helper you just added". Cheap, preserves Composer's mental model. +- **`--resume=`**: same, but target a specific prior chat — when `/cursor:status` or the user pointed you at one explicitly. +- **`--fresh`**: start a brand-new Cursor session. Use it when **the new task has nothing to do with the previous one**, or when the previous run went off the rails and resuming would just carry the confusion forward. + +When in doubt: fresh if the task topic changed, resume if it's the same thread of work. + +## Assembly checklist + +1. Ground the prompt in the target repo's conventions and verify commands. +2. Write the five sections plus the guardrails block, in order. +3. Chunk anything bigger than one reviewable slice. +4. Pick the smallest model that fits; default to `composer-2.5-fast`. +5. Decide resume vs fresh. +6. Remove redundant instructions before sending. diff --git a/plugins/cursor/tests/review.test.mjs b/plugins/cursor/tests/review.test.mjs index 4870a60..264c3c4 100644 --- a/plugins/cursor/tests/review.test.mjs +++ b/plugins/cursor/tests/review.test.mjs @@ -2,7 +2,7 @@ import { execFileSync } from 'node:child_process'; import { writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { main as reviewMain } from '../scripts/review.mjs'; +import { main as reviewMain, buildReviewPrompt } from '../scripts/review.mjs'; import { listJobs } from '../scripts/lib/jobs.mjs'; import { REVIEW_HAPPY_FIXTURE, @@ -118,6 +118,33 @@ describe('review', () => { expect(job.summary).toMatch(/read-only/i); }); + it('adversarial flag runs end-to-end and completes', async () => { + initRepo(tmp.dir); + writeFileSync(join(tmp.dir, 'foo.ts'), 'export const foo = 42;\n'); + const outSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true); + try { + const code = await reviewMain(['--wait', '--adversarial']); + expect(code).toBe(0); + } finally { + outSpy.mockRestore(); + } + const job = listJobs(tmp.dir)[0]; + expect(job.status).toBe('done'); + }); + + it('buildReviewPrompt: adversarial framing only appears with the flag', () => { + const args = { label: 'working tree on main', body: '## Diff\n\n(none)', focus: '' }; + const plain = buildReviewPrompt({ ...args, adversarial: false }); + const adv = buildReviewPrompt({ ...args, adversarial: true }); + expect(plain).toContain('senior code reviewer'); + expect(plain).not.toMatch(/adversarial/i); + expect(adv).toMatch(/adversarial/i); + expect(adv).toMatch(/challenge the design/i); + // Both remain strictly read-only. + expect(plain).toMatch(/READ-ONLY/i); + expect(adv).toMatch(/READ-ONLY/i); + }); + it('supports --base for a branch diff', async () => { initRepo(tmp.dir); const baseBranch = gitOut(tmp.dir, ['rev-parse', '--abbrev-ref', 'HEAD']);