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