gitmem-mcp 1.6.1 → 1.6.3

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,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.6.3] - 2026-06-11
11
+
12
+ ### Fixed
13
+ - **Stale npx cache**: All MCP server configs now use `gitmem-mcp@latest` instead of `gitmem-mcp`. Without `@latest`, npx can serve a cached older version indefinitely — the `-y` flag only auto-confirms prompts, it does not force a registry check. Affects: init wizard, configure command, README, docs, and distribution configs.
14
+
15
+ ## [1.6.2] - 2026-06-11
16
+
17
+ ### Fixed
18
+ - **`archive_learning` short-ID resolution**: Accepts 8-character ID prefixes (e.g., `6edd41e6`) in addition to full UUIDs, matching the short IDs shown by `recall` and `search`.
19
+
20
+ ### Added
21
+ - **Write-path health check**: New startup diagnostic detects two silent failure classes — (1) Supabase credentials present but tier resolved to FREE (writes go to local files instead of Supabase), and (2) pro/dev tier but resolved tables don't exist (prefix mismatch). Logs a loud warning at startup instead of failing silently on the first write.
22
+ - **`GITMEM_TABLE_PREFIX` documentation**: Pro setup guide now documents custom table prefix configuration, mismatch symptoms, and troubleshooting steps.
23
+
10
24
  ## [1.6.1] - 2026-05-25
11
25
 
12
26
  ### Fixed
package/README.md CHANGED
@@ -101,7 +101,7 @@ Add this to your MCP client's config file:
101
101
  "mcpServers": {
102
102
  "gitmem": {
103
103
  "command": "npx",
104
- "args": ["-y", "gitmem-mcp"]
104
+ "args": ["-y", "gitmem-mcp@latest"]
105
105
  }
106
106
  }
107
107
  }
package/bin/gitmem.js CHANGED
@@ -313,7 +313,7 @@ function cmdConfigure() {
313
313
  mcpServers: {
314
314
  gitmem: {
315
315
  command: "npx",
316
- args: ["-y", "gitmem-mcp"],
316
+ args: ["-y", "gitmem-mcp@latest"],
317
317
  },
318
318
  },
319
319
  };
@@ -332,7 +332,7 @@ function cmdConfigure() {
332
332
  mcpServers: {
333
333
  gitmem: {
334
334
  command: "npx",
335
- args: ["-y", "gitmem-mcp"],
335
+ args: ["-y", "gitmem-mcp@latest"],
336
336
  env: {
337
337
  SUPABASE_URL: "https://YOUR_PROJECT.supabase.co",
338
338
  SUPABASE_SERVICE_ROLE_KEY: "eyJ...",
@@ -253,11 +253,11 @@ function log(icon, main, detail) {
253
253
  function buildMcpConfig() {
254
254
  const supabaseUrl = process.env.SUPABASE_URL;
255
255
  if (!supabaseUrl) {
256
- return { command: "npx", args: ["-y", "gitmem-mcp"] };
256
+ return { command: "npx", args: ["-y", "gitmem-mcp@latest"] };
257
257
  }
258
258
  return {
259
259
  command: "npx",
260
- args: ["-y", "gitmem-mcp"],
260
+ args: ["-y", "gitmem-mcp@latest"],
261
261
  env: {
262
262
  SUPABASE_URL: supabaseUrl,
263
263
  SUPABASE_SERVICE_ROLE_KEY:
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Zod schema for archive_learning tool parameters
3
+ */
4
+ import { z } from "zod";
5
+ /**
6
+ * Archive learning parameters schema
7
+ *
8
+ * Accepts full UUIDs or short hex prefixes (4-32 chars).
9
+ * Short prefixes are resolved to full UUIDs at runtime.
10
+ */
11
+ export declare const ArchiveLearningParamsSchema: z.ZodObject<{
12
+ id: z.ZodString;
13
+ reason: z.ZodOptional<z.ZodString>;
14
+ }, "strip", z.ZodTypeAny, {
15
+ id: string;
16
+ reason?: string | undefined;
17
+ }, {
18
+ id: string;
19
+ reason?: string | undefined;
20
+ }>;
21
+ export type ArchiveLearningParams = z.infer<typeof ArchiveLearningParamsSchema>;
22
+ //# sourceMappingURL=archive-learning.d.ts.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Zod schema for archive_learning tool parameters
3
+ */
4
+ import { z } from "zod";
5
+ /**
6
+ * Archive learning parameters schema
7
+ *
8
+ * Accepts full UUIDs or short hex prefixes (4-32 chars).
9
+ * Short prefixes are resolved to full UUIDs at runtime.
10
+ */
11
+ export const ArchiveLearningParamsSchema = z.object({
12
+ id: z.string().min(4, "ID must be at least 4 characters (short hex prefix or full UUID)").max(36),
13
+ reason: z.string().max(500).optional(),
14
+ });
15
+ //# sourceMappingURL=archive-learning.js.map
@@ -20,4 +20,5 @@ export * from "./get-transcript.js";
20
20
  export * from "./prepare-context.js";
21
21
  export * from "./absorb-observations.js";
22
22
  export * from "./active-sessions.js";
23
+ export * from "./archive-learning.js";
23
24
  //# sourceMappingURL=index.d.ts.map
@@ -20,4 +20,5 @@ export * from "./get-transcript.js";
20
20
  export * from "./prepare-context.js";
21
21
  export * from "./absorb-observations.js";
22
22
  export * from "./active-sessions.js";
23
+ export * from "./archive-learning.js";
23
24
  //# sourceMappingURL=index.js.map
@@ -22,6 +22,7 @@ import { PrepareContextParamsSchema } from "./prepare-context.js";
22
22
  import { AbsorbObservationsParamsSchema } from "./absorb-observations.js";
23
23
  import { ListThreadsParamsSchema, ResolveThreadParamsSchema } from "./thread.js";
24
24
  import { ContributeFeedbackParamsSchema } from "./contribute-feedback.js";
25
+ import { ArchiveLearningParamsSchema } from "./archive-learning.js";
25
26
  /**
26
27
  * Map of canonical tool names → Zod schemas.
27
28
  * Aliases (gitmem-r, gm-open, etc.) are resolved to canonical names before lookup.
@@ -45,6 +46,7 @@ const TOOL_SCHEMAS = {
45
46
  list_threads: ListThreadsParamsSchema,
46
47
  resolve_thread: ResolveThreadParamsSchema,
47
48
  contribute_feedback: ContributeFeedbackParamsSchema,
49
+ archive_learning: ArchiveLearningParamsSchema,
48
50
  };
49
51
  /**
50
52
  * Map of alias → canonical name for all tool aliases.
@@ -113,7 +115,7 @@ const ALIAS_MAP = {
113
115
  // cleanup_threads — no schema yet
114
116
  "gitmem-cleanup": "cleanup_threads",
115
117
  "gm-cleanup": "cleanup_threads",
116
- // archive_learning — no schema yet
118
+ // archive_learning
117
119
  "gitmem-al": "archive_learning",
118
120
  "gm-archive": "archive_learning",
119
121
  // contribute_feedback
package/dist/server.js CHANGED
@@ -39,6 +39,7 @@ import { contributeFeedback } from "./tools/contribute-feedback.js";
39
39
  import { indexDocs } from "./tools/index-docs.js";
40
40
  import { searchDocsHandler } from "./tools/search-docs.js";
41
41
  import { getCacheStatus, checkCacheHealth, flushCache, startBackgroundInit, } from "./services/startup.js";
42
+ import { checkWritePath } from "./services/write-health.js";
42
43
  import { getEffectTracker } from "./services/effect-tracker.js";
43
44
  import { RIPPLE, ANSI } from "./services/display-protocol.js";
44
45
  import { getProject } from "./services/session-state.js";
@@ -414,10 +415,18 @@ export async function runServer() {
414
415
  else {
415
416
  console.error(`[gitmem:license] Validated: ${result.tier} tier`);
416
417
  }
418
+ // Verify the write path with the FINAL tier (post-validation): catches an
419
+ // invalid license silently downgrading writes to local files.
420
+ void checkWritePath();
417
421
  }).catch((err) => {
418
422
  console.error(`[gitmem:license] Validation error: ${err}`);
419
423
  });
420
424
  }
425
+ else {
426
+ // No license key to validate — verify the write path with the detected tier
427
+ // (catches a GITMEM_TABLE_PREFIX / schema mismatch on the backward-compat path).
428
+ void checkWritePath();
429
+ }
421
430
  if (hasSupabase()) {
422
431
  // Pro/Dev: Initialize local vector search in background (non-blocking)
423
432
  // This loads scars with embeddings directly from Supabase REST API
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Write-path health check.
3
+ *
4
+ * Verifies that (a) the resolved storage tier matches the presence of Supabase
5
+ * credentials and (b) the tables the WRITE path resolves to actually exist on
6
+ * the configured backend. Surfaces two silent-failure classes loudly at
7
+ * startup instead of on the first failed write:
8
+ *
9
+ * 1. free_with_credentials — Supabase creds are present but the tier resolved
10
+ * to FREE (e.g. an invalid/expired/placeholder GITMEM_API_KEY). Writes go
11
+ * to local .gitmem/ files instead of Supabase, while reads (recall,
12
+ * cache-health) can still reach Supabase and mask the problem.
13
+ * 2. missing_tables — pro/dev tier, but the prefixed tables don't exist (e.g.
14
+ * a GITMEM_TABLE_PREFIX mismatch, or the schema was never applied).
15
+ * create_learning / create_decision 404 (PGRST205) on the first write.
16
+ *
17
+ * Fire-and-forget: never throws (whole body is guarded), never blocks startup.
18
+ * Logs a loud warning to stderr when misconfigured, a one-line confirmation
19
+ * otherwise.
20
+ */
21
+ export type WritePathMode = "local" | "free_with_credentials" | "missing_tables" | "supabase" | "skipped";
22
+ export interface WritePathResult {
23
+ ok: boolean;
24
+ mode: WritePathMode;
25
+ missing?: string[];
26
+ }
27
+ export declare function checkWritePath(): Promise<WritePathResult>;
28
+ //# sourceMappingURL=write-health.d.ts.map
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Write-path health check.
3
+ *
4
+ * Verifies that (a) the resolved storage tier matches the presence of Supabase
5
+ * credentials and (b) the tables the WRITE path resolves to actually exist on
6
+ * the configured backend. Surfaces two silent-failure classes loudly at
7
+ * startup instead of on the first failed write:
8
+ *
9
+ * 1. free_with_credentials — Supabase creds are present but the tier resolved
10
+ * to FREE (e.g. an invalid/expired/placeholder GITMEM_API_KEY). Writes go
11
+ * to local .gitmem/ files instead of Supabase, while reads (recall,
12
+ * cache-health) can still reach Supabase and mask the problem.
13
+ * 2. missing_tables — pro/dev tier, but the prefixed tables don't exist (e.g.
14
+ * a GITMEM_TABLE_PREFIX mismatch, or the schema was never applied).
15
+ * create_learning / create_decision 404 (PGRST205) on the first write.
16
+ *
17
+ * Fire-and-forget: never throws (whole body is guarded), never blocks startup.
18
+ * Logs a loud warning to stderr when misconfigured, a one-line confirmation
19
+ * otherwise.
20
+ */
21
+ import { getTier, hasSupabase, getTablePrefix, getTableName } from "./tier.js";
22
+ import { isConfigured, directQuery } from "./supabase-client.js";
23
+ /** Error messages that indicate a resolved table is absent on the backend. */
24
+ const SCHEMA_MISS = /PGRST205|schema cache|does not exist|Could not find the table/i;
25
+ export async function checkWritePath() {
26
+ try {
27
+ // No Supabase credentials → legitimate free tier; local writes are intended.
28
+ if (!isConfigured()) {
29
+ return { ok: true, mode: "local" };
30
+ }
31
+ // Credentials present but tier resolved to free. With SUPABASE_URL set, the
32
+ // only path to free tier is a missing/invalid license — so create_learning /
33
+ // create_decision are writing to local files instead of Supabase.
34
+ if (!hasSupabase()) {
35
+ console.error("\n\u26a0\ufe0f [gitmem] WRITE PATH: Supabase is configured but the tier resolved to FREE.\n" +
36
+ " create_learning / create_decision are writing to local .gitmem/ files, NOT Supabase.\n" +
37
+ " Likely cause: a missing or invalid GITMEM_API_KEY \u2014 it must be a real gitmem_pro_... key,\n" +
38
+ " present in the SAME environment as this server. Check the startup 'Tier:' line and the\n" +
39
+ " device limit (3). Reads (recall, cache-health) can still reach Supabase, masking this.\n");
40
+ return { ok: false, mode: "free_with_credentials" };
41
+ }
42
+ // Pro/dev: probe the tables the write path actually resolves to. learnings
43
+ // and decisions hard-fail on a prefix/schema mismatch (threads fall back to
44
+ // local), so probing those two is sufficient and avoids column assumptions.
45
+ const prefix = getTablePrefix();
46
+ const prefixSource = process.env.GITMEM_TABLE_PREFIX ? "GITMEM_TABLE_PREFIX" : "default";
47
+ const missing = [];
48
+ for (const base of ["learnings", "decisions"]) {
49
+ const table = getTableName(base);
50
+ try {
51
+ await directQuery(table, { select: "id", limit: 1 });
52
+ }
53
+ catch (err) {
54
+ const msg = err instanceof Error ? err.message : String(err);
55
+ if (SCHEMA_MISS.test(msg))
56
+ missing.push(table);
57
+ // Transient network/auth errors are out of scope for this check.
58
+ }
59
+ }
60
+ if (missing.length > 0) {
61
+ console.error("\n\u26a0\ufe0f [gitmem] WRITE PATH: these resolved tables do not exist on the Supabase backend:\n" +
62
+ ` ${missing.join(", ")}\n` +
63
+ ` Resolved table prefix: "${prefix}" (from ${prefixSource}).\n` +
64
+ " create_learning / create_decision WILL FAIL until this is fixed:\n" +
65
+ " - Pointing at an existing schema (e.g. orchestra_)? Set GITMEM_TABLE_PREFIX to match.\n" +
66
+ " - Fresh project? Run `npx gitmem-mcp setup` (or set DATABASE_URL and re-activate) to create tables.\n");
67
+ return { ok: false, mode: "missing_tables", missing };
68
+ }
69
+ console.error(`[gitmem] Write-path OK (tier ${getTier()}, prefix "${prefix}", learnings/decisions present).`);
70
+ return { ok: true, mode: "supabase" };
71
+ }
72
+ catch (err) {
73
+ // The health check must never break startup. On any unexpected error, stay
74
+ // silent (don't false-alarm) rather than throw from a floated promise.
75
+ console.error(`[gitmem] Write-path check skipped: ${err instanceof Error ? err.message : String(err)}`);
76
+ return { ok: true, mode: "skipped" };
77
+ }
78
+ }
79
+ //# sourceMappingURL=write-health.js.map
@@ -9,7 +9,7 @@
9
9
  * removed from in-memory search results.
10
10
  */
11
11
  export interface ArchiveLearningParams {
12
- /** UUID of the learning to archive */
12
+ /** UUID or short ID prefix of the learning to archive */
13
13
  id: string;
14
14
  /** Optional reason for archiving */
15
15
  reason?: string;
@@ -8,16 +8,64 @@
8
8
  * Also triggers a local cache flush so the archived scar is immediately
9
9
  * removed from in-memory search results.
10
10
  */
11
- import { directPatch, isConfigured } from "../services/supabase-client.js";
11
+ import { directPatch, directQuery, isConfigured } from "../services/supabase-client.js";
12
12
  import { hasSupabase, getTableName } from "../services/tier.js";
13
13
  import { getStorage } from "../services/storage.js";
14
14
  import { flushCache } from "../services/startup.js";
15
15
  import { Timer } from "../services/metrics.js";
16
16
  import { wrapDisplay } from "../services/display-protocol.js";
17
+ const FULL_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18
+ const HEX_PREFIX_RE = /^[0-9a-f]{4,32}$/i;
19
+ /**
20
+ * Resolve a short hex ID prefix to a full UUID.
21
+ * Git-style prefix resolution: accepts 4-32 char hex prefixes.
22
+ * Full UUIDs pass through unchanged.
23
+ */
24
+ async function resolveIdPrefix(input) {
25
+ // Full UUID — pass through
26
+ if (FULL_UUID_RE.test(input)) {
27
+ return { id: input };
28
+ }
29
+ // Validate hex prefix format
30
+ if (!HEX_PREFIX_RE.test(input)) {
31
+ return { error: `Invalid ID format: "${input}". Provide a full UUID or a 4-32 char hex prefix.` };
32
+ }
33
+ const prefix = input.toLowerCase();
34
+ if (hasSupabase() && isConfigured()) {
35
+ // Supabase: use PostgREST like filter
36
+ const matches = await directQuery(getTableName("learnings"), {
37
+ select: "id",
38
+ filters: { id: `like.${prefix}%` },
39
+ limit: 2,
40
+ });
41
+ if (matches.length === 0) {
42
+ return { error: `No learning found with ID prefix "${prefix}"` };
43
+ }
44
+ if (matches.length > 1) {
45
+ const ids = matches.map(m => m.id.slice(0, 12) + "…").join(", ");
46
+ return { error: `Ambiguous prefix "${prefix}" — matches multiple learnings: ${ids}` };
47
+ }
48
+ return { id: matches[0].id };
49
+ }
50
+ else {
51
+ // Local storage: scan and filter
52
+ const storage = getStorage();
53
+ const all = await storage.query("learnings", {});
54
+ const matches = all.filter(r => r.id.toLowerCase().startsWith(prefix));
55
+ if (matches.length === 0) {
56
+ return { error: `No learning found with ID prefix "${prefix}"` };
57
+ }
58
+ if (matches.length > 1) {
59
+ const ids = matches.map(m => m.id.slice(0, 12) + "…").join(", ");
60
+ return { error: `Ambiguous prefix "${prefix}" — matches multiple learnings: ${ids}` };
61
+ }
62
+ return { id: matches[0].id };
63
+ }
64
+ }
17
65
  export async function archiveLearning(params) {
18
66
  const timer = new Timer();
19
67
  if (!params.id || typeof params.id !== "string") {
20
- const msg = "Missing required parameter: id (UUID of the learning to archive)";
68
+ const msg = "Missing required parameter: id (UUID or short ID prefix of the learning to archive)";
21
69
  return {
22
70
  success: false,
23
71
  id: "",
@@ -27,12 +75,25 @@ export async function archiveLearning(params) {
27
75
  performance_ms: timer.stop(),
28
76
  };
29
77
  }
78
+ // Resolve short prefix to full UUID
79
+ const resolved = await resolveIdPrefix(params.id);
80
+ if ("error" in resolved) {
81
+ return {
82
+ success: false,
83
+ id: params.id,
84
+ cache_flushed: false,
85
+ display: wrapDisplay(resolved.error),
86
+ error: resolved.error,
87
+ performance_ms: timer.stop(),
88
+ };
89
+ }
90
+ const resolvedId = resolved.id;
30
91
  try {
31
92
  const archivedAt = new Date().toISOString();
32
93
  let cacheFlushed = false;
33
94
  if (hasSupabase() && isConfigured()) {
34
95
  // Pro/dev: patch in Supabase
35
- await directPatch(getTableName("learnings"), { id: `eq.${params.id}` }, {
96
+ await directPatch(getTableName("learnings"), { id: `eq.${resolvedId}` }, {
36
97
  is_active: false,
37
98
  archived_at: archivedAt,
38
99
  });
@@ -47,12 +108,12 @@ export async function archiveLearning(params) {
47
108
  else {
48
109
  // Free tier: update in local JSON
49
110
  const storage = getStorage();
50
- const existing = await storage.get("learnings", params.id);
111
+ const existing = await storage.get("learnings", resolvedId);
51
112
  if (!existing) {
52
- const msg = `Learning ${params.id} not found in local storage`;
113
+ const msg = `Learning ${resolvedId} not found in local storage`;
53
114
  return {
54
115
  success: false,
55
- id: params.id,
116
+ id: resolvedId,
56
117
  cache_flushed: false,
57
118
  display: wrapDisplay(msg),
58
119
  error: msg,
@@ -61,17 +122,21 @@ export async function archiveLearning(params) {
61
122
  }
62
123
  await storage.upsert("learnings", {
63
124
  ...existing,
64
- id: params.id,
125
+ id: resolvedId,
65
126
  is_active: false,
66
127
  archived_at: archivedAt,
67
128
  });
68
129
  }
69
130
  const latencyMs = timer.stop();
70
131
  const reasonText = params.reason ? ` Reason: ${params.reason}` : "";
71
- const display = `Archived learning ${params.id}.${reasonText}\n(${latencyMs}ms)`;
132
+ // Show resolution when input differed from resolved ID
133
+ const idDisplay = params.id !== resolvedId
134
+ ? `${params.id} → ${resolvedId}`
135
+ : resolvedId;
136
+ const display = `Archived learning ${idDisplay}.${reasonText}\n(${latencyMs}ms)`;
72
137
  return {
73
138
  success: true,
74
- id: params.id,
139
+ id: resolvedId,
75
140
  archived_at: archivedAt,
76
141
  reason: params.reason,
77
142
  cache_flushed: cacheFlushed,
@@ -84,7 +149,7 @@ export async function archiveLearning(params) {
84
149
  const latencyMs = timer.stop();
85
150
  return {
86
151
  success: false,
87
- id: params.id,
152
+ id: resolvedId,
88
153
  cache_flushed: false,
89
154
  display: wrapDisplay(`Failed to archive learning: ${message}`),
90
155
  error: message,
@@ -2072,7 +2072,7 @@ export const TOOLS = [
2072
2072
  properties: {
2073
2073
  id: {
2074
2074
  type: "string",
2075
- description: "UUID of the learning to archive",
2075
+ description: "UUID or short ID prefix of the learning to archive (e.g., the 8-char prefix shown by recall/search)",
2076
2076
  },
2077
2077
  reason: {
2078
2078
  type: "string",
@@ -2090,7 +2090,7 @@ export const TOOLS = [
2090
2090
  properties: {
2091
2091
  id: {
2092
2092
  type: "string",
2093
- description: "UUID of the learning to archive",
2093
+ description: "UUID or short ID prefix of the learning to archive (e.g., the 8-char prefix shown by recall/search)",
2094
2094
  },
2095
2095
  reason: {
2096
2096
  type: "string",
@@ -2108,7 +2108,7 @@ export const TOOLS = [
2108
2108
  properties: {
2109
2109
  id: {
2110
2110
  type: "string",
2111
- description: "UUID of the learning to archive",
2111
+ description: "UUID or short ID prefix of the learning to archive (e.g., the 8-char prefix shown by recall/search)",
2112
2112
  },
2113
2113
  reason: {
2114
2114
  type: "string",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitmem-mcp",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
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",