gitmem-mcp 1.6.3 → 1.6.5

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/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.6.5] - 2026-06-27
11
+
12
+ ### Fixed
13
+ - **`resolve_thread` can now resolve threads created by other sessions**: `list_threads` reads the Supabase source-of-truth, but `resolve_thread` previously matched only the local/session cache — so a thread created by another session showed up in `list_threads` yet returned "Thread not found" when you tried to resolve it. These "visible-but-unresolvable" threads piled up across sessions. `resolve_thread` now falls back to looking the thread up in Supabase (by ID, or by text for `text_match`) before failing, resolves it in the source-of-truth, and syncs the local cache. (GIT-46)
14
+
15
+ ## [1.6.4] - 2026-06-11
16
+
17
+ ### Fixed
18
+ - **Init wizard upgrades stale configs**: Running `npx gitmem-mcp@latest init` on a project with an existing gitmem config now detects `"gitmem-mcp"` without `@latest` in the args and upgrades it in-place. Previously it skipped with "already configured" and left the stale reference untouched.
19
+
10
20
  ## [1.6.3] - 2026-06-11
11
21
 
12
22
  ### Fixed
@@ -555,6 +555,18 @@ async function stepMcpServer() {
555
555
  const hasGitmem = existing?.mcpServers?.gitmem || existing?.mcpServers?.["gitmem-mcp"];
556
556
 
557
557
  if (hasGitmem) {
558
+ // Check if existing config uses stale "gitmem-mcp" without @latest
559
+ const entry = existing.mcpServers.gitmem || existing.mcpServers["gitmem-mcp"];
560
+ const args = entry?.args;
561
+ if (Array.isArray(args)) {
562
+ const idx = args.indexOf("gitmem-mcp");
563
+ if (idx !== -1) {
564
+ args[idx] = "gitmem-mcp@latest";
565
+ writeJson(mcpPath, existing);
566
+ log(CHECK, `Updated MCP args to use gitmem-mcp@latest ${C.dim}(prevents stale npx cache)${C.reset}`);
567
+ return { done: true };
568
+ }
569
+ }
558
570
  log(CHECK, `MCP server already configured ${C.dim}(${mcpName})${C.reset}`);
559
571
  return { done: false };
560
572
  }
@@ -63,6 +63,20 @@ export declare function resolveThreadInSupabase(threadId: string, options?: {
63
63
  resolutionNote?: string;
64
64
  resolvedBySession?: string;
65
65
  }): Promise<boolean>;
66
+ /**
67
+ * Fetch a single thread from Supabase by its thread_id ("t-XXXX").
68
+ *
69
+ * Used by resolve_thread's cross-session fallback (GIT-46): a thread can live
70
+ * in the Supabase source-of-truth (and show up in list_threads) while being
71
+ * absent from this session's local cache because another session created it.
72
+ *
73
+ * Deliberately NOT scoped by project — thread_id is globally unique, and
74
+ * scoping would re-introduce the "can only resolve threads I own" constraint
75
+ * this fallback exists to remove.
76
+ *
77
+ * Returns the mapped ThreadObject, or null if not found / Supabase unavailable.
78
+ */
79
+ export declare function getThreadFromSupabaseById(threadId: string): Promise<ThreadObject | null>;
66
80
  /**
67
81
  * List threads from Supabase with project filter.
68
82
  * Uses threads_lite view (no embedding column).
@@ -145,6 +145,38 @@ export async function resolveThreadInSupabase(threadId, options = {}) {
145
145
  return false;
146
146
  }
147
147
  }
148
+ /**
149
+ * Fetch a single thread from Supabase by its thread_id ("t-XXXX").
150
+ *
151
+ * Used by resolve_thread's cross-session fallback (GIT-46): a thread can live
152
+ * in the Supabase source-of-truth (and show up in list_threads) while being
153
+ * absent from this session's local cache because another session created it.
154
+ *
155
+ * Deliberately NOT scoped by project — thread_id is globally unique, and
156
+ * scoping would re-introduce the "can only resolve threads I own" constraint
157
+ * this fallback exists to remove.
158
+ *
159
+ * Returns the mapped ThreadObject, or null if not found / Supabase unavailable.
160
+ */
161
+ export async function getThreadFromSupabaseById(threadId) {
162
+ if (!hasSupabase() || !supabase.isConfigured()) {
163
+ return null;
164
+ }
165
+ try {
166
+ const rows = await supabase.directQuery(getTableName("threads_lite"), {
167
+ select: "*",
168
+ filters: { thread_id: threadId },
169
+ limit: 1,
170
+ });
171
+ if (rows.length === 0)
172
+ return null;
173
+ return rowToThreadObject(rows[0]);
174
+ }
175
+ catch (error) {
176
+ console.error("[thread-supabase] Failed to get thread by id:", error instanceof Error ? error.message : error);
177
+ return null;
178
+ }
179
+ }
148
180
  /**
149
181
  * List threads from Supabase with project filter.
150
182
  * Uses threads_lite view (no embedding column).
@@ -13,9 +13,9 @@
13
13
  */
14
14
  import { v4 as uuidv4 } from "uuid";
15
15
  import { getTableName } from "../services/tier.js";
16
- import { getThreads, getCurrentSession } from "../services/session-state.js";
17
- import { resolveThread as resolveThreadInList, findThreadById, loadThreadsFile, saveThreadsFile, } from "../services/thread-manager.js";
18
- import { resolveThreadInSupabase } from "../services/thread-supabase.js";
16
+ import { getThreads, getCurrentSession, getProject } from "../services/session-state.js";
17
+ import { resolveThread as resolveThreadInList, findThreadById, findThreadByText, loadThreadsFile, saveThreadsFile, } from "../services/thread-manager.js";
18
+ import { resolveThreadInSupabase, getThreadFromSupabaseById, listThreadsFromSupabase, } from "../services/thread-supabase.js";
19
19
  import { writeTriplesForThreadResolution } from "../services/triple-writer.js";
20
20
  import { getEffectTracker } from "../services/effect-tracker.js";
21
21
  import { getAgentIdentity } from "../services/agent-detection.js";
@@ -66,12 +66,32 @@ export async function resolveThread(params) {
66
66
  effectiveTextMatch = undefined;
67
67
  }
68
68
  // Resolve the thread locally (in-memory / file)
69
- const resolved = resolveThreadInList(threads, {
69
+ let resolved = resolveThreadInList(threads, {
70
70
  threadId: effectiveThreadId,
71
71
  textMatch: effectiveTextMatch,
72
72
  sessionId,
73
73
  resolutionNote: params.resolution_note,
74
74
  });
75
+ // GIT-46: cross-session fallback. list_threads reads the Supabase
76
+ // source-of-truth, but the local match above only sees threads this session
77
+ // created/cached — so a thread created by another session is visible in
78
+ // list_threads yet "not found" here. Before failing, hydrate the thread from
79
+ // Supabase, splice it into the working set, and retry. The downstream flow
80
+ // (local cache write + Supabase resolve + metrics/triples) then proceeds
81
+ // unchanged, syncing the previously-diverged local cache back to the SOT.
82
+ if (!resolved) {
83
+ const hydrated = await hydrateThreadFromSupabase(effectiveThreadId, effectiveTextMatch);
84
+ if (hydrated) {
85
+ if (!findThreadById(threads, hydrated.id)) {
86
+ threads.push(hydrated);
87
+ }
88
+ resolved = resolveThreadInList(threads, {
89
+ threadId: hydrated.id,
90
+ sessionId,
91
+ resolutionNote: params.resolution_note,
92
+ });
93
+ }
94
+ }
75
95
  if (!resolved) {
76
96
  const latencyMs = timer.stop();
77
97
  const searchKey = params.thread_id || params.text_match;
@@ -172,4 +192,28 @@ export async function resolveThread(params) {
172
192
  display: wrapDisplay(resolveMsg),
173
193
  };
174
194
  }
195
+ /**
196
+ * GIT-46: fetch a thread from the Supabase source-of-truth when it isn't in
197
+ * the local/session cache (e.g. created by another session).
198
+ *
199
+ * - By thread_id: direct, project-agnostic lookup (thread_id is globally unique).
200
+ * - By text_match: scan the project's open threads and substring-match, mirroring
201
+ * the local findThreadByText behavior.
202
+ *
203
+ * Returns null when Supabase is unavailable or no thread matches — callers then
204
+ * fall through to the normal "Thread not found" path.
205
+ */
206
+ async function hydrateThreadFromSupabase(threadId, textMatch) {
207
+ if (threadId) {
208
+ return await getThreadFromSupabaseById(threadId);
209
+ }
210
+ if (textMatch) {
211
+ const project = getProject() || "default";
212
+ const openThreads = await listThreadsFromSupabase(project, { statusFilter: "open" });
213
+ if (openThreads) {
214
+ return findThreadByText(openThreads, textMatch);
215
+ }
216
+ }
217
+ return null;
218
+ }
175
219
  //# sourceMappingURL=resolve-thread.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitmem-mcp",
3
- "version": "1.6.3",
3
+ "version": "1.6.5",
4
4
  "mcpName": "io.github.gitmem-dev/gitmem",
5
5
  "description": "Persistent learning memory for AI coding agents. Memory that compounds.",
6
6
  "type": "module",