md-feedback 0.9.6 → 0.9.9

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 +124 -9
  2. package/package.json +4 -1
@@ -21069,7 +21069,7 @@ function generateGeneric(title, filePath, sections, fixes, questions, importants
21069
21069
  L.push("");
21070
21070
  }
21071
21071
  L.push("---");
21072
- L.push("*Generated by [md-feedback](https://github.com/yeominux/md-feedback-clean). Delete when done.*");
21072
+ L.push("*Generated by [md-feedback](https://github.com/yeominux/md-feedback). Delete when done.*");
21073
21073
  return L.join("\n");
21074
21074
  }
21075
21075
  function generateContext(title, filePath, sections, highlights, docMemos, target) {
@@ -21129,10 +21129,11 @@ var FEEDBACK_NOTES_RE = /^<!-- \/?(USER_FEEDBACK_NOTES|@\/?feedback-notes)\b.*--
21129
21129
  var RESPONSE_OPEN_RE = /^<!-- REVIEW_RESPONSE\s+to="([^"]+)"\s*-->$/;
21130
21130
  var RESPONSE_CLOSE_RE = /^<!-- \/REVIEW_RESPONSE\s*-->$/;
21131
21131
  function parseAttrs(lines) {
21132
+ const unesc = (s) => s.replace(/&#10;/g, "\n").replace(/&quot;/g, '"');
21132
21133
  const attrs = {};
21133
21134
  for (const line of lines) {
21134
21135
  const m = line.trim().match(/^(\w+)="([^"]*)"$/);
21135
- if (m) attrs[m[1]] = m[2];
21136
+ if (m) attrs[m[1]] = unesc(m[2]);
21136
21137
  }
21137
21138
  return attrs;
21138
21139
  }
@@ -21362,7 +21363,7 @@ function mergeDocument(parts) {
21362
21363
  return sections.join("\n\n") + "\n";
21363
21364
  }
21364
21365
  function serializeMemoV2(memo) {
21365
- const esc2 = (s) => s.replace(/"/g, "&quot;");
21366
+ const esc2 = (s) => s.replace(/"/g, "&quot;").replace(/\n/g, "&#10;");
21366
21367
  return [
21367
21368
  "<!-- USER_MEMO",
21368
21369
  ` id="${esc2(memo.id)}"`,
@@ -21598,11 +21599,12 @@ function extractMemos(annotatedMarkdown) {
21598
21599
  while (cleanLines.length > 0 && cleanLines[cleanLines.length - 1].trim() === "") cleanLines.pop();
21599
21600
  return { markdown: cleanLines.join("\n"), memos };
21600
21601
  }
21601
- var CHECKPOINT_RE2 = /<!-- CHECKPOINT id="([^"]+)" time="([^"]+)" note="([^"]*)" fixes=(\d+) questions=(\d+) highlights=(\d+) sections="([^"]*)" -->/g;
21602
+ var CHECKPOINT_PATTERN = /<!-- CHECKPOINT id="([^"]+)" time="([^"]+)" note="([^"]*)" fixes=(\d+) questions=(\d+) highlights=(\d+) sections="([^"]*)" -->/g;
21602
21603
  function extractCheckpoints(markdown) {
21603
21604
  const checkpoints = [];
21605
+ const re = new RegExp(CHECKPOINT_PATTERN.source, CHECKPOINT_PATTERN.flags);
21604
21606
  let match;
21605
- while ((match = CHECKPOINT_RE2.exec(markdown)) !== null) {
21607
+ while ((match = re.exec(markdown)) !== null) {
21606
21608
  checkpoints.push({
21607
21609
  id: match[1],
21608
21610
  timestamp: match[2],
@@ -21613,7 +21615,6 @@ function extractCheckpoints(markdown) {
21613
21615
  sectionsReviewed: match[7] ? match[7].split(",") : []
21614
21616
  });
21615
21617
  }
21616
- CHECKPOINT_RE2.lastIndex = 0;
21617
21618
  return checkpoints;
21618
21619
  }
21619
21620
  function serializeCheckpoint2(cp) {
@@ -22306,7 +22307,7 @@ function registerTools(server2) {
22306
22307
  const lineNum = anchorLine + 1;
22307
22308
  const color = type === "fix" ? "red" : type === "question" ? "blue" : "yellow";
22308
22309
  const memo = {
22309
- id: `memo-${Date.now().toString(36)}`,
22310
+ id: `memo-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,
22310
22311
  type,
22311
22312
  status: "open",
22312
22313
  owner: "agent",
@@ -22357,6 +22358,120 @@ function registerTools(server2) {
22357
22358
  }
22358
22359
  }
22359
22360
  );
22361
+ server2.tool(
22362
+ "respond_to_memo",
22363
+ `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".`,
22364
+ {
22365
+ file: external_exports.string().describe("Path to the annotated markdown file"),
22366
+ memoId: external_exports.string().describe("The memo ID to respond to"),
22367
+ response: external_exports.string().describe("The response text (markdown supported)")
22368
+ },
22369
+ async ({ file, memoId, response }) => {
22370
+ try {
22371
+ const markdown = readMarkdownFile(file);
22372
+ const parts = splitDocument(markdown);
22373
+ const memo = parts.memos.find((m) => m.id === memoId);
22374
+ if (!memo) {
22375
+ return {
22376
+ content: [{
22377
+ type: "text",
22378
+ text: JSON.stringify({ error: `Memo not found: ${memoId}` })
22379
+ }],
22380
+ isError: true
22381
+ };
22382
+ }
22383
+ const existingResponse = parts.responses.find((r) => r.to === memoId);
22384
+ if (existingResponse) {
22385
+ const bodyLines = parts.body.split("\n");
22386
+ const newResponseLines = response.split("\n");
22387
+ const start = existingResponse.bodyStartIdx;
22388
+ const end = existingResponse.bodyEndIdx;
22389
+ const count = end >= start ? end - start + 1 : 0;
22390
+ bodyLines.splice(start, count, ...newResponseLines);
22391
+ existingResponse.bodyEndIdx = start + newResponseLines.length - 1;
22392
+ parts.body = bodyLines.join("\n");
22393
+ } else {
22394
+ const bodyLines = parts.body.split("\n");
22395
+ let insertAfter = -1;
22396
+ if (memo.anchor) {
22397
+ const anchorMatch = memo.anchor.match(/^L(\d+)(?::L\d+)?\|(.+)$/);
22398
+ if (anchorMatch) {
22399
+ const lineNum = parseInt(anchorMatch[1], 10) - 1;
22400
+ const expectedHash = computeLineHash(bodyLines[lineNum] || "");
22401
+ if (lineNum >= 0 && lineNum < bodyLines.length && expectedHash === anchorMatch[2]) {
22402
+ insertAfter = lineNum;
22403
+ } else {
22404
+ for (let delta = 1; delta <= 10; delta++) {
22405
+ for (const d of [lineNum - delta, lineNum + delta]) {
22406
+ if (d >= 0 && d < bodyLines.length && computeLineHash(bodyLines[d]) === anchorMatch[2]) {
22407
+ insertAfter = d;
22408
+ break;
22409
+ }
22410
+ }
22411
+ if (insertAfter >= 0) break;
22412
+ }
22413
+ }
22414
+ }
22415
+ }
22416
+ if (insertAfter === -1 && memo.anchorText) {
22417
+ for (let i = 0; i < bodyLines.length; i++) {
22418
+ if (bodyLines[i].includes(memo.anchorText)) {
22419
+ insertAfter = i;
22420
+ break;
22421
+ }
22422
+ }
22423
+ }
22424
+ if (insertAfter === -1) {
22425
+ insertAfter = bodyLines.length - 1;
22426
+ }
22427
+ const responseLines = response.split("\n");
22428
+ const insertIdx = insertAfter + 1;
22429
+ bodyLines.splice(insertIdx, 0, ...responseLines);
22430
+ for (const r of parts.responses) {
22431
+ if (r.bodyStartIdx >= insertIdx) {
22432
+ r.bodyStartIdx += responseLines.length;
22433
+ r.bodyEndIdx += responseLines.length;
22434
+ }
22435
+ }
22436
+ parts.body = bodyLines.join("\n");
22437
+ parts.responses.push({
22438
+ id: `resp_${memoId}`,
22439
+ to: memoId,
22440
+ bodyStartIdx: insertIdx,
22441
+ bodyEndIdx: insertIdx + responseLines.length - 1
22442
+ });
22443
+ }
22444
+ if (memo.status === "open") {
22445
+ memo.status = "answered";
22446
+ memo.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
22447
+ }
22448
+ if (parts.gates.length > 0) {
22449
+ parts.gates = evaluateAllGates(parts.gates, parts.memos);
22450
+ }
22451
+ const updated = mergeDocument(parts);
22452
+ writeMarkdownFile(file, updated);
22453
+ return {
22454
+ content: [{
22455
+ type: "text",
22456
+ text: JSON.stringify({
22457
+ memoId,
22458
+ status: memo.status,
22459
+ responseInserted: true,
22460
+ totalResponses: parts.responses.length
22461
+ }, null, 2)
22462
+ }]
22463
+ };
22464
+ } catch (err) {
22465
+ return {
22466
+ content: [{
22467
+ type: "text",
22468
+ text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) })
22469
+ }],
22470
+ isError: true
22471
+ };
22472
+ }
22473
+ }
22474
+ );
22360
22475
  server2.tool(
22361
22476
  "update_memo_status",
22362
22477
  "Update the status of a memo annotation. Writes the change back to the markdown file. Returns the updated memo.",
@@ -22563,13 +22678,13 @@ function log(msg) {
22563
22678
  }
22564
22679
  var server = new McpServer({
22565
22680
  name: "md-feedback",
22566
- version: "0.9.6"
22681
+ version: "0.9.9"
22567
22682
  });
22568
22683
  registerTools(server);
22569
22684
  async function main() {
22570
22685
  const transport = new StdioServerTransport();
22571
22686
  await server.connect(transport);
22572
- log(`v${"0.9.6"} ready (stdio)`);
22687
+ log(`v${"0.9.9"} ready (stdio)`);
22573
22688
  }
22574
22689
  main().catch((err) => {
22575
22690
  log(`fatal: ${err}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md-feedback",
3
- "version": "0.9.6",
3
+ "version": "0.9.9",
4
4
  "description": "MCP server for AI plan review — agents read your markdown annotations, mark tasks done, evaluate gates, and generate handoffs.",
5
5
  "license": "SUL-1.0",
6
6
  "author": "Yeomin Seon",
@@ -31,6 +31,9 @@
31
31
  "type": "git",
32
32
  "url": "https://github.com/yeominux/md-feedback"
33
33
  },
34
+ "bugs": {
35
+ "url": "https://github.com/yeominux/md-feedback/issues"
36
+ },
34
37
  "homepage": "https://github.com/yeominux/md-feedback#mcp-server",
35
38
  "keywords": [
36
39
  "mcp",