@splicr/mcp-server 0.15.0 → 0.15.1
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/cli.js +108 -0
- package/dist/lib/api-client.d.ts +16 -0
- package/dist/lib/api-client.js +3 -0
- package/dist/lib/github-local.d.ts +6 -0
- package/dist/lib/github-local.js +3 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -201,6 +201,9 @@ async function main() {
|
|
|
201
201
|
case 'webhook':
|
|
202
202
|
await webhookCommand();
|
|
203
203
|
break;
|
|
204
|
+
case 'bootstrap':
|
|
205
|
+
await runBootstrap();
|
|
206
|
+
break;
|
|
204
207
|
case 'dashboard':
|
|
205
208
|
case 'open': {
|
|
206
209
|
const url = 'https://splicr.dev/dashboard';
|
|
@@ -1201,6 +1204,111 @@ async function webhookCommand() {
|
|
|
1201
1204
|
process.exit(1);
|
|
1202
1205
|
}
|
|
1203
1206
|
}
|
|
1207
|
+
async function runBootstrap() {
|
|
1208
|
+
console.error('\n Splicr Bootstrap — extract patterns from PR review history\n');
|
|
1209
|
+
const { hasAuth } = await import('./auth.js');
|
|
1210
|
+
if (!hasAuth()) {
|
|
1211
|
+
console.error(' Not authenticated. Run: splicr setup');
|
|
1212
|
+
process.exit(1);
|
|
1213
|
+
}
|
|
1214
|
+
const { isGhAvailable, extractRepoFullName, ghApi } = await import('./lib/github-local.js');
|
|
1215
|
+
if (!isGhAvailable()) {
|
|
1216
|
+
console.error(' GitHub CLI (gh) not found or not authenticated.');
|
|
1217
|
+
console.error(' Run: gh auth login');
|
|
1218
|
+
process.exit(1);
|
|
1219
|
+
}
|
|
1220
|
+
const repoFullName = extractRepoFullName(process.cwd());
|
|
1221
|
+
if (!repoFullName) {
|
|
1222
|
+
console.error(' No GitHub remote found in this directory.');
|
|
1223
|
+
process.exit(1);
|
|
1224
|
+
}
|
|
1225
|
+
console.error(` Repo: ${repoFullName}`);
|
|
1226
|
+
// Detect project
|
|
1227
|
+
const { detectProject } = await import('./lib/project-detector.js');
|
|
1228
|
+
const detected = await detectProject(process.cwd()).catch(() => null);
|
|
1229
|
+
const projectName = detected?.name || basename(process.cwd());
|
|
1230
|
+
console.error(` Project: ${projectName}`);
|
|
1231
|
+
// Fetch recent merged PRs (last 6 months)
|
|
1232
|
+
console.error(' Fetching PR review comments...\n');
|
|
1233
|
+
const sixMonthsAgo = new Date(Date.now() - 180 * 86400000).toISOString();
|
|
1234
|
+
const comments = ghApi(`repos/${repoFullName}/pulls/comments?sort=created&direction=desc&since=${sixMonthsAgo}&per_page=100`);
|
|
1235
|
+
if (!comments || comments.length === 0) {
|
|
1236
|
+
console.error(' No PR review comments found in the last 6 months.');
|
|
1237
|
+
process.exit(0);
|
|
1238
|
+
}
|
|
1239
|
+
// Filter: 3+ words, non-bot
|
|
1240
|
+
const botSuffixes = ['[bot]', '-bot', '_bot'];
|
|
1241
|
+
const filtered = comments.filter(c => {
|
|
1242
|
+
const wordCount = c.body.trim().split(/\s+/).length;
|
|
1243
|
+
if (wordCount < 3)
|
|
1244
|
+
return false;
|
|
1245
|
+
const login = c.user.login.toLowerCase();
|
|
1246
|
+
if (botSuffixes.some(s => login.endsWith(s)))
|
|
1247
|
+
return false;
|
|
1248
|
+
if (login === 'dependabot' || login === 'renovate' || login === 'codecov')
|
|
1249
|
+
return false;
|
|
1250
|
+
return true;
|
|
1251
|
+
});
|
|
1252
|
+
console.error(` Found ${comments.length} comments, ${filtered.length} after filtering\n`);
|
|
1253
|
+
if (filtered.length === 0) {
|
|
1254
|
+
console.error(' No meaningful review comments found.');
|
|
1255
|
+
process.exit(0);
|
|
1256
|
+
}
|
|
1257
|
+
// We need PR titles for each comment - extract PR numbers from pull_request_url
|
|
1258
|
+
const prNumbers = [...new Set(filtered.map(c => {
|
|
1259
|
+
const match = c.pull_request_url?.match(/\/pulls\/(\d+)$/);
|
|
1260
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
1261
|
+
}).filter(n => n > 0))];
|
|
1262
|
+
// Fetch PR titles in batches
|
|
1263
|
+
const prTitles = new Map();
|
|
1264
|
+
for (const num of prNumbers.slice(0, 50)) {
|
|
1265
|
+
const pr = ghApi(`repos/${repoFullName}/pulls/${num}`);
|
|
1266
|
+
if (pr)
|
|
1267
|
+
prTitles.set(num, pr.title);
|
|
1268
|
+
}
|
|
1269
|
+
// Build payload for API
|
|
1270
|
+
const payload = filtered.slice(0, 100).map(c => {
|
|
1271
|
+
const prMatch = c.pull_request_url?.match(/\/pulls\/(\d+)$/);
|
|
1272
|
+
const prNumber = prMatch ? parseInt(prMatch[1], 10) : 0;
|
|
1273
|
+
return {
|
|
1274
|
+
body: c.body,
|
|
1275
|
+
path: c.path || '',
|
|
1276
|
+
diff_hunk: c.diff_hunk || '',
|
|
1277
|
+
reviewer: c.user.login,
|
|
1278
|
+
pr_number: prNumber,
|
|
1279
|
+
pr_title: prTitles.get(prNumber) || `PR #${prNumber}`,
|
|
1280
|
+
repo: repoFullName,
|
|
1281
|
+
};
|
|
1282
|
+
});
|
|
1283
|
+
// Send to API for extraction
|
|
1284
|
+
const { bootstrapPRComments } = await import('./lib/api-client.js');
|
|
1285
|
+
console.error(` Processing ${payload.length} comments through AI extraction...\n`);
|
|
1286
|
+
try {
|
|
1287
|
+
const result = await bootstrapPRComments({
|
|
1288
|
+
comments: payload,
|
|
1289
|
+
project_name: projectName,
|
|
1290
|
+
});
|
|
1291
|
+
const extracted = result.extracted || 0;
|
|
1292
|
+
const skipped = result.skipped || 0;
|
|
1293
|
+
const duplicates = result.duplicates || 0;
|
|
1294
|
+
console.error(` Done:`);
|
|
1295
|
+
console.error(` ${extracted} patterns extracted`);
|
|
1296
|
+
console.error(` ${skipped} comments skipped (not patterns)`);
|
|
1297
|
+
console.error(` ${duplicates} duplicates`);
|
|
1298
|
+
if (extracted > 0) {
|
|
1299
|
+
console.error(`\n Patterns registered for project "${projectName}".`);
|
|
1300
|
+
console.error(' They will be injected into future agent sessions automatically.\n');
|
|
1301
|
+
}
|
|
1302
|
+
else {
|
|
1303
|
+
console.error('\n No new patterns found. Your PR reviews may already be captured,');
|
|
1304
|
+
console.error(' or the comments were mostly bug fixes/nits rather than conventions.\n');
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
catch (err) {
|
|
1308
|
+
console.error(` Failed: ${err.message}`);
|
|
1309
|
+
process.exit(1);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1204
1312
|
// ─── splicr index ───
|
|
1205
1313
|
async function runIndex() {
|
|
1206
1314
|
const target = process.argv[3];
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -50,6 +50,22 @@ export declare function indexFile(params: {
|
|
|
50
50
|
title: string;
|
|
51
51
|
status: 'created' | 'updated';
|
|
52
52
|
}>;
|
|
53
|
+
export declare function bootstrapPRComments(params: {
|
|
54
|
+
comments: Array<{
|
|
55
|
+
body: string;
|
|
56
|
+
path: string;
|
|
57
|
+
diff_hunk: string;
|
|
58
|
+
reviewer: string;
|
|
59
|
+
pr_number: number;
|
|
60
|
+
pr_title: string;
|
|
61
|
+
repo: string;
|
|
62
|
+
}>;
|
|
63
|
+
project_name: string;
|
|
64
|
+
}): Promise<{
|
|
65
|
+
extracted: number;
|
|
66
|
+
skipped: number;
|
|
67
|
+
duplicates: number;
|
|
68
|
+
}>;
|
|
53
69
|
export declare function listPatterns(projectName: string, status?: string): Promise<any>;
|
|
54
70
|
export declare function deprecatePattern(params: {
|
|
55
71
|
project_name: string;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -61,6 +61,9 @@ export async function saveFromAgent(params) {
|
|
|
61
61
|
export async function indexFile(params) {
|
|
62
62
|
return await apiRequest('POST', '/mcp/index', params);
|
|
63
63
|
}
|
|
64
|
+
export async function bootstrapPRComments(params) {
|
|
65
|
+
return await apiRequest('POST', '/mcp/bootstrap-prs', params);
|
|
66
|
+
}
|
|
64
67
|
export async function listPatterns(projectName, status = 'all') {
|
|
65
68
|
return await apiRequest('GET', `/mcp/patterns?project_name=${encodeURIComponent(projectName)}&status=${status}`);
|
|
66
69
|
}
|
|
@@ -22,4 +22,10 @@ export interface TeamStatus {
|
|
|
22
22
|
}>;
|
|
23
23
|
error?: string;
|
|
24
24
|
}
|
|
25
|
+
/** Check if `gh` CLI is available and authenticated */
|
|
26
|
+
export declare function isGhAvailable(): boolean;
|
|
27
|
+
/** Extract owner/repo from a git remote URL */
|
|
28
|
+
export declare function extractRepoFullName(cwd: string): string | null;
|
|
29
|
+
/** Run a gh api command and parse JSON result */
|
|
30
|
+
export declare function ghApi<T>(endpoint: string): T | null;
|
|
25
31
|
export declare function getLocalGitHubStatus(cwd: string): Promise<TeamStatus>;
|
package/dist/lib/github-local.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { execSync } from 'child_process';
|
|
6
6
|
import { getGitRemoteUrl, normalizeGitUrl } from './project-detector.js';
|
|
7
7
|
/** Check if `gh` CLI is available and authenticated */
|
|
8
|
-
function isGhAvailable() {
|
|
8
|
+
export function isGhAvailable() {
|
|
9
9
|
try {
|
|
10
10
|
execSync('gh auth status', { encoding: 'utf-8', stdio: 'pipe' });
|
|
11
11
|
return true;
|
|
@@ -15,7 +15,7 @@ function isGhAvailable() {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
/** Extract owner/repo from a git remote URL */
|
|
18
|
-
function extractRepoFullName(cwd) {
|
|
18
|
+
export function extractRepoFullName(cwd) {
|
|
19
19
|
const remote = getGitRemoteUrl(cwd);
|
|
20
20
|
if (!remote)
|
|
21
21
|
return null;
|
|
@@ -25,7 +25,7 @@ function extractRepoFullName(cwd) {
|
|
|
25
25
|
return match ? match[1] : null;
|
|
26
26
|
}
|
|
27
27
|
/** Run a gh api command and parse JSON result */
|
|
28
|
-
function ghApi(endpoint) {
|
|
28
|
+
export function ghApi(endpoint) {
|
|
29
29
|
try {
|
|
30
30
|
const result = execSync(`gh api "${endpoint}" --paginate`, {
|
|
31
31
|
encoding: 'utf-8',
|