Skip to content

feat(from-plan): make the task destination and section mapping format-agnostic - #24

Open
willywg wants to merge 3 commits into
freema:mainfrom
willywg:feat/agnostic-task-dir
Open

feat(from-plan): make the task destination and section mapping format-agnostic#24
willywg wants to merge 3 commits into
freema:mainfrom
willywg:feat/agnostic-task-dir

Conversation

@willywg

@willywg willywg commented Jul 31, 2026

Copy link
Copy Markdown

Summary

Follow-up to #16 (and #23). /cursor:from-plan assumes 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 and Validation Loop → verification all match, so the 0.5.1 verbatim fallback (which requires zero matches) never fires, and Goal, What / success criteria and All Needed Context — including its CRITICAL: 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. tasksDir is 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 different PRPs/ that doesn't exist. Worse, in that layout --out-dir PRPs silently created a second, disconnected PRPs/ tree inside the wrong repo, and --in-place refused 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-cc never makes that assumption: it has no from-plan, generates no task file, reads specs via --prompt-file (codex-companion.mjs:645) without ever writing one, and confines every write to CLAUDE_PLUGIN_DATA/state/ (state.mjs:29). The tasks/ 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

  • Never drop an unmapped section. Leftovers are carried through under ## Additional specification context, preserving the author's original heading casing (splitSections now also returns a headings map). Only reviewer-facing commentary stays on an explicit drop-list: Effort / risks, Open questions, Alternatives considered, Notes to reviewer. A ## Goal section fills the Goal slot instead of the task file echoing its own title.
  • Derive the destination from the spec. Point the command at a project spec and the task file is written next to it — PRPs/018-wizard.mdPRPs/<stamp>-018-wizard.md. Spec-driven projects need no configuration at all. New isPlanModeFile() distinguishes a project spec from one of Claude's ~/.claude/plans/ files.
  • Multi-repo as a first-class case. New referenceFor(): @path inside the repo, absolute path outside it. cursor-agent reads 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-place outside-the-repo guard is dropped as unnecessary.
  • Config demoted to a fallback. Resolution order: --out-dir → the spec's own directory → CURSOR_PLUGIN_CC_TASKS_DIR → per-repo tasksDirtasks/. The last three apply only to plan-mode files. /cursor:setup --tasks-dir <dir> / --no-tasks-dir persists 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 existing plan.test.mjs assertions (including not.toContain('Effort / risks')) pass untouched.

Happy to reshape any of this — e.g. drop the /cursor:setup key 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; referenceFor inside/outside/name-prefix-sibling; and end-to-end runs through main() for the same-repo, spec-derived, and cross-repo cases)
  • npm run lint — prettier + eslint clean
  • npm run typechecktsc --checkJs clean (JSDoc updated for headings, the config shape, and the new helpers)
  • Smoke-tested the real multi-repo layout: two git repos, PRPs 016–018 in the monorepo, command run from the sibling code repo. The task file joined its siblings in the monorepo; the code repo got no PRPs/ and no tasks/; --in-place succeeded where it previously errored; the CRITICAL: gotcha reached the generated task file.
  • Verified cursor-agent reads an absolute path outside its repo before relying on it (cursor-agent --print --force from repo A reading a file in repo B returned the file's heading).
  • Docs updated — README.md, commands/from-plan.md, commands/setup.md, CHANGELOG.md (## Unreleased)

Checklist

  • No new runtime dependencies — node stdlib only (node:path)
  • No build step, no new dist/ or .ts files
  • $ARGUMENTS is quoted in every edited commands/*.md
  • No new slash command (flags on existing ones)
  • User-visible behaviour change noted under ## Unreleased in CHANGELOG.md

willywg added 3 commits July 31, 2026 10:24
…-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
…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
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.

Integrate to other spec/plan

1 participant