md-feedback 1.3.0 → 1.3.3

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.
Files changed (2) hide show
  1. package/dist/mcp-server.js +4 -4
  2. package/package.json +73 -73
@@ -47,7 +47,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
47
47
  `);let a=i.filter(l=>l.type==="fix"),c=i.filter(l=>l.type==="question"),u=i.filter(l=>l.type==="important");switch(s){case"claude-code":case"codex":case"copilot":case"cline":case"windsurf":case"roo-code":case"gemini":case"antigravity":return DT(t,e,r,a,c,u);case"cursor":return qT(t,e,r,a,c,u);case"generic":return LT(t,e,r,a,c,u);case"handoff":return"(Use Export > Handoff to generate handoff document)"}}function Fe(t){return t.replace(/&/g,"&").replace(/"/g,""").replace(/\n/g,"
").replace(/-->/g,"-->")}function FT(t){return t.replace(/-->/g,"-->").replace(/
/g,`
48
48
  `).replace(/&quot;/g,'"').replace(/&amp;/g,"&")}function kn(t){let e=5381;for(let r=0;r<t.length;r++)e=(e<<5)+e+t.charCodeAt(r)>>>0;return e.toString(16).padStart(8,"0").slice(0,8)}var UT=/^<!-- USER_MEMO\s+id="([^"]+)"(?:\s+color="([^"]+)")?(?:\s+status="([^"]+)")?\s*:\s*(.*?)\s*-->$/,VT=/^<!-- USER_MEMO\s*$/,HT=/^-->$/,BT=/^<!-- GATE\s*$/,JT=/^-->$/,GT=/^<!-- PLAN_CURSOR\s*$/,KT=/^-->$/,WT=/^<!-- CHECKPOINT\s+id="([^"]+)"\s+time="([^"]+)"\s+note="([^"]*)"\s+fixes=(\d+)\s+questions=(\d+)\s+highlights=(\d+)\s+sections="([^"]*)" -->$/,QT=/^---\s*\n([\s\S]*?)\n---\s*\n/,XT=/^<!-- @memo\s+id="([^"]+)"(?:\s+color="([^"]+)")?(?:\s+date="([^"]+)")?\s*-->$/,YT=/^<!-- @\/memo -->$/,eE=/^<!--$/,tE=/MD Feedback/,rE=/^<!-- \/?(USER_FEEDBACK_NOTES|@\/?feedback-notes)\b.*-->$/,nE=/^<!-- REVIEW_RESPONSE\s+to="([^"]+)"\s*-->$/,oE=/^<!-- \/REVIEW_RESPONSE\s*-->$/,sE=/^<!-- MEMO_IMPL\s*$/,iE=/^-->$/,aE=/^<!-- MEMO_ARTIFACT\s*$/,cE=/^-->$/,uE=/^<!-- MEMO_DEPENDENCY\s+id="([^"]+)"\s+from="([^"]+)"\s+to="([^"]+)"\s+type="([^"]+)" -->$/;function Co(t){let e={};for(let r of t){let n=r.trim().match(/^(\w+)="([^"]*)"$/);n&&(e[n[1]]=FT(n[2]))}return e}function Oe(t){let e="",r=t,n=r.match(QT);n&&(e=n[0],r=r.slice(n[0].length));let o=r.split(`
49
49
  `),s=[],i=[],a=[],c=[],u=[],l=[],d=[],h=[],f=null,m=null,p=0;for(;p<o.length;){let y=o[p],v=y.trim(),w=v.match(nE);if(w){m={id:`resp_${w[1]}`,to:w[1],bodyStartIdx:s.length,bodyEndIdx:-1},p++;continue}if(oE.test(v)){m&&(m.bodyEndIdx=s.length-1,a.push(m),m=null),p++;continue}let b=v.match(UT);if(b){let ee=md(s),A=ky(s),xe=b[2]||"red",Ot=b[3]||"open";i.push({id:b[1],type:Oi(xe),status:Ot,owner:"human",source:"generic",color:xe,text:b[4].replace(/--\u200B>/g,"-->"),anchorText:ee||"",anchor:A>=0?`L${A+1}|${kn(s[A]||"")}`:"",createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()}),p++;continue}if(VT.test(v)){let ee=[];for(p++;p<o.length&&!HT.test(o[p].trim());)ee.push(o[p]),p++;p++;let A=Co(ee),xe=A.anchorText||md(s)||"";i.push({id:A.id||`memo_${Date.now()}`,type:A.type||Oi(A.color||"red"),status:A.status||"open",owner:A.owner||"human",source:A.source||"generic",color:A.color||"red",text:A.text||"",anchorText:xe,anchor:A.anchor||"",createdAt:A.createdAt||new Date().toISOString(),updatedAt:A.updatedAt||new Date().toISOString()});continue}if(BT.test(v)){let ee=[];for(p++;p<o.length&&!JT.test(o[p].trim());)ee.push(o[p]),p++;p++;let A=Co(ee),xe=A.override;h.push({id:A.id||`gate_${Date.now()}`,type:A.type||"custom",status:A.status||"blocked",blockedBy:A.blockedBy?A.blockedBy.split(",").map(Ot=>Ot.trim()).filter(Boolean):[],canProceedIf:A.canProceedIf||"",doneDefinition:A.doneDefinition||"",...xe?{override:xe}:{}});continue}if(GT.test(v)){let ee=[];for(p++;p<o.length&&!KT.test(o[p].trim());)ee.push(o[p]),p++;p++;let A=Co(ee);f={taskId:A.taskId||"",step:A.step||"",nextAction:A.nextAction||"",lastSeenHash:A.lastSeenHash||"",updatedAt:A.updatedAt||new Date().toISOString()};continue}if(sE.test(v)){let ee=[];for(p++;p<o.length&&!iE.test(o[p].trim());)ee.push(o[p]),p++;p++;let A=Co(ee),xe=[];try{xe=JSON.parse(A.operations||"[]")}catch{}c.push({id:A.id||`impl_${Date.now().toString(36)}`,memoId:A.memoId||"",status:A.status||"applied",operations:xe,summary:A.summary||"",appliedAt:A.appliedAt||new Date().toISOString()});continue}if(aE.test(v)){let ee=[];for(p++;p<o.length&&!cE.test(o[p].trim());)ee.push(o[p]),p++;p++;let A=Co(ee);u.push({id:A.id||`art_${Date.now().toString(36)}`,memoId:A.memoId||"",files:A.files?A.files.split(",").map(xe=>xe.trim()).filter(Boolean):[],linkedAt:A.linkedAt||new Date().toISOString()});continue}let $=v.match(uE);if($){l.push({id:$[1],from:$[2],to:$[3],type:$[4]}),p++;continue}let F=v.match(WT);if(F){d.push({id:F[1],timestamp:F[2],note:F[3],fixes:parseInt(F[4],10),questions:parseInt(F[5],10),highlights:parseInt(F[6],10),sectionsReviewed:F[7]?F[7].split(","):[]}),p++;continue}let ae=v.match(XT);if(ae){let ee=[],A=md(s),xe=ky(s);for(p++;p<o.length&&!YT.test(o[p].trim());)ee.push(o[p]),p++;p++;let Ot=ee.map(Zo=>Zo.replace(/^<!--\s*/,"").replace(/\s*-->$/,"")).join(`
50
- `).trim();i.push({id:ae[1],type:Oi(ae[2]||"red"),status:"open",owner:"human",source:"generic",color:ae[2]||"red",text:Ot,anchorText:A||"",anchor:xe>=0?`L${xe+1}|${kn(s[xe]||"")}`:"",createdAt:ae[3]||new Date().toISOString(),updatedAt:ae[3]||new Date().toISOString()});continue}if(eE.test(v)&&p+1<o.length&&tE.test(o[p+1])){for(;p<o.length&&!o[p].includes("-->");)p++;p++;continue}if(rE.test(v)){p++;continue}s.push(y),p++}for(;s.length>0&&s[s.length-1].trim()==="";)s.pop();m&&(m.bodyEndIdx=s.length-1,a.push(m));let g=new Set(a.map(y=>y.to));for(let y of i)y.status==="open"&&g.has(y.id)&&(y.status="answered");return{frontmatter:e,body:s.join(`
50
+ `).trim();i.push({id:ae[1],type:Oi(ae[2]||"red"),status:"open",owner:"human",source:"generic",color:ae[2]||"red",text:Ot,anchorText:A||"",anchor:xe>=0?`L${xe+1}|${kn(s[xe]||"")}`:"",createdAt:ae[3]||new Date().toISOString(),updatedAt:ae[3]||new Date().toISOString()});continue}if(eE.test(v)&&p+1<o.length&&tE.test(o[p+1])){for(;p<o.length&&!o[p].includes("-->");)p++;p++;continue}if(rE.test(v)){p++;continue}s.push(y),p++}for(;s.length>0&&s[s.length-1].trim()==="";)s.pop();m&&(m.bodyEndIdx=s.length-1,a.push(m));let g=new Set(a.map(y=>y.to));for(let y of i)y.status==="open"&&g.has(y.id)&&(y.status="needs_review");return{frontmatter:e,body:s.join(`
51
51
  `),memos:i,responses:a,impls:c,artifacts:u,dependencies:l,checkpoints:d,gates:h,cursor:f}}function Rt(t){let e=[];t.frontmatter&&e.push(t.frontmatter.trimEnd());let r=gE(t.body,t.memos,t.responses||[]);e.push(r);for(let n of t.impls||[])e.push(fE(n));for(let n of t.artifacts||[])e.push(mE(n));for(let n of t.dependencies||[])e.push(hE(n));for(let n of t.gates)e.push(lE(n));for(let n of t.checkpoints)e.push(pE(n));return t.cursor&&e.push(dE(t.cursor)),e.join(`
52
52
 
53
53
  `)+`
@@ -70,11 +70,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
70
70
  `),s="";for(let i=0;i<o.length;i++){let a=o[i],c=a.match(/^## (.+)/);if(c){s=c[1].trim();continue}let u=a.match(/<!-- USER_MEMO\s+id="[^"]+"\s+(.*?)\s*:\s*(.*?)\s*-->/);if(u){let h=u[1].match(/color="([^"]+)"/),f=h?h[1]:"red",m=u[2].replace(/--\u200B>/g,"-->"),p="";for(let y=i-1;y>=0;y--)if(o[y].trim()&&!o[y].includes("<!-- ")){p=o[y].trim(),p=p.replace(/<\/?mark[^>]*>/g,"");break}let g={section:s,text:p,feedback:m};f==="red"?e.push(g):f==="blue"?r.push(g):n.push(g);continue}if(/^<!-- USER_MEMO\s*$/.test(a.trim())){let d=[];for(i++;i<o.length&&!/^-->$/.test(o[i].trim());)d.push(o[i]),i++;let h={};for(let y of d){let v=y.trim().match(/^(\w+)="([^"]*)"$/);v&&(h[v[1]]=v[2])}let f=h.color||"red",m=(h.text||"").replace(/--\u200B>/g,"-->"),p="";if(h.anchorText)p=h.anchorText.replace(/<\/?mark[^>]*>/g,"");else for(let y=i-1;y>=0;y--)if(o[y].trim()&&!o[y].includes("<!-- ")){p=o[y].trim().replace(/<\/?mark[^>]*>/g,"");break}let g={section:s,text:p,feedback:m};f==="red"?e.push(g):f==="blue"?r.push(g):n.push(g);continue}let l=a.match(/<mark[^>]*(?:data-color="([^"]+)"|style="background-color:\s*([^"]+)")?\s*>(.*?)<\/mark>/)||a.match(/==(.*?)==/);if(l){if((i+1<o.length?o[i+1]:"").includes("<!-- USER_MEMO"))continue;let h,f;if(l.length===2)h=l[1],f="yellow";else{let p=(l[1]||l[2]||"").trim();f=wn[p]||"yellow",h=l[3]}let m={section:s,text:h,feedback:""};f==="red"?e.push(m):f==="blue"?r.push(m):n.push(m)}}}function No(t,e){return t.length>e?t.slice(0,e)+"...":t}function vd(t,e="standalone"){let r=[];if(e==="standalone"?(r.push(`# HANDOFF \u2014 \`${t.meta.file}\``),r.push("")):e==="claude-md"?(r.push(`## Session Handoff: ${t.meta.file}`),r.push("")):(r.push("---"),r.push(`description: Session handoff for ${t.meta.file}`),r.push("alwaysApply: true"),r.push("---"),r.push("")),r.push("## Session"),r.push(`- **File**: \`${t.meta.file}\``),r.push(`- **Started**: ${t.meta.startedAt}`),t.meta.lastCheckpoint&&r.push(`- **Last checkpoint**: ${t.meta.lastCheckpoint}`),r.push(`- **Checkpoints**: ${t.meta.checkpointCount}`),r.push(`- **Annotations**: ${t.meta.totalFixes} fix, ${t.meta.totalQuestions} question, ${t.meta.totalHighlights} highlight`),r.push(""),t.decisions.length>0){r.push(`## Decisions Made (${t.decisions.length})`),r.push("Marked as FIX. Decided \u2014 implement as specified.");for(let n=0;n<t.decisions.length;n++){let o=t.decisions[n],s=o.section?`[${o.section}]`:"[General]";o.text&&o.feedback?r.push(`${n+1}. **${s}** "${No(o.text,60)}" \u2192 ${o.feedback}`):o.feedback?r.push(`${n+1}. **${s}** ${o.feedback}`):o.text&&r.push(`${n+1}. **${s}** "${No(o.text,80)}"`)}r.push("")}if(t.openQuestions.length>0){r.push(`## Open Questions (${t.openQuestions.length})`),r.push("Marked as QUESTION. Unresolved \u2014 investigate before implementing.");for(let n=0;n<t.openQuestions.length;n++){let o=t.openQuestions[n],s=o.section?`[${o.section}]`:"[General]";o.text&&o.feedback?r.push(`${n+1}. **${s}** "${No(o.text,60)}" \u2014 ${o.feedback}`):o.feedback?r.push(`${n+1}. **${s}** ${o.feedback}`):o.text&&r.push(`${n+1}. **${s}** "${No(o.text,80)}"`)}r.push("")}if(t.keyPoints.length>0){r.push(`## Key Points (${t.keyPoints.length})`),r.push("Marked as HIGHLIGHT. Important context \u2014 preserve during implementation.");for(let n=0;n<t.keyPoints.length;n++){let o=t.keyPoints[n],s=o.section?`[${o.section}]`:"[General]";o.text&&r.push(`${n+1}. **${s}** "${No(o.text,80)}"`),o.feedback&&r.push(` ${o.feedback}`)}r.push("")}if(t.checkpoints.length>0){r.push("## Progress Checkpoints"),r.push("| # | Time | Note | Fixes | Questions | Highlights |"),r.push("|---|------|------|-------|-----------|------------|");for(let n=0;n<t.checkpoints.length;n++){let o=t.checkpoints[n],s=o.timestamp.split("T")[1]?.split(".")[0]||o.timestamp;r.push(`| ${n+1} | ${s} | ${o.note} | ${o.fixes} | ${o.questions} | ${o.highlights} |`)}r.push("")}if(t.nextSteps.length>0){r.push("## Next Steps");for(let n of t.nextSteps)r.push(`- [ ] ${n}`);r.push("")}return r.push("---"),r.push("*Generated by md-feedback. Feed this to your AI coding agent.*"),r.join(`
71
71
  `)}function Ty(t){let e=t.split(`
72
72
  `),r="",n={file:"",startedAt:"",lastCheckpoint:"",checkpointCount:0,totalFixes:0,totalQuestions:0,totalHighlights:0},o=[],s=[],i=[],a=[],c=[];for(let u of e){let l=u.match(/^## (.+)/);if(l){r=l[1].trim();continue}if(r.startsWith("Session")){let h=u.match(/\*\*File\*\*:\s*`([^`]+)`/);h&&(n.file=h[1]);let f=u.match(/\*\*Started\*\*:\s*(.+)/);f&&(n.startedAt=f[1].trim());let m=u.match(/\*\*Last checkpoint\*\*:\s*(.+)/);m&&(n.lastCheckpoint=m[1].trim());let p=u.match(/\*\*Checkpoints\*\*:\s*(\d+)/);p&&(n.checkpointCount=parseInt(p[1],10));let g=u.match(/\*\*Annotations\*\*:\s*(\d+)\s*fix,\s*(\d+)\s*question,\s*(\d+)\s*highlight/);g&&(n.totalFixes=parseInt(g[1],10),n.totalQuestions=parseInt(g[2],10),n.totalHighlights=parseInt(g[3],10))}let d=u.match(/^\d+\.\s+\*\*\[([^\]]*)\]\*\*\s+(.+)/);if(d){let h=d[1],f=d[2],m=f.match(/"([^"]+)"\s*→\s*(.+)/),p=f.match(/"([^"]+)"\s*—\s*(.+)/),g;if(m)g={section:h,text:m[1],feedback:m[2]};else if(p)g={section:h,text:p[1],feedback:p[2]};else{let y=f.match(/"([^"]+)"/);g={section:h,text:y?y[1]:"",feedback:y?"":f}}r.startsWith("Decisions")?o.push(g):r.startsWith("Open Questions")?s.push(g):r.startsWith("Key Points")&&i.push(g)}if(r.startsWith("Progress Checkpoints")){let h=u.match(/^\|\s*(\d+)\s*\|\s*([^|]+)\|\s*([^|]*)\|\s*(\d+)\s*\|\s*(\d+)\s*\|\s*(\d+)\s*\|/);h&&a.push({id:`ckpt_restored_${h[1]}`,timestamp:h[2].trim(),note:h[3].trim(),fixes:parseInt(h[4],10),questions:parseInt(h[5],10),highlights:parseInt(h[6],10),sectionsReviewed:[]})}if(r.startsWith("Next Steps")){let h=u.match(/^- \[ \]\s+(.+)/);h&&c.push(h[1])}}return n.file?{meta:n,decisions:o,openQuestions:s,keyPoints:i,checkpoints:a,nextSteps:c}:null}function xE(t,e){return t.blockedBy.length>0&&t.blockedBy.map(o=>e.find(s=>s.id===o)).filter(o=>o!=null&&!Le(o.status)).length>0?"blocked":e.some(n=>!Le(n.status))?"proceed":"done"}function It(t,e){return t.map(r=>({...r,status:r.override||xE(r,e)}))}var $d=require("node:fs");var Tn=En(require("node:path")),Ao=require("node:fs"),bE=["**/.env",".env","**/.env.*",".env.*","**/credentials*","credentials*","**/secrets*","secrets*","**/*.pem","*.pem","**/*.key","*.key","**/*.p12","*.p12","**/node_modules/**","node_modules/**","**/.git/**",".git/**"];function Py(t){return{workspaceRoot:t||process.env.MD_FEEDBACK_WORKSPACE||process.cwd(),blocklist:[...bE],allowlist:[]}}function $E(t,e){let r=e.replace(/\\/g,"/"),o=t.replace(/\\/g,"/").replace(/[.+^${}()|[\]]/g,"\\$&").replace(/\*\*/g,"\0").replace(/\*/g,"[^/]*").replace(/\u0000/g,".*").replace(/\?/g,"[^/]");return new RegExp(`^${o}$`).test(r)}function Ey(t,e){return t.some(r=>$E(r,e))}function xd(t,e){let r=Tn.default.resolve(t.workspaceRoot,e),n=Tn.default.resolve(t.workspaceRoot);if(!r.startsWith(n+Tn.default.sep)&&r!==n)return{safe:!1,reason:`Path "${e}" resolves outside workspace root`};if((0,Ao.existsSync)(r))try{let s=(0,Ao.realpathSync)(r),i=(0,Ao.realpathSync)(n);if(!s.startsWith(i+Tn.default.sep)&&s!==i)return{safe:!1,reason:`Path "${e}" resolves outside workspace via symlink`}}catch{}let o=Tn.default.relative(n,r).replace(/\\/g,"/");return Ey(t.blocklist,o)?{safe:!1,reason:`Path "${e}" matches blocklist pattern`}:t.allowlist.length>0&&!Ey(t.allowlist,o)?{safe:!1,reason:`Path "${e}" not in allowlist`}:{safe:!0}}function Ry(t,e,r,n,o,s){let i={},a={},c=0;for(let b of t)i[b.status]=(i[b.status]||0)+1,a[b.type]=(a[b.type]||0)+1,Le(b.status)&&c++;let u=t.length>0?c/t.length:0,l={},d=0,h=0,f=0;for(let b of e)l[b.status]=(l[b.status]||0)+1,b.status==="applied"&&d++,b.status==="reverted"&&h++,b.status==="failed"&&f++;let m={};for(let b of r)m[b.status]=(m[b.status]||0)+1;let p=new Set;for(let b of o)for(let $ of b.files)p.add($);let g=s.filter(b=>b.type==="blocks").length,y=null;n.length>0&&(y=n.reduce((b,$)=>$.timestamp>b?$.timestamp:b,n[0].timestamp));let v=null,w=t.filter(b=>Le(b.status)&&b.createdAt&&b.updatedAt);if(w.length>0){let b=0,$=0;for(let F of w){let ae=new Date(F.createdAt).getTime(),ee=new Date(F.updatedAt).getTime();!isNaN(ae)&&!isNaN(ee)&&ee>ae&&(b+=ee-ae,$++)}$>0&&(v=b/$)}return{totalMemos:t.length,byStatus:i,byType:a,resolutionRate:u,totalImpls:e.length,implsByStatus:l,appliedCount:d,revertedCount:h,failedCount:f,totalGates:r.length,gatesByStatus:m,totalArtifacts:o.length,linkedFiles:p.size,totalDependencies:s.length,blockingChains:g,totalCheckpoints:n.length,lastCheckpoint:y,avgResolutionTime:v}}var Iy=En(require("node:path")),Mi=new Map;async function vt(t,e){let r=Iy.default.resolve(t);for(;Mi.has(r);)await Mi.get(r);let n;Mi.set(r,new Promise(o=>{n=o}));try{return await e()}finally{Mi.delete(r),n()}}function bd(t){let e=5381;for(let r=0;r<t.length;r++)e=(e<<5)+e+t.charCodeAt(r)>>>0;return e.toString(16).padStart(8,"0").slice(0,8)}function Oy(t,e){let r=Py(e);function n(s){let i=xd(r,s);if(!i.safe)throw new Error(i.reason);return Ri(s)}function o(s,i){let a=xd(r,s);if(!a.safe)throw new Error(a.reason);_y(s,i)}t.tool("create_checkpoint","Create a review checkpoint in an annotated markdown file. Records current annotation counts and reviewed sections.",{file:R.string().describe("Path to the annotated markdown file"),note:R.string().describe('Checkpoint note (e.g., "Phase 1 review done")')},async({file:s,note:i})=>vt(s,async()=>{try{let a=n(s),{checkpoint:c,updatedMarkdown:u}=yd(a,i);return o(s,u),{content:[{type:"text",text:JSON.stringify({checkpoint:c},null,2)}]}}catch(a){return{content:[{type:"text",text:JSON.stringify({error:a instanceof Error?a.message:String(a)})}],isError:!0}}})),t.tool("get_checkpoints","List all checkpoints in an annotated markdown file.",{file:R.string().describe("Path to the annotated markdown file")},async({file:s})=>{try{let i=n(s),a=Fr(i);return{content:[{type:"text",text:JSON.stringify({checkpoints:a},null,2)}]}}catch(i){return{content:[{type:"text",text:JSON.stringify({error:i instanceof Error?i.message:String(i)})}],isError:!0}}}),t.tool("generate_handoff","Generate a structured handoff document from an annotated markdown file. Anti-compression format: explicit fields, numbers, lists only.",{file:R.string().describe("Path to the annotated markdown file"),target:R.enum(["standalone","claude-md","cursor-rules"]).optional().describe("Output format target (default: standalone)")},async({file:s,target:i})=>{try{let a=n(s),c=_d(a,s);return{content:[{type:"text",text:vd(c,i||"standalone")}]}}catch(a){return{content:[{type:"text",text:JSON.stringify({error:a instanceof Error?a.message:String(a)})}],isError:!0}}}),t.tool("get_review_status","Get current review session status: annotation counts, checkpoints, and reviewed sections. Summary-only \u2014 returns counts and metadata, not individual memos. Use list_annotations for memo details or get_document_structure for the full parse.",{file:R.string().describe("Path to the annotated markdown file")},async({file:s})=>{try{let i=n(s),a=Sn(i),c=Fr(i),u=Ur(i),l={file:s,annotations:a,checkpointCount:c.length,lastCheckpoint:c.length>0?c[c.length-1].timestamp:null,sectionsReviewed:u};return{content:[{type:"text",text:JSON.stringify(l,null,2)}]}}catch(i){return{content:[{type:"text",text:JSON.stringify({error:i instanceof Error?i.message:String(i)})}],isError:!0}}}),t.tool("pickup_handoff","Parse an existing handoff document to resume a review session. Returns structured data for session continuity.",{file:R.string().describe("Path to the handoff markdown file")},async({file:s})=>{try{let i=n(s),a=Ty(i);return a?{content:[{type:"text",text:JSON.stringify(a,null,2)}]}:{content:[{type:"text",text:JSON.stringify({error:"Not a valid handoff document"})}],isError:!0}}catch(i){return{content:[{type:"text",text:JSON.stringify({error:i instanceof Error?i.message:String(i)})}],isError:!0}}}),t.tool("list_annotations","List all annotations (USER_MEMO comments) in a markdown file. Returns structured array with id, type, status, owner, text, and color. Lightweight \u2014 returns only memo data, no document body or sections. Use get_document_structure for the full parse.",{file:R.string().describe("Path to the annotated markdown file")},async({file:s})=>{try{let i=n(s),c=Oe(i).memos.map(u=>({id:u.id,type:u.type,status:u.status,owner:u.owner,source:u.source,color:u.color,text:u.text,anchorText:u.anchorText,anchor:u.anchor,createdAt:u.createdAt,updatedAt:u.updatedAt}));return{content:[{type:"text",text:JSON.stringify({annotations:c,total:c.length},null,2)}]}}catch(i){return{content:[{type:"text",text:JSON.stringify({error:i instanceof Error?i.message:String(i)})}],isError:!0}}}),t.tool("get_document_structure","Parse an annotated markdown file and return the full v0.4.0 ReviewDocument: { bodyMd, memos[] (with status/owner), checkpoints[], gates[], cursor, sections, summary }. Most comprehensive tool \u2014 use this when you need the complete document state. Use list_annotations for just memos, or get_review_status for just counts.",{file:R.string().describe("Path to the annotated markdown file")},async({file:s})=>{try{let i=n(s),a=Oe(i),c=zn(i),u=Ur(i),l=It(a.gates,a.memos),d=a.memos.filter($=>$.status==="open").length,h=a.memos.filter($=>$.status==="in_progress").length,f=a.memos.filter($=>$.status==="needs_review").length,m=a.memos.filter($=>$.status==="answered").length,p=a.memos.filter($=>$.status==="done").length,g=a.memos.filter($=>$.status==="failed").length,y=a.memos.filter($=>$.status==="wontfix").length,v=l.filter($=>$.status==="blocked").length,w={version:"0.4.0",file:s,bodyMd:a.body,memos:a.memos,checkpoints:a.checkpoints,gates:l,cursor:a.cursor,sections:{all:c,reviewed:u,uncovered:c.filter($=>!u.includes($))},impls:a.impls,artifacts:a.artifacts,dependencies:a.dependencies,summary:{total:a.memos.length,open:d,inProgress:h,needsReview:f,answered:m,done:p,failed:g,wontfix:y,blocked:v,fixes:a.memos.filter($=>$.type==="fix").length,questions:a.memos.filter($=>$.type==="question").length,highlights:a.memos.filter($=>$.type==="highlight").length}},b=Ry(a.memos,a.impls,l,a.checkpoints,a.artifacts,a.dependencies);return{content:[{type:"text",text:JSON.stringify({...w,metrics:b},null,2)}]}}catch(i){return{content:[{type:"text",text:JSON.stringify({error:i instanceof Error?i.message:String(i)})}],isError:!0}}}),t.tool("create_annotation","Create a new review annotation on a markdown file. Finds the anchor text in the document and attaches a review memo. Auto-creates a quality gate and updates cursor.",{file:R.string().describe("Path to the annotated markdown file"),anchorText:R.string().describe("The exact text in the document to annotate (must exist in the file)"),type:R.enum(["fix","question","highlight"]).describe("fix = needs change, question = needs clarification, highlight = mark for reference"),text:R.string().describe("The review feedback or note to attach"),occurrence:R.number().int().min(1).optional().describe("Which occurrence of anchorText to annotate (1-indexed, default 1). Use when the same text appears multiple times.")},async({file:s,anchorText:i,type:a,text:c,occurrence:u})=>vt(s,async()=>{try{let l=n(s),d=Oe(l),h=d.body.split(`
73
- `),f=-1;if(u){let b=0;for(let $=0;$<h.length;$++)if(h[$].includes(i)&&(b++,b===u)){f=$;break}if(f===-1){let $=b===0?`Anchor text not found: "${i}"`:`Anchor text "${i}" has ${b} occurrence(s), but occurrence=${u} requested`;return{content:[{type:"text",text:JSON.stringify({error:$})}],isError:!0}}}else{let b=[];for(let $=0;$<h.length;$++)h[$].includes(i)&&b.push($);if(b.length===0)return{content:[{type:"text",text:JSON.stringify({error:`Anchor text not found: "${i}"`})}],isError:!0};if(b.length===1)f=b[0];else{let $=b.find(F=>h[F].trim()===i.trim());if($!==void 0)f=$;else{let F=b[0],ae=1/0;for(let ee of b){let A=h[ee].length-i.length;A<ae&&(ae=A,F=ee)}f=F}}}let m=bd(h[f]),p=f+1,g=a==="fix"?"red":a==="question"?"blue":"yellow",y={id:`memo-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`,type:a,status:"open",owner:"agent",source:"mcp",color:g,text:c,anchorText:i,anchor:`L${p}:L${p}|${m}`,createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()};d.memos.push(y),d.gates.length===0&&d.gates.push({id:`gate-${Date.now().toString(36)}`,type:"merge",status:"blocked",blockedBy:[],canProceedIf:"",doneDefinition:"All review annotations resolved"}),d.gates=It(d.gates,d.memos);let v=d.memos.filter(b=>b.status!=="open").length;d.cursor={taskId:y.id,step:`${v}/${d.memos.length} resolved`,nextAction:`Created ${a}: "${c.slice(0,50)}"`,lastSeenHash:cr(d.body),updatedAt:new Date().toISOString()};let w=Rt(d);return o(s,w),{content:[{type:"text",text:JSON.stringify({memo:y,gateStatus:d.gates[0]?.status,totalMemos:d.memos.length},null,2)}]}}catch(l){return{content:[{type:"text",text:JSON.stringify({error:l instanceof Error?l.message:String(l)})}],isError:!0}}})),t.tool("respond_to_memo",`Add an AI response to a memo annotation. Inserts a REVIEW_RESPONSE block into the markdown file directly below the memo's anchor text. Automatically sets the memo status to "answered".`,{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().describe("The memo ID to respond to"),response:R.string().describe("The response text (markdown supported)")},async({file:s,memoId:i,response:a})=>vt(s,async()=>{try{let c=n(s),u=Oe(c),l=u.memos.find(f=>f.id===i);if(!l)return{content:[{type:"text",text:JSON.stringify({error:`Memo not found: ${i}`})}],isError:!0};let d=u.responses.find(f=>f.to===i);if(d){let f=u.body.split(`
73
+ `),f=-1;if(u){let b=0;for(let $=0;$<h.length;$++)if(h[$].includes(i)&&(b++,b===u)){f=$;break}if(f===-1){let $=b===0?`Anchor text not found: "${i}"`:`Anchor text "${i}" has ${b} occurrence(s), but occurrence=${u} requested`;return{content:[{type:"text",text:JSON.stringify({error:$})}],isError:!0}}}else{let b=[];for(let $=0;$<h.length;$++)h[$].includes(i)&&b.push($);if(b.length===0)return{content:[{type:"text",text:JSON.stringify({error:`Anchor text not found: "${i}"`})}],isError:!0};if(b.length===1)f=b[0];else{let $=b.find(F=>h[F].trim()===i.trim());if($!==void 0)f=$;else{let F=b[0],ae=1/0;for(let ee of b){let A=h[ee].length-i.length;A<ae&&(ae=A,F=ee)}f=F}}}let m=bd(h[f]),p=f+1,g=a==="fix"?"red":a==="question"?"blue":"yellow",y={id:`memo-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`,type:a,status:"open",owner:"agent",source:"mcp",color:g,text:c,anchorText:i,anchor:`L${p}:L${p}|${m}`,createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()};d.memos.push(y),d.gates.length===0&&d.gates.push({id:`gate-${Date.now().toString(36)}`,type:"merge",status:"blocked",blockedBy:[],canProceedIf:"",doneDefinition:"All review annotations resolved"}),d.gates=It(d.gates,d.memos);let v=d.memos.filter(b=>b.status!=="open").length;d.cursor={taskId:y.id,step:`${v}/${d.memos.length} resolved`,nextAction:`Created ${a}: "${c.slice(0,50)}"`,lastSeenHash:cr(d.body),updatedAt:new Date().toISOString()};let w=Rt(d);return o(s,w),{content:[{type:"text",text:JSON.stringify({memo:y,gateStatus:d.gates[0]?.status,totalMemos:d.memos.length},null,2)}]}}catch(l){return{content:[{type:"text",text:JSON.stringify({error:l instanceof Error?l.message:String(l)})}],isError:!0}}})),t.tool("respond_to_memo",`Add an AI response to a memo annotation. Inserts a REVIEW_RESPONSE block into the markdown file directly below the memo's anchor text. Automatically sets the memo status to "needs_review" for human approval.`,{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().describe("The memo ID to respond to"),response:R.string().describe("The response text (markdown supported)")},async({file:s,memoId:i,response:a})=>vt(s,async()=>{try{let c=n(s),u=Oe(c),l=u.memos.find(f=>f.id===i);if(!l)return{content:[{type:"text",text:JSON.stringify({error:`Memo not found: ${i}`})}],isError:!0};let d=u.responses.find(f=>f.to===i);if(d){let f=u.body.split(`
74
74
  `),m=a.split(`
75
75
  `),p=d.bodyStartIdx,g=d.bodyEndIdx,y=g>=p?g-p+1:0;f.splice(p,y,...m),d.bodyEndIdx=p+m.length-1,u.body=f.join(`
76
76
  `)}else{let f=u.body.split(`
77
77
  `),m=-1;if(l.anchor){let y=l.anchor.match(/^L(\d+)(?::L\d+)?\|(.+)$/);if(y){let v=parseInt(y[1],10)-1,w=bd(f[v]||"");if(v>=0&&v<f.length&&w===y[2])m=v;else for(let b=1;b<=10;b++){for(let $ of[v-b,v+b])if($>=0&&$<f.length&&bd(f[$])===y[2]){m=$;break}if(m>=0)break}}}if(m===-1&&l.anchorText){for(let y=0;y<f.length;y++)if(f[y].includes(l.anchorText)){m=y;break}}m===-1&&(m=f.length-1);let p=a.split(`
78
78
  `),g=m+1;f.splice(g,0,...p);for(let y of u.responses)y.bodyStartIdx>=g&&(y.bodyStartIdx+=p.length,y.bodyEndIdx+=p.length);u.body=f.join(`
79
- `),u.responses.push({id:`resp_${i}`,to:i,bodyStartIdx:g,bodyEndIdx:g+p.length-1})}l.status==="open"&&(l.status="answered",l.updatedAt=new Date().toISOString()),u.gates.length>0&&(u.gates=It(u.gates,u.memos));let h=Rt(u);return o(s,h),{content:[{type:"text",text:JSON.stringify({memoId:i,status:l.status,responseInserted:!0,totalResponses:u.responses.length},null,2)}]}}catch(c){return{content:[{type:"text",text:JSON.stringify({error:c instanceof Error?c.message:String(c)})}],isError:!0}}})),t.tool("update_memo_status","Update the status of a memo annotation. Writes the change back to the markdown file. Returns the updated memo.",{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().describe("The memo ID to update"),status:R.enum(["open","in_progress","needs_review","answered","done","failed","wontfix"]).describe("New status"),owner:R.enum(["human","agent","tool"]).optional().describe("Optionally change the owner")},async({file:s,memoId:i,status:a,owner:c})=>vt(s,async()=>{try{let u=n(s),l=Oe(u),d=l.memos.find(g=>g.id===i);if(!d)return{content:[{type:"text",text:JSON.stringify({error:`Memo not found: ${i}`})}],isError:!0};d.status=a,c&&(d.owner=c),d.updatedAt=new Date().toISOString(),l.gates.length===0&&l.memos.length>0&&l.gates.push({id:`gate-${Date.now().toString(36)}`,type:"merge",status:"blocked",blockedBy:[],canProceedIf:"",doneDefinition:"All review annotations resolved"}),l.gates=It(l.gates,l.memos);let h=l.memos.filter(g=>g.status!=="open").length,f=l.memos.length,m=l.memos.filter(g=>g.status==="open");l.cursor={taskId:i,step:`${h}/${f} resolved`,nextAction:m.length===0?"All annotations resolved \u2014 review complete":`Resolve: ${m.map(g=>g.id).slice(0,3).join(", ")}${m.length>3?"...":""}`,lastSeenHash:cr(l.body),updatedAt:new Date().toISOString()};let p=Rt(l);return o(s,p),{content:[{type:"text",text:JSON.stringify({memo:d,gatesUpdated:l.gates.length,cursor:l.cursor},null,2)}]}}catch(u){return{content:[{type:"text",text:JSON.stringify({error:u instanceof Error?u.message:String(u)})}],isError:!0}}})),t.tool("update_cursor",'Update the plan cursor position in a markdown file. The cursor tracks "where we are" in a plan. Only one cursor per document.',{file:R.string().describe("Path to the annotated markdown file"),taskId:R.string().describe("Current task ID"),step:R.string().describe('Current step (e.g., "3/7" or "Phase 2")'),nextAction:R.string().describe("Description of the next action to take")},async({file:s,taskId:i,step:a,nextAction:c})=>vt(s,async()=>{try{let u=n(s),l=Oe(u);if(l.memos.length>0&&!l.memos.some(h=>h.id===i))return{content:[{type:"text",text:JSON.stringify({error:`Task ID not found: "${i}". Valid IDs: ${l.memos.map(h=>h.id).join(", ")}`})}],isError:!0};l.cursor={taskId:i,step:a,nextAction:c,lastSeenHash:cr(l.body),updatedAt:new Date().toISOString()};let d=Rt(l);return o(s,d),{content:[{type:"text",text:JSON.stringify({cursor:l.cursor},null,2)}]}}catch(u){return{content:[{type:"text",text:JSON.stringify({error:u instanceof Error?u.message:String(u)})}],isError:!0}}})),t.tool("evaluate_gates","Evaluate all gates in a markdown file against current memo statuses. Returns updated gate statuses without modifying the file.",{file:R.string().describe("Path to the annotated markdown file")},async({file:s})=>{try{let i=n(s),a=Oe(i),c=It(a.gates,a.memos);return{content:[{type:"text",text:JSON.stringify({gates:c,summary:{total:c.length,blocked:c.filter(u=>u.status==="blocked").length,proceed:c.filter(u=>u.status==="proceed").length,done:c.filter(u=>u.status==="done").length}},null,2)}]}}catch(i){return{content:[{type:"text",text:JSON.stringify({error:i instanceof Error?i.message:String(i)})}],isError:!0}}}),t.tool("export_review","Export review feedback in a format optimized for a specific AI coding tool. Targets: claude-code, cursor, codex, copilot, cline, windsurf, roo-code, gemini, generic, handoff. Returns formatted markdown ready to save to the appropriate file.",{file:R.string().describe("Path to the annotated markdown file"),target:R.enum(["claude-code","cursor","codex","copilot","cline","windsurf","roo-code","gemini","antigravity","generic","handoff"]).describe("Target AI tool format")},async({file:s,target:i})=>{try{let a=n(s);if(i==="handoff"){let p=_d(a,s);return{content:[{type:"text",text:vd(p,"standalone")}]}}let{memos:c}=hd(a),u=zn(a),l=u[0]||"Plan Review",d={red:"#fca5a5",blue:"#93c5fd",yellow:"#fef08a"},h=c.map(p=>({text:p.anchorText||"",color:d[p.color]||"#fef08a",section:"",context:""})),f=c.map(p=>({id:p.id,text:p.text,color:p.color,section:"",context:p.anchorText||""}));return{content:[{type:"text",text:$y(l,s,u,h,f,i)}]}}catch(a){return{content:[{type:"text",text:JSON.stringify({error:a instanceof Error?a.message:String(a)})}],isError:!0}}}),t.tool("apply_memo","Apply an implementation action to a memo. Supports text_replace (replaces all occurrences in current document), file_patch (overwrites target file \u2014 snapshot saved first), and file_create (create a new file). Creates a snapshot before modification, records the implementation, and updates memo status to done.",{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().describe("The memo ID to apply implementation to"),action:R.enum(["text_replace","file_patch","file_create"]).describe("Type of implementation action"),dryRun:R.boolean().optional().default(!1).describe("If true, return preview without writing"),oldText:R.string().optional().describe("For text_replace: the text to find and replace"),newText:R.string().optional().describe("For text_replace: the replacement text"),targetFile:R.string().optional().describe("For file_patch/file_create: target file path"),patch:R.string().optional().describe("For file_patch: the patch content"),content:R.string().optional().describe("For file_create: the file content to write")},async({file:s,memoId:i,action:a,dryRun:c,oldText:u,newText:l,targetFile:d,patch:h,content:f})=>vt(s,async()=>{try{let m=n(s),p=Oe(m),g=p.memos.find(F=>F.id===i);if(!g)return{content:[{type:"text",text:JSON.stringify({error:`Memo not found: ${i}`})}],isError:!0};let y;if(a==="text_replace"){if(!u||l===void 0)return{content:[{type:"text",text:JSON.stringify({error:"text_replace requires oldText and newText"})}],isError:!0};y={type:"text_replace",file:"",before:u,after:l}}else if(a==="file_patch"){if(!d||!h)return{content:[{type:"text",text:JSON.stringify({error:"file_patch requires targetFile and patch"})}],isError:!0};y={type:"file_patch",file:d,patch:h}}else{if(!d||f===void 0)return{content:[{type:"text",text:JSON.stringify({error:"file_create requires targetFile and content"})}],isError:!0};y={type:"file_create",file:d,content:f}}let v={id:`impl_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,6)}`,memoId:i,status:"applied",operations:[y],summary:`${a} for ${i}`,appliedAt:new Date().toISOString()};if(c)return{content:[{type:"text",text:JSON.stringify({dryRun:!0,impl:v,operation:y,memo:{id:g.id,status:g.status}},null,2)}]};if(Mo(s,m),a==="text_replace"){if(!p.body.includes(u))return{content:[{type:"text",text:JSON.stringify({error:"oldText not found in document body"})}],isError:!0};p.body=p.body.split(u).join(l)}else a==="file_patch"?((0,$d.existsSync)(d)&&Mo(d,Ri(d)),o(d,h)):a==="file_create"&&o(d,f);p.impls.push(v),g.status="needs_review",g.updatedAt=new Date().toISOString(),p.gates.length>0&&(p.gates=It(p.gates,p.memos));let w=p.memos.filter(F=>Le(F.status)).length,b=p.memos.filter(F=>!Le(F.status));p.cursor={taskId:i,step:`${w}/${p.memos.length} resolved`,nextAction:b.length===0?"All annotations resolved \u2014 review complete":`Resolve: ${b.map(F=>F.id).slice(0,3).join(", ")}${b.length>3?"...":""}`,lastSeenHash:cr(p.body),updatedAt:new Date().toISOString()};let $=Rt(p);return o(s,$),{content:[{type:"text",text:JSON.stringify({impl:v,memo:{id:g.id,status:g.status},gatesUpdated:p.gates.length},null,2)}]}}catch(m){return{content:[{type:"text",text:JSON.stringify({error:m instanceof Error?m.message:String(m)})}],isError:!0}}})),t.tool("link_artifacts","Link file artifacts (source files, configs, etc.) to a memo. Creates a MemoArtifact record in the document.",{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().describe("The memo ID to link artifacts to"),files:R.array(R.string()).describe("Array of relative file paths to link")},async({file:s,memoId:i,files:a})=>vt(s,async()=>{try{let c=n(s),u=Oe(c);if(!u.memos.find(f=>f.id===i))return{content:[{type:"text",text:JSON.stringify({error:`Memo not found: ${i}`})}],isError:!0};let d={id:`art_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,6)}`,memoId:i,files:a,linkedAt:new Date().toISOString()};u.artifacts.push(d);let h=Rt(u);return o(s,h),{content:[{type:"text",text:JSON.stringify({artifact:d},null,2)}]}}catch(c){return{content:[{type:"text",text:JSON.stringify({error:c instanceof Error?c.message:String(c)})}],isError:!0}}})),t.tool("update_memo_progress","Update the progress of a memo with a status change and message. Writes progress to .md-feedback/progress.json and updates the memo status.",{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().describe("The memo ID to update progress for"),status:R.enum(["in_progress","needs_review","done","failed"]).describe("New progress status"),message:R.string().describe("Progress message describing what was done or what failed")},async({file:s,memoId:i,status:a,message:c})=>vt(s,async()=>{try{let u=n(s),l=Oe(u),d=l.memos.find(g=>g.id===i);if(!d)return{content:[{type:"text",text:JSON.stringify({error:`Memo not found: ${i}`})}],isError:!0};d.status=a,d.updatedAt=new Date().toISOString();let h={memoId:i,status:a,message:c,timestamp:new Date().toISOString()};vy(s,h),l.gates.length>0&&(l.gates=It(l.gates,l.memos));let f=l.memos.filter(g=>Le(g.status)).length,m=l.memos.filter(g=>!Le(g.status));l.cursor={taskId:i,step:`${f}/${l.memos.length} resolved`,nextAction:m.length===0?"All annotations resolved \u2014 review complete":`Resolve: ${m.map(g=>g.id).slice(0,3).join(", ")}${m.length>3?"...":""}`,lastSeenHash:cr(l.body),updatedAt:new Date().toISOString()};let p=Rt(l);return o(s,p),{content:[{type:"text",text:JSON.stringify({memo:{id:d.id,status:d.status},progressEntry:h,gatesUpdated:l.gates.length},null,2)}]}}catch(u){return{content:[{type:"text",text:JSON.stringify({error:u instanceof Error?u.message:String(u)})}],isError:!0}}})),t.tool("rollback_memo","Rollback the latest implementation for a memo. Reverses text_replace operations (swaps before/after), marks the impl as reverted, and sets the memo status back to open.",{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().describe("The memo ID to rollback")},async({file:s,memoId:i})=>vt(s,async()=>{try{let a=n(s),c=Oe(a),u=c.memos.find(p=>p.id===i);if(!u)return{content:[{type:"text",text:JSON.stringify({error:`Memo not found: ${i}`})}],isError:!0};let l=c.impls.filter(p=>p.memoId===i&&p.status==="applied");if(l.length===0)return{content:[{type:"text",text:JSON.stringify({error:`No applied implementation found for memo: ${i}`})}],isError:!0};let d=l[l.length-1];for(let p of d.operations)p.type==="text_replace"&&c.body.includes(p.after)&&(c.body=c.body.split(p.after).join(p.before));d.status="reverted",u.status="open",u.updatedAt=new Date().toISOString(),c.gates.length>0&&(c.gates=It(c.gates,c.memos));let h=c.memos.filter(p=>Le(p.status)).length,f=c.memos.filter(p=>!Le(p.status));c.cursor={taskId:i,step:`${h}/${c.memos.length} resolved`,nextAction:f.length===0?"All annotations resolved \u2014 review complete":`Resolve: ${f.map(p=>p.id).slice(0,3).join(", ")}${f.length>3?"...":""}`,lastSeenHash:cr(c.body),updatedAt:new Date().toISOString()};let m=Rt(c);return o(s,m),{content:[{type:"text",text:JSON.stringify({rolledBack:d.id,memo:{id:u.id,status:u.status},gatesUpdated:c.gates.length},null,2)}]}}catch(a){return{content:[{type:"text",text:JSON.stringify({error:a instanceof Error?a.message:String(a)})}],isError:!0}}})),t.tool("batch_apply","Apply multiple implementation operations in a single transaction. Parses the document once, applies all operations sequentially, then writes once. Each operation follows the same format as apply_memo.",{file:R.string().describe("Path to the annotated markdown file"),operations:R.array(R.object({memoId:R.string().describe("The memo ID to apply implementation to"),action:R.enum(["text_replace","file_patch","file_create"]).describe("Type of implementation action"),oldText:R.string().optional().describe("For text_replace: the text to find and replace"),newText:R.string().optional().describe("For text_replace: the replacement text"),targetFile:R.string().optional().describe("For file_patch/file_create: target file path"),patch:R.string().optional().describe("For file_patch: the patch content"),content:R.string().optional().describe("For file_create: the file content to write")})).describe("Array of operations to apply")},async({file:s,operations:i})=>vt(s,async()=>{try{let a=n(s),c=Oe(a);Mo(s,a);let u=[];for(let f of i){let m=c.memos.find(y=>y.id===f.memoId);if(!m){u.push({memoId:f.memoId,implId:"",status:"error: memo not found"});continue}let p;if(f.action==="text_replace"){if(!f.oldText||f.newText===void 0){u.push({memoId:f.memoId,implId:"",status:"error: text_replace requires oldText and newText"});continue}if(p={type:"text_replace",file:"",before:f.oldText,after:f.newText},!c.body.includes(f.oldText)){u.push({memoId:f.memoId,implId:"",status:"error: oldText not found in body"});continue}c.body=c.body.split(f.oldText).join(f.newText)}else if(f.action==="file_patch"){if(!f.targetFile||!f.patch){u.push({memoId:f.memoId,implId:"",status:"error: file_patch requires targetFile and patch"});continue}p={type:"file_patch",file:f.targetFile,patch:f.patch},(0,$d.existsSync)(f.targetFile)&&Mo(f.targetFile,Ri(f.targetFile)),o(f.targetFile,f.patch)}else{if(!f.targetFile||f.content===void 0){u.push({memoId:f.memoId,implId:"",status:"error: file_create requires targetFile and content"});continue}p={type:"file_create",file:f.targetFile,content:f.content},o(f.targetFile,f.content)}let g={id:`impl_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,6)}`,memoId:f.memoId,status:"applied",operations:[p],summary:`${f.action} for ${f.memoId}`,appliedAt:new Date().toISOString()};c.impls.push(g),m.status="needs_review",m.updatedAt=new Date().toISOString(),u.push({memoId:f.memoId,implId:g.id,status:"applied"})}c.gates.length>0&&(c.gates=It(c.gates,c.memos));let l=c.memos.filter(f=>Le(f.status)).length,d=c.memos.filter(f=>!Le(f.status));c.cursor={taskId:i[0]?.memoId||"",step:`${l}/${c.memos.length} resolved`,nextAction:d.length===0?"All annotations resolved \u2014 review complete":`Resolve: ${d.map(f=>f.id).slice(0,3).join(", ")}${d.length>3?"...":""}`,lastSeenHash:cr(c.body),updatedAt:new Date().toISOString()},xy(s,{type:"batch_apply",results:u,timestamp:new Date().toISOString()});let h=Rt(c);return o(s,h),{content:[{type:"text",text:JSON.stringify({results:u,gatesUpdated:c.gates.length,cursor:c.cursor},null,2)}]}}catch(a){return{content:[{type:"text",text:JSON.stringify({error:a instanceof Error?a.message:String(a)})}],isError:!0}}})),t.tool("get_memo_changes","Get the implementation history and progress for a memo. Returns all MemoImpl records and progress entries from .md-feedback/progress.json. If memoId is omitted, returns all changes.",{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().optional().describe("Optional memo ID to filter by \u2014 if omitted, returns all changes")},async({file:s,memoId:i})=>{try{let a=n(s),c=Oe(a),u=i?c.impls.filter(h=>h.memoId===i):c.impls,l=fd(s),d=i?l.filter(h=>h.memoId===i):l;return{content:[{type:"text",text:JSON.stringify({impls:u,progress:d},null,2)}]}}catch(a){return{content:[{type:"text",text:JSON.stringify({error:a instanceof Error?a.message:String(a)})}],isError:!0}}})}function wd(t){process.stderr.write(`[md-feedback] ${t}
80
- `)}function wE(){let t=process.argv.find(e=>e.startsWith("--workspace="));return t?t.split("=")[1]:process.env.MD_FEEDBACK_WORKSPACE||void 0}var My=wE(),Cy=new Ti({name:"md-feedback",version:"1.3.0"});Oy(Cy,My);async function kE(){let t=new Pi;await Cy.connect(t);let e=My||process.cwd();wd(`v1.3.0 ready (stdio) workspace=${e}`)}kE().catch(t=>{wd(`fatal: ${t}`),process.exit(1)});0&&(module.exports={log});
79
+ `),u.responses.push({id:`resp_${i}`,to:i,bodyStartIdx:g,bodyEndIdx:g+p.length-1})}(l.status==="open"||l.status==="in_progress")&&(l.status="needs_review",l.updatedAt=new Date().toISOString()),u.gates.length>0&&(u.gates=It(u.gates,u.memos));let h=Rt(u);return o(s,h),{content:[{type:"text",text:JSON.stringify({memoId:i,status:l.status,responseInserted:!0,totalResponses:u.responses.length},null,2)}]}}catch(c){return{content:[{type:"text",text:JSON.stringify({error:c instanceof Error?c.message:String(c)})}],isError:!0}}})),t.tool("update_memo_status","Update the status of a memo annotation. Writes the change back to the markdown file. Returns the updated memo. Terminal statuses (answered, done, failed, wontfix) require human approval via VS Code.",{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().describe("The memo ID to update"),status:R.enum(["open","in_progress","needs_review"]).describe("New status. Terminal statuses (answered, done, failed, wontfix) require human approval via VS Code."),owner:R.enum(["human","agent","tool"]).optional().describe("Optionally change the owner")},async({file:s,memoId:i,status:a,owner:c})=>vt(s,async()=>{try{let u=n(s),l=Oe(u),d=l.memos.find(g=>g.id===i);if(!d)return{content:[{type:"text",text:JSON.stringify({error:`Memo not found: ${i}`})}],isError:!0};d.status=a,c&&(d.owner=c),d.updatedAt=new Date().toISOString(),l.gates.length===0&&l.memos.length>0&&l.gates.push({id:`gate-${Date.now().toString(36)}`,type:"merge",status:"blocked",blockedBy:[],canProceedIf:"",doneDefinition:"All review annotations resolved"}),l.gates=It(l.gates,l.memos);let h=l.memos.filter(g=>g.status!=="open").length,f=l.memos.length,m=l.memos.filter(g=>g.status==="open");l.cursor={taskId:i,step:`${h}/${f} resolved`,nextAction:m.length===0?"All annotations resolved \u2014 review complete":`Resolve: ${m.map(g=>g.id).slice(0,3).join(", ")}${m.length>3?"...":""}`,lastSeenHash:cr(l.body),updatedAt:new Date().toISOString()};let p=Rt(l);return o(s,p),{content:[{type:"text",text:JSON.stringify({memo:d,gatesUpdated:l.gates.length,cursor:l.cursor},null,2)}]}}catch(u){return{content:[{type:"text",text:JSON.stringify({error:u instanceof Error?u.message:String(u)})}],isError:!0}}})),t.tool("update_cursor",'Update the plan cursor position in a markdown file. The cursor tracks "where we are" in a plan. Only one cursor per document.',{file:R.string().describe("Path to the annotated markdown file"),taskId:R.string().describe("Current task ID"),step:R.string().describe('Current step (e.g., "3/7" or "Phase 2")'),nextAction:R.string().describe("Description of the next action to take")},async({file:s,taskId:i,step:a,nextAction:c})=>vt(s,async()=>{try{let u=n(s),l=Oe(u);if(l.memos.length>0&&!l.memos.some(h=>h.id===i))return{content:[{type:"text",text:JSON.stringify({error:`Task ID not found: "${i}". Valid IDs: ${l.memos.map(h=>h.id).join(", ")}`})}],isError:!0};l.cursor={taskId:i,step:a,nextAction:c,lastSeenHash:cr(l.body),updatedAt:new Date().toISOString()};let d=Rt(l);return o(s,d),{content:[{type:"text",text:JSON.stringify({cursor:l.cursor},null,2)}]}}catch(u){return{content:[{type:"text",text:JSON.stringify({error:u instanceof Error?u.message:String(u)})}],isError:!0}}})),t.tool("evaluate_gates","Evaluate all gates in a markdown file against current memo statuses. Returns updated gate statuses without modifying the file.",{file:R.string().describe("Path to the annotated markdown file")},async({file:s})=>{try{let i=n(s),a=Oe(i),c=It(a.gates,a.memos);return{content:[{type:"text",text:JSON.stringify({gates:c,summary:{total:c.length,blocked:c.filter(u=>u.status==="blocked").length,proceed:c.filter(u=>u.status==="proceed").length,done:c.filter(u=>u.status==="done").length}},null,2)}]}}catch(i){return{content:[{type:"text",text:JSON.stringify({error:i instanceof Error?i.message:String(i)})}],isError:!0}}}),t.tool("export_review","Export review feedback in a format optimized for a specific AI coding tool. Targets: claude-code, cursor, codex, copilot, cline, windsurf, roo-code, gemini, generic, handoff. Returns formatted markdown ready to save to the appropriate file.",{file:R.string().describe("Path to the annotated markdown file"),target:R.enum(["claude-code","cursor","codex","copilot","cline","windsurf","roo-code","gemini","antigravity","generic","handoff"]).describe("Target AI tool format")},async({file:s,target:i})=>{try{let a=n(s);if(i==="handoff"){let p=_d(a,s);return{content:[{type:"text",text:vd(p,"standalone")}]}}let{memos:c}=hd(a),u=zn(a),l=u[0]||"Plan Review",d={red:"#fca5a5",blue:"#93c5fd",yellow:"#fef08a"},h=c.map(p=>({text:p.anchorText||"",color:d[p.color]||"#fef08a",section:"",context:""})),f=c.map(p=>({id:p.id,text:p.text,color:p.color,section:"",context:p.anchorText||""}));return{content:[{type:"text",text:$y(l,s,u,h,f,i)}]}}catch(a){return{content:[{type:"text",text:JSON.stringify({error:a instanceof Error?a.message:String(a)})}],isError:!0}}}),t.tool("apply_memo","Apply an implementation action to a memo. Supports text_replace (replaces all occurrences in current document), file_patch (overwrites target file \u2014 snapshot saved first), and file_create (create a new file). Creates a snapshot before modification, records the implementation, and updates memo status to done.",{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().describe("The memo ID to apply implementation to"),action:R.enum(["text_replace","file_patch","file_create"]).describe("Type of implementation action"),dryRun:R.boolean().optional().default(!1).describe("If true, return preview without writing"),oldText:R.string().optional().describe("For text_replace: the text to find and replace"),newText:R.string().optional().describe("For text_replace: the replacement text"),targetFile:R.string().optional().describe("For file_patch/file_create: target file path"),patch:R.string().optional().describe("For file_patch: the patch content"),content:R.string().optional().describe("For file_create: the file content to write")},async({file:s,memoId:i,action:a,dryRun:c,oldText:u,newText:l,targetFile:d,patch:h,content:f})=>vt(s,async()=>{try{let m=n(s),p=Oe(m),g=p.memos.find(F=>F.id===i);if(!g)return{content:[{type:"text",text:JSON.stringify({error:`Memo not found: ${i}`})}],isError:!0};let y;if(a==="text_replace"){if(!u||l===void 0)return{content:[{type:"text",text:JSON.stringify({error:"text_replace requires oldText and newText"})}],isError:!0};y={type:"text_replace",file:"",before:u,after:l}}else if(a==="file_patch"){if(!d||!h)return{content:[{type:"text",text:JSON.stringify({error:"file_patch requires targetFile and patch"})}],isError:!0};y={type:"file_patch",file:d,patch:h}}else{if(!d||f===void 0)return{content:[{type:"text",text:JSON.stringify({error:"file_create requires targetFile and content"})}],isError:!0};y={type:"file_create",file:d,content:f}}let v={id:`impl_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,6)}`,memoId:i,status:"applied",operations:[y],summary:`${a} for ${i}`,appliedAt:new Date().toISOString()};if(c)return{content:[{type:"text",text:JSON.stringify({dryRun:!0,impl:v,operation:y,memo:{id:g.id,status:g.status}},null,2)}]};if(Mo(s,m),a==="text_replace"){if(!p.body.includes(u))return{content:[{type:"text",text:JSON.stringify({error:"oldText not found in document body"})}],isError:!0};p.body=p.body.split(u).join(l)}else a==="file_patch"?((0,$d.existsSync)(d)&&Mo(d,Ri(d)),o(d,h)):a==="file_create"&&o(d,f);p.impls.push(v),g.status="needs_review",g.updatedAt=new Date().toISOString(),p.gates.length>0&&(p.gates=It(p.gates,p.memos));let w=p.memos.filter(F=>Le(F.status)).length,b=p.memos.filter(F=>!Le(F.status));p.cursor={taskId:i,step:`${w}/${p.memos.length} resolved`,nextAction:b.length===0?"All annotations resolved \u2014 review complete":`Resolve: ${b.map(F=>F.id).slice(0,3).join(", ")}${b.length>3?"...":""}`,lastSeenHash:cr(p.body),updatedAt:new Date().toISOString()};let $=Rt(p);return o(s,$),{content:[{type:"text",text:JSON.stringify({impl:v,memo:{id:g.id,status:g.status},gatesUpdated:p.gates.length},null,2)}]}}catch(m){return{content:[{type:"text",text:JSON.stringify({error:m instanceof Error?m.message:String(m)})}],isError:!0}}})),t.tool("link_artifacts","Link file artifacts (source files, configs, etc.) to a memo. Creates a MemoArtifact record in the document.",{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().describe("The memo ID to link artifacts to"),files:R.array(R.string()).describe("Array of relative file paths to link")},async({file:s,memoId:i,files:a})=>vt(s,async()=>{try{let c=n(s),u=Oe(c);if(!u.memos.find(f=>f.id===i))return{content:[{type:"text",text:JSON.stringify({error:`Memo not found: ${i}`})}],isError:!0};let d={id:`art_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,6)}`,memoId:i,files:a,linkedAt:new Date().toISOString()};u.artifacts.push(d);let h=Rt(u);return o(s,h),{content:[{type:"text",text:JSON.stringify({artifact:d},null,2)}]}}catch(c){return{content:[{type:"text",text:JSON.stringify({error:c instanceof Error?c.message:String(c)})}],isError:!0}}})),t.tool("update_memo_progress","Update the progress of a memo with a status change and message. Writes progress to .md-feedback/progress.json and updates the memo status. Terminal statuses (done, failed) require human approval via VS Code.",{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().describe("The memo ID to update progress for"),status:R.enum(["in_progress","needs_review"]).describe("New progress status. Terminal statuses (done, failed) require human approval via VS Code."),message:R.string().describe("Progress message describing what was done or what failed")},async({file:s,memoId:i,status:a,message:c})=>vt(s,async()=>{try{let u=n(s),l=Oe(u),d=l.memos.find(g=>g.id===i);if(!d)return{content:[{type:"text",text:JSON.stringify({error:`Memo not found: ${i}`})}],isError:!0};d.status=a,d.updatedAt=new Date().toISOString();let h={memoId:i,status:a,message:c,timestamp:new Date().toISOString()};vy(s,h),l.gates.length>0&&(l.gates=It(l.gates,l.memos));let f=l.memos.filter(g=>Le(g.status)).length,m=l.memos.filter(g=>!Le(g.status));l.cursor={taskId:i,step:`${f}/${l.memos.length} resolved`,nextAction:m.length===0?"All annotations resolved \u2014 review complete":`Resolve: ${m.map(g=>g.id).slice(0,3).join(", ")}${m.length>3?"...":""}`,lastSeenHash:cr(l.body),updatedAt:new Date().toISOString()};let p=Rt(l);return o(s,p),{content:[{type:"text",text:JSON.stringify({memo:{id:d.id,status:d.status},progressEntry:h,gatesUpdated:l.gates.length},null,2)}]}}catch(u){return{content:[{type:"text",text:JSON.stringify({error:u instanceof Error?u.message:String(u)})}],isError:!0}}})),t.tool("rollback_memo","Rollback the latest implementation for a memo. Reverses text_replace operations (swaps before/after), marks the impl as reverted, and sets the memo status back to open.",{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().describe("The memo ID to rollback")},async({file:s,memoId:i})=>vt(s,async()=>{try{let a=n(s),c=Oe(a),u=c.memos.find(p=>p.id===i);if(!u)return{content:[{type:"text",text:JSON.stringify({error:`Memo not found: ${i}`})}],isError:!0};let l=c.impls.filter(p=>p.memoId===i&&p.status==="applied");if(l.length===0)return{content:[{type:"text",text:JSON.stringify({error:`No applied implementation found for memo: ${i}`})}],isError:!0};let d=l[l.length-1];for(let p of d.operations)p.type==="text_replace"&&c.body.includes(p.after)&&(c.body=c.body.split(p.after).join(p.before));d.status="reverted",u.status="open",u.updatedAt=new Date().toISOString(),c.gates.length>0&&(c.gates=It(c.gates,c.memos));let h=c.memos.filter(p=>Le(p.status)).length,f=c.memos.filter(p=>!Le(p.status));c.cursor={taskId:i,step:`${h}/${c.memos.length} resolved`,nextAction:f.length===0?"All annotations resolved \u2014 review complete":`Resolve: ${f.map(p=>p.id).slice(0,3).join(", ")}${f.length>3?"...":""}`,lastSeenHash:cr(c.body),updatedAt:new Date().toISOString()};let m=Rt(c);return o(s,m),{content:[{type:"text",text:JSON.stringify({rolledBack:d.id,memo:{id:u.id,status:u.status},gatesUpdated:c.gates.length},null,2)}]}}catch(a){return{content:[{type:"text",text:JSON.stringify({error:a instanceof Error?a.message:String(a)})}],isError:!0}}})),t.tool("batch_apply","Apply multiple implementation operations in a single transaction. Parses the document once, applies all operations sequentially, then writes once. Each operation follows the same format as apply_memo.",{file:R.string().describe("Path to the annotated markdown file"),operations:R.array(R.object({memoId:R.string().describe("The memo ID to apply implementation to"),action:R.enum(["text_replace","file_patch","file_create"]).describe("Type of implementation action"),oldText:R.string().optional().describe("For text_replace: the text to find and replace"),newText:R.string().optional().describe("For text_replace: the replacement text"),targetFile:R.string().optional().describe("For file_patch/file_create: target file path"),patch:R.string().optional().describe("For file_patch: the patch content"),content:R.string().optional().describe("For file_create: the file content to write")})).describe("Array of operations to apply")},async({file:s,operations:i})=>vt(s,async()=>{try{let a=n(s),c=Oe(a);Mo(s,a);let u=[];for(let f of i){let m=c.memos.find(y=>y.id===f.memoId);if(!m){u.push({memoId:f.memoId,implId:"",status:"error: memo not found"});continue}let p;if(f.action==="text_replace"){if(!f.oldText||f.newText===void 0){u.push({memoId:f.memoId,implId:"",status:"error: text_replace requires oldText and newText"});continue}if(p={type:"text_replace",file:"",before:f.oldText,after:f.newText},!c.body.includes(f.oldText)){u.push({memoId:f.memoId,implId:"",status:"error: oldText not found in body"});continue}c.body=c.body.split(f.oldText).join(f.newText)}else if(f.action==="file_patch"){if(!f.targetFile||!f.patch){u.push({memoId:f.memoId,implId:"",status:"error: file_patch requires targetFile and patch"});continue}p={type:"file_patch",file:f.targetFile,patch:f.patch},(0,$d.existsSync)(f.targetFile)&&Mo(f.targetFile,Ri(f.targetFile)),o(f.targetFile,f.patch)}else{if(!f.targetFile||f.content===void 0){u.push({memoId:f.memoId,implId:"",status:"error: file_create requires targetFile and content"});continue}p={type:"file_create",file:f.targetFile,content:f.content},o(f.targetFile,f.content)}let g={id:`impl_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,6)}`,memoId:f.memoId,status:"applied",operations:[p],summary:`${f.action} for ${f.memoId}`,appliedAt:new Date().toISOString()};c.impls.push(g),m.status="needs_review",m.updatedAt=new Date().toISOString(),u.push({memoId:f.memoId,implId:g.id,status:"applied"})}c.gates.length>0&&(c.gates=It(c.gates,c.memos));let l=c.memos.filter(f=>Le(f.status)).length,d=c.memos.filter(f=>!Le(f.status));c.cursor={taskId:i[0]?.memoId||"",step:`${l}/${c.memos.length} resolved`,nextAction:d.length===0?"All annotations resolved \u2014 review complete":`Resolve: ${d.map(f=>f.id).slice(0,3).join(", ")}${d.length>3?"...":""}`,lastSeenHash:cr(c.body),updatedAt:new Date().toISOString()},xy(s,{type:"batch_apply",results:u,timestamp:new Date().toISOString()});let h=Rt(c);return o(s,h),{content:[{type:"text",text:JSON.stringify({results:u,gatesUpdated:c.gates.length,cursor:c.cursor},null,2)}]}}catch(a){return{content:[{type:"text",text:JSON.stringify({error:a instanceof Error?a.message:String(a)})}],isError:!0}}})),t.tool("get_memo_changes","Get the implementation history and progress for a memo. Returns all MemoImpl records and progress entries from .md-feedback/progress.json. If memoId is omitted, returns all changes.",{file:R.string().describe("Path to the annotated markdown file"),memoId:R.string().optional().describe("Optional memo ID to filter by \u2014 if omitted, returns all changes")},async({file:s,memoId:i})=>{try{let a=n(s),c=Oe(a),u=i?c.impls.filter(h=>h.memoId===i):c.impls,l=fd(s),d=i?l.filter(h=>h.memoId===i):l;return{content:[{type:"text",text:JSON.stringify({impls:u,progress:d},null,2)}]}}catch(a){return{content:[{type:"text",text:JSON.stringify({error:a instanceof Error?a.message:String(a)})}],isError:!0}}})}function wd(t){process.stderr.write(`[md-feedback] ${t}
80
+ `)}function wE(){let t=process.argv.find(e=>e.startsWith("--workspace="));return t?t.split("=")[1]:process.env.MD_FEEDBACK_WORKSPACE||void 0}var My=wE(),Cy=new Ti({name:"md-feedback",version:"1.3.3"});Oy(Cy,My);async function kE(){let t=new Pi;await Cy.connect(t);let e=My||process.cwd();wd(`v1.3.3 ready (stdio) workspace=${e}`)}kE().catch(t=>{wd(`fatal: ${t}`),process.exit(1)});0&&(module.exports={log});
package/package.json CHANGED
@@ -1,73 +1,73 @@
1
- {
2
- "name": "md-feedback",
3
- "version": "1.3.0",
4
- "description": "MCP server for markdown plan review — AI agents read annotations, mark tasks done, evaluate quality gates, and generate session handoffs. 19 tools for Claude Code, Cursor, Copilot, and 8 more AI tools.",
5
- "license": "SUL-1.0",
6
- "author": "Yeomin Seon",
7
- "type": "commonjs",
8
- "bin": {
9
- "md-feedback": "./bin/md-feedback.cjs"
10
- },
11
- "files": [
12
- "dist/mcp-server.js",
13
- "bin/md-feedback.cjs",
14
- "README.md"
15
- ],
16
- "scripts": {
17
- "build": "node esbuild.mjs"
18
- },
19
- "dependencies": {
20
- "@modelcontextprotocol/sdk": "^1.12.0",
21
- "zod": "^3.23.0"
22
- },
23
- "devDependencies": {
24
- "esbuild": "^0.24.2",
25
- "typescript": "^5.7.0"
26
- },
27
- "engines": {
28
- "node": ">=18"
29
- },
30
- "repository": {
31
- "type": "git",
32
- "url": "https://github.com/yeominux/md-feedback"
33
- },
34
- "bugs": {
35
- "url": "https://github.com/yeominux/md-feedback/issues"
36
- },
37
- "homepage": "https://github.com/yeominux/md-feedback#mcp-server",
38
- "keywords": [
39
- "mcp",
40
- "mcp-server",
41
- "model-context-protocol",
42
- "markdown",
43
- "feedback",
44
- "ai",
45
- "annotation",
46
- "review",
47
- "plan-review",
48
- "ai-agent",
49
- "coding-workflow",
50
- "handoff",
51
- "session-handoff",
52
- "structured-feedback",
53
- "checkpoint",
54
- "ai-context",
55
- "ai-coding",
56
- "claude-code",
57
- "cursor-ai",
58
- "vibe-coding",
59
- "context-engineering",
60
- "gates",
61
- "plan-review-tool",
62
- "document-annotation",
63
- "code-review",
64
- "ai-workflow",
65
- "copilot",
66
- "markdown-review",
67
- "developer-tools",
68
- "quality-gate",
69
- "file-safety",
70
- "concurrent-safety",
71
- "gate-override"
72
- ]
73
- }
1
+ {
2
+ "name": "md-feedback",
3
+ "version": "1.3.3",
4
+ "description": "MCP server for markdown plan review — AI agents read annotations, mark tasks done, evaluate quality gates, and generate session handoffs. 19 tools for Claude Code, Cursor, Copilot, and 8 more AI tools.",
5
+ "license": "SUL-1.0",
6
+ "author": "Yeomin Seon",
7
+ "type": "commonjs",
8
+ "bin": {
9
+ "md-feedback": "./bin/md-feedback.cjs"
10
+ },
11
+ "files": [
12
+ "dist/mcp-server.js",
13
+ "bin/md-feedback.cjs",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "node esbuild.mjs"
18
+ },
19
+ "dependencies": {
20
+ "@modelcontextprotocol/sdk": "^1.12.0",
21
+ "zod": "^3.23.0"
22
+ },
23
+ "devDependencies": {
24
+ "esbuild": "^0.24.2",
25
+ "typescript": "^5.7.0"
26
+ },
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/yeominux/md-feedback"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/yeominux/md-feedback/issues"
36
+ },
37
+ "homepage": "https://github.com/yeominux/md-feedback#mcp-server",
38
+ "keywords": [
39
+ "mcp",
40
+ "mcp-server",
41
+ "model-context-protocol",
42
+ "markdown",
43
+ "feedback",
44
+ "ai",
45
+ "annotation",
46
+ "review",
47
+ "plan-review",
48
+ "ai-agent",
49
+ "coding-workflow",
50
+ "handoff",
51
+ "session-handoff",
52
+ "structured-feedback",
53
+ "checkpoint",
54
+ "ai-context",
55
+ "ai-coding",
56
+ "claude-code",
57
+ "cursor-ai",
58
+ "vibe-coding",
59
+ "context-engineering",
60
+ "gates",
61
+ "plan-review-tool",
62
+ "document-annotation",
63
+ "code-review",
64
+ "ai-workflow",
65
+ "copilot",
66
+ "markdown-review",
67
+ "developer-tools",
68
+ "quality-gate",
69
+ "file-safety",
70
+ "concurrent-safety",
71
+ "gate-override"
72
+ ]
73
+ }