md-feedback 0.9.6 → 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 +123 -8
- 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
|
}
|
|
@@ -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, """);
|
|
21366
|
+
const esc2 = (s) => s.replace(/"/g, """).replace(/\n/g, " ");
|
|
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
|
|
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 =
|
|
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.
|
|
22681
|
+
version: "0.9.8"
|
|
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.
|
|
22687
|
+
log(`v${"0.9.8"} ready (stdio)`);
|
|
22573
22688
|
}
|
|
22574
22689
|
main().catch((err) => {
|
|
22575
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
|
+
}
|