diff --git a/extensions/vscode/.eslintrc.json b/extensions/vscode/.eslintrc.json new file mode 100644 index 0000000..5a60946 --- /dev/null +++ b/extensions/vscode/.eslintrc.json @@ -0,0 +1,36 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2021, + "sourceType": "module", + "ecmaFeatures": { "jsx": true } + }, + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "env": { + "node": true, + "es2021": true + }, + "rules": { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" } + ] + }, + "ignorePatterns": ["out", "node_modules", "reference", "**/*.js"], + "overrides": [ + { + "files": ["src/webview/**/*.{ts,tsx}"], + "env": { "browser": true } + }, + { + "files": ["**/__tests__/**/*.{ts,tsx}", "**/*.test.{ts,tsx}"], + "env": { "jest": true } + } + ] +} diff --git a/extensions/vscode/.gitignore b/extensions/vscode/.gitignore new file mode 100644 index 0000000..ae7fae2 --- /dev/null +++ b/extensions/vscode/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +.superpowers/ +node_modules/ +reference +out/ \ No newline at end of file diff --git a/extensions/vscode/.vscode/launch.json b/extensions/vscode/.vscode/launch.json new file mode 100644 index 0000000..a142310 --- /dev/null +++ b/extensions/vscode/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "npm: compile" + } + ] +} diff --git a/extensions/vscode/.vscodeignore b/extensions/vscode/.vscodeignore new file mode 100644 index 0000000..5aba3d2 --- /dev/null +++ b/extensions/vscode/.vscodeignore @@ -0,0 +1,10 @@ +src/** +reference/** +docs/** +node_modules/** +**/*.ts +**/*.map +tsconfig*.json +webpack.config.js +jest.config.js +.gitignore diff --git a/extensions/vscode/README.md b/extensions/vscode/README.md new file mode 100644 index 0000000..1771037 --- /dev/null +++ b/extensions/vscode/README.md @@ -0,0 +1,92 @@ +# Open Code Review (VSCode 插件) + +基于 [`open-code-review`](https://www.npmjs.com/package/@alibaba-group/open-code-review) (`ocr`) CLI 的 VSCode 代码审查插件。以 Preact WebView 还原原型交互体验,把 AI 代码审查能力集成进编辑器:在侧边栏发起审查、流式查看日志、在编辑器内逐条应用/忽略/标记误报评论,并与侧边栏双向同步。 + +--- + +## 功能 + +- **三种审查模式**:工作区变更、分支对比(`--from` / `--to`)、单次提交(`--commit`)。 +- **待审查文件预览**:基于当前 Git 状态展示变更文件列表。 +- **自定义审查提示词**:可选地为本次审查追加 `--background` 提示。 +- **流式日志**:审查过程中实时滚动 CLI 输出,支持随时取消。 +- **结果展示 + 双向同步**:完成后在侧边栏列出评论卡片,同时在编辑器内渲染 CommentThread;应用/忽略/误报操作在两侧同步。 +- **空 / 取消 / 失败态**:无问题、用户取消、CLI 失败均有对应视图(失败可重试)。 +- **配置管理**:在插件内查看/编辑 LLM 提供商配置(写入通过 `ocr config set`)。 +- **模型切换 / 连通性测试**:状态栏切换模型、测试与 LLM 的连通性。 + +--- + +## 前置依赖 + +1. 全局安装 `ocr` CLI: + + ```bash + npm i -g @alibaba-group/open-code-review + ``` + +2. 配置可用的 LLM(接口地址、API Key、模型)。可用 CLI 直接配置,或在插件内的配置视图填写: + + ```bash + ocr config set llm.url https://api.anthropic.com/v1/messages + ocr config set llm.auth_token sk-... + ocr config set llm.model claude-opus-4-6 + ocr config set llm.use_anthropic true + ``` + + 配置写入 `~/.opencodereview/config.json`。 + +--- + +## 开发 + +```bash +yarn install # 安装依赖 +yarn compile # 开发构建(webpack development,产出 out/extension.js + out/webview.js) +``` + +然后在 VSCode 中按 **F5** 启动 Extension Development Host(已提供 `.vscode/launch.json`),打开一个有 Git 变更的项目即可体验。 + +其他脚本: + +```bash +yarn watch # 监听式开发构建 +yarn test # 运行 Jest 单测 +yarn lint # ESLint +``` + +--- + +## 构建 + +```bash +yarn build # 生产构建(webpack production) +``` + +产物:`out/extension.js`(Extension Host)+ `out/webview.js`(WebView SPA)。 + +--- + +## 架构 + +采用 **Monolithic WebView + Thin Extension Host** 方案: + +- **WebView** 是独立构建的 Preact SPA,还原原型的全部视觉与交互。 +- **Extension Host** 层轻薄,只负责 CLI 调用、文件系统、Git 操作、编辑器评论。 +- 两者通过 `postMessage` 通信,用 `src/shared/` 中的 TypeScript 共享类型保证类型安全。 + +详细的架构设计、技术决策与数据结构参考见 +[`docs/superpowers/specs/2026-06-08-ocr-vscode-extension-design.md`](docs/superpowers/specs/2026-06-08-ocr-vscode-extension-design.md)。 + +``` +src/ +├── extension/ Extension Host(Node.js):services / providers / commands +├── webview/ WebView SPA(Preact):views / components / store / bridge +└── shared/ 双端共享类型与 postMessage 协议(不依赖 vscode) +``` + +--- + +## License + +Apache-2.0 diff --git a/extensions/vscode/docs/superpowers/plans/2026-06-04-ocr-vscode-ui-prototype.md b/extensions/vscode/docs/superpowers/plans/2026-06-04-ocr-vscode-ui-prototype.md new file mode 100644 index 0000000..ef3b400 --- /dev/null +++ b/extensions/vscode/docs/superpowers/plans/2026-06-04-ocr-vscode-ui-prototype.md @@ -0,0 +1,2248 @@ +# OCR VSCode 插件 UI 原型 · 实现计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 产出一个 `prototype.html` 单文件高保真交互原型,在 360px 侧栏宽度内展示 OCR VSCode 插件的完整产品形态:从首次配置 → 选范围与文件 → 启动审查 → 流式过程 → 结果浏览 → 异常路径。 + +**Architecture:** 单文件 HTML,所有 CSS/JS 内联。通过 `body[data-state]` 和 `body[data-config]` 属性驱动状态机切换可见性。localStorage 持久化配置。无外部依赖,`file://` 双击可开。 + +**Tech Stack:** 纯 HTML + CSS(silent-night-ui 变量系统)+ Vanilla JS(< 200 行) + +**Spec:** `docs/superpowers/specs/2026-06-04-ocr-vscode-ui-design.md` + +**Visual reference:** `silent-night-ui/style-reference.md`(CSS 变量 §1、全局基底 §2、禁区清单 §8) + +--- + +## File Structure + +单文件交付,内部按注释分区: + +``` +prototype.html ← 唯一交付物 + + + + + + + +``` + +--- + +### Task 1: HTML 骨架 + CSS 变量 + 全局基底 + VSCode Chrome 布局 + +**Files:** +- Create: `prototype.html` + +**验收标准(浏览器打开后):** +- 页面全黑背景,有微弱的 mint 光晕和星点网格 +- 左侧 48px 深色 Activity Bar 有 3 个圆角占位方块 +- 中间 360px 侧栏区域(`--card` 色背景) +- 右侧灰色编辑器占位区,有 "(editor area · prototype only)" 小字 +- 无滚动条溢出,无 console 报错 + +- [ ] **Step 1: 创建 prototype.html 基础结构** + +创建文件 `prototype.html`,包含完整 HTML 骨架、silent-night-ui CSS 变量、全局基底、VSCode chrome 三栏布局: + +```html + + + + + +OCR · VSCode Plugin UI Prototype + + + + +
+ + +
+ (editor area · prototype only) +
+
+ + + + +``` + +- [ ] **Step 2: 浏览器验证** + +Run: 双击 `prototype.html` 在浏览器中打开 + +验证: +1. 深色背景 + mint 光晕可见 +2. 左侧 48px activity bar,4 个圆角方块,第一个有左侧白色条高亮 +3. 中间 360px 空白侧栏(`--card` 色) +4. 右侧灰色编辑器区域有 "(editor area · prototype only)" 文字 +5. 无 console 报错,无滚动条 + +- [ ] **Step 3: Commit** + +```bash +git add prototype.html +git commit -m "feat: prototype skeleton with CSS variables and VSCode chrome layout" +``` + +--- + +### Task 2: Status Bar 顶部条 + Model Dropdown + +**Files:** +- Modify: `prototype.html`(CSS 区 + HTML sidebar 内部) + +**验收标准:** +- 侧栏顶部一行:mint 脉动圆点 + 模型名 + " · " + provider 名 + ▾ 下拉 + ⚙ 齿轮按钮 +- 点击 ▾ 弹出 dropdown,列出模型(按 provider 分组),active 项左侧有 mint 点 +- 点击 ⚙ 暂无反应(Task 8 接入) +- dropdown 点外面自动关闭 + +- [ ] **Step 1: 添加 Status Bar CSS** + +在 `` 前追加: + +```css +/* === 4. Status Bar === */ +.status-bar { + display: flex; align-items: center; + padding: 10px 14px; + border-bottom: 1px solid var(--rule); + gap: 8px; + flex-shrink: 0; + position: relative; +} +.status-dot { + width: 7px; height: 7px; border-radius: 50%; + background: var(--mint); + box-shadow: 0 0 8px var(--mint-glow); + animation: pulse 1.6s ease-in-out infinite; + flex-shrink: 0; +} +.status-dot.dim { + background: var(--ink-faint); + box-shadow: none; + animation: none; +} +.status-model { + font-size: 12.5px; font-weight: 600; + color: var(--ink); + white-space: nowrap; +} +.status-sep { + color: var(--ink-faint); + font-size: 12px; +} +.status-provider { + font-size: 12px; + color: var(--ink-quiet); + white-space: nowrap; +} +.status-dropdown-trigger { + background: none; border: none; + color: var(--ink-quiet); + font-size: 11px; cursor: pointer; + padding: 2px 4px; + border-radius: 4px; + transition: background-color 0.2s; +} +.status-dropdown-trigger:hover { + background: var(--card-quiet); + color: var(--ink-soft); +} +.status-spacer { flex: 1; } +.status-gear { + background: none; border: none; + color: var(--ink-quiet); + font-size: 14px; cursor: pointer; + padding: 4px 6px; + border-radius: 6px; + transition: background-color 0.2s, color 0.2s; +} +.status-gear:hover { + background: var(--card-quiet); + color: var(--ink); +} +.status-reset { + background: none; border: none; + color: var(--ink-faint); + font-size: 9px; cursor: pointer; + padding: 2px 4px; + letter-spacing: 0.1em; + text-transform: uppercase; + opacity: 0; + transition: opacity 0.2s; +} +.status-bar:hover .status-reset { opacity: 1; } + +/* Model Dropdown */ +.model-dropdown { + position: absolute; + top: 100%; left: 14px; right: 14px; + background: var(--card-soft); + border: 1px solid var(--rule); + border-radius: 12px; + padding: 6px; + z-index: 20; + box-shadow: 0 12px 32px -8px rgba(0,0,0,0.6); + display: none; +} +.model-dropdown.open { display: block; } +.model-dropdown-group { + font-size: 10px; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--ink-faint); + padding: 8px 10px 4px; + font-weight: 600; +} +.model-dropdown-item { + display: flex; align-items: center; gap: 8px; + padding: 7px 10px; + border-radius: 8px; + font-size: 12.5px; + color: var(--ink-soft); + cursor: pointer; + transition: background-color 0.15s; +} +.model-dropdown-item:hover { + background: var(--card-quiet); + color: var(--ink); +} +.model-dropdown-item .md-dot { + width: 6px; height: 6px; border-radius: 50%; + background: transparent; + flex-shrink: 0; +} +.model-dropdown-item.active { color: var(--ink); font-weight: 600; } +.model-dropdown-item.active .md-dot { + background: var(--mint); + box-shadow: 0 0 6px var(--mint-glow); +} +``` + +- [ ] **Step 2: 添加 Status Bar HTML** + +在 `