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.
- package/dist/mcp-server.js +136 -10
- package/package.json +60 -60
package/dist/mcp-server.js
CHANGED
|
@@ -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(/ /g, "\n").replace(/"/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, """);
|
|
21366
|
+
const esc2 = (s) => s.replace(/"/g, """).replace(/\n/g, " ");
|
|
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
|
|
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 =
|
|
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
|
|
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", "
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
+
}
|