md-feedback 0.9.5 → 0.9.8

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 +136 -10
  2. package/package.json +60 -60
@@ -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
  }
@@ -21321,6 +21322,17 @@ function splitDocument(markdown) {
21321
21322
  openResponse.bodyEndIdx = bodyLines.length - 1;
21322
21323
  responses.push(openResponse);
21323
21324
  }
21325
+ for (const memo of memos) {
21326
+ if (memo.status === "done") {
21327
+ memo.status = "answered";
21328
+ }
21329
+ }
21330
+ const respondedMemoIds = new Set(responses.map((r) => r.to));
21331
+ for (const memo of memos) {
21332
+ if (memo.status === "open" && respondedMemoIds.has(memo.id)) {
21333
+ memo.status = "answered";
21334
+ }
21335
+ }
21324
21336
  return {
21325
21337
  frontmatter,
21326
21338
  body: bodyLines.join("\n"),
@@ -21351,7 +21363,7 @@ function mergeDocument(parts) {
21351
21363
  return sections.join("\n\n") + "\n";
21352
21364
  }
21353
21365
  function serializeMemoV2(memo) {
21354
- const esc2 = (s) => s.replace(/"/g, "&quot;");
21366
+ const esc2 = (s) => s.replace(/"/g, "&quot;").replace(/\n/g, "&#10;");
21355
21367
  return [
21356
21368
  "<!-- USER_MEMO",
21357
21369
  ` id="${esc2(memo.id)}"`,
@@ -21587,11 +21599,12 @@ function extractMemos(annotatedMarkdown) {
21587
21599
  while (cleanLines.length > 0 && cleanLines[cleanLines.length - 1].trim() === "") cleanLines.pop();
21588
21600
  return { markdown: cleanLines.join("\n"), memos };
21589
21601
  }
21590
- 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;
21591
21603
  function extractCheckpoints(markdown) {
21592
21604
  const checkpoints = [];
21605
+ const re = new RegExp(CHECKPOINT_PATTERN.source, CHECKPOINT_PATTERN.flags);
21593
21606
  let match;
21594
- while ((match = CHECKPOINT_RE2.exec(markdown)) !== null) {
21607
+ while ((match = re.exec(markdown)) !== null) {
21595
21608
  checkpoints.push({
21596
21609
  id: match[1],
21597
21610
  timestamp: match[2],
@@ -21602,7 +21615,6 @@ function extractCheckpoints(markdown) {
21602
21615
  sectionsReviewed: match[7] ? match[7].split(",") : []
21603
21616
  });
21604
21617
  }
21605
- CHECKPOINT_RE2.lastIndex = 0;
21606
21618
  return checkpoints;
21607
21619
  }
21608
21620
  function serializeCheckpoint2(cp) {
@@ -22215,7 +22227,7 @@ function registerTools(server2) {
22215
22227
  const reviewedSections = getSectionsWithAnnotations(markdown);
22216
22228
  const gates = evaluateAllGates(parts.gates, parts.memos);
22217
22229
  const open = parts.memos.filter((m) => m.status === "open").length;
22218
- const done = parts.memos.filter((m) => m.status === "done" || m.status === "answered" || m.status === "wontfix").length;
22230
+ const done = parts.memos.filter((m) => m.status !== "open").length;
22219
22231
  const blocked = gates.filter((g) => g.status === "blocked").length;
22220
22232
  const structure = {
22221
22233
  version: "0.4.0",
@@ -22295,7 +22307,7 @@ function registerTools(server2) {
22295
22307
  const lineNum = anchorLine + 1;
22296
22308
  const color = type === "fix" ? "red" : type === "question" ? "blue" : "yellow";
22297
22309
  const memo = {
22298
- id: `memo-${Date.now().toString(36)}`,
22310
+ id: `memo-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,
22299
22311
  type,
22300
22312
  status: "open",
22301
22313
  owner: "agent",
@@ -22346,13 +22358,127 @@ function registerTools(server2) {
22346
22358
  }
22347
22359
  }
22348
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
+ );
22349
22475
  server2.tool(
22350
22476
  "update_memo_status",
22351
22477
  "Update the status of a memo annotation. Writes the change back to the markdown file. Returns the updated memo.",
22352
22478
  {
22353
22479
  file: external_exports.string().describe("Path to the annotated markdown file"),
22354
22480
  memoId: external_exports.string().describe("The memo ID to update"),
22355
- status: external_exports.enum(["open", "answered", "done", "wontfix"]).describe("New status"),
22481
+ status: external_exports.enum(["open", "answered", "wontfix"]).describe("New status"),
22356
22482
  owner: external_exports.enum(["human", "agent", "tool"]).optional().describe("Optionally change the owner")
22357
22483
  },
22358
22484
  async ({ file, memoId, status, owner }) => {
@@ -22552,13 +22678,13 @@ function log(msg) {
22552
22678
  }
22553
22679
  var server = new McpServer({
22554
22680
  name: "md-feedback",
22555
- version: "0.9.5"
22681
+ version: "0.9.8"
22556
22682
  });
22557
22683
  registerTools(server);
22558
22684
  async function main() {
22559
22685
  const transport = new StdioServerTransport();
22560
22686
  await server.connect(transport);
22561
- log(`v${"0.9.5"} ready (stdio)`);
22687
+ log(`v${"0.9.8"} ready (stdio)`);
22562
22688
  }
22563
22689
  main().catch((err) => {
22564
22690
  log(`fatal: ${err}`);
package/package.json CHANGED
@@ -1,60 +1,60 @@
1
- {
2
- "name": "md-feedback",
3
- "version": "0.9.5",
4
- "description": "MCP server for AI plan review — agents read your markdown annotations, mark tasks done, evaluate gates, and generate handoffs.",
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
- "homepage": "https://github.com/yeominux/md-feedback#mcp-server",
35
- "keywords": [
36
- "mcp",
37
- "mcp-server",
38
- "model-context-protocol",
39
- "markdown",
40
- "feedback",
41
- "ai",
42
- "annotation",
43
- "review",
44
- "plan-review",
45
- "ai-agent",
46
- "coding-workflow",
47
- "handoff",
48
- "session-handoff",
49
- "structured-feedback",
50
- "checkpoint",
51
- "ai-context",
52
- "ai-coding",
53
- "claude-code",
54
- "cursor-ai",
55
- "vibe-coding",
56
- "context-engineering",
57
- "gates",
58
- "plan-review-tool"
59
- ]
60
- }
1
+ {
2
+ "name": "md-feedback",
3
+ "version": "0.9.8",
4
+ "description": "MCP server for AI plan review — agents read your markdown annotations, mark tasks done, evaluate gates, and generate handoffs.",
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
+ "homepage": "https://github.com/yeominux/md-feedback#mcp-server",
35
+ "keywords": [
36
+ "mcp",
37
+ "mcp-server",
38
+ "model-context-protocol",
39
+ "markdown",
40
+ "feedback",
41
+ "ai",
42
+ "annotation",
43
+ "review",
44
+ "plan-review",
45
+ "ai-agent",
46
+ "coding-workflow",
47
+ "handoff",
48
+ "session-handoff",
49
+ "structured-feedback",
50
+ "checkpoint",
51
+ "ai-context",
52
+ "ai-coding",
53
+ "claude-code",
54
+ "cursor-ai",
55
+ "vibe-coding",
56
+ "context-engineering",
57
+ "gates",
58
+ "plan-review-tool"
59
+ ]
60
+ }