plan-review 1.1.3 → 1.1.5

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,31 +1,30 @@
1
1
  #!/usr/bin/env node
2
- #!/usr/bin/env node
3
- import{Command as rt}from"commander";import{readFileSync as ge,mkdirSync as st,copyFileSync as at}from"node:fs";import{existsSync as ve}from"node:fs";import{homedir as ct}from"node:os";import{dirname as lt,join as N,resolve as ut}from"node:path";import{fileURLToPath as mt}from"node:url";import p from"chalk";function L(e,t="auto"){let n=e.split(`
4
- `),i=we(n),o=Se(n);return t==="auto"?ye(e)?Ce(e,i,o):M(e,i,o):t==="separator"?E(e,i,o):M(e,i,o)}function we(e){let t=e.find(n=>/^# /.test(n));return t?t.replace(/^# /,"").trim():"Untitled"}function Se(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 ye(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 M(e,t,n){let i=be(e);return i.length===0?E(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 be(e){let t=e.split(`
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(`
5
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(`
6
5
  `).trim()}),i=f[1].trim(),o=c,r=[]):i&&r.push(m)}return i&&n.push({heading:i,level:o,body:r.join(`
7
- `).trim()}),n}function E(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(`
8
- `)[0].replace(/^#+\s*/,"").trim();return{id:`section-${r+1}`,heading:a||`Section ${r+1}`,level:2,body:o.trim()}}),comments:[]}}function Ce(e,t,n){let i=e.split(`
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(`
9
8
  `),o=[],r=0,s=0,a="",c="",u=0,l=[],m=!1;function f(){if(!c)return;let h=l.join(`
10
- `).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:$e(h),relatedFiles:Te(h),verification:xe(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(/^## (.+)/),R=h.match(/^### (.+)/);d?(f(),c=d[1].trim(),u=2,l=[]):R?(f(),c=R[1].trim(),u=3,l=[]):l.push(h)}return f(),{title:t,metadata:n,mode:"plan",sections:o,comments:[]}}function $e(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 Te(e){let t=[],n=e.split(`
11
- `),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 xe(e){let t=e.match(/\*\*Verification:\*\*\s*`(.+?)`/);return t?t[1]:void 0}import{createHash as J}from"node:crypto";import{mkdirSync as Re,readFileSync as H,writeFileSync as He,unlinkSync as _,readdirSync as Pe,existsSync as U}from"node:fs";import{join as P,resolve as ke}from"node:path";import{homedir as Ie}from"node:os";function Oe(e){let t=ke(e);return J("sha256").update(t).digest("hex").slice(0,16)}function k(e){return P(y(),Oe(e)+".json")}function y(){let e=P(Ie(),".plan-review","sessions");return Re(e,{recursive:!0}),e}function I(e){return`sha256:${J("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 W(e,t){let n=k(e);if(!U(n))return null;let i;try{i=H(n,"utf-8")}catch{return null}let o;try{o=JSON.parse(i)}catch{console.warn(`[plan-review] Corrupt session file, removing: ${n}`);try{_(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{_(t)}catch{}}function z(){let e=y(),t;try{t=Pe(e)}catch{return[]}let n=[];for(let i of t){if(!i.endsWith(".json"))continue;let o=P(e,i),r;try{let a=H(o,"utf-8");r=JSON.parse(a)}catch{console.warn(`[plan-review] Skipping corrupt session file: ${o}`);continue}let s;if(!U(r.planPath))s=null;else try{let a=H(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 De(e){return[...e].sort((t,n)=>{let i=t.anchor?.startLine??1/0,o=n.anchor?.startLine??1/0;return i-o})}function Fe(e){return e==="approved"?"Approved":"Comment"}function V(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:** ${Fe(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=De(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(`
12
- `)}import*as X from"node:readline";import w from"chalk";import{marked as Y}from"marked";import{markedTerminal as je}from"marked-terminal";import g from"chalk";Y.use(je());function q(e){let t=[];e.level===3&&e.dependencies&&(t.push(Ae(e)),t.push(""));let n=`${"#".repeat(e.level)} ${e.heading}`,i=e.body||"",o=`${n}
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}
13
12
 
14
- ${i}`;return t.push(Y.parse(o)),t.join(`
15
- `)}function Ae(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(`
16
- `)}function G(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(`
17
- `)}async function Z(e,t=!1,n){let i=t?(await import("node:fs")).createReadStream("/dev/tty"):process.stdin,o=X.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(G(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 K(e,s,r,n);else{let u=Be(e,c);if(u){let l=s.indexOf(u);await K(e,s.slice(l),r,n)}else console.error(w.red(`Section "${c}" not found. Try again.`))}}return o.close(),Ne(e),e}async function K(e,t,n,i){for(let o=0;o<t.length;o++){let r=t[o];console.error(q(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 Be(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 Ne(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 Q,spawn as Me}from"node:child_process";import{writeFileSync as Le}from"node:fs";import{resolve as F}from"node:path";import v from"chalk";function ee(e,t,n={}){switch(t){case"stdout":process.stdout.write(e+`
18
- `);break;case"clipboard":Ee(e);break;case"file":Je(e,n.outputFile,n.inputFile);break;case"claude":_e(e);break}}function Ee(e){let t=Ue(process.platform);if(!t){console.error(v.yellow("Clipboard not supported on this platform. Falling back to stdout.")),process.stdout.write(e+`
19
- `);return}try{Q(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+`
20
- `)}}function Je(e,t,n){let i=t?F(t):n?F(n.replace(/\.md$/,".review.md")):F("review.md");try{Le(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+`
21
- `)}}function _e(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+`
22
- `);return}let t=Me("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+`
23
- `)})}function Ue(e){switch(e){case"darwin":return"pbcopy";case"linux":return"xclip -selection clipboard";case"win32":return"clip";default:return null}}function $(){try{return Q("which claude",{stdio:"ignore"}),!0}catch{return!1}}import{createRequire as dt}from"node:module";import*as j from"node:readline";import te from"chalk";async function ne(e){return e?(await import("node:fs")).createReadStream("/dev/tty"):process.stdin}async function oe(e){let t=j.createInterface({input:await ne(e),output:process.stderr}),n=await new Promise(i=>{t.question(te.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 ie(e,t){let n=j.createInterface({input:await ne(t),output:process.stderr}),i=await new Promise(o=>{n.question(te.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 Ke}from"node:http";import{readFile as We}from"node:fs";import{join as ze,normalize as Ve,resolve as re,sep as Ye,extname as qe}from"node:path";var T=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(e){if(typeof e!="object"||e===null)return!1;let t=e;return typeof t.sectionId=="string"&&typeof t.text=="string"}function Ge(e){return e==="approved"||e===null}function ce(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(!Ge(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(!ae(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(!ae(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=qe(s).toLowerCase();if(!se[a]){n.writeHead(415,{"Content-Type":"text/plain"}),n.end("Unsupported media type");return}let c=Ve(s),u=re(r),l=re(ze(u,c));if(!l.startsWith(u+Ye)&&l!==u){n.writeHead(403,{"Content-Type":"text/plain"}),n.end("Forbidden");return}We(l,(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 le(e){return Ke(ce(e))}function ue(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 me(e){return new Promise((t,n)=>{e.close(i=>i?n(i):t()),e.closeAllConnections()})}import{readFileSync as Xe,existsSync as de}from"node:fs";import{join as A,dirname as Ze}from"node:path";import{fileURLToPath as Qe}from"node:url";var pe=Ze(Qe(import.meta.url));function et(){let e=A(pe,"..","browser","index.html");if(de(e))return e;let t=A(pe,"..",".."),n=A(t,"dist","browser","index.html");if(de(n))return n;throw new Error(`Browser HTML not found. Run 'npm run build' first.
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.
24
23
  Looked in:
25
- ${e}
26
- ${n}`)}var B=null;function fe(){return B||(B=Xe(et(),"utf-8")),B}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=le({getDocument:()=>this.doc,getInitialActiveSection:()=>this.initialActiveSection,getAssetBaseDir:()=>this.assetBaseDir,onSubmit:n=>this.submitHandler?.(n),getAssetHtml:()=>fe(),onSessionSave:(n,i)=>this.sessionSaveHandler?.(n,i),onHeartbeat:()=>this.heartbeatHandler?.(),onPause:()=>this.pauseHandler?.(),onCancel:()=>this.cancelHandler?.()}),ue(this.server,t)}async stop(){this.server&&this.server.listening&&(await me(this.server),this.server=null)}};var ot=1800*1e3,it=30*1e3;async function he({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}
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}
27
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
28
- `)}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=N(e,"..","skills","plan-review","SKILL.md");ve(t)||(console.error(p.red("Skill file not found in package. Expected at: "+t)),process.exit(1));let n=N(ct(),".claude","skills","plan-review");st(n,{recursive:!0}),at(t,N(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=z(),t=y();e.length===0&&(console.error(p.dim(`No saved sessions. (${t})`)),process.exit(0)),console.error(p.bold(`Saved review sessions (${t}):
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}):
29
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}
30
- `))}});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=L(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=W(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,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 Z(s,i,a?()=>b(a,c,s.comments,null):void 0);else{let d=await he({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 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=V(l,m);ee(h,f,{outputFile:t.outputFile,inputFile:e})}function gt(e){if(e){if(!ve(e))throw new Error(`File not found: ${e}`);return ge(e,"utf-8")}return process.stdin.isTTY?(S.help(),""):ge("/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`}
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`}
31
30
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../../core/src/parser.ts", "../../core/src/session.ts", "../../core/src/formatter.ts", "../src/navigator.ts", "../src/renderer.ts", "../src/output.ts", "../src/prompts.ts", "../src/browser-review.ts", "../src/server/server.ts", "../src/server/routes.ts", "../src/server/assets.ts", "../src/transport.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport { readFileSync, mkdirSync, copyFileSync } from 'node:fs';\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve as resolvePath } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport chalk from 'chalk';\nimport { parse, formatReview, loadSession, saveSession, clearSession, computeContentHash, listSessions, getSessionDir } from '@plan-review/core';\nimport type { OutputTarget, PlanDocument, ReviewSubmission } from '@plan-review/core';\nimport { navigate } from './navigator.js';\nimport { writeOutput, isClaudeAvailable } from './output.js';\nimport { createRequire } from 'node:module';\nimport { promptOutputTarget, promptYesNo } from './prompts.js';\nimport { runBrowserReview } from './browser-review.js';\n\nconst require = createRequire(import.meta.url);\nconst { version } = require('../package.json');\n\nconst program = new Command();\n\nprogram\n .name('plan-review')\n .description('Interactive CLI for reviewing AI-generated markdown plans')\n .version(version)\n .argument('[file]', 'Path to markdown file (omit to read stdin)')\n .option('-o, --output <target>', 'Output target: stdout, clipboard, file, claude')\n .option('--output-file <path>', 'Custom output file path (with --output file)')\n .option('--split-by <strategy>', 'Force split strategy: heading, separator')\n .option('--fresh', 'Skip session resume, start clean review')\n .option('--cli', 'Use the terminal review UI instead of the browser (SSH/CI/headless)')\n .action(async (file: string | undefined, opts: { output?: string; outputFile?: string; splitBy?: string; cli?: boolean; fresh?: boolean }) => {\n try {\n await run(file, opts);\n } catch (err) {\n if (err instanceof Error) {\n const cancelled = err.message.startsWith('Review cancelled');\n console.error(cancelled ? chalk.yellow(err.message) : chalk.red(`Error: ${err.message}`));\n }\n process.exit(1);\n }\n });\n\nprogram\n .command('install-skill')\n .description('Install Claude Code skill to ~/.claude/skills/plan-review/')\n .action(() => {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const src = join(__dirname, '..', 'skills', 'plan-review', 'SKILL.md');\n if (!existsSync(src)) {\n console.error(chalk.red('Skill file not found in package. Expected at: ' + src));\n process.exit(1);\n }\n const dest = join(homedir(), '.claude', 'skills', 'plan-review');\n mkdirSync(dest, { recursive: true });\n copyFileSync(src, join(dest, 'SKILL.md'));\n console.error(chalk.green(`Skill installed to ${dest}/SKILL.md`));\n console.error(chalk.dim('Claude Code will auto-discover it. Try: \"I want to review this plan\"'));\n });\n\nprogram\n .command('sessions')\n .description('List all saved review sessions')\n .action(() => {\n const sessions = listSessions();\n const dir = getSessionDir();\n if (sessions.length === 0) {\n console.error(chalk.dim(`No saved sessions. (${dir})`));\n process.exit(0);\n }\n console.error(chalk.bold(`Saved review sessions (${dir}):\\n`));\n for (const s of sessions) {\n const age = formatRelativeTime(s.lastModified);\n let status = '';\n if (s.stale === true) status = chalk.yellow(' | plan file changed since last review');\n else if (s.stale === null) status = chalk.red(' | plan file not found');\n console.error(` ${s.planPath}`);\n console.error(chalk.dim(` ${s.commentCount} comment${s.commentCount !== 1 ? 's' : ''} | last modified ${age}${status}\\n`));\n }\n });\n\nprogram.parse();\n\nasync function run(\n file: string | undefined,\n opts: { output?: string; outputFile?: string; splitBy?: string; cli?: boolean; fresh?: boolean },\n): Promise<void> {\n // Validate explicit output target early, before the review starts\n const validTargets: OutputTarget[] = ['stdout', 'clipboard', 'file', 'claude'];\n if (opts.output !== undefined) {\n const explicitTarget = opts.output as OutputTarget;\n if (!validTargets.includes(explicitTarget)) {\n throw new Error(`Invalid output target: \"${opts.output}\". Use: ${validTargets.join(', ')}`);\n }\n // Fail fast: check claude availability before starting review\n if (explicitTarget === 'claude' && !isClaudeAvailable()) {\n console.error(chalk.red('Claude CLI not found in PATH.'));\n console.error(chalk.dim('Install: https://docs.anthropic.com/en/docs/claude-code'));\n console.error(chalk.yellow('Will fall back to stdout after review.'));\n }\n }\n\n // Read input \u2014 track whether it came from stdin\n const inputFromStdin = !file && !process.stdin.isTTY;\n const input = readInput(file);\n if (!input.trim()) {\n console.error(chalk.yellow('Empty file, nothing to review.'));\n process.exit(0);\n }\n\n // Parse\n const splitStrategy = opts.splitBy === 'heading' ? 'heading' as const\n : opts.splitBy === 'separator' ? 'separator' as const\n : 'auto' as const;\n const doc = parse(input, splitStrategy);\n\n console.error(chalk.dim(`Detected mode: ${doc.mode} | ${doc.sections.length} sections`));\n\n // Session resume logic\n const absPath = file ? resolvePath(file) : null;\n const contentHash = computeContentHash(input);\n\n let restoredActiveSection: string | null = null;\n if (absPath) {\n if (opts.fresh) {\n clearSession(absPath);\n } else {\n const session = loadSession(absPath, contentHash);\n if (session && session.comments.length > 0) {\n if (!session.stale) {\n console.error(chalk.green(`Resuming review (${session.comments.length} comment${session.comments.length !== 1 ? 's' : ''}).`));\n doc.comments = session.comments;\n restoredActiveSection = session.activeSection;\n } else {\n // Prompt user for stale session\n const answer = await promptYesNo(\n `Plan file changed since last review (${session.comments.length} comment${session.comments.length !== 1 ? 's' : ''}). Resume anyway?`,\n inputFromStdin,\n );\n if (answer) {\n console.error(chalk.yellow('Resuming with stale session.'));\n doc.comments = session.comments;\n restoredActiveSection = session.activeSection;\n } else {\n clearSession(absPath);\n }\n }\n }\n }\n }\n\n // Navigate (interactive review or browser)\n let reviewed: PlanDocument;\n let reviewMeta: Pick<ReviewSubmission, 'verdict' | 'summary'> = { verdict: null, summary: '' };\n if (!opts.cli) {\n const submission = await runBrowserReview({ doc, absPath, contentHash, restoredActiveSection });\n doc.comments = submission.comments;\n reviewMeta = { verdict: submission.verdict, summary: submission.summary };\n reviewed = doc;\n } else {\n const onCommentChange = absPath\n ? () => saveSession(absPath, contentHash, doc.comments, null)\n : undefined;\n reviewed = await navigate(doc, inputFromStdin, onCommentChange);\n }\n\n // Clear session after successful review completion\n if (absPath) clearSession(absPath);\n\n // Determine output target after review is complete\n let outputTarget: OutputTarget;\n if (opts.output !== undefined) {\n outputTarget = opts.output as OutputTarget;\n } else {\n outputTarget = await promptOutputTarget(inputFromStdin);\n // Check claude availability after prompting\n if (outputTarget === 'claude' && !isClaudeAvailable()) {\n console.error(chalk.red('Claude CLI not found in PATH.'));\n console.error(chalk.dim('Install: https://docs.anthropic.com/en/docs/claude-code'));\n console.error(chalk.yellow('Falling back to stdout.'));\n outputTarget = 'stdout';\n }\n }\n\n // Format and output\n const output = formatReview(reviewed, reviewMeta);\n writeOutput(output, outputTarget, { outputFile: opts.outputFile, inputFile: file });\n}\n\nfunction readInput(file: string | undefined): string {\n if (file) {\n if (!existsSync(file)) {\n throw new Error(`File not found: ${file}`);\n }\n return readFileSync(file, 'utf-8');\n }\n\n // Read from stdin (piped)\n if (!process.stdin.isTTY) {\n return readFileSync('/dev/stdin', 'utf-8');\n }\n\n // No file, no stdin pipe \u2014 show help\n program.help();\n return ''; // unreachable\n}\n\nfunction formatRelativeTime(iso: string): string {\n const ms = Date.now() - new Date(iso).getTime();\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return 'just now';\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n if (days < 7) return `${days}d ago`;\n const weeks = Math.floor(days / 7);\n return `${weeks}w ago`;\n}\n", "import type { PlanDocument, Section, SplitStrategy } from './types.js';\n\nconst MIN_SECTION_CHARS = 5;\n\nexport function parse(input: string, strategy: SplitStrategy = 'auto'): PlanDocument {\n const lines = input.split('\\n');\n const title = extractTitle(lines);\n const metadata = extractMetadata(lines);\n\n if (strategy === 'auto') {\n if (isPlanDocument(input)) {\n return parsePlan(input, title, metadata);\n }\n return parseGeneric(input, title, metadata);\n }\n\n if (strategy === 'separator') {\n return parseBySeparator(input, title, metadata);\n }\n\n return parseGeneric(input, title, metadata);\n}\n\nfunction extractTitle(lines: string[]): string {\n const h1 = lines.find((l) => /^# /.test(l));\n return h1 ? h1.replace(/^# /, '').trim() : 'Untitled';\n}\n\nfunction extractMetadata(lines: string[]): Record<string, string> {\n const meta: Record<string, string> = {};\n for (const line of lines.slice(0, 20)) {\n const match = line.match(/^\\*\\*(\\w[\\w\\s]*?):\\*\\*\\s*(.+)/);\n if (match) {\n meta[match[1].trim()] = match[2].trim();\n }\n }\n return meta;\n}\n\nexport function isPlanDocument(input: string): boolean {\n const stripped = input.replace(/```[\\s\\S]*?```/g, '');\n const hasH2H3Hierarchy =\n /^## /m.test(stripped) && /^### /m.test(stripped);\n const hasPlanFields =\n /\\*\\*Depends On:\\*\\*/m.test(stripped) ||\n /\\*\\*Blocks:\\*\\*/m.test(stripped) ||\n /\\*\\*Verification:\\*\\*/m.test(stripped) ||\n /\\*\\*Related Files:\\*\\*/m.test(stripped);\n\n return hasH2H3Hierarchy && hasPlanFields;\n}\n\nfunction parseGeneric(\n input: string,\n title: string,\n metadata: Record<string, string>,\n): PlanDocument {\n const sections = splitByHeadings(input);\n\n if (sections.length === 0) {\n return parseBySeparator(input, title, metadata);\n }\n\n return {\n title,\n metadata,\n mode: 'generic',\n sections: sections.map((s, i) => ({\n id: `section-${i + 1}`,\n heading: s.heading,\n level: s.level,\n body: s.body,\n })),\n comments: [],\n };\n}\n\ninterface RawSection {\n heading: string;\n level: number;\n body: string;\n}\n\nfunction splitByHeadings(input: string): RawSection[] {\n const lines = input.split('\\n');\n const sections: RawSection[] = [];\n let currentHeading = '';\n let currentLevel = 0;\n let currentBody: string[] = [];\n\n // Find the most common heading level (## or ###)\n const h2Count = (input.match(/^## /gm) || []).length;\n const h3Count = (input.match(/^### /gm) || []).length;\n const splitLevel = h2Count > 0 ? 2 : h3Count > 0 ? 3 : 0;\n\n if (splitLevel === 0) return [];\n\n const headingRegex = new RegExp(`^${'#'.repeat(splitLevel)} (.+)`);\n let inCodeBlock = false;\n\n for (const line of lines) {\n if (line.startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n if (currentHeading) {\n currentBody.push(line);\n }\n continue;\n }\n\n if (inCodeBlock) {\n if (currentHeading) {\n currentBody.push(line);\n }\n continue;\n }\n\n const match = line.match(headingRegex);\n if (match) {\n if (currentHeading) {\n sections.push({\n heading: currentHeading,\n level: currentLevel,\n body: currentBody.join('\\n').trim(),\n });\n }\n currentHeading = match[1].trim();\n currentLevel = splitLevel;\n currentBody = [];\n } else if (currentHeading) {\n currentBody.push(line);\n }\n }\n\n if (currentHeading) {\n sections.push({\n heading: currentHeading,\n level: currentLevel,\n body: currentBody.join('\\n').trim(),\n });\n }\n\n return sections;\n}\n\nfunction parseBySeparator(\n input: string,\n title: string,\n metadata: Record<string, string>,\n): PlanDocument {\n const parts = input.split(/\\n---\\n/).filter((p) => {\n return p.trim().length >= MIN_SECTION_CHARS;\n });\n\n if (parts.length <= 1) {\n return {\n title,\n metadata,\n mode: 'generic',\n sections: [\n {\n id: 'section-1',\n heading: title,\n level: 1,\n body: input.trim(),\n },\n ],\n comments: [],\n };\n }\n\n return {\n title,\n metadata,\n mode: 'generic',\n sections: parts.map((p, i) => {\n const lines = p.trim().split('\\n');\n const firstLine = lines[0].replace(/^#+\\s*/, '').trim();\n return {\n id: `section-${i + 1}`,\n heading: firstLine || `Section ${i + 1}`,\n level: 2,\n body: p.trim(),\n };\n }),\n comments: [],\n };\n}\n\nfunction parsePlan(\n input: string,\n title: string,\n metadata: Record<string, string>,\n): PlanDocument {\n const lines = input.split('\\n');\n const sections: Section[] = [];\n\n let milestoneIndex = 0;\n let taskIndex = 0;\n let currentMilestoneId = '';\n let currentHeading = '';\n let currentLevel = 0;\n let currentBody: string[] = [];\n let inCodeBlock = false;\n\n function flushSection() {\n if (!currentHeading) return;\n\n const body = currentBody.join('\\n').trim();\n\n if (currentLevel === 2) {\n milestoneIndex++;\n taskIndex = 0;\n currentMilestoneId = `milestone-${milestoneIndex}`;\n sections.push({\n id: currentMilestoneId,\n heading: currentHeading,\n level: 2,\n body,\n });\n return;\n }\n\n taskIndex++;\n const id = `${milestoneIndex}.${taskIndex}`;\n sections.push({\n id,\n heading: currentHeading,\n level: 3,\n body,\n parent: currentMilestoneId,\n dependencies: extractDependencies(body),\n relatedFiles: extractRelatedFiles(body),\n verification: extractVerification(body),\n });\n }\n\n for (const line of lines) {\n if (line.startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n currentBody.push(line);\n continue;\n }\n\n if (inCodeBlock) {\n currentBody.push(line);\n continue;\n }\n\n const h2Match = line.match(/^## (.+)/);\n const h3Match = line.match(/^### (.+)/);\n\n if (h2Match) {\n flushSection();\n currentHeading = h2Match[1].trim();\n currentLevel = 2;\n currentBody = [];\n } else if (h3Match) {\n flushSection();\n currentHeading = h3Match[1].trim();\n currentLevel = 3;\n currentBody = [];\n } else {\n currentBody.push(line);\n }\n }\n flushSection();\n\n return {\n title,\n metadata,\n mode: 'plan',\n sections,\n comments: [],\n };\n}\n\nfunction extractDependencies(body: string): { dependsOn: string[]; blocks: string[] } {\n const dependsMatch = body.match(/\\*\\*Depends On:\\*\\*\\s*(.+)/);\n const blocksMatch = body.match(/\\*\\*Blocks:\\*\\*\\s*(.+)/);\n\n const parseList = (raw: string): string[] => {\n const trimmed = raw.trim();\n if (trimmed === '(none)' || trimmed === '') return [];\n return trimmed.split(/,\\s*/).map((s) => s.trim());\n };\n\n return {\n dependsOn: dependsMatch ? parseList(dependsMatch[1]) : [],\n blocks: blocksMatch ? parseList(blocksMatch[1]) : [],\n };\n}\n\nfunction extractRelatedFiles(body: string): string[] {\n const files: string[] = [];\n const lines = body.split('\\n');\n let inRelatedFiles = false;\n\n for (const line of lines) {\n if (/\\*\\*Related Files:\\*\\*/.test(line)) {\n inRelatedFiles = true;\n continue;\n }\n if (inRelatedFiles) {\n const fileMatch = line.match(/^- `(.+)`(.*)$/);\n if (fileMatch) {\n const suffix = fileMatch[2].trim();\n files.push(suffix ? `${fileMatch[1]} ${suffix}` : fileMatch[1]);\n } else if (line.trim() === '' || /^\\*\\*/.test(line.trim())) {\n inRelatedFiles = false;\n }\n }\n }\n\n return files;\n}\n\nfunction extractVerification(body: string): string | undefined {\n const match = body.match(/\\*\\*Verification:\\*\\*\\s*`(.+?)`/);\n return match ? match[1] : undefined;\n}\n", "import { createHash } from 'node:crypto';\nimport {\n mkdirSync,\n readFileSync,\n writeFileSync,\n unlinkSync,\n readdirSync,\n existsSync,\n} from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { ReviewComment } from './types.js';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface SessionData {\n version: number;\n planPath: string;\n contentHash: string;\n comments: ReviewComment[];\n activeSection: string | null;\n lastModified: string;\n}\n\nexport interface SessionLoadResult {\n comments: ReviewComment[];\n activeSection: string | null;\n stale: boolean;\n}\n\n// \u2500\u2500 Internal helpers (not exported) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction pathHash(planPath: string): string {\n const abs = resolve(planPath);\n const hash = createHash('sha256').update(abs).digest('hex');\n return hash.slice(0, 16);\n}\n\nfunction sessionFilePath(planPath: string): string {\n return join(getSessionDir(), pathHash(planPath) + '.json');\n}\n\n// \u2500\u2500 Exported functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function getSessionDir(): string {\n const dir = join(homedir(), '.plan-review', 'sessions');\n mkdirSync(dir, { recursive: true });\n return dir;\n}\n\nexport function computeContentHash(content: string): string {\n const hex = createHash('sha256').update(content).digest('hex');\n return `sha256:${hex}`;\n}\n\nexport function saveSession(\n planPath: string,\n contentHash: string,\n comments: ReviewComment[],\n activeSection: string | null,\n): void {\n try {\n const data: SessionData = {\n version: 1,\n planPath,\n contentHash,\n comments,\n activeSection,\n lastModified: new Date().toISOString(),\n };\n const filePath = sessionFilePath(planPath);\n writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');\n } catch (err) {\n console.warn(`[plan-review] Failed to save session: ${(err as Error).message}`);\n }\n}\n\nexport function loadSession(\n planPath: string,\n currentContentHash: string,\n): SessionLoadResult | null {\n const filePath = sessionFilePath(planPath);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n let raw: string;\n try {\n raw = readFileSync(filePath, 'utf-8');\n } catch {\n return null;\n }\n\n let data: SessionData;\n try {\n data = JSON.parse(raw) as SessionData;\n } catch {\n console.warn(`[plan-review] Corrupt session file, removing: ${filePath}`);\n try {\n unlinkSync(filePath);\n } catch {\n // ignore\n }\n return null;\n }\n\n // Restore Date objects in comment timestamps\n const comments: ReviewComment[] = data.comments.map((c) => ({\n ...c,\n timestamp: new Date(c.timestamp),\n }));\n\n return {\n comments,\n activeSection: data.activeSection,\n stale: data.contentHash !== currentContentHash,\n };\n}\n\nexport function clearSession(planPath: string): void {\n const filePath = sessionFilePath(planPath);\n try {\n unlinkSync(filePath);\n } catch {\n // No error if missing\n }\n}\n\nexport function listSessions(): Array<{\n planPath: string;\n commentCount: number;\n lastModified: string;\n stale: boolean | null;\n}> {\n const dir = getSessionDir();\n let files: string[];\n try {\n files = readdirSync(dir);\n } catch {\n return [];\n }\n\n const results: Array<{\n planPath: string;\n commentCount: number;\n lastModified: string;\n stale: boolean | null;\n }> = [];\n\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n\n const filePath = join(dir, file);\n let data: SessionData;\n try {\n const raw = readFileSync(filePath, 'utf-8');\n data = JSON.parse(raw) as SessionData;\n } catch {\n console.warn(`[plan-review] Skipping corrupt session file: ${filePath}`);\n continue;\n }\n\n let stale: boolean | null;\n if (!existsSync(data.planPath)) {\n stale = null;\n } else {\n try {\n const currentContent = readFileSync(data.planPath, 'utf-8');\n const currentHash = computeContentHash(currentContent);\n stale = currentHash !== data.contentHash;\n } catch {\n stale = null;\n }\n }\n\n results.push({\n planPath: data.planPath,\n commentCount: data.comments.length,\n lastModified: data.lastModified,\n stale,\n });\n }\n\n return results;\n}\n", "import type { PlanDocument, ReviewComment, ReviewVerdict } from './types.js';\n\nfunction escapeMarkdown(text: string): string {\n return text.replace(/([\\\\*_`~\\[\\]#>|])/g, '\\\\$1');\n}\n\nfunction sortComments(comments: ReviewComment[]): ReviewComment[] {\n return [...comments].sort((a, b) => {\n const aLine = a.anchor?.startLine ?? Infinity;\n const bLine = b.anchor?.startLine ?? Infinity;\n return aLine - bLine;\n });\n}\n\nfunction verdictLabel(verdict: ReviewVerdict): string {\n return verdict === 'approved' ? 'Approved' : 'Comment';\n}\n\nexport interface FormatReviewOptions {\n verdict: ReviewVerdict;\n summary: string;\n}\n\nexport function formatReview(doc: PlanDocument, opts: FormatReviewOptions): string {\n const commentedSectionIds = new Set(doc.comments.map((c) => c.sectionId));\n const reviewableSections = doc.sections.filter((s) =>\n doc.mode === 'plan' ? s.level === 3 : s.level >= 2,\n );\n const commentedSections = reviewableSections.filter((s) => commentedSectionIds.has(s.id));\n\n const parts: string[] = [];\n\n parts.push(`# Plan Review: ${doc.title}`);\n parts.push('');\n parts.push('## Review Summary');\n parts.push(`- **Verdict:** ${verdictLabel(opts.verdict)}`);\n parts.push(`- **Sections reviewed:** ${commentedSections.length}/${reviewableSections.length}`);\n parts.push(`- **Comments:** ${doc.comments.length}`);\n const skippedCount = reviewableSections.length - commentedSections.length;\n parts.push(\n `- **Skipped:** ${skippedCount} section${skippedCount === 1 ? '' : 's'} without comments`,\n );\n\n if (opts.summary.trim() !== '') {\n parts.push('');\n parts.push('## Overall Comments');\n parts.push('');\n parts.push(escapeMarkdown(opts.summary));\n }\n\n if (commentedSections.length > 0) {\n parts.push('');\n parts.push('---');\n }\n\n for (const section of commentedSections) {\n const sectionComments = sortComments(\n doc.comments.filter((c) => c.sectionId === section.id),\n );\n\n parts.push('');\n parts.push(`## Section ${section.id}: ${section.heading}`);\n parts.push('');\n\n if (doc.mode === 'plan' && section.dependencies) {\n const deps = section.dependencies;\n if (deps.dependsOn.length > 0) {\n parts.push(`Depends on: ${deps.dependsOn.join(', ')}`);\n }\n if (deps.blocks.length > 0) {\n parts.push(`Blocks: ${deps.blocks.join(', ')}`);\n }\n parts.push('');\n }\n\n for (const comment of sectionComments) {\n if (comment.anchor) {\n parts.push('### Reviewer Comment');\n parts.push('');\n for (const line of comment.anchor.lineTexts) {\n parts.push(`> ${line}`);\n }\n parts.push('');\n parts.push(escapeMarkdown(comment.text));\n } else {\n parts.push('### Reviewer Comment (entire section)');\n parts.push('');\n parts.push(escapeMarkdown(comment.text));\n }\n parts.push('');\n parts.push('---');\n }\n }\n\n return parts.join('\\n');\n}\n", "import * as readline from 'node:readline';\nimport chalk from 'chalk';\nimport type { PlanDocument, ReviewComment, Section } from '@plan-review/core';\nimport { renderSection, renderToc } from './renderer.js';\n\nexport async function navigate(doc: PlanDocument, inputFromStdin: boolean = false, onCommentChange?: () => void): Promise<PlanDocument> {\n // When input was read from stdin, stdin is exhausted.\n // Open /dev/tty directly for interactive prompts.\n const ttyInput = inputFromStdin\n ? (await import('node:fs')).createReadStream('/dev/tty')\n : process.stdin;\n\n const rl = readline.createInterface({\n input: ttyInput,\n output: process.stderr,\n });\n\n const ask = (prompt: string): Promise<string> =>\n new Promise((resolve) => {\n rl.question(prompt, (answer) => resolve(answer.trim()));\n });\n\n const reviewableSections = getReviewableSections(doc);\n\n let running = true;\n\n while (running) {\n console.error(renderToc(doc));\n const input = await ask(\n chalk.cyan('> Enter section (e.g. 1.1), \\'all\\' for linear review, or \\'done\\' to finish: '),\n );\n\n if (input === 'done' || input === 'q') {\n running = false;\n } else if (input === 'all') {\n await linearReview(doc, reviewableSections, ask, onCommentChange);\n } else {\n const section = findSection(doc, input);\n if (section) {\n const startIdx = reviewableSections.indexOf(section);\n await linearReview(doc, reviewableSections.slice(startIdx), ask, onCommentChange);\n } else {\n console.error(chalk.red(`Section \"${input}\" not found. Try again.`));\n }\n }\n }\n\n rl.close();\n printSummary(doc);\n return doc;\n}\n\nasync function linearReview(\n doc: PlanDocument,\n sections: Section[],\n ask: (prompt: string) => Promise<string>,\n onCommentChange?: () => void,\n): Promise<void> {\n for (let i = 0; i < sections.length; i++) {\n const section = sections[i];\n console.error(renderSection(section));\n\n const input = await ask(\n chalk.cyan('> Comment (enter to skip, \\'toc\\' for menu, \\'back\\' for previous): '),\n );\n\n if (input === 'toc') {\n return;\n } else if (input === 'back') {\n i -= (i > 0) ? 2 : 1; // -2 to go back (loop increments), -1 to re-show current\n continue;\n } else if (input !== '') {\n doc.comments.push({\n sectionId: section.id,\n text: input,\n timestamp: new Date(),\n });\n onCommentChange?.();\n }\n }\n}\n\nexport function findSection(doc: PlanDocument, input: string): Section | undefined {\n // Try exact ID match first\n const byId = doc.sections.find((s) => s.id === input);\n if (byId) return byId;\n\n // Try numeric index for generic mode\n const num = parseInt(input, 10);\n if (!isNaN(num)) {\n const reviewable = getReviewableSections(doc);\n if (num >= 1 && num <= reviewable.length) {\n return reviewable[num - 1];\n }\n }\n\n return undefined;\n}\n\nexport function getReviewableSections(doc: PlanDocument): Section[] {\n return doc.sections.filter((s) =>\n doc.mode === 'plan' ? s.level === 3 : s.level >= 2,\n );\n}\n\nexport function printSummary(doc: PlanDocument): void {\n const reviewable = getReviewableSections(doc);\n const commentedIds = new Set(doc.comments.map((c) => c.sectionId));\n\n console.error('');\n console.error(chalk.bold('Review Summary'));\n console.error(` Sections: ${reviewable.length}`);\n console.error(` Commented: ${chalk.green(String(commentedIds.size))}`);\n console.error(` Skipped: ${chalk.dim(String(reviewable.length - commentedIds.size))}`);\n console.error(` Total comments: ${doc.comments.length}`);\n console.error('');\n}\n", "import { marked } from 'marked';\nimport { markedTerminal } from 'marked-terminal';\nimport chalk from 'chalk';\nimport type { Section, PlanDocument } from '@plan-review/core';\n\nmarked.use(markedTerminal());\n\nexport function renderSection(section: Section): string {\n const parts: string[] = [];\n\n if (section.level === 3 && section.dependencies) {\n parts.push(renderMetadataHeader(section));\n parts.push('');\n }\n\n const heading = `${'#'.repeat(section.level)} ${section.heading}`;\n const body = section.body || '';\n const markdown = `${heading}\\n\\n${body}`;\n parts.push(marked.parse(markdown) as string);\n\n return parts.join('\\n');\n}\n\nfunction renderMetadataHeader(section: Section): string {\n const deps = section.dependencies!;\n const dependsOn = deps.dependsOn.length > 0 ? deps.dependsOn.join(', ') : '(none)';\n const blocks = deps.blocks.length > 0 ? deps.blocks.join(', ') : '(none)';\n\n const lines: string[] = [\n `Task ${section.id}: ${section.heading}`,\n `\u2190 Depends on: ${dependsOn}`,\n `\u2192 Blocks: ${blocks}`,\n ];\n\n if (section.relatedFiles && section.relatedFiles.length > 0) {\n const fileList =\n section.relatedFiles.length <= 2\n ? section.relatedFiles.join(', ')\n : `${section.relatedFiles[0]} (+${section.relatedFiles.length - 1} more)`;\n lines.push(`Files: ${fileList}`);\n }\n\n if (section.verification) {\n lines.push(`Verify: ${section.verification}`);\n }\n\n const maxLen = Math.max(...lines.map((l) => l.length));\n const width = Math.min(maxLen + 4, process.stdout.columns || 80);\n const innerWidth = width - 2;\n\n const top = chalk.dim(`\u250C${'\u2500'.repeat(innerWidth)}\u2510`);\n const bottom = chalk.dim(`\u2514${'\u2500'.repeat(innerWidth)}\u2518`);\n const content = lines.map(\n (l) => chalk.dim('\u2502') + ' ' + chalk.cyan(l.slice(0, innerWidth - 2).padEnd(innerWidth - 2)) + ' ' + chalk.dim('\u2502'),\n );\n\n return [top, ...content, bottom].join('\\n');\n}\n\nexport function renderToc(doc: PlanDocument): string {\n const parts: string[] = [];\n const commentedIds = new Set(doc.comments.map((c) => c.sectionId));\n\n parts.push('');\n parts.push(chalk.bold.underline(doc.title));\n parts.push('');\n\n if (doc.mode === 'plan') {\n for (const section of doc.sections) {\n if (section.level === 2) {\n parts.push(chalk.bold.yellow(` ${section.heading}`));\n } else if (section.level === 3) {\n const marker = commentedIds.has(section.id) ? chalk.green('\u2713') : ' ';\n parts.push(` ${marker} ${chalk.dim(section.id)} ${section.heading}`);\n }\n }\n } else {\n const reviewable = doc.sections.filter((s) => s.level >= 2);\n for (let i = 0; i < reviewable.length; i++) {\n const section = reviewable[i];\n const num = String(i + 1).padStart(2);\n const marker = commentedIds.has(section.id) ? chalk.green('\u2713') : ' ';\n parts.push(` ${marker} ${chalk.dim(num)} ${section.heading}`);\n }\n }\n\n const commentedCount = commentedIds.size;\n const reviewable = doc.sections.filter((s) =>\n doc.mode === 'plan' ? s.level === 3 : s.level >= 2,\n );\n const remaining = reviewable.length - commentedCount;\n\n parts.push('');\n parts.push(\n ` ${chalk.green(`${commentedCount} section${commentedCount !== 1 ? 's' : ''} commented`)}` +\n ` ${chalk.dim(`${remaining} remaining`)}`,\n );\n parts.push('');\n\n return parts.join('\\n');\n}\n", "import { execSync, spawn } from 'node:child_process';\nimport { writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport chalk from 'chalk';\nimport type { OutputTarget } from '@plan-review/core';\n\nexport function writeOutput(\n content: string,\n target: OutputTarget,\n options: { outputFile?: string; inputFile?: string } = {},\n): void {\n switch (target) {\n case 'stdout':\n process.stdout.write(content + '\\n');\n break;\n case 'clipboard':\n writeToClipboard(content);\n break;\n case 'file':\n writeToFile(content, options.outputFile, options.inputFile);\n break;\n case 'claude':\n sendToClaude(content);\n break;\n }\n}\n\nfunction writeToClipboard(content: string): void {\n const cmd = getClipboardCommand(process.platform);\n if (!cmd) {\n console.error(chalk.yellow('Clipboard not supported on this platform. Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n return;\n }\n\n try {\n execSync(cmd, { input: content, stdio: ['pipe', 'ignore', 'ignore'] });\n console.error(chalk.green('Review copied to clipboard.'));\n } catch {\n console.error(chalk.yellow('Failed to copy to clipboard. Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n }\n}\n\nfunction writeToFile(content: string, outputFile?: string, inputFile?: string): void {\n const filePath = outputFile\n ? resolve(outputFile)\n : inputFile\n ? resolve(inputFile.replace(/\\.md$/, '.review.md'))\n : resolve('review.md');\n\n try {\n writeFileSync(filePath, content, 'utf-8');\n console.error(chalk.green(`Review written to ${filePath}`));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(chalk.red(`Failed to write file: ${msg}`));\n console.error(chalk.yellow('Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n }\n}\n\nfunction sendToClaude(content: string): void {\n if (!isClaudeAvailable()) {\n console.error(\n chalk.red('Claude CLI not found in PATH.'),\n );\n console.error(chalk.dim('Install: https://docs.anthropic.com/en/docs/claude-code'));\n console.error(chalk.yellow('Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n return;\n }\n\n const child = spawn('claude', [], {\n stdio: ['pipe', 'inherit', 'inherit'],\n });\n child.stdin.write(content);\n child.stdin.end();\n child.on('error', (err) => {\n console.error(chalk.yellow(`Failed to pipe to claude: ${err.message}. Falling back to stdout.`));\n process.stdout.write(content + '\\n');\n });\n}\n\nexport function getClipboardCommand(platform: string): string | null {\n switch (platform) {\n case 'darwin':\n return 'pbcopy';\n case 'linux':\n return 'xclip -selection clipboard';\n case 'win32':\n return 'clip';\n default:\n return null;\n }\n}\n\nexport function isClaudeAvailable(): boolean {\n try {\n execSync('which claude', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n", "import * as readline from 'node:readline';\nimport chalk from 'chalk';\nimport type { OutputTarget } from '@plan-review/core';\n\n// When stdin is the piped plan, readline can't reuse it for prompts \u2014 open\n// /dev/tty directly so the terminal still answers keystrokes.\nasync function ttyInputStream(inputFromStdin: boolean): Promise<NodeJS.ReadableStream> {\n return inputFromStdin\n ? (await import('node:fs')).createReadStream('/dev/tty')\n : process.stdin;\n}\n\nexport async function promptOutputTarget(inputFromStdin: boolean): Promise<OutputTarget> {\n const rl = readline.createInterface({\n input: await ttyInputStream(inputFromStdin),\n output: process.stderr,\n });\n\n const answer = await new Promise<string>((resolve) => {\n rl.question(\n chalk.cyan('> Output: (s)tdout, (c)lipboard, (f)ile, cl(a)ude? '),\n (a) => resolve(a.trim().toLowerCase()),\n );\n });\n rl.close();\n\n switch (answer) {\n case 's': case 'stdout': return 'stdout';\n case 'c': case 'clipboard': return 'clipboard';\n case 'f': case 'file': return 'file';\n case 'a': case 'claude': return 'claude';\n default: return 'stdout';\n }\n}\n\nexport async function promptYesNo(message: string, inputFromStdin: boolean): Promise<boolean> {\n const rl = readline.createInterface({\n input: await ttyInputStream(inputFromStdin),\n output: process.stderr,\n });\n\n const answer = await new Promise<string>((resolve) => {\n rl.question(chalk.yellow(`${message} (y/n) `), (a) => resolve(a.trim().toLowerCase()));\n });\n rl.close();\n\n return answer === 'y' || answer === 'yes';\n}\n", "import { spawnSync } from 'node:child_process';\nimport { dirname as dirnamePath } from 'node:path';\nimport type { PlanDocument } from '@plan-review/core';\nimport { saveSession } from '@plan-review/core';\nimport { HttpTransport } from './transport.js';\nimport type { ReviewSubmission } from './transport.js';\n\nexport interface BrowserReviewOptions {\n doc: PlanDocument;\n absPath: string | null;\n contentHash: string;\n restoredActiveSection: string | null;\n}\n\nconst IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes \u2014 overall ceiling\nconst HEARTBEAT_TIMEOUT_MS = 30 * 1000; // 30s without a heartbeat while visible = browser gone\n\n// Boot an HttpTransport, open the URL in the user's default browser, and\n// resolve with the reviewed comments when the browser posts them. Rejects if\n// the user cancels, closes the tab, or the overall idle ceiling fires.\nexport async function runBrowserReview(\n { doc, absPath, contentHash, restoredActiveSection }: BrowserReviewOptions,\n): Promise<ReviewSubmission> {\n const transport = new HttpTransport();\n transport.sendDocument(doc);\n transport.setInitialActiveSection(restoredActiveSection);\n // Plan-file directory anchors relative image paths via /_assets/<rel>.\n transport.setAssetBaseDir(absPath ? dirnamePath(absPath) : null);\n\n if (absPath) {\n transport.onSessionSave((comments, activeSection) => {\n saveSession(absPath, contentHash, comments, activeSection);\n });\n }\n\n const reviewPromise = new Promise<ReviewSubmission>((resolve, reject) => {\n const idleTimer = setTimeout(\n () => reject(new Error('Browser review timed out after 30 minutes of inactivity')),\n IDLE_TIMEOUT_MS,\n );\n let heartbeatTimer: NodeJS.Timeout | null = null;\n const armHeartbeat = (): void => {\n if (heartbeatTimer) clearTimeout(heartbeatTimer);\n heartbeatTimer = setTimeout(() => {\n clearTimeout(idleTimer);\n reject(new Error('Review cancelled: browser closed (heartbeat lost)'));\n }, HEARTBEAT_TIMEOUT_MS);\n };\n const clearAll = (): void => {\n clearTimeout(idleTimer);\n if (heartbeatTimer) clearTimeout(heartbeatTimer);\n heartbeatTimer = null;\n };\n transport.onHeartbeat(armHeartbeat);\n transport.onPause(() => {\n if (heartbeatTimer) clearTimeout(heartbeatTimer);\n heartbeatTimer = null;\n });\n transport.onCancel(() => {\n clearAll();\n reject(new Error('Review cancelled: browser closed'));\n });\n transport.onReviewSubmit((submission) => {\n clearAll();\n resolve(submission);\n });\n });\n\n const { url } = await transport.start(0);\n process.stderr.write(`Review server running at ${url}\\n`);\n\n try {\n const openCmd = process.platform === 'darwin' ? 'open'\n : process.platform === 'win32' ? 'start'\n : 'xdg-open';\n spawnSync(openCmd, [url], { stdio: 'ignore' });\n } catch {\n process.stderr.write(`Open ${url} in your browser\\n`);\n }\n\n try {\n return await reviewPromise;\n } finally {\n await transport.stop();\n }\n}\n", "import { createServer, type Server } from 'node:http';\nimport { createRouteHandler, type RouteContext } from './routes.js';\n\nexport function createReviewServer(ctx: RouteContext): Server {\n return createServer(createRouteHandler(ctx));\n}\n\nexport function startServer(server: Server, port: number): Promise<{ url: string }> {\n return new Promise((resolve, reject) => {\n server.on('error', reject);\n server.listen(port, () => {\n const addr = server.address();\n const actualPort = typeof addr === 'object' && addr ? addr.port : port;\n resolve({ url: `http://localhost:${actualPort}` });\n });\n });\n}\n\nexport function stopServer(server: Server): Promise<void> {\n return new Promise((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n // Force-close keep-alive sockets so close() doesn't hang on an idle browser tab.\n server.closeAllConnections();\n });\n}\n", "import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { readFile } from 'node:fs';\nimport { join, normalize, resolve as resolvePath, sep, extname } from 'node:path';\nimport type { PlanDocument, ReviewComment, ReviewSubmission } from '@plan-review/core';\n\nconst MAX_BODY_SIZE = 1024 * 1024; // 1MB\n\nconst MIME_BY_EXT: Record<string, string> = {\n '.gif': 'image/gif',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.svg': 'image/svg+xml',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.ico': 'image/x-icon',\n};\n\nexport interface RouteContext {\n getDocument: () => PlanDocument;\n getInitialActiveSection?: () => string | null;\n getAssetBaseDir?: () => string | null;\n onSubmit: (submission: ReviewSubmission) => void;\n getAssetHtml: () => string;\n onSessionSave?: (comments: ReviewComment[], activeSection: string | null) => void;\n onHeartbeat?: () => void;\n onPause?: () => void;\n onCancel?: () => void;\n}\n\nfunction validateComment(obj: unknown): obj is ReviewComment {\n if (typeof obj !== 'object' || obj === null) return false;\n const c = obj as Record<string, unknown>;\n return typeof c.sectionId === 'string' && typeof c.text === 'string';\n}\n\nfunction validateVerdict(value: unknown): value is ReviewSubmission['verdict'] {\n return value === 'approved' || value === null;\n}\n\nexport function createRouteHandler(ctx: RouteContext): (req: IncomingMessage, res: ServerResponse) => void {\n return (req, res) => {\n const { method, url } = req;\n\n if (method === 'GET' && url === '/') {\n const html = ctx.getAssetHtml();\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(html);\n return;\n }\n\n if (method === 'GET' && url === '/api/doc') {\n const doc = ctx.getDocument();\n const initialState = { activeSection: ctx.getInitialActiveSection?.() ?? null };\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ document: doc, initialState }));\n return;\n }\n\n if (method === 'POST' && url === '/api/review') {\n let body = '';\n let size = 0;\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_SIZE) {\n res.writeHead(413, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Request body too large' }));\n req.destroy();\n return;\n }\n body += chunk.toString();\n });\n req.on('end', () => {\n if (size > MAX_BODY_SIZE) return;\n try {\n const parsed = JSON.parse(body);\n const comments = parsed.comments;\n const verdict = parsed.verdict;\n const summary = parsed.summary;\n if (!Array.isArray(comments)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'comments must be an array' }));\n return;\n }\n if (!validateVerdict(verdict)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'verdict must be \"approved\" or null' }));\n return;\n }\n if (typeof summary !== 'string') {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'summary must be a string' }));\n return;\n }\n for (const c of comments) {\n if (!validateComment(c)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Each comment must have sectionId (string) and text (string)' }));\n return;\n }\n }\n ctx.onSubmit({ comments: comments as ReviewComment[], verdict, summary });\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ success: true }));\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n }\n });\n return;\n }\n\n if (method === 'PUT' && url === '/api/session') {\n let body = '';\n let size = 0;\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_SIZE) {\n res.writeHead(413, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Request body too large' }));\n req.destroy();\n return;\n }\n body += chunk.toString();\n });\n req.on('end', () => {\n if (size > MAX_BODY_SIZE) return;\n try {\n const parsed = JSON.parse(body);\n const comments = parsed.comments;\n if (!Array.isArray(comments)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'comments must be an array' }));\n return;\n }\n for (const c of comments) {\n if (!validateComment(c)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Each comment must have sectionId (string) and text (string)' }));\n return;\n }\n }\n const activeSection = typeof parsed.activeSection === 'string' ? parsed.activeSection : null;\n ctx.onSessionSave?.(comments as ReviewComment[], activeSection);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ success: true }));\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n }\n });\n return;\n }\n\n if (method === 'POST' && url === '/api/heartbeat') {\n ctx.onHeartbeat?.();\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (method === 'POST' && url === '/api/pause') {\n ctx.onPause?.();\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (method === 'POST' && url === '/api/cancel') {\n ctx.onCancel?.();\n res.writeHead(204);\n res.end();\n return;\n }\n\n // Static asset proxy: /_assets/<rel-path> serves files from the plan\n // file's directory. Only image extensions are allowed; path traversal\n // (e.g. ../etc/passwd) is rejected. Inline plans set baseDir to null and\n // get a 404 \u2014 there is no on-disk anchor to resolve against.\n if (method === 'GET' && url && url.startsWith('/_assets/')) {\n const baseDir = ctx.getAssetBaseDir?.();\n if (!baseDir) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('No asset base directory');\n return;\n }\n let rel: string;\n try {\n rel = decodeURIComponent(url.slice('/_assets/'.length).split('?')[0].split('#')[0]);\n } catch {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Bad request');\n return;\n }\n const ext = extname(rel).toLowerCase();\n if (!MIME_BY_EXT[ext]) {\n res.writeHead(415, { 'Content-Type': 'text/plain' });\n res.end('Unsupported media type');\n return;\n }\n const normalized = normalize(rel);\n const resolvedBase = resolvePath(baseDir);\n const resolvedFile = resolvePath(join(resolvedBase, normalized));\n if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {\n res.writeHead(403, { 'Content-Type': 'text/plain' });\n res.end('Forbidden');\n return;\n }\n readFile(resolvedFile, (err, buf) => {\n if (err) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n return;\n }\n res.writeHead(200, {\n 'Content-Type': MIME_BY_EXT[ext],\n 'Content-Length': buf.length,\n 'Cache-Control': 'no-store',\n });\n res.end(buf);\n });\n return;\n }\n\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n };\n}\n", "import { readFileSync, existsSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// When running from source (vitest), look for pre-built dist/browser/index.html\n// When running from dist, the sibling ../browser/ path is used\nfunction resolveHtmlPath(): string {\n const siblingPath = join(__dirname, '..', 'browser', 'index.html');\n if (existsSync(siblingPath)) return siblingPath;\n\n // Fallback: walk up to project root and look in dist/browser/\n const projectRoot = join(__dirname, '..', '..');\n const distPath = join(projectRoot, 'dist', 'browser', 'index.html');\n if (existsSync(distPath)) return distPath;\n\n throw new Error(`Browser HTML not found. Run 'npm run build' first.\\nLooked in:\\n ${siblingPath}\\n ${distPath}`);\n}\n\nlet cached: string | null = null;\n\nexport function getAssetHtml(): string {\n if (!cached) {\n cached = readFileSync(resolveHtmlPath(), 'utf-8');\n }\n return cached;\n}\n", "import type { Server } from 'node:http';\nimport type { PlanDocument, ReviewComment, ReviewSubmission } from '@plan-review/core';\nimport { createReviewServer, startServer, stopServer } from './server/server.js';\nimport { getAssetHtml } from './server/assets.js';\n\nexport type { ReviewSubmission, ReviewVerdict } from '@plan-review/core';\n\nexport interface Transport {\n sendDocument(doc: PlanDocument): void;\n onReviewSubmit(handler: (submission: ReviewSubmission) => void): void;\n start(port: number): Promise<{ url: string }>;\n stop(): Promise<void>;\n}\n\nexport class HttpTransport implements Transport {\n private doc: PlanDocument | null = null;\n private initialActiveSection: string | null = null;\n private assetBaseDir: string | null = null;\n private submitHandler: ((submission: ReviewSubmission) => void) | null = null;\n private sessionSaveHandler: ((comments: ReviewComment[], activeSection: string | null) => void) | null = null;\n private heartbeatHandler: (() => void) | null = null;\n private pauseHandler: (() => void) | null = null;\n private cancelHandler: (() => void) | null = null;\n private server: Server | null = null;\n\n sendDocument(doc: PlanDocument): void {\n this.doc = doc;\n }\n\n setInitialActiveSection(section: string | null): void {\n this.initialActiveSection = section;\n }\n\n // Plan-file directory used to serve relative images via /_assets/<rel>.\n // Null for inline plans where there's no on-disk anchor.\n setAssetBaseDir(dir: string | null): void {\n this.assetBaseDir = dir;\n }\n\n onReviewSubmit(handler: (submission: ReviewSubmission) => void): void {\n this.submitHandler = handler;\n }\n\n onSessionSave(handler: (comments: ReviewComment[], activeSection: string | null) => void): void {\n this.sessionSaveHandler = handler;\n }\n\n onHeartbeat(handler: () => void): void {\n this.heartbeatHandler = handler;\n }\n\n onPause(handler: () => void): void {\n this.pauseHandler = handler;\n }\n\n onCancel(handler: () => void): void {\n this.cancelHandler = handler;\n }\n\n async start(port: number): Promise<{ url: string }> {\n if (!this.doc) throw new Error('No document set');\n\n this.server = createReviewServer({\n getDocument: () => this.doc!,\n getInitialActiveSection: () => this.initialActiveSection,\n getAssetBaseDir: () => this.assetBaseDir,\n onSubmit: (submission) => this.submitHandler?.(submission),\n getAssetHtml: () => getAssetHtml(),\n onSessionSave: (comments, activeSection) => this.sessionSaveHandler?.(comments, activeSection),\n onHeartbeat: () => this.heartbeatHandler?.(),\n onPause: () => this.pauseHandler?.(),\n onCancel: () => this.cancelHandler?.(),\n });\n\n return startServer(this.server, port);\n }\n\n async stop(): Promise<void> {\n if (this.server && this.server.listening) {\n await stopServer(this.server);\n this.server = null;\n }\n }\n}\n"],
5
- "mappings": ";;AAEA,OAAS,WAAAA,OAAe,YACxB,OAAS,gBAAAC,GAAc,aAAAC,GAAW,gBAAAC,OAAoB,UACtD,OAAS,cAAAC,OAAkB,UAC3B,OAAS,WAAAC,OAAe,UACxB,OAAS,WAAAC,GAAS,QAAAC,EAAM,WAAWC,OAAmB,YACtD,OAAS,iBAAAC,OAAqB,WAC9B,OAAOC,MAAW,QCJZ,SAAUC,EAAMC,EAAeC,EAA0B,OAAM,CACnE,IAAMC,EAAQF,EAAM,MAAM;CAAI,EACxBG,EAAQC,GAAaF,CAAK,EAC1BG,EAAWC,GAAgBJ,CAAK,EAEtC,OAAID,IAAa,OACXM,GAAeP,CAAK,EACfQ,GAAUR,EAAOG,EAAOE,CAAQ,EAElCI,EAAaT,EAAOG,EAAOE,CAAQ,EAGxCJ,IAAa,YACRS,EAAiBV,EAAOG,EAAOE,CAAQ,EAGzCI,EAAaT,EAAOG,EAAOE,CAAQ,CAC5C,CAEA,SAASD,GAAaF,EAAe,CACnC,IAAMS,EAAKT,EAAM,KAAMU,GAAM,MAAM,KAAKA,CAAC,CAAC,EAC1C,OAAOD,EAAKA,EAAG,QAAQ,MAAO,EAAE,EAAE,KAAI,EAAK,UAC7C,CAEA,SAASL,GAAgBJ,EAAe,CACtC,IAAMW,EAA+B,CAAA,EACrC,QAAWC,KAAQZ,EAAM,MAAM,EAAG,EAAE,EAAG,CACrC,IAAMa,EAAQD,EAAK,MAAM,+BAA+B,EACpDC,IACFF,EAAKE,EAAM,CAAC,EAAE,KAAI,CAAE,EAAIA,EAAM,CAAC,EAAE,KAAI,EAEzC,CACA,OAAOF,CACT,CAEM,SAAUN,GAAeP,EAAa,CAC1C,IAAMgB,EAAWhB,EAAM,QAAQ,kBAAmB,EAAE,EAC9CiB,EACJ,QAAQ,KAAKD,CAAQ,GAAK,SAAS,KAAKA,CAAQ,EAC5CE,EACJ,uBAAuB,KAAKF,CAAQ,GACpC,mBAAmB,KAAKA,CAAQ,GAChC,yBAAyB,KAAKA,CAAQ,GACtC,0BAA0B,KAAKA,CAAQ,EAEzC,OAAOC,GAAoBC,CAC7B,CAEA,SAAST,EACPT,EACAG,EACAE,EAAgC,CAEhC,IAAMc,EAAWC,GAAgBpB,CAAK,EAEtC,OAAImB,EAAS,SAAW,EACfT,EAAiBV,EAAOG,EAAOE,CAAQ,EAGzC,CACL,MAAAF,EACA,SAAAE,EACA,KAAM,UACN,SAAUc,EAAS,IAAI,CAACE,EAAGC,KAAO,CAChC,GAAI,WAAWA,EAAI,CAAC,GACpB,QAASD,EAAE,QACX,MAAOA,EAAE,MACT,KAAMA,EAAE,MACR,EACF,SAAU,CAAA,EAEd,CAQA,SAASD,GAAgBpB,EAAa,CACpC,IAAME,EAAQF,EAAM,MAAM;CAAI,EACxBmB,EAAyB,CAAA,EAC3BI,EAAiB,GACjBC,EAAe,EACfC,EAAwB,CAAA,EAGtBC,GAAW1B,EAAM,MAAM,QAAQ,GAAK,CAAA,GAAI,OACxC2B,GAAW3B,EAAM,MAAM,SAAS,GAAK,CAAA,GAAI,OACzC4B,EAAaF,EAAU,EAAI,EAAIC,EAAU,EAAI,EAAI,EAEvD,GAAIC,IAAe,EAAG,MAAO,CAAA,EAE7B,IAAMC,EAAe,IAAI,OAAO,IAAI,IAAI,OAAOD,CAAU,CAAC,OAAO,EAC7DE,EAAc,GAElB,QAAWhB,KAAQZ,EAAO,CACxB,GAAIY,EAAK,WAAW,KAAK,EAAG,CAC1BgB,EAAc,CAACA,EACXP,GACFE,EAAY,KAAKX,CAAI,EAEvB,QACF,CAEA,GAAIgB,EAAa,CACXP,GACFE,EAAY,KAAKX,CAAI,EAEvB,QACF,CAEA,IAAMC,EAAQD,EAAK,MAAMe,CAAY,EACjCd,GACEQ,GACFJ,EAAS,KAAK,CACZ,QAASI,EACT,MAAOC,EACP,KAAMC,EAAY,KAAK;CAAI,EAAE,KAAI,EAClC,EAEHF,EAAiBR,EAAM,CAAC,EAAE,KAAI,EAC9BS,EAAeI,EACfH,EAAc,CAAA,GACLF,GACTE,EAAY,KAAKX,CAAI,CAEzB,CAEA,OAAIS,GACFJ,EAAS,KAAK,CACZ,QAASI,EACT,MAAOC,EACP,KAAMC,EAAY,KAAK;CAAI,EAAE,KAAI,EAClC,EAGIN,CACT,CAEA,SAAST,EACPV,EACAG,EACAE,EAAgC,CAEhC,IAAM0B,EAAQ/B,EAAM,MAAM,SAAS,EAAE,OAAQgC,GACpCA,EAAE,KAAI,EAAG,QAAU,CAC3B,EAED,OAAID,EAAM,QAAU,EACX,CACL,MAAA5B,EACA,SAAAE,EACA,KAAM,UACN,SAAU,CACR,CACE,GAAI,YACJ,QAASF,EACT,MAAO,EACP,KAAMH,EAAM,KAAI,IAGpB,SAAU,CAAA,GAIP,CACL,MAAAG,EACA,SAAAE,EACA,KAAM,UACN,SAAU0B,EAAM,IAAI,CAACC,EAAGV,IAAK,CAE3B,IAAMW,EADQD,EAAE,KAAI,EAAG,MAAM;CAAI,EACT,CAAC,EAAE,QAAQ,SAAU,EAAE,EAAE,KAAI,EACrD,MAAO,CACL,GAAI,WAAWV,EAAI,CAAC,GACpB,QAASW,GAAa,WAAWX,EAAI,CAAC,GACtC,MAAO,EACP,KAAMU,EAAE,KAAI,EAEhB,CAAC,EACD,SAAU,CAAA,EAEd,CAEA,SAASxB,GACPR,EACAG,EACAE,EAAgC,CAEhC,IAAMH,EAAQF,EAAM,MAAM;CAAI,EACxBmB,EAAsB,CAAA,EAExBe,EAAiB,EACjBC,EAAY,EACZC,EAAqB,GACrBb,EAAiB,GACjBC,EAAe,EACfC,EAAwB,CAAA,EACxBK,EAAc,GAElB,SAASO,GAAY,CACnB,GAAI,CAACd,EAAgB,OAErB,IAAMe,EAAOb,EAAY,KAAK;CAAI,EAAE,KAAI,EAExC,GAAID,IAAiB,EAAG,CACtBU,IACAC,EAAY,EACZC,EAAqB,aAAaF,CAAc,GAChDf,EAAS,KAAK,CACZ,GAAIiB,EACJ,QAASb,EACT,MAAO,EACP,KAAAe,EACD,EACD,MACF,CAEAH,IACA,IAAMI,EAAK,GAAGL,CAAc,IAAIC,CAAS,GACzChB,EAAS,KAAK,CACZ,GAAAoB,EACA,QAAShB,EACT,MAAO,EACP,KAAAe,EACA,OAAQF,EACR,aAAcI,GAAoBF,CAAI,EACtC,aAAcG,GAAoBH,CAAI,EACtC,aAAcI,GAAoBJ,CAAI,EACvC,CACH,CAEA,QAAWxB,KAAQZ,EAAO,CACxB,GAAIY,EAAK,WAAW,KAAK,EAAG,CAC1BgB,EAAc,CAACA,EACfL,EAAY,KAAKX,CAAI,EACrB,QACF,CAEA,GAAIgB,EAAa,CACfL,EAAY,KAAKX,CAAI,EACrB,QACF,CAEA,IAAM6B,EAAU7B,EAAK,MAAM,UAAU,EAC/B8B,EAAU9B,EAAK,MAAM,WAAW,EAElC6B,GACFN,EAAY,EACZd,EAAiBoB,EAAQ,CAAC,EAAE,KAAI,EAChCnB,EAAe,EACfC,EAAc,CAAA,GACLmB,GACTP,EAAY,EACZd,EAAiBqB,EAAQ,CAAC,EAAE,KAAI,EAChCpB,EAAe,EACfC,EAAc,CAAA,GAEdA,EAAY,KAAKX,CAAI,CAEzB,CACA,OAAAuB,EAAY,EAEL,CACL,MAAAlC,EACA,SAAAE,EACA,KAAM,OACN,SAAAc,EACA,SAAU,CAAA,EAEd,CAEA,SAASqB,GAAoBF,EAAY,CACvC,IAAMO,EAAeP,EAAK,MAAM,4BAA4B,EACtDQ,EAAcR,EAAK,MAAM,wBAAwB,EAEjDS,EAAaC,GAAyB,CAC1C,IAAMC,EAAUD,EAAI,KAAI,EACxB,OAAIC,IAAY,UAAYA,IAAY,GAAW,CAAA,EAC5CA,EAAQ,MAAM,MAAM,EAAE,IAAK,GAAM,EAAE,KAAI,CAAE,CAClD,EAEA,MAAO,CACL,UAAWJ,EAAeE,EAAUF,EAAa,CAAC,CAAC,EAAI,CAAA,EACvD,OAAQC,EAAcC,EAAUD,EAAY,CAAC,CAAC,EAAI,CAAA,EAEtD,CAEA,SAASL,GAAoBH,EAAY,CACvC,IAAMY,EAAkB,CAAA,EAClBhD,EAAQoC,EAAK,MAAM;CAAI,EACzBa,EAAiB,GAErB,QAAWrC,KAAQZ,EAAO,CACxB,GAAI,yBAAyB,KAAKY,CAAI,EAAG,CACvCqC,EAAiB,GACjB,QACF,CACA,GAAIA,EAAgB,CAClB,IAAMC,EAAYtC,EAAK,MAAM,gBAAgB,EAC7C,GAAIsC,EAAW,CACb,IAAMC,EAASD,EAAU,CAAC,EAAE,KAAI,EAChCF,EAAM,KAAKG,EAAS,GAAGD,EAAU,CAAC,CAAC,IAAIC,CAAM,GAAKD,EAAU,CAAC,CAAC,CAChE,MAAWtC,EAAK,KAAI,IAAO,IAAM,QAAQ,KAAKA,EAAK,KAAI,CAAE,KACvDqC,EAAiB,GAErB,CACF,CAEA,OAAOD,CACT,CAEA,SAASR,GAAoBJ,EAAY,CACvC,IAAMvB,EAAQuB,EAAK,MAAM,iCAAiC,EAC1D,OAAOvB,EAAQA,EAAM,CAAC,EAAI,MAC5B,CC/TA,OAAS,cAAAuC,MAAkB,cAC3B,OACE,aAAAC,GACA,gBAAAC,EACA,iBAAAC,GACA,cAAAC,EACA,eAAAC,GACA,cAAAC,MACK,UACP,OAAS,QAAAC,EAAM,WAAAC,OAAe,YAC9B,OAAS,WAAAC,OAAe,UAsBxB,SAASC,GAASC,EAAgB,CAChC,IAAMC,EAAMJ,GAAQG,CAAQ,EAE5B,OADaX,EAAW,QAAQ,EAAE,OAAOY,CAAG,EAAE,OAAO,KAAK,EAC9C,MAAM,EAAG,EAAE,CACzB,CAEA,SAASC,EAAgBF,EAAgB,CACvC,OAAOJ,EAAKO,EAAa,EAAIJ,GAASC,CAAQ,EAAI,OAAO,CAC3D,CAIM,SAAUG,GAAa,CAC3B,IAAMC,EAAMR,EAAKE,GAAO,EAAI,eAAgB,UAAU,EACtD,OAAAR,GAAUc,EAAK,CAAE,UAAW,EAAI,CAAE,EAC3BA,CACT,CAEM,SAAUC,EAAmBC,EAAe,CAEhD,MAAO,UADKjB,EAAW,QAAQ,EAAE,OAAOiB,CAAO,EAAE,OAAO,KAAK,CACzC,EACtB,CAEM,SAAUC,EACdP,EACAQ,EACAC,EACAC,EAA4B,CAE5B,GAAI,CACF,IAAMC,EAAoB,CACxB,QAAS,EACT,SAAAX,EACA,YAAAQ,EACA,SAAAC,EACA,cAAAC,EACA,aAAc,IAAI,KAAI,EAAG,YAAW,GAEhCE,EAAWV,EAAgBF,CAAQ,EACzCR,GAAcoB,EAAU,KAAK,UAAUD,EAAM,KAAM,CAAC,EAAG,OAAO,CAChE,OAASE,EAAK,CACZ,QAAQ,KAAK,yCAA0CA,EAAc,OAAO,EAAE,CAChF,CACF,CAEM,SAAUC,EACdd,EACAe,EAA0B,CAE1B,IAAMH,EAAWV,EAAgBF,CAAQ,EAEzC,GAAI,CAACL,EAAWiB,CAAQ,EACtB,OAAO,KAGT,IAAII,EACJ,GAAI,CACFA,EAAMzB,EAAaqB,EAAU,OAAO,CACtC,MAAQ,CACN,OAAO,IACT,CAEA,IAAID,EACJ,GAAI,CACFA,EAAO,KAAK,MAAMK,CAAG,CACvB,MAAQ,CACN,QAAQ,KAAK,iDAAiDJ,CAAQ,EAAE,EACxE,GAAI,CACFnB,EAAWmB,CAAQ,CACrB,MAAQ,CAER,CACA,OAAO,IACT,CAQA,MAAO,CACL,SANgCD,EAAK,SAAS,IAAKM,IAAO,CAC1D,GAAGA,EACH,UAAW,IAAI,KAAKA,EAAE,SAAS,GAC/B,EAIA,cAAeN,EAAK,cACpB,MAAOA,EAAK,cAAgBI,EAEhC,CAEM,SAAUG,EAAalB,EAAgB,CAC3C,IAAMY,EAAWV,EAAgBF,CAAQ,EACzC,GAAI,CACFP,EAAWmB,CAAQ,CACrB,MAAQ,CAER,CACF,CAEM,SAAUO,GAAY,CAM1B,IAAMf,EAAMD,EAAa,EACrBiB,EACJ,GAAI,CACFA,EAAQ1B,GAAYU,CAAG,CACzB,MAAQ,CACN,MAAO,CAAA,CACT,CAEA,IAAMiB,EAKD,CAAA,EAEL,QAAWC,KAAQF,EAAO,CACxB,GAAI,CAACE,EAAK,SAAS,OAAO,EAAG,SAE7B,IAAMV,EAAWhB,EAAKQ,EAAKkB,CAAI,EAC3BX,EACJ,GAAI,CACF,IAAMK,EAAMzB,EAAaqB,EAAU,OAAO,EAC1CD,EAAO,KAAK,MAAMK,CAAG,CACvB,MAAQ,CACN,QAAQ,KAAK,gDAAgDJ,CAAQ,EAAE,EACvE,QACF,CAEA,IAAIW,EACJ,GAAI,CAAC5B,EAAWgB,EAAK,QAAQ,EAC3BY,EAAQ,SAER,IAAI,CACF,IAAMC,EAAiBjC,EAAaoB,EAAK,SAAU,OAAO,EAE1DY,EADoBlB,EAAmBmB,CAAc,IAC7Bb,EAAK,WAC/B,MAAQ,CACNY,EAAQ,IACV,CAGFF,EAAQ,KAAK,CACX,SAAUV,EAAK,SACf,aAAcA,EAAK,SAAS,OAC5B,aAAcA,EAAK,aACnB,MAAAY,EACD,CACH,CAEA,OAAOF,CACT,CCvLA,SAASI,EAAeC,EAAY,CAClC,OAAOA,EAAK,QAAQ,qBAAsB,MAAM,CAClD,CAEA,SAASC,GAAaC,EAAyB,CAC7C,MAAO,CAAC,GAAGA,CAAQ,EAAE,KAAK,CAACC,EAAGC,IAAK,CACjC,IAAMC,EAAQF,EAAE,QAAQ,WAAa,IAC/BG,EAAQF,EAAE,QAAQ,WAAa,IACrC,OAAOC,EAAQC,CACjB,CAAC,CACH,CAEA,SAASC,GAAaC,EAAsB,CAC1C,OAAOA,IAAY,WAAa,WAAa,SAC/C,CAOM,SAAUC,EAAaC,EAAmBC,EAAyB,CACvE,IAAMC,EAAsB,IAAI,IAAIF,EAAI,SAAS,IAAKG,GAAMA,EAAE,SAAS,CAAC,EAClEC,EAAqBJ,EAAI,SAAS,OAAQK,GAC9CL,EAAI,OAAS,OAASK,EAAE,QAAU,EAAIA,EAAE,OAAS,CAAC,EAE9CC,EAAoBF,EAAmB,OAAQC,GAAMH,EAAoB,IAAIG,EAAE,EAAE,CAAC,EAElFE,EAAkB,CAAA,EAExBA,EAAM,KAAK,kBAAkBP,EAAI,KAAK,EAAE,EACxCO,EAAM,KAAK,EAAE,EACbA,EAAM,KAAK,mBAAmB,EAC9BA,EAAM,KAAK,kBAAkBV,GAAaI,EAAK,OAAO,CAAC,EAAE,EACzDM,EAAM,KAAK,4BAA4BD,EAAkB,MAAM,IAAIF,EAAmB,MAAM,EAAE,EAC9FG,EAAM,KAAK,mBAAmBP,EAAI,SAAS,MAAM,EAAE,EACnD,IAAMQ,EAAeJ,EAAmB,OAASE,EAAkB,OACnEC,EAAM,KACJ,kBAAkBC,CAAY,WAAWA,IAAiB,EAAI,GAAK,GAAG,mBAAmB,EAGvFP,EAAK,QAAQ,KAAI,IAAO,KAC1BM,EAAM,KAAK,EAAE,EACbA,EAAM,KAAK,qBAAqB,EAChCA,EAAM,KAAK,EAAE,EACbA,EAAM,KAAKlB,EAAeY,EAAK,OAAO,CAAC,GAGrCK,EAAkB,OAAS,IAC7BC,EAAM,KAAK,EAAE,EACbA,EAAM,KAAK,KAAK,GAGlB,QAAWE,KAAWH,EAAmB,CACvC,IAAMI,EAAkBnB,GACtBS,EAAI,SAAS,OAAQG,GAAMA,EAAE,YAAcM,EAAQ,EAAE,CAAC,EAOxD,GAJAF,EAAM,KAAK,EAAE,EACbA,EAAM,KAAK,cAAcE,EAAQ,EAAE,KAAKA,EAAQ,OAAO,EAAE,EACzDF,EAAM,KAAK,EAAE,EAETP,EAAI,OAAS,QAAUS,EAAQ,aAAc,CAC/C,IAAME,EAAOF,EAAQ,aACjBE,EAAK,UAAU,OAAS,GAC1BJ,EAAM,KAAK,eAAeI,EAAK,UAAU,KAAK,IAAI,CAAC,EAAE,EAEnDA,EAAK,OAAO,OAAS,GACvBJ,EAAM,KAAK,WAAWI,EAAK,OAAO,KAAK,IAAI,CAAC,EAAE,EAEhDJ,EAAM,KAAK,EAAE,CACf,CAEA,QAAWK,KAAWF,EAAiB,CACrC,GAAIE,EAAQ,OAAQ,CAClBL,EAAM,KAAK,sBAAsB,EACjCA,EAAM,KAAK,EAAE,EACb,QAAWM,KAAQD,EAAQ,OAAO,UAChCL,EAAM,KAAK,KAAKM,CAAI,EAAE,EAExBN,EAAM,KAAK,EAAE,EACbA,EAAM,KAAKlB,EAAeuB,EAAQ,IAAI,CAAC,CACzC,MACEL,EAAM,KAAK,uCAAuC,EAClDA,EAAM,KAAK,EAAE,EACbA,EAAM,KAAKlB,EAAeuB,EAAQ,IAAI,CAAC,EAEzCL,EAAM,KAAK,EAAE,EACbA,EAAM,KAAK,KAAK,CAClB,CACF,CAEA,OAAOA,EAAM,KAAK;CAAI,CACxB,CC/FA,UAAYO,MAAc,gBAC1B,OAAOC,MAAW,QCDlB,OAAS,UAAAC,MAAc,SACvB,OAAS,kBAAAC,OAAsB,kBAC/B,OAAOC,MAAW,QAGlBF,EAAO,IAAIC,GAAe,CAAC,EAEpB,SAASE,EAAcC,EAA0B,CACtD,IAAMC,EAAkB,CAAC,EAErBD,EAAQ,QAAU,GAAKA,EAAQ,eACjCC,EAAM,KAAKC,GAAqBF,CAAO,CAAC,EACxCC,EAAM,KAAK,EAAE,GAGf,IAAME,EAAU,GAAG,IAAI,OAAOH,EAAQ,KAAK,CAAC,IAAIA,EAAQ,OAAO,GACzDI,EAAOJ,EAAQ,MAAQ,GACvBK,EAAW,GAAGF,CAAO;AAAA;AAAA,EAAOC,CAAI,GACtC,OAAAH,EAAM,KAAKL,EAAO,MAAMS,CAAQ,CAAW,EAEpCJ,EAAM,KAAK;AAAA,CAAI,CACxB,CAEA,SAASC,GAAqBF,EAA0B,CACtD,IAAMM,EAAON,EAAQ,aACfO,EAAYD,EAAK,UAAU,OAAS,EAAIA,EAAK,UAAU,KAAK,IAAI,EAAI,SACpEE,EAASF,EAAK,OAAO,OAAS,EAAIA,EAAK,OAAO,KAAK,IAAI,EAAI,SAE3DG,EAAkB,CACtB,QAAQT,EAAQ,EAAE,KAAKA,EAAQ,OAAO,GACtC,sBAAiBO,CAAS,GAC1B,kBAAaC,CAAM,EACrB,EAEA,GAAIR,EAAQ,cAAgBA,EAAQ,aAAa,OAAS,EAAG,CAC3D,IAAMU,EACJV,EAAQ,aAAa,QAAU,EAC3BA,EAAQ,aAAa,KAAK,IAAI,EAC9B,GAAGA,EAAQ,aAAa,CAAC,CAAC,MAAMA,EAAQ,aAAa,OAAS,CAAC,SACrES,EAAM,KAAK,UAAUC,CAAQ,EAAE,CACjC,CAEIV,EAAQ,cACVS,EAAM,KAAK,WAAWT,EAAQ,YAAY,EAAE,EAG9C,IAAMW,EAAS,KAAK,IAAI,GAAGF,EAAM,IAAKG,GAAMA,EAAE,MAAM,CAAC,EAE/CC,EADQ,KAAK,IAAIF,EAAS,EAAG,QAAQ,OAAO,SAAW,EAAE,EACpC,EAErBG,EAAMhB,EAAM,IAAI,SAAI,SAAI,OAAOe,CAAU,CAAC,QAAG,EAC7CE,EAASjB,EAAM,IAAI,SAAI,SAAI,OAAOe,CAAU,CAAC,QAAG,EAChDG,EAAUP,EAAM,IACnBG,GAAMd,EAAM,IAAI,QAAG,EAAI,IAAMA,EAAM,KAAKc,EAAE,MAAM,EAAGC,EAAa,CAAC,EAAE,OAAOA,EAAa,CAAC,CAAC,EAAI,IAAMf,EAAM,IAAI,QAAG,CACnH,EAEA,MAAO,CAACgB,EAAK,GAAGE,EAASD,CAAM,EAAE,KAAK;AAAA,CAAI,CAC5C,CAEO,SAASE,EAAUC,EAA2B,CACnD,IAAMjB,EAAkB,CAAC,EACnBkB,EAAe,IAAI,IAAID,EAAI,SAAS,IAAKE,GAAMA,EAAE,SAAS,CAAC,EAMjE,GAJAnB,EAAM,KAAK,EAAE,EACbA,EAAM,KAAKH,EAAM,KAAK,UAAUoB,EAAI,KAAK,CAAC,EAC1CjB,EAAM,KAAK,EAAE,EAETiB,EAAI,OAAS,QACf,QAAWlB,KAAWkB,EAAI,SACxB,GAAIlB,EAAQ,QAAU,EACpBC,EAAM,KAAKH,EAAM,KAAK,OAAO,KAAKE,EAAQ,OAAO,EAAE,CAAC,UAC3CA,EAAQ,QAAU,EAAG,CAC9B,IAAMqB,EAASF,EAAa,IAAInB,EAAQ,EAAE,EAAIF,EAAM,MAAM,QAAG,EAAI,IACjEG,EAAM,KAAK,OAAOoB,CAAM,IAAIvB,EAAM,IAAIE,EAAQ,EAAE,CAAC,KAAKA,EAAQ,OAAO,EAAE,CACzE,MAEG,CACL,IAAMsB,EAAaJ,EAAI,SAAS,OAAQK,GAAMA,EAAE,OAAS,CAAC,EAC1D,QAASC,EAAI,EAAGA,EAAIF,EAAW,OAAQE,IAAK,CAC1C,IAAMxB,EAAUsB,EAAWE,CAAC,EACtBC,EAAM,OAAOD,EAAI,CAAC,EAAE,SAAS,CAAC,EAC9BH,EAASF,EAAa,IAAInB,EAAQ,EAAE,EAAIF,EAAM,MAAM,QAAG,EAAI,IACjEG,EAAM,KAAK,KAAKoB,CAAM,IAAIvB,EAAM,IAAI2B,CAAG,CAAC,KAAKzB,EAAQ,OAAO,EAAE,CAChE,CACF,CAEA,IAAM0B,EAAiBP,EAAa,KAI9BQ,EAHaT,EAAI,SAAS,OAAQ,GACtCA,EAAI,OAAS,OAAS,EAAE,QAAU,EAAI,EAAE,OAAS,CACnD,EAC6B,OAASQ,EAEtC,OAAAzB,EAAM,KAAK,EAAE,EACbA,EAAM,KACJ,KAAKH,EAAM,MAAM,GAAG4B,CAAc,WAAWA,IAAmB,EAAI,IAAM,EAAE,YAAY,CAAC,KAClF5B,EAAM,IAAI,GAAG6B,CAAS,YAAY,CAAC,EAC5C,EACA1B,EAAM,KAAK,EAAE,EAENA,EAAM,KAAK;AAAA,CAAI,CACxB,CD/FA,eAAsB2B,EAASC,EAAmBC,EAA0B,GAAOC,EAAqD,CAGtI,IAAMC,EAAWF,GACZ,KAAM,QAAO,SAAS,GAAG,iBAAiB,UAAU,EACrD,QAAQ,MAENG,EAAc,kBAAgB,CAClC,MAAOD,EACP,OAAQ,QAAQ,MAClB,CAAC,EAEKE,EAAOC,GACX,IAAI,QAASC,GAAY,CACvBH,EAAG,SAASE,EAASE,GAAWD,EAAQC,EAAO,KAAK,CAAC,CAAC,CACxD,CAAC,EAEGC,EAAqBC,EAAsBV,CAAG,EAEhDW,EAAU,GAEd,KAAOA,GAAS,CACd,QAAQ,MAAMC,EAAUZ,CAAG,CAAC,EAC5B,IAAMa,EAAQ,MAAMR,EAClBS,EAAM,KAAK,4EAAgF,CAC7F,EAEA,GAAID,IAAU,QAAUA,IAAU,IAChCF,EAAU,WACDE,IAAU,MACnB,MAAME,EAAaf,EAAKS,EAAoBJ,EAAKH,CAAe,MAC3D,CACL,IAAMc,EAAUC,GAAYjB,EAAKa,CAAK,EACtC,GAAIG,EAAS,CACX,IAAME,EAAWT,EAAmB,QAAQO,CAAO,EACnD,MAAMD,EAAaf,EAAKS,EAAmB,MAAMS,CAAQ,EAAGb,EAAKH,CAAe,CAClF,MACE,QAAQ,MAAMY,EAAM,IAAI,YAAYD,CAAK,yBAAyB,CAAC,CAEvE,CACF,CAEA,OAAAT,EAAG,MAAM,EACTe,GAAanB,CAAG,EACTA,CACT,CAEA,eAAee,EACbf,EACAoB,EACAf,EACAH,EACe,CACf,QAASmB,EAAI,EAAGA,EAAID,EAAS,OAAQC,IAAK,CACxC,IAAML,EAAUI,EAASC,CAAC,EAC1B,QAAQ,MAAMC,EAAcN,CAAO,CAAC,EAEpC,IAAMH,EAAQ,MAAMR,EAClBS,EAAM,KAAK,kEAAsE,CACnF,EAEA,GAAID,IAAU,MACZ,OACK,GAAIA,IAAU,OAAQ,CAC3BQ,GAAMA,EAAI,EAAK,EAAI,EACnB,QACF,MAAWR,IAAU,KACnBb,EAAI,SAAS,KAAK,CAChB,UAAWgB,EAAQ,GACnB,KAAMH,EACN,UAAW,IAAI,IACjB,CAAC,EACDX,IAAkB,EAEtB,CACF,CAEO,SAASe,GAAYjB,EAAmBa,EAAoC,CAEjF,IAAMU,EAAOvB,EAAI,SAAS,KAAMwB,GAAMA,EAAE,KAAOX,CAAK,EACpD,GAAIU,EAAM,OAAOA,EAGjB,IAAME,EAAM,SAASZ,EAAO,EAAE,EAC9B,GAAI,CAAC,MAAMY,CAAG,EAAG,CACf,IAAMC,EAAahB,EAAsBV,CAAG,EAC5C,GAAIyB,GAAO,GAAKA,GAAOC,EAAW,OAChC,OAAOA,EAAWD,EAAM,CAAC,CAE7B,CAGF,CAEO,SAASf,EAAsBV,EAA8B,CAClE,OAAOA,EAAI,SAAS,OAAQwB,GAC1BxB,EAAI,OAAS,OAASwB,EAAE,QAAU,EAAIA,EAAE,OAAS,CACnD,CACF,CAEO,SAASL,GAAanB,EAAyB,CACpD,IAAM0B,EAAahB,EAAsBV,CAAG,EACtC2B,EAAe,IAAI,IAAI3B,EAAI,SAAS,IAAK4B,GAAMA,EAAE,SAAS,CAAC,EAEjE,QAAQ,MAAM,EAAE,EAChB,QAAQ,MAAMd,EAAM,KAAK,gBAAgB,CAAC,EAC1C,QAAQ,MAAM,eAAeY,EAAW,MAAM,EAAE,EAChD,QAAQ,MAAM,gBAAgBZ,EAAM,MAAM,OAAOa,EAAa,IAAI,CAAC,CAAC,EAAE,EACtE,QAAQ,MAAM,cAAcb,EAAM,IAAI,OAAOY,EAAW,OAASC,EAAa,IAAI,CAAC,CAAC,EAAE,EACtF,QAAQ,MAAM,qBAAqB3B,EAAI,SAAS,MAAM,EAAE,EACxD,QAAQ,MAAM,EAAE,CAClB,CEpHA,OAAS,YAAA6B,EAAU,SAAAC,OAAa,qBAChC,OAAS,iBAAAC,OAAqB,UAC9B,OAAS,WAAAC,MAAe,YACxB,OAAOC,MAAW,QAGX,SAASC,GACdC,EACAC,EACAC,EAAuD,CAAC,EAClD,CACN,OAAQD,EAAQ,CACd,IAAK,SACH,QAAQ,OAAO,MAAMD,EAAU;AAAA,CAAI,EACnC,MACF,IAAK,YACHG,GAAiBH,CAAO,EACxB,MACF,IAAK,OACHI,GAAYJ,EAASE,EAAQ,WAAYA,EAAQ,SAAS,EAC1D,MACF,IAAK,SACHG,GAAaL,CAAO,EACpB,KACJ,CACF,CAEA,SAASG,GAAiBH,EAAuB,CAC/C,IAAMM,EAAMC,GAAoB,QAAQ,QAAQ,EAChD,GAAI,CAACD,EAAK,CACR,QAAQ,MAAMR,EAAM,OAAO,mEAAmE,CAAC,EAC/F,QAAQ,OAAO,MAAME,EAAU;AAAA,CAAI,EACnC,MACF,CAEA,GAAI,CACFN,EAASY,EAAK,CAAE,MAAON,EAAS,MAAO,CAAC,OAAQ,SAAU,QAAQ,CAAE,CAAC,EACrE,QAAQ,MAAMF,EAAM,MAAM,6BAA6B,CAAC,CAC1D,MAAQ,CACN,QAAQ,MAAMA,EAAM,OAAO,sDAAsD,CAAC,EAClF,QAAQ,OAAO,MAAME,EAAU;AAAA,CAAI,CACrC,CACF,CAEA,SAASI,GAAYJ,EAAiBQ,EAAqBC,EAA0B,CACnF,IAAMC,EAAWF,EACbX,EAAQW,CAAU,EAClBC,EACEZ,EAAQY,EAAU,QAAQ,QAAS,YAAY,CAAC,EAChDZ,EAAQ,WAAW,EAEzB,GAAI,CACFD,GAAcc,EAAUV,EAAS,OAAO,EACxC,QAAQ,MAAMF,EAAM,MAAM,qBAAqBY,CAAQ,EAAE,CAAC,CAC5D,OAASC,EAAK,CACZ,IAAMC,EAAMD,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,EAC3D,QAAQ,MAAMb,EAAM,IAAI,yBAAyBc,CAAG,EAAE,CAAC,EACvD,QAAQ,MAAMd,EAAM,OAAO,yBAAyB,CAAC,EACrD,QAAQ,OAAO,MAAME,EAAU;AAAA,CAAI,CACrC,CACF,CAEA,SAASK,GAAaL,EAAuB,CAC3C,GAAI,CAACa,EAAkB,EAAG,CACxB,QAAQ,MACNf,EAAM,IAAI,+BAA+B,CAC3C,EACA,QAAQ,MAAMA,EAAM,IAAI,yDAAyD,CAAC,EAClF,QAAQ,MAAMA,EAAM,OAAO,yBAAyB,CAAC,EACrD,QAAQ,OAAO,MAAME,EAAU;AAAA,CAAI,EACnC,MACF,CAEA,IAAMc,EAAQnB,GAAM,SAAU,CAAC,EAAG,CAChC,MAAO,CAAC,OAAQ,UAAW,SAAS,CACtC,CAAC,EACDmB,EAAM,MAAM,MAAMd,CAAO,EACzBc,EAAM,MAAM,IAAI,EAChBA,EAAM,GAAG,QAAUH,GAAQ,CACzB,QAAQ,MAAMb,EAAM,OAAO,6BAA6Ba,EAAI,OAAO,2BAA2B,CAAC,EAC/F,QAAQ,OAAO,MAAMX,EAAU;AAAA,CAAI,CACrC,CAAC,CACH,CAEO,SAASO,GAAoBQ,EAAiC,CACnE,OAAQA,EAAU,CAChB,IAAK,SACH,MAAO,SACT,IAAK,QACH,MAAO,6BACT,IAAK,QACH,MAAO,OACT,QACE,OAAO,IACX,CACF,CAEO,SAASF,GAA6B,CAC3C,GAAI,CACF,OAAAnB,EAAS,eAAgB,CAAE,MAAO,QAAS,CAAC,EACrC,EACT,MAAQ,CACN,MAAO,EACT,CACF,CN3FA,OAAS,iBAAAsB,OAAqB,cOb9B,UAAYC,MAAc,gBAC1B,OAAOC,OAAW,QAKlB,eAAeC,GAAeC,EAAyD,CACrF,OAAOA,GACF,KAAM,QAAO,SAAS,GAAG,iBAAiB,UAAU,EACrD,QAAQ,KACd,CAEA,eAAsBC,GAAmBD,EAAgD,CACvF,IAAME,EAAc,kBAAgB,CAClC,MAAO,MAAMH,GAAeC,CAAc,EAC1C,OAAQ,QAAQ,MAClB,CAAC,EAEKG,EAAS,MAAM,IAAI,QAAiBC,GAAY,CACpDF,EAAG,SACDJ,GAAM,KAAK,qDAAqD,EAC/DO,GAAMD,EAAQC,EAAE,KAAK,EAAE,YAAY,CAAC,CACvC,CACF,CAAC,EAGD,OAFAH,EAAG,MAAM,EAEDC,EAAQ,CACd,IAAK,IAAK,IAAK,SAAU,MAAO,SAChC,IAAK,IAAK,IAAK,YAAa,MAAO,YACnC,IAAK,IAAK,IAAK,OAAQ,MAAO,OAC9B,IAAK,IAAK,IAAK,SAAU,MAAO,SAChC,QAAS,MAAO,QAClB,CACF,CAEA,eAAsBG,GAAYC,EAAiBP,EAA2C,CAC5F,IAAME,EAAc,kBAAgB,CAClC,MAAO,MAAMH,GAAeC,CAAc,EAC1C,OAAQ,QAAQ,MAClB,CAAC,EAEKG,EAAS,MAAM,IAAI,QAAiBC,GAAY,CACpDF,EAAG,SAASJ,GAAM,OAAO,GAAGS,CAAO,SAAS,EAAIF,GAAMD,EAAQC,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CACvF,CAAC,EACD,OAAAH,EAAG,MAAM,EAEFC,IAAW,KAAOA,IAAW,KACtC,CC/CA,OAAS,aAAAK,OAAiB,qBAC1B,OAAS,WAAWC,OAAmB,YCDvC,OAAS,gBAAAC,OAAiC,YCC1C,OAAS,YAAAC,OAAgB,UACzB,OAAS,QAAAC,GAAM,aAAAC,GAAW,WAAWC,GAAa,OAAAC,GAAK,WAAAC,OAAe,YAGtE,IAAMC,EAAgB,KAAO,KAEvBC,GAAsC,CAC1C,OAAQ,YACR,OAAQ,YACR,OAAQ,aACR,QAAS,aACT,OAAQ,gBACR,QAAS,aACT,QAAS,aACT,OAAQ,cACV,EAcA,SAASC,GAAgBC,EAAoC,CAC3D,GAAI,OAAOA,GAAQ,UAAYA,IAAQ,KAAM,MAAO,GACpD,IAAMC,EAAID,EACV,OAAO,OAAOC,EAAE,WAAc,UAAY,OAAOA,EAAE,MAAS,QAC9D,CAEA,SAASC,GAAgBC,EAAsD,CAC7E,OAAOA,IAAU,YAAcA,IAAU,IAC3C,CAEO,SAASC,GAAmBC,EAAwE,CACzG,MAAO,CAACC,EAAKC,IAAQ,CACnB,GAAM,CAAE,OAAAC,EAAQ,IAAAC,CAAI,EAAIH,EAExB,GAAIE,IAAW,OAASC,IAAQ,IAAK,CACnC,IAAMC,EAAOL,EAAI,aAAa,EAC9BE,EAAI,UAAU,IAAK,CAAE,eAAgB,0BAA2B,CAAC,EACjEA,EAAI,IAAIG,CAAI,EACZ,MACF,CAEA,GAAIF,IAAW,OAASC,IAAQ,WAAY,CAC1C,IAAME,EAAMN,EAAI,YAAY,EACtBO,EAAe,CAAE,cAAeP,EAAI,0BAA0B,GAAK,IAAK,EAC9EE,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,SAAUI,EAAK,aAAAC,CAAa,CAAC,CAAC,EACvD,MACF,CAEA,GAAIJ,IAAW,QAAUC,IAAQ,cAAe,CAC9C,IAAII,EAAO,GACPC,EAAO,EACXR,EAAI,GAAG,OAASS,GAAkB,CAEhC,GADAD,GAAQC,EAAM,OACVD,EAAOjB,EAAe,CACxBU,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,wBAAyB,CAAC,CAAC,EAC3DD,EAAI,QAAQ,EACZ,MACF,CACAO,GAAQE,EAAM,SAAS,CACzB,CAAC,EACDT,EAAI,GAAG,MAAO,IAAM,CAClB,GAAI,EAAAQ,EAAOjB,GACX,GAAI,CACF,IAAMmB,EAAS,KAAK,MAAMH,CAAI,EACxBI,EAAWD,EAAO,SAClBE,EAAUF,EAAO,QACjBG,EAAUH,EAAO,QACvB,GAAI,CAAC,MAAM,QAAQC,CAAQ,EAAG,CAC5BV,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,2BAA4B,CAAC,CAAC,EAC9D,MACF,CACA,GAAI,CAACL,GAAgBgB,CAAO,EAAG,CAC7BX,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,oCAAqC,CAAC,CAAC,EACvE,MACF,CACA,GAAI,OAAOY,GAAY,SAAU,CAC/BZ,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,0BAA2B,CAAC,CAAC,EAC7D,MACF,CACA,QAAWN,KAAKgB,EACd,GAAI,CAAClB,GAAgBE,CAAC,EAAG,CACvBM,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,6DAA8D,CAAC,CAAC,EAChG,MACF,CAEFF,EAAI,SAAS,CAAE,SAAUY,EAA6B,QAAAC,EAAS,QAAAC,CAAQ,CAAC,EACxEZ,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,QAAS,EAAK,CAAC,CAAC,CAC3C,MAAQ,CACNA,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,cAAe,CAAC,CAAC,CACnD,CACF,CAAC,EACD,MACF,CAEA,GAAIC,IAAW,OAASC,IAAQ,eAAgB,CAC9C,IAAII,EAAO,GACPC,EAAO,EACXR,EAAI,GAAG,OAASS,GAAkB,CAEhC,GADAD,GAAQC,EAAM,OACVD,EAAOjB,EAAe,CACxBU,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,wBAAyB,CAAC,CAAC,EAC3DD,EAAI,QAAQ,EACZ,MACF,CACAO,GAAQE,EAAM,SAAS,CACzB,CAAC,EACDT,EAAI,GAAG,MAAO,IAAM,CAClB,GAAI,EAAAQ,EAAOjB,GACX,GAAI,CACF,IAAMmB,EAAS,KAAK,MAAMH,CAAI,EACxBI,EAAWD,EAAO,SACxB,GAAI,CAAC,MAAM,QAAQC,CAAQ,EAAG,CAC5BV,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,2BAA4B,CAAC,CAAC,EAC9D,MACF,CACA,QAAWN,KAAKgB,EACd,GAAI,CAAClB,GAAgBE,CAAC,EAAG,CACvBM,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,6DAA8D,CAAC,CAAC,EAChG,MACF,CAEF,IAAMa,EAAgB,OAAOJ,EAAO,eAAkB,SAAWA,EAAO,cAAgB,KACxFX,EAAI,gBAAgBY,EAA6BG,CAAa,EAC9Db,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,QAAS,EAAK,CAAC,CAAC,CAC3C,MAAQ,CACNA,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,cAAe,CAAC,CAAC,CACnD,CACF,CAAC,EACD,MACF,CAEA,GAAIC,IAAW,QAAUC,IAAQ,iBAAkB,CACjDJ,EAAI,cAAc,EAClBE,EAAI,UAAU,GAAG,EACjBA,EAAI,IAAI,EACR,MACF,CAEA,GAAIC,IAAW,QAAUC,IAAQ,aAAc,CAC7CJ,EAAI,UAAU,EACdE,EAAI,UAAU,GAAG,EACjBA,EAAI,IAAI,EACR,MACF,CAEA,GAAIC,IAAW,QAAUC,IAAQ,cAAe,CAC9CJ,EAAI,WAAW,EACfE,EAAI,UAAU,GAAG,EACjBA,EAAI,IAAI,EACR,MACF,CAMA,GAAIC,IAAW,OAASC,GAAOA,EAAI,WAAW,WAAW,EAAG,CAC1D,IAAMY,EAAUhB,EAAI,kBAAkB,EACtC,GAAI,CAACgB,EAAS,CACZd,EAAI,UAAU,IAAK,CAAE,eAAgB,YAAa,CAAC,EACnDA,EAAI,IAAI,yBAAyB,EACjC,MACF,CACA,IAAIe,EACJ,GAAI,CACFA,EAAM,mBAAmBb,EAAI,MAAM,CAAkB,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC,CACpF,MAAQ,CACNF,EAAI,UAAU,IAAK,CAAE,eAAgB,YAAa,CAAC,EACnDA,EAAI,IAAI,aAAa,EACrB,MACF,CACA,IAAMgB,EAAM3B,GAAQ0B,CAAG,EAAE,YAAY,EACrC,GAAI,CAACxB,GAAYyB,CAAG,EAAG,CACrBhB,EAAI,UAAU,IAAK,CAAE,eAAgB,YAAa,CAAC,EACnDA,EAAI,IAAI,wBAAwB,EAChC,MACF,CACA,IAAMiB,EAAa/B,GAAU6B,CAAG,EAC1BG,EAAe/B,GAAY2B,CAAO,EAClCK,EAAehC,GAAYF,GAAKiC,EAAcD,CAAU,CAAC,EAC/D,GAAI,CAACE,EAAa,WAAWD,EAAe9B,EAAG,GAAK+B,IAAiBD,EAAc,CACjFlB,EAAI,UAAU,IAAK,CAAE,eAAgB,YAAa,CAAC,EACnDA,EAAI,IAAI,WAAW,EACnB,MACF,CACAhB,GAASmC,EAAc,CAACC,EAAKC,IAAQ,CACnC,GAAID,EAAK,CACPpB,EAAI,UAAU,IAAK,CAAE,eAAgB,YAAa,CAAC,EACnDA,EAAI,IAAI,WAAW,EACnB,MACF,CACAA,EAAI,UAAU,IAAK,CACjB,eAAgBT,GAAYyB,CAAG,EAC/B,iBAAkBK,EAAI,OACtB,gBAAiB,UACnB,CAAC,EACDrB,EAAI,IAAIqB,CAAG,CACb,CAAC,EACD,MACF,CAEArB,EAAI,UAAU,IAAK,CAAE,eAAgB,YAAa,CAAC,EACnDA,EAAI,IAAI,WAAW,CACrB,CACF,CDhOO,SAASsB,GAAmBC,EAA2B,CAC5D,OAAOC,GAAaC,GAAmBF,CAAG,CAAC,CAC7C,CAEO,SAASG,GAAYC,EAAgBC,EAAwC,CAClF,OAAO,IAAI,QAAQ,CAACC,EAASC,IAAW,CACtCH,EAAO,GAAG,QAASG,CAAM,EACzBH,EAAO,OAAOC,EAAM,IAAM,CACxB,IAAMG,EAAOJ,EAAO,QAAQ,EACtBK,EAAa,OAAOD,GAAS,UAAYA,EAAOA,EAAK,KAAOH,EAClEC,EAAQ,CAAE,IAAK,oBAAoBG,CAAU,EAAG,CAAC,CACnD,CAAC,CACH,CAAC,CACH,CAEO,SAASC,GAAWN,EAA+B,CACxD,OAAO,IAAI,QAAQ,CAACE,EAASC,IAAW,CACtCH,EAAO,MAAOO,GAASA,EAAMJ,EAAOI,CAAG,EAAIL,EAAQ,CAAE,EAErDF,EAAO,oBAAoB,CAC7B,CAAC,CACH,CExBA,OAAS,gBAAAQ,GAAc,cAAAC,OAAkB,UACzC,OAAS,QAAAC,EAAM,WAAAC,OAAe,YAC9B,OAAS,iBAAAC,OAAqB,WAE9B,IAAMC,GAAYF,GAAQC,GAAc,YAAY,GAAG,CAAC,EAIxD,SAASE,IAA0B,CACjC,IAAMC,EAAcL,EAAKG,GAAW,KAAM,UAAW,YAAY,EACjE,GAAIJ,GAAWM,CAAW,EAAG,OAAOA,EAGpC,IAAMC,EAAcN,EAAKG,GAAW,KAAM,IAAI,EACxCI,EAAWP,EAAKM,EAAa,OAAQ,UAAW,YAAY,EAClE,GAAIP,GAAWQ,CAAQ,EAAG,OAAOA,EAEjC,MAAM,IAAI,MAAM;AAAA;AAAA,IAAqEF,CAAW;AAAA,IAAOE,CAAQ,EAAE,CACnH,CAEA,IAAIC,EAAwB,KAErB,SAASC,IAAuB,CACrC,OAAKD,IACHA,EAASV,GAAaM,GAAgB,EAAG,OAAO,GAE3CI,CACT,CCbO,IAAME,EAAN,KAAyC,CACtC,IAA2B,KAC3B,qBAAsC,KACtC,aAA8B,KAC9B,cAAiE,KACjE,mBAAiG,KACjG,iBAAwC,KACxC,aAAoC,KACpC,cAAqC,KACrC,OAAwB,KAEhC,aAAaC,EAAyB,CACpC,KAAK,IAAMA,CACb,CAEA,wBAAwBC,EAA8B,CACpD,KAAK,qBAAuBA,CAC9B,CAIA,gBAAgBC,EAA0B,CACxC,KAAK,aAAeA,CACtB,CAEA,eAAeC,EAAuD,CACpE,KAAK,cAAgBA,CACvB,CAEA,cAAcA,EAAkF,CAC9F,KAAK,mBAAqBA,CAC5B,CAEA,YAAYA,EAA2B,CACrC,KAAK,iBAAmBA,CAC1B,CAEA,QAAQA,EAA2B,CACjC,KAAK,aAAeA,CACtB,CAEA,SAASA,EAA2B,CAClC,KAAK,cAAgBA,CACvB,CAEA,MAAM,MAAMC,EAAwC,CAClD,GAAI,CAAC,KAAK,IAAK,MAAM,IAAI,MAAM,iBAAiB,EAEhD,YAAK,OAASC,GAAmB,CAC/B,YAAa,IAAM,KAAK,IACxB,wBAAyB,IAAM,KAAK,qBACpC,gBAAiB,IAAM,KAAK,aAC5B,SAAWC,GAAe,KAAK,gBAAgBA,CAAU,EACzD,aAAc,IAAMC,GAAa,EACjC,cAAe,CAACC,EAAUC,IAAkB,KAAK,qBAAqBD,EAAUC,CAAa,EAC7F,YAAa,IAAM,KAAK,mBAAmB,EAC3C,QAAS,IAAM,KAAK,eAAe,EACnC,SAAU,IAAM,KAAK,gBAAgB,CACvC,CAAC,EAEMC,GAAY,KAAK,OAAQN,CAAI,CACtC,CAEA,MAAM,MAAsB,CACtB,KAAK,QAAU,KAAK,OAAO,YAC7B,MAAMO,GAAW,KAAK,MAAM,EAC5B,KAAK,OAAS,KAElB,CACF,EJrEA,IAAMC,GAAkB,KAAU,IAC5BC,GAAuB,GAAK,IAKlC,eAAsBC,GACpB,CAAE,IAAAC,EAAK,QAAAC,EAAS,YAAAC,EAAa,sBAAAC,CAAsB,EACxB,CAC3B,IAAMC,EAAY,IAAIC,EACtBD,EAAU,aAAaJ,CAAG,EAC1BI,EAAU,wBAAwBD,CAAqB,EAEvDC,EAAU,gBAAgBH,EAAUK,GAAYL,CAAO,EAAI,IAAI,EAE3DA,GACFG,EAAU,cAAc,CAACG,EAAUC,IAAkB,CACnDC,EAAYR,EAASC,EAAaK,EAAUC,CAAa,CAC3D,CAAC,EAGH,IAAME,EAAgB,IAAI,QAA0B,CAACC,EAASC,IAAW,CACvE,IAAMC,EAAY,WAChB,IAAMD,EAAO,IAAI,MAAM,yDAAyD,CAAC,EACjFf,EACF,EACIiB,EAAwC,KACtCC,EAAe,IAAY,CAC3BD,GAAgB,aAAaA,CAAc,EAC/CA,EAAiB,WAAW,IAAM,CAChC,aAAaD,CAAS,EACtBD,EAAO,IAAI,MAAM,mDAAmD,CAAC,CACvE,EAAGd,EAAoB,CACzB,EACMkB,EAAW,IAAY,CAC3B,aAAaH,CAAS,EAClBC,GAAgB,aAAaA,CAAc,EAC/CA,EAAiB,IACnB,EACAV,EAAU,YAAYW,CAAY,EAClCX,EAAU,QAAQ,IAAM,CAClBU,GAAgB,aAAaA,CAAc,EAC/CA,EAAiB,IACnB,CAAC,EACDV,EAAU,SAAS,IAAM,CACvBY,EAAS,EACTJ,EAAO,IAAI,MAAM,kCAAkC,CAAC,CACtD,CAAC,EACDR,EAAU,eAAgBa,GAAe,CACvCD,EAAS,EACTL,EAAQM,CAAU,CACpB,CAAC,CACH,CAAC,EAEK,CAAE,IAAAC,CAAI,EAAI,MAAMd,EAAU,MAAM,CAAC,EACvC,QAAQ,OAAO,MAAM,4BAA4Bc,CAAG;AAAA,CAAI,EAExD,GAAI,CACF,IAAMC,EAAU,QAAQ,WAAa,SAAW,OAC5C,QAAQ,WAAa,QAAU,QAC/B,WACJC,GAAUD,EAAS,CAACD,CAAG,EAAG,CAAE,MAAO,QAAS,CAAC,CAC/C,MAAQ,CACN,QAAQ,OAAO,MAAM,QAAQA,CAAG;AAAA,CAAoB,CACtD,CAEA,GAAI,CACF,OAAO,MAAMR,CACf,QAAE,CACA,MAAMN,EAAU,KAAK,CACvB,CACF,CRpEA,IAAMiB,GAAUC,GAAc,YAAY,GAAG,EACvC,CAAE,QAAAC,EAAQ,EAAIF,GAAQ,iBAAiB,EAEvCG,EAAU,IAAIC,GAEpBD,EACG,KAAK,aAAa,EAClB,YAAY,2DAA2D,EACvE,QAAQD,EAAO,EACf,SAAS,SAAU,4CAA4C,EAC/D,OAAO,wBAAyB,gDAAgD,EAChF,OAAO,uBAAwB,8CAA8C,EAC7E,OAAO,wBAAyB,0CAA0C,EAC1E,OAAO,UAAW,yCAAyC,EAC3D,OAAO,QAAS,qEAAqE,EACrF,OAAO,MAAOG,EAA0BC,IAAqG,CAC5I,GAAI,CACF,MAAMC,GAAIF,EAAMC,CAAI,CACtB,OAASE,EAAK,CACZ,GAAIA,aAAe,MAAO,CACxB,IAAMC,EAAYD,EAAI,QAAQ,WAAW,kBAAkB,EAC3D,QAAQ,MAAMC,EAAYC,EAAM,OAAOF,EAAI,OAAO,EAAIE,EAAM,IAAI,UAAUF,EAAI,OAAO,EAAE,CAAC,CAC1F,CACA,QAAQ,KAAK,CAAC,CAChB,CACF,CAAC,EAEHL,EACG,QAAQ,eAAe,EACvB,YAAY,4DAA4D,EACxE,OAAO,IAAM,CACZ,IAAMQ,EAAYC,GAAQC,GAAc,YAAY,GAAG,CAAC,EAClDC,EAAMC,EAAKJ,EAAW,KAAM,SAAU,cAAe,UAAU,EAChEK,GAAWF,CAAG,IACjB,QAAQ,MAAMJ,EAAM,IAAI,iDAAmDI,CAAG,CAAC,EAC/E,QAAQ,KAAK,CAAC,GAEhB,IAAMG,EAAOF,EAAKG,GAAQ,EAAG,UAAW,SAAU,aAAa,EAC/DC,GAAUF,EAAM,CAAE,UAAW,EAAK,CAAC,EACnCG,GAAaN,EAAKC,EAAKE,EAAM,UAAU,CAAC,EACxC,QAAQ,MAAMP,EAAM,MAAM,sBAAsBO,CAAI,WAAW,CAAC,EAChE,QAAQ,MAAMP,EAAM,IAAI,sEAAsE,CAAC,CACjG,CAAC,EAEHP,EACG,QAAQ,UAAU,EAClB,YAAY,gCAAgC,EAC5C,OAAO,IAAM,CACZ,IAAMkB,EAAWC,EAAa,EACxBC,EAAMC,EAAc,EACtBH,EAAS,SAAW,IACtB,QAAQ,MAAMX,EAAM,IAAI,uBAAuBa,CAAG,GAAG,CAAC,EACtD,QAAQ,KAAK,CAAC,GAEhB,QAAQ,MAAMb,EAAM,KAAK,0BAA0Ba,CAAG;AAAA,CAAM,CAAC,EAC7D,QAAWE,KAAKJ,EAAU,CACxB,IAAMK,EAAMC,GAAmBF,EAAE,YAAY,EACzCG,EAAS,GACTH,EAAE,QAAU,GAAMG,EAASlB,EAAM,OAAO,wCAAwC,EAC3Ee,EAAE,QAAU,OAAMG,EAASlB,EAAM,IAAI,wBAAwB,GACtE,QAAQ,MAAM,KAAKe,EAAE,QAAQ,EAAE,EAC/B,QAAQ,MAAMf,EAAM,IAAI,OAAOe,EAAE,YAAY,WAAWA,EAAE,eAAiB,EAAI,IAAM,EAAE,oBAAoBC,CAAG,GAAGE,CAAM;AAAA,CAAI,CAAC,CAC9H,CACF,CAAC,EAEHzB,EAAQ,MAAM,EAEd,eAAeI,GACbF,EACAC,EACe,CAEf,IAAMuB,EAA+B,CAAC,SAAU,YAAa,OAAQ,QAAQ,EAC7E,GAAIvB,EAAK,SAAW,OAAW,CAC7B,IAAMwB,EAAiBxB,EAAK,OAC5B,GAAI,CAACuB,EAAa,SAASC,CAAc,EACvC,MAAM,IAAI,MAAM,2BAA2BxB,EAAK,MAAM,WAAWuB,EAAa,KAAK,IAAI,CAAC,EAAE,EAGxFC,IAAmB,UAAY,CAACC,EAAkB,IACpD,QAAQ,MAAMrB,EAAM,IAAI,+BAA+B,CAAC,EACxD,QAAQ,MAAMA,EAAM,IAAI,yDAAyD,CAAC,EAClF,QAAQ,MAAMA,EAAM,OAAO,wCAAwC,CAAC,EAExE,CAGA,IAAMsB,EAAiB,CAAC3B,GAAQ,CAAC,QAAQ,MAAM,MACzC4B,EAAQC,GAAU7B,CAAI,EACvB4B,EAAM,KAAK,IACd,QAAQ,MAAMvB,EAAM,OAAO,gCAAgC,CAAC,EAC5D,QAAQ,KAAK,CAAC,GAIhB,IAAMyB,EAAgB7B,EAAK,UAAY,UAAY,UAC/CA,EAAK,UAAY,YAAc,YAC/B,OACE8B,EAAMC,EAAMJ,EAAOE,CAAa,EAEtC,QAAQ,MAAMzB,EAAM,IAAI,kBAAkB0B,EAAI,IAAI,MAAMA,EAAI,SAAS,MAAM,WAAW,CAAC,EAGvF,IAAME,EAAUjC,EAAOkC,GAAYlC,CAAI,EAAI,KACrCmC,EAAcC,EAAmBR,CAAK,EAExCS,EAAuC,KAC3C,GAAIJ,EACF,GAAIhC,EAAK,MACPqC,EAAaL,CAAO,MACf,CACL,IAAMM,EAAUC,EAAYP,EAASE,CAAW,EAC5CI,GAAWA,EAAQ,SAAS,OAAS,IAClCA,EAAQ,MAMI,MAAME,GACnB,wCAAwCF,EAAQ,SAAS,MAAM,WAAWA,EAAQ,SAAS,SAAW,EAAI,IAAM,EAAE,oBAClHZ,CACF,GAEE,QAAQ,MAAMtB,EAAM,OAAO,8BAA8B,CAAC,EAC1D0B,EAAI,SAAWQ,EAAQ,SACvBF,EAAwBE,EAAQ,eAEhCD,EAAaL,CAAO,GAdtB,QAAQ,MAAM5B,EAAM,MAAM,oBAAoBkC,EAAQ,SAAS,MAAM,WAAWA,EAAQ,SAAS,SAAW,EAAI,IAAM,EAAE,IAAI,CAAC,EAC7HR,EAAI,SAAWQ,EAAQ,SACvBF,EAAwBE,EAAQ,eAgBtC,CAIF,IAAIG,EACAC,EAA4D,CAAE,QAAS,KAAM,QAAS,EAAG,EAC7F,GAAK1C,EAAK,IASRyC,EAAW,MAAME,EAASb,EAAKJ,EAHPM,EACpB,IAAMY,EAAYZ,EAASE,EAAaJ,EAAI,SAAU,IAAI,EAC1D,MAC0D,MATjD,CACb,IAAMe,EAAa,MAAMC,GAAiB,CAAE,IAAAhB,EAAK,QAAAE,EAAS,YAAAE,EAAa,sBAAAE,CAAsB,CAAC,EAC9FN,EAAI,SAAWe,EAAW,SAC1BH,EAAa,CAAE,QAASG,EAAW,QAAS,QAASA,EAAW,OAAQ,EACxEJ,EAAWX,CACb,CAQIE,GAASK,EAAaL,CAAO,EAGjC,IAAIe,EACA/C,EAAK,SAAW,OAClB+C,EAAe/C,EAAK,QAEpB+C,EAAe,MAAMC,GAAmBtB,CAAc,EAElDqB,IAAiB,UAAY,CAACtB,EAAkB,IAClD,QAAQ,MAAMrB,EAAM,IAAI,+BAA+B,CAAC,EACxD,QAAQ,MAAMA,EAAM,IAAI,yDAAyD,CAAC,EAClF,QAAQ,MAAMA,EAAM,OAAO,yBAAyB,CAAC,EACrD2C,EAAe,WAKnB,IAAME,EAASC,EAAaT,EAAUC,CAAU,EAChDS,GAAYF,EAAQF,EAAc,CAAE,WAAY/C,EAAK,WAAY,UAAWD,CAAK,CAAC,CACpF,CAEA,SAAS6B,GAAU7B,EAAkC,CACnD,GAAIA,EAAM,CACR,GAAI,CAACW,GAAWX,CAAI,EAClB,MAAM,IAAI,MAAM,mBAAmBA,CAAI,EAAE,EAE3C,OAAOqD,GAAarD,EAAM,OAAO,CACnC,CAGA,OAAK,QAAQ,MAAM,OAKnBF,EAAQ,KAAK,EACN,IALEuD,GAAa,aAAc,OAAO,CAM7C,CAEA,SAAS/B,GAAmBgC,EAAqB,CAC/C,IAAMC,EAAK,KAAK,IAAI,EAAI,IAAI,KAAKD,CAAG,EAAE,QAAQ,EACxCE,EAAU,KAAK,MAAMD,EAAK,GAAI,EACpC,GAAIC,EAAU,GAAI,MAAO,WACzB,IAAMC,EAAU,KAAK,MAAMD,EAAU,EAAE,EACvC,GAAIC,EAAU,GAAI,MAAO,GAAGA,CAAO,QACnC,IAAMC,EAAQ,KAAK,MAAMD,EAAU,EAAE,EACrC,GAAIC,EAAQ,GAAI,MAAO,GAAGA,CAAK,QAC/B,IAAMC,EAAO,KAAK,MAAMD,EAAQ,EAAE,EAClC,OAAIC,EAAO,EAAU,GAAGA,CAAI,QAErB,GADO,KAAK,MAAMA,EAAO,CAAC,CAClB,OACjB",
6
- "names": ["Command", "readFileSync", "mkdirSync", "copyFileSync", "existsSync", "homedir", "dirname", "join", "resolvePath", "fileURLToPath", "chalk", "parse", "input", "strategy", "lines", "title", "extractTitle", "metadata", "extractMetadata", "isPlanDocument", "parsePlan", "parseGeneric", "parseBySeparator", "h1", "l", "meta", "line", "match", "stripped", "hasH2H3Hierarchy", "hasPlanFields", "sections", "splitByHeadings", "s", "i", "currentHeading", "currentLevel", "currentBody", "h2Count", "h3Count", "splitLevel", "headingRegex", "inCodeBlock", "parts", "p", "firstLine", "milestoneIndex", "taskIndex", "currentMilestoneId", "flushSection", "body", "id", "extractDependencies", "extractRelatedFiles", "extractVerification", "h2Match", "h3Match", "dependsMatch", "blocksMatch", "parseList", "raw", "trimmed", "files", "inRelatedFiles", "fileMatch", "suffix", "createHash", "mkdirSync", "readFileSync", "writeFileSync", "unlinkSync", "readdirSync", "existsSync", "join", "resolve", "homedir", "pathHash", "planPath", "abs", "sessionFilePath", "getSessionDir", "dir", "computeContentHash", "content", "saveSession", "contentHash", "comments", "activeSection", "data", "filePath", "err", "loadSession", "currentContentHash", "raw", "c", "clearSession", "listSessions", "files", "results", "file", "stale", "currentContent", "escapeMarkdown", "text", "sortComments", "comments", "a", "b", "aLine", "bLine", "verdictLabel", "verdict", "formatReview", "doc", "opts", "commentedSectionIds", "c", "reviewableSections", "s", "commentedSections", "parts", "skippedCount", "section", "sectionComments", "deps", "comment", "line", "readline", "chalk", "marked", "markedTerminal", "chalk", "renderSection", "section", "parts", "renderMetadataHeader", "heading", "body", "markdown", "deps", "dependsOn", "blocks", "lines", "fileList", "maxLen", "l", "innerWidth", "top", "bottom", "content", "renderToc", "doc", "commentedIds", "c", "marker", "reviewable", "s", "i", "num", "commentedCount", "remaining", "navigate", "doc", "inputFromStdin", "onCommentChange", "ttyInput", "rl", "ask", "prompt", "resolve", "answer", "reviewableSections", "getReviewableSections", "running", "renderToc", "input", "chalk", "linearReview", "section", "findSection", "startIdx", "printSummary", "sections", "i", "renderSection", "byId", "s", "num", "reviewable", "commentedIds", "c", "execSync", "spawn", "writeFileSync", "resolve", "chalk", "writeOutput", "content", "target", "options", "writeToClipboard", "writeToFile", "sendToClaude", "cmd", "getClipboardCommand", "outputFile", "inputFile", "filePath", "err", "msg", "isClaudeAvailable", "child", "platform", "createRequire", "readline", "chalk", "ttyInputStream", "inputFromStdin", "promptOutputTarget", "rl", "answer", "resolve", "a", "promptYesNo", "message", "spawnSync", "dirnamePath", "createServer", "readFile", "join", "normalize", "resolvePath", "sep", "extname", "MAX_BODY_SIZE", "MIME_BY_EXT", "validateComment", "obj", "c", "validateVerdict", "value", "createRouteHandler", "ctx", "req", "res", "method", "url", "html", "doc", "initialState", "body", "size", "chunk", "parsed", "comments", "verdict", "summary", "activeSection", "baseDir", "rel", "ext", "normalized", "resolvedBase", "resolvedFile", "err", "buf", "createReviewServer", "ctx", "createServer", "createRouteHandler", "startServer", "server", "port", "resolve", "reject", "addr", "actualPort", "stopServer", "err", "readFileSync", "existsSync", "join", "dirname", "fileURLToPath", "__dirname", "resolveHtmlPath", "siblingPath", "projectRoot", "distPath", "cached", "getAssetHtml", "HttpTransport", "doc", "section", "dir", "handler", "port", "createReviewServer", "submission", "getAssetHtml", "comments", "activeSection", "startServer", "stopServer", "IDLE_TIMEOUT_MS", "HEARTBEAT_TIMEOUT_MS", "runBrowserReview", "doc", "absPath", "contentHash", "restoredActiveSection", "transport", "HttpTransport", "dirnamePath", "comments", "activeSection", "saveSession", "reviewPromise", "resolve", "reject", "idleTimer", "heartbeatTimer", "armHeartbeat", "clearAll", "submission", "url", "openCmd", "spawnSync", "require", "createRequire", "version", "program", "Command", "file", "opts", "run", "err", "cancelled", "chalk", "__dirname", "dirname", "fileURLToPath", "src", "join", "existsSync", "dest", "homedir", "mkdirSync", "copyFileSync", "sessions", "listSessions", "dir", "getSessionDir", "s", "age", "formatRelativeTime", "status", "validTargets", "explicitTarget", "isClaudeAvailable", "inputFromStdin", "input", "readInput", "splitStrategy", "doc", "parse", "absPath", "resolvePath", "contentHash", "computeContentHash", "restoredActiveSection", "clearSession", "session", "loadSession", "promptYesNo", "reviewed", "reviewMeta", "navigate", "saveSession", "submission", "runBrowserReview", "outputTarget", "promptOutputTarget", "output", "formatReview", "writeOutput", "readFileSync", "iso", "ms", "seconds", "minutes", "hours", "days"]
4
+ "sourcesContent": ["import { Command } from 'commander';\nimport { readFileSync, mkdirSync, copyFileSync } from 'node:fs';\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve as resolvePath } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport chalk from 'chalk';\nimport { parse, formatReview, loadSession, saveSession, clearSession, computeContentHash, listSessions, getSessionDir } from '@plan-review/core';\nimport type { OutputTarget, PlanDocument, ReviewSubmission } from '@plan-review/core';\nimport { navigate } from './navigator.js';\nimport { writeOutput, isClaudeAvailable } from './output.js';\nimport { createRequire } from 'node:module';\nimport { promptOutputTarget, promptYesNo } from './prompts.js';\nimport { runBrowserReview } from './browser-review.js';\n\nconst require = createRequire(import.meta.url);\nconst { version } = require('../package.json');\n\nconst program = new Command();\n\nprogram\n .name('plan-review')\n .description('Interactive CLI for reviewing AI-generated markdown plans')\n .version(version)\n .argument('[file]', 'Path to markdown file (omit to read stdin)')\n .option('-o, --output <target>', 'Output target: stdout, clipboard, file, claude')\n .option('--output-file <path>', 'Custom output file path (with --output file)')\n .option('--split-by <strategy>', 'Force split strategy: heading, separator')\n .option('--fresh', 'Skip session resume, start clean review')\n .option('--cli', 'Use the terminal review UI instead of the browser (SSH/CI/headless)')\n .action(async (file: string | undefined, opts: { output?: string; outputFile?: string; splitBy?: string; cli?: boolean; fresh?: boolean }) => {\n try {\n await run(file, opts);\n } catch (err) {\n if (err instanceof Error) {\n const cancelled = err.message.startsWith('Review cancelled');\n console.error(cancelled ? chalk.yellow(err.message) : chalk.red(`Error: ${err.message}`));\n }\n process.exit(1);\n }\n });\n\nprogram\n .command('install-skill')\n .description('Install Claude Code skill to ~/.claude/skills/plan-review/')\n .action(() => {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const src = join(__dirname, '..', 'skills', 'plan-review', 'SKILL.md');\n if (!existsSync(src)) {\n console.error(chalk.red('Skill file not found in package. Expected at: ' + src));\n process.exit(1);\n }\n const dest = join(homedir(), '.claude', 'skills', 'plan-review');\n mkdirSync(dest, { recursive: true });\n copyFileSync(src, join(dest, 'SKILL.md'));\n console.error(chalk.green(`Skill installed to ${dest}/SKILL.md`));\n console.error(chalk.dim('Claude Code will auto-discover it. Try: \"I want to review this plan\"'));\n });\n\nprogram\n .command('sessions')\n .description('List all saved review sessions')\n .action(() => {\n const sessions = listSessions();\n const dir = getSessionDir();\n if (sessions.length === 0) {\n console.error(chalk.dim(`No saved sessions. (${dir})`));\n process.exit(0);\n }\n console.error(chalk.bold(`Saved review sessions (${dir}):\\n`));\n for (const s of sessions) {\n const age = formatRelativeTime(s.lastModified);\n let status = '';\n if (s.stale === true) status = chalk.yellow(' | plan file changed since last review');\n else if (s.stale === null) status = chalk.red(' | plan file not found');\n console.error(` ${s.planPath}`);\n console.error(chalk.dim(` ${s.commentCount} comment${s.commentCount !== 1 ? 's' : ''} | last modified ${age}${status}\\n`));\n }\n });\n\nprogram.parse();\n\nasync function run(\n file: string | undefined,\n opts: { output?: string; outputFile?: string; splitBy?: string; cli?: boolean; fresh?: boolean },\n): Promise<void> {\n // Validate explicit output target early, before the review starts\n const validTargets: OutputTarget[] = ['stdout', 'clipboard', 'file', 'claude'];\n if (opts.output !== undefined) {\n const explicitTarget = opts.output as OutputTarget;\n if (!validTargets.includes(explicitTarget)) {\n throw new Error(`Invalid output target: \"${opts.output}\". Use: ${validTargets.join(', ')}`);\n }\n // Fail fast: check claude availability before starting review\n if (explicitTarget === 'claude' && !isClaudeAvailable()) {\n console.error(chalk.red('Claude CLI not found in PATH.'));\n console.error(chalk.dim('Install: https://docs.anthropic.com/en/docs/claude-code'));\n console.error(chalk.yellow('Will fall back to stdout after review.'));\n }\n }\n\n // Read input \u2014 track whether it came from stdin\n const inputFromStdin = !file && !process.stdin.isTTY;\n const input = readInput(file);\n if (!input.trim()) {\n console.error(chalk.yellow('Empty file, nothing to review.'));\n process.exit(0);\n }\n\n // Parse\n const splitStrategy = opts.splitBy === 'heading' ? 'heading' as const\n : opts.splitBy === 'separator' ? 'separator' as const\n : 'auto' as const;\n const doc = parse(input, splitStrategy);\n\n console.error(chalk.dim(`Detected mode: ${doc.mode} | ${doc.sections.length} sections`));\n\n // Session resume logic\n const absPath = file ? resolvePath(file) : null;\n const contentHash = computeContentHash(input);\n\n let restoredActiveSection: string | null = null;\n if (absPath) {\n if (opts.fresh) {\n clearSession(absPath);\n } else {\n const session = loadSession(absPath, contentHash);\n if (session && session.comments.length > 0) {\n if (!session.stale) {\n console.error(chalk.green(`Resuming review (${session.comments.length} comment${session.comments.length !== 1 ? 's' : ''}).`));\n doc.comments = session.comments;\n restoredActiveSection = session.activeSection;\n } else {\n // Prompt user for stale session\n const answer = await promptYesNo(\n `Plan file changed since last review (${session.comments.length} comment${session.comments.length !== 1 ? 's' : ''}). Resume anyway?`,\n inputFromStdin,\n );\n if (answer) {\n console.error(chalk.yellow('Resuming with stale session.'));\n doc.comments = session.comments;\n restoredActiveSection = session.activeSection;\n } else {\n clearSession(absPath);\n }\n }\n }\n }\n }\n\n // Navigate (interactive review or browser)\n let reviewed: PlanDocument;\n let reviewMeta: Pick<ReviewSubmission, 'verdict' | 'summary'> = { verdict: null, summary: '' };\n if (!opts.cli) {\n const submission = await runBrowserReview({ doc, absPath, contentHash, restoredActiveSection });\n doc.comments = submission.comments;\n reviewMeta = { verdict: submission.verdict, summary: submission.summary };\n reviewed = doc;\n } else {\n const onCommentChange = absPath\n ? () => saveSession(absPath, contentHash, doc.comments, null)\n : undefined;\n reviewed = await navigate(doc, inputFromStdin, onCommentChange);\n }\n\n // Clear session after successful review completion\n if (absPath) clearSession(absPath);\n\n // Determine output target after review is complete\n let outputTarget: OutputTarget;\n if (opts.output !== undefined) {\n outputTarget = opts.output as OutputTarget;\n } else {\n outputTarget = await promptOutputTarget(inputFromStdin);\n // Check claude availability after prompting\n if (outputTarget === 'claude' && !isClaudeAvailable()) {\n console.error(chalk.red('Claude CLI not found in PATH.'));\n console.error(chalk.dim('Install: https://docs.anthropic.com/en/docs/claude-code'));\n console.error(chalk.yellow('Falling back to stdout.'));\n outputTarget = 'stdout';\n }\n }\n\n // Format and output\n const output = formatReview(reviewed, reviewMeta);\n writeOutput(output, outputTarget, { outputFile: opts.outputFile, inputFile: file });\n}\n\nfunction readInput(file: string | undefined): string {\n if (file) {\n if (!existsSync(file)) {\n throw new Error(`File not found: ${file}`);\n }\n return readFileSync(file, 'utf-8');\n }\n\n // Read from stdin (piped)\n if (!process.stdin.isTTY) {\n return readFileSync('/dev/stdin', 'utf-8');\n }\n\n // No file, no stdin pipe \u2014 show help\n program.help();\n return ''; // unreachable\n}\n\nfunction formatRelativeTime(iso: string): string {\n const ms = Date.now() - new Date(iso).getTime();\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return 'just now';\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n if (days < 7) return `${days}d ago`;\n const weeks = Math.floor(days / 7);\n return `${weeks}w ago`;\n}\n", "import type { PlanDocument, Section, SplitStrategy } from './types.js';\n\nconst MIN_SECTION_CHARS = 5;\n\nexport function parse(input: string, strategy: SplitStrategy = 'auto'): PlanDocument {\n const lines = input.split('\\n');\n const title = extractTitle(lines);\n const metadata = extractMetadata(lines);\n\n if (strategy === 'auto') {\n if (isPlanDocument(input)) {\n return parsePlan(input, title, metadata);\n }\n return parseGeneric(input, title, metadata);\n }\n\n if (strategy === 'separator') {\n return parseBySeparator(input, title, metadata);\n }\n\n return parseGeneric(input, title, metadata);\n}\n\nfunction extractTitle(lines: string[]): string {\n const h1 = lines.find((l) => /^# /.test(l));\n return h1 ? h1.replace(/^# /, '').trim() : 'Untitled';\n}\n\nfunction extractMetadata(lines: string[]): Record<string, string> {\n const meta: Record<string, string> = {};\n for (const line of lines.slice(0, 20)) {\n const match = line.match(/^\\*\\*(\\w[\\w\\s]*?):\\*\\*\\s*(.+)/);\n if (match) {\n meta[match[1].trim()] = match[2].trim();\n }\n }\n return meta;\n}\n\nexport function isPlanDocument(input: string): boolean {\n const stripped = input.replace(/```[\\s\\S]*?```/g, '');\n const hasH2H3Hierarchy =\n /^## /m.test(stripped) && /^### /m.test(stripped);\n const hasPlanFields =\n /\\*\\*Depends On:\\*\\*/m.test(stripped) ||\n /\\*\\*Blocks:\\*\\*/m.test(stripped) ||\n /\\*\\*Verification:\\*\\*/m.test(stripped) ||\n /\\*\\*Related Files:\\*\\*/m.test(stripped);\n\n return hasH2H3Hierarchy && hasPlanFields;\n}\n\nfunction parseGeneric(\n input: string,\n title: string,\n metadata: Record<string, string>,\n): PlanDocument {\n const sections = splitByHeadings(input);\n\n if (sections.length === 0) {\n return parseBySeparator(input, title, metadata);\n }\n\n return {\n title,\n metadata,\n mode: 'generic',\n sections: sections.map((s, i) => ({\n id: `section-${i + 1}`,\n heading: s.heading,\n level: s.level,\n body: s.body,\n })),\n comments: [],\n };\n}\n\ninterface RawSection {\n heading: string;\n level: number;\n body: string;\n}\n\nfunction splitByHeadings(input: string): RawSection[] {\n const lines = input.split('\\n');\n const sections: RawSection[] = [];\n let currentHeading = '';\n let currentLevel = 0;\n let currentBody: string[] = [];\n\n // Find the most common heading level (## or ###)\n const h2Count = (input.match(/^## /gm) || []).length;\n const h3Count = (input.match(/^### /gm) || []).length;\n const splitLevel = h2Count > 0 ? 2 : h3Count > 0 ? 3 : 0;\n\n if (splitLevel === 0) return [];\n\n const headingRegex = new RegExp(`^${'#'.repeat(splitLevel)} (.+)`);\n let inCodeBlock = false;\n\n for (const line of lines) {\n if (line.startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n if (currentHeading) {\n currentBody.push(line);\n }\n continue;\n }\n\n if (inCodeBlock) {\n if (currentHeading) {\n currentBody.push(line);\n }\n continue;\n }\n\n const match = line.match(headingRegex);\n if (match) {\n if (currentHeading) {\n sections.push({\n heading: currentHeading,\n level: currentLevel,\n body: currentBody.join('\\n').trim(),\n });\n }\n currentHeading = match[1].trim();\n currentLevel = splitLevel;\n currentBody = [];\n } else if (currentHeading) {\n currentBody.push(line);\n }\n }\n\n if (currentHeading) {\n sections.push({\n heading: currentHeading,\n level: currentLevel,\n body: currentBody.join('\\n').trim(),\n });\n }\n\n return sections;\n}\n\nfunction parseBySeparator(\n input: string,\n title: string,\n metadata: Record<string, string>,\n): PlanDocument {\n const parts = input.split(/\\n---\\n/).filter((p) => {\n return p.trim().length >= MIN_SECTION_CHARS;\n });\n\n if (parts.length <= 1) {\n return {\n title,\n metadata,\n mode: 'generic',\n sections: [\n {\n id: 'section-1',\n heading: title,\n level: 1,\n body: input.trim(),\n },\n ],\n comments: [],\n };\n }\n\n return {\n title,\n metadata,\n mode: 'generic',\n sections: parts.map((p, i) => {\n const lines = p.trim().split('\\n');\n const firstLine = lines[0].replace(/^#+\\s*/, '').trim();\n return {\n id: `section-${i + 1}`,\n heading: firstLine || `Section ${i + 1}`,\n level: 2,\n body: p.trim(),\n };\n }),\n comments: [],\n };\n}\n\nfunction parsePlan(\n input: string,\n title: string,\n metadata: Record<string, string>,\n): PlanDocument {\n const lines = input.split('\\n');\n const sections: Section[] = [];\n\n let milestoneIndex = 0;\n let taskIndex = 0;\n let currentMilestoneId = '';\n let currentHeading = '';\n let currentLevel = 0;\n let currentBody: string[] = [];\n let inCodeBlock = false;\n\n function flushSection() {\n if (!currentHeading) return;\n\n const body = currentBody.join('\\n').trim();\n\n if (currentLevel === 2) {\n milestoneIndex++;\n taskIndex = 0;\n currentMilestoneId = `milestone-${milestoneIndex}`;\n sections.push({\n id: currentMilestoneId,\n heading: currentHeading,\n level: 2,\n body,\n });\n return;\n }\n\n taskIndex++;\n const id = `${milestoneIndex}.${taskIndex}`;\n sections.push({\n id,\n heading: currentHeading,\n level: 3,\n body,\n parent: currentMilestoneId,\n dependencies: extractDependencies(body),\n relatedFiles: extractRelatedFiles(body),\n verification: extractVerification(body),\n });\n }\n\n for (const line of lines) {\n if (line.startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n currentBody.push(line);\n continue;\n }\n\n if (inCodeBlock) {\n currentBody.push(line);\n continue;\n }\n\n const h2Match = line.match(/^## (.+)/);\n const h3Match = line.match(/^### (.+)/);\n\n if (h2Match) {\n flushSection();\n currentHeading = h2Match[1].trim();\n currentLevel = 2;\n currentBody = [];\n } else if (h3Match) {\n flushSection();\n currentHeading = h3Match[1].trim();\n currentLevel = 3;\n currentBody = [];\n } else {\n currentBody.push(line);\n }\n }\n flushSection();\n\n return {\n title,\n metadata,\n mode: 'plan',\n sections,\n comments: [],\n };\n}\n\nfunction extractDependencies(body: string): { dependsOn: string[]; blocks: string[] } {\n const dependsMatch = body.match(/\\*\\*Depends On:\\*\\*\\s*(.+)/);\n const blocksMatch = body.match(/\\*\\*Blocks:\\*\\*\\s*(.+)/);\n\n const parseList = (raw: string): string[] => {\n const trimmed = raw.trim();\n if (trimmed === '(none)' || trimmed === '') return [];\n return trimmed.split(/,\\s*/).map((s) => s.trim());\n };\n\n return {\n dependsOn: dependsMatch ? parseList(dependsMatch[1]) : [],\n blocks: blocksMatch ? parseList(blocksMatch[1]) : [],\n };\n}\n\nfunction extractRelatedFiles(body: string): string[] {\n const files: string[] = [];\n const lines = body.split('\\n');\n let inRelatedFiles = false;\n\n for (const line of lines) {\n if (/\\*\\*Related Files:\\*\\*/.test(line)) {\n inRelatedFiles = true;\n continue;\n }\n if (inRelatedFiles) {\n const fileMatch = line.match(/^- `(.+)`(.*)$/);\n if (fileMatch) {\n const suffix = fileMatch[2].trim();\n files.push(suffix ? `${fileMatch[1]} ${suffix}` : fileMatch[1]);\n } else if (line.trim() === '' || /^\\*\\*/.test(line.trim())) {\n inRelatedFiles = false;\n }\n }\n }\n\n return files;\n}\n\nfunction extractVerification(body: string): string | undefined {\n const match = body.match(/\\*\\*Verification:\\*\\*\\s*`(.+?)`/);\n return match ? match[1] : undefined;\n}\n", "import { createHash } from 'node:crypto';\nimport {\n mkdirSync,\n readFileSync,\n writeFileSync,\n unlinkSync,\n readdirSync,\n existsSync,\n} from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { ReviewComment } from './types.js';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface SessionData {\n version: number;\n planPath: string;\n contentHash: string;\n comments: ReviewComment[];\n activeSection: string | null;\n lastModified: string;\n}\n\nexport interface SessionLoadResult {\n comments: ReviewComment[];\n activeSection: string | null;\n stale: boolean;\n}\n\n// \u2500\u2500 Internal helpers (not exported) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction pathHash(planPath: string): string {\n const abs = resolve(planPath);\n const hash = createHash('sha256').update(abs).digest('hex');\n return hash.slice(0, 16);\n}\n\nfunction sessionFilePath(planPath: string): string {\n return join(getSessionDir(), pathHash(planPath) + '.json');\n}\n\n// \u2500\u2500 Exported functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function getSessionDir(): string {\n const dir = join(homedir(), '.plan-review', 'sessions');\n mkdirSync(dir, { recursive: true });\n return dir;\n}\n\nexport function computeContentHash(content: string): string {\n const hex = createHash('sha256').update(content).digest('hex');\n return `sha256:${hex}`;\n}\n\nexport function saveSession(\n planPath: string,\n contentHash: string,\n comments: ReviewComment[],\n activeSection: string | null,\n): void {\n try {\n const data: SessionData = {\n version: 1,\n planPath,\n contentHash,\n comments,\n activeSection,\n lastModified: new Date().toISOString(),\n };\n const filePath = sessionFilePath(planPath);\n writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');\n } catch (err) {\n console.warn(`[plan-review] Failed to save session: ${(err as Error).message}`);\n }\n}\n\nexport function loadSession(\n planPath: string,\n currentContentHash: string,\n): SessionLoadResult | null {\n const filePath = sessionFilePath(planPath);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n let raw: string;\n try {\n raw = readFileSync(filePath, 'utf-8');\n } catch {\n return null;\n }\n\n let data: SessionData;\n try {\n data = JSON.parse(raw) as SessionData;\n } catch {\n console.warn(`[plan-review] Corrupt session file, removing: ${filePath}`);\n try {\n unlinkSync(filePath);\n } catch {\n // ignore\n }\n return null;\n }\n\n // Restore Date objects in comment timestamps\n const comments: ReviewComment[] = data.comments.map((c) => ({\n ...c,\n timestamp: new Date(c.timestamp),\n }));\n\n return {\n comments,\n activeSection: data.activeSection,\n stale: data.contentHash !== currentContentHash,\n };\n}\n\nexport function clearSession(planPath: string): void {\n const filePath = sessionFilePath(planPath);\n try {\n unlinkSync(filePath);\n } catch {\n // No error if missing\n }\n}\n\nexport function listSessions(): Array<{\n planPath: string;\n commentCount: number;\n lastModified: string;\n stale: boolean | null;\n}> {\n const dir = getSessionDir();\n let files: string[];\n try {\n files = readdirSync(dir);\n } catch {\n return [];\n }\n\n const results: Array<{\n planPath: string;\n commentCount: number;\n lastModified: string;\n stale: boolean | null;\n }> = [];\n\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n\n const filePath = join(dir, file);\n let data: SessionData;\n try {\n const raw = readFileSync(filePath, 'utf-8');\n data = JSON.parse(raw) as SessionData;\n } catch {\n console.warn(`[plan-review] Skipping corrupt session file: ${filePath}`);\n continue;\n }\n\n let stale: boolean | null;\n if (!existsSync(data.planPath)) {\n stale = null;\n } else {\n try {\n const currentContent = readFileSync(data.planPath, 'utf-8');\n const currentHash = computeContentHash(currentContent);\n stale = currentHash !== data.contentHash;\n } catch {\n stale = null;\n }\n }\n\n results.push({\n planPath: data.planPath,\n commentCount: data.comments.length,\n lastModified: data.lastModified,\n stale,\n });\n }\n\n return results;\n}\n", "import type { PlanDocument, ReviewComment, ReviewVerdict } from './types.js';\n\nfunction escapeMarkdown(text: string): string {\n return text.replace(/([\\\\*_`~\\[\\]#>|])/g, '\\\\$1');\n}\n\nfunction sortComments(comments: ReviewComment[]): ReviewComment[] {\n return [...comments].sort((a, b) => {\n const aLine = a.anchor?.startLine ?? Infinity;\n const bLine = b.anchor?.startLine ?? Infinity;\n return aLine - bLine;\n });\n}\n\nfunction verdictLabel(verdict: ReviewVerdict): string {\n return verdict === 'approved' ? 'Approved' : 'Comment';\n}\n\nexport interface FormatReviewOptions {\n verdict: ReviewVerdict;\n summary: string;\n}\n\nexport function formatReview(doc: PlanDocument, opts: FormatReviewOptions): string {\n const commentedSectionIds = new Set(doc.comments.map((c) => c.sectionId));\n const reviewableSections = doc.sections.filter((s) =>\n doc.mode === 'plan' ? s.level === 3 : s.level >= 2,\n );\n const commentedSections = reviewableSections.filter((s) => commentedSectionIds.has(s.id));\n\n const parts: string[] = [];\n\n parts.push(`# Plan Review: ${doc.title}`);\n parts.push('');\n parts.push('## Review Summary');\n parts.push(`- **Verdict:** ${verdictLabel(opts.verdict)}`);\n parts.push(`- **Sections reviewed:** ${commentedSections.length}/${reviewableSections.length}`);\n parts.push(`- **Comments:** ${doc.comments.length}`);\n const skippedCount = reviewableSections.length - commentedSections.length;\n parts.push(\n `- **Skipped:** ${skippedCount} section${skippedCount === 1 ? '' : 's'} without comments`,\n );\n\n if (opts.summary.trim() !== '') {\n parts.push('');\n parts.push('## Overall Comments');\n parts.push('');\n parts.push(escapeMarkdown(opts.summary));\n }\n\n if (commentedSections.length > 0) {\n parts.push('');\n parts.push('---');\n }\n\n for (const section of commentedSections) {\n const sectionComments = sortComments(\n doc.comments.filter((c) => c.sectionId === section.id),\n );\n\n parts.push('');\n parts.push(`## Section ${section.id}: ${section.heading}`);\n parts.push('');\n\n if (doc.mode === 'plan' && section.dependencies) {\n const deps = section.dependencies;\n if (deps.dependsOn.length > 0) {\n parts.push(`Depends on: ${deps.dependsOn.join(', ')}`);\n }\n if (deps.blocks.length > 0) {\n parts.push(`Blocks: ${deps.blocks.join(', ')}`);\n }\n parts.push('');\n }\n\n for (const comment of sectionComments) {\n if (comment.anchor) {\n parts.push('### Reviewer Comment');\n parts.push('');\n for (const line of comment.anchor.lineTexts) {\n parts.push(`> ${line}`);\n }\n parts.push('');\n parts.push(escapeMarkdown(comment.text));\n } else {\n parts.push('### Reviewer Comment (entire section)');\n parts.push('');\n parts.push(escapeMarkdown(comment.text));\n }\n parts.push('');\n parts.push('---');\n }\n }\n\n return parts.join('\\n');\n}\n", "import * as readline from 'node:readline';\nimport chalk from 'chalk';\nimport type { PlanDocument, ReviewComment, Section } from '@plan-review/core';\nimport { renderSection, renderToc } from './renderer.js';\n\nexport async function navigate(doc: PlanDocument, inputFromStdin: boolean = false, onCommentChange?: () => void): Promise<PlanDocument> {\n // When input was read from stdin, stdin is exhausted.\n // Open /dev/tty directly for interactive prompts.\n const ttyInput = inputFromStdin\n ? (await import('node:fs')).createReadStream('/dev/tty')\n : process.stdin;\n\n const rl = readline.createInterface({\n input: ttyInput,\n output: process.stderr,\n });\n\n const ask = (prompt: string): Promise<string> =>\n new Promise((resolve) => {\n rl.question(prompt, (answer) => resolve(answer.trim()));\n });\n\n const reviewableSections = getReviewableSections(doc);\n\n let running = true;\n\n while (running) {\n console.error(renderToc(doc));\n const input = await ask(\n chalk.cyan('> Enter section (e.g. 1.1), \\'all\\' for linear review, or \\'done\\' to finish: '),\n );\n\n if (input === 'done' || input === 'q') {\n running = false;\n } else if (input === 'all') {\n await linearReview(doc, reviewableSections, ask, onCommentChange);\n } else {\n const section = findSection(doc, input);\n if (section) {\n const startIdx = reviewableSections.indexOf(section);\n await linearReview(doc, reviewableSections.slice(startIdx), ask, onCommentChange);\n } else {\n console.error(chalk.red(`Section \"${input}\" not found. Try again.`));\n }\n }\n }\n\n rl.close();\n printSummary(doc);\n return doc;\n}\n\nasync function linearReview(\n doc: PlanDocument,\n sections: Section[],\n ask: (prompt: string) => Promise<string>,\n onCommentChange?: () => void,\n): Promise<void> {\n for (let i = 0; i < sections.length; i++) {\n const section = sections[i];\n console.error(renderSection(section));\n\n const input = await ask(\n chalk.cyan('> Comment (enter to skip, \\'toc\\' for menu, \\'back\\' for previous): '),\n );\n\n if (input === 'toc') {\n return;\n } else if (input === 'back') {\n i -= (i > 0) ? 2 : 1; // -2 to go back (loop increments), -1 to re-show current\n continue;\n } else if (input !== '') {\n doc.comments.push({\n sectionId: section.id,\n text: input,\n timestamp: new Date(),\n });\n onCommentChange?.();\n }\n }\n}\n\nexport function findSection(doc: PlanDocument, input: string): Section | undefined {\n // Try exact ID match first\n const byId = doc.sections.find((s) => s.id === input);\n if (byId) return byId;\n\n // Try numeric index for generic mode\n const num = parseInt(input, 10);\n if (!isNaN(num)) {\n const reviewable = getReviewableSections(doc);\n if (num >= 1 && num <= reviewable.length) {\n return reviewable[num - 1];\n }\n }\n\n return undefined;\n}\n\nexport function getReviewableSections(doc: PlanDocument): Section[] {\n return doc.sections.filter((s) =>\n doc.mode === 'plan' ? s.level === 3 : s.level >= 2,\n );\n}\n\nexport function printSummary(doc: PlanDocument): void {\n const reviewable = getReviewableSections(doc);\n const commentedIds = new Set(doc.comments.map((c) => c.sectionId));\n\n console.error('');\n console.error(chalk.bold('Review Summary'));\n console.error(` Sections: ${reviewable.length}`);\n console.error(` Commented: ${chalk.green(String(commentedIds.size))}`);\n console.error(` Skipped: ${chalk.dim(String(reviewable.length - commentedIds.size))}`);\n console.error(` Total comments: ${doc.comments.length}`);\n console.error('');\n}\n", "import { marked } from 'marked';\nimport { markedTerminal } from 'marked-terminal';\nimport chalk from 'chalk';\nimport type { Section, PlanDocument } from '@plan-review/core';\n\nmarked.use(markedTerminal());\n\nexport function renderSection(section: Section): string {\n const parts: string[] = [];\n\n if (section.level === 3 && section.dependencies) {\n parts.push(renderMetadataHeader(section));\n parts.push('');\n }\n\n const heading = `${'#'.repeat(section.level)} ${section.heading}`;\n const body = section.body || '';\n const markdown = `${heading}\\n\\n${body}`;\n parts.push(marked.parse(markdown) as string);\n\n return parts.join('\\n');\n}\n\nfunction renderMetadataHeader(section: Section): string {\n const deps = section.dependencies!;\n const dependsOn = deps.dependsOn.length > 0 ? deps.dependsOn.join(', ') : '(none)';\n const blocks = deps.blocks.length > 0 ? deps.blocks.join(', ') : '(none)';\n\n const lines: string[] = [\n `Task ${section.id}: ${section.heading}`,\n `\u2190 Depends on: ${dependsOn}`,\n `\u2192 Blocks: ${blocks}`,\n ];\n\n if (section.relatedFiles && section.relatedFiles.length > 0) {\n const fileList =\n section.relatedFiles.length <= 2\n ? section.relatedFiles.join(', ')\n : `${section.relatedFiles[0]} (+${section.relatedFiles.length - 1} more)`;\n lines.push(`Files: ${fileList}`);\n }\n\n if (section.verification) {\n lines.push(`Verify: ${section.verification}`);\n }\n\n const maxLen = Math.max(...lines.map((l) => l.length));\n const width = Math.min(maxLen + 4, process.stdout.columns || 80);\n const innerWidth = width - 2;\n\n const top = chalk.dim(`\u250C${'\u2500'.repeat(innerWidth)}\u2510`);\n const bottom = chalk.dim(`\u2514${'\u2500'.repeat(innerWidth)}\u2518`);\n const content = lines.map(\n (l) => chalk.dim('\u2502') + ' ' + chalk.cyan(l.slice(0, innerWidth - 2).padEnd(innerWidth - 2)) + ' ' + chalk.dim('\u2502'),\n );\n\n return [top, ...content, bottom].join('\\n');\n}\n\nexport function renderToc(doc: PlanDocument): string {\n const parts: string[] = [];\n const commentedIds = new Set(doc.comments.map((c) => c.sectionId));\n\n parts.push('');\n parts.push(chalk.bold.underline(doc.title));\n parts.push('');\n\n if (doc.mode === 'plan') {\n for (const section of doc.sections) {\n if (section.level === 2) {\n parts.push(chalk.bold.yellow(` ${section.heading}`));\n } else if (section.level === 3) {\n const marker = commentedIds.has(section.id) ? chalk.green('\u2713') : ' ';\n parts.push(` ${marker} ${chalk.dim(section.id)} ${section.heading}`);\n }\n }\n } else {\n const reviewable = doc.sections.filter((s) => s.level >= 2);\n for (let i = 0; i < reviewable.length; i++) {\n const section = reviewable[i];\n const num = String(i + 1).padStart(2);\n const marker = commentedIds.has(section.id) ? chalk.green('\u2713') : ' ';\n parts.push(` ${marker} ${chalk.dim(num)} ${section.heading}`);\n }\n }\n\n const commentedCount = commentedIds.size;\n const reviewable = doc.sections.filter((s) =>\n doc.mode === 'plan' ? s.level === 3 : s.level >= 2,\n );\n const remaining = reviewable.length - commentedCount;\n\n parts.push('');\n parts.push(\n ` ${chalk.green(`${commentedCount} section${commentedCount !== 1 ? 's' : ''} commented`)}` +\n ` ${chalk.dim(`${remaining} remaining`)}`,\n );\n parts.push('');\n\n return parts.join('\\n');\n}\n", "import { execSync, spawn } from 'node:child_process';\nimport { writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport chalk from 'chalk';\nimport type { OutputTarget } from '@plan-review/core';\n\nexport function writeOutput(\n content: string,\n target: OutputTarget,\n options: { outputFile?: string; inputFile?: string } = {},\n): void {\n switch (target) {\n case 'stdout':\n process.stdout.write(content + '\\n');\n break;\n case 'clipboard':\n writeToClipboard(content);\n break;\n case 'file':\n writeToFile(content, options.outputFile, options.inputFile);\n break;\n case 'claude':\n sendToClaude(content);\n break;\n }\n}\n\nfunction writeToClipboard(content: string): void {\n const cmd = getClipboardCommand(process.platform);\n if (!cmd) {\n console.error(chalk.yellow('Clipboard not supported on this platform. Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n return;\n }\n\n try {\n execSync(cmd, { input: content, stdio: ['pipe', 'ignore', 'ignore'] });\n console.error(chalk.green('Review copied to clipboard.'));\n } catch {\n console.error(chalk.yellow('Failed to copy to clipboard. Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n }\n}\n\nfunction writeToFile(content: string, outputFile?: string, inputFile?: string): void {\n const filePath = outputFile\n ? resolve(outputFile)\n : inputFile\n ? resolve(inputFile.replace(/\\.md$/, '.review.md'))\n : resolve('review.md');\n\n try {\n writeFileSync(filePath, content, 'utf-8');\n console.error(chalk.green(`Review written to ${filePath}`));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(chalk.red(`Failed to write file: ${msg}`));\n console.error(chalk.yellow('Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n }\n}\n\nfunction sendToClaude(content: string): void {\n if (!isClaudeAvailable()) {\n console.error(\n chalk.red('Claude CLI not found in PATH.'),\n );\n console.error(chalk.dim('Install: https://docs.anthropic.com/en/docs/claude-code'));\n console.error(chalk.yellow('Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n return;\n }\n\n const child = spawn('claude', [], {\n stdio: ['pipe', 'inherit', 'inherit'],\n });\n child.stdin.write(content);\n child.stdin.end();\n child.on('error', (err) => {\n console.error(chalk.yellow(`Failed to pipe to claude: ${err.message}. Falling back to stdout.`));\n process.stdout.write(content + '\\n');\n });\n}\n\nexport function getClipboardCommand(platform: string): string | null {\n switch (platform) {\n case 'darwin':\n return 'pbcopy';\n case 'linux':\n return 'xclip -selection clipboard';\n case 'win32':\n return 'clip';\n default:\n return null;\n }\n}\n\nexport function isClaudeAvailable(): boolean {\n try {\n execSync('which claude', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n", "import * as readline from 'node:readline';\nimport chalk from 'chalk';\nimport type { OutputTarget } from '@plan-review/core';\n\n// When stdin is the piped plan, readline can't reuse it for prompts \u2014 open\n// /dev/tty directly so the terminal still answers keystrokes.\nasync function ttyInputStream(inputFromStdin: boolean): Promise<NodeJS.ReadableStream> {\n return inputFromStdin\n ? (await import('node:fs')).createReadStream('/dev/tty')\n : process.stdin;\n}\n\nexport async function promptOutputTarget(inputFromStdin: boolean): Promise<OutputTarget> {\n const rl = readline.createInterface({\n input: await ttyInputStream(inputFromStdin),\n output: process.stderr,\n });\n\n const answer = await new Promise<string>((resolve) => {\n rl.question(\n chalk.cyan('> Output: (s)tdout, (c)lipboard, (f)ile, cl(a)ude? '),\n (a) => resolve(a.trim().toLowerCase()),\n );\n });\n rl.close();\n\n switch (answer) {\n case 's': case 'stdout': return 'stdout';\n case 'c': case 'clipboard': return 'clipboard';\n case 'f': case 'file': return 'file';\n case 'a': case 'claude': return 'claude';\n default: return 'stdout';\n }\n}\n\nexport async function promptYesNo(message: string, inputFromStdin: boolean): Promise<boolean> {\n const rl = readline.createInterface({\n input: await ttyInputStream(inputFromStdin),\n output: process.stderr,\n });\n\n const answer = await new Promise<string>((resolve) => {\n rl.question(chalk.yellow(`${message} (y/n) `), (a) => resolve(a.trim().toLowerCase()));\n });\n rl.close();\n\n return answer === 'y' || answer === 'yes';\n}\n", "import { spawnSync } from 'node:child_process';\nimport { dirname as dirnamePath } from 'node:path';\nimport type { PlanDocument } from '@plan-review/core';\nimport { saveSession } from '@plan-review/core';\nimport { HttpTransport } from './transport.js';\nimport type { ReviewSubmission } from './transport.js';\n\nexport interface BrowserReviewOptions {\n doc: PlanDocument;\n absPath: string | null;\n contentHash: string;\n restoredActiveSection: string | null;\n}\n\nconst IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes \u2014 overall ceiling\nconst HEARTBEAT_TIMEOUT_MS = 30 * 1000; // 30s without a heartbeat while visible = browser gone\n\n// Boot an HttpTransport, open the URL in the user's default browser, and\n// resolve with the reviewed comments when the browser posts them. Rejects if\n// the user cancels, closes the tab, or the overall idle ceiling fires.\nexport async function runBrowserReview(\n { doc, absPath, contentHash, restoredActiveSection }: BrowserReviewOptions,\n): Promise<ReviewSubmission> {\n const transport = new HttpTransport();\n transport.sendDocument(doc);\n transport.setInitialActiveSection(restoredActiveSection);\n // Plan-file directory anchors relative image paths via /_assets/<rel>.\n transport.setAssetBaseDir(absPath ? dirnamePath(absPath) : null);\n\n if (absPath) {\n transport.onSessionSave((comments, activeSection) => {\n saveSession(absPath, contentHash, comments, activeSection);\n });\n }\n\n const reviewPromise = new Promise<ReviewSubmission>((resolve, reject) => {\n const idleTimer = setTimeout(\n () => reject(new Error('Browser review timed out after 30 minutes of inactivity')),\n IDLE_TIMEOUT_MS,\n );\n let heartbeatTimer: NodeJS.Timeout | null = null;\n const armHeartbeat = (): void => {\n if (heartbeatTimer) clearTimeout(heartbeatTimer);\n heartbeatTimer = setTimeout(() => {\n clearTimeout(idleTimer);\n reject(new Error('Review cancelled: browser closed (heartbeat lost)'));\n }, HEARTBEAT_TIMEOUT_MS);\n };\n const clearAll = (): void => {\n clearTimeout(idleTimer);\n if (heartbeatTimer) clearTimeout(heartbeatTimer);\n heartbeatTimer = null;\n };\n transport.onHeartbeat(armHeartbeat);\n transport.onPause(() => {\n if (heartbeatTimer) clearTimeout(heartbeatTimer);\n heartbeatTimer = null;\n });\n transport.onCancel(() => {\n clearAll();\n reject(new Error('Review cancelled: browser closed'));\n });\n transport.onReviewSubmit((submission) => {\n clearAll();\n resolve(submission);\n });\n });\n\n const { url } = await transport.start(0);\n process.stderr.write(`Review server running at ${url}\\n`);\n\n try {\n const openCmd = process.platform === 'darwin' ? 'open'\n : process.platform === 'win32' ? 'start'\n : 'xdg-open';\n spawnSync(openCmd, [url], { stdio: 'ignore' });\n } catch {\n process.stderr.write(`Open ${url} in your browser\\n`);\n }\n\n try {\n return await reviewPromise;\n } finally {\n await transport.stop();\n }\n}\n", "import { createServer, type Server } from 'node:http';\nimport { createRouteHandler, type RouteContext } from './routes.js';\n\nexport function createReviewServer(ctx: RouteContext): Server {\n return createServer(createRouteHandler(ctx));\n}\n\nexport function startServer(server: Server, port: number): Promise<{ url: string }> {\n return new Promise((resolve, reject) => {\n server.on('error', reject);\n server.listen(port, () => {\n const addr = server.address();\n const actualPort = typeof addr === 'object' && addr ? addr.port : port;\n resolve({ url: `http://localhost:${actualPort}` });\n });\n });\n}\n\nexport function stopServer(server: Server): Promise<void> {\n return new Promise((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n // Force-close keep-alive sockets so close() doesn't hang on an idle browser tab.\n server.closeAllConnections();\n });\n}\n", "import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { readFile } from 'node:fs';\nimport { join, normalize, resolve as resolvePath, sep, extname } from 'node:path';\nimport type { PlanDocument, ReviewComment, ReviewSubmission } from '@plan-review/core';\n\nconst MAX_BODY_SIZE = 1024 * 1024; // 1MB\n\nconst MIME_BY_EXT: Record<string, string> = {\n '.gif': 'image/gif',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.svg': 'image/svg+xml',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.ico': 'image/x-icon',\n};\n\nexport interface RouteContext {\n getDocument: () => PlanDocument;\n getInitialActiveSection?: () => string | null;\n getAssetBaseDir?: () => string | null;\n onSubmit: (submission: ReviewSubmission) => void;\n getAssetHtml: () => string;\n onSessionSave?: (comments: ReviewComment[], activeSection: string | null) => void;\n onHeartbeat?: () => void;\n onPause?: () => void;\n onCancel?: () => void;\n}\n\nfunction validateComment(obj: unknown): obj is ReviewComment {\n if (typeof obj !== 'object' || obj === null) return false;\n const c = obj as Record<string, unknown>;\n return typeof c.sectionId === 'string' && typeof c.text === 'string';\n}\n\nfunction validateVerdict(value: unknown): value is ReviewSubmission['verdict'] {\n return value === 'approved' || value === null;\n}\n\nexport function createRouteHandler(ctx: RouteContext): (req: IncomingMessage, res: ServerResponse) => void {\n return (req, res) => {\n const { method, url } = req;\n\n if (method === 'GET' && url === '/') {\n const html = ctx.getAssetHtml();\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(html);\n return;\n }\n\n if (method === 'GET' && url === '/api/doc') {\n const doc = ctx.getDocument();\n const initialState = { activeSection: ctx.getInitialActiveSection?.() ?? null };\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ document: doc, initialState }));\n return;\n }\n\n if (method === 'POST' && url === '/api/review') {\n let body = '';\n let size = 0;\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_SIZE) {\n res.writeHead(413, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Request body too large' }));\n req.destroy();\n return;\n }\n body += chunk.toString();\n });\n req.on('end', () => {\n if (size > MAX_BODY_SIZE) return;\n try {\n const parsed = JSON.parse(body);\n const comments = parsed.comments;\n const verdict = parsed.verdict;\n const summary = parsed.summary;\n if (!Array.isArray(comments)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'comments must be an array' }));\n return;\n }\n if (!validateVerdict(verdict)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'verdict must be \"approved\" or null' }));\n return;\n }\n if (typeof summary !== 'string') {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'summary must be a string' }));\n return;\n }\n for (const c of comments) {\n if (!validateComment(c)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Each comment must have sectionId (string) and text (string)' }));\n return;\n }\n }\n ctx.onSubmit({ comments: comments as ReviewComment[], verdict, summary });\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ success: true }));\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n }\n });\n return;\n }\n\n if (method === 'PUT' && url === '/api/session') {\n let body = '';\n let size = 0;\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_SIZE) {\n res.writeHead(413, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Request body too large' }));\n req.destroy();\n return;\n }\n body += chunk.toString();\n });\n req.on('end', () => {\n if (size > MAX_BODY_SIZE) return;\n try {\n const parsed = JSON.parse(body);\n const comments = parsed.comments;\n if (!Array.isArray(comments)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'comments must be an array' }));\n return;\n }\n for (const c of comments) {\n if (!validateComment(c)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Each comment must have sectionId (string) and text (string)' }));\n return;\n }\n }\n const activeSection = typeof parsed.activeSection === 'string' ? parsed.activeSection : null;\n ctx.onSessionSave?.(comments as ReviewComment[], activeSection);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ success: true }));\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n }\n });\n return;\n }\n\n if (method === 'POST' && url === '/api/heartbeat') {\n ctx.onHeartbeat?.();\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (method === 'POST' && url === '/api/pause') {\n ctx.onPause?.();\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (method === 'POST' && url === '/api/cancel') {\n ctx.onCancel?.();\n res.writeHead(204);\n res.end();\n return;\n }\n\n // Static asset proxy: /_assets/<rel-path> serves files from the plan\n // file's directory. Only image extensions are allowed; path traversal\n // (e.g. ../etc/passwd) is rejected. Inline plans set baseDir to null and\n // get a 404 \u2014 there is no on-disk anchor to resolve against.\n if (method === 'GET' && url && url.startsWith('/_assets/')) {\n const baseDir = ctx.getAssetBaseDir?.();\n if (!baseDir) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('No asset base directory');\n return;\n }\n let rel: string;\n try {\n rel = decodeURIComponent(url.slice('/_assets/'.length).split('?')[0].split('#')[0]);\n } catch {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Bad request');\n return;\n }\n const ext = extname(rel).toLowerCase();\n if (!MIME_BY_EXT[ext]) {\n res.writeHead(415, { 'Content-Type': 'text/plain' });\n res.end('Unsupported media type');\n return;\n }\n const normalized = normalize(rel);\n const resolvedBase = resolvePath(baseDir);\n const resolvedFile = resolvePath(join(resolvedBase, normalized));\n if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {\n res.writeHead(403, { 'Content-Type': 'text/plain' });\n res.end('Forbidden');\n return;\n }\n readFile(resolvedFile, (err, buf) => {\n if (err) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n return;\n }\n res.writeHead(200, {\n 'Content-Type': MIME_BY_EXT[ext],\n 'Content-Length': buf.length,\n 'Cache-Control': 'no-store',\n });\n res.end(buf);\n });\n return;\n }\n\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n };\n}\n", "import { readFileSync, existsSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction resolveHtmlPath(): string {\n const candidates = [\n join(__dirname, 'browser', 'index.html'),\n join(__dirname, '..', 'browser', 'index.html'),\n join(__dirname, '..', 'dist', 'browser', 'index.html'),\n ];\n for (const p of candidates) {\n if (existsSync(p)) return p;\n }\n throw new Error(`Browser HTML not found. Run 'npm run build' first.\\nLooked in:\\n ${candidates.join('\\n ')}`);\n}\n\nlet cached: string | null = null;\n\nexport function getAssetHtml(): string {\n if (!cached) {\n cached = readFileSync(resolveHtmlPath(), 'utf-8');\n }\n return cached;\n}\n", "import type { Server } from 'node:http';\nimport type { PlanDocument, ReviewComment, ReviewSubmission } from '@plan-review/core';\nimport { createReviewServer, startServer, stopServer } from './server/server.js';\nimport { getAssetHtml } from './server/assets.js';\n\nexport type { ReviewSubmission, ReviewVerdict } from '@plan-review/core';\n\nexport interface Transport {\n sendDocument(doc: PlanDocument): void;\n onReviewSubmit(handler: (submission: ReviewSubmission) => void): void;\n start(port: number): Promise<{ url: string }>;\n stop(): Promise<void>;\n}\n\nexport class HttpTransport implements Transport {\n private doc: PlanDocument | null = null;\n private initialActiveSection: string | null = null;\n private assetBaseDir: string | null = null;\n private submitHandler: ((submission: ReviewSubmission) => void) | null = null;\n private sessionSaveHandler: ((comments: ReviewComment[], activeSection: string | null) => void) | null = null;\n private heartbeatHandler: (() => void) | null = null;\n private pauseHandler: (() => void) | null = null;\n private cancelHandler: (() => void) | null = null;\n private server: Server | null = null;\n\n sendDocument(doc: PlanDocument): void {\n this.doc = doc;\n }\n\n setInitialActiveSection(section: string | null): void {\n this.initialActiveSection = section;\n }\n\n // Plan-file directory used to serve relative images via /_assets/<rel>.\n // Null for inline plans where there's no on-disk anchor.\n setAssetBaseDir(dir: string | null): void {\n this.assetBaseDir = dir;\n }\n\n onReviewSubmit(handler: (submission: ReviewSubmission) => void): void {\n this.submitHandler = handler;\n }\n\n onSessionSave(handler: (comments: ReviewComment[], activeSection: string | null) => void): void {\n this.sessionSaveHandler = handler;\n }\n\n onHeartbeat(handler: () => void): void {\n this.heartbeatHandler = handler;\n }\n\n onPause(handler: () => void): void {\n this.pauseHandler = handler;\n }\n\n onCancel(handler: () => void): void {\n this.cancelHandler = handler;\n }\n\n async start(port: number): Promise<{ url: string }> {\n if (!this.doc) throw new Error('No document set');\n\n this.server = createReviewServer({\n getDocument: () => this.doc!,\n getInitialActiveSection: () => this.initialActiveSection,\n getAssetBaseDir: () => this.assetBaseDir,\n onSubmit: (submission) => this.submitHandler?.(submission),\n getAssetHtml: () => getAssetHtml(),\n onSessionSave: (comments, activeSection) => this.sessionSaveHandler?.(comments, activeSection),\n onHeartbeat: () => this.heartbeatHandler?.(),\n onPause: () => this.pauseHandler?.(),\n onCancel: () => this.cancelHandler?.(),\n });\n\n return startServer(this.server, port);\n }\n\n async stop(): Promise<void> {\n if (this.server && this.server.listening) {\n await stopServer(this.server);\n this.server = null;\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["Command", "readFileSync", "mkdirSync", "copyFileSync", "existsSync", "homedir", "dirname", "join", "resolvePath", "fileURLToPath", "chalk", "parse", "input", "strategy", "lines", "title", "extractTitle", "metadata", "extractMetadata", "isPlanDocument", "parsePlan", "parseGeneric", "parseBySeparator", "h1", "l", "meta", "line", "match", "stripped", "hasH2H3Hierarchy", "hasPlanFields", "sections", "splitByHeadings", "s", "i", "currentHeading", "currentLevel", "currentBody", "h2Count", "h3Count", "splitLevel", "headingRegex", "inCodeBlock", "parts", "p", "firstLine", "milestoneIndex", "taskIndex", "currentMilestoneId", "flushSection", "body", "id", "extractDependencies", "extractRelatedFiles", "extractVerification", "h2Match", "h3Match", "dependsMatch", "blocksMatch", "parseList", "raw", "trimmed", "files", "inRelatedFiles", "fileMatch", "suffix", "createHash", "mkdirSync", "readFileSync", "writeFileSync", "unlinkSync", "readdirSync", "existsSync", "join", "resolve", "homedir", "pathHash", "planPath", "abs", "sessionFilePath", "getSessionDir", "dir", "computeContentHash", "content", "saveSession", "contentHash", "comments", "activeSection", "data", "filePath", "err", "loadSession", "currentContentHash", "raw", "c", "clearSession", "listSessions", "files", "results", "file", "stale", "currentContent", "escapeMarkdown", "text", "sortComments", "comments", "a", "b", "aLine", "bLine", "verdictLabel", "verdict", "formatReview", "doc", "opts", "commentedSectionIds", "c", "reviewableSections", "s", "commentedSections", "parts", "skippedCount", "section", "sectionComments", "deps", "comment", "line", "readline", "chalk", "marked", "markedTerminal", "chalk", "renderSection", "section", "parts", "renderMetadataHeader", "heading", "body", "markdown", "deps", "dependsOn", "blocks", "lines", "fileList", "maxLen", "l", "innerWidth", "top", "bottom", "content", "renderToc", "doc", "commentedIds", "c", "marker", "reviewable", "s", "i", "num", "commentedCount", "remaining", "navigate", "doc", "inputFromStdin", "onCommentChange", "ttyInput", "rl", "ask", "prompt", "resolve", "answer", "reviewableSections", "getReviewableSections", "running", "renderToc", "input", "chalk", "linearReview", "section", "findSection", "startIdx", "printSummary", "sections", "i", "renderSection", "byId", "s", "num", "reviewable", "commentedIds", "c", "execSync", "spawn", "writeFileSync", "resolve", "chalk", "writeOutput", "content", "target", "options", "writeToClipboard", "writeToFile", "sendToClaude", "cmd", "getClipboardCommand", "outputFile", "inputFile", "filePath", "err", "msg", "isClaudeAvailable", "child", "platform", "createRequire", "readline", "chalk", "ttyInputStream", "inputFromStdin", "promptOutputTarget", "rl", "answer", "resolve", "a", "promptYesNo", "message", "spawnSync", "dirnamePath", "createServer", "readFile", "join", "normalize", "resolvePath", "sep", "extname", "MAX_BODY_SIZE", "MIME_BY_EXT", "validateComment", "obj", "c", "validateVerdict", "value", "createRouteHandler", "ctx", "req", "res", "method", "url", "html", "doc", "initialState", "body", "size", "chunk", "parsed", "comments", "verdict", "summary", "activeSection", "baseDir", "rel", "ext", "normalized", "resolvedBase", "resolvedFile", "err", "buf", "createReviewServer", "ctx", "createServer", "createRouteHandler", "startServer", "server", "port", "resolve", "reject", "addr", "actualPort", "stopServer", "err", "readFileSync", "existsSync", "join", "dirname", "fileURLToPath", "__dirname", "resolveHtmlPath", "candidates", "p", "cached", "getAssetHtml", "HttpTransport", "doc", "section", "dir", "handler", "port", "createReviewServer", "submission", "getAssetHtml", "comments", "activeSection", "startServer", "stopServer", "IDLE_TIMEOUT_MS", "HEARTBEAT_TIMEOUT_MS", "runBrowserReview", "doc", "absPath", "contentHash", "restoredActiveSection", "transport", "HttpTransport", "dirnamePath", "comments", "activeSection", "saveSession", "reviewPromise", "resolve", "reject", "idleTimer", "heartbeatTimer", "armHeartbeat", "clearAll", "submission", "url", "openCmd", "spawnSync", "require", "createRequire", "version", "program", "Command", "file", "opts", "run", "err", "cancelled", "chalk", "__dirname", "dirname", "fileURLToPath", "src", "join", "existsSync", "dest", "homedir", "mkdirSync", "copyFileSync", "sessions", "listSessions", "dir", "getSessionDir", "s", "age", "formatRelativeTime", "status", "validTargets", "explicitTarget", "isClaudeAvailable", "inputFromStdin", "input", "readInput", "splitStrategy", "doc", "parse", "absPath", "resolvePath", "contentHash", "computeContentHash", "restoredActiveSection", "clearSession", "session", "loadSession", "promptYesNo", "reviewed", "reviewMeta", "navigate", "saveSession", "submission", "runBrowserReview", "outputTarget", "promptOutputTarget", "output", "formatReview", "writeOutput", "readFileSync", "iso", "ms", "seconds", "minutes", "hours", "days"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plan-review",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Interactive CLI for reviewing AI-generated markdown plans",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",