superpowers-mcp 6.3.0 → 6.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | [繁體中文](README.zh-TW.md) | [日本語](README.ja.md) | [한국어](README.ko.md)
4
4
 
5
- [![Version](https://img.shields.io/badge/version-6.3.0-blue.svg)](https://github.com/Poseidoncode/superpowers-mcp)
5
+ [![バージョン](https://img.shields.io/badge/version-6.3.1-blue.svg)](https://github.com/Poseidoncode/superpowers-mcp)
6
6
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
7
 
8
8
  このドキュメントは、オリジナルの Superpowers スキルライブラリを独立した MCP Toolpack にパッケージ化するための情報と使用手順をまとめたものです。
@@ -128,7 +128,17 @@
128
128
 
129
129
  ## 🆕 最近の更新
130
130
 
131
- ### v6.3.0(最新)
131
+ ### v6.3.1(最新)
132
+
133
+ - **SDD ワークスペース所有権マーカーと物理パス正規化**:`sdd-workspace`(Bash)と `sdd-workspace.ps1`(PowerShell)は `plan-path` マーカーと物理パス正規化(`pwd -P` および動的 `pwd` 検出)を使用。同名のプラン(`docs/alpha/plan.md` と `docs/beta/plan.md` など)が衝突せず個別の `.superpowers/sdd/` ワークスペースに分離されます。
134
+ - **SDD レビューパッケージ範囲ガード**:`review-package` および `review-package.ps1` は `git merge-base --is-ancestor BASE HEAD` とコミット数を検証し、空や逆転した範囲による誤判定(false-pass)を防止。
135
+ - **実行権限喪失への耐性**:`task-brief` と `review-package` は `"${BASH:-bash}"` で明示的に呼び出し、アーカイブ解凍等で `+x` 権限が失われても安定して動作。
136
+ - **TDD プロジェクトスイート検証フロア**:`skills/test-driven-development/SKILL.md` はタスク完了前にプロジェクト全体のテストコマンド(`npm test`、`pytest` 等)の実行を義務化。
137
+ - **Code Review ゴースト変更防止**:`skills/requesting-code-review/SKILL.md` で複数コミットのレビュー起点を `git merge-base origin/main HEAD` に固定。
138
+ - **Brainstorming ツールチェーン決定ゲート**:`skills/brainstorming/SKILL.md` の設計提示フェーズでツールチェーン設定を事前に確認し、`Global Constraints` に記録。
139
+ - **テスト拡充**:`tests/sdd/test-sdd-workspace.sh`(11 アサーション)を追加、PowerShell スイート(70 アサーション)を拡張。
140
+
141
+ ### v6.3.0
132
142
 
133
143
  - **上流 obra/superpowers v6.3.0 との同期** — 適用可能な改善をすべて採用し、フォーク固有のセキュリティ強化と PowerShell サポートは維持。
134
144
  - **brainstorming — 3 パスルーター**: すべてのリクエストを事前に `spike` / `bounded` / `architectural` に分類し、プロセス量をタスクに合わせて調整。ただし承認ゲートは常に全パスに適用されます。実行中に隠れた複雑さが判明した場合はパスをアップグレード — ダウングレードはありません。
package/README.ko.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | [繁體中文](README.zh-TW.md) | [日本語](README.ja.md) | [한국어](README.ko.md)
4
4
 
5
- [![Version](https://img.shields.io/badge/version-6.3.0-blue.svg)](https://github.com/Poseidoncode/superpowers-mcp)
5
+ [![Version](https://img.shields.io/badge/version-6.3.1-blue.svg)](https://github.com/Poseidoncode/superpowers-mcp)
6
6
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
7
 
8
8
  이 문서는 원본 Superpowers 스킬 라이브러리를 독립적인 MCP Toolpack으로 패키징하기 위한 정보와 사용 지침을 요약한 것입니다.
@@ -128,7 +128,17 @@
128
128
 
129
129
  ## 🆕 최근 업데이트
130
130
 
131
- ### v6.3.0 (최신)
131
+ ### v6.3.1 (최신)
132
+
133
+ - **SDD 워크스페이스 소유권 마커 및 물리 경로 정규화**: `sdd-workspace`(Bash) 및 `sdd-workspace.ps1`(PowerShell)은 `plan-path` 마커와 물리 경로 정규화(`pwd -P` 및 동적 `pwd` 감지)를 사용. 동일한 이름의 계획 파일(`docs/alpha/plan.md` 및 `docs/beta/plan.md` 등)이 충돌 없이 별도의 `.superpowers/sdd/` 워크스페이스로 격리됩니다.
134
+ - **SDD 리뷰 패키지 범위 가드**: `review-package` 및 `review-package.ps1`은 `git merge-base --is-ancestor BASE HEAD`와 커밋 수를 검증하여 비어 있거나 역전된 범위로 인한 오탐(false-pass)을 방지합니다.
135
+ - **실행 권한 상실에 대한 복원력**: `task-brief`와 `review-package`는 `"${BASH:-bash}"`로 명시적 호출하여 아카이브 압축 해제 등으로 `+x` 권限이 손실되어도 정상 동작합니다.
136
+ - **TDD 프로젝트 스위트 검증 하한선**: `skills/test-driven-development/SKILL.md`는 작업 완료 선언 전 프로젝트 전체 테스트 명령(`npm test`, `pytest` 등) 실행을 의무화합니다.
137
+ - **Code Review 유령 변경 방지**: `skills/requesting-code-review/SKILL.md`에서 다중 커밋의 리뷰 기준점을 `git merge-base origin/main HEAD`로 고정합니다.
138
+ - **Brainstorming 툴체인 결정 게이트**: `skills/brainstorming/SKILL.md`의 설계 제시 단계에서 툴체인 설정을 사전에 확인하고 `Global Constraints`에 기록합니다.
139
+ - **테스트 확장**: `tests/sdd/test-sdd-workspace.sh`(11개 어서션) 추가 및 PowerShell 스위트(70개 어서션) 확장.
140
+
141
+ ### v6.3.0
132
142
 
133
143
  - **상류 obra/superpowers v6.3.0 동기화** — 적용 가능한 개선 사항을 모두 채택하고, 포크 고유의 보안 강화와 PowerShell 지원은 유지.
134
144
  - **brainstorming — 3경로 라우터**: 모든 요청을 사전에 `spike` / `bounded` / `architectural`로 분류하며, 절차의 양은 작업 규모에 맞춰 조정됩니다. 단 승인 게이트는 모든 경로에 동일하게 적용됩니다. 실행 중 숨은 복잡성이 발견되면 경로를 업그레이드 — 다운그레이드는 없습니다.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | [繁體中文](README.zh-TW.md) | [日本語](README.ja.md) | [한국어](README.ko.md)
4
4
 
5
- [![Version](https://img.shields.io/badge/version-6.3.0-blue.svg)](https://github.com/Poseidoncode/superpowers-mcp)
5
+ [![Version](https://img.shields.io/badge/version-6.3.1-blue.svg)](https://github.com/Poseidoncode/superpowers-mcp)
6
6
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
7
 
8
8
  This document summarizes the information and usage instructions for packaging the original Superpowers skills library into an independent MCP Toolpack.
@@ -128,7 +128,17 @@ These skills are designed for orchestrating complex meta-execution patterns with
128
128
 
129
129
  ## 🆕 Recent Updates
130
130
 
131
- ### v6.3.0 (Latest)
131
+ ### v6.3.1 (Latest)
132
+
133
+ - **SDD Ownership Markers & Path Normalization**: `sdd-workspace` (Bash) and `sdd-workspace.ps1` (PowerShell) now manage plan-scoped workspaces using `plan-path` markers with canonical physical path normalization (`pwd -P` and dynamic `pwd` detection). Same-basename plans (e.g. `docs/alpha/plan.md` vs `docs/beta/plan.md`) safely disambiguate into distinct `.superpowers/sdd/` workspaces, preventing artifact and ledger overwrites.
134
+ - **SDD Review Package Range Mechanical Guards**: `review-package` and `review-package.ps1` now enforce `git merge-base --is-ancestor BASE HEAD` and `git rev-list --count BASE..HEAD > 0` (exiting with code 3 on error) to reject invalid or empty commit ranges and prevent false-pass review approvals.
135
+ - **SDD Helper Resilience on Stripped Permissions**: `task-brief` and `review-package` invoke `sdd-workspace` via explicit `"${BASH:-bash}"`, surviving environments where execution bits (`+x`) are stripped during archive extraction.
136
+ - **TDD Verification Floor**: `skills/test-driven-development/SKILL.md` explicitly defines "green" as passing the entire repository test suite (e.g., bare `npm test`, `pytest`, `cargo test`) before declaring a task complete.
137
+ - **Code Review Merge-Base Anchoring**: `skills/requesting-code-review/SKILL.md` now anchors multi-commit review `BASE_SHA` to `git merge-base origin/main HEAD` to prevent phantom deletions when `origin/main` advances.
138
+ - **Brainstorming Tooling Decision Gate**: `skills/brainstorming/SKILL.md` adds a proactive tooling inquiry (linter, formatting, unit/e2e tests, fuzzing) during the Design Presentation phase for new projects, recording choices into the spec's `Global Constraints`.
139
+ - **Tests & Coverage**: Added `tests/sdd/test-sdd-workspace.sh` (11 assertions) and expanded PowerShell suites (70 assertions across 5 files). Full test suites passing 100%.
140
+
141
+ ### v6.3.0
132
142
 
133
143
  - **Upstream sync with obra/superpowers v6.3.0** — all applicable improvements adopted, fork-specific security hardening and PowerShell support preserved.
134
144
  - **brainstorming — three-path router**: every request is classified `spike` / `bounded` / `architectural` up front; the ceremony scales with the task but the approval gate never does. Hidden complexity upgrades the path mid-task — never downgrades.
package/README.zh-TW.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | [繁體中文](README.zh-TW.md) | [日本語](README.ja.md) | [한국어](README.ko.md)
4
4
 
5
- [![版本](https://img.shields.io/badge/version-6.3.0-blue.svg)](https://github.com/Poseidoncode/superpowers-mcp)
5
+ [![版本](https://img.shields.io/badge/version-6.3.1-blue.svg)](https://github.com/Poseidoncode/superpowers-mcp)
6
6
  [![授權](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
7
 
8
8
  本文檔總結了將原始 Superpowers 技能庫打包成獨立 MCP Toolpack 的相關資訊與使用說明。
@@ -128,7 +128,17 @@
128
128
 
129
129
  ## 🆕 最近更新
130
130
 
131
- ### v6.3.0 (最新版)
131
+ ### v6.3.1 (最新版)
132
+
133
+ - **SDD 工作區歸屬標記與實體路徑正規化**:`sdd-workspace`(Bash)與 `sdd-workspace.ps1`(PowerShell)改用 `plan-path` 歸屬標記與標準化實體路徑(`pwd -P` 與動態 `pwd` 偵測)。同名 plan(如 `docs/alpha/plan.md` 與 `docs/beta/plan.md`)會自動解析為獨立的 `.superpowers/sdd/` 工作區,杜絕產物與進度覆蓋。
134
+ - **SDD 審查包範圍機械守衛**:`review-package` 與 `review-package.ps1` 強制校驗 `git merge-base --is-ancestor BASE HEAD` 與 commit 數量大於 0(錯誤時 exit 3),防止空範圍或倒置範圍造成審查假綠燈。
135
+ - **執行權限剝離韌性**:`task-brief` 與 `review-package` 改以 `"${BASH:-bash}"` 調用,在解壓縮或跨系統搬移丟失 `+x` 權限時依然能穩定執行。
136
+ - **TDD 全套件驗證下限**:`skills/test-driven-development/SKILL.md` 明確規定任務完成前必須跑過全專案測試指令(如 `npm test`、`pytest`、`cargo test`)。
137
+ - **Code Review 防幽靈變更**:`skills/requesting-code-review/SKILL.md` 將多 commit 審查起點錨定為 `git merge-base origin/main HEAD`,消除 main 推進引發的幽靈刪除。
138
+ - **Brainstorming 工具鏈決策門禁**:`skills/brainstorming/SKILL.md` 在設計展示階段主動詢問工具鏈配置並記錄於 `Global Constraints`。
139
+ - **測試擴充**:新增 `tests/sdd/test-sdd-workspace.sh`(11 項斷言)並擴充 PowerShell 套件(70 項斷言)。
140
+
141
+ ### v6.3.0
132
142
 
133
143
  - **對齊上游 obra/superpowers v6.3.0** — 採用所有適用改進,保留本 fork 的安全強化與 PowerShell 支援。
134
144
  - **brainstorming — 三路分類流程(three-path router)**:每個請求先分類為 `spike` / `bounded` / `architectural`;流程深度隨任務規模調整,但審批門檻永遠不變。隱藏複雜度會在執行途中升級路徑——絕不降級。
package/out/server.js CHANGED
@@ -49,7 +49,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
49
49
  `}var vs=class{constructor(t=hf.default.stdin,r=hf.default.stdout){this._stdin=t,this._stdout=r,this._readBuffer=new gs,this._started=!1,this._ondata=n=>{this._readBuffer.append(n),this.processReadBuffer()},this._onerror=n=>{this.onerror?.(n)}}async start(){if(this._started)throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror)}processReadBuffer(){for(;;)try{let t=this._readBuffer.readMessage();if(t===null)break;this.onmessage?.(t)}catch(t){this.onerror?.(t)}}async close(){this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror),this._stdin.listenerCount("data")===0&&this._stdin.pause(),this._readBuffer.clear(),this.onclose?.()}send(t){return new Promise(r=>{let n=j_(t);this._stdout.write(n)?r():this._stdout.once("drain",r)})}};var ze=sr(require("fs/promises")),ye=sr(require("path")),$n=10*1024*1024,ys=class{skillsPath;cachedSkills=null;loadingPromise=null;skillMap=new Map;contentCache=new Map;constructor(t){this.skillsPath=t}stripQuotes(t){return t.replace(/^"(.*)"$|^'(.*)'$/,"$1$2").trim()}parseFrontmatter(t){let r=t;if(r.charCodeAt(0)===65279&&(r=r.slice(1)),!r.startsWith("---"))return{name:"",description:""};let n=r.split(/\r?\n/),o=[],i=!1;for(let u=1;u<n.length;u++){if(n[u].trim()==="---"){i=!0;break}o.push(n[u])}if(!i)return{name:"",description:""};let a="",s="",c=!1;for(let u of o){let l=u.match(/^name:\s*(.*?)\s*$/);if(l){a=this.stripQuotes(l[1]),c=!1;continue}let d=u.match(/^description:\s*(.*?)\s*$/);if(d){s=this.stripQuotes(d[1]),c=!0;continue}c&&/^\s+/.test(u)?s+=" "+u.trim():c=!1}return{name:a,description:s}}async exists(t){try{return await ze.access(t),!0}catch{return!1}}async listSkills(t=!1){if(this.cachedSkills&&!t)return this.cachedSkills;if(this.loadingPromise&&!t)return this.loadingPromise;t&&this.contentCache.clear();let r=this.internalListSkills();this.loadingPromise=r;try{return await r}finally{this.loadingPromise===r&&(this.loadingPromise=null)}}async internalListSkills(){if(!await this.exists(this.skillsPath))return this.cachedSkills??[];let t=[],r=new Map,n=!1;try{let o=await ze.readdir(this.skillsPath,{withFileTypes:!0});for(let i of o){if(!i.isDirectory()&&!i.isSymbolicLink())continue;let a=ye.join(this.skillsPath,i.name),s=ye.join(a,"SKILL.md");if(await this.exists(s))try{let c=await this.readFileNoFollow(s,this.skillsPath),{name:u,description:l}=this.parseFrontmatter(c),m={name:u||i.name,description:l,skillPath:s};t.push(m),r.set(m.name.toLowerCase(),m);let p=ye.basename(ye.dirname(m.skillPath)).toLowerCase();r.set(p,m)}catch{process.stderr.write(`Warning: Failed to read skill file in directory "${i.name}"
50
50
  `)}}n=!0}catch(o){process.stderr.write(`Error reading skills directory: ${String(o)}
51
51
  `)}return n&&(this.skillMap=r,this.cachedSkills=t.sort((o,i)=>o.name.localeCompare(i.name))),this.cachedSkills??[]}async findSkill(t){let r=typeof t=="string"?t.trim():"";if(!(!r||r==="."||r===".."||r.includes("/")||r.includes("\\")||r.includes("\0")))return this.cachedSkills||await this.listSkills(),this.skillMap.get(r.toLowerCase())}async readFileNoFollow(t,r){let n=ye.resolve(t),o=ye.resolve(r),i=async()=>{let l=await ze.realpath(n),d=await ze.realpath(o);if(!(await ze.stat(d)).isDirectory())throw new Error("Skills directory must be a directory");let p=ye.relative(d,l);if(p===".."||p.startsWith(`..${ye.sep}`)||ye.isAbsolute(p))throw new Error("File is outside skills directory");let h=await ze.stat(l);if(!h.isFile()||h.nlink!==1)throw new Error("Skill path is not a regular file");if(h.size>$n)throw new Error("Skill file exceeds size limit");return{realFilePath:l,stat:h}},a=await i(),s=await i();if(a.realFilePath!==s.realFilePath||a.stat.dev!==s.stat.dev||a.stat.ino!==s.stat.ino)throw new Error("File changed while validating its path");let c=process.platform==="win32"?0:ze.constants.O_NOFOLLOW,u=await ze.open(s.realFilePath,ze.constants.O_RDONLY|c);try{let l=await u.stat();if(!l.isFile()||l.nlink!==1||l.dev!==s.stat.dev||l.ino!==s.stat.ino||l.size>$n)throw new Error("File changed while opening");let d=[],m=64*1024,p=0;for(;p<=$n;){let v=Buffer.allocUnsafe(Math.min(m,$n+1-p)),{bytesRead:$}=await u.read(v,0,v.length,null);if($===0)break;if(p+=$,d.push(v.subarray(0,$)),p>$n)throw new Error("Skill file exceeds size limit")}let h=await u.stat();if(h.size>$n||h.dev!==s.stat.dev||h.ino!==s.stat.ino)throw new Error("File changed while reading");return Buffer.concat(d,p).toString("utf-8")}finally{await u.close()}}async readSkillContent(t,r=!1){let n=ye.resolve(t),o=ye.resolve(this.skillsPath),i=n,a=o;try{i=await ze.realpath(n),a=await ze.realpath(o)}catch{}let s=ye.relative(a,i);if(s.startsWith("..")||ye.isAbsolute(s))throw new Error(`Access denied: path "${t}" is outside skills directory`);if(this.contentCache.has(i)&&!r)return this.contentCache.get(i);try{let c=await this.readFileNoFollow(t,this.skillsPath);c.charCodeAt(0)===65279&&(c=c.slice(1));let u=c.replace(/^---\s*\r?\n[\s\S]*?\r?\n---\s*\r?\n?/,"").trim();return this.contentCache.set(i,u),u}catch(c){throw new Error(`Failed to read skill content: ${c instanceof Error?c.message:String(c)}`)}}clearCache(){this.cachedSkills=null,this.loadingPromise=null,this.skillMap.clear(),this.contentCache.clear()}};function jT(){let e=process.env.SKILLS_PATH;if(e){let r=ct.resolve(e),n=ct.normalize(r).toLowerCase(),o=ct.parse(r).root.toLowerCase();if(n===o||["/etc","/var","/bin","/sbin","/usr","/root","/sys","/proc","/dev","c:\\windows","c:\\program files","c:\\program files (x86)"].some(s=>n===s||n.startsWith(s+ct.sep)))process.stderr.write(`Warning: Potentially unsafe SKILLS_PATH: "${e}". Fallback to default.
52
- `);else return r}let t=ct.join(__dirname,"..","skills");return E_.existsSync(t)?t:ct.join(__dirname,"skills")}var O_=jT(),It=new ys(O_),Pr=new hs({name:"superpowers-mcp",version:"6.3.0"},{capabilities:{resources:{subscribe:!1},prompts:{},tools:{}}});Pr.setRequestHandler(od,async()=>({resources:(await It.listSkills()).map(t=>({uri:`skill://superpowers/${encodeURIComponent(t.name)}`,name:t.name,description:t.description,mimeType:"text/markdown"}))}));Pr.setRequestHandler(ad,async e=>{let t=e.params.uri,r=t.match(/^skill:\/\/superpowers\/(.+)$/);if(!r)throw new D(M.InvalidRequest,`Invalid skill URI: ${t}`);let n;try{n=decodeURIComponent(r[1])}catch{throw new D(M.InvalidRequest,`Invalid skill URI: ${t}`)}let o=await It.findSkill(n);if(!o)throw new D(M.InvalidRequest,`Skill not found: ${n}`);try{let i=await It.readSkillContent(o.skillPath);return{contents:[{uri:t,mimeType:"text/markdown",text:i}]}}catch{throw new D(M.InternalError,"Failed to read skill content safely.")}});Pr.setRequestHandler(sd,async()=>({prompts:[{name:"session-start",description:"Inject the Superpowers context into an AI agent session. Tells the agent it has superpowers and how to use the skill system."}]}));Pr.setRequestHandler(cd,async e=>{if(e.params.name!=="session-start")throw new D(M.InvalidRequest,`Unknown prompt: ${e.params.name}`);let t=await It.findSkill("using-superpowers"),r="";if(t)try{r=await It.readSkillContent(t.skillPath)}catch{r=`# Superpowers
52
+ `);else return r}let t=ct.join(__dirname,"..","skills");return E_.existsSync(t)?t:ct.join(__dirname,"skills")}var O_=jT(),It=new ys(O_),Pr=new hs({name:"superpowers-mcp",version:"6.3.1"},{capabilities:{resources:{subscribe:!1},prompts:{},tools:{}}});Pr.setRequestHandler(od,async()=>({resources:(await It.listSkills()).map(t=>({uri:`skill://superpowers/${encodeURIComponent(t.name)}`,name:t.name,description:t.description,mimeType:"text/markdown"}))}));Pr.setRequestHandler(ad,async e=>{let t=e.params.uri,r=t.match(/^skill:\/\/superpowers\/(.+)$/);if(!r)throw new D(M.InvalidRequest,`Invalid skill URI: ${t}`);let n;try{n=decodeURIComponent(r[1])}catch{throw new D(M.InvalidRequest,`Invalid skill URI: ${t}`)}let o=await It.findSkill(n);if(!o)throw new D(M.InvalidRequest,`Skill not found: ${n}`);try{let i=await It.readSkillContent(o.skillPath);return{contents:[{uri:t,mimeType:"text/markdown",text:i}]}}catch{throw new D(M.InternalError,"Failed to read skill content safely.")}});Pr.setRequestHandler(sd,async()=>({prompts:[{name:"session-start",description:"Inject the Superpowers context into an AI agent session. Tells the agent it has superpowers and how to use the skill system."}]}));Pr.setRequestHandler(cd,async e=>{if(e.params.name!=="session-start")throw new D(M.InvalidRequest,`Unknown prompt: ${e.params.name}`);let t=await It.findSkill("using-superpowers"),r="";if(t)try{r=await It.readSkillContent(t.skillPath)}catch{r=`# Superpowers
53
53
 
54
54
  You have superpowers. Use the read_skill and list_skills tools to discover and load skills.`}else{let o=ct.join(O_,"using-superpowers","SKILL.md");try{r=await It.readSkillContent(o)}catch{r=`# Superpowers
55
55
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "superpowers-mcp",
3
3
  "displayName": "Superpowers MCP",
4
4
  "description": "Superpowers skills library (TDD, debugging, collaboration workflows) as an MCP server for VSCode and Antigravity",
5
- "version": "6.3.0",
5
+ "version": "6.3.1",
6
6
  "publisher": "superpowers",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -184,6 +184,7 @@ is the whole process.
184
184
  - Scale each section to its complexity: a few sentences if straightforward, up to 200-300 words if nuanced
185
185
  - Ask after each section whether it looks right so far
186
186
  - Cover: architecture, components, data flow, error handling, testing
187
+ - For a new project (or one with no configured tooling), the design presentation includes a short tooling question alongside the architecture: which of these to set up from the start — cheapest before any code exists: aggressive linting + auto-formatting (the stack's standard, e.g. ruff+format / eslint+prettier / clippy+rustfmt); unit-test infrastructure (runner, layout, a first passing fixture); end-to-end test infrastructure; fuzz or mutation testing where the stack supports it. The user's selections land in the spec's Global Constraints so every later plan and task inherits them.
187
188
  - Be ready to go back and clarify if something doesn't make sense
188
189
 
189
190
  **Design for isolation and clarity:**
@@ -25,7 +25,7 @@ Dispatch a code reviewer subagent to catch issues before they cascade. The revie
25
25
 
26
26
  **1. Get git SHAs:**
27
27
  ```bash
28
- BASE_SHA=$(git rev-parse HEAD~1) # or origin/main
28
+ BASE_SHA=$(git rev-parse HEAD~1) # or: git merge-base origin/main HEAD
29
29
  HEAD_SHA=$(git rev-parse HEAD)
30
30
  ```
31
31
 
@@ -22,10 +22,20 @@ head=$3
22
22
  git rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; }
23
23
  git rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&2; exit 2; }
24
24
 
25
+ git merge-base --is-ancestor "$base" "$head" || {
26
+ echo "HEAD ($head) is not a descendant of BASE ($base)" >&2
27
+ exit 3
28
+ }
29
+
30
+ if [ "$(git rev-list --count "${base}..${head}")" -eq 0 ]; then
31
+ echo "empty commit range: ${base}..${head}" >&2
32
+ exit 3
33
+ fi
34
+
25
35
  if [ $# -eq 4 ]; then
26
36
  out=$4
27
37
  else
28
- dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
38
+ dir=$("${BASH:-bash}" "$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
29
39
  out="$dir/review-$(git rev-parse --short "$base")..$(git rev-parse --short "$head").diff"
30
40
  fi
31
41
 
@@ -31,6 +31,18 @@ if ($LASTEXITCODE -ne 0) {
31
31
  exit 2
32
32
  }
33
33
 
34
+ & git merge-base --is-ancestor $base $head *> $null
35
+ if ($LASTEXITCODE -ne 0) {
36
+ [Console]::Error.WriteLine("HEAD ($head) is not a descendant of BASE ($base)")
37
+ exit 3
38
+ }
39
+
40
+ $commitCount = (& git rev-list --count "${base}..${head}").Trim()
41
+ if ([int]$commitCount -eq 0) {
42
+ [Console]::Error.WriteLine("empty commit range: ${base}..${head}")
43
+ exit 3
44
+ }
45
+
34
46
  if ($args.Count -eq 4) {
35
47
  $out = $args[3]
36
48
  } else {
@@ -53,7 +65,7 @@ $content.Add("")
53
65
  $content.Add("## Diff")
54
66
  (& git diff -U10 "${base}..${head}") | ForEach-Object { $content.Add($_) }
55
67
 
56
- Set-Content -Path $out -Value $content -Encoding utf8
68
+ Set-Content -LiteralPath $out -Value $content -Encoding utf8
57
69
  $commits = (& git rev-list --count "${base}..${head}").Trim()
58
70
  $bytes = (Get-Item -LiteralPath $out).Length
59
71
  Write-Output "wrote ${out}: $commits commit(s), $bytes bytes"
@@ -3,11 +3,17 @@
3
3
  # short-lived artifacts: task briefs, implementer reports, review packages,
4
4
  # and the progress ledger. Print the plan directory's absolute path.
5
5
  #
6
- # One directory per plan (.superpowers/sdd/<plan-basename>/) so a follow-up
6
+ # One directory per plan (.superpowers/sdd/<plan-slug>/) so a follow-up
7
7
  # plan in the same working tree can never read or overwrite another plan's
8
8
  # artifacts. A stale ledger misread as current progress makes controllers
9
9
  # skip whole task sequences — plan-scoping removes that failure structurally.
10
10
  #
11
+ # Ownership markers (plan-path file in the workspace directory) disambiguate
12
+ # same-basename plans (e.g. docs/alpha/plan.md vs docs/beta/plan.md) by appending
13
+ # the parent-directory name, then a counter. A workspace with no marker
14
+ # predates the marker scheme and is adopted for the current plan so in-flight
15
+ # workspaces keep resolving.
16
+ #
11
17
  # The workspace lives in the working tree (not under .git/) because Claude Code
12
18
  # treats .git/ as a protected path and denies agent writes there — which blocks
13
19
  # an implementer subagent from writing its report file. A self-ignoring
@@ -28,13 +34,46 @@ fi
28
34
  plan=$1
29
35
  [ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
30
36
 
31
- slug=$(basename "$plan" .md)
37
+ slug=$(basename -- "$plan" .md)
32
38
  [ -n "$slug" ] && [ "$slug" != "." ] && [ "$slug" != ".." ] \
33
39
  || { echo "cannot derive a workspace name from: $plan" >&2; exit 2; }
34
40
 
35
41
  root=$(git rev-parse --show-toplevel)
42
+ root=$(CDPATH= cd -- "$root" && pwd -P)
36
43
  base="$root/.superpowers/sdd"
44
+
45
+ # Normalize the plan path (physical directory, so relative/absolute/../
46
+ # spellings of one plan compare equal) and express it as the marker value:
47
+ # repo-relative when the plan lives under the repo root, absolute otherwise.
48
+ plan_dir=$(CDPATH= cd -- "$(dirname -- "$plan")" && pwd -P)
49
+ plan_abs="$plan_dir/$(basename -- "$plan")"
50
+ case "$plan_abs" in
51
+ "$root"/*) plan_id=${plan_abs#"$root"/} ;;
52
+ *) plan_id=$plan_abs ;;
53
+ esac
54
+
55
+ # True when the workspace at $1 is (or becomes) this plan's: an existing
56
+ # marker must name this plan; a missing marker means a new workspace or a
57
+ # pre-marker legacy one, and either way the plan claims it by writing one.
58
+ owns() {
59
+ if [ -e "$1/plan-path" ]; then
60
+ [ "$(cat "$1/plan-path")" = "$plan_id" ]
61
+ else
62
+ mkdir -p "$1"
63
+ printf '%s\n' "$plan_id" > "$1/plan-path"
64
+ fi
65
+ }
66
+
37
67
  dir="$base/$slug"
38
- mkdir -p "$dir"
68
+ if ! owns "$dir"; then
69
+ parent=$(basename -- "$plan_dir")
70
+ dir="$base/$slug-$parent"
71
+ if ! owns "$dir"; then
72
+ n=2
73
+ while ! owns "$base/$slug-$parent-$n"; do n=$((n + 1)); done
74
+ dir="$base/$slug-$parent-$n"
75
+ fi
76
+ fi
77
+
39
78
  printf '*\n' > "$base/.gitignore"
40
- cd "$dir" && pwd
79
+ CDPATH= cd -- "$dir" && pwd
@@ -30,7 +30,69 @@ if ([string]::IsNullOrEmpty($slug) -or $slug -eq "." -or $slug -eq "..") {
30
30
 
31
31
  $root = (& git rev-parse --show-toplevel).Trim()
32
32
  $base = Join-Path $root ".superpowers/sdd"
33
+
34
+ function Get-PhysicalDirectoryPath($path) {
35
+ if (-not (Test-Path -LiteralPath $path)) { return $path }
36
+ if ($IsWindows) {
37
+ return (Resolve-Path -LiteralPath $path).Path
38
+ }
39
+ $orig = Get-Location
40
+ try {
41
+ Set-Location -LiteralPath $path
42
+ $pwdCmd = (Get-Command -Type Application pwd -ErrorAction SilentlyContinue).Source
43
+ if ($pwdCmd) {
44
+ return (& $pwdCmd -P).Trim()
45
+ } else {
46
+ return (Resolve-Path -LiteralPath $path).Path
47
+ }
48
+ } finally {
49
+ Set-Location $orig
50
+ }
51
+ }
52
+
53
+ # Normalize the plan path (physical directory, so relative/absolute/../
54
+ # spellings of one plan compare equal) and express it as the marker value:
55
+ # repo-relative when the plan lives under the repo root, absolute otherwise.
56
+ $planLeaf = Split-Path -Leaf $plan
57
+ $planParent = Split-Path -Parent $plan
58
+ if ([string]::IsNullOrEmpty($planParent)) { $planParent = "." }
59
+
60
+ $planDir = Get-PhysicalDirectoryPath $planParent
61
+ $rootPhys = Get-PhysicalDirectoryPath $root
62
+
63
+ $planAbs = (Join-Path $planDir $planLeaf) -replace '\\', '/'
64
+ $rootNorm = $rootPhys -replace '\\', '/'
65
+
66
+ if ($planAbs.StartsWith($rootNorm + "/", [System.StringComparison]::OrdinalIgnoreCase)) {
67
+ $planId = $planAbs.Substring($rootNorm.Length + 1)
68
+ } else {
69
+ $planId = $planAbs
70
+ }
71
+
72
+ function Test-And-Claim-Workspace($targetDir, $id) {
73
+ $markerPath = Join-Path $targetDir "plan-path"
74
+ if (Test-Path -LiteralPath $markerPath -PathType Leaf) {
75
+ $existingId = (Get-Content -LiteralPath $markerPath -Raw).Trim()
76
+ return ($existingId -eq $id)
77
+ } else {
78
+ New-Item -ItemType Directory -Force -Path $targetDir | Out-Null
79
+ Set-Content -LiteralPath $markerPath -Value $id -Encoding ascii
80
+ return $true
81
+ }
82
+ }
83
+
33
84
  $dir = Join-Path $base $slug
34
- New-Item -ItemType Directory -Force -Path $dir | Out-Null
35
- Set-Content -Path (Join-Path $base ".gitignore") -Value "*" -NoNewline -Encoding ascii
36
- (Resolve-Path $dir).Path
85
+ if (-not (Test-And-Claim-Workspace $dir $planId)) {
86
+ $parent = Split-Path -Leaf $planDir
87
+ $dir = Join-Path $base "$slug-$parent"
88
+ if (-not (Test-And-Claim-Workspace $dir $planId)) {
89
+ $n = 2
90
+ while (-not (Test-And-Claim-Workspace (Join-Path $base "$slug-$parent-$n") $planId)) {
91
+ $n++
92
+ }
93
+ $dir = Join-Path $base "$slug-$parent-$n"
94
+ }
95
+ }
96
+
97
+ Set-Content -LiteralPath (Join-Path $base ".gitignore") -Value "*" -NoNewline -Encoding ascii
98
+ (Resolve-Path -LiteralPath $dir).Path
@@ -21,7 +21,7 @@ n=$2
21
21
  if [ $# -eq 3 ]; then
22
22
  out=$3
23
23
  else
24
- dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
24
+ dir=$("${BASH:-bash}" "$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
25
25
  out="$dir/task-${n}-brief.md"
26
26
  fi
27
27
 
@@ -42,7 +42,7 @@ foreach ($line in [System.IO.File]::ReadLines((Resolve-Path -LiteralPath $plan).
42
42
  }
43
43
  }
44
44
 
45
- Set-Content -Path $out -Value $selected -Encoding utf8
45
+ Set-Content -LiteralPath $out -Value $selected -Encoding utf8
46
46
  if ((-not (Test-Path -LiteralPath $out)) -or ((Get-Item -LiteralPath $out).Length -eq 0)) {
47
47
  [Console]::Error.WriteLine("task $taskNumber not found in $plan (no heading matching 'Task $taskNumber')")
48
48
  exit 3
@@ -182,6 +182,16 @@ Confirm:
182
182
 
183
183
  **Other tests fail?** Fix now.
184
184
 
185
+ **"Other tests" means the project's suite, not just your file.** A
186
+ green run of the test you wrote is not a green suite. Before you call
187
+ the change done, run the project's test command (bare `pytest`,
188
+ `npm test`, `cargo test` — whatever the repo uses) even when your task
189
+ named only one test file. A scope statement in your task bounds the
190
+ deliverable, not your verification. Any failure that run shows —
191
+ including one you didn't cause — goes in your report by name; a red
192
+ test you watched scroll past and didn't mention is a report falsified
193
+ by omission.
194
+
185
195
  ### REFACTOR - Clean Up
186
196
 
187
197
  After green only: