pi-gsd 2.0.24 → 2.1.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.
@@ -350,7 +350,7 @@ ${p}
350
350
  `,h=Jt.default.join(t,n.directory,f);if(ls.default.existsSync(h)){y({error:"File already exists",path:J(Jt.default.relative(t,h))},r);return}ls.default.writeFileSync(h,Ie(g),"utf-8");let x=J(Jt.default.relative(t,h));y({created:!0,path:x,template:e},r,x)}var ls,Jt,Ks=L(()=>{"use strict";ls=U(require("fs")),Jt=U(require("path"));ye();Ve()});var Xe,ds,us,Jr=L(()=>{"use strict";Xe=require("@oclif/core");je();ds=class t extends w{static description="Select a workflow template";static args={type:Xe.Args.string({required:!0})};static flags={...w.baseFlags};async run(){let{flags:e,args:s}=await this.parse(t),{cwd:r,raw:n}=this.resolveContext(e),{cmdTemplateSelect:i}=await Promise.resolve().then(()=>(Ks(),Zs));i(r,s.type,n)}},us=class t extends w{static description="Fill a template with values";static args={type:Xe.Args.string({required:!0})};static flags={...w.baseFlags,phase:Xe.Flags.string(),plan:Xe.Flags.string(),name:Xe.Flags.string(),type:Xe.Flags.string({char:"t"}),wave:Xe.Flags.string(),fields:Xe.Flags.string({description:"JSON fields"})};async run(){let{flags:e,args:s}=await this.parse(t),{cwd:r,raw:n}=this.resolveContext(e),{cmdTemplateFill:i}=await Promise.resolve().then(()=>(Ks(),Zs)),o={};if(e.fields)try{o=JSON.parse(e.fields)}catch{}i(r,s.type,{phase:e.phase??void 0,plan:e.plan??void 0,name:e.name??void 0,type:e.type??void 0,wave:e.wave??void 0,...o},n)}}});var Ft,ms,ps,fs,gs,hs,Yr=L(()=>{"use strict";Ft=require("@oclif/core");je();ms=class t extends w{static description="Show project progress";static args={format:Ft.Args.string({default:"json"})};static flags={...w.baseFlags};async run(){let{flags:e,args:s}=await this.parse(t),{cwd:r,raw:n}=this.resolveContext(e),{cmdProgressRender:i}=await Promise.resolve().then(()=>(_e(),ve));i(r,s.format,n)}},ps=class t extends w{static description="Show project statistics";static args={format:Ft.Args.string({default:"json"})};static flags={...w.baseFlags};async run(){let{flags:e,args:s}=await this.parse(t),{cwd:r,raw:n}=this.resolveContext(e),{cmdStats:i}=await Promise.resolve().then(()=>(_e(),ve));i(r,s.format,n)}},fs=class t extends w{static description="Mark a todo as complete";static args={id:Ft.Args.string({required:!0})};static flags={...w.baseFlags};async run(){let{flags:e,args:s}=await this.parse(t),{cwd:r,raw:n}=this.resolveContext(e),{cmdTodoComplete:i}=await Promise.resolve().then(()=>(_e(),ve));i(r,s.id,n)}},gs=class t extends w{static description="Match todos to phase";static args={phase:Ft.Args.string({required:!0})};static flags={...w.baseFlags};async run(){let{flags:e,args:s}=await this.parse(t),{cwd:r,raw:n}=this.resolveContext(e),{cmdTodoMatchPhase:i}=await Promise.resolve().then(()=>(_e(),ve));i(r,s.phase,n)}},hs=class t extends w{static description="Extract fields from summary files";static args={phase:Ft.Args.string({required:!0})};static flags={...w.baseFlags,fields:w.baseFlags.pick};async run(){let{flags:e,args:s}=await this.parse(t),{cwd:r,raw:n}=this.resolveContext(e),{cmdSummaryExtract:i}=await Promise.resolve().then(()=>(_e(),ve)),o=e.fields?e.fields.split(","):null;i(r,s.phase,o,n)}}});function ft(t){let e=[],s=/^```[^\n]*\n[\s\S]*?^```/gm,r;for(;(r=s.exec(t))!==null;)e.push([r.index,r.index+r[0].length]);return e}function gt(t,e){return e.some(([s,r])=>t>=s&&t<r)}function Va(t){let e={},s=/([a-zA-Z0-9_:-]+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s/>]*)))?/g,r;for(;(r=s.exec(t))!==null;){let n=r[1],i=r[2]??r[3]??r[4]??"";e[n]=i}return e}function Hr(t,e){if(t[e]!=="<")return null;let s=/^<([a-zA-Z0-9_:-]+)((?:\s+[a-zA-Z0-9_:-]+(?:=(?:"[^"]*"|'[^']*'|[^\s/>]*))?)*)?\s*(\/??>)/,r=t.slice(e),n=s.exec(r);if(!n)return null;let i=n[1],o=(n[2]??"").trim(),a=n[3],c=Va(o);if(a==="/>")return{node:{tag:i,attrs:c,children:[],selfClosing:!0},end:e+n[0].length};let l=e+n[0].length,d=[],u=`</${i}>`;for(;l<t.length;){let m=t.indexOf("<",l);if(m===-1)break;if(t.startsWith(u,m))return{node:{tag:i,attrs:c,children:d,selfClosing:!1},end:m+u.length};if(t.startsWith("<!--",m)){let f=t.indexOf("-->",m+4);l=f!==-1?f+3:t.length;continue}let p=Hr(t,m);p?(d.push(p.node),l=p.end):l=m+1}return{node:{tag:i,attrs:c,children:d,selfClosing:!1},end:l}}function Yt(t){let e=ft(t),s=[],r=/<(gsd-[a-zA-Z0-9_-]+)/g,n;for(;(n=r.exec(t))!==null;){let i=n.index;if(gt(i,e))continue;let o=n[1];if(!za.has(o))continue;let a=Hr(t,i);a&&(s.push({node:a.node,start:i,end:a.end}),r.lastIndex=a.end)}return s}function ys(t,e,s,r){return t.slice(0,e)+r+t.slice(s)}var za,Qs=L(()=>{"use strict";za=new Set(["gsd-execute","gsd-arguments","gsd-paste","gsd-include","gsd-version"])});function Xr(){let t=new Map,e=new Map,s=r=>{let n=t.get(r)?.value;if(n!==void 0)return n;let i=r.indexOf(".");if(i===-1)return;let o=r.slice(0,i),a=r.slice(i+1),c=t.get(o)?.value;if(c!==void 0)try{let l=JSON.parse(c);for(let d of a.split(".")){if(l===null||typeof l!="object")return;l=l[d]}return l==null?void 0:String(l)}catch{return}};return{set(r,n,i){let o=t.get(r);o?.owner&&i&&o.owner!==i?(t.delete(r),t.set(`${o.owner}:${r}`,{name:`${o.owner}:${r}`,value:o.value,owner:o.owner}),t.set(`${i}:${r}`,{name:`${i}:${r}`,value:n,owner:i})):t.set(r,{name:r,value:n,owner:i})},get(r){return t.get(r)?.value},resolve(r){return s(r)},setArray(r,n,i){e.set(r,n),t.set(r,{name:r,value:JSON.stringify(n),owner:i})},getArray(r){if(e.has(r))return e.get(r);let n=t.get(r)?.value;if(n)try{let i=JSON.parse(n);if(Array.isArray(i))return i.map(o=>typeof o=="string"?o:JSON.stringify(o))}catch{}},has(r){return t.has(r)||e.has(r)},entries(){return t.entries()},snapshot(){let r={};for(let[n,i]of t)r[n]=i.value;return r}}}var Zr=L(()=>{"use strict"});function Kr(t,e,s){let r=t.children.find(g=>g.tag==="settings"),n=r?.children.some(g=>g.tag==="keep-extra-args")??!1,i=r?.children.some(g=>g.tag==="strict-args")??!1,a=r?.children.find(g=>g.tag==="delimiters")?.children.find(g=>g.tag==="delimiter"),c;if(a){let g=a.attrs.value??"",h=g==="\\n"?`
351
351
  `:g;c=e.split(h).map(x=>x.trim()).filter(Boolean)}else c=e.trim().split(/\s+/).filter(Boolean);let l=t.children.filter(g=>g.tag==="arg"),d=new Set;for(let g of l.filter(h=>h.attrs.type==="flag")){let h=g.attrs.flag??`--${g.attrs.name}`,x=c.indexOf(h),S=g.attrs.name;S&&(x===-1?s.set(S,"false",void 0):(s.set(S,"true",void 0),d.add(x)))}let u=l.filter(g=>g.attrs.type!=="flag"),m=c.filter((g,h)=>!d.has(h)),p=0;for(let g=0;g<u.length;g++){let h=u[g],x=h.attrs.name,S=h.attrs.type??"string",v=g===u.length-1;if(x){if(p>=m.length){if(!("optional"in h.attrs))throw new ht(`Missing required argument '${x}' (type: ${S})`);s.set(x,"",void 0);continue}if(S==="string"&&v)s.set(x,m.slice(p).join(" "),void 0),p=m.length;else if(S==="number"){let C=m[p++],P=Number(C);if(isNaN(P))throw new ht(`Argument '${x}' expected a number, got '${C}'`);s.set(x,String(P),void 0)}else if(S==="boolean"){let C=m[p++].toLowerCase();if(C!=="true"&&C!=="false")throw new ht(`Argument '${x}' expected true/false, got '${C}'`);s.set(x,C,void 0)}else s.set(x,m[p++]??"",void 0)}}let f=m.slice(p).join(" ");if(f){if(i)throw new ht(`Unexpected extra arguments: '${f}'`);n&&s.set("_extra",f,void 0)}}var ht,er=L(()=>{"use strict";ht=class extends Error{constructor(e){super(e),this.name="WxpArgumentsError"}}});function xs(t,e,s){switch(t.position){case"project":return Ze.default.resolve(e,t.path);case"pkg":return Ze.default.resolve(s,t.path);case"absolute":return Ze.default.resolve(t.path)}}function tr(t,e,s,r){let n=Ze.default.resolve(t),i=`${Ze.default.sep}.planning`;if(n.includes(`${i}${Ze.default.sep}`)||n.endsWith(i))return{ok:!1,reason:".planning/ files are never processed by WXP (hard security invariant)"};for(let o of e.untrustedPaths){let a=xs(o,s,r);if(n.startsWith(a+Ze.default.sep)||n===a)return{ok:!1,reason:`File '${t}' is in an explicitly untrusted path: ${a}`}}for(let o of e.trustedPaths){let a=xs(o,s,r);if(n.startsWith(a+Ze.default.sep)||n===a)return{ok:!0}}return{ok:!1,reason:`File '${t}' is not in a trusted WXP path.`}}function ei(t,e){let s=Ze.default.basename(t);return e.shellBanlist.includes(s)?{ok:!1,reason:`Command '${s}' is explicitly banned by WXP security config.`}:e.shellAllowlist.includes(s)?{ok:!0}:{ok:!1,reason:`Command '${s}' is not in the WXP shell allowlist. Allowed: ${e.shellAllowlist.join(", ")}`}}var Ze,Qr,Ht=L(()=>{"use strict";Ze=U(require("path")),Qr=["pi-gsd-tools","git","node","cat","ls","echo","find"]});function Ss(t,e){if(t.attrs.string!==void 0)return t.attrs.string;if(t.attrs.name!==void 0){let s=e.resolve(t.attrs.name)??"",r=t.attrs.wrap;return r?`${r}${s}${r}`:s}return t.attrs.value!==void 0?t.attrs.value:""}function ni(t,e,s){let r=t.attrs.command??"",n=ei(r,s);if(!n.ok)throw new yt(r,"",e.snapshot(),n.reason);let i=t.children.find(u=>u.tag==="args"),o=t.children.find(u=>u.tag==="outs"),a=i?i.children.filter(u=>u.tag==="arg").map(u=>Ss(u,e)):[],c=o?o.children.some(u=>u.tag==="suppress-errors"):!1,l=o?o.children.filter(u=>u.tag==="out"&&u.attrs.name).map(u=>u.attrs.name):[],d="";try{d=(0,ti.execFileSync)(r,a,{encoding:"utf8",timeout:s.shellTimeoutMs,windowsHide:!0}).trim()}catch(u){if(c){for(let f of l)e.set(f,"",void 0);return}let m=u,p=(m.stderr??m.message??String(u)).trim();throw new yt(r,p,e.snapshot(),`Shell '${r} ${a.join(" ")}' failed: ${p}`)}l.length>0&&e.set(l[0],d,void 0)}var ti,yt,bs=L(()=>{"use strict";ti=require("child_process");Ht();yt=class extends Error{constructor(s,r,n,i){super(i);this.command=s;this.stderr=r;this.variableSnapshot=n;this.name="WxpShellError"}command;stderr;variableSnapshot}});function si(t,e){if(t.attrs.op!=="split")throw new xt('<string-op> only op="split" is supported in v1');let r=t.children.find(m=>m.tag==="args"),n=t.children.find(m=>m.tag==="outs");if(!r||!n)throw new xt("<string-op> requires <args> and <outs>");let i=r.children.filter(m=>m.tag==="arg"),o=n.children.filter(m=>m.tag==="out"),a=i[0],c=i[1];if(!a)throw new xt('<string-op op="split"> requires at least 2 <arg> children');let l=Ss(a,e);if(a.attrs.name&&e.get(a.attrs.name)===void 0)throw new xt(`string-op split: source variable '${a.attrs.name}' is not defined`);let d=c?Ss(c,e):"",u=l.split(d);o.forEach((m,p)=>{let f=m.attrs.name;f&&e.set(f,u[p+1]??u[p]??"",void 0)})}var xt,nr=L(()=>{"use strict";bs();xt=class extends Error{constructor(e){super(e),this.name="WxpStringOpError"}}});function vs(t,e){return t.attrs.name?e.resolve(t.attrs.name)??"":t.attrs.value!==void 0?t.attrs.value:""}function ri(t){return t.attrs.type==="number"}function Ga(t,e){let s=t.children.find(a=>a.tag==="left"),r=t.children.find(a=>a.tag==="right");if(!s||!r)return!1;if(ri(s)||ri(r)){let a=Number(vs(s,e)),c=Number(vs(r,e));switch(t.tag){case"equals":return a===c;case"not-equals":return a!==c;case"less-than":return a<c;case"greater-than":return a>c;case"less-than-or-equal":return a<=c;case"greater-than-or-equal":return a>=c;default:return!1}}let i=vs(s,e),o=vs(r,e);switch(t.tag){case"equals":return i===o;case"not-equals":return i!==o;case"starts-with":return i.startsWith(o);case"contains":return i.includes(o);case"less-than":return Number(i)<Number(o);case"greater-than":return Number(i)>Number(o);case"less-than-or-equal":return Number(i)<=Number(o);case"greater-than-or-equal":return Number(i)>=Number(o);default:return!1}}function Zt(t,e){return t.tag==="and"?t.children.filter(s=>Xt.has(s.tag)).every(s=>Zt(s,e)):t.tag==="or"?t.children.filter(s=>Xt.has(s.tag)).some(s=>Zt(s,e)):Ga(t,e)}function ii(t,e){let s=t.children.find(n=>n.tag==="condition");if(!s)return!1;let r=s.children.find(n=>Xt.has(n.tag));return r?Zt(r,e):!1}function oi(t,e){let s=t.children.find(r=>Xt.has(r.tag));return s?Zt(s,e):!0}var Ba,Xt,ai=L(()=>{"use strict";Ba=new Set(["equals","not-equals","starts-with","contains","less-than","greater-than","less-than-or-equal","greater-than-or-equal"]),Xt=new Set([...Ba,"and","or"])});function Ja(t,e,s){let r=(t.attrs.msg??"").replace(/\{([^}]+)\}/g,(i,o)=>e.resolve(o)??""),n=t.attrs.level;s.onDisplay(r,n==="warning"||n==="error"?n:"info")}function Ya(t,e){let s=t.attrs.src??"",r=t.attrs.out??"",n=t.attrs.path,i=e.get(s);if(i===void 0)throw new Error(`<json-parse>: source variable '${s}' is not defined`);let o;try{o=JSON.parse(i)}catch{throw new Error(`<json-parse>: '${s}' does not contain valid JSON`)}if(n){let a=n.replace(/^\$\.?/,"").split("."),c=o;for(let l of a){if(c===null||typeof c!="object")throw new Error(`<json-parse>: path '${n}' not found`);c=c[l]}o=c}Array.isArray(o)?e.setArray(r,o.map(a=>typeof a=="string"?a:JSON.stringify(a))):o!==null&&typeof o=="object"?e.set(r,JSON.stringify(o),void 0):e.set(r,o==null?"":String(o),void 0)}function Ha(t,e){let s=t.attrs.path??"",r=t.attrs.out??"",n=Kt.default.readFileSync(Qt.default.resolve(s),"utf8");e.set(r,n,void 0)}function Xa(t,e,s){let r=t.attrs.path??"",n=t.attrs.src??"",i=Qt.default.resolve(r);if(Kt.default.existsSync(i))throw new Error(`<write-file>: '${r}' already exists (create-only, never overwrites)`);for(let a of s.config.trustedPaths){let c=xs(a,s.projectRoot,s.pkgRoot);if(i.startsWith(c+Qt.default.sep)||i===c)throw new Error(`<write-file>: cannot write to trusted harness path '${r}'`)}let o=e.get(n)??"";Kt.default.mkdirSync(Qt.default.dirname(i),{recursive:!0}),Kt.default.writeFileSync(i,o,"utf8")}function Za(t,e,s){let r=t.attrs.var??"",n=t.attrs.item??"",i=t.children.find(l=>l.tag==="where"),o=t.children.find(l=>l.tag==="sort-by"),a=t.children.filter(l=>l.tag!=="where"&&l.tag!=="sort-by"),c=e.getArray(r);if(c){if(i&&(c=c.filter(l=>(e.set(n,l,void 0),oi(i,e)))),o){let l=o.attrs.key??"",d=o.attrs.type??"string",u=o.attrs.order??"asc";c=[...c].sort((m,p)=>{e.set(n,m,void 0);let f=e.resolve(`${n}.${l}`)??e.resolve(l)??"";e.set(n,p,void 0);let g=e.resolve(`${n}.${l}`)??e.resolve(l)??"",h=d==="number"?Number(f)-Number(g):f.localeCompare(g);return u==="desc"?-h:h})}for(let l of c){e.set(n,l,void 0);for(let d of a)sr(d,e,s)}}}function sr(t,e,s){switch(t.tag){case"shell":ni(t,e,s.config);break;case"string-op":si(t,e);break;case"json-parse":Ya(t,e);break;case"read-file":Ha(t,e);break;case"write-file":Xa(t,e,s);break;case"display":Ja(t,e,s);break;case"for-each":Za(t,e,s);break;case"if":{let r=ii(t,e),n=t.children.find(a=>a.tag==="then"),i=t.children.find(a=>a.tag==="else"),o=r?n:i;if(o)for(let a of o.children)sr(a,e,s);break}case"gsd-execute":rr(t,e,s);break;default:break}}function rr(t,e,s){try{for(let r of t.children)sr(r,e,s)}catch(r){throw r instanceof yt||r instanceof Error?new _s(r,e.snapshot(),`Execution failed: ${r.message}`):r}}var Kt,Qt,_s,ir=L(()=>{"use strict";Kt=U(require("fs")),Qt=U(require("path"));bs();nr();ai();Ht();_s=class extends Error{constructor(s,r,n){super(n);this.cause=s;this.variableSnapshot=r;this.name="WxpExecutionError"}cause;variableSnapshot}});function ci(t,e){let s=ft(t),r=/<gsd-paste\s+name="([^"]+)"\s*\/>/g,n=[],i;for(;(i=r.exec(t))!==null;)gt(i.index,s)||n.push({index:i.index,full:i[0],name:i[1]});for(let a of n)if(e.get(a.name)===void 0)throw new ws(a.name,e.snapshot());let o=t;for(let a=n.length-1;a>=0;a--){let c=n[a],l=e.get(c.name);o=o.slice(0,c.index)+l+o.slice(c.index+c.full.length)}return o}var ws,or=L(()=>{"use strict";Qs();ws=class extends Error{constructor(s,r){super(`<gsd-paste name="${s}" /> references undefined variable '${s}'`);this.variableName=s;this.variableSnapshot=r;this.name="WxpPasteError"}variableName;variableSnapshot}});function li(t,e,s,r,n,i="",o=Qa){let a=tr(e,s,r,n);if(!a.ok)throw new St(e,new Error(a.reason),{},[],[]);return ec(t,e,s,r,n,i,o)}function ec(t,e,s,r,n,i,o){let a=Xr(),c=[],l=t,d={config:s,projectRoot:r,pkgRoot:n,onDisplay:o};for(let u=0;u<Ka;u++){let p=Yt(l).filter(g=>g.node.tag!=="gsd-version");if(p.length===0)break;let f=p.map(g=>g.node.tag);try{let g=!1;for(let x of Yt(l)){if(x.node.tag!=="gsd-include"||gt(x.start,ft(l)))continue;let S=x.node.attrs.path;if(!S)continue;let v=ks.default.resolve(r,S);if(!ar.default.existsSync(v))throw new Error(`Include not found: ${S}`);let C=tr(v,s,r,n);if(!C.ok)throw new Error(`Include rejected: ${C.reason}`);let P=ar.default.readFileSync(v,"utf8"),$=ks.default.basename(v,ks.default.extname(v));for(let k of x.node.children)if(k.tag==="gsd-arguments")for(let j of k.children.filter(R=>R.tag==="arg")){let R=j.attrs.name,E=j.attrs.as;if(R&&E){let V=a.get(R);V!==void 0&&a.set(E,V,$)}}let T="include-arguments"in x.node.attrs?`
352
352
  ${i}`:"";l=ys(l,x.start,x.end,P+T),c.push("gsd-include"),g=!0;break}if(g)continue;for(let x of Yt(l))if(x.node.tag==="gsd-arguments"&&!gt(x.start,ft(l))){Kr(x.node,i,a),l=ys(l,x.start,x.end,""),c.push("gsd-arguments"),g=!0;break}if(g)continue;for(let x of Yt(l))if(x.node.tag==="gsd-execute"&&!gt(x.start,ft(l))){rr(x.node,a,d),l=ys(l,x.start,x.end,""),c.push("gsd-execute"),g=!0;break}if(g)continue;let h=ci(l,a);if(h!==l){l=h,c.push("gsd-paste");continue}break}catch(g){if(g instanceof St)throw g;let h=g instanceof Error?g:new Error(String(g));throw new St(e,h,a.snapshot(),f,c)}}return l}var ar,ks,Ka,Qa,St,di=L(()=>{"use strict";ar=U(require("fs")),ks=U(require("path"));Qs();Zr();er();ir();or();Ht();ir();bs();or();nr();er();Ka=50,Qa=()=>{},St=class extends Error{constructor(s,r,n,i,o){super(["WXP Processing Error",`File: ${s}`,`Error: ${r.message}`,`Variable Namespace: ${JSON.stringify(n,null,2)}`,`Pending Operations: [${i.join(", ")}]`,`Completed Operations: [${o.join(", ")}]`].join(`
353
- `));this.filePath=s;this.cause=r;this.variableSnapshot=n;this.pendingOperations=i;this.completedOperations=o;this.name="WxpProcessingError"}filePath;cause;variableSnapshot;pendingOperations;completedOperations}});var bt,ui,cr,Ps,mi=L(()=>{"use strict";bt=require("@oclif/core"),ui=require("@oclif/core"),cr=U(require("path"));di();Ht();Ps=class t extends ui.Command{static description="Process WXP tags in a workflow file";static args={file:bt.Args.string({description:"File to process",required:!1})};static flags={input:bt.Flags.string({description:"Input content string (alternative to file)"}),arguments:bt.Flags.string({description:"Raw $ARGUMENTS string",default:""}),"project-root":bt.Flags.string({description:"Project root directory",default:process.cwd()}),"pkg-root":bt.Flags.string({description:"Package root directory",default:process.cwd()})};async run(){let{flags:e,args:s}=await this.parse(t),r,n;if(e.input!==void 0)r=e.input,n=cr.default.join(e["project-root"],".pi","gsd","workflows","_inline.md");else if(s.file){let o=await import("fs");n=cr.default.resolve(s.file),r=o.default.readFileSync(n,"utf8")}else{this.error("Provide a file argument or --input string");return}let i={trustedPaths:[{position:"project",path:".pi/gsd"},{position:"pkg",path:"gsd"}],untrustedPaths:[],shellAllowlist:[...Qr],shellBanlist:[],shellTimeoutMs:3e4};try{let o=li(r,n,i,e["project-root"],e["pkg-root"],e.arguments);process.stdout.write(o)}catch(o){throw o instanceof St&&this.error(o.message,{exit:1}),o}}}});var pi={};xe(pi,{AuditUatCommand:()=>Hn,CommitCommand:()=>is,ConfigEnsureSectionCommand:()=>Fn,ConfigGetCommand:()=>An,ConfigNewProjectCommand:()=>En,ConfigSetCommand:()=>jn,ConfigSetModelProfileCommand:()=>Rn,FrontmatterGetCommand:()=>os,FrontmatterMergeCommand:()=>cs,FrontmatterSetCommand:()=>as,InitCommand:()=>_n,MilestoneCompleteCommand:()=>Un,PhaseAddBatchCommand:()=>Tn,PhaseAddCommand:()=>In,PhaseCompleteCommand:()=>Nn,PhaseInsertCommand:()=>Dn,PhaseNextDecimalCommand:()=>Mn,PhasePlanIndexCommand:()=>Wn,PhaseRemoveCommand:()=>On,ProgressCommand:()=>ms,RequirementsMarkCompleteCommand:()=>Vn,RoadmapAnalyzeCommand:()=>Pn,RoadmapGetPhaseCommand:()=>$n,RoadmapUpdatePlanProgressCommand:()=>Cn,ScaffoldCommand:()=>ss,StateAdvancePlanCommand:()=>gn,StateGetCommand:()=>mn,StateJsonCommand:()=>un,StateLoadCommand:()=>hn,StatePatchCommand:()=>fn,StateReconcileCommand:()=>xn,StateUpdateCommand:()=>pn,StateUpdateProgressCommand:()=>yn,StatsCommand:()=>ps,SummaryExtractCommand:()=>hs,TemplateFillCommand:()=>us,TemplateSelectCommand:()=>ds,TodoCompleteCommand:()=>fs,TodoMatchPhaseCommand:()=>gs,ValidateAgentsCommand:()=>Jn,ValidateConsistencyCommand:()=>Bn,ValidateHealthCommand:()=>Gn,VerifyCommand:()=>Yn,WorkstreamCompleteCommand:()=>Qn,WorkstreamCreateCommand:()=>Xn,WorkstreamGetCommand:()=>ts,WorkstreamListCommand:()=>Zn,WorkstreamProgressCommand:()=>ns,WorkstreamSetCommand:()=>es,WorkstreamStatusCommand:()=>Kn,WxpProcessCommand:()=>Ps});var fi=L(()=>{"use strict";Pr();Cr();jr();Mr();Tr();Dr();Ur();Vr();zr();Br();Gr();Jr();Yr();mi()});var gi=Ei((Od,tc)=>{tc.exports={name:"pi-gsd",version:"2.0.24",description:"Get Shit Done - Unofficial port of the renowned AI-native project-planning spec-driven toolkit",main:"dist/pi-gsd-tools.js",bin:{"pi-gsd-tools":"./dist/pi-gsd-tools.js","pi-gsd":"./dist/pi-gsd-tools.js"},scripts:{build:"tsup",dev:"tsup src/cli.ts --format cjs --out-dir dist --watch",typecheck:"tsc --noEmit",check:"tsc --noEmit && npm run build",postinstall:"node scripts/postinstall.js",prepublishOnly:"npm run build",test:"vitest run","test:unit":"vitest run src/wxp/__tests__ --ignore src/wxp/__tests__/integration.test.ts","test:integration":"vitest run src/wxp/__tests__/integration.test.ts",lint:"eslint src/ --ext .ts"},files:["gsd","dist","scripts/postinstall.js","README.md","LICENSE"],engines:{node:">=18.0.0"},keywords:["ai","agent","planning","cli","workflow","get-shit-done","gsd","productivity","project-management","milestones","phases","spec","pi-package"],author:"Alessio Corsi",license:"MIT",repository:{type:"git",url:"https://github.com/fulgidus/pi-gsd.git"},bugs:{url:"https://github.com/fulgidus/pi-gsd/issues"},homepage:"https://github.com/fulgidus/pi-gsd#readme",publishConfig:{access:"public",registry:"https://registry.npmjs.org/"},pi:{extensions:["./dist/pi-gsd-hooks.js"],prompts:["./gsd/prompts"]},dependencies:{"@oclif/core":"^4.10.5","@toon-format/toon":"^2.1.0","jsonpath-plus":"^10.4.0",zod:"^3.25.76"},devDependencies:{"@mariozechner/pi-coding-agent":"^0.65.0","@types/node":"^22.0.0","@typescript-eslint/eslint-plugin":"^8.58.0","@typescript-eslint/parser":"^8.58.0",eslint:"^10.2.0",tsup:"^8.0.0",typescript:"^5.0.0",vitest:"^4.1.2"}}});var $s={};xe($s,{cmdExtractMessages:()=>oc,cmdProfileSample:()=>ac,cmdScanSessions:()=>ic});function lr(t){if(t)return t;let e=process.env.HOME??"",s=De.join(e,".agent","projects");return re.existsSync(s)?s:De.join(e,".claude","projects")}function dr(){let t=process.env.HOME??"";return De.join(t,".pi","agent","sessions")}function ur(t){return t.startsWith("--")&&t.endsWith("--")?"/"+t.slice(2,-2).replace(/-/g,"/"):t}function nc(t){try{let e=re.readFileSync(t,"utf-8").split(`
353
+ `));this.filePath=s;this.cause=r;this.variableSnapshot=n;this.pendingOperations=i;this.completedOperations=o;this.name="WxpProcessingError"}filePath;cause;variableSnapshot;pendingOperations;completedOperations}});var bt,ui,cr,Ps,mi=L(()=>{"use strict";bt=require("@oclif/core"),ui=require("@oclif/core"),cr=U(require("path"));di();Ht();Ps=class t extends ui.Command{static description="Process WXP tags in a workflow file";static args={file:bt.Args.string({description:"File to process",required:!1})};static flags={input:bt.Flags.string({description:"Input content string (alternative to file)"}),arguments:bt.Flags.string({description:"Raw $ARGUMENTS string",default:""}),"project-root":bt.Flags.string({description:"Project root directory",default:process.cwd()}),"pkg-root":bt.Flags.string({description:"Package root directory",default:process.cwd()})};async run(){let{flags:e,args:s}=await this.parse(t),r,n;if(e.input!==void 0)r=e.input,n=cr.default.join(e["project-root"],".pi","gsd","workflows","_inline.md");else if(s.file){let o=await import("fs");n=cr.default.resolve(s.file),r=o.default.readFileSync(n,"utf8")}else{this.error("Provide a file argument or --input string");return}let i={trustedPaths:[{position:"project",path:".pi/gsd"},{position:"pkg",path:"gsd"}],untrustedPaths:[],shellAllowlist:[...Qr],shellBanlist:[],shellTimeoutMs:3e4};try{let o=li(r,n,i,e["project-root"],e["pkg-root"],e.arguments);process.stdout.write(o)}catch(o){throw o instanceof St&&this.error(o.message,{exit:1}),o}}}});var pi={};xe(pi,{AuditUatCommand:()=>Hn,CommitCommand:()=>is,ConfigEnsureSectionCommand:()=>Fn,ConfigGetCommand:()=>An,ConfigNewProjectCommand:()=>En,ConfigSetCommand:()=>jn,ConfigSetModelProfileCommand:()=>Rn,FrontmatterGetCommand:()=>os,FrontmatterMergeCommand:()=>cs,FrontmatterSetCommand:()=>as,InitCommand:()=>_n,MilestoneCompleteCommand:()=>Un,PhaseAddBatchCommand:()=>Tn,PhaseAddCommand:()=>In,PhaseCompleteCommand:()=>Nn,PhaseInsertCommand:()=>Dn,PhaseNextDecimalCommand:()=>Mn,PhasePlanIndexCommand:()=>Wn,PhaseRemoveCommand:()=>On,ProgressCommand:()=>ms,RequirementsMarkCompleteCommand:()=>Vn,RoadmapAnalyzeCommand:()=>Pn,RoadmapGetPhaseCommand:()=>$n,RoadmapUpdatePlanProgressCommand:()=>Cn,ScaffoldCommand:()=>ss,StateAdvancePlanCommand:()=>gn,StateGetCommand:()=>mn,StateJsonCommand:()=>un,StateLoadCommand:()=>hn,StatePatchCommand:()=>fn,StateReconcileCommand:()=>xn,StateUpdateCommand:()=>pn,StateUpdateProgressCommand:()=>yn,StatsCommand:()=>ps,SummaryExtractCommand:()=>hs,TemplateFillCommand:()=>us,TemplateSelectCommand:()=>ds,TodoCompleteCommand:()=>fs,TodoMatchPhaseCommand:()=>gs,ValidateAgentsCommand:()=>Jn,ValidateConsistencyCommand:()=>Bn,ValidateHealthCommand:()=>Gn,VerifyCommand:()=>Yn,WorkstreamCompleteCommand:()=>Qn,WorkstreamCreateCommand:()=>Xn,WorkstreamGetCommand:()=>ts,WorkstreamListCommand:()=>Zn,WorkstreamProgressCommand:()=>ns,WorkstreamSetCommand:()=>es,WorkstreamStatusCommand:()=>Kn,WxpProcessCommand:()=>Ps});var fi=L(()=>{"use strict";Pr();Cr();jr();Mr();Tr();Dr();Ur();Vr();zr();Br();Gr();Jr();Yr();mi()});var gi=Ei((Od,tc)=>{tc.exports={name:"pi-gsd",version:"2.1.1",description:"Get Shit Done - Unofficial port of the renowned AI-native project-planning spec-driven toolkit",main:"dist/pi-gsd-tools.js",bin:{"pi-gsd-tools":"./dist/pi-gsd-tools.js","pi-gsd":"./dist/pi-gsd-tools.js"},scripts:{build:"tsup",dev:"tsup src/cli.ts --format cjs --out-dir dist --watch",typecheck:"tsc --noEmit",check:"tsc --noEmit && npm run build",postinstall:"node scripts/postinstall.js",prepublishOnly:"npm run build",test:"vitest run","test:unit":"vitest run src/wxp/__tests__ --ignore src/wxp/__tests__/integration.test.ts","test:integration":"vitest run src/wxp/__tests__/integration.test.ts",lint:"eslint src/ --ext .ts"},files:["gsd","dist","scripts/postinstall.js","README.md","LICENSE"],engines:{node:">=18.0.0"},keywords:["ai","agent","planning","cli","workflow","get-shit-done","gsd","productivity","project-management","milestones","phases","spec","pi-package"],author:"Alessio Corsi",license:"MIT",repository:{type:"git",url:"https://github.com/fulgidus/pi-gsd.git"},bugs:{url:"https://github.com/fulgidus/pi-gsd/issues"},homepage:"https://github.com/fulgidus/pi-gsd#readme",publishConfig:{access:"public",registry:"https://registry.npmjs.org/"},pi:{extensions:["./dist/pi-gsd-hooks.js"],prompts:["./gsd/prompts"]},dependencies:{"@oclif/core":"^4.10.5","@toon-format/toon":"^2.1.0","jsonpath-plus":"^10.4.0",zod:"^3.25.76"},devDependencies:{"@mariozechner/pi-coding-agent":"^0.65.0","@types/node":"^22.0.0","@typescript-eslint/eslint-plugin":"^8.58.0","@typescript-eslint/parser":"^8.58.0",eslint:"^10.2.0",tsup:"^8.0.0",typescript:"^5.0.0",vitest:"^4.1.2"}}});var $s={};xe($s,{cmdExtractMessages:()=>oc,cmdProfileSample:()=>ac,cmdScanSessions:()=>ic});function lr(t){if(t)return t;let e=process.env.HOME??"",s=De.join(e,".agent","projects");return re.existsSync(s)?s:De.join(e,".claude","projects")}function dr(){let t=process.env.HOME??"";return De.join(t,".pi","agent","sessions")}function ur(t){return t.startsWith("--")&&t.endsWith("--")?"/"+t.slice(2,-2).replace(/-/g,"/"):t}function nc(t){try{let e=re.readFileSync(t,"utf-8").split(`
354
354
  `).find(r=>r.trim().length>0);if(!e)return!1;let s=JSON.parse(e);return s.type==="session"&&"version"in s}catch{return!1}}function sc(t){return typeof t=="string"?t:Array.isArray(t)?t.filter(e=>e!==null&&typeof e=="object"&&e.type==="text").map(e=>String(e.text??"")).join(" "):""}function rc(t){try{let e=re.readFileSync(t,"utf-8").split(`
355
355
  `).filter(Boolean),s=[];for(let r of e)try{let n=JSON.parse(r);n.type==="message"&&n.message&&s.push(n)}catch{}return s}catch{return[]}}async function ic(t,e,s){let n=(e.harness??null)==="pi",i=dr(),o=re.existsSync(i),a=[];if(o)try{let m=re.readdirSync(i,{withFileTypes:!0}).filter(p=>p.isDirectory());for(let p of m){let f=De.join(i,p.name),g=re.readdirSync(f).filter(h=>h.endsWith(".jsonl"));a.push({name:p.name,sessions:g.length,path:f,source:"pi",cwd:ur(p.name)})}}catch{}let c=lr(n&&!t?null:t),l=!n||t?re.existsSync(c):!1,d=[];if(l&&(!n||t))try{let m=re.readdirSync(c,{withFileTypes:!0}).filter(p=>p.isDirectory());for(let p of m){let f=De.join(c,p.name),g=re.readdirSync(f).filter(h=>h.endsWith(".jsonl")||h.endsWith(".json"));d.push({name:p.name,sessions:g.length,path:f,source:"claude"})}}catch{}let u=n?[...a,...d]:[...d,...a];if(u.length===0){let m=[];o?m.push(i):m.push(i+" (not found)"),n||m.push(l?c:c+" (not found)"),y({available:!1,reason:`No sessions found. Searched: ${m.join(", ")}`,projects:[],count:0},s);return}y({available:!0,pi_base:o?i:null,claude_base:l?c:null,projects:u,count:u.length},s)}async function oc(t,e,s,r){let n=dr(),i=null,o="claude";if(re.existsSync(n)){let d=De.join(n,t);if(re.existsSync(d))i=d,o="pi";else try{let u=re.readdirSync(n,{withFileTypes:!0}).filter(m=>m.isDirectory());for(let m of u){let p=ur(m.name);if(p.endsWith("/"+t)||p===t||m.name===t){i=De.join(n,m.name),o="pi";break}}}catch{}}if(!i){let d=lr(r),u=De.join(d,t);re.existsSync(u)&&(i=u,o="claude")}if(!i){y({error:`Project not found: ${t}`,available_projects:[]},s);return}let a=[],c=re.readdirSync(i).filter(d=>d.endsWith(".jsonl")),l=e.limit??null;for(let d of c){if(e.sessionId&&!d.includes(e.sessionId))continue;let u=De.join(i,d);if(o==="pi"||nc(u))try{let m=re.readFileSync(u,"utf-8").split(`
356
356
  `).filter(Boolean);for(let p of m)try{let f=JSON.parse(p);if(f.type==="message"&&f.message&&(a.push(f.message),l&&a.length>=l))break}catch{}}catch{}else try{let m=re.readFileSync(u,"utf-8").split(`
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: "Capture milestone scope before planning. Optional — /gsd-new-milestone works without it. Flags: --auto"
3
+ ---
4
+ <gsd-include path=".pi/gsd/workflows/discuss-milestone.md" include-arguments />
5
+ <gsd-include path=".pi/gsd/templates/milestone-context.md" />
6
+
7
+ $ARGUMENTS
@@ -0,0 +1,231 @@
1
+ # Milestone Context Template
2
+
3
+ Template for `.planning/MILESTONE-CONTEXT.md` — captures product scope decisions for an upcoming milestone.
4
+
5
+ **Purpose:** Document what the milestone should deliver so `/gsd-new-milestone` can start with known intent rather than gathering it inline. Consumed and deleted by `new-milestone` after it generates requirements and a roadmap.
6
+
7
+ **Key principle:** Product-level only. WHAT users will be able to do — not HOW it will be implemented. Implementation decisions happen in `/gsd-discuss-phase` per phase.
8
+
9
+ **Downstream consumer:**
10
+ - `new-milestone` — reads `<scope>` for feature scoping, `<constraints>` for requirements boundaries, `<success>` to inform success criteria in ROADMAP.md
11
+
12
+ ---
13
+
14
+ ## File Template
15
+
16
+ ```markdown
17
+ # Milestone Context
18
+
19
+ **Gathered:** [date]
20
+ **Status:** Ready for /gsd-new-milestone
21
+
22
+ <milestone_goal>
23
+ ## Goal
24
+
25
+ [One sentence: what this milestone delivers for users]
26
+
27
+ </milestone_goal>
28
+
29
+ <scope>
30
+ ## Scope
31
+
32
+ ### In this milestone
33
+
34
+ - **[Capability name]**: [What users can do — one line]
35
+ - **[Capability name]**: [What users can do — one line]
36
+
37
+ ### Explicitly out of scope
38
+
39
+ - **[Capability name]**: [Reason — "deferred to next milestone", "separate product area", etc.]
40
+
41
+ [If no explicit exclusions: "No explicit exclusions — boundary is the in-scope list above"]
42
+
43
+ </scope>
44
+
45
+ <constraints>
46
+ ## Constraints
47
+
48
+ - [Hard constraint — e.g., "no breaking changes to existing API"]
49
+ - [Hard constraint — e.g., "must work with existing auth system"]
50
+
51
+ [If none: "None — unconstrained milestone"]
52
+
53
+ </constraints>
54
+
55
+ <success>
56
+ ## Success Definition
57
+
58
+ This milestone is successful when:
59
+ - [Observable user outcome — something that can be demoed]
60
+ - [Observable user outcome]
61
+
62
+ </success>
63
+
64
+ <open_questions>
65
+ ## Open Questions for Planning
66
+
67
+ - [Question to resolve early in new-milestone or research]
68
+
69
+ [If none: "None — scope is clear"]
70
+
71
+ </open_questions>
72
+
73
+ ---
74
+
75
+ *Milestone context gathered: [date]*
76
+ *Run /gsd-new-milestone to start planning*
77
+ ```
78
+
79
+ <good_examples>
80
+
81
+ **Example 1: SaaS product — adding collaboration**
82
+
83
+ ```markdown
84
+ # Milestone Context
85
+
86
+ **Gathered:** 2025-03-15
87
+ **Status:** Ready for /gsd-new-milestone
88
+
89
+ <milestone_goal>
90
+ ## Goal
91
+
92
+ Users can invite teammates and collaborate on projects in real time.
93
+
94
+ </milestone_goal>
95
+
96
+ <scope>
97
+ ## Scope
98
+
99
+ ### In this milestone
100
+
101
+ - **Invite by email**: User can send invites to teammates by email address
102
+ - **Role-based access**: Owner, editor, and viewer roles with clear permission boundaries
103
+ - **Shared project view**: Teammates see the same project state with live updates
104
+ - **Activity feed**: Users can see who changed what and when
105
+
106
+ ### Explicitly out of scope
107
+
108
+ - **SSO / SAML**: Enterprise auth deferred to v2.0
109
+ - **Guest links**: Public sharing without accounts — separate product decision needed
110
+
111
+ </scope>
112
+
113
+ <constraints>
114
+ ## Constraints
115
+
116
+ - No breaking changes to existing project data model — solo users must not need to migrate
117
+ - Invite emails must go through existing SendGrid integration (no new email provider)
118
+
119
+ </constraints>
120
+
121
+ <success>
122
+ ## Success Definition
123
+
124
+ This milestone is successful when:
125
+ - A user can invite a colleague and both see the same project within 60 seconds
126
+ - A viewer cannot accidentally edit or delete content
127
+
128
+ </success>
129
+
130
+ <open_questions>
131
+ ## Open Questions for Planning
132
+
133
+ - Should activity feed be real-time (websocket) or polling? Affects architecture phase ordering.
134
+ - What happens to a project if the owner deletes their account?
135
+
136
+ </open_questions>
137
+
138
+ ---
139
+
140
+ *Milestone context gathered: 2025-03-15*
141
+ *Run /gsd-new-milestone to start planning*
142
+ ```
143
+
144
+ **Example 2: CLI tool — v1.1 reliability release**
145
+
146
+ ```markdown
147
+ # Milestone Context
148
+
149
+ **Gathered:** 2025-04-01
150
+ **Status:** Ready for /gsd-new-milestone
151
+
152
+ <milestone_goal>
153
+ ## Goal
154
+
155
+ The backup CLI is reliable enough for unattended production use.
156
+
157
+ </milestone_goal>
158
+
159
+ <scope>
160
+ ## Scope
161
+
162
+ ### In this milestone
163
+
164
+ - **Retry with backoff**: Transient network failures retry automatically, not silently fail
165
+ - **Structured logging**: Machine-readable log output for monitoring integration
166
+ - **Config file support**: Users can set defaults in a config file, not just flags
167
+ - **Dry-run mode**: Users can preview what would be backed up before committing
168
+
169
+ ### Explicitly out of scope
170
+
171
+ - **Restore command**: Planned for v1.2
172
+ - **S3 backend**: Deferred — local filesystem only for now
173
+
174
+ </scope>
175
+
176
+ <constraints>
177
+ ## Constraints
178
+
179
+ - Must remain backwards compatible with v1.0 flag interface — existing scripts must not break
180
+ - No new runtime dependencies (Node built-ins only)
181
+
182
+ </constraints>
183
+
184
+ <success>
185
+ ## Success Definition
186
+
187
+ This milestone is successful when:
188
+ - A backup job can run overnight on a cron without manual intervention
189
+ - A failed run produces a log entry that tells an ops engineer exactly what went wrong
190
+
191
+ </success>
192
+
193
+ <open_questions>
194
+ ## Open Questions for Planning
195
+
196
+ - Should config file use TOML, JSON, or dotenv format? Research common CLI conventions.
197
+
198
+ </open_questions>
199
+
200
+ ---
201
+
202
+ *Milestone context gathered: 2025-04-01*
203
+ *Run /gsd-new-milestone to start planning*
204
+ ```
205
+
206
+ </good_examples>
207
+
208
+ <guidelines>
209
+ **What makes a good MILESTONE-CONTEXT.md:**
210
+
211
+ Good goal (specific, user-observable):
212
+ - "Users can invite teammates and collaborate on projects in real time."
213
+ - "The backup CLI is reliable enough for unattended production use."
214
+
215
+ Bad goal (too vague):
216
+ - "Improve collaboration features"
217
+ - "Make things more reliable"
218
+
219
+ Good scope item (user action):
220
+ - "User can invite colleagues by email address"
221
+ - "Dry-run mode previews changes before committing"
222
+
223
+ Bad scope item (implementation detail):
224
+ - "Add Redis pub/sub for real-time updates"
225
+ - "Refactor retry logic in backup module"
226
+
227
+ **After creation:**
228
+ - File lives at `.planning/MILESTONE-CONTEXT.md`
229
+ - `new-milestone` reads it in step 2, uses it for requirements scoping, then deletes it
230
+ - It does NOT persist — it's a handoff document, not a record
231
+ </guidelines>
@@ -230,12 +230,18 @@ Tag: v[X.Y]
230
230
 
231
231
  ## ▶ Next Up
232
232
 
233
- **Start Next Milestone** - questioning → research → requirements → roadmap
233
+ **Start Next Milestone** - requirements → research → roadmap
234
234
 
235
235
  `/gsd-new-milestone`
236
236
 
237
237
  <sub>`/new` first → fresh context window</sub>
238
238
 
239
+ ---
240
+
241
+ **Optional pre-step:** Capture scope intent before the planning session
242
+
243
+ `/gsd-discuss-milestone` — crystallize what to build next, then run `/gsd-new-milestone`
244
+
239
245
  ---
240
246
  ```
241
247
 
@@ -0,0 +1,467 @@
1
+ <gsd-version v="2.0.24" />
2
+
3
+ <gsd-arguments>
4
+ <settings>
5
+ <keep-extra-args />
6
+ </settings>
7
+ <arg name="auto" type="flag" flag="--auto" optional />
8
+ </gsd-arguments>
9
+
10
+ <gsd-execute>
11
+ <shell command="pi-gsd-tools">
12
+ <args>
13
+ <arg string="init" />
14
+ <arg string="milestone-op" />
15
+ </args>
16
+ <outs>
17
+ <out type="string" name="init" />
18
+ </outs>
19
+ </shell>
20
+ <shell command="pi-gsd-tools">
21
+ <args>
22
+ <arg string="state" />
23
+ <arg string="json" />
24
+ <arg string="--raw" />
25
+ </args>
26
+ <outs>
27
+ <suppress-errors />
28
+ <out type="string" name="state" />
29
+ </outs>
30
+ </shell>
31
+ <shell command="pi-gsd-tools">
32
+ <args>
33
+ <arg string="roadmap" />
34
+ <arg string="analyze" />
35
+ <arg string="--raw" />
36
+ </args>
37
+ <outs>
38
+ <suppress-errors />
39
+ <out type="string" name="roadmap" />
40
+ </outs>
41
+ </shell>
42
+ </gsd-execute>
43
+
44
+ ## Context (pre-injected)
45
+
46
+ **Init:**
47
+ <gsd-paste name="init" />
48
+
49
+ **Project State:**
50
+ <gsd-paste name="state" />
51
+
52
+ **Current Roadmap:**
53
+ <gsd-paste name="roadmap" />
54
+
55
+ ---
56
+
57
+ <purpose>
58
+ Crystallize what the next milestone should deliver before starting the planning machinery. You are a thinking partner — a PM who knows what shipped, asks smart questions, and helps the user clarify product scope before committing to a roadmap.
59
+
60
+ Output: `.planning/MILESTONE-CONTEXT.md`, consumed by /gsd-new-milestone.
61
+
62
+ Optional step — /gsd-new-milestone works without it. The value is separating the "what do we build?" conversation from the requirements and roadmapping machinery.
63
+ </purpose>
64
+
65
+ <philosophy>
66
+ **User = product owner. Agent = PM/advisor.**
67
+
68
+ The user knows:
69
+ - What users are struggling with
70
+ - What the next logical product step is
71
+ - What MUST ship vs nice-to-have
72
+ - Any hard constraints (tech, team, timeline)
73
+
74
+ The user doesn't need to define:
75
+ - How to structure phases (that's the roadmapper)
76
+ - Implementation approach (that's research + discuss-phase)
77
+ - Which requirements to write (that's new-milestone)
78
+
79
+ Your job: help the user articulate a clear, scoped milestone intent that new-milestone can turn into requirements and a roadmap.
80
+ </philosophy>
81
+
82
+ <scope_guardrail>
83
+ **Product-level only.** This discussion is about WHAT the milestone delivers, not HOW.
84
+
85
+ **Allowed:**
86
+ - "Should we tackle X or defer it?"
87
+ - "What's the must-have vs nice-to-have split?"
88
+ - "Any hard constraints for this cycle?"
89
+ - "How will we know this milestone is done?"
90
+
91
+ **Not here:**
92
+ - "Should we use Redis or Postgres for this?"
93
+ - "Which architecture pattern?"
94
+ - "How should we structure the phases?"
95
+
96
+ If the user goes implementation-level, redirect:
97
+ ```
98
+ "That's a planning question — /gsd-new-milestone and /gsd-discuss-phase will handle it.
99
+ For now: do you want [capability] in scope for this milestone?"
100
+ ```
101
+ </scope_guardrail>
102
+
103
+ <answer_validation>
104
+ After every AskUserQuestion call, check if the response is empty or whitespace-only. If so:
105
+ 1. Retry once with the same parameters
106
+ 2. If still empty, present options as a plain-text numbered list
107
+
108
+ **Text mode (`workflow.text_mode: true` or `--text` flag):**
109
+ Replace ALL AskUserQuestion calls with plain-text numbered lists. User types a number.
110
+ Required for Claude Code remote sessions where TUI menus don't forward.
111
+ </answer_validation>
112
+
113
+ <process>
114
+
115
+ ## 1. Initialize
116
+
117
+ <!-- Context pre-injected above via WXP -->
118
+
119
+ Parse init JSON for: `commit_docs`, `context_window`, `milestone_version`, `milestone_name`, `last_completed_milestone`, `roadmap_exists`, `state_exists`.
120
+
121
+ **If `state_exists` is false:**
122
+ ```
123
+ No .planning/ directory found. Set up a project first:
124
+
125
+ /gsd-new-project
126
+ ```
127
+ Exit workflow.
128
+
129
+ Read project files:
130
+ ```bash
131
+ cat .planning/PROJECT.md 2>/dev/null || true
132
+ cat .planning/MILESTONES.md 2>/dev/null || true
133
+ ```
134
+
135
+ Extract from PROJECT.md: project name, core value, non-negotiables, target users.
136
+ Extract from MILESTONES.md: what shipped in completed milestones (summaries, not full detail).
137
+
138
+ **Read text mode config:**
139
+ ```bash
140
+ TEXT_MODE=$(pi-gsd-tools config-get workflow.text_mode 2>/dev/null || echo "false")
141
+ ```
142
+ Enable text mode if `--text` in $ARGUMENTS OR `TEXT_MODE` is `true`.
143
+
144
+ ## 2. Check Existing MILESTONE-CONTEXT.md
145
+
146
+ ```bash
147
+ test -f .planning/MILESTONE-CONTEXT.md && echo "exists" || echo "absent"
148
+ ```
149
+
150
+ **If exists:**
151
+
152
+ **If `--auto`:** Load existing content, continue to step 3 to refresh it. Log: `[auto] Existing MILESTONE-CONTEXT.md found — refreshing.`
153
+
154
+ **Otherwise,** use AskUserQuestion:
155
+ - header: "Context exists"
156
+ - question: "MILESTONE-CONTEXT.md already exists. What do you want to do?"
157
+ - options:
158
+ - "Update it" — Revise and improve existing context
159
+ - "View it" — Show current content, then decide
160
+ - "Skip" — Use as-is, go straight to /gsd-new-milestone
161
+
162
+ If "View": display file contents, then re-ask "Update it" / "Skip".
163
+ If "Skip": display `Next: /gsd-new-milestone` and exit.
164
+ If "Update": load existing content, continue to step 3.
165
+
166
+ **If absent:** Continue to step 3.
167
+
168
+ ## 3. Retrospective Framing
169
+
170
+ Sets the context for "what's next" based on what shipped.
171
+
172
+ **If `last_completed_milestone` is not null:**
173
+
174
+ Read the matching section in `.planning/MILESTONES.md` for `last_completed_milestone.version`.
175
+
176
+ Display (no user input needed):
177
+ ```
178
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
179
+ Last milestone: [version] — [name]
180
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
181
+ [2-3 sentence summary of what shipped from MILESTONES.md]
182
+ ```
183
+
184
+ Then ask ONE freeform question (plain text, NOT AskUserQuestion):
185
+
186
+ > "What feedback or signals are shaping what you want to build next?"
187
+
188
+ Wait for response. Use the answer to seed the scope discussion. Do not ask follow-ups from this — carry the insight forward.
189
+
190
+ **If `--auto`:** Skip retrospective question. Read STATE.md accumulated context for any signals.
191
+
192
+ **If no completed milestones:** Skip retrospective entirely. Continue to step 4.
193
+
194
+ ## 4. Gather Milestone Intent
195
+
196
+ Open question to surface rough direction before structuring.
197
+
198
+ Ask (plain text, NOT AskUserQuestion):
199
+
200
+ > "What do you want this milestone to deliver? Give me the rough picture — we'll tighten the scope next."
201
+
202
+ Wait for response. Parse it for:
203
+ - Feature/capability mentions → candidates for scope-in
204
+ - Exclusions or "not yet" signals → candidates for scope-out
205
+ - Urgency or priority cues
206
+ - Any constraints mentioned in passing
207
+
208
+ Reflect back in 2-3 sentences:
209
+ ```
210
+ "So the core of this milestone is [X]. You also mentioned [Y],
211
+ and [Z] sounds like a natural boundary. Is that the right picture?"
212
+ ```
213
+
214
+ If they confirm: proceed to step 5 with extracted candidates.
215
+ If they adjust: incorporate and reflect again. Max 2 loops, then proceed.
216
+
217
+ **If `--auto`:** Skip reflection loop. Extract candidates from the intent statement directly and proceed.
218
+
219
+ ## 5. Scope Discussion
220
+
221
+ Turn the rough intent into a clear in/out split.
222
+
223
+ **Build candidate list** from:
224
+ - Step 4's response (feature/capability mentions)
225
+ - STATE.md accumulated context (pending items, blockers noted)
226
+ - MILESTONES.md "Future Requirements" or deferred items from last milestone
227
+ - Any items in `.planning/REQUIREMENTS.md` marked Out of Scope that might be reconsidered
228
+
229
+ Group related candidates into clusters (2-4 features per cluster). Present one cluster at a time.
230
+
231
+ **For each cluster:**
232
+
233
+ If text mode: present as numbered list with multi-select.
234
+ Otherwise use AskUserQuestion (multiSelect: true):
235
+ - header: "Scope: [cluster]" (max 12 chars)
236
+ - question: "Which of these belong in this milestone?"
237
+ - options: each candidate with a 1-line description
238
+
239
+ After all clusters, show a running tally:
240
+ ```
241
+ Scoped in: [N] capabilities
242
+ Deferred: [M] capabilities
243
+ ```
244
+
245
+ **Explicit exclusions** — after clusters are done:
246
+
247
+ If text mode: ask as plain-text.
248
+ Otherwise use AskUserQuestion:
249
+ - header: "Out of scope"
250
+ - question: "Anything to explicitly exclude — even if it seems related?"
251
+ - options:
252
+ - "Nothing to add — the scope list covers it"
253
+ - "Yes, I want to explicitly exclude something"
254
+
255
+ If "Yes": ask them to name it (plain text). Capture with reason.
256
+
257
+ **If `--auto`:** Include everything mentioned in step 4's intent or with a clear priority signal. Exclude only items the user explicitly flagged as "not now" or "next milestone".
258
+
259
+ ## 6. Constraints
260
+
261
+ Anything that bounds how this milestone must be shaped.
262
+
263
+ If text mode: present as numbered multi-select list.
264
+ Otherwise use AskUserQuestion (multiSelect: true):
265
+ - header: "Constraints"
266
+ - question: "Any hard constraints for this milestone?"
267
+ - options:
268
+ - "No breaking changes — existing integrations must keep working"
269
+ - "No new external dependencies"
270
+ - "Must maintain backwards compatibility with existing data"
271
+ - "Performance budget — no regressions on key metrics"
272
+ - "None — this milestone is unconstrained"
273
+ - "Other — let me describe it"
274
+
275
+ If "Other": ask plain text, record result.
276
+
277
+ **If `--auto`:** Default to "None" unless PROJECT.md or STATE.md explicitly mentions active constraints.
278
+
279
+ ## 7. Success Definition
280
+
281
+ How does "done" look from the outside?
282
+
283
+ Ask (plain text, NOT AskUserQuestion):
284
+
285
+ > "Finish this sentence: this milestone is a success when users can ___."
286
+
287
+ Wait for response. Parse 1-3 observable outcomes.
288
+
289
+ If the response is vague ("when everything works", "when it's polished"), prompt once:
290
+
291
+ > "What's a concrete user action that proves it — something you could demo?"
292
+
293
+ Capture outcomes. If they list more than 3, keep the 3 most concrete and user-observable.
294
+
295
+ **If `--auto`:** Derive success outcomes from scoped capabilities — "user can [primary action]" for each major in-scope cluster.
296
+
297
+ ## 8. Open Questions
298
+
299
+ Surface anything that needs resolving in new-milestone or early research — not as a blocker, but as a signal.
300
+
301
+ Ask (plain text):
302
+
303
+ > "Any open questions or risks you want the planning session to address early?"
304
+
305
+ If they say "no" or give nothing: record "None — scope is clear."
306
+ If they give questions: capture them for the MILESTONE-CONTEXT.md open_questions section.
307
+
308
+ **If `--auto`:** Skip. Default to "None."
309
+
310
+ ## 9. Write MILESTONE-CONTEXT.md
311
+
312
+ Write to `.planning/MILESTONE-CONTEXT.md`:
313
+
314
+ ```markdown
315
+ # Milestone Context
316
+
317
+ **Gathered:** [ISO date]
318
+ **Status:** Ready for /gsd-new-milestone
319
+
320
+ <milestone_goal>
321
+ ## Goal
322
+
323
+ [One sentence distilled from step 4 — what this milestone delivers for users]
324
+
325
+ </milestone_goal>
326
+
327
+ <scope>
328
+ ## Scope
329
+
330
+ ### In this milestone
331
+
332
+ [For each scoped-in capability, in priority order:]
333
+ - **[Capability name]**: [1-line description of what it means for users]
334
+
335
+ ### Explicitly out of scope
336
+
337
+ [For each explicit exclusion:]
338
+ - **[Capability name]**: [reason — "deferred to next milestone", "separate product area", etc.]
339
+
340
+ [If no explicit exclusions: "No explicit exclusions — boundary is the in-scope list above"]
341
+
342
+ </scope>
343
+
344
+ <constraints>
345
+ ## Constraints
346
+
347
+ [For each constraint from step 6:]
348
+ - [Constraint statement]
349
+
350
+ [If none: "None — unconstrained milestone"]
351
+
352
+ </constraints>
353
+
354
+ <success>
355
+ ## Success Definition
356
+
357
+ This milestone is successful when:
358
+ - [Observable user outcome 1]
359
+ - [Observable user outcome 2]
360
+ [- [Observable user outcome 3] — only if genuinely distinct]
361
+
362
+ </success>
363
+
364
+ <open_questions>
365
+ ## Open Questions for Planning
366
+
367
+ [Questions from step 8 that new-milestone or early research should address:]
368
+ - [Question]
369
+
370
+ [If none: "None — scope is clear"]
371
+
372
+ </open_questions>
373
+
374
+ ---
375
+
376
+ *Milestone context gathered: [date]*
377
+ *Run /gsd-new-milestone to start planning*
378
+ ```
379
+
380
+ ## 10. Commit
381
+
382
+ ```bash
383
+ pi-gsd-tools commit "docs: capture milestone context" --files .planning/MILESTONE-CONTEXT.md
384
+ ```
385
+
386
+ If `commit_docs` is false: skip commit silently.
387
+
388
+ ## 11. Present Summary and Next Steps
389
+
390
+ ```
391
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
392
+ GSD ► MILESTONE CONTEXT CAPTURED ✓
393
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
394
+
395
+ **Goal:** [one sentence from context file]
396
+
397
+ **In scope ([N] capabilities):**
398
+ [bullet list]
399
+
400
+ **Constraints:** [list or "none"]
401
+ **Success:** [first observable outcome]
402
+
403
+ ───────────────────────────────────────────────────────────────
404
+
405
+ ## ▶ Next Up
406
+
407
+ **Start milestone planning** — requirements, research, roadmap
408
+
409
+ `/gsd-new-milestone`
410
+
411
+ <sub>`/new` first → fresh context window</sub>
412
+
413
+ ───────────────────────────────────────────────────────────────
414
+ ```
415
+
416
+ ## 12. Auto-Advance
417
+
418
+ 1. Parse `--auto` flag from $ARGUMENTS.
419
+ 2. Sync chain flag with intent — clear if not in `--auto` run:
420
+ ```bash
421
+ if [[ ! "$ARGUMENTS" =~ --auto ]]; then
422
+ pi-gsd-tools config-set workflow._auto_chain_active false 2>/dev/null
423
+ fi
424
+ ```
425
+ 3. Read chain flag and config:
426
+ ```bash
427
+ AUTO_CHAIN=$(pi-gsd-tools config-get workflow._auto_chain_active 2>/dev/null || echo "false")
428
+ AUTO_CFG=$(pi-gsd-tools config-get workflow.auto_advance 2>/dev/null || echo "false")
429
+ ```
430
+
431
+ **If `--auto` OR `AUTO_CHAIN` is true OR `AUTO_CFG` is true:**
432
+
433
+ Display:
434
+ ```
435
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
436
+ GSD ► AUTO-ADVANCING TO NEW-MILESTONE
437
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
438
+
439
+ Context captured. Launching new-milestone...
440
+ ```
441
+
442
+ Launch:
443
+ ```
444
+ Skill(skill="gsd-new-milestone", args="--auto ${GSD_WS}")
445
+ ```
446
+
447
+ Handle return:
448
+ - **MILESTONE INITIALIZED** → Display success banner, done.
449
+ - **CHECKPOINT / BLOCKED** → Stop chain, show: `Continue: /gsd-new-milestone ${GSD_WS}`
450
+
451
+ **If not auto:** Step 11 already shown. Done.
452
+
453
+ </process>
454
+
455
+ <success_criteria>
456
+ - [ ] .planning/ exists (state_exists check)
457
+ - [ ] Existing MILESTONE-CONTEXT.md handled (update/view/skip)
458
+ - [ ] Last completed milestone surfaced for retrospective framing
459
+ - [ ] Milestone intent gathered via open conversation
460
+ - [ ] Scope in/out defined per capability cluster
461
+ - [ ] Hard constraints captured
462
+ - [ ] Success definition captured as observable user outcomes
463
+ - [ ] Open questions captured for planning session
464
+ - [ ] MILESTONE-CONTEXT.md written to .planning/
465
+ - [ ] Committed (if commit_docs)
466
+ - [ ] User knows next step: /gsd-new-milestone
467
+ </success_criteria>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-gsd",
3
- "version": "2.0.24",
3
+ "version": "2.1.1",
4
4
  "description": "Get Shit Done - Unofficial port of the renowned AI-native project-planning spec-driven toolkit",
5
5
  "main": "dist/pi-gsd-tools.js",
6
6
  "bin": {
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Runs automatically after `npm install pi-gsd` (or `pi install npm:pi-gsd`).
6
6
  * Copies runtime harness content from this package's `gsd/` into the consumer
7
- * project's `.pi/gsd/`, and hook scripts into `.pi/hooks/`.
7
+ * project's `.pi/gsd/`, and hook scripts into `.pi/gsd/hooks/`.
8
8
  *
9
9
  * Version detection: each installed .md file carries a `<gsd-version v="X.Y.Z" />`
10
10
  * tag stamped with the npm package version that wrote it. On reinstall we read
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * Excluded from the `.pi/gsd/` copy:
16
16
  * gsd/prompts/ → served from npm package via pi.prompts (not installed locally)
17
- * gsd/hooks/ → copied to .pi/hooks/ instead
17
+ * gsd/hooks/ → copied to .pi/gsd/hooks/ instead
18
18
  */
19
19
 
20
20
  "use strict";
@@ -131,7 +131,7 @@ function main() {
131
131
  const gsdSrc = path.join(PKG_DIR, "gsd");
132
132
  const gsdDest = path.join(PROJECT_ROOT, ".pi", "gsd");
133
133
  const hooksSrc = path.join(gsdSrc, "hooks");
134
- const hooksDest = path.join(PROJECT_ROOT, ".pi", "hooks");
134
+ const hooksDest = path.join(PROJECT_ROOT, ".pi", "gsd", "hooks");
135
135
 
136
136
  // Detect installed version from a sample file's <gsd-version> tag.
137
137
  const sample = path.join(gsdDest, "workflows", "plan-phase.md");
@@ -164,11 +164,23 @@ function main() {
164
164
  log("skip", `.pi/gsd (v${pkgVersion} already installed, ${gsdSkipped} files skipped)`);
165
165
  }
166
166
 
167
- // ── Hook scripts → .pi/hooks/ ─────────────────────────────────────────────
167
+ // ── Hook scripts → .pi/gsd/hooks/ ──────────────────────────────────────────
168
168
  if (fs.existsSync(hooksSrc)) {
169
169
  const { copied: hCopied } = copyDir(hooksSrc, hooksDest, overwriteAll, null, pkgVersion);
170
170
  if (hCopied > 0) {
171
- log("ok", `.pi/hooks (${hCopied} hook${hCopied === 1 ? "" : "s"} installed)`);
171
+ log("ok", `.pi/gsd/hooks (${hCopied} hook${hCopied === 1 ? "" : "s"} installed)`);
172
+ }
173
+ }
174
+
175
+ // ── Cleanup: stale .pi/hooks/ from old layout (pi ≥0.35 renames this dir) ──
176
+ const oldHooksDir = path.join(PROJECT_ROOT, ".pi", "hooks");
177
+ if (fs.existsSync(oldHooksDir)) {
178
+ const oldEntries = fs.readdirSync(oldHooksDir);
179
+ const allGsd = oldEntries.every((f) => f.startsWith("gsd-") && f.endsWith(".js"));
180
+ if (allGsd) {
181
+ for (const f of oldEntries) fs.rmSync(path.join(oldHooksDir, f));
182
+ fs.rmdirSync(oldHooksDir);
183
+ log("ok", ".pi/hooks (removed - hooks moved to .pi/gsd/hooks/)");
172
184
  }
173
185
  }
174
186