agent-feedbacker 0.1.0 → 0.3.0
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/.claude-plugin/plugin.json +1 -1
- package/README.md +32 -12
- package/bin/feedbacker.mjs +20 -3
- package/hooks/hooks.json +13 -0
- package/package.json +2 -1
- package/skills/review/SKILL.md +6 -6
- package/src/baseline.mjs +122 -0
- package/src/core.mjs +112 -15
- package/src/dist/app.css +1 -1
- package/src/dist/app.js +15 -14
- package/src/mcp.mjs +25 -5
package/src/mcp.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// Minimal MCP server over stdio (newline-delimited JSON-RPC 2.0) with one tool,
|
|
2
2
|
// request_review, which blocks until the user submits the review page.
|
|
3
3
|
|
|
4
|
+
import fs from 'node:fs';
|
|
4
5
|
import readline from 'node:readline';
|
|
5
6
|
import { buildReview, hasChanges, startReview, toMarkdown } from './core.mjs';
|
|
6
7
|
|
|
7
|
-
const VERSION = '
|
|
8
|
+
const { version: VERSION } = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
8
9
|
const PROTOCOL = '2025-06-18';
|
|
9
10
|
|
|
10
11
|
const TOOL = {
|
|
@@ -14,7 +15,8 @@ const TOOL = {
|
|
|
14
15
|
'Open a merge-request-style review page in the user\'s browser with the diffs of the given git repos ' +
|
|
15
16
|
'(working tree, including untracked files, against a base). The user comments on lines, answers your ' +
|
|
16
17
|
'questions and submits. Blocks until they do, then returns their feedback as markdown: a verdict ' +
|
|
17
|
-
'(approved / changes requested), a general comment,
|
|
18
|
+
'(approved / changes requested), a general comment, line comments with the code they refer to, and questions the ' +
|
|
19
|
+
'user asks about the code, which you must answer in your reply. ' +
|
|
18
20
|
'Pass every repo and worktree you changed in this session, as absolute paths.',
|
|
19
21
|
inputSchema: {
|
|
20
22
|
type: 'object',
|
|
@@ -28,13 +30,30 @@ const TOOL = {
|
|
|
28
30
|
properties: {
|
|
29
31
|
path: { type: 'string', description: 'Absolute path to the repo (or any folder inside it).' },
|
|
30
32
|
base: { type: 'string', description: 'Commit, branch or tag to diff against. Default HEAD (uncommitted changes only). With a branch, the diff starts where HEAD forked from it, like a merge request.' },
|
|
33
|
+
exact: { type: 'boolean', description: 'Diff against base itself, not the point where HEAD forked from it. Set by the session-baseline hook; rarely needed by hand.' },
|
|
34
|
+
session: { type: 'boolean', description: 'Set by the session-baseline hook: base is where the repo stood when this session started.' },
|
|
31
35
|
worktrees: { type: 'boolean', description: 'Also review every other git worktree of this repo (e.g. parallel agents\' work), each against the same base.' },
|
|
32
36
|
},
|
|
33
37
|
required: ['path'],
|
|
34
38
|
},
|
|
35
39
|
},
|
|
36
|
-
summary: { type: 'string', description: '
|
|
37
|
-
|
|
40
|
+
summary: { type: 'string', description: 'What you changed and why, shown at the top of the page. Markdown: paragraphs, "- " lists, `code`, **bold**, "### " subheadings. Lead with one or two sentences; put details in a list.' },
|
|
41
|
+
focus: {
|
|
42
|
+
type: 'array',
|
|
43
|
+
description: 'Where the user should look first: the main logic, risky or non-obvious changes. Two to six entries; leave out tests, docs and config unless they are the point.',
|
|
44
|
+
items: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {
|
|
47
|
+
path: { type: 'string', description: 'File path, relative to its repo (or absolute).' },
|
|
48
|
+
repo: { type: 'string', description: 'Repo path or folder name, when the same path exists in several repos.' },
|
|
49
|
+
startLine: { type: 'integer', description: 'First line (new side) of the part to look at.' },
|
|
50
|
+
endLine: { type: 'integer', description: 'Last line (new side).' },
|
|
51
|
+
note: { type: 'string', description: 'Why it matters, in one sentence.' },
|
|
52
|
+
},
|
|
53
|
+
required: ['path', 'note'],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
questions: { type: 'array', items: { type: 'string' }, description: 'Only for a decision you cannot make without the user. Usually leave this out: the user asks you questions on the page instead.' },
|
|
38
57
|
},
|
|
39
58
|
required: ['repos'],
|
|
40
59
|
},
|
|
@@ -59,9 +78,10 @@ async function callTool(id, params) {
|
|
|
59
78
|
return toolError('`repos` must be a non-empty array of { path, base? }.');
|
|
60
79
|
}
|
|
61
80
|
const review = buildReview({
|
|
62
|
-
repos: args.repos.map((r) => (typeof r === 'string' ? r : { path: r.path, base: r.base, worktrees: r.worktrees })),
|
|
81
|
+
repos: args.repos.map((r) => (typeof r === 'string' ? r : { path: r.path, base: r.base, worktrees: r.worktrees, exact: r.exact, session: r.session })),
|
|
63
82
|
summary: args.summary || '',
|
|
64
83
|
questions: Array.isArray(args.questions) ? args.questions.map(String) : [],
|
|
84
|
+
focus: Array.isArray(args.focus) ? args.focus : [],
|
|
65
85
|
});
|
|
66
86
|
const errors = review.repos.filter((r) => r.error).map((r) => `${r.path}: ${r.error}`);
|
|
67
87
|
if (!hasChanges(review) && !review.questions.length) {
|