pi-gsd 1.4.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/pi-gsd.svg)](https://www.npmjs.com/package/pi-gsd)
6
6
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
- [![skills: 57](https://img.shields.io/badge/skills-57-orange.svg)](#skills)
7
+ [![skills: 60](https://img.shields.io/badge/skills-55-orange.svg)](#skills)
8
8
 
9
9
  GSD is a structured software-delivery framework for AI coding agents. It wraps any AI coding session with a six-step phase lifecycle, 57 slash commands, 18 specialized subagents, background hooks, and model profiles - all backed by a git-committed `.planning/` directory that survives context resets.
10
10
 
@@ -38,7 +38,7 @@ After install, run your first GSD command:
38
38
 
39
39
  | Artifact | Count | Description |
40
40
  | ---------- | ----: | ----------------------------------------------------------------- |
41
- | Skills | 57 | pi skill definitions (`/gsd-*`) loaded automatically |
41
+ | Skills | 55 | pi skill definitions (`/gsd-*`) loaded automatically |
42
42
  | CLI binary | 1 | `pi-gsd-tools` - state management, scaffolding, model routing |
43
43
  | Hooks | 5 | Background hooks (context monitor, workflow guard, statusline, …) |
44
44
 
@@ -123,8 +123,8 @@ Switch profile: `/gsd-set-profile <profile>`
123
123
  | Workstreams | ✔️ | ✔️ | Full workstream isolation |
124
124
  | 4 model profiles | ✔️ | ✔️ | quality / balanced / budget / inherit |
125
125
  | 18 subagents | ✔️ | ✔️ | Identical agent definitions |
126
- | 57 GSD skills | ✔️ | ✔️ | All commands available via pi prompt dispatcher (replaces skill system) |
127
- | Different skills paths for pi | ✔️ | ⚡ | All 57 skills moved to `.pi/gsd/` to enable advanced pi-gsd-tools integration |
126
+ | 55 GSD skills | ✔️ | ✔️ | All commands available via pi prompt dispatcher (replaces skill system) |
127
+ | Different skills paths for pi | ✔️ | ⚡ | All 55 skills moved to `.pi/gsd/` to enable advanced pi-gsd-tools integration |
128
128
  | pi harness (`.pi/`) | ❌ | ✔️ | New - GSD installs into pi's config dir |
129
129
  | Background hooks (pi) | ❌ | ✔️ | TypeScript extension (`gsd-hooks.ts`) installed via postinstall |
130
130
  | Pi session history ingestion | ❌ | ✔️ | `/gsd-profile-user` reads pi JSONL sessions from `~/.pi/agent/sessions/` |
@@ -140,9 +140,9 @@ Switch profile: `/gsd-set-profile <profile>`
140
140
  | `/gsd-next` auto-advance | ❌ | ✔️ | Deterministic phase routing, pre-fills editor with the correct next command |
141
141
  | Prompt-dispatch for all skills | ❌ | ✔️ | 54 pi prompt templates — clean autocomplete, arg hints, direct workflow dispatch |
142
142
  | `/gsd-plan-milestone` command | ❌ | ✔️ | Plan all unplanned phases — one mode question, scope pre-check per phase, context-safe checkpoint |
143
- | `/gsd-execute-milestone` command | ❌ | ✔️ | Execute all phases scope guardian (pre+post), UAT gates, recovery loop, worktree isolation + merge |
143
+ | `/gsd-execute-milestone` command | ❌ | ✔️ | Execute all phases + scope guardian + auto gap/debt retry loop (insert-phase) + audit→complete→cleanup |
144
144
 
145
- Legend: ✔️ done · ⚡ enhanced · ⚠️ in progress · 📃 planned · ❌ not available
145
+ Legend: ✔️ done · ⚡ enhanced · ❌ not available
146
146
 
147
147
  ---
148
148
 
@@ -273,7 +273,7 @@ Plans:
273
273
 
274
274
  Plans:
275
275
  - [ ] TBD (run /gsd-plan-phase ${g} to break down)
276
- `,P=new RegExp(`(#{2,4}\\s*Phase\\s+0*${d}:[^\\n]*\\n)`,"i"),k=r.match(P);k||S(`Could not find Phase ${e} header`);let $=r.indexOf(k[0]),A=r.slice($+k[0].length).match(/\n#{2,4}\s+Phase\s+\d/i),O=A?$+k[0].length+A.index:r.length;j.default.writeFileSync(t,r.slice(0,O)+v+r.slice(O),"utf-8"),h({phase_number:g,after_phase:e,name:s,slug:a,directory:z(M.default.join(M.default.relative(n,F(n)),"phases",_))},i,g)}function Ls(n,e,s){let i=[],t=[],r=new RegExp(`^${e}\\.(\\d+)-(.+)$`),o=Oe(n,!0).map(a=>{let l=a.match(r);return l?{dir:a,oldDecimal:parseInt(l[1],10),slug:l[2]}:null}).filter(a=>a!==null&&a.oldDecimal>s).sort((a,l)=>l.oldDecimal-a.oldDecimal);for(let a of o){let l=a.oldDecimal-1,c=`${e}.${a.oldDecimal}`,d=`${e}.${l}`,u=`${e}.${l}-${a.slug}`;j.default.renameSync(M.default.join(n,a.dir),M.default.join(n,u)),i.push({from:a.dir,to:u});for(let f of j.default.readdirSync(M.default.join(n,u)))if(f.includes(c)){let m=f.replace(c,d);j.default.renameSync(M.default.join(n,u,f),M.default.join(n,u,m)),t.push({from:f,to:m})}}return{renamedDirs:i,renamedFiles:t}}function Us(n,e){let s=[],i=[],t=Oe(n,!0).map(r=>{let o=r.match(/^(\d+)([A-Z])?(?:\.(\d+))?-(.+)$/i);if(!o)return null;let a=parseInt(o[1],10);return a>e?{dir:r,oldInt:a,letter:o[2]?o[2].toUpperCase():"",decimal:o[3]?parseInt(o[3],10):null,slug:o[4]}:null}).filter(r=>r!==null).sort((r,o)=>r.oldInt!==o.oldInt?o.oldInt-r.oldInt:(o.decimal||0)-(r.decimal||0));for(let r of t){let o=r.oldInt-1,a=String(o).padStart(2,"0"),l=String(r.oldInt).padStart(2,"0"),c=r.letter||"",d=r.decimal!==null?`.${r.decimal}`:"",u=`${l}${c}${d}`,f=`${a}${c}${d}`,m=`${f}-${r.slug}`;j.default.renameSync(M.default.join(n,r.dir),M.default.join(n,m)),s.push({from:r.dir,to:m});for(let p of j.default.readdirSync(M.default.join(n,m)))if(p.startsWith(u)){let y=f+p.slice(u.length);j.default.renameSync(M.default.join(n,m,p),M.default.join(n,m,y)),i.push({from:p,to:y})}}return{renamedDirs:s,renamedFiles:i}}function Gs(n,e,s,i){let t=j.default.readFileSync(n,"utf-8"),r=Q(e);if(t=t.replace(new RegExp(`\\n?#{2,4}\\s*Phase\\s+${r}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|$)`,"i"),""),t=t.replace(new RegExp(`\\n?-\\s*\\[[ x]\\]\\s*.*Phase\\s+${r}[:\\s][^\\n]*`,"gi"),""),t=t.replace(new RegExp(`\\n?\\|\\s*${r}\\.?\\s[^|]*\\|[^\\n]*`,"gi"),""),!s)for(let o=99;o>i;o--){let a=o-1,l=String(o),c=String(a),d=l.padStart(2,"0"),u=c.padStart(2,"0");t=t.replace(new RegExp(`(#{2,4}\\s*Phase\\s+)${l}(\\s*:)`,"gi"),`$1${c}$2`),t=t.replace(new RegExp(`(Phase\\s+)${l}([:\\s])`,"g"),`$1${c}$2`),t=t.replace(new RegExp(`${d}-(\\d{2})`,"g"),`${u}-$1`),t=t.replace(new RegExp(`(\\|\\s*)${l}\\.\\s`,"g"),`$1${c}. `),t=t.replace(new RegExp(`(Depends on:\\*\\*\\s*Phase\\s+)${l}\\b`,"gi"),`$1${c}`)}j.default.writeFileSync(n,t,"utf-8")}function zs(n,e,s,i){e||S("phase number required for phase remove");let t=M.default.join(F(n),"ROADMAP.md"),r=M.default.join(F(n),"phases");j.default.existsSync(t)||S("ROADMAP.md not found");let o=ce(e),a=e.includes("."),l=s.force||!1,c=Oe(r,!0).find(m=>m.startsWith(o+"-")||m===o)||null;if(c&&!l){let m=j.default.readdirSync(M.default.join(r,c)).filter(p=>p.endsWith("-SUMMARY.md")||p==="SUMMARY.md");m.length>0&&S(`Phase ${e} has ${m.length} executed plan(s). Use --force to remove anyway.`)}c&&j.default.rmSync(M.default.join(r,c),{recursive:!0,force:!0});let d=[],u=[];try{let m=a?Ls(r,o.split(".")[0],parseInt(o.split(".")[1],10)):Us(r,parseInt(o,10));d=m.renamedDirs,u=m.renamedFiles}catch{}Gs(t,e,a,parseInt(o,10));let f=M.default.join(F(n),"STATE.md");if(j.default.existsSync(f)){let m=j.default.readFileSync(f,"utf-8"),p=V(m,"Total Phases");p&&(m=Se(m,"Total Phases",String(parseInt(p,10)-1))??m);let y=m.match(/(\bof\s+)(\d+)(\s*(?:\(|phases?))/i);y&&(m=m.replace(/(\bof\s+)(\d+)(\s*(?:\(|phases?))/i,`$1${parseInt(y[2],10)-1}$3`)),te(f,m,n)}h({removed:e,directory_deleted:c,renamed_directories:d,renamed_files:u,roadmap_updated:!0,state_updated:j.default.existsSync(f)},i)}function Vs(n,e,s){e||S("phase number required for phase complete");let i=M.default.join(F(n),"ROADMAP.md"),t=M.default.join(F(n),"STATE.md"),r=M.default.join(F(n),"phases"),o=ce(e),a=new Date().toISOString().split("T")[0],l=ue(n,e);l||S(`Phase ${e} not found`);let c=l.plans.length,d=l.summaries.length,u=!1,f=[];try{let g=M.default.join(n,l.directory),_=j.default.readdirSync(g);for(let b of _.filter(v=>v.includes("-UAT")&&v.endsWith(".md"))){let v=j.default.readFileSync(M.default.join(g,b),"utf-8");/result: pending/.test(v)&&f.push(`${b}: has pending tests`),/result: blocked/.test(v)&&f.push(`${b}: has blocked tests`),/status: partial/.test(v)&&f.push(`${b}: testing incomplete (partial)`),/status: diagnosed/.test(v)&&f.push(`${b}: has diagnosed gaps`)}for(let b of _.filter(v=>v.includes("-VERIFICATION")&&v.endsWith(".md"))){let v=j.default.readFileSync(M.default.join(g,b),"utf-8");/status: human_needed/.test(v)&&f.push(`${b}: needs human verification`),/status: gaps_found/.test(v)&&f.push(`${b}: has unresolved gaps`)}}catch{}if(j.default.existsSync(i)){let g=j.default.readFileSync(i,"utf-8"),_=new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${Q(e)}[:\\s][^\\n]*)`,"i");g=Ze(g,_,`$1x$2 (completed ${a})`);let b=Q(e);g=g.replace(new RegExp(`^(\\|\\s*${b}\\.?\\s[^|]*(?:\\|[^\\n]*))$`,"im"),P=>{let k=P.split("|").slice(1,-1);return k.length===5?(k[3]=" Complete ",k[4]=` ${a} `):k.length===4&&(k[2]=" Complete ",k[3]=` ${a} `),"|"+k.join("|")+"|"}),g=Ze(g,new RegExp(`(#{2,4}\\s*Phase\\s+${b}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,"i"),`$1${d}/${c} plans complete`),j.default.writeFileSync(i,g,"utf-8");let v=M.default.join(F(n),"REQUIREMENTS.md");if(j.default.existsSync(v)){let k=be(g,n).match(new RegExp(`(#{2,4}\\s*Phase\\s+${Q(e)}[:\\s][\\s\\S]*?)(?=#{2,4}\\s*Phase\\s+|$)`,"i")),T=(k?k[1]:"").match(/\*\*Requirements:\*\*\s*([^\n]+)/i);if(T){let A=T[1].replace(/[[\]]/g,"").split(/[,\s]+/).map(N=>N.trim()).filter(Boolean),O=j.default.readFileSync(v,"utf-8");for(let N of A){let L=Q(N);O=O.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${L}\\*\\*)`,"gi"),"$1x$2"),O=O.replace(new RegExp(`(\\|\\s*${L}\\s*\\|[^|]+\\|)\\s*(?:Pending|In Progress)\\s*(\\|)`,"gi"),"$1 Complete $2")}j.default.writeFileSync(v,O,"utf-8"),u=!0}}}let m=null,p=null,y=!0;try{let g=Pe(n),_=j.default.readdirSync(r,{withFileTypes:!0}).filter(b=>b.isDirectory()).map(b=>b.name).filter(g).sort((b,v)=>ke(b,v));for(let b of _){let v=b.match(/^(\d+[A-Z]?(?:\.\d+)*)-?(.*)/i);if(v&&ke(v[1],e)>0){m=v[1],p=v[2]||null,y=!1;break}}}catch{}if(y&&j.default.existsSync(i))try{let g=be(j.default.readFileSync(i,"utf-8"),n),_=/#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi,b;for(;(b=_.exec(g))!==null;)if(ke(b[1],e)>0){m=b[1],p=b[2].replace(/\(INSERTED\)/i,"").trim().toLowerCase().replace(/\s+/g,"-"),y=!1;break}}catch{}if(j.default.existsSync(t)){let g=j.default.readFileSync(t,"utf-8"),_=m||e,b=V(g,"Current Phase")||V(g,"Phase"),v=String(_);if(b){let k=b.match(/of\s+(\d+)/),$=b.match(/\(([^)]+)\)/);if(k){let T=p?` (${p.replace(/-/g," ")})`:$?` (${$[1]})`:"";v=`${_} of ${k[1]}${T}`}}g=he(g,"Current Phase","Phase",v),p&&(g=he(g,"Current Phase Name",null,p.replace(/-/g," "))),g=he(g,"Status",null,y?"Milestone complete":"Ready to plan"),g=he(g,"Current Plan","Plan","Not started"),g=he(g,"Last Activity","Last activity",a),g=he(g,"Last Activity Description",null,`Phase ${e} complete${m?`, transitioned to Phase ${m}`:""}`);let P=V(g,"Completed Phases");if(P){let k=parseInt(P,10)+1;g=Se(g,"Completed Phases",String(k))??g;let $=V(g,"Total Phases");if($){let T=parseInt($,10);if(T>0){let A=Math.round(k/T*100);g=Se(g,"Progress",`${A}%`)??g,g=g.replace(/(percent:\s*)\d+/,`$1${A}`)}}}te(t,g,n)}h({completed_phase:e,phase_name:l.phase_name,plans_executed:`${d}/${c}`,next_phase:m,next_phase_name:p,is_last_phase:y,date:a,roadmap_updated:j.default.existsSync(i),state_updated:j.default.existsSync(t),requirements_updated:u,warnings:f,has_warnings:f.length>0},s)}var j,M,ut=le(()=>{"use strict";j=W(require("fs")),M=W(require("path"));pe();Ue();Be()});var x,yn,Ir,Er,wr,Mr,Hs,Fr,Bs,Js,Ys,kt,Sn=le(()=>{"use strict";x=require("zod"),yn=x.z.object({milestone:x.z.string().optional(),milestone_name:x.z.string().optional(),current_phase:x.z.string().optional(),current_phase_name:x.z.string().optional(),current_plan:x.z.string().optional(),total_phases:x.z.coerce.number().int().nonnegative().optional(),total_plans_in_phase:x.z.coerce.number().int().nonnegative().optional(),status:x.z.string().optional(),progress:x.z.string().optional(),last_activity:x.z.string().optional(),paused_at:x.z.string().optional(),stopped_at:x.z.string().optional()}).passthrough(),Ir=x.z.object({found:x.z.literal(!0),phase_number:x.z.string(),phase_name:x.z.string(),goal:x.z.string().nullable(),success_criteria:x.z.array(x.z.string()).default([]),section:x.z.string().optional()}).passthrough(),Er=x.z.object({phase:x.z.union([x.z.string(),x.z.number()]),plan:x.z.union([x.z.string(),x.z.number()]),type:x.z.string(),wave:x.z.union([x.z.string(),x.z.number()]),depends_on:x.z.union([x.z.string(),x.z.array(x.z.string())]),files_modified:x.z.union([x.z.string(),x.z.array(x.z.string())]),autonomous:x.z.union([x.z.boolean(),x.z.string()]),must_haves:x.z.union([x.z.string(),x.z.array(x.z.string())])}).passthrough(),wr=x.z.object({phase:x.z.union([x.z.string(),x.z.number()]),plan:x.z.union([x.z.string(),x.z.number()]),subsystem:x.z.string(),tags:x.z.union([x.z.string(),x.z.array(x.z.string())]),duration:x.z.string(),completed:x.z.string()}).passthrough(),Mr=x.z.object({phase:x.z.union([x.z.string(),x.z.number()]),verified:x.z.union([x.z.boolean(),x.z.string()]),status:x.z.string(),score:x.z.union([x.z.number(),x.z.string()])}).passthrough(),Hs=x.z.object({test:x.z.number().int().positive().optional(),name:x.z.string(),expected:x.z.string().optional(),result:x.z.string(),category:x.z.string(),reason:x.z.string().optional(),blocked_by:x.z.string().optional()}).passthrough(),Fr=x.z.object({phase:x.z.string(),phase_dir:x.z.string(),file:x.z.string(),file_path:x.z.string(),type:x.z.literal("uat"),status:x.z.string(),items:x.z.array(Hs)}).passthrough(),Bs=x.z.object({branching_strategy:x.z.enum(["none","phase","milestone","workstream"]).default("none"),phase_branch_template:x.z.string().default("gsd/phase-{phase}-{slug}"),milestone_branch_template:x.z.string().default("gsd/{milestone}-{slug}"),quick_branch_template:x.z.string().nullable().default(null)}).passthrough(),Js=x.z.object({research:x.z.boolean().default(!0),plan_check:x.z.boolean().default(!0),verifier:x.z.boolean().default(!0),nyquist_validation:x.z.boolean().default(!0),auto_advance:x.z.boolean().default(!1),node_repair:x.z.boolean().default(!0),node_repair_budget:x.z.number().int().nonnegative().default(2),ui_phase:x.z.boolean().default(!0),ui_safety_gate:x.z.boolean().default(!0),text_mode:x.z.boolean().default(!1),research_before_questions:x.z.boolean().default(!1),discuss_mode:x.z.string().default("discuss"),skip_discuss:x.z.boolean().default(!1),_auto_chain_active:x.z.boolean().default(!1)}).passthrough(),Ys=x.z.object({context_warnings:x.z.boolean().default(!0),workflow_guard:x.z.boolean().default(!1)}).passthrough(),kt=x.z.object({model_profile:x.z.enum(["quality","balanced","budget","inherit"]).default("balanced"),commit_docs:x.z.boolean().default(!0),parallelization:x.z.boolean().default(!0),search_gitignored:x.z.boolean().default(!1),brave_search:x.z.boolean().default(!1),firecrawl:x.z.boolean().default(!1),exa_search:x.z.boolean().default(!1),git:Bs.default({}),workflow:Js.default({}),hooks:Ys.default({}),agent_skills:x.z.record(x.z.string(),x.z.unknown()).default({})}).passthrough()});var Pt={};fe(Pt,{cmdValidateAgents:()=>oi,cmdValidateConsistency:()=>ii,cmdValidateHealth:()=>ri,cmdVerifyArtifacts:()=>ni,cmdVerifyCommits:()=>ti,cmdVerifyKeyLinks:()=>si,cmdVerifyPhaseCompleteness:()=>Xs,cmdVerifyPlanStructure:()=>Qs,cmdVerifyReferences:()=>ei,cmdVerifySummary:()=>Zs});function Ks(n,e){let s=n;for(let i of e){if(s==null||typeof s!="object")return;s=s[i]}return s}function Zs(n,e,s,i){e||S("summary-path required");let t=Y.default.join(n,e),r=s||2;if(!K.default.existsSync(t)){h({passed:!1,checks:{summary_exists:!1,files_created:{checked:0,found:0,missing:[]},commits_exist:!1,self_check:"not_found"},errors:["SUMMARY.md not found"]},i,"failed");return}let o=K.default.readFileSync(t,"utf-8"),a=[],l=new Set;for(let y of[/`([^`]+\.[a-zA-Z]+)`/g,/(?:Created|Modified|Added|Updated|Edited):\s*`?([^\s`]+\.[a-zA-Z]+)`?/gi]){let g;for(;(g=y.exec(o))!==null;)g[1]&&!g[1].startsWith("http")&&g[1].includes("/")&&l.add(g[1])}let c=Array.from(l).slice(0,r),d=c.filter(y=>!K.default.existsSync(Y.default.join(n,y))),u=o.match(/\b[0-9a-f]{7,40}\b/g)||[],f=!1;for(let y of u.slice(0,3))if(oe(n,["cat-file","-t",y]).stdout==="commit"){f=!0;break}let m="not_found";if(/##\s*(?:Self[- ]?Check|Verification|Quality Check)/i.test(o)){let y=o.slice(o.search(/##\s*(?:Self[- ]?Check|Verification|Quality Check)/i));/(?:fail|✗|❌|incomplete|blocked)/i.test(y)?m="failed":/(?:all\s+)?(?:pass|✓|✅|complete|succeeded)/i.test(y)&&(m="passed")}d.length>0&&a.push("Missing files: "+d.join(", ")),!f&&u.length>0&&a.push("Referenced commit hashes not found in git history"),m==="failed"&&a.push("Self-check section indicates failure");let p=d.length===0&&m!=="failed";h({passed:p,checks:{summary_exists:!0,files_created:{checked:c.length,found:c.length-d.length,missing:d},commits_exist:f,self_check:m},errors:a},i,p?"passed":"failed")}function Qs(n,e,s){e||S("file path required");let i=Y.default.isAbsolute(e)?e:Y.default.join(n,e),t=we(i);if(!t){h({error:"File not found",path:e},s);return}let r=ee(t),o=[],a=[];for(let f of["phase","plan","type","wave","depends_on","files_modified","autonomous","must_haves"])r[f]===void 0&&o.push(`Missing required frontmatter field: ${f}`);let l=/<task[^>]*>([\s\S]*?)<\/task>/g,c=[],d;for(;(d=l.exec(t))!==null;){let f=d[1],m=f.match(/<name>([\s\S]*?)<\/name>/),p=m?m[1].trim():"unnamed",y=/<files>/.test(f),g=/<action>/.test(f),_=/<verify>/.test(f),b=/<done>/.test(f);m||o.push("Task missing <name> element"),g||o.push(`Task '${p}' missing <action>`),_||a.push(`Task '${p}' missing <verify>`),b||a.push(`Task '${p}' missing <done>`),y||a.push(`Task '${p}' missing <files>`),c.push({name:p,hasFiles:y,hasAction:g,hasVerify:_,hasDone:b})}c.length===0&&a.push("No <task> elements found"),r.wave&&parseInt(String(r.wave))>1&&(!r.depends_on||Array.isArray(r.depends_on)&&r.depends_on.length===0)&&a.push("Wave > 1 but depends_on is empty"),/<task\s+type=["']?checkpoint/.test(t)&&r.autonomous!=="false"&&r.autonomous!==!1&&o.push("Has checkpoint tasks but autonomous is not false"),h({valid:o.length===0,errors:o,warnings:a,task_count:c.length,tasks:c,frontmatter_fields:Object.keys(r)},s,o.length===0?"valid":"invalid")}function Xs(n,e,s){e||S("phase required");let i=ue(n,e);if(!i?.found){h({error:"Phase not found",phase:e},s);return}let t=Y.default.join(n,i.directory),r=[],o=[],a;try{a=K.default.readdirSync(t)}catch{h({error:"Cannot read phase directory"},s);return}let l=a.filter(p=>p.match(/-PLAN\.md$/i)),c=a.filter(p=>p.match(/-SUMMARY\.md$/i)),d=new Set(l.map(p=>p.replace(/-PLAN\.md$/i,""))),u=new Set(c.map(p=>p.replace(/-SUMMARY\.md$/i,""))),f=[...d].filter(p=>!u.has(p)),m=[...u].filter(p=>!d.has(p));f.length>0&&r.push(`Plans without summaries: ${f.join(", ")}`),m.length>0&&o.push(`Summaries without plans: ${m.join(", ")}`),h({complete:r.length===0,phase:i.phase_number,plan_count:l.length,summary_count:c.length,incomplete_plans:f,orphan_summaries:m,errors:r,warnings:o},s,r.length===0?"complete":"incomplete")}function ei(n,e,s){e||S("file path required");let i=Y.default.isAbsolute(e)?e:Y.default.join(n,e),t=we(i);if(!t){h({error:"File not found",path:e},s);return}let r=[],o=[];for(let a of t.match(/@([^\s\n,)]+\/[^\s\n,)]+)/g)||[]){let l=a.slice(1),c=l.startsWith("~/")?Y.default.join(process.env.HOME??"",l.slice(2)):Y.default.join(n,l);(K.default.existsSync(c)?r:o).push(l)}for(let a of t.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10})`/g)||[]){let l=a.slice(1,-1);l.startsWith("http")||l.includes("${")||l.includes("{{")||r.includes(l)||o.includes(l)||(K.default.existsSync(Y.default.join(n,l))?r:o).push(l)}h({valid:o.length===0,found:r.length,missing:o,total:r.length+o.length},s,o.length===0?"valid":"invalid")}function ti(n,e,s){(!e||e.length===0)&&S("At least one commit hash required");let i=[],t=[];for(let r of e)(oe(n,["cat-file","-t",r]).stdout.trim()==="commit"?i:t).push(r);h({all_valid:t.length===0,valid:i,invalid:t,total:e.length},s,t.length===0?"valid":"invalid")}function ni(n,e,s){e||S("plan file path required");let i=Y.default.isAbsolute(e)?e:Y.default.join(n,e),t=we(i);if(!t){h({error:"File not found",path:e},s);return}let r=St(t,"artifacts");if(r.length===0){h({error:"No must_haves.artifacts found in frontmatter",path:e},s);return}let o=[];for(let l of r){if(typeof l=="string")continue;let c=l;if(!c.path)continue;let d=Y.default.join(n,c.path),u=K.default.existsSync(d),f={path:c.path,exists:u,issues:[],passed:!1};if(u){let m=we(d)??"",p=m.split(`
276
+ `,P=new RegExp(`(#{2,4}\\s*Phase\\s+0*${d}:[^\\n]*\\n)`,"i"),k=r.match(P);k||S(`Could not find Phase ${e} header`);let $=r.indexOf(k[0]),A=r.slice($+k[0].length).match(/\n#{2,4}\s+Phase\s+\d/i),O=A?$+k[0].length+A.index:r.length;j.default.writeFileSync(t,r.slice(0,O)+v+r.slice(O),"utf-8"),h({phase_number:g,after_phase:e,name:s,slug:a,directory:z(M.default.join(M.default.relative(n,F(n)),"phases",_))},i,g)}function Ls(n,e,s){let i=[],t=[],r=new RegExp(`^${e}\\.(\\d+)-(.+)$`),o=Oe(n,!0).map(a=>{let l=a.match(r);return l?{dir:a,oldDecimal:parseInt(l[1],10),slug:l[2]}:null}).filter(a=>a!==null&&a.oldDecimal>s).sort((a,l)=>l.oldDecimal-a.oldDecimal);for(let a of o){let l=a.oldDecimal-1,c=`${e}.${a.oldDecimal}`,d=`${e}.${l}`,u=`${e}.${l}-${a.slug}`;j.default.renameSync(M.default.join(n,a.dir),M.default.join(n,u)),i.push({from:a.dir,to:u});for(let f of j.default.readdirSync(M.default.join(n,u)))if(f.includes(c)){let m=f.replace(c,d);j.default.renameSync(M.default.join(n,u,f),M.default.join(n,u,m)),t.push({from:f,to:m})}}return{renamedDirs:i,renamedFiles:t}}function Us(n,e){let s=[],i=[],t=Oe(n,!0).map(r=>{let o=r.match(/^(\d+)([A-Z])?(?:\.(\d+))?-(.+)$/i);if(!o)return null;let a=parseInt(o[1],10);return a>e?{dir:r,oldInt:a,letter:o[2]?o[2].toUpperCase():"",decimal:o[3]?parseInt(o[3],10):null,slug:o[4]}:null}).filter(r=>r!==null).sort((r,o)=>r.oldInt!==o.oldInt?o.oldInt-r.oldInt:(o.decimal||0)-(r.decimal||0));for(let r of t){let o=r.oldInt-1,a=String(o).padStart(2,"0"),l=String(r.oldInt).padStart(2,"0"),c=r.letter||"",d=r.decimal!==null?`.${r.decimal}`:"",u=`${l}${c}${d}`,f=`${a}${c}${d}`,m=`${f}-${r.slug}`;j.default.renameSync(M.default.join(n,r.dir),M.default.join(n,m)),s.push({from:r.dir,to:m});for(let p of j.default.readdirSync(M.default.join(n,m)))if(p.startsWith(u)){let y=f+p.slice(u.length);j.default.renameSync(M.default.join(n,m,p),M.default.join(n,m,y)),i.push({from:p,to:y})}}return{renamedDirs:s,renamedFiles:i}}function Gs(n,e,s,i){let t=j.default.readFileSync(n,"utf-8"),r=Q(e);if(t=t.replace(new RegExp(`\\n?#{2,4}\\s*Phase\\s+${r}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|$)`,"i"),""),t=t.replace(new RegExp(`\\n?-\\s*\\[[ x]\\]\\s*.*Phase\\s+${r}[:\\s][^\\n]*`,"gi"),""),t=t.replace(new RegExp(`\\n?\\|\\s*${r}\\.?\\s[^|]*\\|[^\\n]*`,"gi"),""),!s)for(let o=99;o>i;o--){let a=o-1,l=String(o),c=String(a),d=l.padStart(2,"0"),u=c.padStart(2,"0");t=t.replace(new RegExp(`(#{2,4}\\s*Phase\\s+)${l}(\\s*:)`,"gi"),`$1${c}$2`),t=t.replace(new RegExp(`(Phase\\s+)${l}([:\\s])`,"g"),`$1${c}$2`),t=t.replace(new RegExp(`${d}-(\\d{2})`,"g"),`${u}-$1`),t=t.replace(new RegExp(`(\\|\\s*)${l}\\.\\s`,"g"),`$1${c}. `),t=t.replace(new RegExp(`(Depends on:\\*\\*\\s*Phase\\s+)${l}\\b`,"gi"),`$1${c}`)}j.default.writeFileSync(n,t,"utf-8")}function zs(n,e,s,i){e||S("phase number required for phase remove");let t=M.default.join(F(n),"ROADMAP.md"),r=M.default.join(F(n),"phases");j.default.existsSync(t)||S("ROADMAP.md not found");let o=ce(e),a=e.includes("."),l=s.force||!1,c=Oe(r,!0).find(m=>m.startsWith(o+"-")||m===o)||null;if(c&&!l){let m=j.default.readdirSync(M.default.join(r,c)).filter(p=>p.endsWith("-SUMMARY.md")||p==="SUMMARY.md");m.length>0&&S(`Phase ${e} has ${m.length} executed plan(s). Use --force to remove anyway.`)}c&&j.default.rmSync(M.default.join(r,c),{recursive:!0,force:!0});let d=[],u=[];try{let m=a?Ls(r,o.split(".")[0],parseInt(o.split(".")[1],10)):Us(r,parseInt(o,10));d=m.renamedDirs,u=m.renamedFiles}catch{}Gs(t,e,a,parseInt(o,10));let f=M.default.join(F(n),"STATE.md");if(j.default.existsSync(f)){let m=j.default.readFileSync(f,"utf-8"),p=V(m,"Total Phases");p&&(m=Se(m,"Total Phases",String(parseInt(p,10)-1))??m);let y=m.match(/(\bof\s+)(\d+)(\s*(?:\(|phases?))/i);y&&(m=m.replace(/(\bof\s+)(\d+)(\s*(?:\(|phases?))/i,`$1${parseInt(y[2],10)-1}$3`)),te(f,m,n)}h({removed:e,directory_deleted:c,renamed_directories:d,renamed_files:u,roadmap_updated:!0,state_updated:j.default.existsSync(f)},i)}function Vs(n,e,s){e||S("phase number required for phase complete");let i=M.default.join(F(n),"ROADMAP.md"),t=M.default.join(F(n),"STATE.md"),r=M.default.join(F(n),"phases"),o=ce(e),a=new Date().toISOString().split("T")[0],l=ue(n,e);l||S(`Phase ${e} not found`);let c=l.plans.length,d=l.summaries.length,u=!1,f=[];try{let g=M.default.join(n,l.directory),_=j.default.readdirSync(g);for(let b of _.filter(v=>v.includes("-UAT")&&v.endsWith(".md"))){let v=j.default.readFileSync(M.default.join(g,b),"utf-8");/result: pending/.test(v)&&f.push(`${b}: has pending tests`),/result: blocked/.test(v)&&f.push(`${b}: has blocked tests`),/status: partial/.test(v)&&f.push(`${b}: testing incomplete (partial)`),/status: diagnosed/.test(v)&&f.push(`${b}: has diagnosed gaps`)}for(let b of _.filter(v=>v.includes("-VERIFICATION")&&v.endsWith(".md"))){let v=j.default.readFileSync(M.default.join(g,b),"utf-8");/status: human_needed/.test(v)&&f.push(`${b}: needs human verification`),/status: gaps_found/.test(v)&&f.push(`${b}: has unresolved gaps`)}}catch{}if(j.default.existsSync(i)){let g=j.default.readFileSync(i,"utf-8"),_=new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${Q(e)}[:\\s][^\\n]*)`,"i");g=Ze(g,_,`$1x$2 (completed ${a})`);let b=Q(e);g=g.replace(new RegExp(`^(\\|\\s*${b}\\.?\\s[^|]*(?:\\|[^\\n]*))$`,"im"),P=>{let k=P.split("|").slice(1,-1);return k.length===5?(k[3]=" Complete ",k[4]=` ${a} `):k.length===4&&(k[2]=" Complete ",k[3]=` ${a} `),"|"+k.join("|")+"|"}),g=Ze(g,new RegExp(`(#{2,4}\\s*Phase\\s+${b}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,"i"),`$1${d}/${c} plans complete`),j.default.writeFileSync(i,g,"utf-8");let v=M.default.join(F(n),"REQUIREMENTS.md");if(j.default.existsSync(v)){let k=be(g,n).match(new RegExp(`(#{2,4}\\s*Phase\\s+${Q(e)}[:\\s][\\s\\S]*?)(?=#{2,4}\\s*Phase\\s+|$)`,"i")),T=(k?k[1]:"").match(/\*\*Requirements:\*\*\s*([^\n]+)/i);if(T){let A=T[1].replace(/[[\]]/g,"").split(/[,\s]+/).map(N=>N.trim()).filter(Boolean),O=j.default.readFileSync(v,"utf-8");for(let N of A){let L=Q(N);O=O.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${L}\\*\\*)`,"gi"),"$1x$2"),O=O.replace(new RegExp(`(\\|\\s*${L}\\s*\\|[^|]+\\|)\\s*(?:Pending|In Progress)\\s*(\\|)`,"gi"),"$1 Complete $2")}j.default.writeFileSync(v,O,"utf-8"),u=!0}}}let m=null,p=null,y=!0;try{let g=Pe(n),_=j.default.readdirSync(r,{withFileTypes:!0}).filter(b=>b.isDirectory()).map(b=>b.name).filter(g).sort((b,v)=>ke(b,v));for(let b of _){let v=b.match(/^(\d+[A-Z]?(?:\.\d+)*)-?(.*)/i);if(v&&ke(v[1],e)>0){m=v[1],p=v[2]||null,y=!1;break}}}catch{}if(y&&j.default.existsSync(i))try{let g=be(j.default.readFileSync(i,"utf-8"),n),_=/#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi,b;for(;(b=_.exec(g))!==null;)if(ke(b[1],e)>0){m=b[1],p=b[2].replace(/\(INSERTED\)/i,"").trim().toLowerCase().replace(/\s+/g,"-"),y=!1;break}}catch{}if(j.default.existsSync(t)){let g=j.default.readFileSync(t,"utf-8"),_=m||e,b=V(g,"Current Phase")||V(g,"Phase"),v=String(_);if(b){let k=b.match(/of\s+(\d+)/),$=b.match(/\(([^)]+)\)/);if(k){let T=p?` (${p.replace(/-/g," ")})`:$?` (${$[1]})`:"";v=`${_} of ${k[1]}${T}`}}g=he(g,"Current Phase","Phase",v),p&&(g=he(g,"Current Phase Name",null,p.replace(/-/g," "))),g=he(g,"Status",null,y?"Milestone complete":"Ready to plan"),g=he(g,"Current Plan","Plan","Not started"),g=he(g,"Last Activity","Last activity",a),g=he(g,"Last Activity Description",null,`Phase ${e} complete${m?`, transitioned to Phase ${m}`:""}`);let P=V(g,"Completed Phases");if(P){let k=parseInt(P,10)+1;g=Se(g,"Completed Phases",String(k))??g;let $=V(g,"Total Phases");if($){let T=parseInt($,10);if(T>0){let A=Math.round(k/T*100);g=Se(g,"Progress",`${A}%`)??g,g=g.replace(/(percent:\s*)\d+/,`$1${A}`)}}}te(t,g,n)}h({completed_phase:e,phase_name:l.phase_name,plans_executed:`${d}/${c}`,next_phase:m,next_phase_name:p,is_last_phase:y,date:a,roadmap_updated:j.default.existsSync(i),state_updated:j.default.existsSync(t),requirements_updated:u,warnings:f,has_warnings:f.length>0},s)}var j,M,ut=le(()=>{"use strict";j=W(require("fs")),M=W(require("path"));pe();Ue();Be()});var x,yn,Ir,Er,wr,Mr,Hs,Fr,Bs,Js,Ys,kt,Sn=le(()=>{"use strict";x=require("zod"),yn=x.z.object({milestone:x.z.string().optional(),milestone_name:x.z.string().optional(),current_phase:x.z.string().optional(),current_phase_name:x.z.string().optional(),current_plan:x.z.string().optional(),total_phases:x.z.coerce.number().int().nonnegative().optional(),total_plans_in_phase:x.z.coerce.number().int().nonnegative().optional(),status:x.z.string().optional(),progress:x.z.string().optional(),last_activity:x.z.string().optional(),paused_at:x.z.string().optional(),stopped_at:x.z.string().optional()}).passthrough(),Ir=x.z.object({found:x.z.literal(!0),phase_number:x.z.string(),phase_name:x.z.string(),goal:x.z.string().nullable(),success_criteria:x.z.array(x.z.string()).default([]),section:x.z.string().optional()}).passthrough(),Er=x.z.object({phase:x.z.union([x.z.string(),x.z.number()]),plan:x.z.union([x.z.string(),x.z.number()]),type:x.z.string(),wave:x.z.union([x.z.string(),x.z.number()]),depends_on:x.z.union([x.z.string(),x.z.array(x.z.string())]),files_modified:x.z.union([x.z.string(),x.z.array(x.z.string())]),autonomous:x.z.union([x.z.boolean(),x.z.string()]),must_haves:x.z.union([x.z.string(),x.z.array(x.z.string())])}).passthrough(),wr=x.z.object({phase:x.z.union([x.z.string(),x.z.number()]),plan:x.z.union([x.z.string(),x.z.number()]),subsystem:x.z.string(),tags:x.z.union([x.z.string(),x.z.array(x.z.string())]),duration:x.z.string(),completed:x.z.string()}).passthrough(),Mr=x.z.object({phase:x.z.union([x.z.string(),x.z.number()]),verified:x.z.union([x.z.boolean(),x.z.string()]),status:x.z.string(),score:x.z.union([x.z.number(),x.z.string()])}).passthrough(),Hs=x.z.object({test:x.z.number().int().positive().optional(),name:x.z.string(),expected:x.z.string().optional(),result:x.z.string(),category:x.z.string(),reason:x.z.string().optional(),blocked_by:x.z.string().optional()}).passthrough(),Fr=x.z.object({phase:x.z.string(),phase_dir:x.z.string(),file:x.z.string(),file_path:x.z.string(),type:x.z.literal("uat"),status:x.z.string(),items:x.z.array(Hs)}).passthrough(),Bs=x.z.object({branching_strategy:x.z.enum(["none","phase","milestone","workstream"]).default("none"),phase_branch_template:x.z.string().default("gsd/phase-{phase}-{slug}"),milestone_branch_template:x.z.string().default("gsd/{milestone}-{slug}"),quick_branch_template:x.z.string().nullable().default(null)}).passthrough(),Js=x.z.object({research:x.z.boolean().default(!0),plan_check:x.z.boolean().default(!0),verifier:x.z.boolean().default(!0),nyquist_validation:x.z.boolean().default(!0),auto_advance:x.z.boolean().default(!1),node_repair:x.z.boolean().default(!0),node_repair_budget:x.z.number().int().nonnegative().default(2),auto_retry_audit:x.z.boolean().default(!0),auto_retry_audit_budget:x.z.number().int().nonnegative().default(1),auto_retry_tech_debt:x.z.boolean().default(!0),auto_retry_tech_debt_budget:x.z.number().int().nonnegative().default(1),ui_phase:x.z.boolean().default(!0),ui_safety_gate:x.z.boolean().default(!0),text_mode:x.z.boolean().default(!1),research_before_questions:x.z.boolean().default(!1),discuss_mode:x.z.string().default("discuss"),skip_discuss:x.z.boolean().default(!1),_auto_chain_active:x.z.boolean().default(!1)}).passthrough(),Ys=x.z.object({context_warnings:x.z.boolean().default(!0),workflow_guard:x.z.boolean().default(!1)}).passthrough(),kt=x.z.object({model_profile:x.z.enum(["quality","balanced","budget","inherit"]).default("balanced"),commit_docs:x.z.boolean().default(!0),parallelization:x.z.boolean().default(!0),search_gitignored:x.z.boolean().default(!1),brave_search:x.z.boolean().default(!1),firecrawl:x.z.boolean().default(!1),exa_search:x.z.boolean().default(!1),git:Bs.default({}),workflow:Js.default({}),hooks:Ys.default({}),agent_skills:x.z.record(x.z.string(),x.z.unknown()).default({})}).passthrough()});var Pt={};fe(Pt,{cmdValidateAgents:()=>oi,cmdValidateConsistency:()=>ii,cmdValidateHealth:()=>ri,cmdVerifyArtifacts:()=>ni,cmdVerifyCommits:()=>ti,cmdVerifyKeyLinks:()=>si,cmdVerifyPhaseCompleteness:()=>Xs,cmdVerifyPlanStructure:()=>Qs,cmdVerifyReferences:()=>ei,cmdVerifySummary:()=>Zs});function Ks(n,e){let s=n;for(let i of e){if(s==null||typeof s!="object")return;s=s[i]}return s}function Zs(n,e,s,i){e||S("summary-path required");let t=Y.default.join(n,e),r=s||2;if(!K.default.existsSync(t)){h({passed:!1,checks:{summary_exists:!1,files_created:{checked:0,found:0,missing:[]},commits_exist:!1,self_check:"not_found"},errors:["SUMMARY.md not found"]},i,"failed");return}let o=K.default.readFileSync(t,"utf-8"),a=[],l=new Set;for(let y of[/`([^`]+\.[a-zA-Z]+)`/g,/(?:Created|Modified|Added|Updated|Edited):\s*`?([^\s`]+\.[a-zA-Z]+)`?/gi]){let g;for(;(g=y.exec(o))!==null;)g[1]&&!g[1].startsWith("http")&&g[1].includes("/")&&l.add(g[1])}let c=Array.from(l).slice(0,r),d=c.filter(y=>!K.default.existsSync(Y.default.join(n,y))),u=o.match(/\b[0-9a-f]{7,40}\b/g)||[],f=!1;for(let y of u.slice(0,3))if(oe(n,["cat-file","-t",y]).stdout==="commit"){f=!0;break}let m="not_found";if(/##\s*(?:Self[- ]?Check|Verification|Quality Check)/i.test(o)){let y=o.slice(o.search(/##\s*(?:Self[- ]?Check|Verification|Quality Check)/i));/(?:fail|✗|❌|incomplete|blocked)/i.test(y)?m="failed":/(?:all\s+)?(?:pass|✓|✅|complete|succeeded)/i.test(y)&&(m="passed")}d.length>0&&a.push("Missing files: "+d.join(", ")),!f&&u.length>0&&a.push("Referenced commit hashes not found in git history"),m==="failed"&&a.push("Self-check section indicates failure");let p=d.length===0&&m!=="failed";h({passed:p,checks:{summary_exists:!0,files_created:{checked:c.length,found:c.length-d.length,missing:d},commits_exist:f,self_check:m},errors:a},i,p?"passed":"failed")}function Qs(n,e,s){e||S("file path required");let i=Y.default.isAbsolute(e)?e:Y.default.join(n,e),t=we(i);if(!t){h({error:"File not found",path:e},s);return}let r=ee(t),o=[],a=[];for(let f of["phase","plan","type","wave","depends_on","files_modified","autonomous","must_haves"])r[f]===void 0&&o.push(`Missing required frontmatter field: ${f}`);let l=/<task[^>]*>([\s\S]*?)<\/task>/g,c=[],d;for(;(d=l.exec(t))!==null;){let f=d[1],m=f.match(/<name>([\s\S]*?)<\/name>/),p=m?m[1].trim():"unnamed",y=/<files>/.test(f),g=/<action>/.test(f),_=/<verify>/.test(f),b=/<done>/.test(f);m||o.push("Task missing <name> element"),g||o.push(`Task '${p}' missing <action>`),_||a.push(`Task '${p}' missing <verify>`),b||a.push(`Task '${p}' missing <done>`),y||a.push(`Task '${p}' missing <files>`),c.push({name:p,hasFiles:y,hasAction:g,hasVerify:_,hasDone:b})}c.length===0&&a.push("No <task> elements found"),r.wave&&parseInt(String(r.wave))>1&&(!r.depends_on||Array.isArray(r.depends_on)&&r.depends_on.length===0)&&a.push("Wave > 1 but depends_on is empty"),/<task\s+type=["']?checkpoint/.test(t)&&r.autonomous!=="false"&&r.autonomous!==!1&&o.push("Has checkpoint tasks but autonomous is not false"),h({valid:o.length===0,errors:o,warnings:a,task_count:c.length,tasks:c,frontmatter_fields:Object.keys(r)},s,o.length===0?"valid":"invalid")}function Xs(n,e,s){e||S("phase required");let i=ue(n,e);if(!i?.found){h({error:"Phase not found",phase:e},s);return}let t=Y.default.join(n,i.directory),r=[],o=[],a;try{a=K.default.readdirSync(t)}catch{h({error:"Cannot read phase directory"},s);return}let l=a.filter(p=>p.match(/-PLAN\.md$/i)),c=a.filter(p=>p.match(/-SUMMARY\.md$/i)),d=new Set(l.map(p=>p.replace(/-PLAN\.md$/i,""))),u=new Set(c.map(p=>p.replace(/-SUMMARY\.md$/i,""))),f=[...d].filter(p=>!u.has(p)),m=[...u].filter(p=>!d.has(p));f.length>0&&r.push(`Plans without summaries: ${f.join(", ")}`),m.length>0&&o.push(`Summaries without plans: ${m.join(", ")}`),h({complete:r.length===0,phase:i.phase_number,plan_count:l.length,summary_count:c.length,incomplete_plans:f,orphan_summaries:m,errors:r,warnings:o},s,r.length===0?"complete":"incomplete")}function ei(n,e,s){e||S("file path required");let i=Y.default.isAbsolute(e)?e:Y.default.join(n,e),t=we(i);if(!t){h({error:"File not found",path:e},s);return}let r=[],o=[];for(let a of t.match(/@([^\s\n,)]+\/[^\s\n,)]+)/g)||[]){let l=a.slice(1),c=l.startsWith("~/")?Y.default.join(process.env.HOME??"",l.slice(2)):Y.default.join(n,l);(K.default.existsSync(c)?r:o).push(l)}for(let a of t.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10})`/g)||[]){let l=a.slice(1,-1);l.startsWith("http")||l.includes("${")||l.includes("{{")||r.includes(l)||o.includes(l)||(K.default.existsSync(Y.default.join(n,l))?r:o).push(l)}h({valid:o.length===0,found:r.length,missing:o,total:r.length+o.length},s,o.length===0?"valid":"invalid")}function ti(n,e,s){(!e||e.length===0)&&S("At least one commit hash required");let i=[],t=[];for(let r of e)(oe(n,["cat-file","-t",r]).stdout.trim()==="commit"?i:t).push(r);h({all_valid:t.length===0,valid:i,invalid:t,total:e.length},s,t.length===0?"valid":"invalid")}function ni(n,e,s){e||S("plan file path required");let i=Y.default.isAbsolute(e)?e:Y.default.join(n,e),t=we(i);if(!t){h({error:"File not found",path:e},s);return}let r=St(t,"artifacts");if(r.length===0){h({error:"No must_haves.artifacts found in frontmatter",path:e},s);return}let o=[];for(let l of r){if(typeof l=="string")continue;let c=l;if(!c.path)continue;let d=Y.default.join(n,c.path),u=K.default.existsSync(d),f={path:c.path,exists:u,issues:[],passed:!1};if(u){let m=we(d)??"",p=m.split(`
277
277
  `).length;if(c.min_lines&&p<c.min_lines&&f.issues.push(`Only ${p} lines, need ${c.min_lines}`),c.contains&&!m.includes(c.contains)&&f.issues.push(`Missing pattern: ${c.contains}`),c.exports){let y=Array.isArray(c.exports)?c.exports:[c.exports];for(let g of y)m.includes(g)||f.issues.push(`Missing export: ${g}`)}f.passed=f.issues.length===0}else f.issues.push("File not found");o.push(f)}let a=o.filter(l=>l.passed).length;h({all_passed:a===o.length,passed:a,total:o.length,artifacts:o},s,a===o.length?"valid":"invalid")}function si(n,e,s){e||S("plan file path required");let i=Y.default.isAbsolute(e)?e:Y.default.join(n,e),t=we(i);if(!t){h({error:"File not found",path:e},s);return}let r=St(t,"key_links");if(r.length===0){h({error:"No must_haves.key_links found in frontmatter",path:e},s);return}let o=[];for(let l of r){if(typeof l=="string")continue;let c=l,d={from:c.from??"",to:c.to??"",via:c.via||"",verified:!1,detail:""},u=we(Y.default.join(n,c.from||""));if(!u)d.detail="Source file not found";else if(c.pattern)try{let f=new RegExp(c.pattern);if(f.test(u))d.verified=!0,d.detail="Pattern found in source";else{let m=we(Y.default.join(n,c.to||""));m&&f.test(m)?(d.verified=!0,d.detail="Pattern found in target"):d.detail=`Pattern "${c.pattern}" not found in source or target`}}catch{d.detail=`Invalid regex pattern: ${c.pattern}`}else u.includes(c.to||"")?(d.verified=!0,d.detail="Target referenced in source"):d.detail="Target not referenced in source";o.push(d)}let a=o.filter(l=>l.verified).length;h({all_verified:a===o.length,verified:a,total:o.length,links:o},s,a===o.length?"valid":"invalid")}function ii(n,e){let s=Y.default.join(F(n),"ROADMAP.md"),i=Y.default.join(F(n),"phases"),t=[],r=[];if(!K.default.existsSync(s)){t.push("ROADMAP.md not found"),h({passed:!1,errors:t,warnings:r},e,"failed");return}let o=be(K.default.readFileSync(s,"utf-8"),n),a=new Set,l=/#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:/gi,c;for(;(c=l.exec(o))!==null;)a.add(c[1]);let d=new Set;try{K.default.readdirSync(i,{withFileTypes:!0}).filter(m=>m.isDirectory()).map(m=>m.name).forEach(m=>{let p=m.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);p&&d.add(p[1])})}catch{}for(let m of a)!d.has(m)&&!d.has(ce(m))&&r.push(`Phase ${m} in ROADMAP.md but no directory on disk`);for(let m of d){let p=String(parseInt(m,10));!a.has(m)&&!a.has(p)&&r.push(`Phase ${m} exists on disk but not in ROADMAP.md`)}if(B(n).phase_naming!=="custom"){let m=[...d].filter(p=>!p.includes(".")).map(p=>parseInt(p,10)).sort((p,y)=>p-y);for(let p=1;p<m.length;p++)m[p]!==m[p-1]+1&&r.push(`Gap in phase numbering: ${m[p-1]} \u2192 ${m[p]}`)}let f=t.length===0;h({passed:f,errors:t,warnings:r,warning_count:r.length},e,f?"passed":"failed")}function ri(n,e,s){let i=Y.default.resolve(n);if(i===_n.default.homedir()){h({status:"error",errors:[{code:"E010",message:"CWD is home directory - health check would read the wrong .planning/ directory.",fix:"cd into your project directory and retry"}],warnings:[],info:[{code:"I010",message:`Resolved CWD: ${i}`}],repairable_count:0},s);return}let t=F(n),r=J(n),o=Y.default.join(r,"PROJECT.md"),a=Y.default.join(t,"ROADMAP.md"),l=Y.default.join(t,"STATE.md"),c=Y.default.join(r,"config.json"),d=Y.default.join(t,"phases"),u=[],f=[],m=[],p=[],y=(b,v,P,k,$=!1,T)=>{let A={code:v,message:P,fix:k,repairable:$,...T};b==="error"?u.push(A):b==="warning"?f.push(A):m.push(A)};if(!K.default.existsSync(t)){y("error","E001",".planning/ directory not found","Run /gsd-new-project to initialize"),h({status:"broken",errors:u,warnings:f,info:m,repairable_count:0},s);return}if(!K.default.existsSync(o))y("error","E002","PROJECT.md not found","Run /gsd-new-project to create");else{let b=K.default.readFileSync(o,"utf-8");for(let v of["## What This Is","## Core Value","## Requirements"])b.includes(v)||y("warning","W001",`PROJECT.md missing section: ${v}`,"Add section manually")}if(K.default.existsSync(a)||y("error","E003","ROADMAP.md not found","Run /gsd-new-milestone to create roadmap"),!K.default.existsSync(l))y("error","E004","STATE.md not found","Run /gsd-health --repair to regenerate",!0),p.push("regenerateState");else try{let b=K.default.readFileSync(l,"utf-8"),v=ee(b),P=yn.safeParse(v);if(!P.success){for(let k of P.error.issues){let $=k.path.join(".")||"(root)";y("warning","W011",`STATE.md frontmatter: field "${$}" \u2014 ${k.message}`,"Check STATE.md frontmatter manually or run /gsd-health --repair to regenerate",!0)}p.includes("regenerateState")||p.push("regenerateState")}}catch{}if(!K.default.existsSync(c))y("warning","W003","config.json not found","Run /gsd-health --repair to create with defaults",!0),p.push("createConfig");else try{let b=JSON.parse(K.default.readFileSync(c,"utf-8")),v=kt.safeParse(b);if(!v.success){for(let P of v.error.issues){let k=P.path.join(".")||"(root)",$=Ks(b,P.path);y("warning","W005",`config.json: field "${k}" - ${P.message}`,"Run pi-gsd-tools validate health --repair to fix using schema defaults",!0,{field:k,expected:P.message,actual:$})}p.includes("fixSchemaDefaults")||p.push("fixSchemaDefaults")}}catch(b){y("error","E005",`config.json: JSON parse error - ${b.message}`,"Run pi-gsd-tools validate health --repair to reset to defaults",!0),p.push("resetConfig")}try{let b=ot();b.agents_installed||y("warning","W010",b.installed_agents.length===0?`No GSD agents found in ${b.agents_dir}`:`Missing ${b.missing_agents.length} GSD agents: ${b.missing_agents.join(", ")}`,"Run the GSD installer: pi install npm:pi-gsd")}catch{}let g=[];if(e.repair&&p.length>0)for(let b of p)try{if(b==="createConfig"||b==="resetConfig"){let v=kt.parse({});K.default.writeFileSync(c,JSON.stringify(v,null,2),"utf-8"),g.push({action:b,success:!0,path:"config.json"})}else if(b==="fixSchemaDefaults"&&K.default.existsSync(c)){let v=JSON.parse(K.default.readFileSync(c,"utf-8")),P=kt.parse(v);K.default.writeFileSync(c,JSON.stringify(P,null,2),"utf-8"),g.push({action:b,success:!0,path:"config.json"})}else if(b==="regenerateState"){if(K.default.existsSync(l)){let P=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19),k=`${l}.bak-${P}`;K.default.copyFileSync(l,k),g.push({action:"backupState",success:!0,path:k})}let v=ie(n);te(l,`# Session State
278
278
 
279
279
  ## Project Reference
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-gsd",
3
- "version": "1.4.1",
3
+ "version": "1.6.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": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: gsd-execute-milestone
3
- description: Execute all planned phases in the milestone scope guardian, UAT gates, recovery loop
3
+ description: Execute all planned phases + full milestone lifecycle (audit complete cleanup)
4
4
  ---
5
5
 
6
6
  <objective>
@@ -1,21 +0,0 @@
1
- ---
2
- name: gsd-health
3
- description: Diagnose planning directory health and optionally repair issues
4
- ---
5
-
6
- <objective>
7
- Validate `.planning/` directory integrity and report actionable issues. Checks for missing files, invalid configurations, inconsistent state, and orphaned plans.
8
- </objective>
9
-
10
- <execution_context>
11
- @.pi/gsd/workflows/health.md
12
- </execution_context>
13
-
14
- <process>
15
- 1. Run: `pi-gsd-tools validate health --output toon`
16
- - If successful, present the toon output to the user.
17
- - If the command fails or toon output is unavailable, fall back to: `pi-gsd-tools validate health` (plain JSON output).
18
-
19
- 2. Execute the health workflow from @.pi/gsd/workflows/health.md end-to-end.
20
- Parse --repair flag from arguments and pass to workflow.
21
- </process>
@@ -1,24 +0,0 @@
1
- ---
2
- name: gsd-help
3
- description: Show available GSD commands and usage guide
4
- ---
5
-
6
- <objective>
7
- Display the complete GSD command reference.
8
-
9
- Output ONLY the reference content below. Do NOT add:
10
-
11
- - Project-specific analysis
12
- - Git status or file context
13
- - Next-step suggestions
14
- - Any commentary beyond the reference
15
- </objective>
16
-
17
- <execution_context>
18
- @.pi/gsd/workflows/help.md
19
- </execution_context>
20
-
21
- <process>
22
- Output the complete GSD command reference from @.pi/gsd/workflows/help.md.
23
- Display the reference content directly - no additions or modifications.
24
- </process>
@@ -1,19 +0,0 @@
1
- ---
2
- name: gsd-next
3
- description: Automatically advance to the next logical step in the GSD workflow
4
- ---
5
-
6
- <objective>
7
- Detect the current project state and automatically invoke the next logical GSD workflow step.
8
- No arguments needed - reads STATE.md, ROADMAP.md, and phase directories to determine what comes next.
9
-
10
- Designed for rapid multi-project workflows where remembering which phase/step you're on is overhead.
11
- </objective>
12
-
13
- <execution_context>
14
- @.pi/gsd/workflows/next.md
15
- </execution_context>
16
-
17
- <process>
18
- Execute the next workflow from @.pi/gsd/workflows/next.md end-to-end.
19
- </process>
@@ -1,23 +0,0 @@
1
- ---
2
- name: gsd-progress
3
- description: Check project progress, show context, and route to next action (execute or plan)
4
- ---
5
-
6
- <objective>
7
- Check project progress, summarize recent work and what's ahead, then intelligently route to the next action - either executing an existing plan or creating the next one.
8
-
9
- Provides situational awareness before continuing work.
10
- </objective>
11
-
12
- <execution_context>
13
- @.pi/gsd/workflows/progress.md
14
- </execution_context>
15
-
16
- <process>
17
- 1. Run: `pi-gsd-tools progress --output toon`
18
- - If successful, present the toon output to the user.
19
- - If the command fails or toon output is unavailable, fall back to: `pi-gsd-tools progress` (plain JSON output).
20
-
21
- 2. Execute the progress workflow from @.pi/gsd/workflows/progress.md end-to-end.
22
- Preserve all routing logic (Routes A through F) and edge case handling.
23
- </process>
@@ -1,20 +0,0 @@
1
- ---
2
- name: gsd-stats
3
- description: Display project statistics - phases, plans, requirements, git metrics, and timeline
4
- ---
5
-
6
- <objective>
7
- Display comprehensive project statistics including phase progress, plan execution metrics, requirements completion, git history stats, and project timeline.
8
- </objective>
9
-
10
- <execution_context>
11
- @.pi/gsd/workflows/stats.md
12
- </execution_context>
13
-
14
- <process>
15
- 1. Run: `pi-gsd-tools stats --output toon`
16
- - If successful, present the toon output to the user.
17
- - If the command fails or toon output is unavailable, fall back to: `pi-gsd-tools stats` (plain JSON output).
18
-
19
- 2. Execute the stats workflow from @.pi/gsd/workflows/stats.md end-to-end.
20
- </process>