diff --git a/CHANGELOG.md b/CHANGELOG.md index 47c094c..8f0f1a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ 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.5.1 — from-plan pass-through for external spec formats + +### Fixed + +- **`/cursor:from-plan` no longer discards plans it cannot parse** (#16). A plan/spec whose headings don't match the Claude plan-mode shape (e.g. files produced by other spec/plan tooling) was reduced to a four-placeholder skeleton — the entire plan body was silently dropped and Cursor received an empty task. Such documents are now embedded **verbatim** in the task file (with the guardrail block still appended), matching the pass-through behaviour the module docs always promised. `SECTION_HINTS` additionally learned common spec-driven headings (`overview`, `problem statement`, `requirements`, `design`, `spec`, `tasks`, `steps`, `testing`, `validation`, `success criteria`), so those map onto the proper task sections instead of falling back. README now documents that `from-plan` accepts any path, not just `~/.claude/plans/`. + ## 0.5.0 — session lifecycle hooks, stop review gate, process-group cancel Second port wave from upstream [`openai/codex-plugin-cc`](https://github.com/openai/codex-plugin-cc) (session tracking, the stop-time review gate, prompt templates, structured review output), adapted to the Cursor CLI and the zero-deps runtime — plus a real cancellation bug found while comparing the two codebases. (#17, #18, #20, #21) diff --git a/README.md b/README.md index f4c9cc0..f5b49ac 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,15 @@ Examples: /cursor:from-plan --list # show the 15 most recent plans ``` +**Not limited to Claude's plan mode.** The `[plan-name]` argument also accepts an absolute or repo-relative **path to any Markdown plan/spec file** — one written by another spec/plan plugin (Superpowers, GSD, …) or by hand: + +``` +/cursor:from-plan ./specs/checkout-flow/plan.md +/cursor:from-plan --delegate docs/rfc-042.md +``` + +Sections named like `Overview`, `Requirements`, `Design`, `Tasks`, or `Testing` are mapped onto the task shape; a document whose structure isn't recognised at all is embedded **verbatim** (with the guardrail block appended) instead of being reduced to placeholders. And if you don't need the task-file conversion, `/cursor:delegate "Implement @specs/checkout-flow/plan.md"` sends the file to Cursor directly. + This is the closest thing to "plan in Claude, execute in Cursor" in one session: Claude does the thinking, Cursor does the typing, and the task file is a durable contract between the two. ### `/cursor:review [flags] [focus...]` diff --git a/plugins/cursor/package-lock.json b/plugins/cursor/package-lock.json index 6917a1e..a4b2c83 100644 --- a/plugins/cursor/package-lock.json +++ b/plugins/cursor/package-lock.json @@ -1,12 +1,12 @@ { "name": "cursor-plugin-cc", - "version": "0.5.0", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cursor-plugin-cc", - "version": "0.5.0", + "version": "0.5.1", "license": "MIT", "devDependencies": { "@eslint/js": "^9.16.0", diff --git a/plugins/cursor/package.json b/plugins/cursor/package.json index 0827f41..623afdd 100644 --- a/plugins/cursor/package.json +++ b/plugins/cursor/package.json @@ -1,6 +1,6 @@ { "name": "cursor-plugin-cc", - "version": "0.5.0", + "version": "0.5.1", "description": "Use Cursor CLI from Claude Code to delegate coding tasks to Composer and other Cursor models.", "type": "module", "license": "MIT", diff --git a/plugins/cursor/plugin.json b/plugins/cursor/plugin.json index b9f0999..4b3caf4 100644 --- a/plugins/cursor/plugin.json +++ b/plugins/cursor/plugin.json @@ -1,6 +1,6 @@ { "name": "cursor", - "version": "0.5.0", + "version": "0.5.1", "description": "Hand off tasks from Claude Code to cursor-agent. Composer-optimised.", "author": { "name": "Tomas Grasl", diff --git a/plugins/cursor/scripts/lib/plan.mjs b/plugins/cursor/scripts/lib/plan.mjs index da692ae..561d789 100644 --- a/plugins/cursor/scripts/lib/plan.mjs +++ b/plugins/cursor/scripts/lib/plan.mjs @@ -176,9 +176,32 @@ export function slugify(s) { return cleaned.slice(0, 50) || 'plan'; } +// Hints are ordered most- to least-specific per intent. The tail of each list +// covers common spec-driven formats from other plan/spec tooling (Superpowers, +// GSD, hand-written specs), not just Claude Code's plan-mode shape. (#16) const SECTION_HINTS = { - context: ['context', 'background', 'why', 'motivation'], - approach: ['approach', 'plan', 'implementation', 'solution', 'design'], + context: [ + 'context', + 'background', + 'why', + 'motivation', + 'problem statement', + 'problem', + 'overview', + 'summary', + ], + approach: [ + 'approach', + 'plan', + 'implementation', + 'solution', + 'design', + 'requirements', + 'specification', + 'spec', + 'tasks', + 'steps', + ], files: [ 'file-by-file change list', 'files to touch', @@ -188,7 +211,16 @@ const SECTION_HINTS = { 'critical files to modify', 'files', ], - verification: ['verification', 'how to verify', 'test plan', 'tests', 'acceptance criteria'], + verification: [ + 'verification', + 'how to verify', + 'test plan', + 'tests', + 'acceptance criteria', + 'testing', + 'validation', + 'success criteria', + ], }; /** @@ -231,6 +263,36 @@ export function buildTaskContent(plan) { lines.push(''); lines.push(`> Generated from Claude Code plan: \`${plan.path}\``); lines.push(''); + + // Pass-through fallback: a plan/spec in a shape we don't recognise (another + // spec plugin's format, a hand-written doc) must never be reduced to four + // placeholder sections — that silently discards the entire plan body. + // Embed the document verbatim instead and let Cursor follow it as written. + if (!context && !approach && !files && !verification) { + const rawBody = String(plan.raw ?? '') + .replace(/^\s*#\s[^\n]*\n/, '') // drop the leading H1 — already emitted above + .trim(); + lines.push('## Task specification'); + lines.push(''); + lines.push( + '_The source plan uses its own structure (not the Claude plan-mode shape); it is included verbatim below — follow it as written._', + ); + lines.push(''); + lines.push(rawBody || '(the source plan file is empty)'); + lines.push(''); + lines.push('## How to verify'); + lines.push(''); + lines.push( + 'Follow any verification steps in the specification above; otherwise:\n\n' + + "- Run the project's test suite (`npm test`, `pnpm test`, `task test`, etc.).\n" + + '- Run the type-check / lint if the project has one.\n' + + '- Manual spot-check of the changed behaviour.', + ); + lines.push(''); + pushConstraints(lines); + return lines.join('\n'); + } + lines.push('## Goal'); lines.push(''); lines.push(plan.title ? plan.title : '(see Context below)'); @@ -258,6 +320,14 @@ export function buildTaskContent(plan) { '- Manual spot-check of the changed behaviour.', ); lines.push(''); + pushConstraints(lines); + return lines.join('\n'); +} + +/** + * @param {string[]} lines + */ +function pushConstraints(lines) { lines.push('## Constraints'); lines.push(''); lines.push( @@ -269,5 +339,4 @@ export function buildTaskContent(plan) { '- Do not modify lockfiles (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`) unless dependencies are part of the task.', ); lines.push(''); - return lines.join('\n'); } diff --git a/plugins/cursor/tests/plan.test.mjs b/plugins/cursor/tests/plan.test.mjs index de79be6..407fcc1 100644 --- a/plugins/cursor/tests/plan.test.mjs +++ b/plugins/cursor/tests/plan.test.mjs @@ -103,6 +103,72 @@ describe('buildTaskContent', () => { expect(out).toContain('just this.'); expect(out).toContain('(no Approach'); }); + + it('passes an unrecognised plan shape through verbatim instead of placeholders', () => { + const raw = [ + '# Externí spec', + '', + '## Etapa 1', + '', + 'Přidat endpoint /health.', + '', + '## Poznámky', + '', + 'Držet se stylu okolního kódu.', + '', + ].join('\n'); + const plan = { + path: '/tmp/external-spec.md', + title: 'Externí spec', + slug: 'externi-spec', + sections: splitSections(raw).sections, + raw, + }; + const out = buildTaskContent(plan); + // The whole body survives… + expect(out).toContain('Přidat endpoint /health.'); + expect(out).toContain('Držet se stylu okolního kódu.'); + expect(out).toContain('follow it as written'); + // …and no placeholder skeleton is emitted. + expect(out).not.toContain('(no Context section'); + expect(out).not.toContain('(no Approach'); + // Guardrails still apply. + expect(out).toContain('## Constraints'); + // The leading H1 is not duplicated. + expect(out.match(/# Externí spec/g)).toHaveLength(1); + }); + + it('maps common spec-driven headings onto the task sections', () => { + const raw = [ + '# Spec-shaped plan', + '', + '## Overview', + '', + 'Why we do this.', + '', + '## Requirements', + '', + '- must do X', + '', + '## Testing', + '', + '- run `npm test`', + '', + ].join('\n'); + const plan = { + path: '/tmp/spec.md', + title: 'Spec-shaped plan', + slug: 'spec-shaped-plan', + sections: splitSections(raw).sections, + raw, + }; + const out = buildTaskContent(plan); + expect(out).toContain('Why we do this.'); + expect(out).toContain('- must do X'); + expect(out).toContain('- run `npm test`'); + expect(out).not.toContain('(no Context section'); + expect(out).not.toContain('(no Approach'); + }); }); describe('resolvePlanPath + parsePlanFile against a temp plans dir', () => {