diff --git a/CLAUDE.md b/CLAUDE.md index ac77404..24727c2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -116,12 +116,23 @@ main ← Production releases (semantic-release runs here) | `feature/*` | New features | `develop` | | `fix/*` | Bug fixes | `develop` | -### Branch Protection Rules (Critical) +### Branch Protection Rules (MANDATORY) - **NEVER commit directly to `develop` or `main`** — always create a feature/fix branch first -- **All work goes through PRs** — even small fixes. This ensures CI runs and reviews happen -- **PR flow:** `feature/*` → `develop` → `main` -- **Claude Code must also follow this** — create a branch, commit there, push, then create PR to develop +- **All work goes through PRs** — even small fixes, even single-line changes +- **PR flow:** `feature/*` or `fix/*` → `develop` (via PR) → `main` (via PR) +- **Branch naming:** `feature/short-description` for new features, `fix/short-description` for bug fixes + +**Claude Code MUST follow this workflow:** + +1. `git checkout develop && git pull origin develop` +2. `git checkout -b fix/description-here` (or `feature/`) +3. Make changes, commit on the branch +4. `git push -u origin fix/description-here` +5. `gh pr create --base develop --head fix/description-here` +6. After merge: `git checkout develop && git pull && git branch -d fix/description-here` + +**NEVER do:** `git commit` on develop, `git push origin develop`, `gh pr create --base main --head develop` (unless releasing) ### Release Process (Automated) diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 9a026b6..b64de08 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -2403,7 +2403,7 @@ function registerPluginDiscoveryHandlers(): void { }); // Install plugin from a remote URL (marketplace download) - ipcMain.handle('plugins:installFromUrl', async (_event, url: string, _pluginSlug: string) => { + ipcMain.handle('plugins:installFromUrl', async (_event, url: string, pluginSlug: string) => { // Safety: only allow https URLs if (!url.startsWith('https://')) { return { success: false, error: 'Only HTTPS URLs are allowed' }; @@ -2502,6 +2502,18 @@ function registerPluginDiscoveryHandlers(): void { return { success: false, error: 'Invalid manifest: missing id or name' }; } + // Cross-plugin overwrite protection: if we requested plugin A but the + // archive contains plugin B, block when it would overwrite an existing plugin + if (pluginSlug && manifest.id !== pluginSlug) { + const wouldOverwrite = join(paths.plugins, manifest.id); + if (existsSync(wouldOverwrite)) { + return { + success: false, + error: `Archive contains "${manifest.id}" but "${pluginSlug}" was requested. Refusing to overwrite existing plugin.`, + }; + } + } + // Validate plugin ID - only allow alphanumeric, hyphens, underscores if (!/^[a-zA-Z0-9_-]+$/.test(manifest.id)) { return {