@splicr/mcp-server 0.14.2 → 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 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];
@@ -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;
@@ -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>;
@@ -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',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splicr/mcp-server",
3
- "version": "0.14.2",
3
+ "version": "0.15.1",
4
4
  "description": "Splicr MCP server — route what you read to what you're building",
5
5
  "type": "module",
6
6
  "bin": "./dist/cli.js",