diff --git a/.project-docs/20-architecture/data-flow.md b/.project-docs/20-architecture/data-flow.md index 6297cda..1eaeefa 100644 --- a/.project-docs/20-architecture/data-flow.md +++ b/.project-docs/20-architecture/data-flow.md @@ -11,7 +11,6 @@ | 提交绑定 | 云端成功上传响应 | Main → submission binding v2 | 只持久化成功的 app/version/review/hash;落盘失败返回固定告警但不反转提交 | | 运营发布 | Works Square 审核与交付 | 公共 `play_url` | 客户端只消费服务端发布结果;真实合同校验 → OSS/CDN 生产链仍待整链验收 | | 可信发布门禁 | source+built+artifact contract | 服务端逐字节重算/合同校验 → 不可变 Release 固化 → 人工审核 | 服务端不运行项目 Vite;仍是不可绕过权威,未来 runtime 强门禁需可信 verifier | -| 真机预览 | 项目空间 | Main → Owner preview / `play_url` | 核对 app、version、release 和同源 HTTPS;`runtime_url` 仅一版本兼容回退 | | 设计会话创建/切换 | AI 绘画页面或侧栏 | Renderer API → Main → Workspace Conversation API | 新会话属于现有 Workspace;读取独立消息、Brief、Quote 和 `turnRevision` | | 设计消息与确认 | 当前 Conversation | Main → 持久 Agent Gateway Session → Conversation 快照 | 请求和流式结果同时绑定 Workspace + Conversation | | 设计任务同步 | 任一 Conversation 的事件流 / REST | Renderer Workspace 任务列表 | Task 和 Asset 按 Workspace 归并,切换 Conversation 后仍可见 | @@ -20,7 +19,7 @@ - Main 持有刷新凭据、发布 Token、固定 npm runtime、源码/构建归档、临时目录、幂等键和 submission binding v2;Renderer 不持有归档路径、构建 origin 或自动部署状态。 - 项目内 `.niancode/project.json` 保存 `ProjectType`;Main 在配置写入和目录复用时保持其不可变,并在打包时重新读取校验。 -- Renderer 仅持有短效公开会话状态、提交展示状态和安全投影后的公共播放/短时预览 URL。 +- Renderer 仅持有短效公开会话状态和提交展示状态。 - 本地构建临时目录、HTTP origin 和预检 WebContents/partition 只属于一次调用;预检读取与 `built_archive` 相同的内存字节,但结果不写为可信上传 receipt,也不覆盖生产 opaque-origin。 - 旧 schema v1 `submitted` 记录迁移并保留;旧 `armed`、`waiting_for_package`、`waiting_for_login`、`uploading`、`failed` 归一为 `legacy_retired`,不再启动 watcher 或上传任务。 - AI 绘画 Conversation 持有消息、Brief、Quote、`turnRevision` 和服务端 Session 绑定;Workspace 持有 Conversation 列表、生成任务和资产。 @@ -29,8 +28,8 @@ ## External Interfaces -- Works Square 项目创建、版本上传、构建状态、Owner 状态与 Release preview API。 -- 本机 Host API 的发布与真机预览路由;发布路由要求 Renderer capability。 +- Works Square 项目创建、版本上传、构建状态与 Release 状态 API。 +- 本机 Host API 的发布路由;发布路由要求 Renderer capability。 - 服务端安全投影后的公共 `play_url`;只接受同源 HTTPS、精确 App 路径和可信版本状态。 - Works Square Workspace/Conversation API、每个 Conversation 的 Agent Gateway Session 与单次 WebSocket ticket。 diff --git a/.project-docs/20-architecture/system-overview.md b/.project-docs/20-architecture/system-overview.md index 62e4759..511c958 100644 --- a/.project-docs/20-architecture/system-overview.md +++ b/.project-docs/20-architecture/system-overview.md @@ -2,20 +2,19 @@ ## Current Architecture -Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展示;Electron Main 持有认证、文件系统、安全打包、Works Square 网络访问和预览 URL 校验。 +Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展示;Electron Main 持有认证、文件系统、安全打包、Works Square 网络访问和发布安全校验。 ## Main Components | Component | Responsibility | Notes | |---|---|---| -| Renderer | 项目配置、一键提交状态、真机二维码 | 不接触账号 Token、ZIP、幂等键或本地绝对路径 | +| Renderer | 项目配置、一键提交状态 | 不接触账号 Token、ZIP、幂等键或本地绝对路径 | | Host API | 校验本地项目请求并投影安全响应 | 发布 mutation 还必须通过 Renderer capability;Host token/base 不能单独触发发布 | | Project Configuration & Template | 保存不可变 `ProjectType`,原子生成新项目骨架 | 小游戏/小程序生成受控 Vite 模板;自定义保持最小项目空间 | | Project Release Builder | Main-owned 安全快照、本地 npm/Vite 构建、双归档与 artifact contract | 固定 npm 11.6.2;Vite 由项目 lockfile 锁定;产物与预检使用同一内存字节 | | Works Session | 七天真实活动滑动续期 | 刷新凭据由 Main 安全持有 | | Submission Binding | 保存云端已接受的精确 app/version/review/hash 绑定 | schema v2 只记录成功提交;旧中间态迁移为 `legacy_retired`,不恢复后台任务 | | Play URL Projection | 校验服务端公共播放地址 | 只接受同源 HTTPS 和精确 `/apps/{app_id}/`;优先 `play_url`,`runtime_url` 仅一版本回退 | -| Device Preview | 核对本地绑定、远端版本和 Release | 待审使用短时 Owner preview;已发布使用安全投影后的 `play_url` | | Built Artifact Preflight | 在上传前对最终 built snapshot 做桌面/移动 UX fail-fast | Main 使用临时 loopback origin 与 Electron WebContents/CDP;不使用 Playwright、不生成可信证明 | | AI Design Workspace | 保存项目身份、Conversation 列表、生成任务和资产 | 任务和资产在切换 Conversation 后继续可见 | | AI Design Conversation | 保存消息、Brief、Quote、`turnRevision` 与服务端 Agent Session 绑定 | 同一 Workspace 内互相隔离;Session 由服务端持久化 | @@ -32,7 +31,7 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展 - 本地 `projectType` 只选择产品路径和内部构建 preset,不是可信授权声明;Main 仍需安全打包,服务端仍需独立校验清单和包体。 - 云端确认上传成功后,本机 submission binding 失败只能产生固定、无路径的 `binding_warning`,不能把请求改判为失败;Renderer 仍继续轮询服务端校验与 Release 固化状态。 - 公共播放投影只有在上游 `playable === true`、版本名非空且 URL 通过同源 HTTPS、无 userinfo/loopback、长度、精确路径和无 query/fragment 校验时才可播放;不可信数据 fail closed。 -- Renderer 只能获得安全状态字段和可展示的播放/短时预览 URL,不得持有发布凭据、归档路径或自动部署状态。 +- Renderer 只能获得安全状态字段和安全投影的公共播放 URL,不得持有发布凭据、归档路径或自动部署状态。 - 落盘文件名 `works-cloud-deploy.json` 仅为已安装客户端的数据兼容;领域模型和代码接口是 submission binding,不表示仍存在 cloud deployment coordinator。 - AI 绘画中,一个 Workspace 可包含多条 Conversation;消息、Brief、Quote 和 `turnRevision` 属于 Conversation,生成任务和资产属于 Workspace。新建 Conversation 不得隐式创建新 Workspace。 - 每条 Conversation 复用服务端持久 Agent Gateway Session。客户端不得在注销或退出时 DELETE 该 Session;只关闭本地事件流并清除本机 Session-id 缓存,重新访问时从 Conversation API 刷新。 diff --git a/.project-docs/30-worklog/current-state.md b/.project-docs/30-worklog/current-state.md index 70711fe..d74a972 100644 --- a/.project-docs/30-worklog/current-state.md +++ b/.project-docs/30-worklog/current-state.md @@ -6,7 +6,6 @@ This file is the integrated default-branch snapshot. Feature tasks record progre - `86ece3a` / `4dde8f3`:客户端登录七天滑动续期及集成提交。 - `724290e` / `dcc92fc`:Main-owned 一键打包提交审核及集成提交。 -- `1a19ad9` / `f02e99e`:项目真机预览、待审 Release Owner preview 及集成提交。 - `493b31c`:客户端三类 `ProjectType`、小游戏/小程序受控发布模板与自定义项目发布隔离。 - `4df0477` / `8dd99c1`:客户端静态发布唯一链路、旧 Compose 协调链退役及安全边界补强。 - `4980894` / `03dae62`:AI 绘画 Enter 发送及同一 Workspace 下的多 Conversation 客户端模型。 @@ -21,7 +20,7 @@ AI 绘画的一个 Workspace 可包含多条 Conversation。消息、Brief、Quo ## Recently Completed -- 2026-08-08:合并登录续期、一键发布和真机预览;发布成功后保存精确 app/version/review 映射,Renderer 不接触 Token、ZIP 或本地路径。 +- 2026-08-08:合并登录续期和一键发布;发布成功后保存精确 app/version/review 映射,Renderer 不接触 Token、ZIP 或本地路径。 - 2026-08-08:补齐跨平台 Electron E2E fixture、Windows ZIP 预检和异常成功响应安全投影。 - 2026-08-09:新建项目支持小游戏、小程序和自定义三类不可变产品类型;前两类生成固定 Vite 发布模板,自定义及缺少类型字段的旧项目不显示一键提交入口。 - 2026-08-10:删除客户端旧 Compose deploy-check、watcher/arm/upload 协调和手工 ZIP 路由;submission binding v2 保留旧 `submitted` 绑定并把旧中间态归一为 `legacy_retired`,同时补齐 Renderer capability、`play_url` 安全投影和本机绑定失败告警。 @@ -37,8 +36,7 @@ AI 绘画的一个 Workspace 可包含多条 Conversation。消息、Brief、Quo 1. 成组核对客户端 source+built+contract 上传 → 服务端逐字节校验 → OSS immutable Release → CDN/Edge 的发布契约与客户端 `play_url` 消费契约。 2. 配置真实生产环境,分别执行“小游戏/小程序创建 → 客户端本地构建与同字节预检 → 提交 → 服务端合同/摘要校验与不可变 Release 固化 → 运营批准 → App 播放”。 -3. 核对待审 Owner preview 到期刷新、CDN 发布/撤回、生产监控和告警证据。 -4. 在一个客户端兼容版本且服务端与存量数据稳定提供 `play_url` 后,删除 `runtime_url` 回退。 +3. 核对生产环境的提交状态、构建结果和告警证据。 ## Open Questions / Blockers @@ -47,7 +45,7 @@ AI 绘画的一个 Workspace 可包含多条 Conversation。消息、Brief、Quo ## Risky Areas - 一键提交已成功但本机 submission binding 落盘失败时必须保持提交成功、显示固定 `binding_warning` 并继续轮询,避免用户误判上传失败。 -- 公共 `play_url` 必须满足 Works Square 同源 HTTPS、无 userinfo/loopback、精确 `/apps/{app_id}/` 路径、无 query/fragment、版本非空且上游标记可播放;待审预览仍需绑定当前项目、精确版本和 Release。 +- 公共 `play_url` 必须满足 Works Square 同源 HTTPS、无 userinfo/loopback、精确 `/apps/{app_id}/` 路径、无 query/fragment、版本非空且上游标记可播放。 - `/api/works/projects/publish-source` 必须在读取凭据和项目文件前校验 Renderer capability;Host token/base 不能替代该 UI 边界。 - `works-cloud-deploy.json` 仅是已安装数据的兼容文件名,不代表客户端仍拥有自动部署协调器;旧中间态不得恢复为后台任务。 - 刷新凭据、发布 Token、ZIP、幂等键和重试只能由 Electron Main 持有。 diff --git a/.project-docs/40-domain/business-rules.md b/.project-docs/40-domain/business-rules.md index b5c441c..8306142 100644 --- a/.project-docs/40-domain/business-rules.md +++ b/.project-docs/40-domain/business-rules.md @@ -11,8 +11,7 @@ - 项目 Vite config/plugins 以桌面用户权限执行,因此发布链只适用于用户信任的本地项目;它不是 sandbox。安装依赖需要网络,运行时闭包缺失或版本不符必须 fail closed。 - 提交前预检必须由 Main 以一次性 loopback origin 提供最终上传 `built_archive` 的同一内存文件快照,以 fresh 非持久 Electron WebContents/CDP 检查桌面/移动视口、运行错误、白屏与外域访问;不得调用 Playwright 或污染用户浏览器状态。 - 客户端预检是可绕过的 UX fail-fast,不上传可信 receipt,也不声称具备生产 opaque-origin parity。服务端把源码、构建归档和 contract 当作不可信字节,独立重算、校验并固化不可变 Release;人工审核仍是不可绕过发布门禁。未来若要求 runtime 强门禁,必须由可信 verifier 绑定精确构建产物。 -- 待审版本只能通过绑定精确 Release 的短时 Owner preview 验收;旧版本、构建中或失败状态不能生成可扫码假象。 -- 发布和预览安全边界由 Electron Main 持有,Renderer 不接触账号 Token、ZIP、幂等键和本地绝对路径。 +- 发布安全边界由 Electron Main 持有,Renderer 不接触账号 Token、ZIP、幂等键和本地绝对路径。 - 发布 Host API 必须在读取凭据、查询项目和打包前校验 Renderer capability;仅持有 Host token/base 的非 UI 调用方不得发起发布。 - 客户端只持久化服务端已接受的 submission binding v2。旧 `submitted` 绑定必须保留;旧 `armed`、`waiting_for_package`、`waiting_for_login`、`uploading`、`failed` 必须迁移为可理解的 `legacy_retired`,不得恢复后台任务。 - 云端上传成功但本机 submission binding 保存失败时,提交仍视为成功;客户端显示固定、无本地路径的告警并继续轮询服务端校验与 Release 固化状态,避免诱导重复提交。 diff --git a/README.md b/README.md index f67290e..6b0d56a 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,23 @@ Makelore 是一个面向软件与视觉创作的 AI 桌面工作台。当前版本为 `2.0.0`,包含两个已开通产品模块,并保留一个学习模块入口: -- `Makelore Code|AI 编程`:管理本地项目、项目 Agent、会话、文件上下文、代码变更和运行时,并为服务端可运行版本提供真机扫码预览。 +- `Makelore Code|AI 编程`:管理本地项目、项目 Agent、会话、文件上下文、代码变更和运行时。 - `Makelore Canvas|AI 绘画`:以设计项目(Workspace)组织 Agent 对话、方向确认、图片/视频生成任务和私有结果。 - `Makelore Learning|AI 学习`:数学与知识宇宙主题的学习入口,当前暂未开通;开屏页和模块切换菜单保留置灰入口,不能进入。 -作品广场、素材广场、独立发布上传和云部署页面不属于 Makelore 2.0 工作台。新建项目可选择“小游戏”“小程序”或“自定义项目”:小游戏和小程序会创建完整的平台发布模板,项目配置底部提供“一键提交审核”;Electron Main 对源码快照运行安装包内固定 npm 和项目 `package-lock.json` 锁定的 Vite,使用内置浏览器预检同一份输出字节,再将源码归档、构建归档与校验清单一并提交。平台独立重算摘要、固化不可变 Release 并进入运营审核;客户端预检不构成可信审核凭据。自定义项目只创建工作空间,不配置默认发布方式。项目成果预览 `/deliverables` 继续保留。 +应用启动默认进入 AI 模块入口选择页。入口页可在未登录状态浏览;未登录用户点击已开通模块时进入浏览器授权,授权成功后回到入口选择页,已登录用户可直接进入对应工作区。 + +作品广场、素材广场、独立发布上传和云部署页面不属于 Makelore 2.0 工作台。新建项目可选择“小游戏”“小程序”或“自定义项目”:小游戏和小程序会创建完整的平台发布模板,项目配置底部提供“一键提交审核”;Main 自动预检、安全打包并提交,构建通过后进入运营审核,审核通过即直接发布。自定义项目只创建工作空间,不配置默认发布方式。项目成果预览 `/deliverables` 继续保留。 ## 当前产品状态 - 桌面技术栈:Electron、React 19、Vite、TypeScript、Zustand、Tailwind CSS。 -- AI 编程运行时:Electron Main 只启动安装目录 `resources/opencode-ai/bin/` 中固定版本的 OpenCode;Renderer 不直接启动或调用运行时。首选回环端口被不健康进程占用时,Main 会改用系统分配的临时端口,并把实际 URL 贯穿到所有运行时请求。 +- AI 编程运行时:Electron Main 只启动安装目录 `resources/opencode-ai/bin/` 中固定版本的 OpenCode;Renderer 不直接启动或调用运行时。已有登录态、已初始化项目和可用 Provider 时,Main 会在应用启动后后台预热运行时;首次设置、条件不完整或预热失败时仍由 Chat 按需启动。首选回环端口被不健康进程占用时,Main 会改用系统分配的临时端口,并把实际 URL 贯穿到所有运行时请求。 - 共享开发浏览器:AI 编程右侧提供项目级浏览器,用户与 Agent 查看并调试同一实时页面、Console 和 Network,支持本地与公网开发地址。 -- 真机预览:AI 编程项目空间可读取当前静态 Release 状态;待审版本使用短时预览地址,已发布版本使用同源 HTTPS `play_url`,供手机直接测试触控、屏幕和移动网络环境。 - 后端边界:Renderer 通过 Main 所有的 Host API 访问认证、模型、同步、更新、语音、图像与运行时能力。 - AI 绘画:每个设计项目固定一个设计 Agent,并可包含多条互相独立的设计会话。消息、Brief、Quote 和 `turnRevision` 属于 Conversation;图片/视频生成任务与资产属于 Workspace,切换或新建会话不会创建新项目,也不会隐藏项目任务。每条 Conversation 复用服务端持久 Agent Gateway Session,任务进度通过实时流推送并在统一列表中展示,断流时使用低频 REST 同步。生产环境使用 Works Square 云端 Workspace 契约,上游不可用时明确报错。 - 视觉系统:单一浅色主题,品牌蓝 `#3A5578`、星火橙 `#F26A3D`、白色画布与低饱和蓝灰层级。 +- 字体系统:Renderer UI 内嵌 Inter Variable 与 Source Han Sans SC,按字符范围统一中英文;日文与俄文保留语言感知的系统回退,代码、路径和日志使用独立等宽字体。 - 品牌资产:生产 SVG、PNG、应用图标、托盘图标与安装器视觉位于 [`resources/brand/`](resources/brand/README.md) 和 `resources/icons/`。 ## 安装与开发 @@ -95,20 +97,17 @@ Windows 打包脚本会先准备目标架构所需的 Python、uv 与 OpenCode - 一键提交时,Makelore 会从待上传构建归档的同一组 Main-owned 内存字节启动临时回环站点,并在两个独立的临时 Chromium profile 中检查桌面和移动视口的主页面加载、运行错误、失败资源与白屏。临时页面不挂载到界面,不读取或写入用户浏览器的 Cookie、历史和登录态;检查结束后始终销毁并清理,也不要求用户预先打开开发预览。 - 客户端复用 Electron 内置 Chromium,不安装 Playwright 或额外浏览器。预检只改善提交前反馈,可被非官方客户端绕过,也不会上传“已通过”凭据;平台仍把源码、构建归档和清单视为不可信输入,逐字节重算并在人工审核后发布。安装包携带固定 npm 运行时,项目依赖和 Vite 版本由 `package-lock.json` 锁定;依赖准备需要本地网络。 -### 真机预览 +### 项目伙伴与会话 -- 真机预览属于 Makelore Code 的项目级能力,不是新的顶层 AI 模块,也不复用 `/deliverables`,不恢复发布上传或云部署工作台。 -- Renderer 只读取 Main-owned Host API 的项目预览快照;Main 绑定当前项目、管理登录续签、核对目标版本,并只放行 Works Square 同源 HTTPS 静态 Release 地址。二维码不包含账号 Token,也不生成本机回环地址。 -- 构建成功且进入待审的版本使用短时、只读、精确 Release 绑定的 Owner preview ticket;已发布版本优先使用 `play_url`,`runtime_url` 仅保留一个客户端兼容版本。构建中、旧版本或失败状态不会伪装成可扫码。 - -### 项目联系人与会话 - -- 新项目默认没有联系人或 Agent;用户在项目内手动创建联系人时必须填写名称、预设头像、职责和精确的 `provider/model`,提示词与 Skill 属于后置高级设置。 -- 一个联系人可以拥有多条互相独立的 OpenCode Session;联系人、会话、归档时间、置顶和未读数分别由项目配置与 `.niancode/conversations.json` 保存。 -- 联系人展开后按时间展示会话,默认显示前五条,更多会话通过“更多会话”展开;每条会话只显示一行精简的最新消息预览和右侧时间,归档按钮仅在悬浮或聚焦会话卡片时出现,选中联系人会在卡片上保持明确的展开状态反馈。 -- 创建联系人后先进入联系人对话,首条消息发送时才懒创建 OpenCode Session;新会话从干净上下文开始,标题从“新对话”在首条消息发送后自动生成,也支持手动重命名。 +- 新项目默认没有伙伴或 Agent;用户在项目内手动创建伙伴时必须填写名称、预设头像、职责和精确的 `provider/model`,提示词与 Skill 属于后置高级设置。 +- 项目配置页的新增与已有伙伴维护统一使用居中弹窗;编辑保存先更新当前页面草稿,点击底部“保存项目配置”后统一持久化。模型资源抽屉只展示已配置模型、提供方和文本/多模态能力,不提供项目级选择;模型切换只能在伙伴维护弹窗中完成,聊天输入区仅展示当前伙伴模型。 +- 读取旧项目时会把仍缺少模型的伙伴从兼容保留的 `defaultModel` 自动迁移到伙伴自身配置,之后运行时只认伙伴模型。 +- 技能资源入口使用扳手图标;点击已安装 Skill 后先展示其目录结构,再展示主文件 `SKILL.md` 原文,并支持返回技能列表。 +- 一个伙伴可以拥有多条互相独立的 OpenCode Session;伙伴、会话、归档时间、置顶和未读数分别由项目配置与 `.niancode/conversations.json` 保存。 +- 伙伴展开后按时间展示会话,默认显示前五条,更多会话通过“更多会话”展开;每条会话只显示一行精简的最新消息预览和右侧时间,归档按钮仅在悬浮或聚焦会话卡片时出现,选中伙伴会在卡片上保持明确的展开状态反馈。 +- 创建伙伴后先进入伙伴对话,首条消息发送时才懒创建 OpenCode Session;新会话从干净上下文开始,标题从“新对话”在首条消息发送后自动生成,也支持手动重命名。 - 不同 Session 由 OpenCode 自己并发运行;同一 Session 的后续消息按顺序排队。Makelore 只展示运行中、待处理和未读状态,不增加额外的全局并发锁。 -- 归档联系人或单个会话前必须确认;归档会停止对应运行、保留历史,并可从列表底部恢复。未读按会话记录,打开一个会话只清除它自己的未读数。 +- 归档伙伴或单个会话前必须确认;归档会停止对应运行、保留历史,并可从列表底部恢复。未读按会话记录,打开一个会话只清除它自己的未读数。 - OpenCode 的问题与权限请求在当前会话内联处理,不自动批准;上下文压缩沿用 OpenCode 自动机制,在最新消息位置显示“正在压缩上下文”。 ## 兼容标识 diff --git a/electron/api/routes/auth.ts b/electron/api/routes/auth.ts index bf8ea8b..2d73d58 100644 --- a/electron/api/routes/auth.ts +++ b/electron/api/routes/auth.ts @@ -70,6 +70,7 @@ type DesktopAuthTokenPayload = { }; const DESKTOP_AUTH_TIMEOUT_MS = 10 * 60 * 1000; +const MAX_AUTH_ERROR_LENGTH = 180; function readRequiredString(value: unknown, field: string): string { if (typeof value !== 'string' || !value.trim()) { @@ -164,17 +165,29 @@ async function readResponsePayload(response: Response): Promise { } function getErrorMessage(payload: unknown, fallback: string): string { + const compact = (value: string): string => { + const normalized = value.replace(/\s+/g, ' ').trim(); + if ( + !normalized + || normalized.length > MAX_AUTH_ERROR_LENGTH + || /; for (const field of ['msg', 'message', 'error_description', 'error']) { const value = record[field]; if (typeof value === 'string' && value.trim()) { - return value; + return compact(value); } } } if (typeof payload === 'string' && payload.trim()) { - return payload; + return compact(payload); } return fallback; } @@ -238,7 +251,7 @@ async function pollDesktopAuthToken( const payload = await readResponsePayload(response) as DesktopAuthTokenPayload; if (!response.ok) { - throw new Error(getErrorMessage(payload, `Desktop authorization failed (${response.status})`)); + throw new Error(getErrorMessage(payload, '登录授权失败,请稍后重试。')); } if (payload.status === 'approved' && payload.token) { @@ -248,7 +261,7 @@ async function pollDesktopAuthToken( await delay(pollIntervalMs); } - throw new Error('Authorization timed out'); + throw new Error('登录超时,请重新尝试。'); } async function handleBrowserAuthorization( @@ -264,7 +277,7 @@ async function handleBrowserAuthorization( if (!response.ok) { sendJson(res, response.status >= 400 && response.status < 500 ? response.status : 502, { success: false, - error: getErrorMessage(payload, `Desktop authorization start failed (${response.status})`), + error: getErrorMessage(payload, '登录服务暂时不可用,请稍后重试。'), }); return; } diff --git a/electron/api/routes/device-preview.ts b/electron/api/routes/device-preview.ts deleted file mode 100644 index e14b86f..0000000 --- a/electron/api/routes/device-preview.ts +++ /dev/null @@ -1,407 +0,0 @@ -import type { IncomingMessage, ServerResponse } from 'node:http'; -import type { HostApiContext } from '../context'; -import { sendJson } from '../route-utils'; -import { hasRendererCapability } from '../renderer-capability'; -import { WORKS_SQUARE_CONFIG } from '../works-config'; -import { - trustedWorksProjectPlayUrl, - trustedWorksReleasePreviewUrl, -} from '../works-play-url'; -import { getValidWorksSquareAccessToken } from '../../services/works-square-session'; -import { proxyAwareFetch } from '../../utils/proxy-fetch'; -import type { WorksSubmissionBindingRecord } from '../../../shared/works-submission-binding'; -import type { DevicePreviewSnapshot } from '../../../shared/device-preview'; - -type RemoteProjectSnapshot = { - appId: string | null; - playable: boolean | null; - runtimeUrl: string | null; - runtimeVersionName: string | null; - latestVersionId: string | null; - latestVersionName: string | null; - latestReleaseId: string | null; - reviewStatus: string | null; -}; - -class DevicePreviewRequestError extends Error { - readonly statusCode: number; - - constructor(message: string, statusCode: number) { - super(message); - this.name = 'DevicePreviewRequestError'; - this.statusCode = statusCode; - } -} - -const DEVICE_PREVIEW_ROUTE = /^\/api\/opencode\/projects\/([^/]+)\/device-preview$/; -const OWNER_STATUS_TIMEOUT_MS = 10_000; -const READY_REVIEW_STATUSES = new Set(['approved', 'published']); -const FAILED_REVIEW_STATUSES = new Set(['blocked', 'failed', 'rejected']); - -function asRecord(value: unknown): Record | null { - return value && typeof value === 'object' && !Array.isArray(value) - ? value as Record - : null; -} - -function readString(value: unknown): string | null { - return typeof value === 'string' && value.trim() ? value.trim() : null; -} - -async function readResponsePayload(response: Response): Promise { - const text = await response.text(); - if (!text.trim()) return null; - try { - return JSON.parse(text) as unknown; - } catch { - return null; - } -} - -function projectFromPayload(payload: unknown): RemoteProjectSnapshot | null { - const root = asRecord(payload); - if (!root) return null; - const wrappedStatus = asRecord(root.status); - const project = asRecord(root.project) - ?? asRecord(wrappedStatus?.project) - ?? (readString(root.app_id) ? root : null); - if (!project) return null; - const latestVersion = asRecord(root.latest_version) ?? asRecord(wrappedStatus?.latest_version); - return { - appId: readString(project.app_id), - playable: typeof project.playable === 'boolean' ? project.playable : null, - runtimeUrl: readString(project.play_url) ?? readString(project.runtime_url), - runtimeVersionName: readString(project.version_name), - latestVersionId: readString(latestVersion?.id) ?? readString(latestVersion?.version_id), - latestVersionName: readString(latestVersion?.version_name), - latestReleaseId: readString(latestVersion?.release_id), - reviewStatus: readString(latestVersion?.review_status), - }; -} - -function normalizedWorksBaseUrl(): URL { - const base = WORKS_SQUARE_CONFIG.apiBaseUrl.trim().replace(/\/+$/, ''); - return new URL(`${base}/`); -} - -async function requireManagedAccessToken(): Promise { - const accessToken = await getValidWorksSquareAccessToken(); - if (!accessToken) { - throw new DevicePreviewRequestError('请先登录,再核对当前项目的真机预览版本', 401); - } - return accessToken; -} - -async function fetchOwnedRemoteProject( - appId: string, - accessToken: string, -): Promise { - const worksBase = normalizedWorksBaseUrl(); - const abortController = new AbortController(); - const timeout = setTimeout(() => abortController.abort(), OWNER_STATUS_TIMEOUT_MS); - let response: Response; - try { - response = await proxyAwareFetch( - new URL(`/api/projects/mine/${encodeURIComponent(appId)}/status`, worksBase).toString(), - { - method: 'GET', - headers: { Authorization: `Bearer ${accessToken}` }, - signal: abortController.signal, - }, - ); - } catch (error) { - if (abortController.signal.aborted) { - throw new DevicePreviewRequestError('服务端预览状态读取超时,请稍后刷新', 502); - } - throw error; - } finally { - clearTimeout(timeout); - } - if (response.status === 404) return null; - if (response.status === 401 || response.status === 403) { - throw new DevicePreviewRequestError('登录状态已失效,请重新登录后刷新预览', response.status); - } - if (!response.ok) { - throw new DevicePreviewRequestError(`服务端预览状态读取失败(${response.status})`, 502); - } - - const project = projectFromPayload(await readResponsePayload(response)); - if (!project) { - throw new DevicePreviewRequestError('服务端返回了无法识别的预览状态', 502); - } - return project; -} - -async function createOwnedReleasePreview( - appId: string, - releaseId: string, - accessToken: string, -): Promise { - const worksBase = normalizedWorksBaseUrl(); - const abortController = new AbortController(); - const timeout = setTimeout(() => abortController.abort(), OWNER_STATUS_TIMEOUT_MS); - let response: Response; - try { - response = await proxyAwareFetch( - new URL( - `/api/projects/${encodeURIComponent(appId)}/releases/${encodeURIComponent(releaseId)}/preview-url`, - worksBase, - ).toString(), - { - method: 'POST', - headers: { Authorization: `Bearer ${accessToken}` }, - signal: abortController.signal, - }, - ); - } catch (error) { - if (abortController.signal.aborted) { - throw new DevicePreviewRequestError('服务端预览地址生成超时,请稍后刷新', 502); - } - throw error; - } finally { - clearTimeout(timeout); - } - - if (response.status === 401 || response.status === 403) { - throw new DevicePreviewRequestError('登录状态已失效,请重新登录后刷新预览', response.status); - } - if (!response.ok) { - throw new DevicePreviewRequestError(`服务端预览地址生成失败(${response.status})`, 502); - } - const payload = asRecord(await readResponsePayload(response)); - const previewUrl = readString(payload?.url); - if (!previewUrl) { - throw new DevicePreviewRequestError('服务端返回了无法识别的预览地址', 502); - } - return previewUrl; -} - -function localSnapshot( - projectId: string, - deployment: WorksSubmissionBindingRecord | null, -): Omit { - return { - projectId, - ...(deployment?.app_id ? { appId: deployment.app_id } : {}), - ...(deployment?.version_id ? { versionId: deployment.version_id } : {}), - ...(deployment?.version_name ? { versionName: deployment.version_name } : {}), - ...(deployment?.review_status ? { reviewStatus: deployment.review_status } : {}), - ...(deployment?.updated_at ? { updatedAt: deployment.updated_at } : {}), - }; -} - -function normalizedReviewStatus(value: string | null): string | null { - return value?.trim().toLowerCase() || null; -} - -async function resolveDevicePreview( - projectId: string, - deployment: WorksSubmissionBindingRecord | null, -): Promise { - if (!deployment) { - return { - projectId, - state: 'not_deployed', - message: '当前项目还没有与本机绑定的服务端预览版本。', - }; - } - - if (deployment.project_id !== projectId) { - return { - projectId, - state: 'unavailable', - message: '本地预览记录不属于当前项目,请重新生成。', - }; - } - - const base = localSnapshot(projectId, deployment); - - if (deployment.status === 'legacy_retired') { - return { - ...base, - state: 'unavailable', - message: deployment.message || '旧版自动部署任务已停用,请重新提交审核。', - }; - } - - if (!deployment.app_id || !deployment.version_id || !deployment.version_name) { - return { - ...base, - state: 'unavailable', - message: '本地预览记录缺少版本标识,请重新生成。', - }; - } - - const accessToken = await requireManagedAccessToken(); - const remote = await fetchOwnedRemoteProject(deployment.app_id, accessToken); - if (!remote) { - return { - ...base, - state: 'building', - message: '服务端正在接收或构建本次预览版本。', - }; - } - - if (remote.appId !== deployment.app_id) { - return { - ...base, - state: 'unavailable', - message: '服务端返回的项目标识不匹配,已停止展示预览。', - }; - } - - const reviewStatus = normalizedReviewStatus(remote.reviewStatus); - const remoteBase = { - ...base, - ...(remote.reviewStatus ? { reviewStatus: remote.reviewStatus } : {}), - }; - const targetVersionMatches = remote.latestVersionId === deployment.version_id - && remote.latestVersionName === deployment.version_name; - const launchUrl = remote.runtimeUrl - ? trustedWorksProjectPlayUrl( - remote.runtimeUrl, - normalizedWorksBaseUrl(), - deployment.app_id, - ) - : null; - - if (!targetVersionMatches) { - return { - ...remoteBase, - state: 'unavailable', - message: '服务端最新版本已与本地绑定版本不一致,请重新同步项目状态。', - }; - } - - if (reviewStatus && FAILED_REVIEW_STATUSES.has(reviewStatus)) { - return { - ...remoteBase, - state: 'unavailable', - message: '本次预览版本未通过服务端检查,请修改后重新生成。', - }; - } - - if (reviewStatus === 'building' || reviewStatus === 'queued' || reviewStatus === 'reviewing') { - return { - ...remoteBase, - state: 'building', - message: '本次预览版本仍在构建或审核,暂不展示旧版本。', - }; - } - - if (reviewStatus === 'pending_review') { - if (!remote.latestReleaseId) { - return { - ...remoteBase, - state: 'building', - message: '本次版本已构建完成,正在准备审核前预览。', - }; - } - const rawPreviewUrl = await createOwnedReleasePreview( - deployment.app_id, - remote.latestReleaseId, - accessToken, - ); - const previewUrl = trustedWorksReleasePreviewUrl( - rawPreviewUrl, - normalizedWorksBaseUrl(), - remote.latestReleaseId, - ); - if (!previewUrl) { - return { - ...remoteBase, - state: 'unavailable', - message: '服务端返回的审核前预览地址未通过安全校验。', - }; - } - return { - ...remoteBase, - state: 'ready', - launchUrl: previewUrl, - message: '审核前真机预览已就绪。', - }; - } - - if (remote.runtimeUrl && !launchUrl) { - return { - ...remoteBase, - state: 'unavailable', - message: '服务端返回的预览地址未通过安全校验。', - }; - } - - if ( - remote.playable === true - && launchUrl - && remote.runtimeVersionName === deployment.version_name - && reviewStatus !== null - && READY_REVIEW_STATUSES.has(reviewStatus) - ) { - return { - ...remoteBase, - state: 'ready', - launchUrl, - message: '真机预览已就绪。', - }; - } - - if ( - reviewStatus !== null - && READY_REVIEW_STATUSES.has(reviewStatus) - && remote.runtimeVersionName !== deployment.version_name - ) { - return { - ...remoteBase, - state: 'building', - message: '本次版本已通过审核,服务端运行地址仍在切换中。', - }; - } - - return { - ...remoteBase, - state: 'unavailable', - message: '服务端尚未确认本次版本可供真机访问。', - }; -} - -export async function handleDevicePreviewRoutes( - req: IncomingMessage, - res: ServerResponse, - url: URL, - ctx: HostApiContext, -): Promise { - const match = url.pathname.match(DEVICE_PREVIEW_ROUTE); - if (!match) return false; - - res.setHeader('Cache-Control', 'no-store'); - if (req.method !== 'GET') { - res.setHeader('Allow', 'GET'); - sendJson(res, 405, { success: false, error: 'Method not allowed' }); - return true; - } - if (!hasRendererCapability(req)) { - sendJson(res, 403, { success: false, error: 'Renderer capability required' }); - return true; - } - - try { - const projectId = decodeURIComponent(match[1] ?? '').trim(); - const activeProject = await ctx.opencodeProjectStore.getActiveProject(); - if (!projectId || !activeProject || activeProject.id !== projectId) { - sendJson(res, 409, { success: false, error: 'Select this project before opening device preview' }); - return true; - } - - const deployment = ctx.worksSubmissionBinding - ? await ctx.worksSubmissionBinding.get(projectId) - : null; - const preview = await resolveDevicePreview(projectId, deployment); - sendJson(res, 200, { success: true, preview }); - } catch (error) { - sendJson(res, error instanceof DevicePreviewRequestError ? error.statusCode : 502, { - success: false, - error: error instanceof Error ? error.message : String(error), - }); - } - return true; -} diff --git a/electron/api/routes/opencode.ts b/electron/api/routes/opencode.ts index 93add09..8867737 100644 --- a/electron/api/routes/opencode.ts +++ b/electron/api/routes/opencode.ts @@ -24,7 +24,7 @@ import { } from '../../opencode/playwright-mcp'; import { NIANCODE_USER_MODEL_ACCOUNT_ID } from '../../../shared/user-model-config'; import { listInstalledOpencodeSkills } from '../../opencode/skill-registry'; -import { BUNDLED_COURSE_SKILL_IDS } from '../../opencode/superpowers'; +import { BUNDLED_COURSE_SKILL_IDS } from '../../opencode/course-skills'; import { logger } from '../../utils/logger'; import { getProviderService } from '../../services/providers/provider-service'; import { getValidWorksSquareAccessToken } from '../../services/works-square-session'; @@ -41,6 +41,7 @@ import { } from '../../opencode/project-conversations'; import { completeProjectSession, + markProjectSessionRead, removeProjectSessionMetadata, patchProjectSessionMetadata, upsertProjectSessionMetadata, @@ -1017,16 +1018,8 @@ export async function handleOpencodeRoutes( if (!body.projectId || !body.config) throw new Error('Missing project configuration'); const project = await findProjectById(ctx, body.projectId); if (!project) throw new Error('Project not found'); - const previousResult = await readProjectConfig(project.path); - const previousConfig = previousResult.status === 'valid' ? previousResult.config : null; const config = await writeProjectConfig(project.path, body.config); - let status = ctx.opencodeManager.getStatus(); - const runtimeSettingsChanged = !previousConfig - || previousConfig.defaultModel !== config.defaultModel - || previousConfig.superpowersEnabled !== config.superpowersEnabled; - if (config.initialized && runtimeSettingsChanged && status.state === 'running') { - status = await ctx.opencodeManager.restart(); - } + const status = ctx.opencodeManager.getStatus(); sendJson(res, 200, { success: true, config, knowledgeFiles: await listProjectKnowledge(project.path), status }); } catch (error) { sendJson(res, 500, { success: false, error: error instanceof Error ? error.message : String(error) }); @@ -1079,7 +1072,7 @@ export async function handleOpencodeRoutes( nextState = removeProjectSessionMetadata(state, sessionId, now); break; case 'read': - nextState = patchProjectSessionMetadata(state, sessionId, { unreadCount: 0 }, now); + nextState = markProjectSessionRead(state, sessionId, now); break; case 'increment-unread': { const current = state.sessions.find((item) => item.sessionId === sessionId); diff --git a/electron/api/server.ts b/electron/api/server.ts index 5481e6d..c3cab78 100644 --- a/electron/api/server.ts +++ b/electron/api/server.ts @@ -17,7 +17,6 @@ import { handleUsageRoutes } from './routes/usage'; import { handleFileRoutes } from './routes/files'; import { handleMeowaGameAssetsRoutes } from './routes/meowa-game-assets'; import { handleAgentBrowserRoutes } from './routes/agent-browser'; -import { handleDevicePreviewRoutes } from './routes/device-preview'; import { sendJson, setCorsHeaders, requireJsonContentType } from './route-utils'; import { rotateRendererCapability } from './renderer-capability'; @@ -34,7 +33,6 @@ const coreRouteHandlers: RouteHandler[] = [ handleAuthRoutes, handleImageWorkspaceRoutes, handleWorksRoutes, - handleDevicePreviewRoutes, handleAgentBrowserRoutes, handleUserSyncRoutes, handleOpencodeRoutes, diff --git a/electron/main/index.ts b/electron/main/index.ts index 4050b08..8f69ffc 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -16,17 +16,19 @@ import { resolveOpencodeRuntimePaths } from '../opencode/paths'; import { resolveBundledAgentBrowserPluginPath, resolveBundledCourseSkillsDir, - resolveBundledSuperpowersDir, -} from '../opencode/superpowers'; +} from '../opencode/course-skills'; import { createElectronProjectStorage, createProjectStore, type OpencodeProjectStore, } from '../opencode/project-store'; +import { readProjectConfig } from '../opencode/project-config'; +import { warmupOpencodeRuntime } from '../opencode/startup-warmup'; import { registerIpcHandlers } from './ipc-handlers'; import { createTray } from './tray'; import { createMenu } from './menu'; import { registerZoomShortcuts } from './zoom-shortcuts'; +import { getNativeWindowMaterialOptions } from './window-material'; import { appUpdater, registerUpdateHandlers } from './updater'; import { logger } from '../utils/logger'; @@ -90,6 +92,39 @@ const WINDOWS_APP_USER_MODEL_ID = 'app.niancode.desktop'; const isE2EMode = process.env.NIANCODE_E2E === '1'; const requestedUserDataDir = process.env.NIANCODE_USER_DATA_DIR?.trim(); +async function buildMakeloreOpencodeRuntimeConfig() { + return await buildOpencodeRuntimeConfigFromNianCodeProviders({ + mcpServers: { + [PLAYWRIGHT_MCP_SERVER_ID]: resolvePlaywrightMcpServer(), + }, + }); +} + +function scheduleOpencodeRuntimeWarmup(): void { + if (isE2EMode) return; + + void warmupOpencodeRuntime({ + hasAuthenticatedSession: () => Boolean(getWorksSquareSessionSnapshot()), + getStatus: () => opencodeManager.getStatus(), + getActiveProject: () => opencodeProjectStore.getActiveProject(), + readProjectConfig, + getConfiguredProviderCount: async () => { + const runtime = await buildMakeloreOpencodeRuntimeConfig(); + return Object.keys(runtime.config.provider).length; + }, + start: () => opencodeManager.start(), + onError: (error, phase) => { + logger.warn(`[opencode-runtime] Startup warmup ${phase} failed`, error); + }, + }).then((result) => { + if (result.started) { + logger.info('[opencode-runtime] Startup warmup completed'); + } + }).catch((error) => { + logger.warn('[opencode-runtime] Startup warmup could not be scheduled', error); + }); +} + if (isE2EMode && requestedUserDataDir) { app.setPath('userData', requestedUserDataDir); } @@ -205,14 +240,17 @@ function createWindow(): BrowserWindow { const isWindows = process.platform === 'win32'; const useCustomTitleBar = isWindows; const shouldSkipSetupForE2E = process.env.NIANCODE_E2E_SKIP_SETUP === '1'; + const minimumWorkspaceColumnWidth = 256; + const minimumWorkspaceWidth = minimumWorkspaceColumnWidth * 4; const win = new BrowserWindow({ title: 'Makelore', width: 1280, height: 800, - minWidth: 1100, + minWidth: minimumWorkspaceWidth, minHeight: 700, icon: getAppIcon(), + ...getNativeWindowMaterialOptions(process.platform), webPreferences: { preload: join(__dirname, '../preload/index.js'), nodeIntegration: false, @@ -221,7 +259,10 @@ function createWindow(): BrowserWindow { webviewTag: false, }, titleBarStyle: isMac ? 'hiddenInset' : useCustomTitleBar ? 'hidden' : 'default', - trafficLightPosition: isMac ? { x: 16, y: 16 } : undefined, + // Keep the native traffic lights on the same centerline as the 40px + // renderer title bar. The native glyphs sit about 7px below the + // configured origin, so y=13 centers them on the renderer controls. + trafficLightPosition: isMac ? { x: 16, y: 13 } : undefined, frame: isMac || !useCustomTitleBar, show: false, }); @@ -534,6 +575,11 @@ async function initialize(): Promise { browserOAuthManager.on('oauth:error', (error) => { hostEventBus.emit('oauth:error', error); }); + + // Start the local Code runtime in the background once the Main process has + // restored session state and registered the Host API. Chat still retains + // its lazy-start fallback for first-run and failed-warmup cases. + scheduleOpencodeRuntimeWarmup(); } if (gotTheLock) { @@ -579,11 +625,6 @@ if (gotTheLock) { platform: process.platform, arch: process.arch, }); - const bundledSuperpowersDir = resolveBundledSuperpowersDir({ - isPackaged: app.isPackaged, - resourcesPath: process.resourcesPath, - appPath: app.getAppPath(), - }); const bundledCourseSkillsDir = resolveBundledCourseSkillsDir({ isPackaged: app.isPackaged, resourcesPath: process.resourcesPath, @@ -599,17 +640,12 @@ if (gotTheLock) { binPath: opencodePaths.binPath, preflightPreferredPort: true, userDataDir: app.getPath('userData'), - bundledSuperpowersDir, bundledCourseSkillsDir, bundledAgentBrowserPluginPath, pythonRuntime, uvRuntime, runtimeConfigProvider: async () => { - const runtime = await buildOpencodeRuntimeConfigFromNianCodeProviders({ - mcpServers: { - [PLAYWRIGHT_MCP_SERVER_ID]: resolvePlaywrightMcpServer(), - }, - }); + const runtime = await buildMakeloreOpencodeRuntimeConfig(); return { ...runtime, config: runtime.config as unknown as Record, diff --git a/electron/main/window-material.ts b/electron/main/window-material.ts new file mode 100644 index 0000000..e6c98e4 --- /dev/null +++ b/electron/main/window-material.ts @@ -0,0 +1,23 @@ +import type { BrowserWindowConstructorOptions } from 'electron'; + +type NativeWindowMaterialOptions = Pick< + BrowserWindowConstructorOptions, + 'backgroundColor' | 'transparent' | 'vibrancy' | 'visualEffectState' +>; + +/** + * Use the native macOS material only where Electron and the window manager + * support it. Other platforms keep the normal opaque window as a safe fallback. + */ +export function getNativeWindowMaterialOptions( + platform: NodeJS.Platform, +): NativeWindowMaterialOptions { + if (platform !== 'darwin') return {}; + + return { + backgroundColor: '#00000000', + transparent: true, + vibrancy: 'under-window', + visualEffectState: 'active', + }; +} diff --git a/electron/opencode/client.ts b/electron/opencode/client.ts index 03b9038..e5635d9 100644 --- a/electron/opencode/client.ts +++ b/electron/opencode/client.ts @@ -90,6 +90,12 @@ export interface OpencodeSkillInfo { description?: string; location: string; content: string; + entries?: OpencodeSkillEntry[]; +} + +export interface OpencodeSkillEntry { + path: string; + type: 'file' | 'directory'; } export interface RevertOpencodeSessionMessageInput { diff --git a/electron/opencode/course-skills.ts b/electron/opencode/course-skills.ts new file mode 100644 index 0000000..55d0d17 --- /dev/null +++ b/electron/opencode/course-skills.ts @@ -0,0 +1,112 @@ +import { + cpSync, + existsSync, + lstatSync, + mkdirSync, + rmSync, + unlinkSync, +} from 'node:fs'; +import { join } from 'node:path'; + +export interface BundledSkillsPathInput { + isPackaged: boolean; + resourcesPath: string; + appPath: string; +} + +export interface EnsureBundledCourseSkillsOptions { + managedConfigDir: string; + sourceDir?: string; +} + +export const BUNDLED_COURSE_SKILL_IDS = [ + 'agent-browser', + 'deploy-publish-check', + 'designer-design-spec', + 'dev-build-test', + 'game-assets', + 'marketing-launch-story', + 'nianxxgame-skill', + 'partner-agent-showcase', + 'pm-project-plan', + 'product-demo-prototype', + 'ui-ux-course-quality', + 'youth-plain-language', + 'youth-ai-product-course', +] as const; +const RETIRED_COURSE_SKILL_IDS = ['course-stage-review', 'student-growth-logger'] as const; +const LEGACY_SUPERPOWERS_ARTIFACTS = [ + 'plugins/superpowers-niancode.js', + 'plugins/superpowers.js', + 'superpowers-active.json', + 'superpowers-bundles', +] as const; + +export function resolveBundledCourseSkillsDir(input: BundledSkillsPathInput): string { + return input.isPackaged + ? join(input.resourcesPath, 'course-skills') + : join(input.appPath, '.opencode', 'skills'); +} + +export function resolveBundledAgentBrowserPluginPath(input: BundledSkillsPathInput): string { + return join( + resolveBundledCourseSkillsDir(input), + 'agent-browser', + '.opencode', + 'plugins', + 'niancode-agent-browser.js', + ); +} + +export function getManagedOpencodeConfigDir(userDataDir: string): string { + return join(userDataDir, 'opencode', 'niancode-config'); +} + +function copyDirectorySync(sourceDir: string, targetDir: string): void { + cpSync(sourceDir, targetDir, { + recursive: true, + force: true, + filter: (sourcePath, targetPath) => { + // Node 24.14's Windows override path can corrupt non-ASCII destinations + // when replacing a file. Public unlink keeps force-overwrite semantics. + if (lstatSync(sourcePath).isFile() && existsSync(targetPath)) { + unlinkSync(targetPath); + } + return true; + }, + }); +} + +export function removeLegacySuperpowersArtifacts(managedConfigDir: string): void { + for (const artifact of LEGACY_SUPERPOWERS_ARTIFACTS) { + rmSync(join(managedConfigDir, artifact), { recursive: true, force: true }); + } +} + +export function ensureBundledCourseSkills(options: EnsureBundledCourseSkillsOptions): boolean { + const sourceDir = options.sourceDir?.trim(); + if (!sourceDir || !existsSync(sourceDir)) { + return false; + } + + const targetSkillsDir = join(options.managedConfigDir, 'skills'); + mkdirSync(targetSkillsDir, { recursive: true }); + for (const skillId of RETIRED_COURSE_SKILL_IDS) { + rmSync(join(targetSkillsDir, skillId), { recursive: true, force: true }); + } + copyDirectorySync(sourceDir, targetSkillsDir); + + return true; +} + +export function ensureBundledAgentBrowserPlugin(options: { + managedConfigDir: string; + sourcePath?: string; +}): boolean { + const sourcePath = options.sourcePath?.trim(); + if (!sourcePath || !existsSync(sourcePath)) return false; + const targetPluginsDir = join(options.managedConfigDir, 'plugins'); + mkdirSync(targetPluginsDir, { recursive: true }); + cpSync(sourcePath, join(targetPluginsDir, 'niancode-agent-browser.js'), { force: true }); + return true; +} diff --git a/electron/opencode/manager.ts b/electron/opencode/manager.ts index 434ddf1..2b5a9db 100644 --- a/electron/opencode/manager.ts +++ b/electron/opencode/manager.ts @@ -11,9 +11,9 @@ import { import { ensureBundledAgentBrowserPlugin, ensureBundledCourseSkills, - ensureBundledSuperpowersPlugin, getManagedOpencodeConfigDir, -} from './superpowers'; + removeLegacySuperpowersArtifacts, +} from './course-skills'; import { logger } from '../utils/logger'; import { prependManagedRuntimesToPath, @@ -164,7 +164,6 @@ export interface OpencodeManagerOptions { port: number; binPath: string; userDataDir?: string; - bundledSuperpowersDir?: string; bundledCourseSkillsDir?: string; bundledAgentBrowserPluginPath?: string; pythonRuntime?: PythonRuntime; @@ -1049,10 +1048,7 @@ export class OpencodeManager extends EventEmitter { mkdirSync(dataHome, { recursive: true }); mkdirSync(cacheHome, { recursive: true }); mkdirSync(managedConfigDir, { recursive: true }); - ensureBundledSuperpowersPlugin({ - managedConfigDir, - sourceDir: this.options.bundledSuperpowersDir, - }); + removeLegacySuperpowersArtifacts(managedConfigDir); ensureBundledCourseSkills({ managedConfigDir, sourceDir: this.options.bundledCourseSkillsDir, diff --git a/electron/opencode/paths.ts b/electron/opencode/paths.ts index 65bbbd6..7c85313 100644 --- a/electron/opencode/paths.ts +++ b/electron/opencode/paths.ts @@ -1,5 +1,6 @@ -import { existsSync, readFileSync } from 'node:fs'; -import { join } from 'node:path'; +import { existsSync, readFileSync, realpathSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { dirname, join } from 'node:path'; export interface RuntimePathInput { isPackaged: boolean; @@ -47,11 +48,31 @@ function getNativeOpencodeBinName(platform: NodeJS.Platform): string { return platform === 'win32' ? 'opencode.exe' : 'opencode'; } -function resolveDevelopmentNativeBinPath(input: RuntimePathInput): string | undefined { +function resolveNativePackageRoot(runtimeDir: string, packageName: string): string | undefined { + try { + const resolvedRuntimeDir = realpathSync(runtimeDir); + const runtimeRequire = createRequire(join(resolvedRuntimeDir, 'package.json')); + return dirname(runtimeRequire.resolve(`${packageName}/package.json`)); + } catch { + return undefined; + } +} + +function resolveDevelopmentNativeBinPath( + input: RuntimePathInput, + runtimeDir: string, +): string | undefined { const arch = input.arch ?? process.arch; for (const packageName of getDevelopmentNativePackageCandidates(input.platform, arch)) { - const binPath = join(input.appPath, 'node_modules', packageName, 'bin', getNativeOpencodeBinName(input.platform)); - if (existsSync(binPath)) return binPath; + const binaryName = getNativeOpencodeBinName(input.platform); + const hoistedBinPath = join(input.appPath, 'node_modules', packageName, 'bin', binaryName); + if (existsSync(hoistedBinPath)) return hoistedBinPath; + + const nativePackageRoot = resolveNativePackageRoot(runtimeDir, packageName); + if (!nativePackageRoot) continue; + + const virtualStoreBinPath = join(nativePackageRoot, 'bin', binaryName); + if (existsSync(virtualStoreBinPath)) return virtualStoreBinPath; } return undefined; } @@ -73,7 +94,7 @@ export function resolveOpencodeRuntimePaths(input: RuntimePathInput): OpencodeRu : join(input.appPath, 'node_modules', OPENCODE_RUNTIME_PACKAGE); const developmentNativeBinPath = input.isPackaged ? undefined - : resolveDevelopmentNativeBinPath(input); + : resolveDevelopmentNativeBinPath(input, runtimeDir); return { runtimeDir, diff --git a/electron/opencode/project-config.ts b/electron/opencode/project-config.ts index ef0c8dd..d2b7718 100644 --- a/electron/opencode/project-config.ts +++ b/electron/opencode/project-config.ts @@ -31,6 +31,21 @@ function normalizeProjectType(value: unknown): ProjectType { throw new Error('Invalid project type'); } +function needsLegacyAgentModelMigration(value: unknown): boolean { + if (!value || typeof value !== 'object' || Array.isArray(value)) return false; + const raw = value as Partial; + const defaultModel = typeof raw.defaultModel === 'string' && raw.defaultModel.trim() + ? raw.defaultModel.trim() + : null; + if (!defaultModel || !Array.isArray(raw.agents)) return false; + return raw.agents.some((agent) => ( + Boolean(agent) + && typeof agent === 'object' + && !(typeof (agent as { model?: unknown }).model === 'string' + && (agent as { model?: string }).model?.trim()) + )); +} + function normalizeAgent(value: unknown): ProjectAgentConfig | null { if (!value || typeof value !== 'object' || Array.isArray(value)) return null; const raw = value as Partial; @@ -80,9 +95,16 @@ export function normalizeProjectConfig(value: unknown): ProjectConfig { } const raw = value as Partial; if (raw.schemaVersion !== 1) throw new Error('Unsupported project config schema'); + const defaultModel = typeof raw.defaultModel === 'string' && raw.defaultModel.trim() + ? raw.defaultModel.trim() + : null; const agents = Array.isArray(raw.agents) ? raw.agents.map(normalizeAgent) : []; if (agents.some((item) => !item)) throw new Error('Invalid project Agent configuration'); - const normalizedAgents = agents.filter((item): item is ProjectAgentConfig => Boolean(item)); + const normalizedAgents = agents + .filter((item): item is ProjectAgentConfig => Boolean(item)) + .map((agent) => agent.model || !defaultModel + ? agent + : { ...agent, model: defaultModel }); if (new Set(normalizedAgents.map((item) => item.id)).size !== normalizedAgents.length) { throw new Error('Duplicate project Agent id'); } @@ -97,10 +119,9 @@ export function normalizeProjectConfig(value: unknown): ProjectConfig { schemaVersion: 1, projectType: normalizeProjectType(raw.projectType), initialized, - superpowersEnabled: raw.superpowersEnabled === true, - defaultModel: typeof raw.defaultModel === 'string' && raw.defaultModel.trim() - ? raw.defaultModel.trim() - : null, + // Keep this legacy field readable for compatibility, but runtime model + // selection is owned by each project Agent. + defaultModel, agents: normalizedAgents, knowledgeDirectory: 'knowledge', createdAt, @@ -111,7 +132,12 @@ export function normalizeProjectConfig(value: unknown): ProjectConfig { export async function readProjectConfig(projectPath: string): Promise { try { const raw = JSON.parse(await readFile(configPath(projectPath), 'utf8')) as unknown; - return { status: 'valid', config: normalizeProjectConfig(raw) }; + const config = normalizeProjectConfig(raw); + if (needsLegacyAgentModelMigration(raw)) { + if (config.initialized) await materializeAgents(projectPath, config); + await writeFile(configPath(projectPath), `${JSON.stringify(config, null, 2)}\n`, 'utf8'); + } + return { status: 'valid', config }; } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { status: 'missing' }; return { status: 'invalid', error: error instanceof Error ? error.message : String(error) }; @@ -165,7 +191,7 @@ function buildAgentMarkdown(config: ProjectConfig, agent: ProjectAgentConfig): s ? agent.skillIds.map((skill) => ` ${skill}: allow`).join('\n') : ' "*": deny'; const shellPermission = agent.skillIds.includes('game-assets') ? ' bash: allow\n' : ''; - const model = agent.model ?? config.defaultModel; + const model = agent.model; const prompt = agent.prompt.trim() || buildProjectAgentPrompt(config, agent); return `--- description: ${yamlString(agent.name)} diff --git a/electron/opencode/skill-registry.ts b/electron/opencode/skill-registry.ts index 8a7af27..5ff96cf 100644 --- a/electron/opencode/skill-registry.ts +++ b/electron/opencode/skill-registry.ts @@ -1,6 +1,6 @@ import { readdir, readFile } from 'node:fs/promises'; -import { basename, dirname, join } from 'node:path'; -import type { OpencodeSkillInfo } from './client'; +import { basename, dirname, join, relative, sep } from 'node:path'; +import type { OpencodeSkillEntry, OpencodeSkillInfo } from './client'; const SKILL_ROOT_NAMES = ['skill', 'skills'] as const; @@ -29,6 +29,36 @@ async function collectSkillFiles(root: string): Promise { return files; } +async function collectSkillEntries(root: string, current = root): Promise { + let entries; + try { + entries = await readdir(current, { withFileTypes: true }); + } catch (error) { + if (typeof error === 'object' && error && 'code' in error && error.code === 'ENOENT') { + return []; + } + throw error; + } + + entries.sort((left, right) => { + const directoryOrder = Number(right.isDirectory()) - Number(left.isDirectory()); + return directoryOrder || left.name.localeCompare(right.name); + }); + + const result: OpencodeSkillEntry[] = []; + for (const entry of entries) { + const entryPath = join(current, entry.name); + const relativePath = relative(root, entryPath).split(sep).join('/'); + if (entry.isDirectory()) { + result.push({ path: relativePath, type: 'directory' }); + result.push(...await collectSkillEntries(root, entryPath)); + } else if (entry.isFile()) { + result.push({ path: relativePath, type: 'file' }); + } + } + return result; +} + function unquoteYamlScalar(value: string): string { const trimmed = value.trim(); if ( @@ -73,6 +103,7 @@ export async function listInstalledOpencodeSkills(managedConfigDir?: string | nu description: readFrontmatterScalar(content, 'description'), location, content, + entries: await collectSkillEntries(dirname(location)), }); } diff --git a/electron/opencode/startup-warmup.ts b/electron/opencode/startup-warmup.ts new file mode 100644 index 0000000..4325ab0 --- /dev/null +++ b/electron/opencode/startup-warmup.ts @@ -0,0 +1,92 @@ +import type { OpencodeStatus } from './manager'; + +type ProjectConfigReadResult = { + status: 'valid' | 'missing' | 'invalid'; + config?: { initialized?: boolean }; +}; + +export type OpencodeStartupWarmupSkipReason = + | 'no-session' + | 'runtime-active' + | 'no-active-project' + | 'project-not-ready' + | 'no-provider' + | 'eligibility-check-failed' + | 'start-failed'; + +export type OpencodeStartupWarmupResult = + | { started: true; status: OpencodeStatus } + | { started: false; reason: OpencodeStartupWarmupSkipReason; error?: unknown }; + +export interface OpencodeStartupWarmupDependencies { + hasAuthenticatedSession: () => boolean; + getStatus: () => OpencodeStatus; + getActiveProject: () => Promise<{ path: string } | null>; + readProjectConfig: (projectPath: string) => Promise; + getConfiguredProviderCount: () => Promise; + start: () => Promise; + onError?: (error: unknown, phase: 'eligibility' | 'start') => void; +} + +function reportError( + dependencies: OpencodeStartupWarmupDependencies, + error: unknown, + phase: 'eligibility' | 'start', +): void { + try { + dependencies.onError?.(error, phase); + } catch { + // Logging must never turn a background warmup into an unhandled rejection. + } +} + +/** + * Starts the local runtime in the background when the persisted app state is + * ready for a Code session. This deliberately does not throw: startup + * warmup is an optimization and the normal Chat-page lazy start remains the + * recovery path when it is unavailable. + */ +export async function warmupOpencodeRuntime( + dependencies: OpencodeStartupWarmupDependencies, +): Promise { + if (!dependencies.hasAuthenticatedSession()) { + return { started: false, reason: 'no-session' }; + } + + const status = dependencies.getStatus(); + if (status.state !== 'stopped') { + return { started: false, reason: 'runtime-active' }; + } + + let project: { path: string } | null; + let projectConfig: ProjectConfigReadResult; + let providerCount: number; + try { + project = await dependencies.getActiveProject(); + if (!project) { + return { started: false, reason: 'no-active-project' }; + } + + projectConfig = await dependencies.readProjectConfig(project.path); + if (projectConfig.status !== 'valid' || projectConfig.config?.initialized !== true) { + return { started: false, reason: 'project-not-ready' }; + } + + providerCount = await dependencies.getConfiguredProviderCount(); + } catch (error) { + reportError(dependencies, error, 'eligibility'); + return { started: false, reason: 'eligibility-check-failed', error }; + } + + if (!Number.isFinite(providerCount) || providerCount <= 0) { + return { started: false, reason: 'no-provider' }; + } + + try { + const startedStatus = await dependencies.start(); + return { started: true, status: startedStatus }; + } catch (error) { + reportError(dependencies, error, 'start'); + return { started: false, reason: 'start-failed', error }; + } +} diff --git a/electron/opencode/superpowers.ts b/electron/opencode/superpowers.ts deleted file mode 100644 index eb506f9..0000000 --- a/electron/opencode/superpowers.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { randomUUID } from 'node:crypto'; -import { - cpSync, - existsSync, - lstatSync, - mkdirSync, - readFileSync, - renameSync, - rmSync, - unlinkSync, - writeFileSync, -} from 'node:fs'; -import { isAbsolute, join, relative, resolve, sep } from 'node:path'; -import { retryTransientFilesystemOperation } from './filesystem-retry'; - -export interface BundledSuperpowersPathInput { - isPackaged: boolean; - resourcesPath: string; - appPath: string; -} - -export interface EnsureBundledSuperpowersOptions { - managedConfigDir: string; - sourceDir?: string; -} - -export interface EnsureBundledCourseSkillsOptions { - managedConfigDir: string; - sourceDir?: string; -} - -const WRAPPER_PLUGIN_NAME = 'superpowers-niancode.js'; -const SUPERPOWERS_REPO_DIR = 'superpowers'; -const SUPERPOWERS_BUNDLES_DIR = 'superpowers-bundles'; -const SUPERPOWERS_ACTIVE_MANIFEST = 'superpowers-active.json'; -export const BUNDLED_COURSE_SKILL_IDS = [ - 'agent-browser', - 'designer-design-spec', - 'dev-build-test', - 'game-assets', - 'marketing-launch-story', - 'nianxxgame-skill', - 'partner-agent-showcase', - 'pm-project-plan', - 'product-demo-prototype', - 'ui-ux-course-quality', - 'youth-plain-language', - 'youth-ai-product-course', -] as const; -const RETIRED_COURSE_SKILL_IDS = [ - 'course-stage-review', - 'deploy-publish-check', - 'student-growth-logger', -] as const; - -export function resolveBundledSuperpowersDir(input: BundledSuperpowersPathInput): string { - const resourcesDir = input.isPackaged - ? join(input.resourcesPath, 'resources') - : join(input.appPath, 'resources'); - - return join(resourcesDir, 'skills', SUPERPOWERS_REPO_DIR); -} - -export function resolveBundledCourseSkillsDir(input: BundledSuperpowersPathInput): string { - return input.isPackaged - ? join(input.resourcesPath, 'course-skills') - : join(input.appPath, '.opencode', 'skills'); -} - -export function resolveBundledAgentBrowserPluginPath(input: BundledSuperpowersPathInput): string { - return join( - resolveBundledCourseSkillsDir(input), - 'agent-browser', - '.opencode', - 'plugins', - 'niancode-agent-browser.js', - ); -} - -export function getManagedOpencodeConfigDir(userDataDir: string): string { - return join(userDataDir, 'opencode', 'niancode-config'); -} - -function readBundledSuperpowersVersion(sourceDir: string): string { - const packagePath = join(sourceDir, 'package.json'); - const packageJson = JSON.parse(retryTransientFilesystemOperation( - () => readFileSync(packagePath, 'utf8'), - )) as { version?: unknown }; - if (typeof packageJson.version !== 'string' || !packageJson.version.trim()) { - throw new Error(`Bundled Superpowers package has no valid version: ${packagePath}`); - } - return packageJson.version.trim(); -} - -function toSafeBundleName(version: string): string { - const safeVersion = version.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, ''); - if (!safeVersion) { - throw new Error(`Bundled Superpowers version cannot form a safe directory name: ${version}`); - } - return safeVersion; -} - -function isCompleteBundle(bundleDir: string, expectedVersion: string): boolean { - try { - if (!existsSync(join(bundleDir, '.opencode', 'plugins', 'superpowers.js')) - || !existsSync(join(bundleDir, 'skills', 'using-superpowers', 'SKILL.md'))) { - return false; - } - return readBundledSuperpowersVersion(bundleDir) === expectedVersion; - } catch { - return false; - } -} - -function resolveManifestBundle( - managedConfigDir: string, - bundlesDir: string, - expectedVersion: string, -): string | null { - try { - const manifest = JSON.parse(retryTransientFilesystemOperation( - () => readFileSync(join(managedConfigDir, SUPERPOWERS_ACTIVE_MANIFEST), 'utf8'), - )) as { version?: unknown; directory?: unknown }; - if (manifest.version !== expectedVersion || typeof manifest.directory !== 'string') { - return null; - } - - const bundlesRoot = resolve(bundlesDir); - const bundleDir = resolve(bundlesDir, manifest.directory); - const relativeBundlePath = relative(bundlesRoot, bundleDir); - if (!relativeBundlePath - || relativeBundlePath === '..' - || relativeBundlePath.startsWith(`..${sep}`) - || isAbsolute(relativeBundlePath)) { - return null; - } - return isCompleteBundle(bundleDir, expectedVersion) ? bundleDir : null; - } catch { - return null; - } -} - -function copyDirectorySync(sourceDir: string, targetDir: string): void { - cpSync(sourceDir, targetDir, { - recursive: true, - force: true, - filter: (sourcePath, targetPath) => { - // Node 24.14's Windows override path can corrupt non-ASCII destinations - // when replacing a file. Public unlink keeps force-overwrite semantics. - if (lstatSync(sourcePath).isFile() && existsSync(targetPath)) { - unlinkSync(targetPath); - } - return true; - }, - }); -} - -function installImmutableBundle( - sourceDir: string, - bundlesDir: string, - safeBundleName: string, - expectedVersion: string, -): string { - retryTransientFilesystemOperation(() => mkdirSync(bundlesDir, { recursive: true })); - const stagingDir = join( - bundlesDir, - `.staging-${safeBundleName}-${process.pid}-${randomUUID()}`, - ); - - try { - retryTransientFilesystemOperation(() => copyDirectorySync(sourceDir, stagingDir)); - if (!isCompleteBundle(stagingDir, expectedVersion)) { - throw new Error(`Bundled Superpowers staging copy is incomplete: ${stagingDir}`); - } - - const preferredDir = join(bundlesDir, safeBundleName); - if (isCompleteBundle(preferredDir, expectedVersion)) { - return preferredDir; - } - - const selectedDir = existsSync(preferredDir) - ? join(bundlesDir, `${safeBundleName}-${randomUUID()}`) - : preferredDir; - try { - retryTransientFilesystemOperation(() => renameSync(stagingDir, selectedDir)); - } catch (error) { - if (!isCompleteBundle(selectedDir, expectedVersion)) { - throw error; - } - } - return selectedDir; - } finally { - if (existsSync(stagingDir)) { - try { - retryTransientFilesystemOperation(() => rmSync(stagingDir, { - recursive: true, - force: true, - })); - } catch { - // Startup cleanup is best-effort; preserve the installation result or error. - } - } - } -} - -function writeSelectedBundleFiles( - managedConfigDir: string, - bundlesDir: string, - bundleDir: string, - version: string, -): void { - const targetPluginsDir = join(managedConfigDir, 'plugins'); - retryTransientFilesystemOperation(() => mkdirSync(targetPluginsDir, { recursive: true })); - - const pluginPath = join(bundleDir, '.opencode', 'plugins', 'superpowers.js'); - let pluginImport = relative(targetPluginsDir, pluginPath).split(sep).join('/'); - if (!pluginImport.startsWith('.')) pluginImport = `./${pluginImport}`; - - retryTransientFilesystemOperation(() => writeFileSync( - join(targetPluginsDir, WRAPPER_PLUGIN_NAME), - `export { SuperpowersPlugin } from '${pluginImport}';\n`, - 'utf8', - )); - retryTransientFilesystemOperation(() => writeFileSync( - join(managedConfigDir, SUPERPOWERS_ACTIVE_MANIFEST), - `${JSON.stringify({ - version, - directory: relative(bundlesDir, bundleDir), - }, null, 2)}\n`, - 'utf8', - )); -} - -export function ensureBundledSuperpowersPlugin(options: EnsureBundledSuperpowersOptions): boolean { - const sourceDir = options.sourceDir?.trim(); - if (!sourceDir) { - return false; - } - - const sourcePluginPath = join(sourceDir, '.opencode', 'plugins', 'superpowers.js'); - const sourceSkillsDir = join(sourceDir, 'skills'); - if (!existsSync(sourcePluginPath) || !existsSync(sourceSkillsDir)) { - return false; - } - - const version = readBundledSuperpowersVersion(sourceDir); - const safeBundleName = toSafeBundleName(version); - const bundlesDir = join(options.managedConfigDir, SUPERPOWERS_BUNDLES_DIR); - const preferredDir = join(bundlesDir, safeBundleName); - const bundleDir = resolveManifestBundle( - options.managedConfigDir, - bundlesDir, - version, - ) ?? (isCompleteBundle(preferredDir, version) - ? preferredDir - : installImmutableBundle(sourceDir, bundlesDir, safeBundleName, version)); - - writeSelectedBundleFiles( - options.managedConfigDir, - bundlesDir, - bundleDir, - version, - ); - - return true; -} - -export function ensureBundledCourseSkills(options: EnsureBundledCourseSkillsOptions): boolean { - const sourceDir = options.sourceDir?.trim(); - if (!sourceDir || !existsSync(sourceDir)) { - return false; - } - - const targetSkillsDir = join(options.managedConfigDir, 'skills'); - mkdirSync(targetSkillsDir, { recursive: true }); - for (const skillId of RETIRED_COURSE_SKILL_IDS) { - rmSync(join(targetSkillsDir, skillId), { recursive: true, force: true }); - } - copyDirectorySync(sourceDir, targetSkillsDir); - - return true; -} - -export function ensureBundledAgentBrowserPlugin(options: { - managedConfigDir: string; - sourcePath?: string; -}): boolean { - const sourcePath = options.sourcePath?.trim(); - if (!sourcePath || !existsSync(sourcePath)) return false; - const targetPluginsDir = join(options.managedConfigDir, 'plugins'); - mkdirSync(targetPluginsDir, { recursive: true }); - cpSync(sourcePath, join(targetPluginsDir, 'niancode-agent-browser.js'), { force: true }); - return true; -} diff --git a/package.json b/package.json index 33a0844..a8c866b 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,6 @@ "npm": "11.6.2", "pdfjs-dist": "^5.7.284", "posthog-node": "^5.28.0", - "qrcode.react": "4.2.0", "rehype-katex": "^7.0.1", "remark-frontmatter": "^5.0.0", "remark-math": "^6.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04f6712..73383c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,9 +71,6 @@ importers: posthog-node: specifier: ^5.28.0 version: 5.28.5 - qrcode.react: - specifier: 4.2.0 - version: 4.2.0(react@19.2.4) rehype-katex: specifier: ^7.0.1 version: 7.0.1 @@ -120,9 +117,6 @@ importers: '@opencode-ai/plugin': specifier: 1.18.9 version: 1.18.9 - opencode-ai: - specifier: 1.18.9 - version: 1.18.9 '@playwright/test': specifier: ^1.56.1 version: 1.59.0 @@ -249,6 +243,9 @@ importers: mpg123-decoder: specifier: ^1.0.3 version: 1.0.3 + opencode-ai: + specifier: 1.18.9 + version: 1.18.9 opusscript: specifier: ^0.1.1 version: 0.1.1 @@ -4307,11 +4304,6 @@ packages: resolution: {integrity: sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==} hasBin: true - qrcode.react@4.2.0: - resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - qs@6.15.0: resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} engines: {node: '>=0.6'} @@ -9444,10 +9436,6 @@ snapshots: qrcode-terminal@0.12.0: {} - qrcode.react@4.2.0(react@19.2.4): - dependencies: - react: 19.2.4 - qs@6.15.0: dependencies: side-channel: 1.1.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ff782e8..1719de4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,7 +1,8 @@ allowBuilds: - electron: set this to true or false - electron-winstaller: set this to true or false - esbuild: set this to true or false - opencode-ai: set this to true or false - protobufjs: set this to true or false - sharp: set this to true or false + electron: true + electron-winstaller: true + esbuild: true + msgpackr-extract: true + opencode-ai: true + protobufjs: true + sharp: true diff --git a/public/fonts/licenses/NOTICE.md b/public/fonts/licenses/NOTICE.md new file mode 100644 index 0000000..56da0b3 --- /dev/null +++ b/public/fonts/licenses/NOTICE.md @@ -0,0 +1,14 @@ +# Makelore bundled fonts + +Makelore bundles the following font software for consistent cross-platform +renderer typography: + +- Inter Variable and Inter Variable Italic, version 4.1 + - Copyright (c) 2016 The Inter Project Authors + - Source: https://github.com/rsms/inter/tree/v4.1 +- Source Han Sans SC Regular, version 2.005R + - Copyright 2014-2025 Adobe + - Source: https://github.com/adobe-fonts/source-han-sans/tree/2.005R + +The font files are distributed under the SIL Open Font License, Version 1.1. +See `OFL-1.1.txt` for the complete license text. diff --git a/public/fonts/licenses/OFL-1.1.txt b/public/fonts/licenses/OFL-1.1.txt new file mode 100644 index 0000000..e5e2741 --- /dev/null +++ b/public/fonts/licenses/OFL-1.1.txt @@ -0,0 +1,80 @@ +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The fonts, +including any derivative works, can be bundled, embedded, modified and +redistributed and/or sold with any software provided that any reserved names +are not used by derivative works. The fonts and derivatives, however, cannot +be released under any other type of license. The requirement for fonts to +remain under this license does not apply to any document created using the +fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may include +source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or +substituting -- in part or in whole -- any of the components of the Original +Version, by changing formats or by porting the Font Software to a new +environment. + +"Author" refers to any designer, engineer, programmer, technical writer or +other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining a copy +of the Font Software, to use, study, copy, merge, embed, modify, redistribute, +and sell modified and unmodified copies of the Font Software, subject to the +following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy contains +the above copyright notice and this license. These can be included either as +stand-alone text files, human-readable headers or in the appropriate +machine-readable metadata fields within text or binary files as long as those +fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) +unless explicit written permission is granted by the corresponding Copyright +Holder. This restriction only applies to the primary font name as presented +to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any Modified +Version, except to acknowledge the contribution of the Copyright Holder(s) +and the Author(s) or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be +distributed entirely under this license, and must not be distributed under +any other license. The requirement for fonts to remain under this license +does not apply to any document created using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES ARISING FROM, OUT OF OR IN CONNECTION WITH THE FONT SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/resources/skills/superpowers/.claude-plugin/marketplace.json b/resources/skills/superpowers/.claude-plugin/marketplace.json deleted file mode 100644 index b6c7949..0000000 --- a/resources/skills/superpowers/.claude-plugin/marketplace.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "superpowers-dev", - "description": "Development marketplace for Superpowers core skills library", - "owner": { - "name": "Jesse Vincent", - "email": "jesse@fsck.com" - }, - "plugins": [ - { - "name": "superpowers", - "description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques", - "version": "5.0.7", - "source": "./", - "author": { - "name": "Jesse Vincent", - "email": "jesse@fsck.com" - } - } - ] -} diff --git a/resources/skills/superpowers/.claude-plugin/plugin.json b/resources/skills/superpowers/.claude-plugin/plugin.json deleted file mode 100644 index cad4d55..0000000 --- a/resources/skills/superpowers/.claude-plugin/plugin.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "superpowers", - "description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques", - "version": "5.0.7", - "author": { - "name": "Jesse Vincent", - "email": "jesse@fsck.com" - }, - "homepage": "https://github.com/obra/superpowers", - "repository": "https://github.com/obra/superpowers", - "license": "MIT", - "keywords": [ - "skills", - "tdd", - "debugging", - "collaboration", - "best-practices", - "workflows" - ] -} diff --git a/resources/skills/superpowers/.codex/INSTALL.md b/resources/skills/superpowers/.codex/INSTALL.md deleted file mode 100644 index c415e2e..0000000 --- a/resources/skills/superpowers/.codex/INSTALL.md +++ /dev/null @@ -1,67 +0,0 @@ -# Installing Superpowers for Codex - -Enable superpowers skills in Codex via native skill discovery. Just clone and symlink. - -## Prerequisites - -- Git - -## Installation - -1. **Clone the superpowers repository:** - ```bash - git clone https://github.com/obra/superpowers.git ~/.codex/superpowers - ``` - -2. **Create the skills symlink:** - ```bash - mkdir -p ~/.agents/skills - ln -s ~/.codex/superpowers/skills ~/.agents/skills/superpowers - ``` - - **Windows (PowerShell):** - ```powershell - New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.agents\skills" - cmd /c mklink /J "$env:USERPROFILE\.agents\skills\superpowers" "$env:USERPROFILE\.codex\superpowers\skills" - ``` - -3. **Restart Codex** (quit and relaunch the CLI) to discover the skills. - -## Migrating from old bootstrap - -If you installed superpowers before native skill discovery, you need to: - -1. **Update the repo:** - ```bash - cd ~/.codex/superpowers && git pull - ``` - -2. **Create the skills symlink** (step 2 above) — this is the new discovery mechanism. - -3. **Remove the old bootstrap block** from `~/.codex/AGENTS.md` — any block referencing `superpowers-codex bootstrap` is no longer needed. - -4. **Restart Codex.** - -## Verify - -```bash -ls -la ~/.agents/skills/superpowers -``` - -You should see a symlink (or junction on Windows) pointing to your superpowers skills directory. - -## Updating - -```bash -cd ~/.codex/superpowers && git pull -``` - -Skills update instantly through the symlink. - -## Uninstalling - -```bash -rm ~/.agents/skills/superpowers -``` - -Optionally delete the clone: `rm -rf ~/.codex/superpowers`. diff --git a/resources/skills/superpowers/.cursor-plugin/plugin.json b/resources/skills/superpowers/.cursor-plugin/plugin.json deleted file mode 100644 index 153deac..0000000 --- a/resources/skills/superpowers/.cursor-plugin/plugin.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "superpowers", - "displayName": "Superpowers", - "description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques", - "version": "5.0.7", - "author": { - "name": "Jesse Vincent", - "email": "jesse@fsck.com" - }, - "homepage": "https://github.com/obra/superpowers", - "repository": "https://github.com/obra/superpowers", - "license": "MIT", - "keywords": [ - "skills", - "tdd", - "debugging", - "collaboration", - "best-practices", - "workflows" - ], - "skills": "./skills/", - "agents": "./agents/", - "commands": "./commands/", - "hooks": "./hooks/hooks-cursor.json" -} diff --git a/resources/skills/superpowers/.gitattributes b/resources/skills/superpowers/.gitattributes deleted file mode 100644 index def8027..0000000 --- a/resources/skills/superpowers/.gitattributes +++ /dev/null @@ -1,18 +0,0 @@ -# Ensure shell scripts always have LF line endings -*.sh text eol=lf -hooks/session-start text eol=lf - -# Ensure the polyglot wrapper keeps LF (it's parsed by both cmd and bash) -*.cmd text eol=lf - -# Common text files -*.md text eol=lf -*.json text eol=lf -*.js text eol=lf -*.mjs text eol=lf -*.ts text eol=lf - -# Explicitly mark binary files -*.png binary -*.jpg binary -*.gif binary diff --git a/resources/skills/superpowers/.github/FUNDING.yml b/resources/skills/superpowers/.github/FUNDING.yml deleted file mode 100644 index f646aa7..0000000 --- a/resources/skills/superpowers/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -github: [obra] diff --git a/resources/skills/superpowers/.github/ISSUE_TEMPLATE/bug_report.md b/resources/skills/superpowers/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 235d867..0000000 --- a/resources/skills/superpowers/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: Bug Report -about: Something isn't working as expected -labels: bug ---- - - - -- [ ] I searched existing issues and this is not a duplicate - -## Environment - -| Field | Value | -|-------|-------| -| Superpowers version | | -| Harness (Claude Code, Cursor, etc.) | | -| Harness version | | -| Model | | -| OS + shell | | - -## Is this a Superpowers issue or a platform issue? - - -- [ ] I confirmed this issue does not occur without Superpowers installed - -## What happened? - - -## Steps to reproduce -1. -2. -3. - -## Expected behavior - - -## Actual behavior - - -## Debug log or conversation transcript - diff --git a/resources/skills/superpowers/.github/ISSUE_TEMPLATE/config.yml b/resources/skills/superpowers/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index e3dc760..0000000 --- a/resources/skills/superpowers/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Questions & Help - url: https://discord.gg/Jd8Vphy9jq - about: For usage questions, troubleshooting help, and general discussion, please visit our Discord instead of opening an issue. diff --git a/resources/skills/superpowers/.github/ISSUE_TEMPLATE/feature_request.md b/resources/skills/superpowers/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index d184796..0000000 --- a/resources/skills/superpowers/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Feature Request -about: Propose a change or addition to Superpowers -labels: enhancement ---- - - - -- [ ] I searched existing issues and this has not been proposed before - -## What problem does this solve? - - -## Proposed solution - - -## What alternatives did you consider? - - -## Is this appropriate for core Superpowers? - - -## Context - diff --git a/resources/skills/superpowers/.github/ISSUE_TEMPLATE/platform_support.md b/resources/skills/superpowers/.github/ISSUE_TEMPLATE/platform_support.md deleted file mode 100644 index dc5bf33..0000000 --- a/resources/skills/superpowers/.github/ISSUE_TEMPLATE/platform_support.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: IDE / Platform Support Request -about: Request support for a new IDE, editor, or AI coding tool -labels: platform-support ---- - - - -- [ ] I searched existing issues for this IDE/platform - -## Which IDE or platform? - - -## Does this tool have a plugin or extension system? - - -## Have you tried manual installation? - diff --git a/resources/skills/superpowers/.github/PULL_REQUEST_TEMPLATE.md b/resources/skills/superpowers/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 4308271..0000000 --- a/resources/skills/superpowers/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,87 +0,0 @@ - - -## What problem are you trying to solve? - - -## What does this PR change? - - -## Is this change appropriate for the core library? - - -## What alternatives did you consider? - - -## Does this PR contain multiple unrelated changes? - - -## Existing PRs -- [ ] I have reviewed all open AND closed PRs for duplicates or prior art -- Related PRs: - - - -## Environment tested - -| Harness (e.g. Claude Code, Cursor) | Harness version | Model | Model version/ID | -|-------------------------------------|-----------------|-------|------------------| -| | | | | - -## Evaluation -- What was the initial prompt you (or your human partner) used to start - the session that led to this change? -- How many eval sessions did you run AFTER making the change? -- How did outcomes change compared to before the change? - - - -## Rigor - -- [ ] If this is a skills change: I used `superpowers:writing-skills` and - completed adversarial pressure testing (paste results below) -- [ ] This change was tested adversarially, not just on the happy path -- [ ] I did not modify carefully-tuned content (Red Flags table, - rationalizations, "human partner" language) without extensive evals - showing the change is an improvement - - - -## Human review -- [ ] A human has reviewed the COMPLETE proposed diff before submission - - diff --git a/resources/skills/superpowers/.gitignore b/resources/skills/superpowers/.gitignore deleted file mode 100644 index 1c25a50..0000000 --- a/resources/skills/superpowers/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -.worktrees/ -.private-journal/ -.claude/ -.DS_Store -node_modules/ -inspo -triage/ diff --git a/resources/skills/superpowers/.opencode/INSTALL.md b/resources/skills/superpowers/.opencode/INSTALL.md deleted file mode 100644 index 2dc2b23..0000000 --- a/resources/skills/superpowers/.opencode/INSTALL.md +++ /dev/null @@ -1,83 +0,0 @@ -# Installing Superpowers for OpenCode - -## Prerequisites - -- [OpenCode.ai](https://opencode.ai) installed - -## Installation - -Add superpowers to the `plugin` array in your `opencode.json` (global or project-level): - -```json -{ - "plugin": ["superpowers@git+https://github.com/obra/superpowers.git"] -} -``` - -Restart OpenCode. That's it — the plugin auto-installs and registers all skills. - -Verify by asking: "Tell me about your superpowers" - -## Migrating from the old symlink-based install - -If you previously installed superpowers using `git clone` and symlinks, remove the old setup: - -```bash -# Remove old symlinks -rm -f ~/.config/opencode/plugins/superpowers.js -rm -rf ~/.config/opencode/skills/superpowers - -# Optionally remove the cloned repo -rm -rf ~/.config/opencode/superpowers - -# Remove skills.paths from opencode.json if you added one for superpowers -``` - -Then follow the installation steps above. - -## Usage - -Use OpenCode's native `skill` tool: - -``` -use skill tool to list skills -use skill tool to load superpowers/brainstorming -``` - -## Updating - -Superpowers updates automatically when you restart OpenCode. - -To pin a specific version: - -```json -{ - "plugin": ["superpowers@git+https://github.com/obra/superpowers.git#v5.0.3"] -} -``` - -## Troubleshooting - -### Plugin not loading - -1. Check logs: `opencode run --print-logs "hello" 2>&1 | grep -i superpowers` -2. Verify the plugin line in your `opencode.json` -3. Make sure you're running a recent version of OpenCode - -### Skills not found - -1. Use `skill` tool to list what's discovered -2. Check that the plugin is loading (see above) - -### Tool mapping - -When skills reference Claude Code tools: -- `TodoWrite` → `todowrite` -- `Task` with subagents → `@mention` syntax -- `Skill` tool → OpenCode's native `skill` tool -- File operations → your native tools - -## Getting Help - -- Report issues: https://github.com/obra/superpowers/issues -- Full documentation: https://github.com/obra/superpowers/blob/main/docs/README.opencode.md diff --git a/resources/skills/superpowers/.opencode/plugins/superpowers.js b/resources/skills/superpowers/.opencode/plugins/superpowers.js deleted file mode 100644 index 63f66c1..0000000 --- a/resources/skills/superpowers/.opencode/plugins/superpowers.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Superpowers plugin for OpenCode.ai - * - * Injects superpowers bootstrap context via system prompt transform. - * Auto-registers skills directory via config hook (no symlinks needed). - */ - -import path from 'path'; -import fs from 'fs'; -import os from 'os'; -import { fileURLToPath } from 'url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -// Simple frontmatter extraction (avoid dependency on skills-core for bootstrap) -const extractAndStripFrontmatter = (content) => { - const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); - if (!match) return { frontmatter: {}, content }; - - const frontmatterStr = match[1]; - const body = match[2]; - const frontmatter = {}; - - for (const line of frontmatterStr.split('\n')) { - const colonIdx = line.indexOf(':'); - if (colonIdx > 0) { - const key = line.slice(0, colonIdx).trim(); - const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, ''); - frontmatter[key] = value; - } - } - - return { frontmatter, content: body }; -}; - -// Normalize a path: trim whitespace, expand ~, resolve to absolute -const normalizePath = (p, homeDir) => { - if (!p || typeof p !== 'string') return null; - let normalized = p.trim(); - if (!normalized) return null; - if (normalized.startsWith('~/')) { - normalized = path.join(homeDir, normalized.slice(2)); - } else if (normalized === '~') { - normalized = homeDir; - } - return path.resolve(normalized); -}; - -export function isSuperpowersEnabledForDirectory(directory) { - if (!directory || typeof directory !== 'string') return true; - - try { - const projectConfigPath = path.join(directory, '.niancode', 'project.json'); - const projectConfig = JSON.parse(fs.readFileSync(projectConfigPath, 'utf8')); - if (typeof projectConfig.superpowersEnabled === 'boolean') { - return projectConfig.superpowersEnabled; - } - } catch { - // Non-NianCode and incomplete projects retain the historical default. - } - - return true; -} - -export const SuperpowersPlugin = async ({ directory }) => { - if (!isSuperpowersEnabledForDirectory(directory)) return {}; - - const homeDir = os.homedir(); - const superpowersSkillsDir = path.resolve(__dirname, '../../skills'); - const envConfigDir = normalizePath(process.env.OPENCODE_CONFIG_DIR, homeDir); - const configDir = envConfigDir || path.join(homeDir, '.config/opencode'); - - // Helper to generate bootstrap content - const getBootstrapContent = () => { - // Try to load using-superpowers skill - const skillPath = path.join(superpowersSkillsDir, 'using-superpowers', 'SKILL.md'); - if (!fs.existsSync(skillPath)) return null; - - const fullContent = fs.readFileSync(skillPath, 'utf8'); - const { content } = extractAndStripFrontmatter(fullContent); - - const toolMapping = `**Tool Mapping for OpenCode:** -When skills reference tools you don't have, substitute OpenCode equivalents: -- \`TodoWrite\` → \`todowrite\` -- \`Task\` tool with subagents → Use OpenCode's subagent system (@mention) -- \`Skill\` tool → OpenCode's native \`skill\` tool -- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools - -Use OpenCode's native \`skill\` tool to list and load skills.`; - - return ` -You have superpowers. - -**IMPORTANT: The using-superpowers skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the skill tool to load "using-superpowers" again - that would be redundant.** - -${content} - -${toolMapping} -`; - }; - - return { - // Inject skills path into live config so OpenCode discovers superpowers skills - // without requiring manual symlinks or config file edits. - // This works because Config.get() returns a cached singleton — modifications - // here are visible when skills are lazily discovered later. - config: async (config) => { - config.skills = config.skills || {}; - config.skills.paths = config.skills.paths || []; - if (!config.skills.paths.includes(superpowersSkillsDir)) { - config.skills.paths.push(superpowersSkillsDir); - } - }, - - // Inject bootstrap into the first user message of each session. - // Using a user message instead of a system message avoids: - // 1. Token bloat from system messages repeated every turn (#750) - // 2. Multiple system messages breaking Qwen and other models (#894) - 'experimental.chat.messages.transform': async (_input, output) => { - const bootstrap = getBootstrapContent(); - if (!bootstrap || !output.messages.length) return; - const firstUser = output.messages.find(m => m.info.role === 'user'); - if (!firstUser || !firstUser.parts.length) return; - // Only inject once - if (firstUser.parts.some(p => p.type === 'text' && p.text.includes('EXTREMELY_IMPORTANT'))) return; - const ref = firstUser.parts[0]; - firstUser.parts.unshift({ ...ref, type: 'text', text: bootstrap }); - } - }; -}; diff --git a/resources/skills/superpowers/.version-bump.json b/resources/skills/superpowers/.version-bump.json deleted file mode 100644 index f5dbe31..0000000 --- a/resources/skills/superpowers/.version-bump.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "files": [ - { "path": "package.json", "field": "version" }, - { "path": ".claude-plugin/plugin.json", "field": "version" }, - { "path": ".cursor-plugin/plugin.json", "field": "version" }, - { "path": ".claude-plugin/marketplace.json", "field": "plugins.0.version" }, - { "path": "gemini-extension.json", "field": "version" } - ], - "audit": { - "exclude": [ - "CHANGELOG.md", - "RELEASE-NOTES.md", - "node_modules", - ".git", - ".version-bump.json", - "scripts/bump-version.sh" - ] - } -} diff --git a/resources/skills/superpowers/AGENTS.md b/resources/skills/superpowers/AGENTS.md deleted file mode 100644 index 681311e..0000000 --- a/resources/skills/superpowers/AGENTS.md +++ /dev/null @@ -1 +0,0 @@ -CLAUDE.md \ No newline at end of file diff --git a/resources/skills/superpowers/CHANGELOG.md b/resources/skills/superpowers/CHANGELOG.md deleted file mode 100644 index b383d93..0000000 --- a/resources/skills/superpowers/CHANGELOG.md +++ /dev/null @@ -1,13 +0,0 @@ -# Changelog - -## [5.0.5] - 2026-03-17 - -### Fixed - -- **Brainstorm server ESM fix**: Renamed `server.js` → `server.cjs` so the brainstorming server starts correctly on Node.js 22+ where the root `package.json` `"type": "module"` caused `require()` to fail. ([PR #784](https://github.com/obra/superpowers/pull/784) by @sarbojitrana, fixes [#774](https://github.com/obra/superpowers/issues/774), [#780](https://github.com/obra/superpowers/issues/780), [#783](https://github.com/obra/superpowers/issues/783)) -- **Brainstorm owner-PID on Windows**: Skip `BRAINSTORM_OWNER_PID` lifecycle monitoring on Windows/MSYS2 where the PID namespace is invisible to Node.js. Prevents the server from self-terminating after 60 seconds. The 30-minute idle timeout remains as the safety net. ([#770](https://github.com/obra/superpowers/issues/770), docs from [PR #768](https://github.com/obra/superpowers/pull/768) by @lucasyhzhu-debug) -- **stop-server.sh reliability**: Verify the server process actually died before reporting success. Waits up to 2 seconds for graceful shutdown, escalates to `SIGKILL`, and reports failure if the process survives. ([#723](https://github.com/obra/superpowers/issues/723)) - -### Changed - -- **Execution handoff**: Restore user choice between subagent-driven-development and executing-plans after plan writing. Subagent-driven is recommended but no longer mandatory. (Reverts `5e51c3e`) diff --git a/resources/skills/superpowers/CLAUDE.md b/resources/skills/superpowers/CLAUDE.md deleted file mode 100644 index 3a50e0f..0000000 --- a/resources/skills/superpowers/CLAUDE.md +++ /dev/null @@ -1,85 +0,0 @@ -# Superpowers — Contributor Guidelines - -## If You Are an AI Agent - -Stop. Read this section before doing anything. - -This repo has a 94% PR rejection rate. Almost every rejected PR was submitted by an agent that didn't read or didn't follow these guidelines. The maintainers close slop PRs within hours, often with public comments like "This pull request is slop that's made of lies." - -**Your job is to protect your human partner from that outcome.** Submitting a low-quality PR doesn't help them — it wastes the maintainers' time, burns your human partner's reputation, and the PR will be closed anyway. That is not being helpful. That is being a tool of embarrassment. - -Before you open a PR against this repo, you MUST: - -1. **Read the entire PR template** at `.github/PULL_REQUEST_TEMPLATE.md` and fill in every section with real, specific answers. Not summaries. Not placeholders. -2. **Search for existing PRs** — open AND closed — that address the same problem. If duplicates exist, STOP and tell your human partner. Do not open another duplicate. -3. **Verify this is a real problem.** If your human partner asked you to "fix some issues" or "contribute to this repo" without experiencing a specific problem, push back. Ask them what broke, what failed, what the user experience was. -4. **Confirm the change belongs in core.** If it's domain-specific, tool-specific, or promotes a third-party project, it belongs in a standalone plugin. Tell your human partner. -5. **Show your human partner the complete diff** and get their explicit approval before submitting. - -If any of these checks fail, do not open the PR. Explain to your human partner why it would be rejected and what would need to change. They will thank you for saving them the embarrassment. - -## Pull Request Requirements - -**Every PR must fully complete the PR template.** No section may be left blank or filled with placeholder text. PRs that skip sections will be closed without review. - -**Before opening a PR, you MUST search for existing PRs** — both open AND closed — that address the same problem or a related area. Reference what you found in the "Existing PRs" section. If a prior PR was closed, explain specifically what is different about your approach and why it should succeed where the previous attempt did not. - -**PRs that show no evidence of human involvement will be closed.** A human must review the complete proposed diff before submission. - -## What We Will Not Accept - -### Third-party dependencies - -PRs that add optional or required dependencies on third-party projects will not be accepted unless they are adding support for a new harness (e.g., a new IDE or CLI tool). Superpowers is a zero-dependency plugin by design. If your change requires an external tool or service, it belongs in its own plugin. - -### "Compliance" changes to skills - -Our internal skill philosophy differs from Anthropic's published guidance on writing skills. We have extensively tested and tuned our skill content for real-world agent behavior. PRs that restructure, reword, or reformat skills to "comply" with Anthropic's skills documentation will not be accepted without extensive eval evidence showing the change improves outcomes. The bar for modifying behavior-shaping content is very high. - -### Project-specific or personal configuration - -Skills, hooks, or configuration that only benefit a specific project, team, domain, or workflow do not belong in core. Publish these as a separate plugin. - -### Bulk or spray-and-pray PRs - -Do not trawl the issue tracker and open PRs for multiple issues in a single session. Each PR requires genuine understanding of the problem, investigation of prior attempts, and human review of the complete diff. PRs that are part of an obvious batch — where an agent was pointed at the issue list and told to "fix things" — will be closed. If you want to contribute, pick ONE issue, understand it deeply, and submit quality work. - -### Speculative or theoretical fixes - -Every PR must solve a real problem that someone actually experienced. "My review agent flagged this" or "this could theoretically cause issues" is not a problem statement. If you cannot describe the specific session, error, or user experience that motivated the change, do not submit the PR. - -### Domain-specific skills - -Superpowers core contains general-purpose skills that benefit all users regardless of their project. Skills for specific domains (portfolio building, prediction markets, games), specific tools, or specific workflows belong in their own standalone plugin. Ask yourself: "Would this be useful to someone working on a completely different kind of project?" If not, publish it separately. - -### Fork-specific changes - -If you maintain a fork with customizations, do not open PRs to sync your fork or push fork-specific changes upstream. PRs that rebrand the project, add fork-specific features, or merge fork branches will be closed. - -### Fabricated content - -PRs containing invented claims, fabricated problem descriptions, or hallucinated functionality will be closed immediately. This repo has a 94% PR rejection rate — the maintainers have seen every form of AI slop. They will notice. - -### Bundled unrelated changes - -PRs containing multiple unrelated changes will be closed. Split them into separate PRs. - -## Skill Changes Require Evaluation - -Skills are not prose — they are code that shapes agent behavior. If you modify skill content: - -- Use `superpowers:writing-skills` to develop and test changes -- Run adversarial pressure testing across multiple sessions -- Show before/after eval results in your PR -- Do not modify carefully-tuned content (Red Flags tables, rationalization lists, "human partner" language) without evidence the change is an improvement - -## Understand the Project Before Contributing - -Before proposing changes to skill design, workflow philosophy, or architecture, read existing skills and understand the project's design decisions. Superpowers has its own tested philosophy about skill design, agent behavior shaping, and terminology (e.g., "your human partner" is deliberate, not interchangeable with "the user"). Changes that rewrite the project's voice or restructure its approach without understanding why it exists will be rejected. - -## General - -- Read `.github/PULL_REQUEST_TEMPLATE.md` before submitting -- One problem per PR -- Test on at least one harness and report results in the environment table -- Describe the problem you solved, not just what you changed diff --git a/resources/skills/superpowers/CODE_OF_CONDUCT.md b/resources/skills/superpowers/CODE_OF_CONDUCT.md deleted file mode 100644 index f642254..0000000 --- a/resources/skills/superpowers/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,128 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -jesse@primeradiant.com. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. diff --git a/resources/skills/superpowers/GEMINI.md b/resources/skills/superpowers/GEMINI.md deleted file mode 100644 index 0dd2f58..0000000 --- a/resources/skills/superpowers/GEMINI.md +++ /dev/null @@ -1,2 +0,0 @@ -@./skills/using-superpowers/SKILL.md -@./skills/using-superpowers/references/gemini-tools.md diff --git a/resources/skills/superpowers/LICENSE b/resources/skills/superpowers/LICENSE deleted file mode 100644 index abf0390..0000000 --- a/resources/skills/superpowers/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Jesse Vincent - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/resources/skills/superpowers/README.md b/resources/skills/superpowers/README.md deleted file mode 100644 index 45191a7..0000000 --- a/resources/skills/superpowers/README.md +++ /dev/null @@ -1,190 +0,0 @@ -# Superpowers - -Superpowers is a complete software development workflow for your coding agents, built on top of a set of composable "skills" and some initial instructions that make sure your agent uses them. - -## How it works - -It starts from the moment you fire up your coding agent. As soon as it sees that you're building something, it *doesn't* just jump into trying to write code. Instead, it steps back and asks you what you're really trying to do. - -Once it's teased a spec out of the conversation, it shows it to you in chunks short enough to actually read and digest. - -After you've signed off on the design, your agent puts together an implementation plan that's clear enough for an enthusiastic junior engineer with poor taste, no judgement, no project context, and an aversion to testing to follow. It emphasizes true red/green TDD, YAGNI (You Aren't Gonna Need It), and DRY. - -Next up, once you say "go", it launches a *subagent-driven-development* process, having agents work through each engineering task, inspecting and reviewing their work, and continuing forward. It's not uncommon for Claude to be able to work autonomously for a couple hours at a time without deviating from the plan you put together. - -There's a bunch more to it, but that's the core of the system. And because the skills trigger automatically, you don't need to do anything special. Your coding agent just has Superpowers. - - -## Sponsorship - -If Superpowers has helped you do stuff that makes money and you are so inclined, I'd greatly appreciate it if you'd consider [sponsoring my opensource work](https://github.com/sponsors/obra). - -Thanks! - -- Jesse - - -## Installation - -**Note:** Installation differs by platform. Claude Code or Cursor have built-in plugin marketplaces. Codex and OpenCode require manual setup. - -### Claude Code Official Marketplace - -Superpowers is available via the [official Claude plugin marketplace](https://claude.com/plugins/superpowers) - -Install the plugin from Claude marketplace: - -```bash -/plugin install superpowers@claude-plugins-official -``` - -### Claude Code (via Plugin Marketplace) - -In Claude Code, register the marketplace first: - -```bash -/plugin marketplace add obra/superpowers-marketplace -``` - -Then install the plugin from this marketplace: - -```bash -/plugin install superpowers@superpowers-marketplace -``` - -### Cursor (via Plugin Marketplace) - -In Cursor Agent chat, install from marketplace: - -```text -/add-plugin superpowers -``` - -or search for "superpowers" in the plugin marketplace. - -### Codex - -Tell Codex: - -``` -Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.codex/INSTALL.md -``` - -**Detailed docs:** [docs/README.codex.md](docs/README.codex.md) - -### OpenCode - -Tell OpenCode: - -``` -Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.opencode/INSTALL.md -``` - -**Detailed docs:** [docs/README.opencode.md](docs/README.opencode.md) - -### GitHub Copilot CLI - -```bash -copilot plugin marketplace add obra/superpowers-marketplace -copilot plugin install superpowers@superpowers-marketplace -``` - -### Gemini CLI - -```bash -gemini extensions install https://github.com/obra/superpowers -``` - -To update: - -```bash -gemini extensions update superpowers -``` - -### Verify Installation - -Start a new session in your chosen platform and ask for something that should trigger a skill (for example, "help me plan this feature" or "let's debug this issue"). The agent should automatically invoke the relevant superpowers skill. - -## The Basic Workflow - -1. **brainstorming** - Activates before writing code. Refines rough ideas through questions, explores alternatives, presents design in sections for validation. Saves design document. - -2. **using-git-worktrees** - Activates after design approval. Creates isolated workspace on new branch, runs project setup, verifies clean test baseline. - -3. **writing-plans** - Activates with approved design. Breaks work into bite-sized tasks (2-5 minutes each). Every task has exact file paths, complete code, verification steps. - -4. **subagent-driven-development** or **executing-plans** - Activates with plan. Dispatches fresh subagent per task with two-stage review (spec compliance, then code quality), or executes in batches with human checkpoints. - -5. **test-driven-development** - Activates during implementation. Enforces RED-GREEN-REFACTOR: write failing test, watch it fail, write minimal code, watch it pass, commit. Deletes code written before tests. - -6. **requesting-code-review** - Activates between tasks. Reviews against plan, reports issues by severity. Critical issues block progress. - -7. **finishing-a-development-branch** - Activates when tasks complete. Verifies tests, presents options (merge/PR/keep/discard), cleans up worktree. - -**The agent checks for relevant skills before any task.** Mandatory workflows, not suggestions. - -## What's Inside - -### Skills Library - -**Testing** -- **test-driven-development** - RED-GREEN-REFACTOR cycle (includes testing anti-patterns reference) - -**Debugging** -- **systematic-debugging** - 4-phase root cause process (includes root-cause-tracing, defense-in-depth, condition-based-waiting techniques) -- **verification-before-completion** - Ensure it's actually fixed - -**Collaboration** -- **brainstorming** - Socratic design refinement -- **writing-plans** - Detailed implementation plans -- **executing-plans** - Batch execution with checkpoints -- **dispatching-parallel-agents** - Concurrent subagent workflows -- **requesting-code-review** - Pre-review checklist -- **receiving-code-review** - Responding to feedback -- **using-git-worktrees** - Parallel development branches -- **finishing-a-development-branch** - Merge/PR decision workflow -- **subagent-driven-development** - Fast iteration with two-stage review (spec compliance, then code quality) - -**Meta** -- **writing-skills** - Create new skills following best practices (includes testing methodology) -- **using-superpowers** - Introduction to the skills system - -## Philosophy - -- **Test-Driven Development** - Write tests first, always -- **Systematic over ad-hoc** - Process over guessing -- **Complexity reduction** - Simplicity as primary goal -- **Evidence over claims** - Verify before declaring success - -Read more: [Superpowers for Claude Code](https://blog.fsck.com/2025/10/09/superpowers/) - -## Contributing - -Skills live directly in this repository. To contribute: - -1. Fork the repository -2. Create a branch for your skill -3. Follow the `writing-skills` skill for creating and testing new skills -4. Submit a PR - -See `skills/writing-skills/SKILL.md` for the complete guide. - -## Updating - -Skills update automatically when you update the plugin: - -```bash -/plugin update superpowers -``` - -## License - -MIT License - see LICENSE file for details - -## Community - -Superpowers is built by [Jesse Vincent](https://blog.fsck.com) and the rest of the folks at [Prime Radiant](https://primeradiant.com). - -- **Discord**: [Join us](https://discord.gg/Jd8Vphy9jq) for community support, questions, and sharing what you're building with Superpowers -- **Issues**: https://github.com/obra/superpowers/issues -- **Release announcements**: [Sign up](https://primeradiant.com/superpowers/) to get notified about new versions diff --git a/resources/skills/superpowers/RELEASE-NOTES.md b/resources/skills/superpowers/RELEASE-NOTES.md deleted file mode 100644 index 675eca2..0000000 --- a/resources/skills/superpowers/RELEASE-NOTES.md +++ /dev/null @@ -1,1096 +0,0 @@ -# Superpowers Release Notes - -## v5.0.7 (2026-03-31) - -### GitHub Copilot CLI Support - -- **SessionStart context injection** — Copilot CLI v1.0.11 added support for `additionalContext` in sessionStart hook output. The session-start hook now detects the `COPILOT_CLI` environment variable and emits the SDK-standard `{ "additionalContext": "..." }` format, giving Copilot CLI users the full superpowers bootstrap at session start. (Original fix by @culinablaz in PR #910) -- **Tool mapping** — added `references/copilot-tools.md` with the full Claude Code to Copilot CLI tool equivalence table -- **Skill and README updates** — added Copilot CLI to the `using-superpowers` skill's platform instructions and README installation section - -### OpenCode Fixes - -- **Skills path consistency** — the bootstrap text no longer advertises a misleading `configDir/skills/superpowers/` path that didn't match the runtime path. The agent should use the native `skill` tool, not navigate to files by path. Tests now use consistent paths derived from a single source of truth. (#847, #916) -- **Bootstrap as user message** — moved bootstrap injection from `experimental.chat.system.transform` to `experimental.chat.messages.transform`, prepending to the first user message instead of adding a system message. Avoids token bloat from system messages repeated every turn (#750) and fixes compatibility with Qwen and other models that break on multiple system messages (#894). - -## v5.0.6 (2026-03-24) - -### Inline Self-Review Replaces Subagent Review Loops - -The subagent review loop (dispatching a fresh agent to review plans/specs) doubled execution time (~25 min overhead) without measurably improving plan quality. Regression testing across 5 versions with 5 trials each showed identical quality scores regardless of whether the review loop ran. - -- **brainstorming** — replaced Spec Review Loop (subagent dispatch + 3-iteration cap) with inline Spec Self-Review checklist: placeholder scan, internal consistency, scope check, ambiguity check -- **writing-plans** — replaced Plan Review Loop (subagent dispatch + 3-iteration cap) with inline Self-Review checklist: spec coverage, placeholder scan, type consistency -- **writing-plans** — added explicit "No Placeholders" section defining plan failures (TBD, vague descriptions, undefined references, "similar to Task N") -- Self-review catches 3-5 real bugs per run in ~30s instead of ~25 min, with comparable defect rates to the subagent approach - -### Brainstorm Server - -- **Session directory restructured** — the brainstorm server session directory now contains two peer subdirectories: `content/` (HTML files served to the browser) and `state/` (events, server-info, pid, log). Previously, server state and user interaction data were stored alongside served content, making them accessible over HTTP. The `screen_dir` and `state_dir` paths are both included in the server-started JSON. (Reported by 吉田仁) - -### Bug Fixes - -- **Owner-PID lifecycle fixes** — the brainstorm server's owner-PID monitoring had two bugs causing false shutdowns within 60 seconds: (1) EPERM from cross-user PIDs (Tailscale SSH, etc.) was treated as "process dead", and (2) on WSL the grandparent PID resolves to a short-lived subprocess that exits before the first lifecycle check. Fixed by treating EPERM as "alive" and validating the owner PID at startup — if it's already dead, monitoring is disabled and the server relies on the 30-minute idle timeout. This also removes the Windows/MSYS2-specific carve-out from `start-server.sh` since the server now handles it generically. (#879) -- **writing-skills** — corrected false claim that SKILL.md frontmatter supports "only two fields"; now says "two required fields" and links to the agentskills.io specification for all supported fields (PR #882 by @arittr) - -### Codex App Compatibility - -- **codex-tools** — added named agent dispatch mapping documenting how to translate Claude Code's named agent types to Codex's `spawn_agent` with worker roles (PR #647 by @arittr) -- **codex-tools** — added environment detection and Codex App finishing sections for worktree-aware skills (by @arittr) -- **Design spec** — added Codex App compatibility design spec (PRI-823) covering read-only environment detection, worktree-safe skill behavior, and sandbox fallback patterns (by @arittr) - -## v5.0.5 (2026-03-17) - -### Bug Fixes - -- **Brainstorm server ESM fix** — renamed `server.js` → `server.cjs` so the brainstorming server starts correctly on Node.js 22+ where the root `package.json` `"type": "module"` caused `require()` to fail. (PR #784 by @sarbojitrana, fixes #774, #780, #783) -- **Brainstorm owner-PID on Windows** — skip PID lifecycle monitoring on Windows/MSYS2 where the PID namespace is invisible to Node.js, preventing the server from self-terminating after 60 seconds. (#770, docs from PR #768 by @lucasyhzlu-debug) -- **stop-server.sh reliability** — verify the server process actually died before reporting success. SIGTERM + 2s wait + SIGKILL fallback. (#723) - -### Changed - -- **Execution handoff** — restore user choice between subagent-driven and inline execution after plan writing. Subagent-driven is recommended but no longer mandatory. - -## v5.0.4 (2026-03-16) - -### Review Loop Refinements - -Dramatically reduces token usage and speeds up spec and plan reviews by eliminating unnecessary review passes and tightening reviewer focus. - -- **Single whole-plan review** — plan reviewer now reviews the complete plan in one pass instead of chunk-by-chunk. Removed all chunk-related concepts (`## Chunk N:` headings, 1000-line chunk limits, per-chunk dispatch). -- **Raised the bar for blocking issues** — both spec and plan reviewer prompts now include a "Calibration" section: only flag issues that would cause real problems during implementation. Minor wording, stylistic preferences, and formatting quibbles should not block approval. -- **Reduced max review iterations** — from 5 to 3 for both spec and plan review loops. If the reviewer is calibrated correctly, 3 rounds is plenty. -- **Streamlined reviewer checklists** — spec reviewer trimmed from 7 categories to 5; plan reviewer from 7 to 4. Removed formatting-focused checks (task syntax, chunk size) in favor of substance (buildability, spec alignment). - -### OpenCode - -- **One-line plugin install** — OpenCode plugin now auto-registers the skills directory via a `config` hook. No symlinks or `skills.paths` config needed. Install is just adding one line to `opencode.json`. (PR #753) -- **Added `package.json`** so OpenCode can install superpowers as an npm package from git. - -### Bug Fixes - -- **Verify server actually stopped** — `stop-server.sh` now confirms the process is dead before reporting success. SIGTERM + 2s wait + SIGKILL fallback. Reports failure if the process survives. (PR #751) -- **Generic agent language** — brainstorm companion waiting page now says "the agent" instead of "Claude". - -## v5.0.3 (2026-03-15) - -### Cursor Support - -- **Cursor hooks** — added `hooks/hooks-cursor.json` with Cursor's camelCase format (`sessionStart`, `version: 1`) and updated `.cursor-plugin/plugin.json` to reference it. Fixed platform detection in `session-start` to check `CURSOR_PLUGIN_ROOT` first (Cursor may also set `CLAUDE_PLUGIN_ROOT`). (Based on PR #709) - -### Bug Fixes - -- **Stop firing SessionStart hook on `--resume`** — the startup hook was re-injecting context on resumed sessions, which already have the context in their conversation history. The hook now fires only on `startup`, `clear`, and `compact`. -- **Bash 5.3+ hook hang** — replaced heredoc (`cat <` on served HTML pages - -### Subagent Context Isolation - -- All delegation skills (brainstorming, dispatching-parallel-agents, requesting-code-review, subagent-driven-development, writing-plans) now include context isolation principle -- Subagents receive only the context they need, preventing context window pollution - -## v5.0.1 (2026-03-10) - -### Agentskills Compliance - -**Brainstorm-server moved into skill directory** - -- Moved `lib/brainstorm-server/` → `skills/brainstorming/scripts/` per the [agentskills.io](https://agentskills.io) specification -- All `${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/` references replaced with relative `scripts/` paths -- Skills are now fully portable across platforms — no platform-specific env vars needed to locate scripts -- `lib/` directory removed (was the last remaining content) - -### New Features - -**Gemini CLI extension** - -- Native Gemini CLI extension support via `gemini-extension.json` and `GEMINI.md` at repo root -- `GEMINI.md` @imports `using-superpowers` skill and tool mapping table at session start -- Gemini CLI tool mapping reference (`skills/using-superpowers/references/gemini-tools.md`) — translates Claude Code tool names (Read, Write, Edit, Bash, etc.) to Gemini CLI equivalents (read_file, write_file, replace, etc.) -- Documents Gemini CLI limitations: no subagent support, skills fall back to `executing-plans` -- Extension root at repo root for cross-platform compatibility (avoids Windows symlink issues) -- Install instructions added to README - -### Improvements - -**Multi-platform brainstorm server launch** - -- Per-platform launch instructions in visual-companion.md: Claude Code (default mode), Codex (auto-foreground via `CODEX_CI`), Gemini CLI (`--foreground` with `is_background`), and fallback for other environments -- Server now writes startup JSON to `$SCREEN_DIR/.server-info` so agents can find the URL and port even when stdout is hidden by background execution - -**Brainstorm server dependencies bundled** - -- `node_modules` vendored into the repo so the brainstorm server works immediately on fresh plugin installs without requiring `npm` at runtime -- Removed `fsevents` from bundled deps (macOS-only native binary; chokidar falls back gracefully without it) -- Fallback auto-install via `npm install` if `node_modules` is missing - -**OpenCode tool mapping fix** - -- `TodoWrite` → `todowrite` (was incorrectly mapped to `update_plan`); verified against OpenCode source - -### Bug Fixes - -**Windows/Linux: single quotes break SessionStart hook** (#577, #529, #644, PR #585) - -- Single quotes around `${CLAUDE_PLUGIN_ROOT}` in hooks.json fail on Windows (cmd.exe doesn't recognize single quotes as path delimiters) and on Linux (single quotes prevent variable expansion) -- Fix: replaced single quotes with escaped double quotes — works across macOS bash, Windows cmd.exe, Windows Git Bash, and Linux, with and without spaces in paths -- Verified on Windows 11 (NT 10.0.26200.0) with Claude Code 2.1.72 and Git for Windows - -**Brainstorming spec review loop skipped** (#677) - -- The spec review loop (dispatch spec-document-reviewer subagent, iterate until approved) existed in the prose "After the Design" section but was missing from the checklist and process flow diagram -- Since agents follow the diagram and checklist more reliably than prose, the spec review step was being skipped entirely -- Added step 7 (spec review loop) to the checklist and corresponding nodes to the dot graph -- Tested with `claude --plugin-dir` and `claude-session-driver`: worker now correctly dispatches the reviewer - -**Cursor install command** (PR #676) - -- Fixed Cursor install command in README: `/plugin-add` → `/add-plugin` (confirmed via Cursor 2.5 release announcement) - -**User review gate in brainstorming** (#565) - -- Added explicit user review step between spec completion and writing-plans handoff -- User must approve the spec before implementation planning begins -- Checklist, process flow, and prose updated with the new gate - -**Session-start hook emits context only once per platform** - -- Hook now detects whether it's running in Claude Code or another platform -- Emits `hookSpecificOutput` for Claude Code, `additional_context` for others — prevents double context injection - -**Linting fix in token analysis script** - -- `except:` → `except Exception:` in `tests/claude-code/analyze-token-usage.py` - -### Maintenance - -**Removed dead code** - -- Deleted `lib/skills-core.js` and its test (`tests/opencode/test-skills-core.js`) — unused since February 2026 -- Removed skills-core existence check from `tests/opencode/test-plugin-loading.sh` - -### Community - -- @karuturi — Claude Code official marketplace install instructions (PR #610) -- @mvanhorn — session-start hook dual-emit fix, OpenCode tool mapping fix -- @daniel-graham — linting fix for bare except -- PR #585 author — Windows/Linux hooks quoting fix - ---- - -## v5.0.0 (2026-03-09) - -### Breaking Changes - -**Specs and plans directory restructured** - -- Specs (brainstorming output) now save to `docs/superpowers/specs/YYYY-MM-DD--design.md` -- Plans (writing-plans output) now save to `docs/superpowers/plans/YYYY-MM-DD-.md` -- User preferences for spec/plan locations override these defaults -- All internal skill references, test files, and example paths updated to match -- Migration: move existing files from `docs/plans/` to new locations if desired - -**Subagent-driven development mandatory on capable harnesses** - -Writing-plans no longer offers a choice between subagent-driven and executing-plans. On harnesses with subagent support (Claude Code, Codex), subagent-driven-development is required. Executing-plans is reserved for harnesses without subagent capability, and now tells the user that Superpowers works better on a subagent-capable platform. - -**Executing-plans no longer batches** - -Removed the "execute 3 tasks then stop for review" pattern. Plans now execute continuously, stopping only for blockers. - -**Slash commands deprecated** - -`/brainstorm`, `/write-plan`, and `/execute-plan` now show deprecation notices pointing users to the corresponding skills. Commands will be removed in the next major release. - -### New Features - -**Visual brainstorming companion** - -Optional browser-based companion for brainstorming sessions. When a topic would benefit from visuals, the brainstorming skill offers to show mockups, diagrams, comparisons, and other content in a browser window alongside terminal conversation. - -- `lib/brainstorm-server/` — WebSocket server with browser helper library, session management scripts, and dark/light themed frame template ("Superpowers Brainstorming" with GitHub link) -- `skills/brainstorming/visual-companion.md` — Progressive disclosure guide for server workflow, screen authoring, and feedback collection -- Brainstorming skill adds a visual companion decision point to its process flow: after exploring project context, the skill evaluates whether upcoming questions involve visual content and offers the companion in its own message -- Per-question decision: even after accepting, each question is evaluated for whether browser or terminal is more appropriate -- Integration tests in `tests/brainstorm-server/` - -**Document review system** - -Automated review loops for spec and plan documents using subagent dispatch: - -- `skills/brainstorming/spec-document-reviewer-prompt.md` — Reviewer checks completeness, consistency, architecture, and YAGNI -- `skills/writing-plans/plan-document-reviewer-prompt.md` — Reviewer checks spec alignment, task decomposition, file structure, and file size -- Brainstorming dispatches spec reviewer after writing the design doc -- Writing-plans includes chunk-based plan review loop after each section -- Review loops repeat until approved or escalate after 5 iterations -- End-to-end tests in `tests/claude-code/test-document-review-system.sh` -- Design spec and implementation plan in `docs/superpowers/` - -**Architecture guidance across the skill pipeline** - -Design-for-isolation and file-size-awareness guidance added to brainstorming, writing-plans, and subagent-driven-development: - -- **Brainstorming** — New sections: "Design for isolation and clarity" (clear boundaries, well-defined interfaces, independently testable units) and "Working in existing codebases" (follow existing patterns, targeted improvements only) -- **Writing-plans** — New "File Structure" section: map out files and responsibilities before defining tasks. New "Scope Check" backstop: catch multi-subsystem specs that should have been decomposed during brainstorming -- **SDD implementer** — New "Code Organization" section (follow plan's file structure, report concerns about growing files) and "When You're in Over Your Head" escalation guidance -- **SDD code quality reviewer** — Now checks architecture, unit decomposition, plan conformance, and file growth -- **Spec/plan reviewers** — Architecture and file size added to review criteria -- **Scope assessment** — Brainstorming now assesses whether a project is too large for a single spec. Multi-subsystem requests are flagged early and decomposed into sub-projects, each with its own spec → plan → implementation cycle - -**Subagent-driven development improvements** - -- **Model selection** — Guidance for choosing model capability by task type: cheap models for mechanical implementation, standard for integration, capable for architecture and review -- **Implementer status protocol** — Subagents now report DONE, DONE_WITH_CONCERNS, BLOCKED, or NEEDS_CONTEXT. Controller handles each status appropriately: re-dispatching with more context, upgrading model capability, breaking tasks apart, or escalating to human - -### Improvements - -**Instruction priority hierarchy** - -Added explicit priority ordering to using-superpowers: - -1. User's explicit instructions (CLAUDE.md, AGENTS.md, direct requests) — highest priority -2. Superpowers skills — override default system behavior -3. Default system prompt — lowest priority - -If CLAUDE.md or AGENTS.md says "don't use TDD" and a skill says "always use TDD," the user's instructions win. - -**SUBAGENT-STOP gate** - -Added `` block to using-superpowers. Subagents dispatched for specific tasks now skip the skill instead of activating the 1% rule and invoking full skill workflows. - -**Multi-platform improvements** - -- Codex tool mapping moved to progressive disclosure reference file (`references/codex-tools.md`) -- Platform Adaptation pointer added so non-Claude-Code platforms can find tool equivalents -- Plan headers now address "agentic workers" instead of "Claude" specifically -- Collab feature requirement documented in `docs/README.codex.md` - -**Writing-plans template updates** - -- Plan steps now use checkbox syntax (`- [ ] **Step N:**`) for progress tracking -- Plan header references both subagent-driven-development and executing-plans with platform-aware routing - ---- - -## v4.3.1 (2026-02-21) - -### Added - -**Cursor support** - -Superpowers now works with Cursor's plugin system. Includes a `.cursor-plugin/plugin.json` manifest and Cursor-specific installation instructions in the README. The SessionStart hook output now includes an `additional_context` field alongside the existing `hookSpecificOutput.additionalContext` for Cursor hook compatibility. - -### Fixed - -**Windows: Restored polyglot wrapper for reliable hook execution (#518, #504, #491, #487, #466, #440)** - -Claude Code's `.sh` auto-detection on Windows was prepending `bash` to the hook command, breaking execution. The fix: - -- Renamed `session-start.sh` to `session-start` (extensionless) so auto-detection doesn't interfere -- Restored `run-hook.cmd` polyglot wrapper with multi-location bash discovery (standard Git for Windows paths, then PATH fallback) -- Exits silently if no bash is found rather than erroring -- On Unix, the wrapper runs the script directly via `exec bash` -- Uses POSIX-safe `dirname "$0"` path resolution (works on dash/sh, not just bash) - -This fixes SessionStart failures on Windows with spaces in paths, missing WSL, `set -euo pipefail` fragility on MSYS, and backslash mangling. - -## v4.3.0 (2026-02-12) - -This fix should dramatically improve superpowers skills compliance and should reduce the chances of Claude entering its native plan mode unintentionally. - -### Changed - -**Brainstorming skill now enforces its workflow instead of describing it** - -Models were skipping the design phase and jumping straight to implementation skills like frontend-design, or collapsing the entire brainstorming process into a single text block. The skill now uses hard gates, a mandatory checklist, and a graphviz process flow to enforce compliance: - -- ``: no implementation skills, code, or scaffolding until design is presented and user approves -- Explicit checklist (6 items) that must be created as tasks and completed in order -- Graphviz process flow with `writing-plans` as the only valid terminal state -- Anti-pattern callout for "this is too simple to need a design" — the exact rationalization models use to skip the process -- Design section sizing based on section complexity, not project complexity - -**Using-superpowers workflow graph intercepts EnterPlanMode** - -Added an `EnterPlanMode` intercept to the skill flow graph. When the model is about to enter Claude's native plan mode, it checks whether brainstorming has happened and routes through the brainstorming skill instead. Plan mode is never entered. - -### Fixed - -**SessionStart hook now runs synchronously** - -Changed `async: true` to `async: false` in hooks.json. When async, the hook could fail to complete before the model's first turn, meaning using-superpowers instructions weren't in context for the first message. - -## v4.2.0 (2026-02-05) - -### Breaking Changes - -**Codex: Replaced bootstrap CLI with native skill discovery** - -The `superpowers-codex` bootstrap CLI, Windows `.cmd` wrapper, and related bootstrap content file have been removed. Codex now uses native skill discovery via `~/.agents/skills/superpowers/` symlink, so the old `use_skill`/`find_skills` CLI tools are no longer needed. - -Installation is now just clone + symlink (documented in INSTALL.md). No Node.js dependency required. The old `~/.codex/skills/` path is deprecated. - -### Fixes - -**Windows: Fixed Claude Code 2.1.x hook execution (#331)** - -Claude Code 2.1.x changed how hooks execute on Windows: it now auto-detects `.sh` files in commands and prepends `bash`. This broke the polyglot wrapper pattern because `bash "run-hook.cmd" session-start.sh` tries to execute the `.cmd` file as a bash script. - -Fix: hooks.json now calls session-start.sh directly. Claude Code 2.1.x handles the bash invocation automatically. Also added .gitattributes to enforce LF line endings for shell scripts (fixes CRLF issues on Windows checkout). - -**Windows: SessionStart hook runs async to prevent terminal freeze (#404, #413, #414, #419)** - -The synchronous SessionStart hook blocked the TUI from entering raw mode on Windows, freezing all keyboard input. Running the hook async prevents the freeze while still injecting superpowers context. - -**Windows: Fixed O(n^2) `escape_for_json` performance** - -The character-by-character loop using `${input:$i:1}` was O(n^2) in bash due to substring copy overhead. On Windows Git Bash this took 60+ seconds. Replaced with bash parameter substitution (`${s//old/new}`) which runs each pattern as a single C-level pass — 7x faster on macOS, dramatically faster on Windows. - -**Codex: Fixed Windows/PowerShell invocation (#285, #243)** - -- Windows doesn't respect shebangs, so directly invoking the extensionless `superpowers-codex` script triggered an "Open with" dialog. All invocations now prefixed with `node`. -- Fixed `~/` path expansion on Windows — PowerShell doesn't expand `~` when passed as an argument to `node`. Changed to `$HOME` which expands correctly in both bash and PowerShell. - -**Codex: Fixed path resolution in installer** - -Used `fileURLToPath()` instead of manual URL pathname parsing to correctly handle paths with spaces and special characters on all platforms. - -**Codex: Fixed stale skills path in writing-skills** - -Updated `~/.codex/skills/` reference (deprecated) to `~/.agents/skills/` for native discovery. - -### Improvements - -**Worktree isolation now required before implementation** - -Added `using-git-worktrees` as a required skill for both `subagent-driven-development` and `executing-plans`. Implementation workflows now explicitly require setting up an isolated worktree before starting work, preventing accidental work directly on main. - -**Main branch protection softened to require explicit consent** - -Instead of prohibiting main branch work entirely, the skills now allow it with explicit user consent. More flexible while still ensuring users are aware of the implications. - -**Simplified installation verification** - -Removed `/help` command check and specific slash command list from verification steps. Skills are primarily invoked by describing what you want to do, not by running specific commands. - -**Codex: Clarified subagent tool mapping in bootstrap** - -Improved documentation of how Codex tools map to Claude Code equivalents for subagent workflows. - -### Tests - -- Added worktree requirement test for subagent-driven-development -- Added main branch red flag warning test -- Fixed case sensitivity in skill recognition test assertions - ---- - -## v4.1.1 (2026-01-23) - -### Fixes - -**OpenCode: Standardized on `plugins/` directory per official docs (#343)** - -OpenCode's official documentation uses `~/.config/opencode/plugins/` (plural). Our docs previously used `plugin/` (singular). While OpenCode accepts both forms, we've standardized on the official convention to avoid confusion. - -Changes: -- Renamed `.opencode/plugin/` to `.opencode/plugins/` in repo structure -- Updated all installation docs (INSTALL.md, README.opencode.md) across all platforms -- Updated test scripts to match - -**OpenCode: Fixed symlink instructions (#339, #342)** - -- Added explicit `rm` before `ln -s` (fixes "file already exists" errors on reinstall) -- Added missing skills symlink step that was absent from INSTALL.md -- Updated from deprecated `use_skill`/`find_skills` to native `skill` tool references - ---- - -## v4.1.0 (2026-01-23) - -### Breaking Changes - -**OpenCode: Switched to native skills system** - -Superpowers for OpenCode now uses OpenCode's native `skill` tool instead of custom `use_skill`/`find_skills` tools. This is a cleaner integration that works with OpenCode's built-in skill discovery. - -**Migration required:** Skills must be symlinked to `~/.config/opencode/skills/superpowers/` (see updated installation docs). - -### Fixes - -**OpenCode: Fixed agent reset on session start (#226)** - -The previous bootstrap injection method using `session.prompt({ noReply: true })` caused OpenCode to reset the selected agent to "build" on first message. Now uses `experimental.chat.system.transform` hook which modifies the system prompt directly without side effects. - -**OpenCode: Fixed Windows installation (#232)** - -- Removed dependency on `skills-core.js` (eliminates broken relative imports when file is copied instead of symlinked) -- Added comprehensive Windows installation docs for cmd.exe, PowerShell, and Git Bash -- Documented proper symlink vs junction usage for each platform - -**Claude Code: Fixed Windows hook execution for Claude Code 2.1.x** - -Claude Code 2.1.x changed how hooks execute on Windows: it now auto-detects `.sh` files in commands and prepends `bash `. This broke the polyglot wrapper pattern because `bash "run-hook.cmd" session-start.sh` tries to execute the .cmd file as a bash script. - -Fix: hooks.json now calls session-start.sh directly. Claude Code 2.1.x handles the bash invocation automatically. Also added .gitattributes to enforce LF line endings for shell scripts (fixes CRLF issues on Windows checkout). - ---- - -## v4.0.3 (2025-12-26) - -### Improvements - -**Strengthened using-superpowers skill for explicit skill requests** - -Addressed a failure mode where Claude would skip invoking a skill even when the user explicitly requested it by name (e.g., "subagent-driven-development, please"). Claude would think "I know what that means" and start working directly instead of loading the skill. - -Changes: -- Updated "The Rule" to say "Invoke relevant or requested skills" instead of "Check for skills" - emphasizing active invocation over passive checking -- Added "BEFORE any response or action" - the original wording only mentioned "response" but Claude would sometimes take action without responding first -- Added reassurance that invoking a wrong skill is okay - reduces hesitation -- Added new red flag: "I know what that means" → Knowing the concept ≠ using the skill - -**Added explicit skill request tests** - -New test suite in `tests/explicit-skill-requests/` that verifies Claude correctly invokes skills when users request them by name. Includes single-turn and multi-turn test scenarios. - -## v4.0.2 (2025-12-23) - -### Fixes - -**Slash commands now user-only** - -Added `disable-model-invocation: true` to all three slash commands (`/brainstorm`, `/execute-plan`, `/write-plan`). Claude can no longer invoke these commands via the Skill tool—they're restricted to manual user invocation only. - -The underlying skills (`superpowers:brainstorming`, `superpowers:executing-plans`, `superpowers:writing-plans`) remain available for Claude to invoke autonomously. This change prevents confusion when Claude would invoke a command that just redirects to a skill anyway. - -## v4.0.1 (2025-12-23) - -### Fixes - -**Clarified how to access skills in Claude Code** - -Fixed a confusing pattern where Claude would invoke a skill via the Skill tool, then try to Read the skill file separately. The `using-superpowers` skill now explicitly states that the Skill tool loads skill content directly—no need to read files. - -- Added "How to Access Skills" section to `using-superpowers` -- Changed "read the skill" → "invoke the skill" in instructions -- Updated slash commands to use fully qualified skill names (e.g., `superpowers:brainstorming`) - -**Added GitHub thread reply guidance to receiving-code-review** (h/t @ralphbean) - -Added a note about replying to inline review comments in the original thread rather than as top-level PR comments. - -**Added automation-over-documentation guidance to writing-skills** (h/t @EthanJStark) - -Added guidance that mechanical constraints should be automated, not documented—save skills for judgment calls. - -## v4.0.0 (2025-12-17) - -### New Features - -**Two-stage code review in subagent-driven-development** - -Subagent workflows now use two separate review stages after each task: - -1. **Spec compliance review** - Skeptical reviewer verifies implementation matches spec exactly. Catches missing requirements AND over-building. Won't trust implementer's report—reads actual code. - -2. **Code quality review** - Only runs after spec compliance passes. Reviews for clean code, test coverage, maintainability. - -This catches the common failure mode where code is well-written but doesn't match what was requested. Reviews are loops, not one-shot: if reviewer finds issues, implementer fixes them, then reviewer checks again. - -Other subagent workflow improvements: -- Controller provides full task text to workers (not file references) -- Workers can ask clarifying questions before AND during work -- Self-review checklist before reporting completion -- Plan read once at start, extracted to TodoWrite - -New prompt templates in `skills/subagent-driven-development/`: -- `implementer-prompt.md` - Includes self-review checklist, encourages questions -- `spec-reviewer-prompt.md` - Skeptical verification against requirements -- `code-quality-reviewer-prompt.md` - Standard code review - -**Debugging techniques consolidated with tools** - -`systematic-debugging` now bundles supporting techniques and tools: -- `root-cause-tracing.md` - Trace bugs backward through call stack -- `defense-in-depth.md` - Add validation at multiple layers -- `condition-based-waiting.md` - Replace arbitrary timeouts with condition polling -- `find-polluter.sh` - Bisection script to find which test creates pollution -- `condition-based-waiting-example.ts` - Complete implementation from real debugging session - -**Testing anti-patterns reference** - -`test-driven-development` now includes `testing-anti-patterns.md` covering: -- Testing mock behavior instead of real behavior -- Adding test-only methods to production classes -- Mocking without understanding dependencies -- Incomplete mocks that hide structural assumptions - -**Skill test infrastructure** - -Three new test frameworks for validating skill behavior: - -`tests/skill-triggering/` - Validates skills trigger from naive prompts without explicit naming. Tests 6 skills to ensure descriptions alone are sufficient. - -`tests/claude-code/` - Integration tests using `claude -p` for headless testing. Verifies skill usage via session transcript (JSONL) analysis. Includes `analyze-token-usage.py` for cost tracking. - -`tests/subagent-driven-dev/` - End-to-end workflow validation with two complete test projects: -- `go-fractals/` - CLI tool with Sierpinski/Mandelbrot (10 tasks) -- `svelte-todo/` - CRUD app with localStorage and Playwright (12 tasks) - -### Major Changes - -**DOT flowcharts as executable specifications** - -Rewrote key skills using DOT/GraphViz flowcharts as the authoritative process definition. Prose becomes supporting content. - -**The Description Trap** (documented in `writing-skills`): Discovered that skill descriptions override flowchart content when descriptions contain workflow summaries. Claude follows the short description instead of reading the detailed flowchart. Fix: descriptions must be trigger-only ("Use when X") with no process details. - -**Skill priority in using-superpowers** - -When multiple skills apply, process skills (brainstorming, debugging) now explicitly come before implementation skills. "Build X" triggers brainstorming first, then domain skills. - -**brainstorming trigger strengthened** - -Description changed to imperative: "You MUST use this before any creative work—creating features, building components, adding functionality, or modifying behavior." - -### Breaking Changes - -**Skill consolidation** - Six standalone skills merged: -- `root-cause-tracing`, `defense-in-depth`, `condition-based-waiting` → bundled in `systematic-debugging/` -- `testing-skills-with-subagents` → bundled in `writing-skills/` -- `testing-anti-patterns` → bundled in `test-driven-development/` -- `sharing-skills` removed (obsolete) - -### Other Improvements - -- **render-graphs.js** - Tool to extract DOT diagrams from skills and render to SVG -- **Rationalizations table** in using-superpowers - Scannable format including new entries: "I need more context first", "Let me explore first", "This feels productive" -- **docs/testing.md** - Guide to testing skills with Claude Code integration tests - ---- - -## v3.6.2 (2025-12-03) - -### Fixed - -- **Linux Compatibility**: Fixed polyglot hook wrapper (`run-hook.cmd`) to use POSIX-compliant syntax - - Replaced bash-specific `${BASH_SOURCE[0]:-$0}` with standard `$0` on line 16 - - Resolves "Bad substitution" error on Ubuntu/Debian systems where `/bin/sh` is dash - - Fixes #141 - ---- - -## v3.5.1 (2025-11-24) - -### Changed - -- **OpenCode Bootstrap Refactor**: Switched from `chat.message` hook to `session.created` event for bootstrap injection - - Bootstrap now injects at session creation via `session.prompt()` with `noReply: true` - - Explicitly tells the model that using-superpowers is already loaded to prevent redundant skill loading - - Consolidated bootstrap content generation into shared `getBootstrapContent()` helper - - Cleaner single-implementation approach (removed fallback pattern) - ---- - -## v3.5.0 (2025-11-23) - -### Added - -- **OpenCode Support**: Native JavaScript plugin for OpenCode.ai - - Custom tools: `use_skill` and `find_skills` - - Message insertion pattern for skill persistence across context compaction - - Automatic context injection via chat.message hook - - Auto re-injection on session.compacted events - - Three-tier skill priority: project > personal > superpowers - - Project-local skills support (`.opencode/skills/`) - - Shared core module (`lib/skills-core.js`) for code reuse with Codex - - Automated test suite with proper isolation (`tests/opencode/`) - - Platform-specific documentation (`docs/README.opencode.md`, `docs/README.codex.md`) - -### Changed - -- **Refactored Codex Implementation**: Now uses shared `lib/skills-core.js` ES module - - Eliminates code duplication between Codex and OpenCode - - Single source of truth for skill discovery and parsing - - Codex successfully loads ES modules via Node.js interop - -- **Improved Documentation**: Rewrote README to explain problem/solution clearly - - Removed duplicate sections and conflicting information - - Added complete workflow description (brainstorm → plan → execute → finish) - - Simplified platform installation instructions - - Emphasized skill-checking protocol over automatic activation claims - ---- - -## v3.4.1 (2025-10-31) - -### Improvements - -- Optimized superpowers bootstrap to eliminate redundant skill execution. The `using-superpowers` skill content is now provided directly in session context, with clear guidance to use the Skill tool only for other skills. This reduces overhead and prevents the confusing loop where agents would execute `using-superpowers` manually despite already having the content from session start. - -## v3.4.0 (2025-10-30) - -### Improvements - -- Simplified `brainstorming` skill to return to original conversational vision. Removed heavyweight 6-phase process with formal checklists in favor of natural dialogue: ask questions one at a time, then present design in 200-300 word sections with validation. Keeps documentation and implementation handoff features. - -## v3.3.1 (2025-10-28) - -### Improvements - -- Updated `brainstorming` skill to require autonomous recon before questioning, encourage recommendation-driven decisions, and prevent agents from delegating prioritization back to humans. -- Applied writing clarity improvements to `brainstorming` skill following Strunk's "Elements of Style" principles (omitted needless words, converted negative to positive form, improved parallel construction). - -### Bug Fixes - -- Clarified `writing-skills` guidance so it points to the correct agent-specific personal skill directories (`~/.claude/skills` for Claude Code, `~/.codex/skills` for Codex). - -## v3.3.0 (2025-10-28) - -### New Features - -**Experimental Codex Support** -- Added unified `superpowers-codex` script with bootstrap/use-skill/find-skills commands -- Cross-platform Node.js implementation (works on Windows, macOS, Linux) -- Namespaced skills: `superpowers:skill-name` for superpowers skills, `skill-name` for personal -- Personal skills override superpowers skills when names match -- Clean skill display: shows name/description without raw frontmatter -- Helpful context: shows supporting files directory for each skill -- Tool mapping for Codex: TodoWrite→update_plan, subagents→manual fallback, etc. -- Bootstrap integration with minimal AGENTS.md for automatic startup -- Complete installation guide and bootstrap instructions specific to Codex - -**Key differences from Claude Code integration:** -- Single unified script instead of separate tools -- Tool substitution system for Codex-specific equivalents -- Simplified subagent handling (manual work instead of delegation) -- Updated terminology: "Superpowers skills" instead of "Core skills" - -### Files Added -- `.codex/INSTALL.md` - Installation guide for Codex users -- `.codex/superpowers-bootstrap.md` - Bootstrap instructions with Codex adaptations -- `.codex/superpowers-codex` - Unified Node.js executable with all functionality - -**Note:** Codex support is experimental. The integration provides core superpowers functionality but may require refinement based on user feedback. - -## v3.2.3 (2025-10-23) - -### Improvements - -**Updated using-superpowers skill to use Skill tool instead of Read tool** -- Changed skill invocation instructions from Read tool to Skill tool -- Updated description: "using Read tool" → "using Skill tool" -- Updated step 3: "Use the Read tool" → "Use the Skill tool to read and run" -- Updated rationalization list: "Read the current version" → "Run the current version" - -The Skill tool is the proper mechanism for invoking skills in Claude Code. This update corrects the bootstrap instructions to guide agents toward the correct tool. - -### Files Changed -- Updated: `skills/using-superpowers/SKILL.md` - Changed tool references from Read to Skill - -## v3.2.2 (2025-10-21) - -### Improvements - -**Strengthened using-superpowers skill against agent rationalization** -- Added EXTREMELY-IMPORTANT block with absolute language about mandatory skill checking - - "If even 1% chance a skill applies, you MUST read it" - - "You do not have a choice. You cannot rationalize your way out." -- Added MANDATORY FIRST RESPONSE PROTOCOL checklist - - 5-step process agents must complete before any response - - Explicit "responding without this = failure" consequence -- Added Common Rationalizations section with 8 specific evasion patterns - - "This is just a simple question" → WRONG - - "I can check files quickly" → WRONG - - "Let me gather information first" → WRONG - - Plus 5 more common patterns observed in agent behavior - -These changes address observed agent behavior where they rationalize around skill usage despite clear instructions. The forceful language and pre-emptive counter-arguments aim to make non-compliance harder. - -### Files Changed -- Updated: `skills/using-superpowers/SKILL.md` - Added three layers of enforcement to prevent skill-skipping rationalization - -## v3.2.1 (2025-10-20) - -### New Features - -**Code reviewer agent now included in plugin** -- Added `superpowers:code-reviewer` agent to plugin's `agents/` directory -- Agent provides systematic code review against plans and coding standards -- Previously required users to have personal agent configuration -- All skill references updated to use namespaced `superpowers:code-reviewer` -- Fixes #55 - -### Files Changed -- New: `agents/code-reviewer.md` - Agent definition with review checklist and output format -- Updated: `skills/requesting-code-review/SKILL.md` - References to `superpowers:code-reviewer` -- Updated: `skills/subagent-driven-development/SKILL.md` - References to `superpowers:code-reviewer` - -## v3.2.0 (2025-10-18) - -### New Features - -**Design documentation in brainstorming workflow** -- Added Phase 4: Design Documentation to brainstorming skill -- Design documents now written to `docs/plans/YYYY-MM-DD--design.md` before implementation -- Restores functionality from original brainstorming command that was lost during skill conversion -- Documents written before worktree setup and implementation planning -- Tested with subagent to verify compliance under time pressure - -### Breaking Changes - -**Skill reference namespace standardization** -- All internal skill references now use `superpowers:` namespace prefix -- Updated format: `superpowers:test-driven-development` (previously just `test-driven-development`) -- Affects all REQUIRED SUB-SKILL, RECOMMENDED SUB-SKILL, and REQUIRED BACKGROUND references -- Aligns with how skills are invoked using the Skill tool -- Files updated: brainstorming, executing-plans, subagent-driven-development, systematic-debugging, testing-skills-with-subagents, writing-plans, writing-skills - -### Improvements - -**Design vs implementation plan naming** -- Design documents use `-design.md` suffix to prevent filename collisions -- Implementation plans continue using existing `YYYY-MM-DD-.md` format -- Both stored in `docs/plans/` directory with clear naming distinction - -## v3.1.1 (2025-10-17) - -### Bug Fixes - -- **Fixed command syntax in README** (#44) - Updated all command references to use correct namespaced syntax (`/superpowers:brainstorm` instead of `/brainstorm`). Plugin-provided commands are automatically namespaced by Claude Code to avoid conflicts between plugins. - -## v3.1.0 (2025-10-17) - -### Breaking Changes - -**Skill names standardized to lowercase** -- All skill frontmatter `name:` fields now use lowercase kebab-case matching directory names -- Examples: `brainstorming`, `test-driven-development`, `using-git-worktrees` -- All skill announcements and cross-references updated to lowercase format -- This ensures consistent naming across directory names, frontmatter, and documentation - -### New Features - -**Enhanced brainstorming skill** -- Added Quick Reference table showing phases, activities, and tool usage -- Added copyable workflow checklist for tracking progress -- Added decision flowchart for when to revisit earlier phases -- Added comprehensive AskUserQuestion tool guidance with concrete examples -- Added "Question Patterns" section explaining when to use structured vs open-ended questions -- Restructured Key Principles as scannable table - -**Anthropic best practices integration** -- Added `skills/writing-skills/anthropic-best-practices.md` - Official Anthropic skill authoring guide -- Referenced in writing-skills SKILL.md for comprehensive guidance -- Provides patterns for progressive disclosure, workflows, and evaluation - -### Improvements - -**Skill cross-reference clarity** -- All skill references now use explicit requirement markers: - - `**REQUIRED BACKGROUND:**` - Prerequisites you must understand - - `**REQUIRED SUB-SKILL:**` - Skills that must be used in workflow - - `**Complementary skills:**` - Optional but helpful related skills -- Removed old path format (`skills/collaboration/X` → just `X`) -- Updated Integration sections with categorized relationships (Required vs Complementary) -- Updated cross-reference documentation with best practices - -**Alignment with Anthropic best practices** -- Fixed description grammar and voice (fully third-person) -- Added Quick Reference tables for scanning -- Added workflow checklists Claude can copy and track -- Appropriate use of flowcharts for non-obvious decision points -- Improved scannable table formats -- All skills well under 500-line recommendation - -### Bug Fixes - -- **Re-added missing command redirects** - Restored `commands/brainstorm.md` and `commands/write-plan.md` that were accidentally removed in v3.0 migration -- Fixed `defense-in-depth` name mismatch (was `Defense-in-Depth-Validation`) -- Fixed `receiving-code-review` name mismatch (was `Code-Review-Reception`) -- Fixed `commands/brainstorm.md` reference to correct skill name -- Removed references to non-existent related skills - -### Documentation - -**writing-skills improvements** -- Updated cross-referencing guidance with explicit requirement markers -- Added reference to Anthropic's official best practices -- Improved examples showing proper skill reference format - -## v3.0.1 (2025-10-16) - -### Changes - -We now use Anthropic's first-party skills system! - -## v2.0.2 (2025-10-12) - -### Bug Fixes - -- **Fixed false warning when local skills repo is ahead of upstream** - The initialization script was incorrectly warning "New skills available from upstream" when the local repository had commits ahead of upstream. The logic now correctly distinguishes between three git states: local behind (should update), local ahead (no warning), and diverged (should warn). - -## v2.0.1 (2025-10-12) - -### Bug Fixes - -- **Fixed session-start hook execution in plugin context** (#8, PR #9) - The hook was failing silently with "Plugin hook error" preventing skills context from loading. Fixed by: - - Using `${BASH_SOURCE[0]:-$0}` fallback when BASH_SOURCE is unbound in Claude Code's execution context - - Adding `|| true` to handle empty grep results gracefully when filtering status flags - ---- - -# Superpowers v2.0.0 Release Notes - -## Overview - -Superpowers v2.0 makes skills more accessible, maintainable, and community-driven through a major architectural shift. - -The headline change is **skills repository separation**: all skills, scripts, and documentation have moved from the plugin into a dedicated repository ([obra/superpowers-skills](https://github.com/obra/superpowers-skills)). This transforms superpowers from a monolithic plugin into a lightweight shim that manages a local clone of the skills repository. Skills auto-update on session start. Users fork and contribute improvements via standard git workflows. The skills library versions independently from the plugin. - -Beyond infrastructure, this release adds nine new skills focused on problem-solving, research, and architecture. We rewrote the core **using-skills** documentation with imperative tone and clearer structure, making it easier for Claude to understand when and how to use skills. **find-skills** now outputs paths you can paste directly into the Read tool, eliminating friction in the skills discovery workflow. - -Users experience seamless operation: the plugin handles cloning, forking, and updating automatically. Contributors find the new architecture makes improving and sharing skills trivial. This release lays the foundation for skills to evolve rapidly as a community resource. - -## Breaking Changes - -### Skills Repository Separation - -**The biggest change:** Skills no longer live in the plugin. They've been moved to a separate repository at [obra/superpowers-skills](https://github.com/obra/superpowers-skills). - -**What this means for you:** - -- **First install:** Plugin automatically clones skills to `~/.config/superpowers/skills/` -- **Forking:** During setup, you'll be offered the option to fork the skills repo (if `gh` is installed) -- **Updates:** Skills auto-update on session start (fast-forward when possible) -- **Contributing:** Work on branches, commit locally, submit PRs to upstream -- **No more shadowing:** Old two-tier system (personal/core) replaced with single-repo branch workflow - -**Migration:** - -If you have an existing installation: -1. Your old `~/.config/superpowers/.git` will be backed up to `~/.config/superpowers/.git.bak` -2. Old skills will be backed up to `~/.config/superpowers/skills.bak` -3. Fresh clone of obra/superpowers-skills will be created at `~/.config/superpowers/skills/` - -### Removed Features - -- **Personal superpowers overlay system** - Replaced with git branch workflow -- **setup-personal-superpowers hook** - Replaced by initialize-skills.sh - -## New Features - -### Skills Repository Infrastructure - -**Automatic Clone & Setup** (`lib/initialize-skills.sh`) -- Clones obra/superpowers-skills on first run -- Offers fork creation if GitHub CLI is installed -- Sets up upstream/origin remotes correctly -- Handles migration from old installation - -**Auto-Update** -- Fetches from tracking remote on every session start -- Auto-merges with fast-forward when possible -- Notifies when manual sync needed (branch diverged) -- Uses pulling-updates-from-skills-repository skill for manual sync - -### New Skills - -**Problem-Solving Skills** (`skills/problem-solving/`) -- **collision-zone-thinking** - Force unrelated concepts together for emergent insights -- **inversion-exercise** - Flip assumptions to reveal hidden constraints -- **meta-pattern-recognition** - Spot universal principles across domains -- **scale-game** - Test at extremes to expose fundamental truths -- **simplification-cascades** - Find insights that eliminate multiple components -- **when-stuck** - Dispatch to right problem-solving technique - -**Research Skills** (`skills/research/`) -- **tracing-knowledge-lineages** - Understand how ideas evolved over time - -**Architecture Skills** (`skills/architecture/`) -- **preserving-productive-tensions** - Keep multiple valid approaches instead of forcing premature resolution - -### Skills Improvements - -**using-skills (formerly getting-started)** -- Renamed from getting-started to using-skills -- Complete rewrite with imperative tone (v4.0.0) -- Front-loaded critical rules -- Added "Why" explanations for all workflows -- Always includes /SKILL.md suffix in references -- Clearer distinction between rigid rules and flexible patterns - -**writing-skills** -- Cross-referencing guidance moved from using-skills -- Added token efficiency section (word count targets) -- Improved CSO (Claude Search Optimization) guidance - -**sharing-skills** -- Updated for new branch-and-PR workflow (v2.0.0) -- Removed personal/core split references - -**pulling-updates-from-skills-repository** (new) -- Complete workflow for syncing with upstream -- Replaces old "updating-skills" skill - -### Tools Improvements - -**find-skills** -- Now outputs full paths with /SKILL.md suffix -- Makes paths directly usable with Read tool -- Updated help text - -**skill-run** -- Moved from scripts/ to skills/using-skills/ -- Improved documentation - -### Plugin Infrastructure - -**Session Start Hook** -- Now loads from skills repository location -- Shows full skills list at session start -- Prints skills location info -- Shows update status (updated successfully / behind upstream) -- Moved "skills behind" warning to end of output - -**Environment Variables** -- `SUPERPOWERS_SKILLS_ROOT` set to `~/.config/superpowers/skills` -- Used consistently throughout all paths - -## Bug Fixes - -- Fixed duplicate upstream remote addition when forking -- Fixed find-skills double "skills/" prefix in output -- Removed obsolete setup-personal-superpowers call from session-start -- Fixed path references throughout hooks and commands - -## Documentation - -### README -- Updated for new skills repository architecture -- Prominent link to superpowers-skills repo -- Updated auto-update description -- Fixed skill names and references -- Updated Meta skills list - -### Testing Documentation -- Added comprehensive testing checklist (`docs/TESTING-CHECKLIST.md`) -- Created local marketplace config for testing -- Documented manual testing scenarios - -## Technical Details - -### File Changes - -**Added:** -- `lib/initialize-skills.sh` - Skills repo initialization and auto-update -- `docs/TESTING-CHECKLIST.md` - Manual testing scenarios -- `.claude-plugin/marketplace.json` - Local testing config - -**Removed:** -- `skills/` directory (82 files) - Now in obra/superpowers-skills -- `scripts/` directory - Now in obra/superpowers-skills/skills/using-skills/ -- `hooks/setup-personal-superpowers.sh` - Obsolete - -**Modified:** -- `hooks/session-start.sh` - Use skills from ~/.config/superpowers/skills -- `commands/brainstorm.md` - Updated paths to SUPERPOWERS_SKILLS_ROOT -- `commands/write-plan.md` - Updated paths to SUPERPOWERS_SKILLS_ROOT -- `commands/execute-plan.md` - Updated paths to SUPERPOWERS_SKILLS_ROOT -- `README.md` - Complete rewrite for new architecture - -### Commit History - -This release includes: -- 20+ commits for skills repository separation -- PR #1: Amplifier-inspired problem-solving and research skills -- PR #2: Personal superpowers overlay system (later replaced) -- Multiple skill refinements and documentation improvements - -## Upgrade Instructions - -### Fresh Install - -```bash -# In Claude Code -/plugin marketplace add obra/superpowers-marketplace -/plugin install superpowers@superpowers-marketplace -``` - -The plugin handles everything automatically. - -### Upgrading from v1.x - -1. **Backup your personal skills** (if you have any): - ```bash - cp -r ~/.config/superpowers/skills ~/superpowers-skills-backup - ``` - -2. **Update the plugin:** - ```bash - /plugin update superpowers - ``` - -3. **On next session start:** - - Old installation will be backed up automatically - - Fresh skills repo will be cloned - - If you have GitHub CLI, you'll be offered the option to fork - -4. **Migrate personal skills** (if you had any): - - Create a branch in your local skills repo - - Copy your personal skills from backup - - Commit and push to your fork - - Consider contributing back via PR - -## What's Next - -### For Users - -- Explore the new problem-solving skills -- Try the branch-based workflow for skill improvements -- Contribute skills back to the community - -### For Contributors - -- Skills repository is now at https://github.com/obra/superpowers-skills -- Fork → Branch → PR workflow -- See skills/meta/writing-skills/SKILL.md for TDD approach to documentation - -## Known Issues - -None at this time. - -## Credits - -- Problem-solving skills inspired by Amplifier patterns -- Community contributions and feedback -- Extensive testing and iteration on skill effectiveness - ---- - -**Full Changelog:** https://github.com/obra/superpowers/compare/dd013f6...main -**Skills Repository:** https://github.com/obra/superpowers-skills -**Issues:** https://github.com/obra/superpowers/issues diff --git a/resources/skills/superpowers/agents/code-reviewer.md b/resources/skills/superpowers/agents/code-reviewer.md deleted file mode 100644 index 4e14076..0000000 --- a/resources/skills/superpowers/agents/code-reviewer.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: code-reviewer -description: | - Use this agent when a major project step has been completed and needs to be reviewed against the original plan and coding standards. Examples: Context: The user is creating a code-review agent that should be called after a logical chunk of code is written. user: "I've finished implementing the user authentication system as outlined in step 3 of our plan" assistant: "Great work! Now let me use the code-reviewer agent to review the implementation against our plan and coding standards" Since a major project step has been completed, use the code-reviewer agent to validate the work against the plan and identify any issues. Context: User has completed a significant feature implementation. user: "The API endpoints for the task management system are now complete - that covers step 2 from our architecture document" assistant: "Excellent! Let me have the code-reviewer agent examine this implementation to ensure it aligns with our plan and follows best practices" A numbered step from the planning document has been completed, so the code-reviewer agent should review the work. -model: inherit ---- - -You are a Senior Code Reviewer with expertise in software architecture, design patterns, and best practices. Your role is to review completed project steps against original plans and ensure code quality standards are met. - -When reviewing completed work, you will: - -1. **Plan Alignment Analysis**: - - Compare the implementation against the original planning document or step description - - Identify any deviations from the planned approach, architecture, or requirements - - Assess whether deviations are justified improvements or problematic departures - - Verify that all planned functionality has been implemented - -2. **Code Quality Assessment**: - - Review code for adherence to established patterns and conventions - - Check for proper error handling, type safety, and defensive programming - - Evaluate code organization, naming conventions, and maintainability - - Assess test coverage and quality of test implementations - - Look for potential security vulnerabilities or performance issues - -3. **Architecture and Design Review**: - - Ensure the implementation follows SOLID principles and established architectural patterns - - Check for proper separation of concerns and loose coupling - - Verify that the code integrates well with existing systems - - Assess scalability and extensibility considerations - -4. **Documentation and Standards**: - - Verify that code includes appropriate comments and documentation - - Check that file headers, function documentation, and inline comments are present and accurate - - Ensure adherence to project-specific coding standards and conventions - -5. **Issue Identification and Recommendations**: - - Clearly categorize issues as: Critical (must fix), Important (should fix), or Suggestions (nice to have) - - For each issue, provide specific examples and actionable recommendations - - When you identify plan deviations, explain whether they're problematic or beneficial - - Suggest specific improvements with code examples when helpful - -6. **Communication Protocol**: - - If you find significant deviations from the plan, ask the coding agent to review and confirm the changes - - If you identify issues with the original plan itself, recommend plan updates - - For implementation problems, provide clear guidance on fixes needed - - Always acknowledge what was done well before highlighting issues - -Your output should be structured, actionable, and focused on helping maintain high code quality while ensuring project goals are met. Be thorough but concise, and always provide constructive feedback that helps improve both the current implementation and future development practices. diff --git a/resources/skills/superpowers/commands/brainstorm.md b/resources/skills/superpowers/commands/brainstorm.md deleted file mode 100644 index dbc6699..0000000 --- a/resources/skills/superpowers/commands/brainstorm.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -description: "Deprecated - use the superpowers:brainstorming skill instead" ---- - -Tell your human partner that this command is deprecated and will be removed in the next major release. They should ask you to use the "superpowers brainstorming" skill instead. diff --git a/resources/skills/superpowers/commands/execute-plan.md b/resources/skills/superpowers/commands/execute-plan.md deleted file mode 100644 index 298e295..0000000 --- a/resources/skills/superpowers/commands/execute-plan.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -description: "Deprecated - use the superpowers:executing-plans skill instead" ---- - -Tell your human partner that this command is deprecated and will be removed in the next major release. They should ask you to use the "superpowers executing-plans" skill instead. diff --git a/resources/skills/superpowers/commands/write-plan.md b/resources/skills/superpowers/commands/write-plan.md deleted file mode 100644 index 5275df7..0000000 --- a/resources/skills/superpowers/commands/write-plan.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -description: "Deprecated - use the superpowers:writing-plans skill instead" ---- - -Tell your human partner that this command is deprecated and will be removed in the next major release. They should ask you to use the "superpowers writing-plans" skill instead. diff --git a/resources/skills/superpowers/gemini-extension.json b/resources/skills/superpowers/gemini-extension.json deleted file mode 100644 index d52654a..0000000 --- a/resources/skills/superpowers/gemini-extension.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "superpowers", - "description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques", - "version": "5.0.7", - "contextFileName": "GEMINI.md" -} diff --git a/resources/skills/superpowers/hooks/hooks-cursor.json b/resources/skills/superpowers/hooks/hooks-cursor.json deleted file mode 100644 index 6df4461..0000000 --- a/resources/skills/superpowers/hooks/hooks-cursor.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": 1, - "hooks": { - "sessionStart": [ - { - "command": "./hooks/session-start" - } - ] - } -} diff --git a/resources/skills/superpowers/hooks/hooks.json b/resources/skills/superpowers/hooks/hooks.json deleted file mode 100644 index 79d8cee..0000000 --- a/resources/skills/superpowers/hooks/hooks.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "hooks": { - "SessionStart": [ - { - "matcher": "startup|clear|compact", - "hooks": [ - { - "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start", - "async": false - } - ] - } - ] - } -} diff --git a/resources/skills/superpowers/hooks/run-hook.cmd b/resources/skills/superpowers/hooks/run-hook.cmd deleted file mode 100644 index ceec3a7..0000000 --- a/resources/skills/superpowers/hooks/run-hook.cmd +++ /dev/null @@ -1,46 +0,0 @@ -: << 'CMDBLOCK' -@echo off -REM Cross-platform polyglot wrapper for hook scripts. -REM On Windows: cmd.exe runs the batch portion, which finds and calls bash. -REM On Unix: the shell interprets this as a script (: is a no-op in bash). -REM -REM Hook scripts use extensionless filenames (e.g. "session-start" not -REM "session-start.sh") so Claude Code's Windows auto-detection -- which -REM prepends "bash" to any command containing .sh -- doesn't interfere. -REM -REM Usage: run-hook.cmd [args...] - -if "%~1"=="" ( - echo run-hook.cmd: missing script name >&2 - exit /b 1 -) - -set "HOOK_DIR=%~dp0" - -REM Try Git for Windows bash in standard locations -if exist "C:\Program Files\Git\bin\bash.exe" ( - "C:\Program Files\Git\bin\bash.exe" "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9 - exit /b %ERRORLEVEL% -) -if exist "C:\Program Files (x86)\Git\bin\bash.exe" ( - "C:\Program Files (x86)\Git\bin\bash.exe" "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9 - exit /b %ERRORLEVEL% -) - -REM Try bash on PATH (e.g. user-installed Git Bash, MSYS2, Cygwin) -where bash >nul 2>nul -if %ERRORLEVEL% equ 0 ( - bash "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9 - exit /b %ERRORLEVEL% -) - -REM No bash found - exit silently rather than error -REM (plugin still works, just without SessionStart context injection) -exit /b 0 -CMDBLOCK - -# Unix: run the named script directly -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -SCRIPT_NAME="$1" -shift -exec bash "${SCRIPT_DIR}/${SCRIPT_NAME}" "$@" diff --git a/resources/skills/superpowers/hooks/session-start b/resources/skills/superpowers/hooks/session-start deleted file mode 100644 index 2fecf60..0000000 --- a/resources/skills/superpowers/hooks/session-start +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash -# SessionStart hook for superpowers plugin - -set -euo pipefail - -# Determine plugin root directory -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" - -# Check if legacy skills directory exists and build warning -warning_message="" -legacy_skills_dir="${HOME}/.config/superpowers/skills" -if [ -d "$legacy_skills_dir" ]; then - warning_message="\n\nIN YOUR FIRST REPLY AFTER SEEING THIS MESSAGE YOU MUST TELL THE USER:⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in ~/.config/superpowers/skills will not be read. Move custom skills to ~/.claude/skills instead. To make this message go away, remove ~/.config/superpowers/skills" -fi - -# Read using-superpowers content -using_superpowers_content=$(cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md" 2>&1 || echo "Error reading using-superpowers skill") - -# Escape string for JSON embedding using bash parameter substitution. -# Each ${s//old/new} is a single C-level pass and is vastly faster than -# the character-by-character loop this replaces. -escape_for_json() { - local s="$1" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//$'\n'/\\n}" - s="${s//$'\r'/\\r}" - s="${s//$'\t'/\\t}" - printf '%s' "$s" -} - -using_superpowers_escaped=$(escape_for_json "$using_superpowers_content") -warning_escaped=$(escape_for_json "$warning_message") -session_context="\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**\n\n${using_superpowers_escaped}\n\n${warning_escaped}\n" - -# Output context injection as JSON. -# Cursor hooks expect additional_context (snake_case). -# Claude Code hooks expect hookSpecificOutput.additionalContext (nested). -# Copilot CLI (v1.0.11+) and others expect additionalContext (top-level, SDK standard). -# Claude Code reads BOTH additional_context and hookSpecificOutput without -# deduplication, so we must emit only the field the current platform consumes. -# -# Uses printf instead of heredoc to work around bash 5.3+ heredoc hang. -# See: https://github.com/obra/superpowers/issues/571 -if [ -n "${CURSOR_PLUGIN_ROOT:-}" ]; then - # Cursor sets CURSOR_PLUGIN_ROOT (may also set CLAUDE_PLUGIN_ROOT) - printf '{\n "additional_context": "%s"\n}\n' "$session_context" -elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -z "${COPILOT_CLI:-}" ]; then - # Claude Code sets CLAUDE_PLUGIN_ROOT without COPILOT_CLI - printf '{\n "hookSpecificOutput": {\n "hookEventName": "SessionStart",\n "additionalContext": "%s"\n }\n}\n' "$session_context" -else - # Copilot CLI (sets COPILOT_CLI=1) or unknown platform — SDK standard format - printf '{\n "additionalContext": "%s"\n}\n' "$session_context" -fi - -exit 0 diff --git a/resources/skills/superpowers/package.json b/resources/skills/superpowers/package.json deleted file mode 100644 index 04f22b4..0000000 --- a/resources/skills/superpowers/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "superpowers", - "version": "5.0.7", - "type": "module", - "main": ".opencode/plugins/superpowers.js" -} diff --git a/resources/skills/superpowers/scripts/bump-version.sh b/resources/skills/superpowers/scripts/bump-version.sh deleted file mode 100644 index 01adec9..0000000 --- a/resources/skills/superpowers/scripts/bump-version.sh +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env bash -# -# bump-version.sh — bump version numbers across all declared files, -# with drift detection and repo-wide audit for missed files. -# -# Usage: -# bump-version.sh Bump all declared files to new version -# bump-version.sh --check Report current versions (detect drift) -# bump-version.sh --audit Check + grep repo for old version strings -# -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -CONFIG="$REPO_ROOT/.version-bump.json" - -if [[ ! -f "$CONFIG" ]]; then - echo "error: .version-bump.json not found at $CONFIG" >&2 - exit 1 -fi - -# --- helpers --- - -# Read a dotted field path from a JSON file. -# Handles both simple ("version") and nested ("plugins.0.version") paths. -read_json_field() { - local file="$1" field="$2" - # Convert dot-path to jq path: "plugins.0.version" -> .plugins[0].version - local jq_path - jq_path=$(echo "$field" | sed -E 's/\.([0-9]+)/[\1]/g' | sed 's/^/./' | sed 's/\.\././g') - jq -r "$jq_path" "$file" -} - -# Write a dotted field path in a JSON file, preserving formatting. -write_json_field() { - local file="$1" field="$2" value="$3" - local jq_path - jq_path=$(echo "$field" | sed -E 's/\.([0-9]+)/[\1]/g' | sed 's/^/./' | sed 's/\.\././g') - local tmp="${file}.tmp" - jq "$jq_path = \"$value\"" "$file" > "$tmp" && mv "$tmp" "$file" -} - -# Read the list of declared files from config. -# Outputs lines of "pathfield" -declared_files() { - jq -r '.files[] | "\(.path)\t\(.field)"' "$CONFIG" -} - -# Read the audit exclude patterns from config. -audit_excludes() { - jq -r '.audit.exclude[]' "$CONFIG" 2>/dev/null -} - -# --- commands --- - -cmd_check() { - local has_drift=0 - local versions=() - - echo "Version check:" - echo "" - - while IFS=$'\t' read -r path field; do - local fullpath="$REPO_ROOT/$path" - if [[ ! -f "$fullpath" ]]; then - printf " %-45s MISSING\n" "$path ($field)" - has_drift=1 - continue - fi - local ver - ver=$(read_json_field "$fullpath" "$field") - printf " %-45s %s\n" "$path ($field)" "$ver" - versions+=("$ver") - done < <(declared_files) - - echo "" - - # Check if all versions match - local unique - unique=$(printf '%s\n' "${versions[@]}" | sort -u | wc -l | tr -d ' ') - if [[ "$unique" -gt 1 ]]; then - echo "DRIFT DETECTED — versions are not in sync:" - printf '%s\n' "${versions[@]}" | sort | uniq -c | sort -rn | while read -r count ver; do - echo " $ver ($count files)" - done - has_drift=1 - else - echo "All declared files are in sync at ${versions[0]}" - fi - - return $has_drift -} - -cmd_audit() { - # First run check - cmd_check || true - echo "" - - # Determine the current version (most common across declared files) - local current_version - current_version=$( - while IFS=$'\t' read -r path field; do - local fullpath="$REPO_ROOT/$path" - [[ -f "$fullpath" ]] && read_json_field "$fullpath" "$field" - done < <(declared_files) | sort | uniq -c | sort -rn | head -1 | awk '{print $2}' - ) - - if [[ -z "$current_version" ]]; then - echo "error: could not determine current version" >&2 - return 1 - fi - - echo "Audit: scanning repo for version string '$current_version'..." - echo "" - - # Build grep exclude args - local -a exclude_args=() - while IFS= read -r pattern; do - exclude_args+=("--exclude=$pattern" "--exclude-dir=$pattern") - done < <(audit_excludes) - - # Also always exclude binary files and .git - exclude_args+=("--exclude-dir=.git" "--exclude-dir=node_modules" "--binary-files=without-match") - - # Get list of declared paths for comparison - local -a declared_paths=() - while IFS=$'\t' read -r path _field; do - declared_paths+=("$path") - done < <(declared_files) - - # Grep for the version string - local found_undeclared=0 - while IFS= read -r match; do - local match_file - match_file=$(echo "$match" | cut -d: -f1) - # Make path relative to repo root - local rel_path="${match_file#$REPO_ROOT/}" - - # Check if this file is in the declared list - local is_declared=0 - for dp in "${declared_paths[@]}"; do - if [[ "$rel_path" == "$dp" ]]; then - is_declared=1 - break - fi - done - - if [[ "$is_declared" -eq 0 ]]; then - if [[ "$found_undeclared" -eq 0 ]]; then - echo "UNDECLARED files containing '$current_version':" - found_undeclared=1 - fi - echo " $match" - fi - done < <(grep -rn "${exclude_args[@]}" -F "$current_version" "$REPO_ROOT" 2>/dev/null || true) - - if [[ "$found_undeclared" -eq 0 ]]; then - echo "No undeclared files contain the version string. All clear." - else - echo "" - echo "Review the above files — if they should be bumped, add them to .version-bump.json" - echo "If they should be skipped, add them to the audit.exclude list." - fi -} - -cmd_bump() { - local new_version="$1" - - # Validate semver-ish format - if ! echo "$new_version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then - echo "error: '$new_version' doesn't look like a version (expected X.Y.Z)" >&2 - exit 1 - fi - - echo "Bumping all declared files to $new_version..." - echo "" - - while IFS=$'\t' read -r path field; do - local fullpath="$REPO_ROOT/$path" - if [[ ! -f "$fullpath" ]]; then - echo " SKIP (missing): $path" - continue - fi - local old_ver - old_ver=$(read_json_field "$fullpath" "$field") - write_json_field "$fullpath" "$field" "$new_version" - printf " %-45s %s -> %s\n" "$path ($field)" "$old_ver" "$new_version" - done < <(declared_files) - - echo "" - echo "Done. Running audit to check for missed files..." - echo "" - cmd_audit -} - -# --- main --- - -case "${1:-}" in - --check) - cmd_check - ;; - --audit) - cmd_audit - ;; - --help|-h|"") - echo "Usage: bump-version.sh | --check | --audit" - echo "" - echo " Bump all declared files to the given version" - echo " --check Show current versions, detect drift" - echo " --audit Check + scan repo for undeclared version references" - exit 0 - ;; - --*) - echo "error: unknown flag '$1'" >&2 - exit 1 - ;; - *) - cmd_bump "$1" - ;; -esac diff --git a/resources/skills/superpowers/skills/brainstorming/SKILL.md b/resources/skills/superpowers/skills/brainstorming/SKILL.md deleted file mode 100644 index 06cd0a2..0000000 --- a/resources/skills/superpowers/skills/brainstorming/SKILL.md +++ /dev/null @@ -1,164 +0,0 @@ ---- -name: brainstorming -description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation." ---- - -# Brainstorming Ideas Into Designs - -Help turn ideas into fully formed designs and specs through natural collaborative dialogue. - -Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design and get user approval. - - -Do NOT invoke any implementation skill, write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it. This applies to EVERY project regardless of perceived simplicity. - - -## Anti-Pattern: "This Is Too Simple To Need A Design" - -Every project goes through this process. A todo list, a single-function utility, a config change — all of them. "Simple" projects are where unexamined assumptions cause the most wasted work. The design can be short (a few sentences for truly simple projects), but you MUST present it and get approval. - -## Checklist - -You MUST create a task for each of these items and complete them in order: - -1. **Explore project context** — check files, docs, recent commits -2. **Offer visual companion** (if topic will involve visual questions) — this is its own message, not combined with a clarifying question. See the Visual Companion section below. -3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria -4. **Propose 2-3 approaches** — with trade-offs and your recommendation -5. **Present design** — in sections scaled to their complexity, get user approval after each section -6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD--design.md` and commit -7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below) -8. **User reviews written spec** — ask user to review the spec file before proceeding -9. **Transition to implementation** — invoke writing-plans skill to create implementation plan - -## Process Flow - -```dot -digraph brainstorming { - "Explore project context" [shape=box]; - "Visual questions ahead?" [shape=diamond]; - "Offer Visual Companion\n(own message, no other content)" [shape=box]; - "Ask clarifying questions" [shape=box]; - "Propose 2-3 approaches" [shape=box]; - "Present design sections" [shape=box]; - "User approves design?" [shape=diamond]; - "Write design doc" [shape=box]; - "Spec self-review\n(fix inline)" [shape=box]; - "User reviews spec?" [shape=diamond]; - "Invoke writing-plans skill" [shape=doublecircle]; - - "Explore project context" -> "Visual questions ahead?"; - "Visual questions ahead?" -> "Offer Visual Companion\n(own message, no other content)" [label="yes"]; - "Visual questions ahead?" -> "Ask clarifying questions" [label="no"]; - "Offer Visual Companion\n(own message, no other content)" -> "Ask clarifying questions"; - "Ask clarifying questions" -> "Propose 2-3 approaches"; - "Propose 2-3 approaches" -> "Present design sections"; - "Present design sections" -> "User approves design?"; - "User approves design?" -> "Present design sections" [label="no, revise"]; - "User approves design?" -> "Write design doc" [label="yes"]; - "Write design doc" -> "Spec self-review\n(fix inline)"; - "Spec self-review\n(fix inline)" -> "User reviews spec?"; - "User reviews spec?" -> "Write design doc" [label="changes requested"]; - "User reviews spec?" -> "Invoke writing-plans skill" [label="approved"]; -} -``` - -**The terminal state is invoking writing-plans.** Do NOT invoke frontend-design, mcp-builder, or any other implementation skill. The ONLY skill you invoke after brainstorming is writing-plans. - -## The Process - -**Understanding the idea:** - -- Check out the current project state first (files, docs, recent commits) -- Before asking detailed questions, assess scope: if the request describes multiple independent subsystems (e.g., "build a platform with chat, file storage, billing, and analytics"), flag this immediately. Don't spend questions refining details of a project that needs to be decomposed first. -- If the project is too large for a single spec, help the user decompose into sub-projects: what are the independent pieces, how do they relate, what order should they be built? Then brainstorm the first sub-project through the normal design flow. Each sub-project gets its own spec → plan → implementation cycle. -- For appropriately-scoped projects, ask questions one at a time to refine the idea -- Prefer multiple choice questions when possible, but open-ended is fine too -- Only one question per message - if a topic needs more exploration, break it into multiple questions -- Focus on understanding: purpose, constraints, success criteria - -**Exploring approaches:** - -- Propose 2-3 different approaches with trade-offs -- Present options conversationally with your recommendation and reasoning -- Lead with your recommended option and explain why - -**Presenting the design:** - -- Once you believe you understand what you're building, present the design -- Scale each section to its complexity: a few sentences if straightforward, up to 200-300 words if nuanced -- Ask after each section whether it looks right so far -- Cover: architecture, components, data flow, error handling, testing -- Be ready to go back and clarify if something doesn't make sense - -**Design for isolation and clarity:** - -- Break the system into smaller units that each have one clear purpose, communicate through well-defined interfaces, and can be understood and tested independently -- For each unit, you should be able to answer: what does it do, how do you use it, and what does it depend on? -- Can someone understand what a unit does without reading its internals? Can you change the internals without breaking consumers? If not, the boundaries need work. -- Smaller, well-bounded units are also easier for you to work with - you reason better about code you can hold in context at once, and your edits are more reliable when files are focused. When a file grows large, that's often a signal that it's doing too much. - -**Working in existing codebases:** - -- Explore the current structure before proposing changes. Follow existing patterns. -- Where existing code has problems that affect the work (e.g., a file that's grown too large, unclear boundaries, tangled responsibilities), include targeted improvements as part of the design - the way a good developer improves code they're working in. -- Don't propose unrelated refactoring. Stay focused on what serves the current goal. - -## After the Design - -**Documentation:** - -- Write the validated design (spec) to `docs/superpowers/specs/YYYY-MM-DD--design.md` - - (User preferences for spec location override this default) -- Use elements-of-style:writing-clearly-and-concisely skill if available -- Commit the design document to git - -**Spec Self-Review:** -After writing the spec document, look at it with fresh eyes: - -1. **Placeholder scan:** Any "TBD", "TODO", incomplete sections, or vague requirements? Fix them. -2. **Internal consistency:** Do any sections contradict each other? Does the architecture match the feature descriptions? -3. **Scope check:** Is this focused enough for a single implementation plan, or does it need decomposition? -4. **Ambiguity check:** Could any requirement be interpreted two different ways? If so, pick one and make it explicit. - -Fix any issues inline. No need to re-review — just fix and move on. - -**User Review Gate:** -After the spec review loop passes, ask the user to review the written spec before proceeding: - -> "Spec written and committed to ``. Please review it and let me know if you want to make any changes before we start writing out the implementation plan." - -Wait for the user's response. If they request changes, make them and re-run the spec review loop. Only proceed once the user approves. - -**Implementation:** - -- Invoke the writing-plans skill to create a detailed implementation plan -- Do NOT invoke any other skill. writing-plans is the next step. - -## Key Principles - -- **One question at a time** - Don't overwhelm with multiple questions -- **Multiple choice preferred** - Easier to answer than open-ended when possible -- **YAGNI ruthlessly** - Remove unnecessary features from all designs -- **Explore alternatives** - Always propose 2-3 approaches before settling -- **Incremental validation** - Present design, get approval before moving on -- **Be flexible** - Go back and clarify when something doesn't make sense - -## Visual Companion - -A browser-based companion for showing mockups, diagrams, and visual options during brainstorming. Available as a tool — not a mode. Accepting the companion means it's available for questions that benefit from visual treatment; it does NOT mean every question goes through the browser. - -**Offering the companion:** When you anticipate that upcoming questions will involve visual content (mockups, layouts, diagrams), offer it once for consent: -> "Some of what we're working on might be easier to explain if I can show it to you in a web browser. I can put together mockups, diagrams, comparisons, and other visuals as we go. This feature is still new and can be token-intensive. Want to try it? (Requires opening a local URL)" - -**This offer MUST be its own message.** Do not combine it with clarifying questions, context summaries, or any other content. The message should contain ONLY the offer above and nothing else. Wait for the user's response before continuing. If they decline, proceed with text-only brainstorming. - -**Per-question decision:** Even after the user accepts, decide FOR EACH QUESTION whether to use the browser or the terminal. The test: **would the user understand this better by seeing it than reading it?** - -- **Use the browser** for content that IS visual — mockups, wireframes, layout comparisons, architecture diagrams, side-by-side visual designs -- **Use the terminal** for content that is text — requirements questions, conceptual choices, tradeoff lists, A/B/C/D text options, scope decisions - -A question about a UI topic is not automatically a visual question. "What does personality mean in this context?" is a conceptual question — use the terminal. "Which wizard layout works better?" is a visual question — use the browser. - -If they agree to the companion, read the detailed guide before proceeding: -`skills/brainstorming/visual-companion.md` diff --git a/resources/skills/superpowers/skills/brainstorming/scripts/frame-template.html b/resources/skills/superpowers/skills/brainstorming/scripts/frame-template.html deleted file mode 100644 index dcfe018..0000000 --- a/resources/skills/superpowers/skills/brainstorming/scripts/frame-template.html +++ /dev/null @@ -1,214 +0,0 @@ - - - - - Superpowers Brainstorming - - - -
-

Superpowers Brainstorming

-
Connected
-
- -
-
- -
-
- -
- Click an option above, then return to the terminal -
- - - diff --git a/resources/skills/superpowers/skills/brainstorming/scripts/helper.js b/resources/skills/superpowers/skills/brainstorming/scripts/helper.js deleted file mode 100644 index 111f97f..0000000 --- a/resources/skills/superpowers/skills/brainstorming/scripts/helper.js +++ /dev/null @@ -1,88 +0,0 @@ -(function() { - const WS_URL = 'ws://' + window.location.host; - let ws = null; - let eventQueue = []; - - function connect() { - ws = new WebSocket(WS_URL); - - ws.onopen = () => { - eventQueue.forEach(e => ws.send(JSON.stringify(e))); - eventQueue = []; - }; - - ws.onmessage = (msg) => { - const data = JSON.parse(msg.data); - if (data.type === 'reload') { - window.location.reload(); - } - }; - - ws.onclose = () => { - setTimeout(connect, 1000); - }; - } - - function sendEvent(event) { - event.timestamp = Date.now(); - if (ws && ws.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify(event)); - } else { - eventQueue.push(event); - } - } - - // Capture clicks on choice elements - document.addEventListener('click', (e) => { - const target = e.target.closest('[data-choice]'); - if (!target) return; - - sendEvent({ - type: 'click', - text: target.textContent.trim(), - choice: target.dataset.choice, - id: target.id || null - }); - - // Update indicator bar (defer so toggleSelect runs first) - setTimeout(() => { - const indicator = document.getElementById('indicator-text'); - if (!indicator) return; - const container = target.closest('.options') || target.closest('.cards'); - const selected = container ? container.querySelectorAll('.selected') : []; - if (selected.length === 0) { - indicator.textContent = 'Click an option above, then return to the terminal'; - } else if (selected.length === 1) { - const label = selected[0].querySelector('h3, .content h3, .card-body h3')?.textContent?.trim() || selected[0].dataset.choice; - indicator.innerHTML = '' + label + ' selected — return to terminal to continue'; - } else { - indicator.innerHTML = '' + selected.length + ' selected — return to terminal to continue'; - } - }, 0); - }); - - // Frame UI: selection tracking - window.selectedChoice = null; - - window.toggleSelect = function(el) { - const container = el.closest('.options') || el.closest('.cards'); - const multi = container && container.dataset.multiselect !== undefined; - if (container && !multi) { - container.querySelectorAll('.option, .card').forEach(o => o.classList.remove('selected')); - } - if (multi) { - el.classList.toggle('selected'); - } else { - el.classList.add('selected'); - } - window.selectedChoice = el.dataset.choice; - }; - - // Expose API for explicit use - window.brainstorm = { - send: sendEvent, - choice: (value, metadata = {}) => sendEvent({ type: 'choice', value, ...metadata }) - }; - - connect(); -})(); diff --git a/resources/skills/superpowers/skills/brainstorming/scripts/server.cjs b/resources/skills/superpowers/skills/brainstorming/scripts/server.cjs deleted file mode 100644 index 562c17f..0000000 --- a/resources/skills/superpowers/skills/brainstorming/scripts/server.cjs +++ /dev/null @@ -1,354 +0,0 @@ -const crypto = require('crypto'); -const http = require('http'); -const fs = require('fs'); -const path = require('path'); - -// ========== WebSocket Protocol (RFC 6455) ========== - -const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A }; -const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; - -function computeAcceptKey(clientKey) { - return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64'); -} - -function encodeFrame(opcode, payload) { - const fin = 0x80; - const len = payload.length; - let header; - - if (len < 126) { - header = Buffer.alloc(2); - header[0] = fin | opcode; - header[1] = len; - } else if (len < 65536) { - header = Buffer.alloc(4); - header[0] = fin | opcode; - header[1] = 126; - header.writeUInt16BE(len, 2); - } else { - header = Buffer.alloc(10); - header[0] = fin | opcode; - header[1] = 127; - header.writeBigUInt64BE(BigInt(len), 2); - } - - return Buffer.concat([header, payload]); -} - -function decodeFrame(buffer) { - if (buffer.length < 2) return null; - - const secondByte = buffer[1]; - const opcode = buffer[0] & 0x0F; - const masked = (secondByte & 0x80) !== 0; - let payloadLen = secondByte & 0x7F; - let offset = 2; - - if (!masked) throw new Error('Client frames must be masked'); - - if (payloadLen === 126) { - if (buffer.length < 4) return null; - payloadLen = buffer.readUInt16BE(2); - offset = 4; - } else if (payloadLen === 127) { - if (buffer.length < 10) return null; - payloadLen = Number(buffer.readBigUInt64BE(2)); - offset = 10; - } - - const maskOffset = offset; - const dataOffset = offset + 4; - const totalLen = dataOffset + payloadLen; - if (buffer.length < totalLen) return null; - - const mask = buffer.slice(maskOffset, dataOffset); - const data = Buffer.alloc(payloadLen); - for (let i = 0; i < payloadLen; i++) { - data[i] = buffer[dataOffset + i] ^ mask[i % 4]; - } - - return { opcode, payload: data, bytesConsumed: totalLen }; -} - -// ========== Configuration ========== - -const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383)); -const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1'; -const URL_HOST = process.env.BRAINSTORM_URL_HOST || (HOST === '127.0.0.1' ? 'localhost' : HOST); -const SESSION_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm'; -const CONTENT_DIR = path.join(SESSION_DIR, 'content'); -const STATE_DIR = path.join(SESSION_DIR, 'state'); -let ownerPid = process.env.BRAINSTORM_OWNER_PID ? Number(process.env.BRAINSTORM_OWNER_PID) : null; - -const MIME_TYPES = { - '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript', - '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', - '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml' -}; - -// ========== Templates and Constants ========== - -const WAITING_PAGE = ` - -Brainstorm Companion - - -

Brainstorm Companion

-

Waiting for the agent to push a screen...

`; - -const frameTemplate = fs.readFileSync(path.join(__dirname, 'frame-template.html'), 'utf-8'); -const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8'); -const helperInjection = ''; - -// ========== Helper Functions ========== - -function isFullDocument(html) { - const trimmed = html.trimStart().toLowerCase(); - return trimmed.startsWith('', content); -} - -function getNewestScreen() { - const files = fs.readdirSync(CONTENT_DIR) - .filter(f => f.endsWith('.html')) - .map(f => { - const fp = path.join(CONTENT_DIR, f); - return { path: fp, mtime: fs.statSync(fp).mtime.getTime() }; - }) - .sort((a, b) => b.mtime - a.mtime); - return files.length > 0 ? files[0].path : null; -} - -// ========== HTTP Request Handler ========== - -function handleRequest(req, res) { - touchActivity(); - if (req.method === 'GET' && req.url === '/') { - const screenFile = getNewestScreen(); - let html = screenFile - ? (raw => isFullDocument(raw) ? raw : wrapInFrame(raw))(fs.readFileSync(screenFile, 'utf-8')) - : WAITING_PAGE; - - if (html.includes('')) { - html = html.replace('', helperInjection + '\n'); - } else { - html += helperInjection; - } - - res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); - res.end(html); - } else if (req.method === 'GET' && req.url.startsWith('/files/')) { - const fileName = req.url.slice(7); - const filePath = path.join(CONTENT_DIR, path.basename(fileName)); - if (!fs.existsSync(filePath)) { - res.writeHead(404); - res.end('Not found'); - return; - } - const ext = path.extname(filePath).toLowerCase(); - const contentType = MIME_TYPES[ext] || 'application/octet-stream'; - res.writeHead(200, { 'Content-Type': contentType }); - res.end(fs.readFileSync(filePath)); - } else { - res.writeHead(404); - res.end('Not found'); - } -} - -// ========== WebSocket Connection Handling ========== - -const clients = new Set(); - -function handleUpgrade(req, socket) { - const key = req.headers['sec-websocket-key']; - if (!key) { socket.destroy(); return; } - - const accept = computeAcceptKey(key); - socket.write( - 'HTTP/1.1 101 Switching Protocols\r\n' + - 'Upgrade: websocket\r\n' + - 'Connection: Upgrade\r\n' + - 'Sec-WebSocket-Accept: ' + accept + '\r\n\r\n' - ); - - let buffer = Buffer.alloc(0); - clients.add(socket); - - socket.on('data', (chunk) => { - buffer = Buffer.concat([buffer, chunk]); - while (buffer.length > 0) { - let result; - try { - result = decodeFrame(buffer); - } catch (e) { - socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0))); - clients.delete(socket); - return; - } - if (!result) break; - buffer = buffer.slice(result.bytesConsumed); - - switch (result.opcode) { - case OPCODES.TEXT: - handleMessage(result.payload.toString()); - break; - case OPCODES.CLOSE: - socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0))); - clients.delete(socket); - return; - case OPCODES.PING: - socket.write(encodeFrame(OPCODES.PONG, result.payload)); - break; - case OPCODES.PONG: - break; - default: { - const closeBuf = Buffer.alloc(2); - closeBuf.writeUInt16BE(1003); - socket.end(encodeFrame(OPCODES.CLOSE, closeBuf)); - clients.delete(socket); - return; - } - } - } - }); - - socket.on('close', () => clients.delete(socket)); - socket.on('error', () => clients.delete(socket)); -} - -function handleMessage(text) { - let event; - try { - event = JSON.parse(text); - } catch (e) { - console.error('Failed to parse WebSocket message:', e.message); - return; - } - touchActivity(); - console.log(JSON.stringify({ source: 'user-event', ...event })); - if (event.choice) { - const eventsFile = path.join(STATE_DIR, 'events'); - fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n'); - } -} - -function broadcast(msg) { - const frame = encodeFrame(OPCODES.TEXT, Buffer.from(JSON.stringify(msg))); - for (const socket of clients) { - try { socket.write(frame); } catch (e) { clients.delete(socket); } - } -} - -// ========== Activity Tracking ========== - -const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes -let lastActivity = Date.now(); - -function touchActivity() { - lastActivity = Date.now(); -} - -// ========== File Watching ========== - -const debounceTimers = new Map(); - -// ========== Server Startup ========== - -function startServer() { - if (!fs.existsSync(CONTENT_DIR)) fs.mkdirSync(CONTENT_DIR, { recursive: true }); - if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true }); - - // Track known files to distinguish new screens from updates. - // macOS fs.watch reports 'rename' for both new files and overwrites, - // so we can't rely on eventType alone. - const knownFiles = new Set( - fs.readdirSync(CONTENT_DIR).filter(f => f.endsWith('.html')) - ); - - const server = http.createServer(handleRequest); - server.on('upgrade', handleUpgrade); - - const watcher = fs.watch(CONTENT_DIR, (eventType, filename) => { - if (!filename || !filename.endsWith('.html')) return; - - if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename)); - debounceTimers.set(filename, setTimeout(() => { - debounceTimers.delete(filename); - const filePath = path.join(CONTENT_DIR, filename); - - if (!fs.existsSync(filePath)) return; // file was deleted - touchActivity(); - - if (!knownFiles.has(filename)) { - knownFiles.add(filename); - const eventsFile = path.join(STATE_DIR, 'events'); - if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile); - console.log(JSON.stringify({ type: 'screen-added', file: filePath })); - } else { - console.log(JSON.stringify({ type: 'screen-updated', file: filePath })); - } - - broadcast({ type: 'reload' }); - }, 100)); - }); - watcher.on('error', (err) => console.error('fs.watch error:', err.message)); - - function shutdown(reason) { - console.log(JSON.stringify({ type: 'server-stopped', reason })); - const infoFile = path.join(STATE_DIR, 'server-info'); - if (fs.existsSync(infoFile)) fs.unlinkSync(infoFile); - fs.writeFileSync( - path.join(STATE_DIR, 'server-stopped'), - JSON.stringify({ reason, timestamp: Date.now() }) + '\n' - ); - watcher.close(); - clearInterval(lifecycleCheck); - server.close(() => process.exit(0)); - } - - function ownerAlive() { - if (!ownerPid) return true; - try { process.kill(ownerPid, 0); return true; } catch (e) { return e.code === 'EPERM'; } - } - - // Check every 60s: exit if owner process died or idle for 30 minutes - const lifecycleCheck = setInterval(() => { - if (!ownerAlive()) shutdown('owner process exited'); - else if (Date.now() - lastActivity > IDLE_TIMEOUT_MS) shutdown('idle timeout'); - }, 60 * 1000); - lifecycleCheck.unref(); - - // Validate owner PID at startup. If it's already dead, the PID resolution - // was wrong (common on WSL, Tailscale SSH, and cross-user scenarios). - // Disable monitoring and rely on the idle timeout instead. - if (ownerPid) { - try { process.kill(ownerPid, 0); } - catch (e) { - if (e.code !== 'EPERM') { - console.log(JSON.stringify({ type: 'owner-pid-invalid', pid: ownerPid, reason: 'dead at startup' })); - ownerPid = null; - } - } - } - - server.listen(PORT, HOST, () => { - const info = JSON.stringify({ - type: 'server-started', port: Number(PORT), host: HOST, - url_host: URL_HOST, url: 'http://' + URL_HOST + ':' + PORT, - screen_dir: CONTENT_DIR, state_dir: STATE_DIR - }); - console.log(info); - fs.writeFileSync(path.join(STATE_DIR, 'server-info'), info + '\n'); - }); -} - -if (require.main === module) { - startServer(); -} - -module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES }; diff --git a/resources/skills/superpowers/skills/brainstorming/scripts/start-server.sh b/resources/skills/superpowers/skills/brainstorming/scripts/start-server.sh deleted file mode 100644 index 9ef6dcb..0000000 --- a/resources/skills/superpowers/skills/brainstorming/scripts/start-server.sh +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env bash -# Start the brainstorm server and output connection info -# Usage: start-server.sh [--project-dir ] [--host ] [--url-host ] [--foreground] [--background] -# -# Starts server on a random high port, outputs JSON with URL. -# Each session gets its own directory to avoid conflicts. -# -# Options: -# --project-dir Store session files under /.superpowers/brainstorm/ -# instead of /tmp. Files persist after server stops. -# --host Host/interface to bind (default: 127.0.0.1). -# Use 0.0.0.0 in remote/containerized environments. -# --url-host Hostname shown in returned URL JSON. -# --foreground Run server in the current terminal (no backgrounding). -# --background Force background mode (overrides Codex auto-foreground). - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - -# Parse arguments -PROJECT_DIR="" -FOREGROUND="false" -FORCE_BACKGROUND="false" -BIND_HOST="127.0.0.1" -URL_HOST="" -while [[ $# -gt 0 ]]; do - case "$1" in - --project-dir) - PROJECT_DIR="$2" - shift 2 - ;; - --host) - BIND_HOST="$2" - shift 2 - ;; - --url-host) - URL_HOST="$2" - shift 2 - ;; - --foreground|--no-daemon) - FOREGROUND="true" - shift - ;; - --background|--daemon) - FORCE_BACKGROUND="true" - shift - ;; - *) - echo "{\"error\": \"Unknown argument: $1\"}" - exit 1 - ;; - esac -done - -if [[ -z "$URL_HOST" ]]; then - if [[ "$BIND_HOST" == "127.0.0.1" || "$BIND_HOST" == "localhost" ]]; then - URL_HOST="localhost" - else - URL_HOST="$BIND_HOST" - fi -fi - -# Some environments reap detached/background processes. Auto-foreground when detected. -if [[ -n "${CODEX_CI:-}" && "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then - FOREGROUND="true" -fi - -# Windows/Git Bash reaps nohup background processes. Auto-foreground when detected. -if [[ "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then - case "${OSTYPE:-}" in - msys*|cygwin*|mingw*) FOREGROUND="true" ;; - esac - if [[ -n "${MSYSTEM:-}" ]]; then - FOREGROUND="true" - fi -fi - -# Generate unique session directory -SESSION_ID="$$-$(date +%s)" - -if [[ -n "$PROJECT_DIR" ]]; then - SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}" -else - SESSION_DIR="/tmp/brainstorm-${SESSION_ID}" -fi - -STATE_DIR="${SESSION_DIR}/state" -PID_FILE="${STATE_DIR}/server.pid" -LOG_FILE="${STATE_DIR}/server.log" - -# Create fresh session directory with content and state peers -mkdir -p "${SESSION_DIR}/content" "$STATE_DIR" - -# Kill any existing server -if [[ -f "$PID_FILE" ]]; then - old_pid=$(cat "$PID_FILE") - kill "$old_pid" 2>/dev/null - rm -f "$PID_FILE" -fi - -cd "$SCRIPT_DIR" - -# Resolve the harness PID (grandparent of this script). -# $PPID is the ephemeral shell the harness spawned to run us — it dies -# when this script exits. The harness itself is $PPID's parent. -OWNER_PID="$(ps -o ppid= -p "$PPID" 2>/dev/null | tr -d ' ')" -if [[ -z "$OWNER_PID" || "$OWNER_PID" == "1" ]]; then - OWNER_PID="$PPID" -fi - -# Foreground mode for environments that reap detached/background processes. -if [[ "$FOREGROUND" == "true" ]]; then - echo "$$" > "$PID_FILE" - env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs - exit $? -fi - -# Start server, capturing output to log file -# Use nohup to survive shell exit; disown to remove from job table -nohup env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs > "$LOG_FILE" 2>&1 & -SERVER_PID=$! -disown "$SERVER_PID" 2>/dev/null -echo "$SERVER_PID" > "$PID_FILE" - -# Wait for server-started message (check log file) -for i in {1..50}; do - if grep -q "server-started" "$LOG_FILE" 2>/dev/null; then - # Verify server is still alive after a short window (catches process reapers) - alive="true" - for _ in {1..20}; do - if ! kill -0 "$SERVER_PID" 2>/dev/null; then - alive="false" - break - fi - sleep 0.1 - done - if [[ "$alive" != "true" ]]; then - echo "{\"error\": \"Server started but was killed. Retry in a persistent terminal with: $SCRIPT_DIR/start-server.sh${PROJECT_DIR:+ --project-dir $PROJECT_DIR} --host $BIND_HOST --url-host $URL_HOST --foreground\"}" - exit 1 - fi - grep "server-started" "$LOG_FILE" | head -1 - exit 0 - fi - sleep 0.1 -done - -# Timeout - server didn't start -echo '{"error": "Server failed to start within 5 seconds"}' -exit 1 diff --git a/resources/skills/superpowers/skills/brainstorming/scripts/stop-server.sh b/resources/skills/superpowers/skills/brainstorming/scripts/stop-server.sh deleted file mode 100644 index a6b94e6..0000000 --- a/resources/skills/superpowers/skills/brainstorming/scripts/stop-server.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash -# Stop the brainstorm server and clean up -# Usage: stop-server.sh -# -# Kills the server process. Only deletes session directory if it's -# under /tmp (ephemeral). Persistent directories (.superpowers/) are -# kept so mockups can be reviewed later. - -SESSION_DIR="$1" - -if [[ -z "$SESSION_DIR" ]]; then - echo '{"error": "Usage: stop-server.sh "}' - exit 1 -fi - -STATE_DIR="${SESSION_DIR}/state" -PID_FILE="${STATE_DIR}/server.pid" - -if [[ -f "$PID_FILE" ]]; then - pid=$(cat "$PID_FILE") - - # Try to stop gracefully, fallback to force if still alive - kill "$pid" 2>/dev/null || true - - # Wait for graceful shutdown (up to ~2s) - for i in {1..20}; do - if ! kill -0 "$pid" 2>/dev/null; then - break - fi - sleep 0.1 - done - - # If still running, escalate to SIGKILL - if kill -0 "$pid" 2>/dev/null; then - kill -9 "$pid" 2>/dev/null || true - - # Give SIGKILL a moment to take effect - sleep 0.1 - fi - - if kill -0 "$pid" 2>/dev/null; then - echo '{"status": "failed", "error": "process still running"}' - exit 1 - fi - - rm -f "$PID_FILE" "${STATE_DIR}/server.log" - - # Only delete ephemeral /tmp directories - if [[ "$SESSION_DIR" == /tmp/* ]]; then - rm -rf "$SESSION_DIR" - fi - - echo '{"status": "stopped"}' -else - echo '{"status": "not_running"}' -fi diff --git a/resources/skills/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md b/resources/skills/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md deleted file mode 100644 index 35acbb6..0000000 --- a/resources/skills/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +++ /dev/null @@ -1,49 +0,0 @@ -# Spec Document Reviewer Prompt Template - -Use this template when dispatching a spec document reviewer subagent. - -**Purpose:** Verify the spec is complete, consistent, and ready for implementation planning. - -**Dispatch after:** Spec document is written to docs/superpowers/specs/ - -``` -Task tool (general-purpose): - description: "Review spec document" - prompt: | - You are a spec document reviewer. Verify this spec is complete and ready for planning. - - **Spec to review:** [SPEC_FILE_PATH] - - ## What to Check - - | Category | What to Look For | - |----------|------------------| - | Completeness | TODOs, placeholders, "TBD", incomplete sections | - | Consistency | Internal contradictions, conflicting requirements | - | Clarity | Requirements ambiguous enough to cause someone to build the wrong thing | - | Scope | Focused enough for a single plan — not covering multiple independent subsystems | - | YAGNI | Unrequested features, over-engineering | - - ## Calibration - - **Only flag issues that would cause real problems during implementation planning.** - A missing section, a contradiction, or a requirement so ambiguous it could be - interpreted two different ways — those are issues. Minor wording improvements, - stylistic preferences, and "sections less detailed than others" are not. - - Approve unless there are serious gaps that would lead to a flawed plan. - - ## Output Format - - ## Spec Review - - **Status:** Approved | Issues Found - - **Issues (if any):** - - [Section X]: [specific issue] - [why it matters for planning] - - **Recommendations (advisory, do not block approval):** - - [suggestions for improvement] -``` - -**Reviewer returns:** Status, Issues (if any), Recommendations diff --git a/resources/skills/superpowers/skills/brainstorming/visual-companion.md b/resources/skills/superpowers/skills/brainstorming/visual-companion.md deleted file mode 100644 index 2113863..0000000 --- a/resources/skills/superpowers/skills/brainstorming/visual-companion.md +++ /dev/null @@ -1,287 +0,0 @@ -# Visual Companion Guide - -Browser-based visual brainstorming companion for showing mockups, diagrams, and options. - -## When to Use - -Decide per-question, not per-session. The test: **would the user understand this better by seeing it than reading it?** - -**Use the browser** when the content itself is visual: - -- **UI mockups** — wireframes, layouts, navigation structures, component designs -- **Architecture diagrams** — system components, data flow, relationship maps -- **Side-by-side visual comparisons** — comparing two layouts, two color schemes, two design directions -- **Design polish** — when the question is about look and feel, spacing, visual hierarchy -- **Spatial relationships** — state machines, flowcharts, entity relationships rendered as diagrams - -**Use the terminal** when the content is text or tabular: - -- **Requirements and scope questions** — "what does X mean?", "which features are in scope?" -- **Conceptual A/B/C choices** — picking between approaches described in words -- **Tradeoff lists** — pros/cons, comparison tables -- **Technical decisions** — API design, data modeling, architectural approach selection -- **Clarifying questions** — anything where the answer is words, not a visual preference - -A question *about* a UI topic is not automatically a visual question. "What kind of wizard do you want?" is conceptual — use the terminal. "Which of these wizard layouts feels right?" is visual — use the browser. - -## How It Works - -The server watches a directory for HTML files and serves the newest one to the browser. You write HTML content to `screen_dir`, the user sees it in their browser and can click to select options. Selections are recorded to `state_dir/events` that you read on your next turn. - -**Content fragments vs full documents:** If your HTML file starts with `/.superpowers/brainstorm/` for the session directory. - -**Note:** Pass the project root as `--project-dir` so mockups persist in `.superpowers/brainstorm/` and survive server restarts. Without it, files go to `/tmp` and get cleaned up. Remind the user to add `.superpowers/` to `.gitignore` if it's not already there. - -**Launching the server by platform:** - -**Claude Code (macOS / Linux):** -```bash -# Default mode works — the script backgrounds the server itself -scripts/start-server.sh --project-dir /path/to/project -``` - -**Claude Code (Windows):** -```bash -# Windows auto-detects and uses foreground mode, which blocks the tool call. -# Use run_in_background: true on the Bash tool call so the server survives -# across conversation turns. -scripts/start-server.sh --project-dir /path/to/project -``` -When calling this via the Bash tool, set `run_in_background: true`. Then read `$STATE_DIR/server-info` on the next turn to get the URL and port. - -**Codex:** -```bash -# Codex reaps background processes. The script auto-detects CODEX_CI and -# switches to foreground mode. Run it normally — no extra flags needed. -scripts/start-server.sh --project-dir /path/to/project -``` - -**Gemini CLI:** -```bash -# Use --foreground and set is_background: true on your shell tool call -# so the process survives across turns -scripts/start-server.sh --project-dir /path/to/project --foreground -``` - -**Other environments:** The server must keep running in the background across conversation turns. If your environment reaps detached processes, use `--foreground` and launch the command with your platform's background execution mechanism. - -If the URL is unreachable from your browser (common in remote/containerized setups), bind a non-loopback host: - -```bash -scripts/start-server.sh \ - --project-dir /path/to/project \ - --host 0.0.0.0 \ - --url-host localhost -``` - -Use `--url-host` to control what hostname is printed in the returned URL JSON. - -## The Loop - -1. **Check server is alive**, then **write HTML** to a new file in `screen_dir`: - - Before each write, check that `$STATE_DIR/server-info` exists. If it doesn't (or `$STATE_DIR/server-stopped` exists), the server has shut down — restart it with `start-server.sh` before continuing. The server auto-exits after 30 minutes of inactivity. - - Use semantic filenames: `platform.html`, `visual-style.html`, `layout.html` - - **Never reuse filenames** — each screen gets a fresh file - - Use Write tool — **never use cat/heredoc** (dumps noise into terminal) - - Server automatically serves the newest file - -2. **Tell user what to expect and end your turn:** - - Remind them of the URL (every step, not just first) - - Give a brief text summary of what's on screen (e.g., "Showing 3 layout options for the homepage") - - Ask them to respond in the terminal: "Take a look and let me know what you think. Click to select an option if you'd like." - -3. **On your next turn** — after the user responds in the terminal: - - Read `$STATE_DIR/events` if it exists — this contains the user's browser interactions (clicks, selections) as JSON lines - - Merge with the user's terminal text to get the full picture - - The terminal message is the primary feedback; `state_dir/events` provides structured interaction data - -4. **Iterate or advance** — if feedback changes current screen, write a new file (e.g., `layout-v2.html`). Only move to the next question when the current step is validated. - -5. **Unload when returning to terminal** — when the next step doesn't need the browser (e.g., a clarifying question, a tradeoff discussion), push a waiting screen to clear the stale content: - - ```html - -
-

Continuing in terminal...

-
- ``` - - This prevents the user from staring at a resolved choice while the conversation has moved on. When the next visual question comes up, push a new content file as usual. - -6. Repeat until done. - -## Writing Content Fragments - -Write just the content that goes inside the page. The server wraps it in the frame template automatically (header, theme CSS, selection indicator, and all interactive infrastructure). - -**Minimal example:** - -```html -

Which layout works better?

-

Consider readability and visual hierarchy

- -
-
-
A
-
-

Single Column

-

Clean, focused reading experience

-
-
-
-
B
-
-

Two Column

-

Sidebar navigation with main content

-
-
-
-``` - -That's it. No ``, no CSS, no `