pi-gsd 2.0.24 → 2.1.0
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/dist/pi-gsd-tools.js +1 -1
- package/gsd/prompts/gsd-discuss-milestone.md +7 -0
- package/gsd/templates/milestone-context.md +231 -0
- package/gsd/workflows/complete-milestone.md +7 -1
- package/gsd/workflows/discuss-milestone.md +467 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +17 -5
package/dist/pi-gsd-tools.js
CHANGED
|
@@ -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
|
|
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.0",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** -
|
|
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
package/scripts/postinstall.js
CHANGED
|
@@ -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
|
|