plan-review 1.1.5 → 1.1.6

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/index.js CHANGED
@@ -1,30 +1,30 @@
1
1
  #!/usr/bin/env node
2
- import{Command as rt}from"commander";import{readFileSync as he,mkdirSync as st,copyFileSync as at}from"node:fs";import{existsSync as ge}from"node:fs";import{homedir as ct}from"node:os";import{dirname as lt,join as M,resolve as ut}from"node:path";import{fileURLToPath as mt}from"node:url";import p from"chalk";function E(e,t="auto"){let n=e.split(`
3
- `),i=ve(n),o=we(n);return t==="auto"?Se(e)?be(e,i,o):L(e,i,o):t==="separator"?J(e,i,o):L(e,i,o)}function ve(e){let t=e.find(n=>/^# /.test(n));return t?t.replace(/^# /,"").trim():"Untitled"}function we(e){let t={};for(let n of e.slice(0,20)){let i=n.match(/^\*\*(\w[\w\s]*?):\*\*\s*(.+)/);i&&(t[i[1].trim()]=i[2].trim())}return t}function Se(e){let t=e.replace(/```[\s\S]*?```/g,""),n=/^## /m.test(t)&&/^### /m.test(t),i=/\*\*Depends On:\*\*/m.test(t)||/\*\*Blocks:\*\*/m.test(t)||/\*\*Verification:\*\*/m.test(t)||/\*\*Related Files:\*\*/m.test(t);return n&&i}function L(e,t,n){let i=ye(e);return i.length===0?J(e,t,n):{title:t,metadata:n,mode:"generic",sections:i.map((o,r)=>({id:`section-${r+1}`,heading:o.heading,level:o.level,body:o.body})),comments:[]}}function ye(e){let t=e.split(`
4
- `),n=[],i="",o=0,r=[],s=(e.match(/^## /gm)||[]).length,a=(e.match(/^### /gm)||[]).length,c=s>0?2:a>0?3:0;if(c===0)return[];let u=new RegExp(`^${"#".repeat(c)} (.+)`),l=!1;for(let m of t){if(m.startsWith("```")){l=!l,i&&r.push(m);continue}if(l){i&&r.push(m);continue}let f=m.match(u);f?(i&&n.push({heading:i,level:o,body:r.join(`
2
+ import{Command as it}from"commander";import{readFileSync as M,mkdirSync as rt,copyFileSync as st}from"node:fs";import{existsSync as N}from"node:fs";import{homedir as at}from"node:os";import{dirname as lt,join as L,resolve as ct}from"node:path";import{fileURLToPath as ut}from"node:url";import p from"chalk";function _(t,e="auto"){let n=t.split(`
3
+ `),i=he(n),o=ge(n);return e==="auto"?ve(t)?Se(t,i,o):B(t,i,o):e==="separator"?J(t,i,o):B(t,i,o)}function he(t){let e=t.find(n=>/^# /.test(n));return e?e.replace(/^# /,"").trim():"Untitled"}function ge(t){let e={};for(let n of t.slice(0,20)){let i=n.match(/^\*\*(\w[\w\s]*?):\*\*\s*(.+)/);i&&(e[i[1].trim()]=i[2].trim())}return e}function ve(t){let e=t.replace(/```[\s\S]*?```/g,""),n=/^## /m.test(e)&&/^### /m.test(e),i=/\*\*Depends On:\*\*/m.test(e)||/\*\*Blocks:\*\*/m.test(e)||/\*\*Verification:\*\*/m.test(e)||/\*\*Related Files:\*\*/m.test(e);return n&&i}function B(t,e,n){let i=we(t);return i.length===0?J(t,e,n):{title:e,metadata:n,mode:"generic",sections:i.map((o,r)=>({id:`section-${r+1}`,heading:o.heading,level:o.level,body:o.body})),comments:[]}}function we(t){let e=t.split(`
4
+ `),n=[],i="",o=0,r=[],s=(t.match(/^## /gm)||[]).length,a=(t.match(/^### /gm)||[]).length,c=s>0?2:a>0?3:0;if(c===0)return[];let l=new RegExp(`^${"#".repeat(c)} (.+)`),u=!1;for(let m of e){if(m.startsWith("```")){u=!u,i&&r.push(m);continue}if(u){i&&r.push(m);continue}let f=m.match(l);f?(i&&n.push({heading:i,level:o,body:r.join(`
5
5
  `).trim()}),i=f[1].trim(),o=c,r=[]):i&&r.push(m)}return i&&n.push({heading:i,level:o,body:r.join(`
6
- `).trim()}),n}function J(e,t,n){let i=e.split(/\n---\n/).filter(o=>o.trim().length>=5);return i.length<=1?{title:t,metadata:n,mode:"generic",sections:[{id:"section-1",heading:t,level:1,body:e.trim()}],comments:[]}:{title:t,metadata:n,mode:"generic",sections:i.map((o,r)=>{let a=o.trim().split(`
7
- `)[0].replace(/^#+\s*/,"").trim();return{id:`section-${r+1}`,heading:a||`Section ${r+1}`,level:2,body:o.trim()}}),comments:[]}}function be(e,t,n){let i=e.split(`
8
- `),o=[],r=0,s=0,a="",c="",u=0,l=[],m=!1;function f(){if(!c)return;let h=l.join(`
9
- `).trim();if(u===2){r++,s=0,a=`milestone-${r}`,o.push({id:a,heading:c,level:2,body:h});return}s++;let d=`${r}.${s}`;o.push({id:d,heading:c,level:3,body:h,parent:a,dependencies:Ce(h),relatedFiles:$e(h),verification:Te(h)})}for(let h of i){if(h.startsWith("```")){m=!m,l.push(h);continue}if(m){l.push(h);continue}let d=h.match(/^## (.+)/),H=h.match(/^### (.+)/);d?(f(),c=d[1].trim(),u=2,l=[]):H?(f(),c=H[1].trim(),u=3,l=[]):l.push(h)}return f(),{title:t,metadata:n,mode:"plan",sections:o,comments:[]}}function Ce(e){let t=e.match(/\*\*Depends On:\*\*\s*(.+)/),n=e.match(/\*\*Blocks:\*\*\s*(.+)/),i=o=>{let r=o.trim();return r==="(none)"||r===""?[]:r.split(/,\s*/).map(s=>s.trim())};return{dependsOn:t?i(t[1]):[],blocks:n?i(n[1]):[]}}function $e(e){let t=[],n=e.split(`
10
- `),i=!1;for(let o of n){if(/\*\*Related Files:\*\*/.test(o)){i=!0;continue}if(i){let r=o.match(/^- `(.+)`(.*)$/);if(r){let s=r[2].trim();t.push(s?`${r[1]} ${s}`:r[1])}else(o.trim()===""||/^\*\*/.test(o.trim()))&&(i=!1)}}return t}function Te(e){let t=e.match(/\*\*Verification:\*\*\s*`(.+?)`/);return t?t[1]:void 0}import{createHash as _}from"node:crypto";import{mkdirSync as xe,readFileSync as R,writeFileSync as He,unlinkSync as U,readdirSync as Re,existsSync as W}from"node:fs";import{join as P,resolve as Pe}from"node:path";import{homedir as ke}from"node:os";function Ie(e){let t=Pe(e);return _("sha256").update(t).digest("hex").slice(0,16)}function k(e){return P(y(),Ie(e)+".json")}function y(){let e=P(ke(),".plan-review","sessions");return xe(e,{recursive:!0}),e}function I(e){return`sha256:${_("sha256").update(e).digest("hex")}`}function b(e,t,n,i){try{let o={version:1,planPath:e,contentHash:t,comments:n,activeSection:i,lastModified:new Date().toISOString()},r=k(e);He(r,JSON.stringify(o,null,2),"utf-8")}catch(o){console.warn(`[plan-review] Failed to save session: ${o.message}`)}}function z(e,t){let n=k(e);if(!W(n))return null;let i;try{i=R(n,"utf-8")}catch{return null}let o;try{o=JSON.parse(i)}catch{console.warn(`[plan-review] Corrupt session file, removing: ${n}`);try{U(n)}catch{}return null}return{comments:o.comments.map(s=>({...s,timestamp:new Date(s.timestamp)})),activeSection:o.activeSection,stale:o.contentHash!==t}}function C(e){let t=k(e);try{U(t)}catch{}}function V(){let e=y(),t;try{t=Re(e)}catch{return[]}let n=[];for(let i of t){if(!i.endsWith(".json"))continue;let o=P(e,i),r;try{let a=R(o,"utf-8");r=JSON.parse(a)}catch{console.warn(`[plan-review] Skipping corrupt session file: ${o}`);continue}let s;if(!W(r.planPath))s=null;else try{let a=R(r.planPath,"utf-8");s=I(a)!==r.contentHash}catch{s=null}n.push({planPath:r.planPath,commentCount:r.comments.length,lastModified:r.lastModified,stale:s})}return n}function O(e){return e.replace(/([\\*_`~\[\]#>|])/g,"\\$1")}function Oe(e){return[...e].sort((t,n)=>{let i=t.anchor?.startLine??1/0,o=n.anchor?.startLine??1/0;return i-o})}function De(e){return e==="approved"?"Approved":"Comment"}function Y(e,t){let n=new Set(e.comments.map(a=>a.sectionId)),i=e.sections.filter(a=>e.mode==="plan"?a.level===3:a.level>=2),o=i.filter(a=>n.has(a.id)),r=[];r.push(`# Plan Review: ${e.title}`),r.push(""),r.push("## Review Summary"),r.push(`- **Verdict:** ${De(t.verdict)}`),r.push(`- **Sections reviewed:** ${o.length}/${i.length}`),r.push(`- **Comments:** ${e.comments.length}`);let s=i.length-o.length;r.push(`- **Skipped:** ${s} section${s===1?"":"s"} without comments`),t.summary.trim()!==""&&(r.push(""),r.push("## Overall Comments"),r.push(""),r.push(O(t.summary))),o.length>0&&(r.push(""),r.push("---"));for(let a of o){let c=Oe(e.comments.filter(u=>u.sectionId===a.id));if(r.push(""),r.push(`## Section ${a.id}: ${a.heading}`),r.push(""),e.mode==="plan"&&a.dependencies){let u=a.dependencies;u.dependsOn.length>0&&r.push(`Depends on: ${u.dependsOn.join(", ")}`),u.blocks.length>0&&r.push(`Blocks: ${u.blocks.join(", ")}`),r.push("")}for(let u of c){if(u.anchor){r.push("### Reviewer Comment"),r.push("");for(let l of u.anchor.lineTexts)r.push(`> ${l}`);r.push(""),r.push(O(u.text))}else r.push("### Reviewer Comment (entire section)"),r.push(""),r.push(O(u.text));r.push(""),r.push("---")}}return r.join(`
11
- `)}import*as Z from"node:readline";import w from"chalk";import{marked as q}from"marked";import{markedTerminal as Fe}from"marked-terminal";import g from"chalk";q.use(Fe());function G(e){let t=[];e.level===3&&e.dependencies&&(t.push(je(e)),t.push(""));let n=`${"#".repeat(e.level)} ${e.heading}`,i=e.body||"",o=`${n}
6
+ `).trim()}),n}function J(t,e,n){let i=t.split(/\n---\n/).filter(o=>o.trim().length>=5);return i.length<=1?{title:e,metadata:n,mode:"generic",sections:[{id:"section-1",heading:e,level:1,body:t.trim()}],comments:[]}:{title:e,metadata:n,mode:"generic",sections:i.map((o,r)=>{let a=o.trim().split(`
7
+ `)[0].replace(/^#+\s*/,"").trim();return{id:`section-${r+1}`,heading:a||`Section ${r+1}`,level:2,body:o.trim()}}),comments:[]}}function Se(t,e,n){let i=t.split(`
8
+ `),o=[],r=0,s=0,a="",c="",l=0,u=[],m=!1;function f(){if(!c)return;let h=u.join(`
9
+ `).trim();if(l===2){r++,s=0,a=`milestone-${r}`,o.push({id:a,heading:c,level:2,body:h});return}s++;let d=`${r}.${s}`;o.push({id:d,heading:c,level:3,body:h,parent:a,dependencies:ye(h),relatedFiles:be(h),verification:Ce(h)})}for(let h of i){if(h.startsWith("```")){m=!m,u.push(h);continue}if(m){u.push(h);continue}let d=h.match(/^## (.+)/),w=h.match(/^### (.+)/);d?(f(),c=d[1].trim(),l=2,u=[]):w?(f(),c=w[1].trim(),l=3,u=[]):u.push(h)}return f(),{title:e,metadata:n,mode:"plan",sections:o,comments:[]}}function ye(t){let e=t.match(/\*\*Depends On:\*\*\s*(.+)/),n=t.match(/\*\*Blocks:\*\*\s*(.+)/),i=o=>{let r=o.trim();return r==="(none)"||r===""?[]:r.split(/,\s*/).map(s=>s.trim())};return{dependsOn:e?i(e[1]):[],blocks:n?i(n[1]):[]}}function be(t){let e=[],n=t.split(`
10
+ `),i=!1;for(let o of n){if(/\*\*Related Files:\*\*/.test(o)){i=!0;continue}if(i){let r=o.match(/^- `(.+)`(.*)$/);if(r){let s=r[2].trim();e.push(s?`${r[1]} ${s}`:r[1])}else(o.trim()===""||/^\*\*/.test(o.trim()))&&(i=!1)}}return e}function Ce(t){let e=t.match(/\*\*Verification:\*\*\s*`(.+?)`/);return e?e[1]:void 0}function I(t){return t.replace(/([\\*_`~\[\]#>|])/g,"\\$1")}function Te(t){return[...t].sort((e,n)=>{let i=e.anchor?.startLine??1/0,o=n.anchor?.startLine??1/0;return i-o})}function Re(t){return t==="approved"?"Approved":"Comment"}function U(t,e){let n=new Set(t.comments.map(a=>a.sectionId)),i=t.sections.filter(a=>t.mode==="plan"?a.level===3:a.level>=2),o=i.filter(a=>n.has(a.id)),r=[];r.push(`# Plan Review: ${t.title}`),r.push(""),r.push("## Review Summary"),r.push(`- **Verdict:** ${Re(e.verdict)}`),r.push(`- **Sections reviewed:** ${o.length}/${i.length}`),r.push(`- **Comments:** ${t.comments.length}`);let s=i.length-o.length;r.push(`- **Skipped:** ${s} section${s===1?"":"s"} without comments`),e.summary.trim()!==""&&(r.push(""),r.push("## Overall Comments"),r.push(""),r.push(I(e.summary))),o.length>0&&(r.push(""),r.push("---"));for(let a of o){let c=Te(t.comments.filter(l=>l.sectionId===a.id));if(r.push(""),r.push(`## Section ${a.id}: ${a.heading}`),r.push(""),t.mode==="plan"&&a.dependencies){let l=a.dependencies;l.dependsOn.length>0&&r.push(`Depends on: ${l.dependsOn.join(", ")}`),l.blocks.length>0&&r.push(`Blocks: ${l.blocks.join(", ")}`),r.push("")}for(let l of c){if(l.anchor){r.push("### Reviewer Comment"),r.push("");for(let u of l.anchor.lineTexts)r.push(`> ${u}`);r.push(""),r.push(I(l.text))}else r.push("### Reviewer Comment (entire section)"),r.push(""),r.push(I(l.text));r.push(""),r.push("---")}}return r.join(`
11
+ `)}import{createHash as V}from"node:crypto";import{existsSync as $e}from"node:fs";import{mkdir as Pe,readFile as W,readdir as xe,unlink as O,writeFile as He}from"node:fs/promises";import{homedir as ke}from"node:os";import{join as D}from"node:path";var y=D(ke(),".plan-review","sessions");function R(t){return`sha256:${V("sha256").update(t).digest("hex")}`}function z(t){if(typeof t!="object"||t===null)return!1;let e=t;return typeof e.version=="number"&&typeof e.planPath=="string"&&typeof e.contentHash=="string"&&Array.isArray(e.comments)&&(typeof e.activeSection=="string"||e.activeSection===null)&&typeof e.lastModified=="string"}var S=class{dir;keyHashLength;constructor(e){this.dir=e.dir,this.keyHashLength=e.keyHashLength??16}filePath(e){let n=V("sha256").update(e).digest("hex").slice(0,this.keyHashLength);return D(this.dir,`${n}.json`)}async save(e,n){await Pe(this.dir,{recursive:!0}),await He(this.filePath(e),JSON.stringify(n,null,2),"utf-8")}async load(e){let n=this.filePath(e);if(!$e(n))return null;let i;try{i=await W(n,"utf-8")}catch{return null}let o;try{o=JSON.parse(i)}catch{console.warn(`[plan-review] Corrupt session file, removing: ${n}`);try{await O(n)}catch{}return null}if(!z(o)){console.warn(`[plan-review] Malformed session file, removing: ${n}`);try{await O(n)}catch{}return null}return{...o,comments:o.comments.map(r=>({...r,timestamp:new Date(r.timestamp)}))}}async clear(e){try{await O(this.filePath(e))}catch{}}async list(){let e;try{e=await xe(this.dir)}catch{return[]}let n=[];for(let i of e){if(!i.endsWith(".json"))continue;let o=D(this.dir,i);try{let r=JSON.parse(await W(o,"utf-8"));if(!z(r)){console.warn(`[plan-review] Skipping malformed session file: ${o}`);continue}n.push({key:r.planPath,commentCount:r.comments.length,lastModified:r.lastModified})}catch{console.warn(`[plan-review] Skipping corrupt session file: ${o}`)}}return n}};import*as X from"node:readline";import b from"chalk";import{marked as Y}from"marked";import{markedTerminal as Ie}from"marked-terminal";import g from"chalk";Y.use(Ie());function q(t){let e=[];t.level===3&&t.dependencies&&(e.push(Oe(t)),e.push(""));let n=`${"#".repeat(t.level)} ${t.heading}`,i=t.body||"",o=`${n}
12
12
 
13
- ${i}`;return t.push(q.parse(o)),t.join(`
14
- `)}function je(e){let t=e.dependencies,n=t.dependsOn.length>0?t.dependsOn.join(", "):"(none)",i=t.blocks.length>0?t.blocks.join(", "):"(none)",o=[`Task ${e.id}: ${e.heading}`,`\u2190 Depends on: ${n}`,`\u2192 Blocks: ${i}`];if(e.relatedFiles&&e.relatedFiles.length>0){let m=e.relatedFiles.length<=2?e.relatedFiles.join(", "):`${e.relatedFiles[0]} (+${e.relatedFiles.length-1} more)`;o.push(`Files: ${m}`)}e.verification&&o.push(`Verify: ${e.verification}`);let r=Math.max(...o.map(m=>m.length)),a=Math.min(r+4,process.stdout.columns||80)-2,c=g.dim(`\u250C${"\u2500".repeat(a)}\u2510`),u=g.dim(`\u2514${"\u2500".repeat(a)}\u2518`),l=o.map(m=>g.dim("\u2502")+" "+g.cyan(m.slice(0,a-2).padEnd(a-2))+" "+g.dim("\u2502"));return[c,...l,u].join(`
15
- `)}function K(e){let t=[],n=new Set(e.comments.map(s=>s.sectionId));if(t.push(""),t.push(g.bold.underline(e.title)),t.push(""),e.mode==="plan"){for(let s of e.sections)if(s.level===2)t.push(g.bold.yellow(` ${s.heading}`));else if(s.level===3){let a=n.has(s.id)?g.green("\u2713"):" ";t.push(` ${a} ${g.dim(s.id)} ${s.heading}`)}}else{let s=e.sections.filter(a=>a.level>=2);for(let a=0;a<s.length;a++){let c=s[a],u=String(a+1).padStart(2),l=n.has(c.id)?g.green("\u2713"):" ";t.push(` ${l} ${g.dim(u)} ${c.heading}`)}}let i=n.size,r=e.sections.filter(s=>e.mode==="plan"?s.level===3:s.level>=2).length-i;return t.push(""),t.push(` ${g.green(`${i} section${i!==1?"s":""} commented`)} ${g.dim(`${r} remaining`)}`),t.push(""),t.join(`
16
- `)}async function Q(e,t=!1,n){let i=t?(await import("node:fs")).createReadStream("/dev/tty"):process.stdin,o=Z.createInterface({input:i,output:process.stderr}),r=c=>new Promise(u=>{o.question(c,l=>u(l.trim()))}),s=D(e),a=!0;for(;a;){console.error(K(e));let c=await r(w.cyan("> Enter section (e.g. 1.1), 'all' for linear review, or 'done' to finish: "));if(c==="done"||c==="q")a=!1;else if(c==="all")await X(e,s,r,n);else{let u=Ae(e,c);if(u){let l=s.indexOf(u);await X(e,s.slice(l),r,n)}else console.error(w.red(`Section "${c}" not found. Try again.`))}}return o.close(),Be(e),e}async function X(e,t,n,i){for(let o=0;o<t.length;o++){let r=t[o];console.error(G(r));let s=await n(w.cyan("> Comment (enter to skip, 'toc' for menu, 'back' for previous): "));if(s==="toc")return;if(s==="back"){o-=o>0?2:1;continue}else s!==""&&(e.comments.push({sectionId:r.id,text:s,timestamp:new Date}),i?.())}}function Ae(e,t){let n=e.sections.find(o=>o.id===t);if(n)return n;let i=parseInt(t,10);if(!isNaN(i)){let o=D(e);if(i>=1&&i<=o.length)return o[i-1]}}function D(e){return e.sections.filter(t=>e.mode==="plan"?t.level===3:t.level>=2)}function Be(e){let t=D(e),n=new Set(e.comments.map(i=>i.sectionId));console.error(""),console.error(w.bold("Review Summary")),console.error(` Sections: ${t.length}`),console.error(` Commented: ${w.green(String(n.size))}`),console.error(` Skipped: ${w.dim(String(t.length-n.size))}`),console.error(` Total comments: ${e.comments.length}`),console.error("")}import{execSync as ee,spawn as Ne}from"node:child_process";import{writeFileSync as Me}from"node:fs";import{resolve as F}from"node:path";import v from"chalk";function te(e,t,n={}){switch(t){case"stdout":process.stdout.write(e+`
17
- `);break;case"clipboard":Le(e);break;case"file":Ee(e,n.outputFile,n.inputFile);break;case"claude":Je(e);break}}function Le(e){let t=_e(process.platform);if(!t){console.error(v.yellow("Clipboard not supported on this platform. Falling back to stdout.")),process.stdout.write(e+`
18
- `);return}try{ee(t,{input:e,stdio:["pipe","ignore","ignore"]}),console.error(v.green("Review copied to clipboard."))}catch{console.error(v.yellow("Failed to copy to clipboard. Falling back to stdout.")),process.stdout.write(e+`
19
- `)}}function Ee(e,t,n){let i=t?F(t):n?F(n.replace(/\.md$/,".review.md")):F("review.md");try{Me(i,e,"utf-8"),console.error(v.green(`Review written to ${i}`))}catch(o){let r=o instanceof Error?o.message:String(o);console.error(v.red(`Failed to write file: ${r}`)),console.error(v.yellow("Falling back to stdout.")),process.stdout.write(e+`
20
- `)}}function Je(e){if(!$()){console.error(v.red("Claude CLI not found in PATH.")),console.error(v.dim("Install: https://docs.anthropic.com/en/docs/claude-code")),console.error(v.yellow("Falling back to stdout.")),process.stdout.write(e+`
21
- `);return}let t=Ne("claude",[],{stdio:["pipe","inherit","inherit"]});t.stdin.write(e),t.stdin.end(),t.on("error",n=>{console.error(v.yellow(`Failed to pipe to claude: ${n.message}. Falling back to stdout.`)),process.stdout.write(e+`
22
- `)})}function _e(e){switch(e){case"darwin":return"pbcopy";case"linux":return"xclip -selection clipboard";case"win32":return"clip";default:return null}}function $(){try{return ee("which claude",{stdio:"ignore"}),!0}catch{return!1}}import{createRequire as dt}from"node:module";import*as j from"node:readline";import ne from"chalk";async function oe(e){return e?(await import("node:fs")).createReadStream("/dev/tty"):process.stdin}async function ie(e){let t=j.createInterface({input:await oe(e),output:process.stderr}),n=await new Promise(i=>{t.question(ne.cyan("> Output: (s)tdout, (c)lipboard, (f)ile, cl(a)ude? "),o=>i(o.trim().toLowerCase()))});switch(t.close(),n){case"s":case"stdout":return"stdout";case"c":case"clipboard":return"clipboard";case"f":case"file":return"file";case"a":case"claude":return"claude";default:return"stdout"}}async function re(e,t){let n=j.createInterface({input:await oe(t),output:process.stderr}),i=await new Promise(o=>{n.question(ne.yellow(`${e} (y/n) `),r=>o(r.trim().toLowerCase()))});return n.close(),i==="y"||i==="yes"}import{spawnSync as tt}from"node:child_process";import{dirname as nt}from"node:path";import{createServer as Ge}from"node:http";import{readFile as Ue}from"node:fs";import{join as We,normalize as ze,resolve as se,sep as Ve,extname as Ye}from"node:path";var T=1024*1024,ae={".gif":"image/gif",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".svg":"image/svg+xml",".webp":"image/webp",".avif":"image/avif",".ico":"image/x-icon"};function ce(e){if(typeof e!="object"||e===null)return!1;let t=e;return typeof t.sectionId=="string"&&typeof t.text=="string"}function qe(e){return e==="approved"||e===null}function le(e){return(t,n)=>{let{method:i,url:o}=t;if(i==="GET"&&o==="/"){let r=e.getAssetHtml();n.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),n.end(r);return}if(i==="GET"&&o==="/api/doc"){let r=e.getDocument(),s={activeSection:e.getInitialActiveSection?.()??null};n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify({document:r,initialState:s}));return}if(i==="POST"&&o==="/api/review"){let r="",s=0;t.on("data",a=>{if(s+=a.length,s>T){n.writeHead(413,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Request body too large"})),t.destroy();return}r+=a.toString()}),t.on("end",()=>{if(!(s>T))try{let a=JSON.parse(r),c=a.comments,u=a.verdict,l=a.summary;if(!Array.isArray(c)){n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"comments must be an array"}));return}if(!qe(u)){n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:'verdict must be "approved" or null'}));return}if(typeof l!="string"){n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"summary must be a string"}));return}for(let m of c)if(!ce(m)){n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Each comment must have sectionId (string) and text (string)"}));return}e.onSubmit({comments:c,verdict:u,summary:l}),n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify({success:!0}))}catch{n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Invalid JSON"}))}});return}if(i==="PUT"&&o==="/api/session"){let r="",s=0;t.on("data",a=>{if(s+=a.length,s>T){n.writeHead(413,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Request body too large"})),t.destroy();return}r+=a.toString()}),t.on("end",()=>{if(!(s>T))try{let a=JSON.parse(r),c=a.comments;if(!Array.isArray(c)){n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"comments must be an array"}));return}for(let l of c)if(!ce(l)){n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Each comment must have sectionId (string) and text (string)"}));return}let u=typeof a.activeSection=="string"?a.activeSection:null;e.onSessionSave?.(c,u),n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify({success:!0}))}catch{n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Invalid JSON"}))}});return}if(i==="POST"&&o==="/api/heartbeat"){e.onHeartbeat?.(),n.writeHead(204),n.end();return}if(i==="POST"&&o==="/api/pause"){e.onPause?.(),n.writeHead(204),n.end();return}if(i==="POST"&&o==="/api/cancel"){e.onCancel?.(),n.writeHead(204),n.end();return}if(i==="GET"&&o&&o.startsWith("/_assets/")){let r=e.getAssetBaseDir?.();if(!r){n.writeHead(404,{"Content-Type":"text/plain"}),n.end("No asset base directory");return}let s;try{s=decodeURIComponent(o.slice(9).split("?")[0].split("#")[0])}catch{n.writeHead(400,{"Content-Type":"text/plain"}),n.end("Bad request");return}let a=Ye(s).toLowerCase();if(!ae[a]){n.writeHead(415,{"Content-Type":"text/plain"}),n.end("Unsupported media type");return}let c=ze(s),u=se(r),l=se(We(u,c));if(!l.startsWith(u+Ve)&&l!==u){n.writeHead(403,{"Content-Type":"text/plain"}),n.end("Forbidden");return}Ue(l,(m,f)=>{if(m){n.writeHead(404,{"Content-Type":"text/plain"}),n.end("Not Found");return}n.writeHead(200,{"Content-Type":ae[a],"Content-Length":f.length,"Cache-Control":"no-store"}),n.end(f)});return}n.writeHead(404,{"Content-Type":"text/plain"}),n.end("Not Found")}}function ue(e){return Ge(le(e))}function me(e,t){return new Promise((n,i)=>{e.on("error",i),e.listen(t,()=>{let o=e.address(),r=typeof o=="object"&&o?o.port:t;n({url:`http://localhost:${r}`})})})}function de(e){return new Promise((t,n)=>{e.close(i=>i?n(i):t()),e.closeAllConnections()})}import{readFileSync as Ke,existsSync as Xe}from"node:fs";import{join as A,dirname as Ze}from"node:path";import{fileURLToPath as Qe}from"node:url";var B=Ze(Qe(import.meta.url));function et(){let e=[A(B,"browser","index.html"),A(B,"..","browser","index.html"),A(B,"..","dist","browser","index.html")];for(let t of e)if(Xe(t))return t;throw new Error(`Browser HTML not found. Run 'npm run build' first.
13
+ ${i}`;return e.push(Y.parse(o)),e.join(`
14
+ `)}function Oe(t){let e=t.dependencies,n=e.dependsOn.length>0?e.dependsOn.join(", "):"(none)",i=e.blocks.length>0?e.blocks.join(", "):"(none)",o=[`Task ${t.id}: ${t.heading}`,`\u2190 Depends on: ${n}`,`\u2192 Blocks: ${i}`];if(t.relatedFiles&&t.relatedFiles.length>0){let m=t.relatedFiles.length<=2?t.relatedFiles.join(", "):`${t.relatedFiles[0]} (+${t.relatedFiles.length-1} more)`;o.push(`Files: ${m}`)}t.verification&&o.push(`Verify: ${t.verification}`);let r=Math.max(...o.map(m=>m.length)),a=Math.min(r+4,process.stdout.columns||80)-2,c=g.dim(`\u250C${"\u2500".repeat(a)}\u2510`),l=g.dim(`\u2514${"\u2500".repeat(a)}\u2518`),u=o.map(m=>g.dim("\u2502")+" "+g.cyan(m.slice(0,a-2).padEnd(a-2))+" "+g.dim("\u2502"));return[c,...u,l].join(`
15
+ `)}function G(t){let e=[],n=new Set(t.comments.map(s=>s.sectionId));if(e.push(""),e.push(g.bold.underline(t.title)),e.push(""),t.mode==="plan"){for(let s of t.sections)if(s.level===2)e.push(g.bold.yellow(` ${s.heading}`));else if(s.level===3){let a=n.has(s.id)?g.green("\u2713"):" ";e.push(` ${a} ${g.dim(s.id)} ${s.heading}`)}}else{let s=t.sections.filter(a=>a.level>=2);for(let a=0;a<s.length;a++){let c=s[a],l=String(a+1).padStart(2),u=n.has(c.id)?g.green("\u2713"):" ";e.push(` ${u} ${g.dim(l)} ${c.heading}`)}}let i=n.size,r=t.sections.filter(s=>t.mode==="plan"?s.level===3:s.level>=2).length-i;return e.push(""),e.push(` ${g.green(`${i} section${i!==1?"s":""} commented`)} ${g.dim(`${r} remaining`)}`),e.push(""),e.join(`
16
+ `)}async function Q(t,e=!1,n){let i=e?(await import("node:fs")).createReadStream("/dev/tty"):process.stdin,o=X.createInterface({input:i,output:process.stderr}),r=c=>new Promise(l=>{o.question(c,u=>l(u.trim()))}),s=F(t),a=!0;for(;a;){console.error(G(t));let c=await r(b.cyan("> Enter section (e.g. 1.1), 'all' for linear review, or 'done' to finish: "));if(c==="done"||c==="q")a=!1;else if(c==="all")await K(t,s,r,n);else{let l=De(t,c);if(l){let u=s.indexOf(l);await K(t,s.slice(u),r,n)}else console.error(b.red(`Section "${c}" not found. Try again.`))}}return o.close(),Fe(t),t}async function K(t,e,n,i){for(let o=0;o<e.length;o++){let r=e[o];console.error(q(r));let s=await n(b.cyan("> Comment (enter to skip, 'toc' for menu, 'back' for previous): "));if(s==="toc")return;if(s==="back"){o-=o>0?2:1;continue}else s!==""&&(t.comments.push({sectionId:r.id,text:s,timestamp:new Date}),i?.())}}function De(t,e){let n=t.sections.find(o=>o.id===e);if(n)return n;let i=parseInt(e,10);if(!isNaN(i)){let o=F(t);if(i>=1&&i<=o.length)return o[i-1]}}function F(t){return t.sections.filter(e=>t.mode==="plan"?e.level===3:e.level>=2)}function Fe(t){let e=F(t),n=new Set(t.comments.map(i=>i.sectionId));console.error(""),console.error(b.bold("Review Summary")),console.error(` Sections: ${e.length}`),console.error(` Commented: ${b.green(String(n.size))}`),console.error(` Skipped: ${b.dim(String(e.length-n.size))}`),console.error(` Total comments: ${t.comments.length}`),console.error("")}import{execSync as Z,spawn as Ae}from"node:child_process";import{writeFileSync as je}from"node:fs";import{resolve as A}from"node:path";import v from"chalk";function ee(t,e,n={}){switch(e){case"stdout":process.stdout.write(t+`
17
+ `);break;case"clipboard":Ee(t);break;case"file":Le(t,n.outputFile,n.inputFile);break;case"claude":Me(t);break}}function Ee(t){let e=Ne(process.platform);if(!e){console.error(v.yellow("Clipboard not supported on this platform. Falling back to stdout.")),process.stdout.write(t+`
18
+ `);return}try{Z(e,{input:t,stdio:["pipe","ignore","ignore"]}),console.error(v.green("Review copied to clipboard."))}catch{console.error(v.yellow("Failed to copy to clipboard. Falling back to stdout.")),process.stdout.write(t+`
19
+ `)}}function Le(t,e,n){let i=e?A(e):n?A(n.replace(/\.md$/,".review.md")):A("review.md");try{je(i,t,"utf-8"),console.error(v.green(`Review written to ${i}`))}catch(o){let r=o instanceof Error?o.message:String(o);console.error(v.red(`Failed to write file: ${r}`)),console.error(v.yellow("Falling back to stdout.")),process.stdout.write(t+`
20
+ `)}}function Me(t){if(!$()){console.error(v.red("Claude CLI not found in PATH.")),console.error(v.dim("Install: https://docs.anthropic.com/en/docs/claude-code")),console.error(v.yellow("Falling back to stdout.")),process.stdout.write(t+`
21
+ `);return}let e=Ae("claude",[],{stdio:["pipe","inherit","inherit"]});e.stdin.write(t),e.stdin.end(),e.on("error",n=>{console.error(v.yellow(`Failed to pipe to claude: ${n.message}. Falling back to stdout.`)),process.stdout.write(t+`
22
+ `)})}function Ne(t){switch(t){case"darwin":return"pbcopy";case"linux":return"xclip -selection clipboard";case"win32":return"clip";default:return null}}function $(){try{return Z("which claude",{stdio:"ignore"}),!0}catch{return!1}}import{createRequire as mt}from"node:module";import*as j from"node:readline";import te from"chalk";async function ne(t){return t?(await import("node:fs")).createReadStream("/dev/tty"):process.stdin}async function oe(t){let e=j.createInterface({input:await ne(t),output:process.stderr}),n=await new Promise(i=>{e.question(te.cyan("> Output: (s)tdout, (c)lipboard, (f)ile, cl(a)ude? "),o=>i(o.trim().toLowerCase()))});switch(e.close(),n){case"s":case"stdout":return"stdout";case"c":case"clipboard":return"clipboard";case"f":case"file":return"file";case"a":case"claude":return"claude";default:return"stdout"}}async function ie(t,e){let n=j.createInterface({input:await ne(e),output:process.stderr}),i=await new Promise(o=>{n.question(te.yellow(`${t} (y/n) `),r=>o(r.trim().toLowerCase()))});return n.close(),i==="y"||i==="yes"}import{spawnSync as Qe}from"node:child_process";import{dirname as Ze}from"node:path";import{createServer as Ve}from"node:http";import{readFile as Be}from"node:fs";import{join as _e,normalize as Je,resolve as re,sep as Ue,extname as We}from"node:path";var P=1024*1024,se={".gif":"image/gif",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".svg":"image/svg+xml",".webp":"image/webp",".avif":"image/avif",".ico":"image/x-icon"};function ae(t){if(typeof t!="object"||t===null)return!1;let e=t;return typeof e.sectionId=="string"&&typeof e.text=="string"}function ze(t){return t==="approved"||t===null}function le(t){return(e,n)=>{let{method:i,url:o}=e;if(i==="GET"&&o==="/"){let r=t.getAssetHtml();n.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),n.end(r);return}if(i==="GET"&&o==="/api/doc"){let r=t.getDocument(),s={activeSection:t.getInitialActiveSection?.()??null};n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify({document:r,initialState:s}));return}if(i==="POST"&&o==="/api/review"){let r="",s=0;e.on("data",a=>{if(s+=a.length,s>P){n.writeHead(413,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Request body too large"})),e.destroy();return}r+=a.toString()}),e.on("end",()=>{if(!(s>P))try{let a=JSON.parse(r),c=a.comments,l=a.verdict,u=a.summary;if(!Array.isArray(c)){n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"comments must be an array"}));return}if(!ze(l)){n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:'verdict must be "approved" or null'}));return}if(typeof u!="string"){n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"summary must be a string"}));return}for(let m of c)if(!ae(m)){n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Each comment must have sectionId (string) and text (string)"}));return}t.onSubmit({comments:c,verdict:l,summary:u}),n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify({success:!0}))}catch{n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Invalid JSON"}))}});return}if(i==="PUT"&&o==="/api/session"){let r="",s=0;e.on("data",a=>{if(s+=a.length,s>P){n.writeHead(413,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Request body too large"})),e.destroy();return}r+=a.toString()}),e.on("end",async()=>{if(s>P)return;let a,c;try{let l=JSON.parse(r);if(a=l.comments,!Array.isArray(a)){n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"comments must be an array"}));return}for(let u of a)if(!ae(u)){n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Each comment must have sectionId (string) and text (string)"}));return}c=typeof l.activeSection=="string"?l.activeSection:null}catch{n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Invalid JSON"}));return}try{await t.onSessionSave?.(a,c),n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify({success:!0}))}catch{n.writeHead(500,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Failed to save session"}))}});return}if(i==="POST"&&o==="/api/heartbeat"){t.onHeartbeat?.(),n.writeHead(204),n.end();return}if(i==="POST"&&o==="/api/pause"){t.onPause?.(),n.writeHead(204),n.end();return}if(i==="POST"&&o==="/api/cancel"){t.onCancel?.(),n.writeHead(204),n.end();return}if(i==="GET"&&o&&o.startsWith("/_assets/")){let r=t.getAssetBaseDir?.();if(!r){n.writeHead(404,{"Content-Type":"text/plain"}),n.end("No asset base directory");return}let s;try{s=decodeURIComponent(o.slice(9).split("?")[0].split("#")[0])}catch{n.writeHead(400,{"Content-Type":"text/plain"}),n.end("Bad request");return}let a=We(s).toLowerCase();if(!se[a]){n.writeHead(415,{"Content-Type":"text/plain"}),n.end("Unsupported media type");return}let c=Je(s),l=re(r),u=re(_e(l,c));if(!u.startsWith(l+Ue)&&u!==l){n.writeHead(403,{"Content-Type":"text/plain"}),n.end("Forbidden");return}Be(u,(m,f)=>{if(m){n.writeHead(404,{"Content-Type":"text/plain"}),n.end("Not Found");return}n.writeHead(200,{"Content-Type":se[a],"Content-Length":f.length,"Cache-Control":"no-store"}),n.end(f)});return}n.writeHead(404,{"Content-Type":"text/plain"}),n.end("Not Found")}}function ce(t){return Ve(le(t))}function ue(t,e){return new Promise((n,i)=>{t.on("error",i),t.listen(e,()=>{let o=t.address(),r=typeof o=="object"&&o?o.port:e;n({url:`http://localhost:${r}`})})})}function me(t){return new Promise((e,n)=>{t.close(i=>i?n(i):e()),t.closeAllConnections()})}import{readFileSync as Ye,existsSync as qe}from"node:fs";import{join as x,dirname as Ge}from"node:path";import{fileURLToPath as Ke}from"node:url";var H=Ge(Ke(import.meta.url));function Xe(){let t=[x(H,"browser","index.html"),x(H,"..","browser","index.html"),x(H,"..","dist","browser","index.html"),x(H,"..","..","dist","browser","index.html")];for(let e of t)if(qe(e))return e;throw new Error(`Browser HTML not found. Run 'npm run build' first.
23
23
  Looked in:
24
- ${e.join(`
25
- `)}`)}var N=null;function pe(){return N||(N=Ke(et(),"utf-8")),N}var x=class{doc=null;initialActiveSection=null;assetBaseDir=null;submitHandler=null;sessionSaveHandler=null;heartbeatHandler=null;pauseHandler=null;cancelHandler=null;server=null;sendDocument(t){this.doc=t}setInitialActiveSection(t){this.initialActiveSection=t}setAssetBaseDir(t){this.assetBaseDir=t}onReviewSubmit(t){this.submitHandler=t}onSessionSave(t){this.sessionSaveHandler=t}onHeartbeat(t){this.heartbeatHandler=t}onPause(t){this.pauseHandler=t}onCancel(t){this.cancelHandler=t}async start(t){if(!this.doc)throw new Error("No document set");return this.server=ue({getDocument:()=>this.doc,getInitialActiveSection:()=>this.initialActiveSection,getAssetBaseDir:()=>this.assetBaseDir,onSubmit:n=>this.submitHandler?.(n),getAssetHtml:()=>pe(),onSessionSave:(n,i)=>this.sessionSaveHandler?.(n,i),onHeartbeat:()=>this.heartbeatHandler?.(),onPause:()=>this.pauseHandler?.(),onCancel:()=>this.cancelHandler?.()}),me(this.server,t)}async stop(){this.server&&this.server.listening&&(await de(this.server),this.server=null)}};var ot=1800*1e3,it=30*1e3;async function fe({doc:e,absPath:t,contentHash:n,restoredActiveSection:i}){let o=new x;o.sendDocument(e),o.setInitialActiveSection(i),o.setAssetBaseDir(t?nt(t):null),t&&o.onSessionSave((a,c)=>{b(t,n,a,c)});let r=new Promise((a,c)=>{let u=setTimeout(()=>c(new Error("Browser review timed out after 30 minutes of inactivity")),ot),l=null,m=()=>{l&&clearTimeout(l),l=setTimeout(()=>{clearTimeout(u),c(new Error("Review cancelled: browser closed (heartbeat lost)"))},it)},f=()=>{clearTimeout(u),l&&clearTimeout(l),l=null};o.onHeartbeat(m),o.onPause(()=>{l&&clearTimeout(l),l=null}),o.onCancel(()=>{f(),c(new Error("Review cancelled: browser closed"))}),o.onReviewSubmit(h=>{f(),a(h)})}),{url:s}=await o.start(0);process.stderr.write(`Review server running at ${s}
26
- `);try{let a=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";tt(a,[s],{stdio:"ignore"})}catch{process.stderr.write(`Open ${s} in your browser
27
- `)}try{return await r}finally{await o.stop()}}var pt=dt(import.meta.url),{version:ft}=pt("../package.json"),S=new rt;S.name("plan-review").description("Interactive CLI for reviewing AI-generated markdown plans").version(ft).argument("[file]","Path to markdown file (omit to read stdin)").option("-o, --output <target>","Output target: stdout, clipboard, file, claude").option("--output-file <path>","Custom output file path (with --output file)").option("--split-by <strategy>","Force split strategy: heading, separator").option("--fresh","Skip session resume, start clean review").option("--cli","Use the terminal review UI instead of the browser (SSH/CI/headless)").action(async(e,t)=>{try{await ht(e,t)}catch(n){if(n instanceof Error){let i=n.message.startsWith("Review cancelled");console.error(i?p.yellow(n.message):p.red(`Error: ${n.message}`))}process.exit(1)}});S.command("install-skill").description("Install Claude Code skill to ~/.claude/skills/plan-review/").action(()=>{let e=lt(mt(import.meta.url)),t=M(e,"..","skills","plan-review","SKILL.md");ge(t)||(console.error(p.red("Skill file not found in package. Expected at: "+t)),process.exit(1));let n=M(ct(),".claude","skills","plan-review");st(n,{recursive:!0}),at(t,M(n,"SKILL.md")),console.error(p.green(`Skill installed to ${n}/SKILL.md`)),console.error(p.dim('Claude Code will auto-discover it. Try: "I want to review this plan"'))});S.command("sessions").description("List all saved review sessions").action(()=>{let e=V(),t=y();e.length===0&&(console.error(p.dim(`No saved sessions. (${t})`)),process.exit(0)),console.error(p.bold(`Saved review sessions (${t}):
28
- `));for(let n of e){let i=vt(n.lastModified),o="";n.stale===!0?o=p.yellow(" | plan file changed since last review"):n.stale===null&&(o=p.red(" | plan file not found")),console.error(` ${n.planPath}`),console.error(p.dim(` ${n.commentCount} comment${n.commentCount!==1?"s":""} | last modified ${i}${o}
29
- `))}});S.parse();async function ht(e,t){let n=["stdout","clipboard","file","claude"];if(t.output!==void 0){let d=t.output;if(!n.includes(d))throw new Error(`Invalid output target: "${t.output}". Use: ${n.join(", ")}`);d==="claude"&&!$()&&(console.error(p.red("Claude CLI not found in PATH.")),console.error(p.dim("Install: https://docs.anthropic.com/en/docs/claude-code")),console.error(p.yellow("Will fall back to stdout after review.")))}let i=!e&&!process.stdin.isTTY,o=gt(e);o.trim()||(console.error(p.yellow("Empty file, nothing to review.")),process.exit(0));let r=t.splitBy==="heading"?"heading":t.splitBy==="separator"?"separator":"auto",s=E(o,r);console.error(p.dim(`Detected mode: ${s.mode} | ${s.sections.length} sections`));let a=e?ut(e):null,c=I(o),u=null;if(a)if(t.fresh)C(a);else{let d=z(a,c);d&&d.comments.length>0&&(d.stale?await re(`Plan file changed since last review (${d.comments.length} comment${d.comments.length!==1?"s":""}). Resume anyway?`,i)?(console.error(p.yellow("Resuming with stale session.")),s.comments=d.comments,u=d.activeSection):C(a):(console.error(p.green(`Resuming review (${d.comments.length} comment${d.comments.length!==1?"s":""}).`)),s.comments=d.comments,u=d.activeSection))}let l,m={verdict:null,summary:""};if(t.cli)l=await Q(s,i,a?()=>b(a,c,s.comments,null):void 0);else{let d=await fe({doc:s,absPath:a,contentHash:c,restoredActiveSection:u});s.comments=d.comments,m={verdict:d.verdict,summary:d.summary},l=s}a&&C(a);let f;t.output!==void 0?f=t.output:(f=await ie(i),f==="claude"&&!$()&&(console.error(p.red("Claude CLI not found in PATH.")),console.error(p.dim("Install: https://docs.anthropic.com/en/docs/claude-code")),console.error(p.yellow("Falling back to stdout.")),f="stdout"));let h=Y(l,m);te(h,f,{outputFile:t.outputFile,inputFile:e})}function gt(e){if(e){if(!ge(e))throw new Error(`File not found: ${e}`);return he(e,"utf-8")}return process.stdin.isTTY?(S.help(),""):he("/dev/stdin","utf-8")}function vt(e){let t=Date.now()-new Date(e).getTime(),n=Math.floor(t/1e3);if(n<60)return"just now";let i=Math.floor(n/60);if(i<60)return`${i}m ago`;let o=Math.floor(i/60);if(o<24)return`${o}h ago`;let r=Math.floor(o/24);return r<7?`${r}d ago`:`${Math.floor(r/7)}w ago`}
24
+ ${t.join(`
25
+ `)}`)}var E=null;function de(){return E||(E=Ye(Xe(),"utf-8")),E}var k=class{doc=null;initialActiveSection=null;assetBaseDir=null;submitHandler=null;sessionSaveHandler=null;heartbeatHandler=null;pauseHandler=null;cancelHandler=null;server=null;sendDocument(e){this.doc=e}setInitialActiveSection(e){this.initialActiveSection=e}setAssetBaseDir(e){this.assetBaseDir=e}onReviewSubmit(e){this.submitHandler=e}onSessionSave(e){this.sessionSaveHandler=e}onHeartbeat(e){this.heartbeatHandler=e}onPause(e){this.pauseHandler=e}onCancel(e){this.cancelHandler=e}async start(e){if(!this.doc)throw new Error("No document set");return this.server=ce({getDocument:()=>this.doc,getInitialActiveSection:()=>this.initialActiveSection,getAssetBaseDir:()=>this.assetBaseDir,onSubmit:n=>this.submitHandler?.(n),getAssetHtml:()=>de(),onSessionSave:(n,i)=>this.sessionSaveHandler?.(n,i),onHeartbeat:()=>this.heartbeatHandler?.(),onPause:()=>this.pauseHandler?.(),onCancel:()=>this.cancelHandler?.()}),ue(this.server,e)}async stop(){this.server&&this.server.listening&&(await me(this.server),this.server=null)}};var et=1800*1e3,tt=30*1e3,nt=new S({dir:y});async function pe({doc:t,absPath:e,contentHash:n,restoredActiveSection:i}){let o=new k;o.sendDocument(t),o.setInitialActiveSection(i),o.setAssetBaseDir(e?Ze(e):null),e&&o.onSessionSave((a,c)=>ot(nt,e,n,a,c));let r=new Promise((a,c)=>{let l=setTimeout(()=>c(new Error("Browser review timed out after 30 minutes of inactivity")),et),u=null,m=()=>{u&&clearTimeout(u),u=setTimeout(()=>{clearTimeout(l),c(new Error("Review cancelled: browser closed (heartbeat lost)"))},tt)},f=()=>{clearTimeout(l),u&&clearTimeout(u),u=null};o.onHeartbeat(m),o.onPause(()=>{u&&clearTimeout(u),u=null}),o.onCancel(()=>{f(),c(new Error("Review cancelled: browser closed"))}),o.onReviewSubmit(h=>{f(),a(h)})}),{url:s}=await o.start(0);process.stderr.write(`Review server running at ${s}
26
+ `);try{let a=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";Qe(a,[s],{stdio:"ignore"})}catch{process.stderr.write(`Open ${s} in your browser
27
+ `)}try{return await r}finally{await o.stop()}}async function ot(t,e,n,i,o){let r={version:1,planPath:e,contentHash:n,comments:i,activeSection:o,lastModified:new Date().toISOString()};await t.save(e,r)}var dt=mt(import.meta.url),{version:pt}=dt("../package.json"),T=new it,C=new S({dir:y});T.name("plan-review").description("Interactive CLI for reviewing AI-generated markdown plans").version(pt).argument("[file]","Path to markdown file (omit to read stdin)").option("-o, --output <target>","Output target: stdout, clipboard, file, claude").option("--output-file <path>","Custom output file path (with --output file)").option("--split-by <strategy>","Force split strategy: heading, separator").option("--fresh","Skip session resume, start clean review").option("--cli","Use the terminal review UI instead of the browser (SSH/CI/headless)").action(async(t,e)=>{try{await ft(t,e)}catch(n){if(n instanceof Error){let i=n.message.startsWith("Review cancelled");console.error(i?p.yellow(n.message):p.red(`Error: ${n.message}`))}process.exit(1)}});T.command("install-skill").description("Install Claude Code skill to ~/.claude/skills/plan-review/").action(()=>{let t=lt(ut(import.meta.url)),e=L(t,"..","skills","plan-review","SKILL.md");N(e)||(console.error(p.red("Skill file not found in package. Expected at: "+e)),process.exit(1));let n=L(at(),".claude","skills","plan-review");rt(n,{recursive:!0}),st(e,L(n,"SKILL.md")),console.error(p.green(`Skill installed to ${n}/SKILL.md`)),console.error(p.dim('Claude Code will auto-discover it. Try: "I want to review this plan"'))});T.command("sessions").description("List all saved review sessions").action(async()=>{let t=await St(C),e=y;t.length===0&&(console.error(p.dim(`No saved sessions. (${e})`)),process.exit(0)),console.error(p.bold(`Saved review sessions (${e}):
28
+ `));for(let n of t){let i=gt(n.lastModified),o="";n.stale===!0?o=p.yellow(" | plan file changed since last review"):n.stale===null&&(o=p.red(" | plan file not found")),console.error(` ${n.planPath}`),console.error(p.dim(` ${n.commentCount} comment${n.commentCount!==1?"s":""} | last modified ${i}${o}
29
+ `))}});T.parse();async function ft(t,e){let n=["stdout","clipboard","file","claude"];if(e.output!==void 0){let d=e.output;if(!n.includes(d))throw new Error(`Invalid output target: "${e.output}". Use: ${n.join(", ")}`);d==="claude"&&!$()&&(console.error(p.red("Claude CLI not found in PATH.")),console.error(p.dim("Install: https://docs.anthropic.com/en/docs/claude-code")),console.error(p.yellow("Will fall back to stdout after review.")))}let i=!t&&!process.stdin.isTTY,o=ht(t);o.trim()||(console.error(p.yellow("Empty file, nothing to review.")),process.exit(0));let r=e.splitBy==="heading"?"heading":e.splitBy==="separator"?"separator":"auto",s=_(o,r);console.error(p.dim(`Detected mode: ${s.mode} | ${s.sections.length} sections`));let a=t?ct(t):null,c=R(o),l=null;if(a)if(e.fresh)await C.clear(a);else{let d=await wt(C,a,c);d&&d.comments.length>0&&(d.stale?await ie(`Plan file changed since last review (${d.comments.length} comment${d.comments.length!==1?"s":""}). Resume anyway?`,i)?(console.error(p.yellow("Resuming with stale session.")),s.comments=d.comments,l=d.activeSection):await C.clear(a):(console.error(p.green(`Resuming review (${d.comments.length} comment${d.comments.length!==1?"s":""}).`)),s.comments=d.comments,l=d.activeSection))}let u,m={verdict:null,summary:""};if(e.cli)u=await Q(s,i,a?()=>{vt(C,a,c,s.comments,null).catch(w=>{let fe=w instanceof Error?w.message:String(w);console.warn(p.yellow(`Failed to save session: ${fe}`))})}:void 0);else{let d=await pe({doc:s,absPath:a,contentHash:c,restoredActiveSection:l});s.comments=d.comments,m={verdict:d.verdict,summary:d.summary},u=s}a&&await C.clear(a);let f;e.output!==void 0?f=e.output:(f=await oe(i),f==="claude"&&!$()&&(console.error(p.red("Claude CLI not found in PATH.")),console.error(p.dim("Install: https://docs.anthropic.com/en/docs/claude-code")),console.error(p.yellow("Falling back to stdout.")),f="stdout"));let h=U(u,m);ee(h,f,{outputFile:e.outputFile,inputFile:t})}function ht(t){if(t){if(!N(t))throw new Error(`File not found: ${t}`);return M(t,"utf-8")}return process.stdin.isTTY?(T.help(),""):M("/dev/stdin","utf-8")}function gt(t){let e=Date.now()-new Date(t).getTime(),n=Math.floor(e/1e3);if(n<60)return"just now";let i=Math.floor(n/60);if(i<60)return`${i}m ago`;let o=Math.floor(i/60);if(o<24)return`${o}h ago`;let r=Math.floor(o/24);return r<7?`${r}d ago`:`${Math.floor(r/7)}w ago`}async function vt(t,e,n,i,o){let r={version:1,planPath:e,contentHash:n,comments:i,activeSection:o,lastModified:new Date().toISOString()};await t.save(e,r)}async function wt(t,e,n){let i=await t.load(e);return i?{comments:i.comments,activeSection:i.activeSection,stale:i.contentHash!==n}:null}async function St(t){let e=await t.list(),n=[];for(let i of e){let o=await t.load(i.key),r=o?.planPath??i.key,s=null;if(N(r)&&o)try{s=R(M(r,"utf-8"))!==o.contentHash}catch{s=null}n.push({planPath:r,commentCount:i.commentCount,lastModified:i.lastModified,stale:s})}return n}
30
30
  //# sourceMappingURL=index.js.map