gitmem-mcp 1.4.4 → 1.6.0

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +21 -4
  3. package/bin/gitmem.js +10 -0
  4. package/dist/commands/activate.d.ts +20 -0
  5. package/dist/commands/activate.js +562 -0
  6. package/dist/commands/deactivate.d.ts +10 -0
  7. package/dist/commands/deactivate.js +95 -0
  8. package/dist/commands/migrate-local.d.ts +53 -0
  9. package/dist/commands/migrate-local.js +177 -0
  10. package/dist/hooks/format-utils.js +4 -0
  11. package/dist/schemas/log.d.ts +2 -2
  12. package/dist/schemas/search.d.ts +2 -2
  13. package/dist/schemas/session-close.d.ts +12 -12
  14. package/dist/server.js +33 -2
  15. package/dist/services/analytics.d.ts +22 -0
  16. package/dist/services/analytics.js +68 -0
  17. package/dist/services/doc-chunker.d.ts +45 -0
  18. package/dist/services/doc-chunker.js +208 -0
  19. package/dist/services/doc-index.d.ts +88 -0
  20. package/dist/services/doc-index.js +328 -0
  21. package/dist/services/license.d.ts +57 -0
  22. package/dist/services/license.js +200 -0
  23. package/dist/services/supabase-client.d.ts +6 -0
  24. package/dist/services/supabase-client.js +75 -22
  25. package/dist/services/tier.d.ts +13 -3
  26. package/dist/services/tier.js +38 -7
  27. package/dist/tools/definitions.d.ts +688 -0
  28. package/dist/tools/definitions.js +87 -0
  29. package/dist/tools/index-docs.d.ts +30 -0
  30. package/dist/tools/index-docs.js +163 -0
  31. package/dist/tools/prepare-context.js +7 -0
  32. package/dist/tools/recall.js +25 -4
  33. package/dist/tools/search-docs.d.ts +38 -0
  34. package/dist/tools/search-docs.js +94 -0
  35. package/dist/tools/search.js +11 -1
  36. package/dist/tools/session-close.js +76 -7
  37. package/dist/tools/session-start.js +57 -5
  38. package/package.json +1 -1
  39. package/schema/setup.sql +489 -25
@@ -0,0 +1,57 @@
1
+ /**
2
+ * License Key Validation for GitMem Pro Tier
3
+ *
4
+ * Detection chain:
5
+ * 1. GITMEM_TIER env var (explicit override — testing/dev)
6
+ * 2. api_key in config.json or GITMEM_API_KEY env var:
7
+ * a. Check license-cache.json → if valid + not expired (72h) → return cached tier
8
+ * b. No cache → optimistic "pro" (validated async in runServer)
9
+ * 3. No key + no SUPABASE_URL + no config.supabase_url → free
10
+ * 4. No key + SUPABASE_URL set (env var) → pro (backward compat for us)
11
+ *
12
+ * Async validation (validateLicense()):
13
+ * - Called in runServer() startup (non-blocking)
14
+ * - POST to hardcoded validation endpoint with api_key + install_id
15
+ * - Success: cache to ~/.gitmem/license-cache.json (72h TTL)
16
+ * - Failure: downgrade _tier to free, log warning
17
+ * - Network error: honor existing cache if valid, else downgrade
18
+ */
19
+ export interface LicenseValidationResult {
20
+ valid: boolean;
21
+ tier: string | null;
22
+ message: string;
23
+ }
24
+ /**
25
+ * Get license key from env var or config.json
26
+ */
27
+ export declare function getLicenseKey(): string | null;
28
+ /**
29
+ * Get Pro config (Supabase + OpenRouter credentials) from config.json
30
+ * Env vars override config.json values.
31
+ */
32
+ export declare function getProConfig(): {
33
+ supabaseUrl: string;
34
+ supabaseKey: string;
35
+ openrouterKey: string;
36
+ };
37
+ /**
38
+ * Delete license cache (used by deactivate)
39
+ */
40
+ export declare function clearLicenseCache(): void;
41
+ /**
42
+ * Check if license key has a valid cached result (non-async, for tier detection)
43
+ */
44
+ export declare function getCachedLicenseTier(): string | null;
45
+ /**
46
+ * Validate license key against GitMem's Supabase RPC endpoint.
47
+ * Calls gitmem_validate_license() via PostgREST using the anon key.
48
+ * Returns the validation result.
49
+ *
50
+ * This is async and should be called non-blocking during startup.
51
+ */
52
+ export declare function validateLicense(apiKey: string, installId: string): Promise<LicenseValidationResult>;
53
+ /**
54
+ * Get the validation URL (for diagnostics/testing)
55
+ */
56
+ export declare function getValidationUrl(): string;
57
+ //# sourceMappingURL=license.d.ts.map
@@ -0,0 +1,200 @@
1
+ /**
2
+ * License Key Validation for GitMem Pro Tier
3
+ *
4
+ * Detection chain:
5
+ * 1. GITMEM_TIER env var (explicit override — testing/dev)
6
+ * 2. api_key in config.json or GITMEM_API_KEY env var:
7
+ * a. Check license-cache.json → if valid + not expired (72h) → return cached tier
8
+ * b. No cache → optimistic "pro" (validated async in runServer)
9
+ * 3. No key + no SUPABASE_URL + no config.supabase_url → free
10
+ * 4. No key + SUPABASE_URL set (env var) → pro (backward compat for us)
11
+ *
12
+ * Async validation (validateLicense()):
13
+ * - Called in runServer() startup (non-blocking)
14
+ * - POST to hardcoded validation endpoint with api_key + install_id
15
+ * - Success: cache to ~/.gitmem/license-cache.json (72h TTL)
16
+ * - Failure: downgrade _tier to free, log warning
17
+ * - Network error: honor existing cache if valid, else downgrade
18
+ */
19
+ import * as fs from "fs";
20
+ import * as path from "path";
21
+ import { getGitmemDir } from "./gitmem-dir.js";
22
+ // Hardcoded validation endpoint — calls RPC directly on our Supabase via PostgREST.
23
+ // Users never see or configure this URL.
24
+ const VALIDATION_URL = "https://cjptxyezuxdiinufgrrm.supabase.co/rest/v1/rpc/gitmem_validate_license";
25
+ // Anon key for our project (safe to embed — RPC is SECURITY DEFINER, RLS blocks direct table access)
26
+ const VALIDATION_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNqcHR4eWV6dXhkaWludWZncnJtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjYxODY3MDMsImV4cCI6MjA4MTc2MjcwM30.L0oZy3LYCMikmZ15IUU5DnfJmucM37DJ14nUkM3AreY";
27
+ // Cache TTL: 72 hours
28
+ const CACHE_TTL_MS = 72 * 60 * 60 * 1000;
29
+ /**
30
+ * Get license key from env var or config.json
31
+ */
32
+ export function getLicenseKey() {
33
+ // Env var takes priority
34
+ const envKey = process.env.GITMEM_API_KEY;
35
+ if (envKey)
36
+ return envKey;
37
+ // Read from config.json
38
+ try {
39
+ const configPath = path.join(getGitmemDir(), "config.json");
40
+ if (fs.existsSync(configPath)) {
41
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
42
+ if (raw.api_key && typeof raw.api_key === "string") {
43
+ return raw.api_key;
44
+ }
45
+ }
46
+ }
47
+ catch {
48
+ // File doesn't exist or is invalid
49
+ }
50
+ return null;
51
+ }
52
+ /**
53
+ * Get Pro config (Supabase + OpenRouter credentials) from config.json
54
+ * Env vars override config.json values.
55
+ */
56
+ export function getProConfig() {
57
+ let supabaseUrl = process.env.SUPABASE_URL || "";
58
+ let supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_KEY || "";
59
+ let openrouterKey = process.env.OPENROUTER_API_KEY || "";
60
+ // Fall back to config.json
61
+ try {
62
+ const configPath = path.join(getGitmemDir(), "config.json");
63
+ if (fs.existsSync(configPath)) {
64
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
65
+ if (!supabaseUrl && raw.supabase_url)
66
+ supabaseUrl = raw.supabase_url;
67
+ if (!supabaseKey && raw.supabase_key)
68
+ supabaseKey = raw.supabase_key;
69
+ if (!openrouterKey && raw.openrouter_key)
70
+ openrouterKey = raw.openrouter_key;
71
+ }
72
+ }
73
+ catch {
74
+ // File doesn't exist or is invalid
75
+ }
76
+ return { supabaseUrl, supabaseKey, openrouterKey };
77
+ }
78
+ /**
79
+ * Read cached license validation result
80
+ */
81
+ function readLicenseCache() {
82
+ try {
83
+ const cachePath = path.join(getGitmemDir(), "license-cache.json");
84
+ if (!fs.existsSync(cachePath))
85
+ return null;
86
+ const raw = JSON.parse(fs.readFileSync(cachePath, "utf-8"));
87
+ const validatedAt = new Date(raw.validated_at).getTime();
88
+ const now = Date.now();
89
+ // Check TTL
90
+ if (now - validatedAt > CACHE_TTL_MS) {
91
+ console.error("[gitmem:license] Cache expired");
92
+ return null;
93
+ }
94
+ return raw;
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ }
100
+ /**
101
+ * Write license validation result to cache
102
+ */
103
+ function writeLicenseCache(result) {
104
+ try {
105
+ const gitmemDir = getGitmemDir();
106
+ if (!fs.existsSync(gitmemDir)) {
107
+ fs.mkdirSync(gitmemDir, { recursive: true });
108
+ }
109
+ const cachePath = path.join(gitmemDir, "license-cache.json");
110
+ fs.writeFileSync(cachePath, JSON.stringify(result, null, 2));
111
+ }
112
+ catch (err) {
113
+ console.error("[gitmem:license] Failed to write cache:", err);
114
+ }
115
+ }
116
+ /**
117
+ * Delete license cache (used by deactivate)
118
+ */
119
+ export function clearLicenseCache() {
120
+ try {
121
+ const cachePath = path.join(getGitmemDir(), "license-cache.json");
122
+ if (fs.existsSync(cachePath)) {
123
+ fs.unlinkSync(cachePath);
124
+ }
125
+ }
126
+ catch {
127
+ // Ignore
128
+ }
129
+ }
130
+ /**
131
+ * Check if license key has a valid cached result (non-async, for tier detection)
132
+ */
133
+ export function getCachedLicenseTier() {
134
+ const cache = readLicenseCache();
135
+ if (cache && cache.valid) {
136
+ return cache.tier;
137
+ }
138
+ return null;
139
+ }
140
+ /**
141
+ * Validate license key against GitMem's Supabase RPC endpoint.
142
+ * Calls gitmem_validate_license() via PostgREST using the anon key.
143
+ * Returns the validation result.
144
+ *
145
+ * This is async and should be called non-blocking during startup.
146
+ */
147
+ export async function validateLicense(apiKey, installId) {
148
+ try {
149
+ const controller = new AbortController();
150
+ const timeout = setTimeout(() => controller.abort(), 10000); // 10s timeout
151
+ const response = await fetch(VALIDATION_URL, {
152
+ method: "POST",
153
+ headers: {
154
+ "Content-Type": "application/json",
155
+ apikey: VALIDATION_ANON_KEY,
156
+ Authorization: `Bearer ${VALIDATION_ANON_KEY}`,
157
+ },
158
+ body: JSON.stringify({ p_api_key: apiKey, p_install_id: installId }),
159
+ signal: controller.signal,
160
+ });
161
+ clearTimeout(timeout);
162
+ if (!response.ok) {
163
+ const text = await response.text().catch(() => "");
164
+ return { valid: false, tier: null, message: `HTTP ${response.status}: ${text}` };
165
+ }
166
+ // PostgREST RPC returns an array of rows
167
+ const rows = await response.json();
168
+ const data = Array.isArray(rows) ? rows[0] : rows;
169
+ if (!data) {
170
+ return { valid: false, tier: null, message: "Empty validation response" };
171
+ }
172
+ // Cache successful validation
173
+ if (data.valid && data.tier) {
174
+ writeLicenseCache({
175
+ valid: true,
176
+ tier: data.tier,
177
+ validated_at: new Date().toISOString(),
178
+ api_key_prefix: apiKey.substring(0, 16) + "...",
179
+ });
180
+ }
181
+ return data;
182
+ }
183
+ catch (err) {
184
+ const message = err instanceof Error ? err.message : "Unknown error";
185
+ // Network error: honor existing cache
186
+ const cache = readLicenseCache();
187
+ if (cache && cache.valid) {
188
+ console.error(`[gitmem:license] Network error, using cached validation: ${message}`);
189
+ return { valid: true, tier: cache.tier, message: "Using cached validation (offline)" };
190
+ }
191
+ return { valid: false, tier: null, message: `Network error: ${message}` };
192
+ }
193
+ }
194
+ /**
195
+ * Get the validation URL (for diagnostics/testing)
196
+ */
197
+ export function getValidationUrl() {
198
+ return VALIDATION_URL;
199
+ }
200
+ //# sourceMappingURL=license.js.map
@@ -37,6 +37,9 @@ export declare function getRecord<T = unknown>(table: string, id: string): Promi
37
37
  export declare function upsertRecord<T = unknown>(table: string, data: Record<string, unknown>): Promise<T>;
38
38
  /**
39
39
  * Semantic search across tables
40
+ *
41
+ * Generates an embedding for the query, then calls the gitmem_semantic_search
42
+ * RPC function directly via PostgREST.
40
43
  */
41
44
  export declare function semanticSearch<T = unknown>(options: SupabaseSearchOptions): Promise<T[]>;
42
45
  /**
@@ -115,6 +118,9 @@ export declare function directPatch<T = unknown>(table: string, filters: Record<
115
118
  export declare function loadScarsWithEmbeddings<T = unknown>(project?: string, limit?: number): Promise<T[]>;
116
119
  /**
117
120
  * Scar search with severity weighting
121
+ *
122
+ * Generates an embedding for the query, then calls the gitmem_scar_search
123
+ * RPC function directly via PostgREST. No Edge Function required.
118
124
  */
119
125
  export declare function scarSearch<T = unknown>(query: string, matchCount?: number, project?: Project): Promise<T[]>;
120
126
  /**
@@ -45,9 +45,11 @@ export function escapePostgRESTValue(value) {
45
45
  // PostgREST uses ( ) , as structural delimiters in or=() expressions
46
46
  return value.replace(/[(),]/g, "");
47
47
  }
48
- // Configuration from environment
49
- const SUPABASE_URL = process.env.SUPABASE_URL || "";
50
- const SUPABASE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_KEY || "";
48
+ // Configuration from environment, with config.json fallback for Pro tier
49
+ import { getProConfig } from "./license.js";
50
+ const _proConfig = getProConfig();
51
+ const SUPABASE_URL = _proConfig.supabaseUrl;
52
+ const SUPABASE_KEY = _proConfig.supabaseKey;
51
53
  // Direct REST API base URL
52
54
  const SUPABASE_REST_URL = SUPABASE_URL ? `${SUPABASE_URL}/rest/v1` : "";
53
55
  /**
@@ -141,21 +143,45 @@ export async function upsertRecord(table, data) {
141
143
  }
142
144
  /**
143
145
  * Semantic search across tables
146
+ *
147
+ * Generates an embedding for the query, then calls the gitmem_semantic_search
148
+ * RPC function directly via PostgREST.
144
149
  */
145
150
  export async function semanticSearch(options) {
146
- const { query, tables, match_count = 10, project } = options;
147
- const args = {
148
- query,
149
- match_count,
150
- };
151
- if (tables && tables.length > 0) {
152
- args.tables = tables;
151
+ if (!isConfigured()) {
152
+ throw new Error("Supabase not configured");
153
153
  }
154
- if (project) {
155
- args.project = project;
154
+ const { query, match_count = 10 } = options;
155
+ // Generate embedding for the query
156
+ const { embed } = await import("./embedding.js");
157
+ const embedding = await embed(query);
158
+ if (!embedding) {
159
+ console.error("[semantic-search] No embedding provider configured — cannot run semantic search");
160
+ return [];
161
+ }
162
+ // Call the RPC function directly via PostgREST
163
+ const rpcName = `${getTableName("").replace(/_$/, "")}_semantic_search`;
164
+ const url = `${SUPABASE_URL}/rest/v1/rpc/${rpcName}`;
165
+ const response = await fetch(url, {
166
+ method: "POST",
167
+ headers: {
168
+ "Content-Type": "application/json",
169
+ apikey: SUPABASE_KEY,
170
+ Authorization: `Bearer ${SUPABASE_KEY}`,
171
+ },
172
+ body: JSON.stringify({
173
+ query_embedding: `[${embedding.join(",")}]`,
174
+ match_count,
175
+ similarity_threshold: 0.0,
176
+ }),
177
+ signal: AbortSignal.timeout(15_000),
178
+ });
179
+ if (!response.ok) {
180
+ const text = await response.text();
181
+ throw new Error(`Supabase RPC error: ${response.status} - ${text.slice(0, 200)}`);
156
182
  }
157
- const result = await callMcp("semantic_search", args);
158
- return result.results || [];
183
+ const rows = (await response.json());
184
+ return rows || [];
159
185
  }
160
186
  // ============================================================================
161
187
  // DIRECT SUPABASE QUERIES (bypass ww-mcp for bulk operations)
@@ -408,17 +434,44 @@ export async function loadScarsWithEmbeddings(project, limit = 500) {
408
434
  }
409
435
  /**
410
436
  * Scar search with severity weighting
437
+ *
438
+ * Generates an embedding for the query, then calls the gitmem_scar_search
439
+ * RPC function directly via PostgREST. No Edge Function required.
411
440
  */
412
441
  export async function scarSearch(query, matchCount = 5, project) {
413
- const args = {
414
- query,
415
- match_count: matchCount,
416
- };
417
- if (project) {
418
- args.project = project;
442
+ if (!isConfigured()) {
443
+ throw new Error("Supabase not configured");
444
+ }
445
+ // Generate embedding for the query
446
+ const { embed } = await import("./embedding.js");
447
+ const embedding = await embed(query);
448
+ if (!embedding) {
449
+ console.error("[scar-search] No embedding provider configured — cannot run semantic search");
450
+ return [];
451
+ }
452
+ // Call the RPC function directly via PostgREST
453
+ const rpcName = `${getTableName("").replace(/_$/, "")}_scar_search`;
454
+ const url = `${SUPABASE_URL}/rest/v1/rpc/${rpcName}`;
455
+ const response = await fetch(url, {
456
+ method: "POST",
457
+ headers: {
458
+ "Content-Type": "application/json",
459
+ apikey: SUPABASE_KEY,
460
+ Authorization: `Bearer ${SUPABASE_KEY}`,
461
+ },
462
+ body: JSON.stringify({
463
+ query_embedding: `[${embedding.join(",")}]`,
464
+ match_count: matchCount,
465
+ similarity_threshold: 0.0,
466
+ }),
467
+ signal: AbortSignal.timeout(15_000),
468
+ });
469
+ if (!response.ok) {
470
+ const text = await response.text();
471
+ throw new Error(`Supabase RPC error: ${response.status} - ${text.slice(0, 200)}`);
419
472
  }
420
- const result = await callMcp("scar_search", args);
421
- return result.results || [];
473
+ const rows = (await response.json());
474
+ return rows || [];
422
475
  }
423
476
  /**
424
477
  * Scar search with caching
@@ -6,15 +6,23 @@
6
6
  * pro — Supabase + embeddings, semantic search, cloud persistence, variants
7
7
  * dev — Everything in pro + compliance, transcripts, metrics
8
8
  *
9
- * Detection:
10
- * GITMEM_TIER=free|pro|dev (explicit override)
11
- * Auto-detect: no SUPABASE_URL free, GITMEM_DEV=1 dev, else → pro
9
+ * Detection chain:
10
+ * 1. GITMEM_TIER env var (explicit override — testing/dev)
11
+ * 2. api_key in config.json or GITMEM_API_KEY env var:
12
+ * a. Check license-cache.json → if valid + not expired (72h) → return cached tier
13
+ * b. No cache → optimistic "pro" (validated async in runServer)
14
+ * 3. No key + no SUPABASE_URL + no config.supabase_url → free
15
+ * 4. No key + SUPABASE_URL set (env var) → pro (backward compat for us)
12
16
  */
13
17
  export type GitMemTier = "free" | "pro" | "dev";
14
18
  /**
15
19
  * Get the current tier (cached after first call)
16
20
  */
17
21
  export declare function getTier(): GitMemTier;
22
+ /**
23
+ * Force-set tier (used by license validation on failure)
24
+ */
25
+ export declare function setTier(tier: GitMemTier): void;
18
26
  /**
19
27
  * Reset tier detection (for testing)
20
28
  */
@@ -33,6 +41,8 @@ export declare function hasTranscripts(): boolean;
33
41
  export declare function hasBatchOperations(): boolean;
34
42
  /** Whether cache management tools are available (pro, dev) */
35
43
  export declare function hasCacheManagement(): boolean;
44
+ /** Whether Pro-tier insights (decay tags, analytics snippets, blindspots) are active (pro, dev) */
45
+ export declare function hasProInsights(): boolean;
36
46
  /** Whether detailed performance metrics recording is active (pro, dev — aligned with hasVariants) */
37
47
  export declare function hasMetrics(): boolean;
38
48
  /** Whether advanced agent detection (5-agent matrix) is active (dev only) */
@@ -6,23 +6,42 @@
6
6
  * pro — Supabase + embeddings, semantic search, cloud persistence, variants
7
7
  * dev — Everything in pro + compliance, transcripts, metrics
8
8
  *
9
- * Detection:
10
- * GITMEM_TIER=free|pro|dev (explicit override)
11
- * Auto-detect: no SUPABASE_URL free, GITMEM_DEV=1 dev, else → pro
9
+ * Detection chain:
10
+ * 1. GITMEM_TIER env var (explicit override — testing/dev)
11
+ * 2. api_key in config.json or GITMEM_API_KEY env var:
12
+ * a. Check license-cache.json → if valid + not expired (72h) → return cached tier
13
+ * b. No cache → optimistic "pro" (validated async in runServer)
14
+ * 3. No key + no SUPABASE_URL + no config.supabase_url → free
15
+ * 4. No key + SUPABASE_URL set (env var) → pro (backward compat for us)
12
16
  */
17
+ import { getLicenseKey, getCachedLicenseTier, getProConfig } from "./license.js";
13
18
  let _tier = null;
14
19
  /**
15
- * Detect tier from environment variables
20
+ * Detect tier from environment variables, license key, and config
16
21
  */
17
22
  function detectTier() {
23
+ // 1. Explicit override via env var (testing/dev)
18
24
  const explicit = process.env.GITMEM_TIER?.toLowerCase();
19
25
  if (explicit === "free" || explicit === "pro" || explicit === "dev") {
20
26
  return explicit;
21
27
  }
22
- const supabaseUrl = process.env.SUPABASE_URL;
28
+ // 2. License key present → check cache or optimistic pro
29
+ const apiKey = getLicenseKey();
30
+ if (apiKey) {
31
+ // 2a. Check cached validation
32
+ const cachedTier = getCachedLicenseTier();
33
+ if (cachedTier === "pro" || cachedTier === "dev") {
34
+ return cachedTier;
35
+ }
36
+ // 2b. Key present but no valid cache → optimistic pro
37
+ // (validated async in runServer, downgraded if invalid)
38
+ return "pro";
39
+ }
40
+ // 3. No key — check for Supabase URL (env var or config.json)
41
+ const supabaseUrl = process.env.SUPABASE_URL || getProConfig().supabaseUrl;
23
42
  if (!supabaseUrl)
24
43
  return "free";
25
- // Dev tier markers
44
+ // 4. Supabase URL set but no license key → backward compat (internal dev)
26
45
  if (process.env.GITMEM_DEV === "true" || process.env.GITMEM_DEV === "1") {
27
46
  return "dev";
28
47
  }
@@ -38,6 +57,13 @@ export function getTier() {
38
57
  }
39
58
  return _tier;
40
59
  }
60
+ /**
61
+ * Force-set tier (used by license validation on failure)
62
+ */
63
+ export function setTier(tier) {
64
+ _tier = tier;
65
+ console.error(`[gitmem] Tier updated: ${tier}`);
66
+ }
41
67
  /**
42
68
  * Reset tier detection (for testing)
43
69
  */
@@ -75,6 +101,10 @@ export function hasBatchOperations() {
75
101
  export function hasCacheManagement() {
76
102
  return getTier() !== "free";
77
103
  }
104
+ /** Whether Pro-tier insights (decay tags, analytics snippets, blindspots) are active (pro, dev) */
105
+ export function hasProInsights() {
106
+ return getTier() !== "free";
107
+ }
78
108
  /** Whether detailed performance metrics recording is active (pro, dev — aligned with hasVariants) */
79
109
  export function hasMetrics() {
80
110
  return getTier() !== "free";
@@ -96,7 +126,8 @@ export function hasEnforcementFields() {
96
126
  */
97
127
  export function getTablePrefix() {
98
128
  // Default prefix for all tiers. Override with GITMEM_TABLE_PREFIX env var.
99
- return process.env.GITMEM_TABLE_PREFIX || "orchestra_";
129
+ // User schema uses gitmem_ prefix. Orchestra infra uses orchestra_ (set via env var).
130
+ return process.env.GITMEM_TABLE_PREFIX || "gitmem_";
100
131
  }
101
132
  /**
102
133
  * Get the fully-qualified table name for a base table name