md-feedback 1.3.10 → 1.3.12

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 +2 -2
  2. package/package.json +55 -55
@@ -66,7 +66,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
66
66
  `;return{checkpoint:o,updatedMarkdown:a}}function Gi(t,e){let r=en(t),n=Zn(t),o=tn(t),s=rn(t),i={file:e,startedAt:r.length>0?r[0].timestamp:new Date().toISOString(),lastCheckpoint:r.length>0?r[r.length-1].timestamp:"",checkpointCount:r.length,totalFixes:n.fixes,totalQuestions:n.questions,totalHighlights:n.highlights},a=[],c=[],u=[];hP(t,a,c,u);let d=[];for(let h of c)d.push(`Resolve: ${h.feedback||h.text}`);let l=s.filter(h=>!o.includes(h));for(let h of l)d.push(`Review uncovered: ${h} section`);return{meta:i,decisions:a,openQuestions:c,keyPoints:u,checkpoints:r,nextSteps:d}}function hP(t,e,r,n){let o=t.split(`
67
67
  `),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="([^"]+)"/),m=h?h[1]:"red",p=u[2].replace(/--\u200B>/g,"-->"),f="";for(let g=i-1;g>=0;g--)if(o[g].trim()&&!o[g].includes("<!-- ")){f=o[g].trim(),f=f.replace(/<\/?mark[^>]*>/g,"");break}let y={section:s,text:f,feedback:p};m==="red"?e.push(y):m==="blue"?r.push(y):n.push(y);continue}if(/^<!-- USER_MEMO\s*$/.test(a.trim())){let l=[];for(i++;i<o.length&&!/^-->$/.test(o[i].trim());)l.push(o[i]),i++;let h={};for(let g of l){let _=g.trim().match(/^(\w+)="([^"]*)"$/);_&&(h[_[1]]=_[2])}let m=h.color||"red",p=(h.text||"").replace(/--\u200B>/g,"-->"),f="";if(h.anchorText)f=h.anchorText.replace(/<\/?mark[^>]*>/g,"");else for(let g=i-1;g>=0;g--)if(o[g].trim()&&!o[g].includes("<!-- ")){f=o[g].trim().replace(/<\/?mark[^>]*>/g,"");break}let y={section:s,text:f,feedback:p};m==="red"?e.push(y):m==="blue"?r.push(y):n.push(y);continue}let d=a.match(/<mark[^>]*(?:data-color="([^"]+)"|style="background-color:\s*([^"]+)")?\s*>(.*?)<\/mark>/)||a.match(/==(.*?)==/);if(d){if((i+1<o.length?o[i+1]:"").includes("<!-- USER_MEMO"))continue;let h,m;if(d.length===2)h=d[1],m="yellow";else{let f=(d[1]||d[2]||"").trim();m=yr[f]||"yellow",h=d[3]}let p={section:s,text:h,feedback:""};m==="red"?e.push(p):m==="blue"?r.push(p):n.push(p)}}}function Wi(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}** "${ze(o.text,60)}" \u2192 ${o.feedback}`):o.feedback?r.push(`${n+1}. **${s}** ${o.feedback}`):o.text&&r.push(`${n+1}. **${s}** "${ze(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}** "${ze(o.text,60)}" \u2014 ${o.feedback}`):o.feedback?r.push(`${n+1}. **${s}** ${o.feedback}`):o.text&&r.push(`${n+1}. **${s}** "${ze(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}** "${ze(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(`
68
68
  `)}function t_(t){let e=t.split(`
69
- `),r="",n={file:"",startedAt:"",lastCheckpoint:"",checkpointCount:0,totalFixes:0,totalQuestions:0,totalHighlights:0},o=[],s=[],i=[],a=[],c=[];for(let u of e){let d=u.match(/^## (.+)/);if(d){r=d[1].trim();continue}if(r.startsWith("Session")){let h=u.match(/\*\*File\*\*:\s*`([^`]+)`/);h&&(n.file=h[1]);let m=u.match(/\*\*Started\*\*:\s*(.+)/);m&&(n.startedAt=m[1].trim());let p=u.match(/\*\*Last checkpoint\*\*:\s*(.+)/);p&&(n.lastCheckpoint=p[1].trim());let f=u.match(/\*\*Checkpoints\*\*:\s*(\d+)/);f&&(n.checkpointCount=parseInt(f[1],10));let y=u.match(/\*\*Annotations\*\*:\s*(\d+)\s*fix,\s*(\d+)\s*question,\s*(\d+)\s*highlight/);y&&(n.totalFixes=parseInt(y[1],10),n.totalQuestions=parseInt(y[2],10),n.totalHighlights=parseInt(y[3],10))}let l=u.match(/^\d+\.\s+\*\*\[([^\]]*)\]\*\*\s+(.+)/);if(l){let h=l[1],m=l[2],p=m.match(/"([^"]+)"\s*→\s*(.+)/),f=m.match(/"([^"]+)"\s*—\s*(.+)/),y;if(p)y={section:h,text:p[1],feedback:p[2]};else if(f)y={section:h,text:f[1],feedback:f[2]};else{let g=m.match(/"([^"]+)"/);y={section:h,text:g?g[1]:"",feedback:g?"":m}}r.startsWith("Decisions")?o.push(y):r.startsWith("Open Questions")?s.push(y):r.startsWith("Key Points")&&i.push(y)}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 gP(t,e){return t.blockedBy.length>0&&t.blockedBy.map(o=>e.find(s=>s.id===o)).filter(o=>o!=null&&!zt(o.status)).length>0?"blocked":e.some(n=>!zt(n.status))?"proceed":"done"}function Pt(t,e){return t.map(r=>({...r,status:r.override||gP(r,e)}))}function r_(t,e,r,n,o,s){let i={},a={},c=0;for(let x of t)i[x.status]=(i[x.status]||0)+1,a[x.type]=(a[x.type]||0)+1,zt(x.status)&&c++;let u=t.length>0?c/t.length:0,d={},l=0,h=0,m=0;for(let x of e)d[x.status]=(d[x.status]||0)+1,x.status==="applied"&&l++,x.status==="reverted"&&h++,x.status==="failed"&&m++;let p={};for(let x of r)p[x.status]=(p[x.status]||0)+1;let f=new Set;for(let x of o)for(let S of x.files)f.add(S);let y=s.filter(x=>x.type==="blocks").length,g=null;n.length>0&&(g=n.reduce((x,S)=>S.timestamp>x?S.timestamp:x,n[0].timestamp));let _=null,v=t.filter(x=>zt(x.status)&&x.createdAt&&x.updatedAt);if(v.length>0){let x=0,S=0;for(let Z of v){let I=new Date(Z.createdAt).getTime(),Q=new Date(Z.updatedAt).getTime();!isNaN(I)&&!isNaN(Q)&&Q>I&&(x+=Q-I,S++)}S>0&&(_=x/S)}return{totalMemos:t.length,byStatus:i,byType:a,resolutionRate:u,totalImpls:e.length,implsByStatus:d,appliedCount:l,revertedCount:h,failedCount:m,totalGates:r.length,gatesByStatus:p,totalArtifacts:o.length,linkedFiles:f.size,totalDependencies:s.length,blockingChains:y,totalCheckpoints:n.length,lastCheckpoint:g,avgResolutionTime:_}}var at=class extends Error{constructor(r,n,o){super(n);this.code=r;this.details=o;this.name="ToolError"}},Bo=class extends at{constructor(e,r){super("FILE_SAFETY",e,r),this.name="FileSafetyError"}},Bi=class extends at{constructor(e){super("FILE_NOT_FOUND",`File not found: ${e}`,{file:e}),this.name="FileNotFoundError"}},Ji=class extends at{constructor(e,r){super("FILE_READ",`Cannot read file ${e}${r?`: ${r}`:""}`,{file:e,...r?{cause:r}:{}}),this.name="FileReadError"}},Ki=class extends at{constructor(e,r){super("FILE_WRITE",`Cannot write file ${e}${r?`: ${r}`:""}`,{file:e,...r?{cause:r}:{}}),this.name="FileWriteError"}},Qi=class extends at{constructor(e){super("FILE_LOCK_TIMEOUT",`Timeout acquiring lock: ${e}`,{lockPath:e}),this.name="FileLockTimeoutError"}},vr=class extends at{constructor(e,r){super("PATCH_APPLY",e,r),this.name="PatchApplyError"}},Rt=class extends at{constructor(e){super("MEMO_NOT_FOUND",`Memo not found: ${e}`,{memoId:e}),this.name="MemoNotFoundError"}},ve=class extends at{constructor(e,r){super("OPERATION_INVALID",e,r),this.name="OperationValidationError"}},Jo=class extends at{constructor(e,r){super("ANCHOR_NOT_FOUND",`Anchor text not found: "${e}"`,{anchorText:e,matchCount:0,...r??{}}),this.name="AnchorNotFoundError"}},Xi=class extends at{constructor(e){super("HANDOFF_INVALID","Not a valid handoff document",{file:e}),this.name="InvalidHandoffError"}};function Wd(t){return t instanceof at?{error:t.message,code:t.code,type:t.name,...t.details?{details:t.details}:{}}:t instanceof Error?{error:t.message}:{error:String(t)}}var n_={respond_to_memo:{allowedMemoTypes:["question"],guidance:"Use apply_memo/update_memo_progress for fix memos."},apply_memo:{allowedMemoTypes:["fix"],guidance:"Use respond_to_memo for question memos."},batch_apply:{allowedMemoTypes:["fix"],guidance:"Use respond_to_memo for question memos."}},yP={...n_};function o_(t=process.env){return t.MD_FEEDBACK_POLICY_PROFILE?.trim().toLowerCase()==="strict"?"strict":"default"}function s_(t){return t==="strict"?yP:n_}function i_(t=o_()){let e=s_(t);return{profile:t,memoActions:{respond_to_memo:{allowedMemoTypes:[...e.respond_to_memo.allowedMemoTypes]},apply_memo:{allowedMemoTypes:[...e.apply_memo.allowedMemoTypes]},batch_apply:{allowedMemoTypes:[...e.batch_apply.allowedMemoTypes]}}}}function Yi(t,e,r,n=o_()){let s=s_(n)[t];if(!s.allowedMemoTypes.includes(r))throw new ve(`${t} supports only ${s.allowedMemoTypes.join(", ")} memos. ${s.guidance}`,{memoId:e,memoType:r,action:t,policyProfile:n,allowedMemoTypes:s.allowedMemoTypes})}var yt=require("node:fs"),Qd=require("node:path"),ra=require("node:crypto");var se=require("fs"),Ne=require("path"),Bd=require("node:crypto");var _P=2e3,vP=10,xP=20;function Jd(t){return(0,Ne.isAbsolute)(t)?t:(0,Ne.resolve)(process.cwd(),t)}function ta(t){let e=Jd(t);if(!(0,se.existsSync)(e))throw new Bi(e);try{return(0,se.readFileSync)(e,"utf-8")}catch(r){throw new Ji(e,r instanceof Error?r.message:String(r))}}function a_(t,e){let r=Jd(t),n=(0,Ne.dirname)(r),o=(0,Ne.join)(n,`.mf-tmp-${(0,Bd.randomBytes)(6).toString("hex")}`);try{(0,se.writeFileSync)(o,e,"utf-8"),(0,se.renameSync)(o,r)}catch(s){try{(0,se.unlinkSync)(o)}catch{}throw new Ki(r,s instanceof Error?s.message:String(s))}}var ea=new Map;async function bP(t,e){for(;ea.has(t);)await ea.get(t);let r;ea.set(t,new Promise(n=>{r=n}));try{return await e()}finally{ea.delete(t),r()}}function wP(t){return new Promise(e=>setTimeout(e,t))}async function $P(t,e=_P){let r=Date.now();for(;;)try{let n=(0,se.openSync)(t,"wx");return()=>{try{(0,se.closeSync)(n)}catch{}try{(0,se.unlinkSync)(t)}catch{}}}catch(n){if(n.code!=="EEXIST")throw n;if(Date.now()-r>e)throw new Qi(t);await wP(vP)}}function nn(t){let e=Jd(t),r=(0,Ne.join)((0,Ne.dirname)(e),".md-feedback");return(0,se.existsSync)(r)||(0,se.mkdirSync)(r,{recursive:!0}),r}function Ko(t,e){let r=nn(t),n=(0,Ne.join)(r,"snapshots");(0,se.existsSync)(n)||(0,se.mkdirSync)(n,{recursive:!0});let o=new Date().toISOString().replace(/[:.]/g,"-"),s=(0,Ne.join)(n,`snapshot-${o}.md`);(0,se.writeFileSync)(s,e,"utf-8");let i=(0,se.readdirSync)(n).filter(a=>a.startsWith("snapshot-")).sort();for(;i.length>xP;)try{(0,se.unlinkSync)((0,Ne.join)(n,i.shift()))}catch{}return s}function c_(t){let e=nn(t),r=(0,Ne.join)(e,"progress.json");if(!(0,se.existsSync)(r))return[];try{return JSON.parse((0,se.readFileSync)(r,"utf-8"))}catch{return[]}}async function u_(t,e){let r=nn(t),n=(0,Ne.join)(r,"progress.json"),o=`${n}.lock`;await bP(n,async()=>{let s=await $P(o);try{let i=[];if((0,se.existsSync)(n))try{i=JSON.parse((0,se.readFileSync)(n,"utf-8"))}catch{i=[]}i.push(e);let a=`${n}.tmp-${(0,Bd.randomBytes)(6).toString("hex")}`;(0,se.writeFileSync)(a,JSON.stringify(i,null,2),"utf-8"),(0,se.renameSync)(a,n)}finally{s()}})}function l_(t,e){let r=nn(t),n=(0,Ne.join)(r,"transactions");(0,se.existsSync)(n)||(0,se.mkdirSync)(n,{recursive:!0});let o=new Date().toISOString().replace(/[:.]/g,"-"),s=(0,Ne.join)(n,`tx-${o}.json`);return(0,se.writeFileSync)(s,JSON.stringify(e,null,2),"utf-8"),s}var Qo=["scope","root_cause","implementation","verification"],kP={create_annotation:["scope","root_cause"],respond_to_memo:["root_cause","implementation"],apply_memo:["implementation"],batch_apply:["implementation"],rollback_memo:["implementation"],update_memo_status:["implementation","verification"],update_memo_progress:["implementation","verification"],link_artifacts:["implementation","verification"],update_cursor:["implementation","verification"],create_checkpoint:["verification"],set_memo_severity:["scope","root_cause","implementation","verification"],request_approval_checkpoint:["scope","root_cause","implementation","verification"],approve_checkpoint:["scope","root_cause","implementation","verification"]},SP=new Set(["batch_apply","rollback_memo"]);function d_(t){return(0,Qd.join)(nn(t),"workflow.json")}function p_(t){return(0,Qd.join)(nn(t),"severity.json")}function f_(t=process.env){return t.MD_FEEDBACK_WORKFLOW_ENFORCEMENT?.trim().toLowerCase()==="strict"?"strict":"off"}function m_(t){let e=p_(t);if(!(0,yt.existsSync)(e))return{version:"1.0",overrides:{},updatedAt:new Date().toISOString()};try{let r=JSON.parse((0,yt.readFileSync)(e,"utf-8"));return{version:"1.0",overrides:r.overrides&&typeof r.overrides=="object"?r.overrides:{},updatedAt:typeof r.updatedAt=="string"?r.updatedAt:new Date().toISOString()}}catch{return{version:"1.0",overrides:{},updatedAt:new Date().toISOString()}}}function TP(t,e){let r=p_(t),n=`${r}.tmp-${(0,ra.randomBytes)(6).toString("hex")}`;(0,yt.writeFileSync)(n,JSON.stringify(e,null,2),"utf-8"),(0,yt.renameSync)(n,r)}function zP(t){return t==="fix"?"blocking":"non_blocking"}function h_(t,e,r){let o={version:"1.0",overrides:{...m_(t).overrides,[e]:r},updatedAt:new Date().toISOString()};return TP(t,o),o}function Xd(t){let e=ta(t),r=we(e),n=m_(t).overrides,o=r.memos.filter(s=>!zt(s.status)).filter(s=>(n[s.id]??zP(s.type))==="blocking").map(s=>s.id);return{overrides:n,unresolvedBlockingMemos:o}}function na(t,e){let r=d_(t),n=`${r}.tmp-${(0,ra.randomBytes)(6).toString("hex")}`;(0,yt.writeFileSync)(n,JSON.stringify(e,null,2),"utf-8"),(0,yt.renameSync)(n,r)}function Kd(){return{version:"1.0",phase:"scope",status:"active",transitions:[],pendingCheckpoint:null,approvals:[],approvalGrant:null,updatedAt:new Date().toISOString()}}function EP(t){let e=d_(t);if(!(0,yt.existsSync)(e))return Kd();try{let r=JSON.parse((0,yt.readFileSync)(e,"utf-8")),n=r.phase,o=Array.isArray(r.transitions)?r.transitions:[];return!n||!Qo.includes(n)?Kd():{version:"1.0",phase:n,status:"active",transitions:o,pendingCheckpoint:r.pendingCheckpoint&&typeof r.pendingCheckpoint=="object"?r.pendingCheckpoint:null,approvals:Array.isArray(r.approvals)?r.approvals:[],approvalGrant:r.approvalGrant&&typeof r.approvalGrant=="object"?r.approvalGrant:null,updatedAt:typeof r.updatedAt=="string"?r.updatedAt:new Date().toISOString()}}catch{return Kd()}}function xr(t){let e=EP(t);return e.transitions.length===0&&e.phase==="scope"&&na(t,e),e}function nt(t,e){let r=xr(t);if(f_()!=="strict")return r;let n=kP[e];if(!n||n.includes(r.phase))return r;throw new ve(`Tool "${e}" is not allowed in phase "${r.phase}".`,{tool:e,currentPhase:r.phase,allowedPhases:n})}function g_(t,e){let r=xr(t),n=e(r);return na(t,n),n}function Yd(t,e,r){return g_(t,n=>n.pendingCheckpoint&&n.pendingCheckpoint.tool===e?n:{...n,pendingCheckpoint:{id:`chk_${(0,ra.randomBytes)(4).toString("hex")}`,tool:e,reason:r,requestedAt:new Date().toISOString()},updatedAt:new Date().toISOString()})}function y_(t,e,r,n){return g_(t,o=>{let s=o.pendingCheckpoint;if(!s||s.tool!==e)throw new ve(`No pending checkpoint for tool "${e}".`,{tool:e,pendingCheckpoint:s});let i={checkpointId:s.id,tool:e,approvedBy:r,reason:n,approvedAt:new Date().toISOString()};return{...o,pendingCheckpoint:null,approvals:[...o.approvals,i],approvalGrant:{checkpointId:s.id,tool:e,approvedBy:r,approvedAt:i.approvedAt,consumed:!1},updatedAt:new Date().toISOString()}})}function ep(t,e,r){if(f_()!=="strict"||!SP.has(e))return xr(t);let n=xr(t);if(n.approvalGrant&&!n.approvalGrant.consumed&&n.approvalGrant.tool===e){let s={...n,approvalGrant:{...n.approvalGrant,consumed:!0},updatedAt:new Date().toISOString()};return na(t,s),s}let o=Yd(t,e,r);throw new ve(`Approval required before high-risk tool "${e}".`,{tool:e,reason:r,pendingCheckpoint:o.pendingCheckpoint})}function __(t,e,r,n){let o=xr(t);if(e===o.phase)return o;let s=Qo.indexOf(o.phase);if(Qo.indexOf(e)!==s+1)throw new ve(`Invalid workflow transition: ${o.phase} -> ${e}. Expected next phase: ${Qo[s+1]??"none"}.`,{currentPhase:o.phase,requestedPhase:e,expectedNextPhase:Qo[s+1]??null});if(e==="verification"){let c=Xd(t);if(c.unresolvedBlockingMemos.length>0)throw new ve("Cannot advance to verification with unresolved blocking memos.",{currentPhase:o.phase,requestedPhase:e,unresolvedBlockingMemos:c.unresolvedBlockingMemos})}let a={...o,phase:e,updatedAt:new Date().toISOString(),transitions:[...o.transitions,{from:o.phase,to:e,tool:r,note:n,timestamp:new Date().toISOString()}]};return na(t,a),a}function v_(t,e){let{safeRead:r,wrapTool:n}=e;t.tool("get_policy_status","Return current runtime policy profile and memo-action routing rules.",{},async()=>n(async()=>{let o=i_();return{content:[{type:"text",text:JSON.stringify({policy:o},null,2)}]}})),t.tool("get_workflow_status","Return current workflow phase and transition history for a document.",{file:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{r(o);let s=xr(o);return{content:[{type:"text",text:JSON.stringify({workflow:s},null,2)}]}})),t.tool("get_severity_status","Return memo severity overrides and unresolved blocking memo IDs for the document.",{file:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{r(o);let s=Xd(o);return{content:[{type:"text",text:JSON.stringify({severity:s},null,2)}]}})),t.tool("get_checkpoints","List all checkpoints in an annotated markdown file.",{file:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{let s=r(o),i=en(s);return{content:[{type:"text",text:JSON.stringify({checkpoints:i},null,2)}]}})),t.tool("generate_handoff","Generate a structured handoff document from an annotated markdown file. Anti-compression format: explicit fields, numbers, lists only.",{file:T.string().describe("Path to the annotated markdown file"),target:T.enum(["standalone","claude-md","cursor-rules"]).optional().describe("Output format target (default: standalone)")},async({file:o,target:s})=>n(async()=>{let i=r(o),a=Gi(i,o);return{content:[{type:"text",text:Wi(a,s||"standalone")}]}})),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:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{let s=r(o),i=Zn(s),a=en(s),c=tn(s),u={file:o,annotations:i,checkpointCount:a.length,lastCheckpoint:a.length>0?a[a.length-1].timestamp:null,sectionsReviewed:c};return{content:[{type:"text",text:JSON.stringify(u,null,2)}]}})),t.tool("pickup_handoff","Parse an existing handoff document to resume a review session. Returns structured data for session continuity.",{file:T.string().describe("Path to the handoff markdown file")},async({file:o})=>n(async()=>{let s=r(o),i=t_(s);if(!i)throw new Xi(o);return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}})),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:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{let s=r(o),a=we(s).memos.map(c=>({id:c.id,type:c.type,status:c.status,owner:c.owner,source:c.source,color:c.color,text:c.text,anchorText:c.anchorText,anchor:c.anchor,createdAt:c.createdAt,updatedAt:c.updatedAt}));return{content:[{type:"text",text:JSON.stringify({annotations:a,total:a.length},null,2)}]}})),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:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{let s=r(o),i=we(s),a=rn(s),c=tn(s),u=Pt(i.gates,i.memos),d=0,l=0,h=0,m=0,p=0,f=0,y=0,g=0,_=0,v=0;for(let I of i.memos)I.status==="open"?d++:I.status==="in_progress"?l++:I.status==="needs_review"?h++:I.status==="answered"?m++:I.status==="done"?p++:I.status==="failed"?f++:I.status==="wontfix"&&y++,I.type==="fix"?g++:I.type==="question"?_++:v++;let x=0;for(let I of u)I.status==="blocked"&&x++;let S={version:"0.4.0",file:o,bodyMd:i.body,memos:i.memos,checkpoints:i.checkpoints,gates:u,cursor:i.cursor,sections:{all:a,reviewed:c,uncovered:a.filter(I=>!c.includes(I))},impls:i.impls,artifacts:i.artifacts,dependencies:i.dependencies,summary:{total:i.memos.length,open:d,inProgress:l,needsReview:h,answered:m,done:p,failed:f,wontfix:y,blocked:x,fixes:g,questions:_,highlights:v}},Z=r_(i.memos,i.impls,u,i.checkpoints,i.artifacts,i.dependencies);return{content:[{type:"text",text:JSON.stringify({...S,metrics:Z},null,2)}]}})),t.tool("evaluate_gates","Evaluate all gates in a markdown file against current memo statuses. Returns updated gate statuses without modifying the file.",{file:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{let s=r(o),i=we(s),a=Pt(i.gates,i.memos);return{content:[{type:"text",text:JSON.stringify({gates:a,summary:{total:a.length,blocked:a.filter(c=>c.status==="blocked").length,proceed:a.filter(c=>c.status==="proceed").length,done:a.filter(c=>c.status==="done").length}},null,2)}]}}))}function x_(t,e){let{safeRead:r,wrapTool:n}=e;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:T.string().describe("Path to the annotated markdown file"),target:T.enum(["claude-code","cursor","codex","copilot","cline","windsurf","roo-code","gemini","antigravity","generic","handoff"]).describe("Target AI tool format")},async({file:o,target:s})=>n(async()=>{let i=r(o);if(s==="handoff"){let f=Gi(i,o);return{content:[{type:"text",text:Wi(f,"standalone")}]}}let c=we(i).memos,u=rn(i),d=u[0]||"Plan Review",l={red:"#fca5a5",blue:"#93c5fd",yellow:"#fef08a"},h=c.map(f=>({text:f.anchorText||"",color:l[f.color]||"#fef08a",section:"",context:""})),m=c.map(f=>({id:f.id,text:f.text,color:f.color,section:"",context:f.anchorText||""}));return{content:[{type:"text",text:e_(d,o,u,h,m,s)}]}})),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:T.string().describe("Path to the annotated markdown file"),memoId:T.string().optional().describe("Optional memo ID to filter by \u2014 if omitted, returns all changes")},async({file:o,memoId:s})=>n(async()=>{let i=r(o),a=we(i),c=s?a.impls.filter(l=>l.memoId===s):a.impls,u=c_(o),d=s?u.filter(l=>l.memoId===s):u;return{content:[{type:"text",text:JSON.stringify({impls:c,progress:d},null,2)}]}}))}function b_(t,e){v_(t,e),x_(t,e)}var on=require("node:fs");var w_=Ln(require("node:path")),oa=new Map;async function Ge(t,e){let r=w_.default.resolve(t);for(;oa.has(r);)await oa.get(r);let n;oa.set(r,new Promise(o=>{n=o}));try{return await e()}finally{oa.delete(r),n()}}function $_(t,e){let{safeRead:r,safeWrite:n,wrapTool:o,ensureDefaultGate:s,updateCursorFromMemos:i,applyUnifiedDiff:a}=e;t.tool("create_checkpoint","Create a review checkpoint in an annotated markdown file. Records current annotation counts and reviewed sections.",{file:T.string().describe("Path to the annotated markdown file"),note:T.string().describe('Checkpoint note (e.g., "Phase 1 review done")')},async({file:c,note:u})=>Ge(c,async()=>o(async()=>{nt(c,"create_checkpoint");let d=r(c),{checkpoint:l,updatedMarkdown:h}=Gd(d,u);return n(c,h),{content:[{type:"text",text:JSON.stringify({checkpoint:l},null,2)}]}}))),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:T.string().describe("Path to the annotated markdown file"),anchorText:T.string().describe("The exact text in the document to annotate (must exist in the file)"),type:T.enum(["fix","question","highlight"]).describe("fix = needs change, question = needs clarification, highlight = mark for reference"),text:T.string().describe("The review feedback or note to attach"),occurrence:T.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:c,anchorText:u,type:d,text:l,occurrence:h})=>Ge(c,async()=>o(async()=>{nt(c,"create_annotation");let m=r(c),p=we(m),f=p.body.split(`
69
+ `),r="",n={file:"",startedAt:"",lastCheckpoint:"",checkpointCount:0,totalFixes:0,totalQuestions:0,totalHighlights:0},o=[],s=[],i=[],a=[],c=[];for(let u of e){let d=u.match(/^## (.+)/);if(d){r=d[1].trim();continue}if(r.startsWith("Session")){let h=u.match(/\*\*File\*\*:\s*`([^`]+)`/);h&&(n.file=h[1]);let m=u.match(/\*\*Started\*\*:\s*(.+)/);m&&(n.startedAt=m[1].trim());let p=u.match(/\*\*Last checkpoint\*\*:\s*(.+)/);p&&(n.lastCheckpoint=p[1].trim());let f=u.match(/\*\*Checkpoints\*\*:\s*(\d+)/);f&&(n.checkpointCount=parseInt(f[1],10));let y=u.match(/\*\*Annotations\*\*:\s*(\d+)\s*fix,\s*(\d+)\s*question,\s*(\d+)\s*highlight/);y&&(n.totalFixes=parseInt(y[1],10),n.totalQuestions=parseInt(y[2],10),n.totalHighlights=parseInt(y[3],10))}let l=u.match(/^\d+\.\s+\*\*\[([^\]]*)\]\*\*\s+(.+)/);if(l){let h=l[1],m=l[2],p=m.match(/"([^"]+)"\s*→\s*(.+)/),f=m.match(/"([^"]+)"\s*—\s*(.+)/),y;if(p)y={section:h,text:p[1],feedback:p[2]};else if(f)y={section:h,text:f[1],feedback:f[2]};else{let g=m.match(/"([^"]+)"/);y={section:h,text:g?g[1]:"",feedback:g?"":m}}r.startsWith("Decisions")?o.push(y):r.startsWith("Open Questions")?s.push(y):r.startsWith("Key Points")&&i.push(y)}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 gP(t,e){return t.blockedBy.length>0&&t.blockedBy.map(o=>e.find(s=>s.id===o)).filter(o=>o!=null&&!zt(o.status)).length>0?"blocked":e.some(n=>!zt(n.status))?"proceed":"done"}function Pt(t,e){return t.map(r=>({...r,status:r.override||gP(r,e)}))}function r_(t,e,r,n,o,s){let i={},a={},c=0;for(let x of t)i[x.status]=(i[x.status]||0)+1,a[x.type]=(a[x.type]||0)+1,zt(x.status)&&c++;let u=t.length>0?c/t.length:0,d={},l=0,h=0,m=0;for(let x of e)d[x.status]=(d[x.status]||0)+1,x.status==="applied"&&l++,x.status==="reverted"&&h++,x.status==="failed"&&m++;let p={};for(let x of r)p[x.status]=(p[x.status]||0)+1;let f=new Set;for(let x of o)for(let S of x.files)f.add(S);let y=s.filter(x=>x.type==="blocks").length,g=null;n.length>0&&(g=n.reduce((x,S)=>S.timestamp>x?S.timestamp:x,n[0].timestamp));let _=null,v=t.filter(x=>zt(x.status)&&x.createdAt&&x.updatedAt);if(v.length>0){let x=0,S=0;for(let Z of v){let I=new Date(Z.createdAt).getTime(),Q=new Date(Z.updatedAt).getTime();!isNaN(I)&&!isNaN(Q)&&Q>I&&(x+=Q-I,S++)}S>0&&(_=x/S)}return{totalMemos:t.length,byStatus:i,byType:a,resolutionRate:u,totalImpls:e.length,implsByStatus:d,appliedCount:l,revertedCount:h,failedCount:m,totalGates:r.length,gatesByStatus:p,totalArtifacts:o.length,linkedFiles:f.size,totalDependencies:s.length,blockingChains:y,totalCheckpoints:n.length,lastCheckpoint:g,avgResolutionTime:_}}var at=class extends Error{constructor(r,n,o){super(n);this.code=r;this.details=o;this.name="ToolError"}},Bo=class extends at{constructor(e,r){super("FILE_SAFETY",e,r),this.name="FileSafetyError"}},Bi=class extends at{constructor(e){super("FILE_NOT_FOUND",`File not found: ${e}`,{file:e}),this.name="FileNotFoundError"}},Ji=class extends at{constructor(e,r){super("FILE_READ",`Cannot read file ${e}${r?`: ${r}`:""}`,{file:e,...r?{cause:r}:{}}),this.name="FileReadError"}},Ki=class extends at{constructor(e,r){super("FILE_WRITE",`Cannot write file ${e}${r?`: ${r}`:""}`,{file:e,...r?{cause:r}:{}}),this.name="FileWriteError"}},Qi=class extends at{constructor(e){super("FILE_LOCK_TIMEOUT",`Timeout acquiring lock: ${e}`,{lockPath:e}),this.name="FileLockTimeoutError"}},vr=class extends at{constructor(e,r){super("PATCH_APPLY",e,r),this.name="PatchApplyError"}},Rt=class extends at{constructor(e){super("MEMO_NOT_FOUND",`Memo not found: ${e}`,{memoId:e}),this.name="MemoNotFoundError"}},ve=class extends at{constructor(e,r){super("OPERATION_INVALID",e,r),this.name="OperationValidationError"}},Jo=class extends at{constructor(e,r){super("ANCHOR_NOT_FOUND",`Anchor text not found: "${e}"`,{anchorText:e,matchCount:0,...r??{}}),this.name="AnchorNotFoundError"}},Xi=class extends at{constructor(e){super("HANDOFF_INVALID","Not a valid handoff document",{file:e}),this.name="InvalidHandoffError"}};function Wd(t){return t instanceof at?{error:t.message,code:t.code,type:t.name,...t.details?{details:t.details}:{}}:t instanceof Error?{error:t.message}:{error:String(t)}}var n_={respond_to_memo:{allowedMemoTypes:["question"],guidance:"Use apply_memo/update_memo_progress for fix memos."},apply_memo:{allowedMemoTypes:["fix"],guidance:"Use respond_to_memo for question memos."},batch_apply:{allowedMemoTypes:["fix"],guidance:"Use respond_to_memo for question memos."}},yP={...n_};function o_(t=process.env){return t.MD_FEEDBACK_POLICY_PROFILE?.trim().toLowerCase()==="strict"?"strict":"default"}function s_(t){return t==="strict"?yP:n_}function i_(t=o_()){let e=s_(t);return{profile:t,memoActions:{respond_to_memo:{allowedMemoTypes:[...e.respond_to_memo.allowedMemoTypes]},apply_memo:{allowedMemoTypes:[...e.apply_memo.allowedMemoTypes]},batch_apply:{allowedMemoTypes:[...e.batch_apply.allowedMemoTypes]}}}}function Yi(t,e,r,n=o_()){let s=s_(n)[t];if(!s.allowedMemoTypes.includes(r))throw new ve(`${t} supports only ${s.allowedMemoTypes.join(", ")} memos. ${s.guidance}`,{memoId:e,memoType:r,action:t,policyProfile:n,allowedMemoTypes:s.allowedMemoTypes})}var yt=require("node:fs"),Qd=require("node:path"),ra=require("node:crypto");var se=require("fs"),Ne=require("path"),Bd=require("node:crypto");var _P=2e3,vP=10,xP=20;function Jd(t){return(0,Ne.isAbsolute)(t)?t:(0,Ne.resolve)(process.cwd(),t)}function ta(t){let e=Jd(t);if(!(0,se.existsSync)(e))throw new Bi(e);try{return(0,se.readFileSync)(e,"utf-8")}catch(r){throw new Ji(e,r instanceof Error?r.message:String(r))}}function a_(t,e){let r=Jd(t),n=(0,Ne.dirname)(r),o=(0,Ne.join)(n,`.mf-tmp-${(0,Bd.randomBytes)(6).toString("hex")}`);try{(0,se.writeFileSync)(o,e,"utf-8"),(0,se.renameSync)(o,r)}catch(s){try{(0,se.unlinkSync)(o)}catch{}throw new Ki(r,s instanceof Error?s.message:String(s))}}var ea=new Map;async function bP(t,e){for(;ea.has(t);)await ea.get(t);let r;ea.set(t,new Promise(n=>{r=n}));try{return await e()}finally{ea.delete(t),r()}}function wP(t){return new Promise(e=>setTimeout(e,t))}async function $P(t,e=_P){let r=Date.now();for(;;)try{let n=(0,se.openSync)(t,"wx");return()=>{try{(0,se.closeSync)(n)}catch{}try{(0,se.unlinkSync)(t)}catch{}}}catch(n){if(n.code!=="EEXIST")throw n;if(Date.now()-r>e)throw new Qi(t);await wP(vP)}}function nn(t){let e=Jd(t),r=(0,Ne.join)((0,Ne.dirname)(e),".md-feedback");return(0,se.existsSync)(r)||(0,se.mkdirSync)(r,{recursive:!0}),r}function Ko(t,e){let r=nn(t),n=(0,Ne.join)(r,"snapshots");(0,se.existsSync)(n)||(0,se.mkdirSync)(n,{recursive:!0});let o=new Date().toISOString().replace(/[:.]/g,"-"),s=(0,Ne.join)(n,`snapshot-${o}.md`);(0,se.writeFileSync)(s,e,"utf-8");let i=(0,se.readdirSync)(n).filter(a=>a.startsWith("snapshot-")).sort();for(;i.length>xP;)try{(0,se.unlinkSync)((0,Ne.join)(n,i.shift()))}catch{}return s}function c_(t){let e=nn(t),r=(0,Ne.join)(e,"progress.json");if(!(0,se.existsSync)(r))return[];try{return JSON.parse((0,se.readFileSync)(r,"utf-8"))}catch{return[]}}async function u_(t,e){let r=nn(t),n=(0,Ne.join)(r,"progress.json"),o=`${n}.lock`;await bP(n,async()=>{let s=await $P(o);try{let i=[];if((0,se.existsSync)(n))try{i=JSON.parse((0,se.readFileSync)(n,"utf-8"))}catch{i=[]}i.push(e);let a=`${n}.tmp-${(0,Bd.randomBytes)(6).toString("hex")}`;(0,se.writeFileSync)(a,JSON.stringify(i,null,2),"utf-8"),(0,se.renameSync)(a,n)}finally{s()}})}function l_(t,e){let r=nn(t),n=(0,Ne.join)(r,"transactions");(0,se.existsSync)(n)||(0,se.mkdirSync)(n,{recursive:!0});let o=new Date().toISOString().replace(/[:.]/g,"-"),s=(0,Ne.join)(n,`tx-${o}.json`);return(0,se.writeFileSync)(s,JSON.stringify(e,null,2),"utf-8"),s}var Qo=["scope","root_cause","implementation","verification"],kP={create_annotation:["scope","root_cause"],respond_to_memo:["root_cause","implementation"],apply_memo:["implementation"],batch_apply:["implementation"],rollback_memo:["implementation"],update_memo_status:["implementation","verification"],update_memo_progress:["implementation","verification"],link_artifacts:["implementation","verification"],update_cursor:["implementation","verification"],create_checkpoint:["verification"],set_memo_severity:["scope","root_cause","implementation","verification"],request_approval_checkpoint:["scope","root_cause","implementation","verification"],approve_checkpoint:["scope","root_cause","implementation","verification"]},SP=new Set(["batch_apply","rollback_memo"]);function d_(t){return(0,Qd.join)(nn(t),"workflow.json")}function p_(t){return(0,Qd.join)(nn(t),"severity.json")}function f_(t=process.env){return t.MD_FEEDBACK_WORKFLOW_ENFORCEMENT?.trim().toLowerCase()==="strict"?"strict":"off"}function m_(t){let e=p_(t);if(!(0,yt.existsSync)(e))return{version:"1.0",overrides:{},updatedAt:new Date().toISOString()};try{let r=(0,yt.readFileSync)(e,"utf-8").replace(/^\uFEFF/,""),n=JSON.parse(r);return{version:"1.0",overrides:n.overrides&&typeof n.overrides=="object"?n.overrides:{},updatedAt:typeof n.updatedAt=="string"?n.updatedAt:new Date().toISOString()}}catch{return{version:"1.0",overrides:{},updatedAt:new Date().toISOString()}}}function TP(t,e){let r=p_(t),n=`${r}.tmp-${(0,ra.randomBytes)(6).toString("hex")}`;(0,yt.writeFileSync)(n,JSON.stringify(e,null,2),"utf-8"),(0,yt.renameSync)(n,r)}function zP(t){return t==="fix"?"blocking":"non_blocking"}function h_(t,e,r){let o={version:"1.0",overrides:{...m_(t).overrides,[e]:r},updatedAt:new Date().toISOString()};return TP(t,o),o}function Xd(t){let e=ta(t),r=we(e),n=m_(t).overrides,o=r.memos.filter(s=>!zt(s.status)).filter(s=>(n[s.id]??zP(s.type))==="blocking").map(s=>s.id);return{overrides:n,unresolvedBlockingMemos:o}}function na(t,e){let r=d_(t),n=`${r}.tmp-${(0,ra.randomBytes)(6).toString("hex")}`;(0,yt.writeFileSync)(n,JSON.stringify(e,null,2),"utf-8"),(0,yt.renameSync)(n,r)}function Kd(){return{version:"1.0",phase:"scope",status:"active",transitions:[],pendingCheckpoint:null,approvals:[],approvalGrant:null,updatedAt:new Date().toISOString()}}function EP(t){let e=d_(t);if(!(0,yt.existsSync)(e))return Kd();try{let r=(0,yt.readFileSync)(e,"utf-8").replace(/^\uFEFF/,""),n=JSON.parse(r),o=n.phase,s=Array.isArray(n.transitions)?n.transitions:[];return!o||!Qo.includes(o)?Kd():{version:"1.0",phase:o,status:"active",transitions:s,pendingCheckpoint:n.pendingCheckpoint&&typeof n.pendingCheckpoint=="object"?n.pendingCheckpoint:null,approvals:Array.isArray(n.approvals)?n.approvals:[],approvalGrant:n.approvalGrant&&typeof n.approvalGrant=="object"?n.approvalGrant:null,updatedAt:typeof n.updatedAt=="string"?n.updatedAt:new Date().toISOString()}}catch{return Kd()}}function xr(t){let e=EP(t);return e.transitions.length===0&&e.phase==="scope"&&na(t,e),e}function nt(t,e){let r=xr(t);if(f_()!=="strict")return r;let n=kP[e];if(!n||n.includes(r.phase))return r;throw new ve(`Tool "${e}" is not allowed in phase "${r.phase}".`,{tool:e,currentPhase:r.phase,allowedPhases:n})}function g_(t,e){let r=xr(t),n=e(r);return na(t,n),n}function Yd(t,e,r){return g_(t,n=>n.pendingCheckpoint&&n.pendingCheckpoint.tool===e?n:{...n,pendingCheckpoint:{id:`chk_${(0,ra.randomBytes)(4).toString("hex")}`,tool:e,reason:r,requestedAt:new Date().toISOString()},updatedAt:new Date().toISOString()})}function y_(t,e,r,n){return g_(t,o=>{let s=o.pendingCheckpoint;if(!s||s.tool!==e)throw new ve(`No pending checkpoint for tool "${e}".`,{tool:e,pendingCheckpoint:s});let i={checkpointId:s.id,tool:e,approvedBy:r,reason:n,approvedAt:new Date().toISOString()};return{...o,pendingCheckpoint:null,approvals:[...o.approvals,i],approvalGrant:{checkpointId:s.id,tool:e,approvedBy:r,approvedAt:i.approvedAt,consumed:!1},updatedAt:new Date().toISOString()}})}function ep(t,e,r){if(f_()!=="strict"||!SP.has(e))return xr(t);let n=xr(t);if(n.approvalGrant&&!n.approvalGrant.consumed&&n.approvalGrant.tool===e){let s={...n,approvalGrant:{...n.approvalGrant,consumed:!0},updatedAt:new Date().toISOString()};return na(t,s),s}let o=Yd(t,e,r);throw new ve(`Approval required before high-risk tool "${e}".`,{tool:e,reason:r,pendingCheckpoint:o.pendingCheckpoint})}function __(t,e,r,n){let o=xr(t);if(e===o.phase)return o;let s=Qo.indexOf(o.phase);if(Qo.indexOf(e)!==s+1)throw new ve(`Invalid workflow transition: ${o.phase} -> ${e}. Expected next phase: ${Qo[s+1]??"none"}.`,{currentPhase:o.phase,requestedPhase:e,expectedNextPhase:Qo[s+1]??null});if(e==="verification"){let c=Xd(t);if(c.unresolvedBlockingMemos.length>0)throw new ve("Cannot advance to verification with unresolved blocking memos.",{currentPhase:o.phase,requestedPhase:e,unresolvedBlockingMemos:c.unresolvedBlockingMemos})}let a={...o,phase:e,updatedAt:new Date().toISOString(),transitions:[...o.transitions,{from:o.phase,to:e,tool:r,note:n,timestamp:new Date().toISOString()}]};return na(t,a),a}function v_(t,e){let{safeRead:r,wrapTool:n}=e;t.tool("get_policy_status","Return current runtime policy profile and memo-action routing rules.",{},async()=>n(async()=>{let o=i_();return{content:[{type:"text",text:JSON.stringify({policy:o},null,2)}]}})),t.tool("get_workflow_status","Return current workflow phase and transition history for a document.",{file:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{r(o);let s=xr(o);return{content:[{type:"text",text:JSON.stringify({workflow:s},null,2)}]}})),t.tool("get_severity_status","Return memo severity overrides and unresolved blocking memo IDs for the document.",{file:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{r(o);let s=Xd(o);return{content:[{type:"text",text:JSON.stringify({severity:s},null,2)}]}})),t.tool("get_checkpoints","List all checkpoints in an annotated markdown file.",{file:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{let s=r(o),i=en(s);return{content:[{type:"text",text:JSON.stringify({checkpoints:i},null,2)}]}})),t.tool("generate_handoff","Generate a structured handoff document from an annotated markdown file. Anti-compression format: explicit fields, numbers, lists only.",{file:T.string().describe("Path to the annotated markdown file"),target:T.enum(["standalone","claude-md","cursor-rules"]).optional().describe("Output format target (default: standalone)")},async({file:o,target:s})=>n(async()=>{let i=r(o),a=Gi(i,o);return{content:[{type:"text",text:Wi(a,s||"standalone")}]}})),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:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{let s=r(o),i=Zn(s),a=en(s),c=tn(s),u={file:o,annotations:i,checkpointCount:a.length,lastCheckpoint:a.length>0?a[a.length-1].timestamp:null,sectionsReviewed:c};return{content:[{type:"text",text:JSON.stringify(u,null,2)}]}})),t.tool("pickup_handoff","Parse an existing handoff document to resume a review session. Returns structured data for session continuity.",{file:T.string().describe("Path to the handoff markdown file")},async({file:o})=>n(async()=>{let s=r(o),i=t_(s);if(!i)throw new Xi(o);return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}})),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:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{let s=r(o),a=we(s).memos.map(c=>({id:c.id,type:c.type,status:c.status,owner:c.owner,source:c.source,color:c.color,text:c.text,anchorText:c.anchorText,anchor:c.anchor,createdAt:c.createdAt,updatedAt:c.updatedAt}));return{content:[{type:"text",text:JSON.stringify({annotations:a,total:a.length},null,2)}]}})),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:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{let s=r(o),i=we(s),a=rn(s),c=tn(s),u=Pt(i.gates,i.memos),d=0,l=0,h=0,m=0,p=0,f=0,y=0,g=0,_=0,v=0;for(let I of i.memos)I.status==="open"?d++:I.status==="in_progress"?l++:I.status==="needs_review"?h++:I.status==="answered"?m++:I.status==="done"?p++:I.status==="failed"?f++:I.status==="wontfix"&&y++,I.type==="fix"?g++:I.type==="question"?_++:v++;let x=0;for(let I of u)I.status==="blocked"&&x++;let S={version:"0.4.0",file:o,bodyMd:i.body,memos:i.memos,checkpoints:i.checkpoints,gates:u,cursor:i.cursor,sections:{all:a,reviewed:c,uncovered:a.filter(I=>!c.includes(I))},impls:i.impls,artifacts:i.artifacts,dependencies:i.dependencies,summary:{total:i.memos.length,open:d,inProgress:l,needsReview:h,answered:m,done:p,failed:f,wontfix:y,blocked:x,fixes:g,questions:_,highlights:v}},Z=r_(i.memos,i.impls,u,i.checkpoints,i.artifacts,i.dependencies);return{content:[{type:"text",text:JSON.stringify({...S,metrics:Z},null,2)}]}})),t.tool("evaluate_gates","Evaluate all gates in a markdown file against current memo statuses. Returns updated gate statuses without modifying the file.",{file:T.string().describe("Path to the annotated markdown file")},async({file:o})=>n(async()=>{let s=r(o),i=we(s),a=Pt(i.gates,i.memos);return{content:[{type:"text",text:JSON.stringify({gates:a,summary:{total:a.length,blocked:a.filter(c=>c.status==="blocked").length,proceed:a.filter(c=>c.status==="proceed").length,done:a.filter(c=>c.status==="done").length}},null,2)}]}}))}function x_(t,e){let{safeRead:r,wrapTool:n}=e;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:T.string().describe("Path to the annotated markdown file"),target:T.enum(["claude-code","cursor","codex","copilot","cline","windsurf","roo-code","gemini","antigravity","generic","handoff"]).describe("Target AI tool format")},async({file:o,target:s})=>n(async()=>{let i=r(o);if(s==="handoff"){let f=Gi(i,o);return{content:[{type:"text",text:Wi(f,"standalone")}]}}let c=we(i).memos,u=rn(i),d=u[0]||"Plan Review",l={red:"#fca5a5",blue:"#93c5fd",yellow:"#fef08a"},h=c.map(f=>({text:f.anchorText||"",color:l[f.color]||"#fef08a",section:"",context:""})),m=c.map(f=>({id:f.id,text:f.text,color:f.color,section:"",context:f.anchorText||""}));return{content:[{type:"text",text:e_(d,o,u,h,m,s)}]}})),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:T.string().describe("Path to the annotated markdown file"),memoId:T.string().optional().describe("Optional memo ID to filter by \u2014 if omitted, returns all changes")},async({file:o,memoId:s})=>n(async()=>{let i=r(o),a=we(i),c=s?a.impls.filter(l=>l.memoId===s):a.impls,u=c_(o),d=s?u.filter(l=>l.memoId===s):u;return{content:[{type:"text",text:JSON.stringify({impls:c,progress:d},null,2)}]}}))}function b_(t,e){v_(t,e),x_(t,e)}var on=require("node:fs");var w_=Ln(require("node:path")),oa=new Map;async function Ge(t,e){let r=w_.default.resolve(t);for(;oa.has(r);)await oa.get(r);let n;oa.set(r,new Promise(o=>{n=o}));try{return await e()}finally{oa.delete(r),n()}}function $_(t,e){let{safeRead:r,safeWrite:n,wrapTool:o,ensureDefaultGate:s,updateCursorFromMemos:i,applyUnifiedDiff:a}=e;t.tool("create_checkpoint","Create a review checkpoint in an annotated markdown file. Records current annotation counts and reviewed sections.",{file:T.string().describe("Path to the annotated markdown file"),note:T.string().describe('Checkpoint note (e.g., "Phase 1 review done")')},async({file:c,note:u})=>Ge(c,async()=>o(async()=>{nt(c,"create_checkpoint");let d=r(c),{checkpoint:l,updatedMarkdown:h}=Gd(d,u);return n(c,h),{content:[{type:"text",text:JSON.stringify({checkpoint:l},null,2)}]}}))),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:T.string().describe("Path to the annotated markdown file"),anchorText:T.string().describe("The exact text in the document to annotate (must exist in the file)"),type:T.enum(["fix","question","highlight"]).describe("fix = needs change, question = needs clarification, highlight = mark for reference"),text:T.string().describe("The review feedback or note to attach"),occurrence:T.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:c,anchorText:u,type:d,text:l,occurrence:h})=>Ge(c,async()=>o(async()=>{nt(c,"create_annotation");let m=r(c),p=we(m),f=p.body.split(`
70
70
  `),y=-1;if(h){let Z=0;for(let I=0;I<f.length;I++)if(f[I].includes(u)&&(Z++,Z===h)){y=I;break}if(y===-1){let I=Z===0?{occurrenceRequested:h,matchCount:Z}:{occurrenceRequested:h,matchCount:Z};throw new Jo(u,I)}}else{let Z=[];for(let I=0;I<f.length;I++)f[I].includes(u)&&Z.push(I);if(Z.length===0)throw new Jo(u,{occurrenceRequested:null,matchCount:0});if(Z.length===1)y=Z[0];else{let I=Z.find(Q=>f[Q].trim()===u.trim());if(I!==void 0)y=I;else{let Q=Z[0],$e=1/0;for(let X of Z){let N=f[X].length-u.length;N<$e&&($e=N,Q=X)}y=Q}}}let g=Et(f[y]),_=y+1,v=d==="fix"?"red":d==="question"?"blue":"yellow",x={id:rt("memo"),type:d,status:"open",owner:"agent",source:"mcp",color:v,text:l,anchorText:u,anchor:`L${_}:L${_}|${g}`,createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()};p.memos.push(x),s(p),p.gates=Pt(p.gates,p.memos),i(p,x.id,`Created ${d}: "${l.slice(0,50)}"`);let S=Lt(p);return n(c,S),{content:[{type:"text",text:JSON.stringify({memo:x,gateStatus:p.gates[0]?.status,totalMemos:p.memos.length},null,2)}]}}))),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:T.string().describe("Path to the annotated markdown file"),memoId:T.string().describe("The memo ID to respond to"),response:T.string().describe("The response text (markdown supported)")},async({file:c,memoId:u,response:d})=>Ge(c,async()=>o(async()=>{nt(c,"respond_to_memo");let l=r(c),h=we(l),m=h.memos.find(y=>y.id===u);if(!m)throw new Rt(u);Yi("respond_to_memo",u,m.type);let p=h.responses.find(y=>y.to===u);if(p){let y=h.body.split(`
71
71
  `),g=d.split(`
72
72
  `),_=p.bodyStartIdx,v=p.bodyEndIdx,x=v>=_?v-_+1:0;y.splice(_,x,...g),p.bodyEndIdx=_+g.length-1,h.body=y.join(`
@@ -77,4 +77,4 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
77
77
  `),f=h.split(`
78
78
  `),y=[],g=0,_=0,v=!1;for(;_<f.length;){let S=f[_].match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);if(!S){_++;continue}v=!0;let I=Number(S[1])-1;if(I<g||I>p.length)throw new vr(`Invalid patch hunk range for ${m}`,{file:m});for(y.push(...p.slice(g,I)),g=I,_++;_<f.length&&!f[_].startsWith("@@ ");){let Q=f[_];if(Q.startsWith("\")){_++;continue}let $e=Q[0],X=Q.slice(1);if($e===" "){if(g>=p.length||p[g]!==X)throw new vr(`Patch context mismatch in ${m}`,{file:m});y.push(X),g++}else if($e==="-"){if(g>=p.length||p[g]!==X)throw new vr(`Patch delete mismatch in ${m}`,{file:m});g++}else if($e==="+")y.push(X);else throw new vr(`Unsupported patch line in ${m}: "${Q}"`,{file:m});_++}}if(!v)throw new vr(`Invalid unified diff for ${m}: no hunks found`,{file:m});return y.push(...p.slice(g)),y.join(`
79
79
  `)}return{safeRead:o,safeWrite:s,wrapTool:a,ensureDefaultGate:c,updateCursorFromMemos:u,applyUnifiedDiff:d}}function z_(t,e,r){let n=T_({workspace:e,log:r});b_(t,n),$_(t,{safeRead:n.safeRead,safeWrite:n.safeWrite,wrapTool:n.wrapTool,ensureDefaultGate:n.ensureDefaultGate,updateCursorFromMemos:n.updateCursorFromMemos,applyUnifiedDiff:n.applyUnifiedDiff})}function E_(t,e){let r=t.filter(o=>o.startsWith("--workspace=")),n=r.length>0?r[r.length-1]:void 0;if(n){let o=n.slice(12);if(o)return o}return e.MD_FEEDBACK_WORKSPACE||void 0}function sa(t){process.stderr.write(`[md-feedback] ${t}
80
- `)}function IP(){return E_(process.argv,process.env)}var P_=IP(),R_=new Fi({name:"md-feedback",version:"1.3.10"});z_(R_,P_,sa);async function OP(){let t=new Vi;await R_.connect(t);let e=P_||process.cwd();sa(`v1.3.10 ready (stdio) workspace=${e}`)}OP().catch(t=>{sa(`fatal: ${t}`),process.exit(1)});0&&(module.exports={log});
80
+ `)}function IP(){return E_(process.argv,process.env)}var P_=IP(),R_=new Fi({name:"md-feedback",version:"1.3.12"});z_(R_,P_,sa);async function OP(){let t=new Vi;await R_.connect(t);let e=P_||process.cwd();sa(`v1.3.12 ready (stdio) workspace=${e}`)}OP().catch(t=>{sa(`fatal: ${t}`),process.exit(1)});0&&(module.exports={log});
package/package.json CHANGED
@@ -1,57 +1,57 @@
1
1
  {
2
2
  "name": "md-feedback",
3
- "version": "1.3.10",
4
- "description": "MCP server for markdown plan review — companion to the MD Feedback VS Code extension. 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
- "LICENSE"
16
- ],
17
- "scripts": {
18
- "build": "node esbuild.mjs",
19
- "test": "vitest run"
20
- },
21
- "dependencies": {
22
- "@modelcontextprotocol/sdk": "^1.12.0",
23
- "zod": "^3.23.0"
24
- },
25
- "devDependencies": {
26
- "esbuild": "^0.24.2",
27
- "typescript": "^5.7.0"
28
- },
29
- "engines": {
30
- "node": ">=18"
31
- },
32
- "repository": {
33
- "type": "git",
34
- "url": "https://github.com/yeominux/md-feedback"
35
- },
36
- "bugs": {
37
- "url": "https://github.com/yeominux/md-feedback/issues"
38
- },
39
- "homepage": "https://github.com/yeominux/md-feedback#mcp-server",
40
- "keywords": [
41
- "mcp",
42
- "mcp-server",
43
- "model-context-protocol",
44
- "markdown",
45
- "feedback",
46
- "annotation",
47
- "review",
48
- "plan-review",
49
- "ai",
50
- "ai-agent",
51
- "ai-coding",
52
- "handoff",
53
- "quality-gate",
54
- "developer-tools",
55
- "vibe-coding"
56
- ]
57
- }
3
+ "version": "1.3.12",
4
+ "description": "MCP server for markdown plan review — companion to the MD Feedback VS Code extension. 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
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "build": "node esbuild.mjs",
19
+ "test": "vitest run"
20
+ },
21
+ "dependencies": {
22
+ "@modelcontextprotocol/sdk": "^1.12.0",
23
+ "zod": "^3.23.0"
24
+ },
25
+ "devDependencies": {
26
+ "esbuild": "^0.24.2",
27
+ "typescript": "^5.7.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/yeominux/md-feedback"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/yeominux/md-feedback/issues"
38
+ },
39
+ "homepage": "https://github.com/yeominux/md-feedback#mcp-server",
40
+ "keywords": [
41
+ "mcp",
42
+ "mcp-server",
43
+ "model-context-protocol",
44
+ "markdown",
45
+ "feedback",
46
+ "annotation",
47
+ "review",
48
+ "plan-review",
49
+ "ai",
50
+ "ai-agent",
51
+ "ai-coding",
52
+ "handoff",
53
+ "quality-gate",
54
+ "developer-tools",
55
+ "vibe-coding"
56
+ ]
57
+ }