@jussmor/commit-memory-mcp 0.3.5 → 0.3.7
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/README.md +194 -29
- package/dist/db/client.d.ts +26 -1
- package/dist/db/client.js +401 -0
- package/dist/git/insights.d.ts +51 -0
- package/dist/git/insights.js +146 -0
- package/dist/git/worktree.d.ts +3 -0
- package/dist/git/worktree.js +52 -0
- package/dist/mcp/server.js +435 -65
- package/dist/pr/sync.d.ts +13 -0
- package/dist/pr/sync.js +240 -0
- package/dist/types.d.ts +93 -0
- package/package.json +1 -1
package/dist/mcp/server.js
CHANGED
|
@@ -6,14 +6,59 @@ import { execFileSync } from "node:child_process";
|
|
|
6
6
|
import fs from "node:fs";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { pathToFileURL } from "node:url";
|
|
9
|
-
import { openDatabase } from "../db/client.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
import { archiveFeatureContext, buildContextPack, openDatabase, promoteContextFacts, upsertWorktreeSession, } from "../db/client.js";
|
|
10
|
+
import { commitDetails, latestCommitForFile, mainBranchOvernightBrief, resumeFeatureSessionBrief, whoChangedFile, } from "../git/insights.js";
|
|
11
|
+
import { listActiveWorktrees } from "../git/worktree.js";
|
|
12
|
+
import { syncPullRequestContext } from "../pr/sync.js";
|
|
13
|
+
function fetchRemote(repoPath) {
|
|
14
|
+
execFileSync("git", ["-C", repoPath, "fetch", "--all", "--prune"], {
|
|
15
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
15
16
|
});
|
|
16
17
|
}
|
|
18
|
+
function detectReferencedPrNumber(text) {
|
|
19
|
+
const match = text.match(/#(\d{1,8})\b/);
|
|
20
|
+
if (!match) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const value = Number.parseInt(match[1] ?? "", 10);
|
|
24
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function loadPullRequestContext(db, prNumber, repoOwner, repoName) {
|
|
30
|
+
const pr = repoOwner && repoName
|
|
31
|
+
? (db
|
|
32
|
+
.prepare(`
|
|
33
|
+
SELECT repo_owner, repo_name, pr_number, title, body, author, state, created_at, updated_at, merged_at, url
|
|
34
|
+
FROM prs
|
|
35
|
+
WHERE repo_owner = ? AND repo_name = ? AND pr_number = ?
|
|
36
|
+
LIMIT 1
|
|
37
|
+
`)
|
|
38
|
+
.get(repoOwner, repoName, prNumber) ?? null)
|
|
39
|
+
: (db
|
|
40
|
+
.prepare(`
|
|
41
|
+
SELECT repo_owner, repo_name, pr_number, title, body, author, state, created_at, updated_at, merged_at, url
|
|
42
|
+
FROM prs
|
|
43
|
+
WHERE pr_number = ?
|
|
44
|
+
ORDER BY updated_at DESC
|
|
45
|
+
LIMIT 1
|
|
46
|
+
`)
|
|
47
|
+
.get(prNumber) ?? null);
|
|
48
|
+
if (!pr) {
|
|
49
|
+
return { pr: null, decisions: [] };
|
|
50
|
+
}
|
|
51
|
+
const decisions = db
|
|
52
|
+
.prepare(`
|
|
53
|
+
SELECT id, source, author, summary, severity, created_at
|
|
54
|
+
FROM pr_decisions
|
|
55
|
+
WHERE repo_owner = ? AND repo_name = ? AND pr_number = ?
|
|
56
|
+
ORDER BY created_at DESC
|
|
57
|
+
LIMIT 50
|
|
58
|
+
`)
|
|
59
|
+
.all(pr.repo_owner, pr.repo_name, pr.pr_number);
|
|
60
|
+
return { pr, decisions };
|
|
61
|
+
}
|
|
17
62
|
function getConfig() {
|
|
18
63
|
const repoPath = path.resolve(process.env.COMMIT_RAG_REPO ?? process.cwd());
|
|
19
64
|
const dbPath = path.resolve(process.env.COMMIT_RAG_DB ?? path.join(repoPath, ".commit-rag.db"));
|
|
@@ -23,7 +68,7 @@ function getConfig() {
|
|
|
23
68
|
export async function startMcpServer() {
|
|
24
69
|
const server = new Server({
|
|
25
70
|
name: "commit-memory-mcp",
|
|
26
|
-
version: "0.
|
|
71
|
+
version: "0.4.0",
|
|
27
72
|
}, {
|
|
28
73
|
capabilities: {
|
|
29
74
|
tools: {},
|
|
@@ -32,117 +77,442 @@ export async function startMcpServer() {
|
|
|
32
77
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
33
78
|
tools: [
|
|
34
79
|
{
|
|
35
|
-
name: "
|
|
36
|
-
description: "
|
|
80
|
+
name: "sync_pr_context",
|
|
81
|
+
description: "Sync pull request description/comments/reviews from GitHub CLI into local context DB.",
|
|
37
82
|
inputSchema: {
|
|
38
83
|
type: "object",
|
|
39
84
|
properties: {
|
|
40
|
-
|
|
41
|
-
|
|
85
|
+
owner: { type: "string" },
|
|
86
|
+
repo: { type: "string" },
|
|
87
|
+
prNumbers: {
|
|
88
|
+
type: "array",
|
|
89
|
+
items: { type: "number" },
|
|
90
|
+
},
|
|
91
|
+
domain: { type: "string" },
|
|
92
|
+
feature: { type: "string" },
|
|
93
|
+
branch: { type: "string" },
|
|
94
|
+
taskType: { type: "string" },
|
|
42
95
|
limit: { type: "number" },
|
|
43
96
|
},
|
|
44
|
-
required: ["
|
|
97
|
+
required: ["owner", "repo"],
|
|
45
98
|
},
|
|
46
99
|
},
|
|
47
100
|
{
|
|
48
|
-
name: "
|
|
49
|
-
description: "
|
|
101
|
+
name: "build_context_pack",
|
|
102
|
+
description: "Build a scoped context pack for a task/domain/feature/branch to keep agent prompts small.",
|
|
50
103
|
inputSchema: {
|
|
51
104
|
type: "object",
|
|
52
105
|
properties: {
|
|
53
|
-
|
|
106
|
+
domain: { type: "string" },
|
|
107
|
+
feature: { type: "string" },
|
|
108
|
+
branch: { type: "string" },
|
|
109
|
+
taskType: { type: "string" },
|
|
110
|
+
includeDraft: { type: "boolean" },
|
|
111
|
+
limit: { type: "number" },
|
|
54
112
|
},
|
|
55
|
-
required: [
|
|
113
|
+
required: [],
|
|
56
114
|
},
|
|
57
115
|
},
|
|
58
116
|
{
|
|
59
|
-
name: "
|
|
60
|
-
description: "
|
|
117
|
+
name: "promote_context_facts",
|
|
118
|
+
description: "Promote scoped draft facts into durable promoted context.",
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: {
|
|
122
|
+
domain: { type: "string" },
|
|
123
|
+
feature: { type: "string" },
|
|
124
|
+
branch: { type: "string" },
|
|
125
|
+
sourceType: { type: "string" },
|
|
126
|
+
},
|
|
127
|
+
required: [],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "archive_feature_context",
|
|
132
|
+
description: "Archive all active facts for a domain/feature once work is complete.",
|
|
133
|
+
inputSchema: {
|
|
134
|
+
type: "object",
|
|
135
|
+
properties: {
|
|
136
|
+
domain: { type: "string" },
|
|
137
|
+
feature: { type: "string" },
|
|
138
|
+
},
|
|
139
|
+
required: ["domain", "feature"],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "list_active_worktrees",
|
|
144
|
+
description: "List active git worktrees for multi-session feature work.",
|
|
145
|
+
inputSchema: {
|
|
146
|
+
type: "object",
|
|
147
|
+
properties: {
|
|
148
|
+
baseBranch: { type: "string" },
|
|
149
|
+
},
|
|
150
|
+
required: [],
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: "who_changed_this",
|
|
155
|
+
description: "Show who changed a file recently and summarize top authors.",
|
|
156
|
+
inputSchema: {
|
|
157
|
+
type: "object",
|
|
158
|
+
properties: {
|
|
159
|
+
filePath: { type: "string" },
|
|
160
|
+
limit: { type: "number" },
|
|
161
|
+
},
|
|
162
|
+
required: ["filePath"],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "why_was_this_changed",
|
|
167
|
+
description: "Explain intent for a commit or file using git history and synced PR decisions.",
|
|
61
168
|
inputSchema: {
|
|
62
169
|
type: "object",
|
|
63
170
|
properties: {
|
|
64
171
|
sha: { type: "string" },
|
|
172
|
+
filePath: { type: "string" },
|
|
173
|
+
owner: { type: "string" },
|
|
174
|
+
repo: { type: "string" },
|
|
65
175
|
},
|
|
66
|
-
required: [
|
|
176
|
+
required: [],
|
|
67
177
|
},
|
|
68
178
|
},
|
|
69
179
|
{
|
|
70
|
-
name: "
|
|
71
|
-
description: "
|
|
180
|
+
name: "get_main_branch_overnight_brief",
|
|
181
|
+
description: "Summarize what changed recently on main branch while you were offline.",
|
|
72
182
|
inputSchema: {
|
|
73
183
|
type: "object",
|
|
74
184
|
properties: {
|
|
185
|
+
baseBranch: { type: "string" },
|
|
186
|
+
sinceHours: { type: "number" },
|
|
75
187
|
limit: { type: "number" },
|
|
76
188
|
},
|
|
77
189
|
required: [],
|
|
78
190
|
},
|
|
79
191
|
},
|
|
192
|
+
{
|
|
193
|
+
name: "resume_feature_session_brief",
|
|
194
|
+
description: "Brief branch divergence and overlap risk for a feature worktree.",
|
|
195
|
+
inputSchema: {
|
|
196
|
+
type: "object",
|
|
197
|
+
properties: {
|
|
198
|
+
worktreePath: { type: "string" },
|
|
199
|
+
baseBranch: { type: "string" },
|
|
200
|
+
},
|
|
201
|
+
required: [],
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "pre_plan_sync_brief",
|
|
206
|
+
description: "Run sync + overnight + feature resume analysis before planning work.",
|
|
207
|
+
inputSchema: {
|
|
208
|
+
type: "object",
|
|
209
|
+
properties: {
|
|
210
|
+
owner: { type: "string" },
|
|
211
|
+
repo: { type: "string" },
|
|
212
|
+
baseBranch: { type: "string" },
|
|
213
|
+
worktreePath: { type: "string" },
|
|
214
|
+
filePath: { type: "string" },
|
|
215
|
+
sinceHours: { type: "number" },
|
|
216
|
+
limit: { type: "number" },
|
|
217
|
+
},
|
|
218
|
+
required: ["owner", "repo"],
|
|
219
|
+
},
|
|
220
|
+
},
|
|
80
221
|
],
|
|
81
222
|
}));
|
|
82
223
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
83
224
|
const { repoPath, dbPath, limit: defaultLimit } = getConfig();
|
|
84
|
-
if (request.params.name === "
|
|
85
|
-
const
|
|
86
|
-
const
|
|
225
|
+
if (request.params.name === "sync_pr_context") {
|
|
226
|
+
const owner = String(request.params.arguments?.owner ?? "").trim();
|
|
227
|
+
const repo = String(request.params.arguments?.repo ?? "").trim();
|
|
228
|
+
if (!owner || !repo) {
|
|
229
|
+
return {
|
|
230
|
+
content: [{ type: "text", text: "owner and repo are required" }],
|
|
231
|
+
isError: true,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const numbersRaw = request.params.arguments?.prNumbers;
|
|
235
|
+
const prNumbers = Array.isArray(numbersRaw)
|
|
236
|
+
? numbersRaw
|
|
237
|
+
.map((value) => Number(value))
|
|
238
|
+
.filter((value) => Number.isFinite(value) && value > 0)
|
|
239
|
+
: undefined;
|
|
240
|
+
const limit = Number(request.params.arguments?.limit ?? 25);
|
|
241
|
+
const domain = String(request.params.arguments?.domain ?? "").trim();
|
|
242
|
+
const feature = String(request.params.arguments?.feature ?? "").trim();
|
|
243
|
+
const branch = String(request.params.arguments?.branch ?? "").trim();
|
|
244
|
+
const taskType = String(request.params.arguments?.taskType ?? "").trim();
|
|
245
|
+
const summary = await syncPullRequestContext({
|
|
246
|
+
repoPath,
|
|
247
|
+
dbPath,
|
|
248
|
+
repoOwner: owner,
|
|
249
|
+
repoName: repo,
|
|
250
|
+
prNumbers,
|
|
251
|
+
limit,
|
|
252
|
+
domain: domain || undefined,
|
|
253
|
+
feature: feature || undefined,
|
|
254
|
+
branch: branch || undefined,
|
|
255
|
+
taskType: taskType || undefined,
|
|
256
|
+
});
|
|
87
257
|
return {
|
|
88
258
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }],
|
|
89
259
|
};
|
|
90
260
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
261
|
+
if (request.params.name === "build_context_pack") {
|
|
262
|
+
const limit = Number(request.params.arguments?.limit ?? 20);
|
|
263
|
+
const domain = String(request.params.arguments?.domain ?? "").trim();
|
|
264
|
+
const feature = String(request.params.arguments?.feature ?? "").trim();
|
|
265
|
+
const branch = String(request.params.arguments?.branch ?? "").trim();
|
|
266
|
+
const taskType = String(request.params.arguments?.taskType ?? "").trim() || "general";
|
|
267
|
+
const includeDraft = Boolean(request.params.arguments?.includeDraft);
|
|
268
|
+
const db = openDatabase(dbPath);
|
|
269
|
+
try {
|
|
270
|
+
const pack = buildContextPack(db, {
|
|
271
|
+
domain: domain || undefined,
|
|
272
|
+
feature: feature || undefined,
|
|
273
|
+
branch: branch || undefined,
|
|
274
|
+
taskType,
|
|
275
|
+
includeDraft,
|
|
276
|
+
limit: Number.isFinite(limit) && limit > 0 ? limit : 20,
|
|
277
|
+
});
|
|
106
278
|
return {
|
|
107
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
279
|
+
content: [{ type: "text", text: JSON.stringify(pack, null, 2) }],
|
|
108
280
|
};
|
|
109
281
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
282
|
+
finally {
|
|
283
|
+
db.close();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (request.params.name === "promote_context_facts") {
|
|
287
|
+
const domain = String(request.params.arguments?.domain ?? "").trim();
|
|
288
|
+
const feature = String(request.params.arguments?.feature ?? "").trim();
|
|
289
|
+
const branch = String(request.params.arguments?.branch ?? "").trim();
|
|
290
|
+
const sourceType = String(request.params.arguments?.sourceType ?? "").trim();
|
|
291
|
+
const db = openDatabase(dbPath);
|
|
292
|
+
try {
|
|
293
|
+
const promotedCount = promoteContextFacts(db, {
|
|
294
|
+
domain: domain || undefined,
|
|
295
|
+
feature: feature || undefined,
|
|
296
|
+
branch: branch || undefined,
|
|
297
|
+
sourceType: sourceType || undefined,
|
|
298
|
+
});
|
|
299
|
+
return {
|
|
300
|
+
content: [
|
|
301
|
+
{
|
|
302
|
+
type: "text",
|
|
303
|
+
text: JSON.stringify({ promotedCount }, null, 2),
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
db.close();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (request.params.name === "archive_feature_context") {
|
|
313
|
+
const domain = String(request.params.arguments?.domain ?? "").trim();
|
|
314
|
+
const feature = String(request.params.arguments?.feature ?? "").trim();
|
|
315
|
+
if (!domain || !feature) {
|
|
119
316
|
return {
|
|
120
|
-
content: [{ type: "text", text:
|
|
317
|
+
content: [{ type: "text", text: "domain and feature are required" }],
|
|
318
|
+
isError: true,
|
|
121
319
|
};
|
|
122
320
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
321
|
+
const db = openDatabase(dbPath);
|
|
322
|
+
try {
|
|
323
|
+
const archivedCount = archiveFeatureContext(db, {
|
|
324
|
+
domain,
|
|
325
|
+
feature,
|
|
326
|
+
});
|
|
327
|
+
return {
|
|
328
|
+
content: [
|
|
329
|
+
{
|
|
330
|
+
type: "text",
|
|
331
|
+
text: JSON.stringify({ archivedCount }, null, 2),
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
finally {
|
|
337
|
+
db.close();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (request.params.name === "list_active_worktrees") {
|
|
341
|
+
const baseBranch = String(request.params.arguments?.baseBranch ?? "").trim() || "main";
|
|
342
|
+
const worktrees = listActiveWorktrees(repoPath);
|
|
343
|
+
const db = openDatabase(dbPath);
|
|
344
|
+
try {
|
|
345
|
+
for (const worktree of worktrees) {
|
|
346
|
+
upsertWorktreeSession(db, {
|
|
347
|
+
path: worktree.path,
|
|
348
|
+
branch: worktree.branch,
|
|
349
|
+
baseBranch,
|
|
350
|
+
lastSyncedAt: new Date().toISOString(),
|
|
351
|
+
});
|
|
130
352
|
}
|
|
131
|
-
|
|
353
|
+
}
|
|
354
|
+
finally {
|
|
355
|
+
db.close();
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
content: [{ type: "text", text: JSON.stringify(worktrees, null, 2) }],
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
if (request.params.name === "who_changed_this") {
|
|
362
|
+
const filePath = String(request.params.arguments?.filePath ?? "").trim();
|
|
363
|
+
const limit = Number(request.params.arguments?.limit ?? 20);
|
|
364
|
+
if (!filePath) {
|
|
132
365
|
return {
|
|
133
|
-
content: [{ type: "text", text:
|
|
366
|
+
content: [{ type: "text", text: "filePath is required" }],
|
|
367
|
+
isError: true,
|
|
134
368
|
};
|
|
135
369
|
}
|
|
370
|
+
const output = whoChangedFile({
|
|
371
|
+
repoPath,
|
|
372
|
+
filePath,
|
|
373
|
+
limit: Number.isFinite(limit) && limit > 0 ? limit : 20,
|
|
374
|
+
});
|
|
136
375
|
return {
|
|
137
|
-
content: [
|
|
138
|
-
{ type: "text", text: `Unknown tool: ${request.params.name}` },
|
|
139
|
-
],
|
|
140
|
-
isError: true,
|
|
376
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
141
377
|
};
|
|
142
378
|
}
|
|
143
|
-
|
|
144
|
-
|
|
379
|
+
if (request.params.name === "why_was_this_changed") {
|
|
380
|
+
const owner = String(request.params.arguments?.owner ?? "").trim();
|
|
381
|
+
const repo = String(request.params.arguments?.repo ?? "").trim();
|
|
382
|
+
const filePath = String(request.params.arguments?.filePath ?? "").trim();
|
|
383
|
+
const rawSha = String(request.params.arguments?.sha ?? "").trim();
|
|
384
|
+
const sha = rawSha || (filePath ? latestCommitForFile(repoPath, filePath) : null);
|
|
385
|
+
if (!sha) {
|
|
386
|
+
return {
|
|
387
|
+
content: [
|
|
388
|
+
{
|
|
389
|
+
type: "text",
|
|
390
|
+
text: "Provide sha or a filePath that has commit history.",
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
isError: true,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
const commit = commitDetails(repoPath, sha);
|
|
397
|
+
const prNumber = detectReferencedPrNumber(`${commit.subject}\n${commit.body}`);
|
|
398
|
+
let prContext = { pr: null, decisions: [] };
|
|
399
|
+
if (prNumber) {
|
|
400
|
+
const db = openDatabase(dbPath);
|
|
401
|
+
try {
|
|
402
|
+
prContext = loadPullRequestContext(db, prNumber, owner || undefined, repo || undefined);
|
|
403
|
+
}
|
|
404
|
+
finally {
|
|
405
|
+
db.close();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const result = {
|
|
409
|
+
filePath: filePath || null,
|
|
410
|
+
commit,
|
|
411
|
+
referencedPullRequestNumber: prNumber,
|
|
412
|
+
pullRequest: prContext.pr,
|
|
413
|
+
decisions: prContext.decisions,
|
|
414
|
+
};
|
|
415
|
+
return {
|
|
416
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
if (request.params.name === "get_main_branch_overnight_brief") {
|
|
420
|
+
const baseBranch = String(request.params.arguments?.baseBranch ?? "").trim() || "main";
|
|
421
|
+
const sinceHours = Number(request.params.arguments?.sinceHours ?? 12);
|
|
422
|
+
const limit = Number(request.params.arguments?.limit ?? defaultLimit);
|
|
423
|
+
fetchRemote(repoPath);
|
|
424
|
+
const brief = mainBranchOvernightBrief({
|
|
425
|
+
repoPath,
|
|
426
|
+
baseBranch,
|
|
427
|
+
sinceHours: Number.isFinite(sinceHours) ? sinceHours : 12,
|
|
428
|
+
limit: Number.isFinite(limit) ? limit : defaultLimit,
|
|
429
|
+
});
|
|
430
|
+
return {
|
|
431
|
+
content: [{ type: "text", text: JSON.stringify(brief, null, 2) }],
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
if (request.params.name === "resume_feature_session_brief") {
|
|
435
|
+
const worktreePath = String(request.params.arguments?.worktreePath ?? "").trim() || repoPath;
|
|
436
|
+
const baseBranch = String(request.params.arguments?.baseBranch ?? "").trim() || "main";
|
|
437
|
+
fetchRemote(repoPath);
|
|
438
|
+
const brief = resumeFeatureSessionBrief({
|
|
439
|
+
worktreePath,
|
|
440
|
+
baseBranch,
|
|
441
|
+
});
|
|
442
|
+
const db = openDatabase(dbPath);
|
|
443
|
+
try {
|
|
444
|
+
upsertWorktreeSession(db, {
|
|
445
|
+
path: brief.worktreePath,
|
|
446
|
+
branch: brief.branch,
|
|
447
|
+
baseBranch,
|
|
448
|
+
lastSyncedAt: new Date().toISOString(),
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
finally {
|
|
452
|
+
db.close();
|
|
453
|
+
}
|
|
454
|
+
return {
|
|
455
|
+
content: [{ type: "text", text: JSON.stringify(brief, null, 2) }],
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
if (request.params.name === "pre_plan_sync_brief") {
|
|
459
|
+
const owner = String(request.params.arguments?.owner ?? "").trim();
|
|
460
|
+
const repo = String(request.params.arguments?.repo ?? "").trim();
|
|
461
|
+
const baseBranch = String(request.params.arguments?.baseBranch ?? "").trim() || "main";
|
|
462
|
+
const worktreePath = String(request.params.arguments?.worktreePath ?? "").trim() || repoPath;
|
|
463
|
+
const filePath = String(request.params.arguments?.filePath ?? "").trim();
|
|
464
|
+
const sinceHours = Number(request.params.arguments?.sinceHours ?? 12);
|
|
465
|
+
const limit = Number(request.params.arguments?.limit ?? 25);
|
|
466
|
+
if (!owner || !repo) {
|
|
467
|
+
return {
|
|
468
|
+
content: [{ type: "text", text: "owner and repo are required" }],
|
|
469
|
+
isError: true,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
fetchRemote(repoPath);
|
|
473
|
+
const syncSummary = await syncPullRequestContext({
|
|
474
|
+
repoPath,
|
|
475
|
+
dbPath,
|
|
476
|
+
repoOwner: owner,
|
|
477
|
+
repoName: repo,
|
|
478
|
+
limit,
|
|
479
|
+
});
|
|
480
|
+
const overnight = mainBranchOvernightBrief({
|
|
481
|
+
repoPath,
|
|
482
|
+
baseBranch,
|
|
483
|
+
sinceHours: Number.isFinite(sinceHours) ? sinceHours : 12,
|
|
484
|
+
limit,
|
|
485
|
+
});
|
|
486
|
+
const resume = resumeFeatureSessionBrief({
|
|
487
|
+
worktreePath,
|
|
488
|
+
baseBranch,
|
|
489
|
+
});
|
|
490
|
+
const fileFocus = filePath
|
|
491
|
+
? whoChangedFile({
|
|
492
|
+
repoPath,
|
|
493
|
+
filePath,
|
|
494
|
+
limit: 10,
|
|
495
|
+
})
|
|
496
|
+
: null;
|
|
497
|
+
const prePlan = {
|
|
498
|
+
syncSummary,
|
|
499
|
+
overnight,
|
|
500
|
+
resume,
|
|
501
|
+
fileFocus,
|
|
502
|
+
recommendations: [
|
|
503
|
+
"Review blocker-level decisions from synced PR context first.",
|
|
504
|
+
"Rebase or merge main if behind is non-zero before coding.",
|
|
505
|
+
"Resolve overlap files before expanding feature scope.",
|
|
506
|
+
],
|
|
507
|
+
};
|
|
508
|
+
return {
|
|
509
|
+
content: [{ type: "text", text: JSON.stringify(prePlan, null, 2) }],
|
|
510
|
+
};
|
|
145
511
|
}
|
|
512
|
+
return {
|
|
513
|
+
content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
|
|
514
|
+
isError: true,
|
|
515
|
+
};
|
|
146
516
|
});
|
|
147
517
|
const transport = new StdioServerTransport();
|
|
148
518
|
await server.connect(transport);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PullRequestSyncSummary } from "../types.js";
|
|
2
|
+
export declare function syncPullRequestContext(options: {
|
|
3
|
+
repoPath: string;
|
|
4
|
+
dbPath: string;
|
|
5
|
+
repoOwner: string;
|
|
6
|
+
repoName: string;
|
|
7
|
+
prNumbers?: number[];
|
|
8
|
+
limit?: number;
|
|
9
|
+
domain?: string;
|
|
10
|
+
feature?: string;
|
|
11
|
+
branch?: string;
|
|
12
|
+
taskType?: string;
|
|
13
|
+
}): Promise<PullRequestSyncSummary>;
|