npx claudepluginhub jnuyens/gsd-plugin --plugin gsdDefined in hooks/hooks.json
node -e "const f=require('fs'),p=require('path'),o=require('os'),c=[];if(process.env.CLAUDE_PLUGIN_ROOT)c.push(p.join(process.env.CLAUDE_PLUGIN_ROOT,'bin/gsd-tools.cjs'));const d=p.join(o.homedir(),'.claude/plugins/cache/gsd-plugin/gsd');try{for(const v of f.readdirSync(d).filter(x=>/^\d+\.\d+\.\d+$/.test(x)).sort((a,b)=>{const A=a.split('.').map(Number),B=b.split('.').map(Number);return B[0]-A[0]||B[1]-A[1]||B[2]-A[2];}))c.push(p.join(d,v,'bin/gsd-tools.cjs'));}catch(e){}for(const x of c){if(!f.existsSync(x))continue;if(process.env.CLAUDE_PLUGIN_ROOT&&x!==c[0])process.stderr.write('GSD: plugin path stale, using '+x+'\n');process.argv.splice(1,0,x);require(x);break;}" hook stop3000msnode -e "const f=require('fs'),p=require('path'),o=require('os'),c=[];if(process.env.CLAUDE_PLUGIN_ROOT)c.push(p.join(process.env.CLAUDE_PLUGIN_ROOT,'bin/gsd-tools.cjs'));const d=p.join(o.homedir(),'.claude/plugins/cache/gsd-plugin/gsd');try{for(const v of f.readdirSync(d).filter(x=>/^\d+\.\d+\.\d+$/.test(x)).sort((a,b)=>{const A=a.split('.').map(Number),B=b.split('.').map(Number);return B[0]-A[0]||B[1]-A[1]||B[2]-A[2];}))c.push(p.join(d,v,'bin/gsd-tools.cjs'));}catch(e){}for(const x of c){if(!f.existsSync(x))continue;if(process.env.CLAUDE_PLUGIN_ROOT&&x!==c[0])process.stderr.write('GSD: plugin path stale, using '+x+'\n');process.argv.splice(1,0,x);require(x);break;}" hook pre-compact5000msEdit|Writenode -e "const f=require('fs'),p=require('path'),o=require('os'),c=[];if(process.env.CLAUDE_PLUGIN_ROOT)c.push(p.join(process.env.CLAUDE_PLUGIN_ROOT,'bin/gsd-tools.cjs'));const d=p.join(o.homedir(),'.claude/plugins/cache/gsd-plugin/gsd');try{for(const v of f.readdirSync(d).filter(x=>/^\d+\.\d+\.\d+$/.test(x)).sort((a,b)=>{const A=a.split('.').map(Number),B=b.split('.').map(Number);return B[0]-A[0]||B[1]-A[1]||B[2]-A[2];}))c.push(p.join(d,v,'bin/gsd-tools.cjs'));}catch(e){}for(const x of c){if(!f.existsSync(x))continue;if(process.env.CLAUDE_PLUGIN_ROOT&&x!==c[0])process.stderr.write('GSD: plugin path stale, using '+x+'\n');process.argv.splice(1,0,x);require(x);break;}" hook pre-tool-use3000msBash|Edit|Write|MultiEdit|NotebookEdit|Read|Grep|Glob|WebFetch|WebSearchnode -e "const f=require('fs'),p=require('path'),o=require('os'),c=[];if(process.env.CLAUDE_PLUGIN_ROOT)c.push(p.join(process.env.CLAUDE_PLUGIN_ROOT,'bin/gsd-tools.cjs'));const d=p.join(o.homedir(),'.claude/plugins/cache/gsd-plugin/gsd');try{for(const v of f.readdirSync(d).filter(x=>/^\d+\.\d+\.\d+$/.test(x)).sort((a,b)=>{const A=a.split('.').map(Number),B=b.split('.').map(Number);return B[0]-A[0]||B[1]-A[1]||B[2]-A[2];}))c.push(p.join(d,v,'bin/gsd-tools.cjs'));}catch(e){}for(const x of c){if(!f.existsSync(x))continue;if(process.env.CLAUDE_PLUGIN_ROOT&&x!==c[0])process.stderr.write('GSD: plugin path stale, using '+x+'\n');process.argv.splice(1,0,x);require(x);break;}" hook post-tool-use3000msnode -e "const f=require('fs'),p=require('path'),o=require('os'),c=[];if(process.env.CLAUDE_PLUGIN_ROOT)c.push(p.join(process.env.CLAUDE_PLUGIN_ROOT,'bin/gsd-tools.cjs'));const d=p.join(o.homedir(),'.claude/plugins/cache/gsd-plugin/gsd');try{for(const v of f.readdirSync(d).filter(x=>/^\d+\.\d+\.\d+$/.test(x)).sort((a,b)=>{const A=a.split('.').map(Number),B=b.split('.').map(Number);return B[0]-A[0]||B[1]-A[1]||B[2]-A[2];}))c.push(p.join(d,v,'bin/gsd-tools.cjs'));}catch(e){}for(const x of c){if(!f.existsSync(x))continue;if(process.env.CLAUDE_PLUGIN_ROOT&&x!==c[0])process.stderr.write('GSD: plugin path stale, using '+x+'\n');process.argv.splice(1,0,x);require(x);break;}" hook session-start5000msSafety framework blocks dangerous Bash tools and guards Write/Edit calls via PreToolUse; quality gate on Stop; backups on PreCompact; state tracking/formatting post-tools. Python scripts touch bash/files.
Share bugs, ideas, or general feedback.