diff --git a/CLAUDE.md b/CLAUDE.md index 2d3fe48..e6f957d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,6 +20,9 @@ plannotator/ │ │ └── review-editor.html # Built code review app │ ├── marketing/ # Marketing site, docs, and blog (plannotator.ai) │ │ └── astro.config.mjs # Astro 5 static site with content collections +│ ├── vscode-extension/ # VS Code extension for inline annotations +│ │ ├── src/extension.ts # Add/export annotation commands +│ │ └── package.json # Extension manifest │ └── review/ # Standalone review server (for development) │ ├── index.html │ ├── index.tsx @@ -324,6 +327,7 @@ bun run dev:hook # Hook server (plan review) bun run dev:review # Review editor (code review) bun run dev:portal # Portal editor bun run dev:marketing # Marketing site +bun run dev:vscode # VS Code extension (watch mode) ``` ## Build @@ -334,6 +338,7 @@ bun run build:review # Code review editor bun run build:opencode # OpenCode plugin (copies HTML from hook + review) bun run build:portal # Static build for share.plannotator.ai bun run build:marketing # Static build for plannotator.ai +bun run build:vscode # VS Code extension bun run build # Build hook + opencode (main targets) ``` diff --git a/apps/vscode-extension/.vscode/launch.json b/apps/vscode-extension/.vscode/launch.json new file mode 100644 index 0000000..9ad3146 --- /dev/null +++ b/apps/vscode-extension/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/dist/**/*.js"], + "preLaunchTask": "build-extension" + } + ] +} diff --git a/apps/vscode-extension/.vscode/tasks.json b/apps/vscode-extension/.vscode/tasks.json new file mode 100644 index 0000000..07e5c8a --- /dev/null +++ b/apps/vscode-extension/.vscode/tasks.json @@ -0,0 +1,12 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build-extension", + "type": "shell", + "command": "node esbuild.config.mjs", + "group": "build", + "problemMatcher": ["$tsc"] + } + ] +} diff --git a/apps/vscode-extension/.vscodeignore b/apps/vscode-extension/.vscodeignore new file mode 100644 index 0000000..cab96d9 --- /dev/null +++ b/apps/vscode-extension/.vscodeignore @@ -0,0 +1,5 @@ +src/ +node_modules/ +tsconfig.json +esbuild.config.mjs +.vscode/ diff --git a/apps/vscode-extension/esbuild.config.mjs b/apps/vscode-extension/esbuild.config.mjs new file mode 100644 index 0000000..7ac135b --- /dev/null +++ b/apps/vscode-extension/esbuild.config.mjs @@ -0,0 +1,23 @@ +import * as esbuild from "esbuild"; + +const watch = process.argv.includes("--watch"); + +const config = { + entryPoints: ["src/extension.ts"], + bundle: true, + outfile: "dist/extension.js", + external: ["vscode"], + format: "cjs", + platform: "node", + target: "node18", + sourcemap: true, + minify: !watch, +}; + +if (watch) { + const ctx = await esbuild.context(config); + await ctx.watch(); + console.log("Watching for changes..."); +} else { + await esbuild.build(config); +} diff --git a/apps/vscode-extension/package.json b/apps/vscode-extension/package.json new file mode 100644 index 0000000..3277ae0 --- /dev/null +++ b/apps/vscode-extension/package.json @@ -0,0 +1,72 @@ +{ + "name": "plannotator", + "displayName": "Plannotator", + "description": "Add inline annotation markers to any file for plan review and code feedback", + "version": "0.0.1", + "publisher": "backnotprop", + "license": "MIT OR Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/backnotprop/plannotator.git", + "directory": "apps/vscode-extension" + }, + "engines": { + "vscode": "^1.80.0" + }, + "categories": ["Other"], + "keywords": ["annotation", "code-review", "plannotator"], + "main": "./dist/extension.js", + "activationEvents": [], + "contributes": { + "commands": [ + { + "command": "plannotator.addAnnotation", + "title": "Plannotator: Add Annotation" + }, + { + "command": "plannotator.exportAnnotations", + "title": "Plannotator: Export Annotations" + } + ], + "menus": { + "editor/context": [ + { + "command": "plannotator.addAnnotation", + "group": "1_modification@99" + }, + { + "command": "plannotator.exportAnnotations", + "group": "1_modification@100" + } + ] + }, + "keybindings": [ + { + "command": "plannotator.addAnnotation", + "key": "ctrl+alt+p", + "mac": "cmd+alt+p", + "when": "editorTextFocus" + } + ], + "configuration": { + "title": "Plannotator", + "properties": { + "plannotator.annotationPrefix": { + "type": "string", + "default": "@plannotator", + "description": "Marker prefix for annotation comments" + } + } + } + }, + "scripts": { + "build": "node esbuild.config.mjs", + "watch": "node esbuild.config.mjs --watch", + "package": "npx @vscode/vsce package --no-dependencies" + }, + "devDependencies": { + "@types/vscode": "^1.80.0", + "esbuild": "^0.20.0", + "typescript": "~5.8.2" + } +} diff --git a/apps/vscode-extension/src/extension.ts b/apps/vscode-extension/src/extension.ts new file mode 100644 index 0000000..f2bf880 --- /dev/null +++ b/apps/vscode-extension/src/extension.ts @@ -0,0 +1,168 @@ +import * as vscode from "vscode"; + +export function activate(context: vscode.ExtensionContext) { + const addAnnotation = vscode.commands.registerTextEditorCommand( + "plannotator.addAnnotation", + async (editor) => { + const config = vscode.workspace.getConfiguration("plannotator"); + const prefix = config.get("annotationPrefix", "@plannotator"); + const closingTag = prefix.startsWith("@") + ? "/" + prefix.slice(1) + : "/" + prefix; + + const doc = editor.document; + const selection = editor.selection; + const startLine = selection.start.line; + const endLine = selection.end.line; + const multiLine = startLine !== endLine; + const indent = doc.lineAt(startLine).text.match(/^\s*/)![0]; + + // Auto-increment ID by scanning existing annotations + const idPattern = new RegExp(`${escapeRegex(prefix)}\\s+(\\d+)`); + let maxId = 0; + for (let i = 0; i < doc.lineCount; i++) { + const match = doc.lineAt(i).text.match(idPattern); + if (match) { + maxId = Math.max(maxId, parseInt(match[1])); + } + } + const nextId = String(maxId + 1).padStart(4, "0"); + + const startMarker = `${indent}\n`; + + const inserted = await editor.edit((eb) => { + if (multiLine) { + // Insert end marker first so startLine doesn't shift + const endInsertLine = endLine + 1; + const endMarker = `${indent}\n`; + eb.insert(new vscode.Position(endInsertLine, 0), endMarker); + } + eb.insert(new vscode.Position(startLine, 0), startMarker); + }); + + if (inserted) { + const cursorCol = + indent.length + ``, + ); + const endPattern = new RegExp( + `^\\s*`, + ); + const anyMarkerPattern = new RegExp( + `^\\s*