| name | project-editor-profile |
| description | Generates editor-agnostic workspace settings from a .editor/profile.json manifest: .vscode/settings.json, extensions.json, and Cursor workspaceOpen plugin loading. Use when setting up per-project extensions, Cursor plugins, editor defaults, or porting workspace config to new repos. |
Project editor profile
Maintain a single manifest (.editor/profile.json) and generate editor files that VS Code, Cursor, and VSCodium all understand. Keeps extensions/plugins scoped to the stack instead of loading every global install.
Example in this repo: .editor/profile.json → run pnpm editor:sync.
Layout
.editor/
profile.json # Project manifest (stack, extensions, cursor plugins, settings)
defaults.settings.json # Shared baseline merged into every project
.vscode/ # Generated — commit to git
settings.json
extensions.json
.cursor/ # Generated — commit to git
hooks.json # workspaceOpen → load-workspace-plugins.mjs
scripts/
sync-editor-profile.mjs # Regenerate .vscode + .cursor/hooks.json
load-workspace-plugins.mjs # Hook: emit pluginPaths from profile
Workflow checklist
- [ ] 1. Detect stack (package.json, configs, test runners)
- [ ] 2. Edit .editor/profile.json (extensions, cursor.plugins, settings overrides)
- [ ] 3. Run: pnpm editor:sync (or node scripts/sync-editor-profile.mjs)
- [ ] 4. Reload window; accept recommended extensions when prompted
- [ ] 5. Confirm Cursor plugins load (hooks) or install missing marketplace plugins
- [ ] 6. Commit .editor/, .vscode/, .cursor/hooks.json
Step 1: Detect stack
| Signal | File / dep | Typical extensions | Unwanted (other stacks) |
|---|
| Next.js | next, next.config.* | Biome, Tailwind CSS IntelliSense | ESLint, Prettier (legacy) |
| React | react, react-dom | ESLint | — |
| TypeScript | typescript, tsconfig.json | Biome; use workspace typescript.tsdk | — |
| Tailwind | tailwindcss, @tailwindcss/postcss | Tailwind CSS IntelliSense | — |
| Playwright | @playwright/test, playwright.config.* | Playwright | Prefer host browser when project documents that pattern |
| pnpm | pnpm-lock.yaml, packageManager | — | npm-only tooling |
| Python | pyproject.toml, requirements.txt | Python, Pylance | (if absent, unwanted Python) |
| Rust | Cargo.toml | rust-analyzer | (if absent, unwanted Rust) |
| Svelte | svelte, .svelte | Svelte + Cursor svelte plugin | — |
Do not recommend extensions for stacks the repo does not use — reduces attack surface and UI noise (audit-ide-dependencies).
Step 2: Edit profile.json
Minimal shape:
{
"name": "my-app",
"stack": ["next", "typescript", "pnpm"],
"extensions": {
"recommend": ["dbaeumer.vscode-eslint"],
"unwanted": ["rust-lang.rust-analyzer"]
},
"cursor": {
"plugins": [
{ "name": "shadcn", "publisher": "cursor-public", "optional": true }
]
},
"settings": {
"eslint.useFlatConfig": true
}
}
extensions.recommend → .vscode/extensions.json recommendations (VS Code/Cursor prompt to install).
extensions.unwanted → unwantedRecommendations (discourage irrelevant global extensions).
cursor.plugins → resolved under $CURSOR_HOME/plugins/cache/<publisher>/<name>/<hash>/ by load-workspace-plugins.mjs on workspaceOpen (Cursor hooks).
settings → merged over .editor/defaults.settings.json into .vscode/settings.json.
Step 3: Generate
pnpm editor:sync
Never hand-edit generated .vscode/settings.json long-term — change profile.json and re-sync.
Formatter / extension settings (important)
Cursor validates editor.defaultFormatter against installed extensions only. If ESLint is only recommended but not installed, use built-ins in profile.settings:
vscode.typescript-language-features — TS/JS format
vscode.json-language-features — JSON
Put extension-only keys in .editor/profile.extensions.json (merged by editor:sync only when that extension exists under ~/.cursor/extensions/). Example: Biome (biomejs.biome) for lint + format instead of ESLint.
Step 4: Good defaults (baseline)
defaults.settings.json already sets:
- 2-space indent, final newline, trim whitespace
- File nesting for
package.json / lockfiles / configs
- TypeScript import preferences and move-update-imports
- Sensible git/terminal defaults
Override per project in profile.settings only when needed (e.g. eslint.useFlatConfig, Tailwind v4 experimental.configFile, Playwright reuse browser).
Override per project in profile.settings (e.g. Biome formatter, workspace TypeScript SDK, .next excludes, Tailwind v4 config path).
Step 5: Cursor vs VS Code
| Feature | VS Code / Cursor | Notes |
|---|
| Workspace settings | .vscode/settings.json | Same JSON schema |
| Extension recommendations | .vscode/extensions.json | Cursor uses same file |
| Cursor marketplace plugins | .cursor/hooks.json + load-workspace-plugins.mjs | Requires plugin installed in Cursor once; hook loads cache path per workspace |
| User settings | ~/.config/Cursor/User/settings.json | Global — do not duplicate project overrides here |
Optional: symlink agent skills into .cursor/skills/ or .agents/skills/ per your project's agent docs.
Step 6: New project / extend
- Copy
.editor/defaults.settings.json and scripts/sync-editor-profile.mjs, scripts/load-workspace-plugins.mjs.
- Add
"editor:sync": "node scripts/sync-editor-profile.mjs" to package.json.
- Author
.editor/profile.json from stack table above.
- Run
pnpm editor:sync and commit outputs.
For monorepos, use one profile at the root or per-package profiles with separate sync invocations (node scripts/sync-editor-profile.mjs from each package after adjusting paths — extend script if needed).
Anti-patterns
- Putting project-only settings in global Cursor
settings.json
- Recommending every extension you personally use globally
- Hand-editing
.vscode/settings.json without updating profile.json
- Listing Cursor plugins in
profile.json that were never installed from the marketplace (use "optional": true or install first)
- Running
npm audit fix inside ~/.cursor/extensions (audit-ide-dependencies)
Related skills
References