feat(from-plan): make the task destination and section mapping format-agnostic - #24
Open
willywg wants to merge 3 commits into
Open
feat(from-plan): make the task destination and section mapping format-agnostic#24willywg wants to merge 3 commits into
willywg wants to merge 3 commits into
Conversation
…-agnostic `/cursor:from-plan` hardcoded `tasks/` as the output directory and silently dropped any section the four plan-mode intents did not claim. Together these made the plugin assume Claude's plan-mode shape, so spec-driven workflows (PRP / Context Engineering, Spec Kit, OpenSpec) both grew a redundant `tasks/` tree and lost the most load-bearing half of their document. A PRP is the clearest case: `Why` -> context, `Implementation Blueprint` -> approach and `Validation Loop` -> verification all match, so the v0.5.1 verbatim fallback (which requires *zero* matches) never fires, and `Goal`, `What` / success criteria and `All Needed Context` — including its `CRITICAL:` gotchas — were dropped with no warning. Changes: - Carry every unclaimed section through under `## Additional specification context`, preserving the author's original heading casing. Only explicit reviewer-facing commentary (`Effort / risks`, `Open questions`, `Alternatives considered`) is still dropped. - Map a `## Goal` section onto the Goal slot instead of echoing the title. - Resolve the output directory as `--out-dir` -> `CURSOR_PLUGIN_CC_TASKS_DIR` -> per-repo `tasksDir` config -> `tasks/`. Relative values resolve against the repo root so the destination is stable from any subdirectory. - Add `/cursor:setup --tasks-dir <dir>` (and `--no-tasks-dir`) to persist it per repo, surfaced in the doctor report. - Add `--in-place`, which delegates the source spec as written and creates no task file, for workflows where the spec already is the task. Refuses a spec outside the repo, since Cursor resolves `@path` from the repo root. Defaults are unchanged: with no flag, no env var and no config, the output is still `tasks/` and existing plan-mode conversions are byte-identical. Refs freema#16
…es under Unreleased
…rt multi-repo
The previous commit made `tasks/` configurable, but configuration turned out to
be the wrong instrument. The `tasksDir` key is stored per repository, keyed by a
hash of the CWD's repo — so a developer with one central spec directory and N
service repos would have to set it N times, and each setting would point at a
different, non-existent `PRPs/` inside the wrong repo.
Concretely, with PRPs kept in a monorepo and code in a sibling repo:
- `--out-dir PRPs` silently created a second, disconnected `PRPs/` tree inside
the code repo, unrelated to the real 001-017.
- `--in-place` refused outright, because of the guard added in the previous
commit requiring the spec to live inside the repo.
The faulty assumption was not the directory string; it was that the spec and the
code share a repository. Upstream `openai/codex-plugin-cc` — which this plugin
was ported from — never makes that assumption: it has no from-plan, generates no
task file, reads specs via `--prompt-file` without ever writing one, and confines
every write to `CLAUDE_PLUGIN_DATA/state/`. The `tasks/` directory is an addition
of this fork.
Changes:
- Derive the destination from the source spec: when the plan is a project spec
(not one of Claude's `~/.claude/plans/` files), write the task file next to it.
A spec already knows where it belongs, and that answer stays correct from any
repository. New `isPlanModeFile()` draws the distinction.
- Resolution order is now `--out-dir` -> the spec's directory -> env ->
per-repo config -> `tasks/`; the last three apply only to plan-mode files.
- Add `referenceFor()`: `@path` inside the repo, absolute path outside it.
`cursor-agent` reads absolute paths fine, so a spec in another repo is
reachable; the prompt tells Cursor to apply its changes in the current repo.
- Drop the `--in-place` outside-the-repo refusal, now unnecessary.
Spec-driven projects need no configuration at all, and the multi-repo layout
works out of the box. Plan-mode output is unchanged.
Refs freema#16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #16 (and #23).
/cursor:from-planassumes Claude's plan-mode shape in two ways: it silently drops any section the four intents don't claim, and it hardcodes where the task file goes. This PR fixes both, and the second fix turned out to need more than a config knob.Section loss. A PRP (Context Engineering format) is the clearest case — full repro in #16:
Why→ context,Implementation Blueprint→ approach andValidation Loop→ verification all match, so the 0.5.1 verbatim fallback (which requires zero matches) never fires, andGoal,What/ success criteria andAll Needed Context— including itsCRITICAL:gotchas — are discarded with no warning. In that example Cursor is told to build the feature with the "SSR flashes light theme" warning removed.Where the task file goes. I first made this a config key, then hit the real problem while using it.
tasksDiris stored per repository, keyed by a hash of the CWD's repo. A developer keeping PRPs centralised in one monorepo while the code lives in sibling service repos would have to set it once per repo, and every setting would point at a differentPRPs/that doesn't exist. Worse, in that layout--out-dir PRPssilently created a second, disconnectedPRPs/tree inside the wrong repo, and--in-placerefused outright.The faulty assumption wasn't the directory string — it was that the spec and the code share a repository. Notably, upstream
openai/codex-plugin-ccnever makes that assumption: it has nofrom-plan, generates no task file, reads specs via--prompt-file(codex-companion.mjs:645) without ever writing one, and confines every write toCLAUDE_PLUGIN_DATA/state/(state.mjs:29). Thetasks/directory is an addition of this fork, not something inherited — so this PR moves back toward the upstream posture rather than away from it.Changes
## Additional specification context, preserving the author's original heading casing (splitSectionsnow also returns aheadingsmap). Only reviewer-facing commentary stays on an explicit drop-list:Effort / risks,Open questions,Alternatives considered,Notes to reviewer. A## Goalsection fills the Goal slot instead of the task file echoing its own title.PRPs/018-wizard.md→PRPs/<stamp>-018-wizard.md. Spec-driven projects need no configuration at all. NewisPlanModeFile()distinguishes a project spec from one of Claude's~/.claude/plans/files.referenceFor():@pathinside the repo, absolute path outside it.cursor-agentreads absolute paths fine (verified), so a spec in another repo is reachable, and the prompt tells Cursor to apply its changes in the current repository. The--in-placeoutside-the-repo guard is dropped as unnecessary.--out-dir→ the spec's own directory →CURSOR_PLUGIN_CC_TASKS_DIR→ per-repotasksDir→tasks/. The last three apply only to plan-mode files./cursor:setup --tasks-dir <dir>/--no-tasks-dirpersists it, surfaced in--doctor.Plan-mode output is unchanged. With no spec path, no flag, no env var and no config, output still lands in
tasks/and conversions are byte-identical — the existingplan.test.mjsassertions (includingnot.toContain('Effort / risks')) pass untouched.Happy to reshape any of this — e.g. drop the
/cursor:setupkey and keep flag + env only, or split the section-loss fix from the destination work into two PRs if you'd rather review them separately.Closes #16
Test plan
npm test— 151 specs green (24 new, covering: section leftovers and the still-dropped commentary;isPlanModeFile; the five-level destination precedence;referenceForinside/outside/name-prefix-sibling; and end-to-end runs throughmain()for the same-repo, spec-derived, and cross-repo cases)npm run lint— prettier + eslint cleannpm run typecheck—tsc --checkJsclean (JSDoc updated forheadings, the config shape, and the new helpers)PRPs/and notasks/;--in-placesucceeded where it previously errored; theCRITICAL:gotcha reached the generated task file.cursor-agentreads an absolute path outside its repo before relying on it (cursor-agent --print --forcefrom repo A reading a file in repo B returned the file's heading).README.md,commands/from-plan.md,commands/setup.md,CHANGELOG.md(## Unreleased)Checklist
node:path)dist/or.tsfiles$ARGUMENTSis quoted in every editedcommands/*.md## UnreleasedinCHANGELOG.md