@jussmor/commit-memory-mcp 0.3.1 → 0.3.2

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 CHANGED
@@ -47,3 +47,14 @@ For MCP Registry publication, keep `package.json` `mcpName` and `server.json` `n
47
47
  - `OLLAMA_EMBED_MODEL` local embedding model name
48
48
 
49
49
  If `OLLAMA_EMBED_MODEL` is not set, the package uses deterministic local fallback embeddings.
50
+
51
+ ### Copilot LLM reranking (optional)
52
+
53
+ Set `COPILOT_TOKEN` to a GitHub token with Copilot access to enable LLM-based reranking.
54
+ After initial vector/keyword retrieval, results are sent to Copilot for semantic scoring and re-sorted.
55
+
56
+ - `COPILOT_TOKEN` GitHub PAT or token with Copilot access (enables reranking)
57
+ - `COPILOT_MODEL` model slug (default: `gpt-4o-mini`, supports `claude-sonnet-4-5`, `gpt-4o`, etc.)
58
+ - `COPILOT_BASE_URL` API base URL (default: `https://api.githubcopilot.com`)
59
+
60
+ Reranking works alongside or instead of Ollama — no embedding model required.
@@ -1,4 +1,5 @@
1
1
  import { embedText } from "./embeddings.js";
2
+ import { copilotRerankEnabled, rerankWithCopilot } from "./rerank.js";
2
3
  function scoreWithBoost(base, row, activeFile) {
3
4
  let score = base;
4
5
  if (activeFile && row.file_path === activeFile) {
@@ -16,6 +17,9 @@ function createPreview(hunkText) {
16
17
  return hunkText.split("\n").slice(0, 6).join("\n");
17
18
  }
18
19
  export async function searchRelatedCommits(db, query, limit, activeFile) {
20
+ // Fetch extra candidates when Copilot reranking is enabled so the LLM has
21
+ // more to work with before we trim to the requested limit.
22
+ const fetchLimit = copilotRerankEnabled() ? limit * 2 : limit;
19
23
  const embedding = await embedText(query);
20
24
  const embeddingJson = JSON.stringify(embedding);
21
25
  try {
@@ -36,8 +40,8 @@ export async function searchRelatedCommits(db, query, limit, activeFile) {
36
40
  JOIN commits cm ON cm.sha = c.sha
37
41
  WHERE v.embedding MATCH ? AND k = ?
38
42
  `)
39
- .all(embeddingJson, limit);
40
- return rows.map((row) => {
43
+ .all(embeddingJson, fetchLimit);
44
+ const candidates = rows.map((row) => {
41
45
  const base = 1 / (1 + Math.max(0, row.distance));
42
46
  const score = scoreWithBoost(base, row, activeFile);
43
47
  return {
@@ -51,6 +55,8 @@ export async function searchRelatedCommits(db, query, limit, activeFile) {
51
55
  preview: createPreview(row.hunk_text),
52
56
  };
53
57
  });
58
+ const reranked = await rerankWithCopilot(query, candidates);
59
+ return reranked.slice(0, limit);
54
60
  }
55
61
  catch {
56
62
  const rows = db
@@ -69,8 +75,8 @@ export async function searchRelatedCommits(db, query, limit, activeFile) {
69
75
  ORDER BY cm.date DESC
70
76
  LIMIT ?
71
77
  `)
72
- .all(`%${query}%`, limit);
73
- return rows.map((row, idx) => ({
78
+ .all(`%${query}%`, fetchLimit);
79
+ const keywordCandidates = rows.map((row, idx) => ({
74
80
  chunkId: row.chunk_id,
75
81
  sha: row.sha,
76
82
  filePath: row.file_path,
@@ -80,6 +86,8 @@ export async function searchRelatedCommits(db, query, limit, activeFile) {
80
86
  author: row.author,
81
87
  preview: createPreview(row.hunk_text),
82
88
  }));
89
+ const rerankedKeyword = await rerankWithCopilot(query, keywordCandidates);
90
+ return rerankedKeyword.slice(0, limit);
83
91
  }
84
92
  }
85
93
  export function explainCommitMatch(db, chunkId) {
@@ -0,0 +1,12 @@
1
+ import type { SearchResult } from "../types.js";
2
+ /**
3
+ * Rerank search results using GitHub Copilot chat completions (Claude / GPT).
4
+ *
5
+ * Activated when COPILOT_TOKEN is set.
6
+ * Env vars:
7
+ * COPILOT_TOKEN – GitHub PAT or token with Copilot access (required)
8
+ * COPILOT_MODEL – model slug (default: "gpt-4o-mini")
9
+ * COPILOT_BASE_URL – API base URL (default: https://api.githubcopilot.com)
10
+ */
11
+ export declare function rerankWithCopilot(query: string, results: SearchResult[]): Promise<SearchResult[]>;
12
+ export declare function copilotRerankEnabled(): boolean;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Rerank search results using GitHub Copilot chat completions (Claude / GPT).
3
+ *
4
+ * Activated when COPILOT_TOKEN is set.
5
+ * Env vars:
6
+ * COPILOT_TOKEN – GitHub PAT or token with Copilot access (required)
7
+ * COPILOT_MODEL – model slug (default: "gpt-4o-mini")
8
+ * COPILOT_BASE_URL – API base URL (default: https://api.githubcopilot.com)
9
+ */
10
+ export async function rerankWithCopilot(query, results) {
11
+ const token = process.env.COPILOT_TOKEN;
12
+ if (!token || results.length === 0) {
13
+ return results;
14
+ }
15
+ const baseUrl = process.env.COPILOT_BASE_URL ?? "https://api.githubcopilot.com";
16
+ const model = process.env.COPILOT_MODEL ?? "gpt-4o-mini";
17
+ const commitList = results
18
+ .map((r, i) => `${i + 1}. [${r.date}] ${r.author} — ${r.subject}\n File: ${r.filePath}\n ${r.preview.split("\n").slice(0, 3).join(" | ")}`)
19
+ .join("\n\n");
20
+ const prompt = `You are a code search assistant. Rate each commit excerpt's relevance to the query on a scale from 0.0 to 1.0.
21
+
22
+ Query: "${query}"
23
+
24
+ Commits:
25
+ ${commitList}
26
+
27
+ Respond with ONLY a JSON array of numbers, one per commit, in the same order. Example: [0.9, 0.2, 0.7]`;
28
+ const messages = [{ role: "user", content: prompt }];
29
+ const response = await fetch(`${baseUrl}/chat/completions`, {
30
+ method: "POST",
31
+ headers: {
32
+ "Content-Type": "application/json",
33
+ Authorization: `Bearer ${token}`,
34
+ "Copilot-Integration-Id": "commit-memory-mcp",
35
+ },
36
+ body: JSON.stringify({
37
+ model,
38
+ messages,
39
+ temperature: 0,
40
+ max_tokens: 256,
41
+ }),
42
+ });
43
+ if (!response.ok) {
44
+ // Fall back to original order if the API call fails
45
+ console.error(`[rerank] Copilot API error ${response.status}, using original order`);
46
+ return results;
47
+ }
48
+ const data = (await response.json());
49
+ const content = data.choices?.[0]?.message?.content ?? "";
50
+ // Extract JSON array from the response (tolerates markdown fences)
51
+ const match = content.match(/\[[\d.,\s]+\]/);
52
+ if (!match) {
53
+ console.error("[rerank] Could not parse scores from Copilot response");
54
+ return results;
55
+ }
56
+ let scores;
57
+ try {
58
+ scores = JSON.parse(match[0]);
59
+ }
60
+ catch {
61
+ return results;
62
+ }
63
+ return results
64
+ .map((result, i) => ({
65
+ ...result,
66
+ score: typeof scores[i] === "number" ? scores[i] : result.score,
67
+ }))
68
+ .sort((a, b) => b.score - a.score);
69
+ }
70
+ export function copilotRerankEnabled() {
71
+ return Boolean(process.env.COPILOT_TOKEN);
72
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jussmor/commit-memory-mcp",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "mcpName": "io.github.jussmor/commit-memory",
5
5
  "description": "Commit-aware RAG with sqlite-vec and MCP tools for local agent workflows",
6
6
  "license": "MIT",