gitmem-mcp 1.5.1 → 1.6.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.
@@ -0,0 +1,95 @@
1
+ /**
2
+ * GitMem Pro Deactivation
3
+ *
4
+ * 1. Calls gitmem_deactivate_device RPC to remove this device server-side
5
+ * 2. Removes api_key, supabase_url, supabase_key, openrouter_key from config.json
6
+ * 3. Deletes license-cache.json
7
+ * Does NOT remove .gitmem/ directory or local data.
8
+ */
9
+ import * as fs from "fs";
10
+ import * as path from "path";
11
+ import { getGitmemDir, getInstallId } from "../services/gitmem-dir.js";
12
+ import { clearLicenseCache, getLicenseKey, getValidationUrl, } from "../services/license.js";
13
+ // Same infra endpoint as validation — just different RPC
14
+ const DEACTIVATION_URL = getValidationUrl().replace("gitmem_validate_license", "gitmem_deactivate_device");
15
+ const VALIDATION_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNqcHR4eWV6dXhkaWludWZncnJtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjYxODY3MDMsImV4cCI6MjA4MTc2MjcwM30.L0oZy3LYCMikmZ15IUU5DnfJmucM37DJ14nUkM3AreY";
16
+ async function deactivateDeviceRemote(apiKey, installId) {
17
+ try {
18
+ const controller = new AbortController();
19
+ const timeout = setTimeout(() => controller.abort(), 10000);
20
+ const response = await fetch(DEACTIVATION_URL, {
21
+ method: "POST",
22
+ headers: {
23
+ "Content-Type": "application/json",
24
+ apikey: VALIDATION_ANON_KEY,
25
+ Authorization: `Bearer ${VALIDATION_ANON_KEY}`,
26
+ },
27
+ body: JSON.stringify({ p_api_key: apiKey, p_install_id: installId }),
28
+ signal: controller.signal,
29
+ });
30
+ clearTimeout(timeout);
31
+ if (!response.ok) {
32
+ return { success: false, message: `HTTP ${response.status}` };
33
+ }
34
+ const rows = (await response.json());
35
+ const data = Array.isArray(rows) ? rows[0] : rows;
36
+ return data || { success: false, message: "Empty response" };
37
+ }
38
+ catch (err) {
39
+ const message = err instanceof Error ? err.message : "Unknown error";
40
+ return { success: false, message: `Network error: ${message}` };
41
+ }
42
+ }
43
+ export async function main(_args) {
44
+ const gitmemDir = getGitmemDir();
45
+ const configPath = path.join(gitmemDir, "config.json");
46
+ if (!fs.existsSync(configPath)) {
47
+ console.log("No config.json found — nothing to deactivate.");
48
+ return;
49
+ }
50
+ let config = {};
51
+ try {
52
+ config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
53
+ }
54
+ catch {
55
+ console.error("Error reading config.json");
56
+ process.exit(1);
57
+ }
58
+ const apiKey = getLicenseKey();
59
+ const installId = getInstallId();
60
+ const hadKey = !!apiKey;
61
+ // Step 1: Remove device server-side (if we have both key and install_id)
62
+ if (apiKey && installId) {
63
+ const result = await deactivateDeviceRemote(apiKey, installId);
64
+ if (result.success) {
65
+ console.log(` ✓ ${result.message}`);
66
+ }
67
+ else {
68
+ console.log(` ⚠ Server deactivation failed: ${result.message}`);
69
+ console.log(" Local credentials will still be removed.");
70
+ }
71
+ }
72
+ // Step 2: Remove Pro credentials from config
73
+ delete config.api_key;
74
+ delete config.supabase_url;
75
+ delete config.supabase_key;
76
+ delete config.openrouter_key;
77
+ // Write back config (preserving project, install_id, feedback_enabled)
78
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
79
+ // Step 3: Clear license cache
80
+ clearLicenseCache();
81
+ if (hadKey) {
82
+ console.log("\nPro tier deactivated.");
83
+ console.log(" - Device removed from license server");
84
+ console.log(" - License key removed from config.json");
85
+ console.log(" - Supabase and OpenRouter credentials removed");
86
+ console.log(" - License cache cleared");
87
+ console.log("");
88
+ console.log("Local data in .gitmem/ is preserved (scars, threads, sessions).");
89
+ console.log("Restart your editor to switch to free tier.");
90
+ }
91
+ else {
92
+ console.log("No active Pro license found. Already on free tier.");
93
+ }
94
+ }
95
+ //# sourceMappingURL=deactivate.js.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Local-to-Supabase Migration
3
+ *
4
+ * Migrates existing free-tier local .gitmem/ data to Supabase when
5
+ * a user upgrades to Pro. Called during `activate` after schema is
6
+ * verified and credentials are saved.
7
+ *
8
+ * Collections migrated:
9
+ * - learnings (scars, wins, patterns, anti-patterns)
10
+ * - sessions
11
+ * - decisions
12
+ * - scar_usage
13
+ *
14
+ * Threads are NOT migrated — they remain local (thread lifecycle is
15
+ * tied to .gitmem/threads.json and managed by session_start).
16
+ *
17
+ * Migration is idempotent: uses Supabase upsert (merge-duplicates)
18
+ * so re-running is safe. Existing Supabase records with same ID are
19
+ * updated, not duplicated.
20
+ */
21
+ export interface MigrationResult {
22
+ migrated: Record<string, number>;
23
+ skipped: Record<string, number>;
24
+ errors: Record<string, string[]>;
25
+ total: number;
26
+ hasLocalData: boolean;
27
+ }
28
+ /**
29
+ * Check if there is local data worth migrating
30
+ */
31
+ export declare function hasLocalData(gitmemDir?: string): boolean;
32
+ /**
33
+ * Migrate local .gitmem data to Supabase
34
+ *
35
+ * @param supabaseUrl - User's Supabase project URL
36
+ * @param supabaseKey - User's service role key
37
+ * @param tablePrefix - Table prefix (default: "gitmem_")
38
+ * @param gitmemDir - Override .gitmem directory path
39
+ * @param onProgress - Callback for progress reporting
40
+ */
41
+ export declare function migrateLocalToSupabase(opts: {
42
+ supabaseUrl: string;
43
+ supabaseKey: string;
44
+ tablePrefix?: string;
45
+ gitmemDir?: string;
46
+ onProgress?: (msg: string) => void;
47
+ }): Promise<MigrationResult>;
48
+ /**
49
+ * Check if there are .pre-migration backup files that can be re-imported.
50
+ * This handles the case where a previous migration partially failed —
51
+ * the user runs `activate` again and we pick up from the backups.
52
+ */
53
+ export declare function hasPreMigrationData(gitmemDir?: string): boolean;
54
+ /**
55
+ * Re-import from .pre-migration backup files.
56
+ * Same as migrateLocalToSupabase but reads from *.json.pre-migration files.
57
+ * Idempotent: uses upsert (merge-duplicates) so re-running is safe.
58
+ */
59
+ export declare function reimportFromBackups(opts: {
60
+ supabaseUrl: string;
61
+ supabaseKey: string;
62
+ tablePrefix?: string;
63
+ gitmemDir?: string;
64
+ onProgress?: (msg: string) => void;
65
+ }): Promise<MigrationResult>;
66
+ /**
67
+ * Rename local collection files after successful migration
68
+ * Adds .pre-migration suffix so data isn't lost but won't be re-read by free tier
69
+ */
70
+ export declare function archiveLocalData(gitmemDir?: string): string[];
71
+ //# sourceMappingURL=migrate-local.d.ts.map
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Local-to-Supabase Migration
3
+ *
4
+ * Migrates existing free-tier local .gitmem/ data to Supabase when
5
+ * a user upgrades to Pro. Called during `activate` after schema is
6
+ * verified and credentials are saved.
7
+ *
8
+ * Collections migrated:
9
+ * - learnings (scars, wins, patterns, anti-patterns)
10
+ * - sessions
11
+ * - decisions
12
+ * - scar_usage
13
+ *
14
+ * Threads are NOT migrated — they remain local (thread lifecycle is
15
+ * tied to .gitmem/threads.json and managed by session_start).
16
+ *
17
+ * Migration is idempotent: uses Supabase upsert (merge-duplicates)
18
+ * so re-running is safe. Existing Supabase records with same ID are
19
+ * updated, not duplicated.
20
+ */
21
+ import * as fs from "fs";
22
+ import * as path from "path";
23
+ import { getGitmemDir } from "../services/gitmem-dir.js";
24
+ /** Collections that map to Supabase tables */
25
+ const MIGRATABLE_COLLECTIONS = ["learnings", "sessions", "decisions", "scar_usage"];
26
+ /** Fields that should NOT be sent to Supabase (local-only or computed) */
27
+ const STRIP_FIELDS = new Set(["is_starter"]);
28
+ /** Fields that Supabase will reject if null (remove instead of sending null) */
29
+ const NULLABLE_STRIP = new Set(["embedding"]);
30
+ /**
31
+ * Known columns per table — only these fields are sent to Supabase.
32
+ * PostgREST rejects POSTs with unknown columns, so we must whitelist.
33
+ * This prevents accumulated local fields from different code versions
34
+ * from causing migration failures.
35
+ */
36
+ const KNOWN_COLUMNS = {
37
+ learnings: new Set([
38
+ "id", "learning_type", "title", "description", "severity", "scar_type",
39
+ "counter_arguments", "problem_context", "solution_approach", "applies_when",
40
+ "keywords", "domain", "embedding", "project", "source_date",
41
+ "source_linear_issue", "persona_name", "why_this_matters",
42
+ "action_protocol", "self_check_criteria", "is_active",
43
+ "decay_multiplier", "repeat_mistake", "related_scar_id",
44
+ "repeat_mistake_details", "created_at", "updated_at",
45
+ ]),
46
+ sessions: new Set([
47
+ "id", "session_title", "session_date", "agent", "project",
48
+ "linear_issue", "recording_path", "transcript_path", "decisions",
49
+ "open_threads", "closing_reflection", "close_compliance",
50
+ "rapport_summary", "embedding", "created_at", "updated_at",
51
+ ]),
52
+ decisions: new Set([
53
+ "id", "decision_date", "title", "decision", "rationale",
54
+ "alternatives_considered", "personas_involved", "docs_affected",
55
+ "linear_issue", "session_id", "project", "embedding", "created_at",
56
+ ]),
57
+ scar_usage: new Set([
58
+ "id", "scar_id", "session_id", "agent", "issue_id",
59
+ "issue_identifier", "reference_type", "reference_context",
60
+ "surfaced_at", "acknowledged_at", "referenced",
61
+ "execution_successful", "variant_id", "created_at",
62
+ ]),
63
+ };
64
+ /**
65
+ * Check if there is local data worth migrating
66
+ */
67
+ export function hasLocalData(gitmemDir) {
68
+ const dir = gitmemDir || getGitmemDir();
69
+ for (const collection of MIGRATABLE_COLLECTIONS) {
70
+ const filePath = path.join(dir, `${collection}.json`);
71
+ if (fs.existsSync(filePath)) {
72
+ try {
73
+ const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
74
+ if (Array.isArray(data) && data.length > 0)
75
+ return true;
76
+ }
77
+ catch {
78
+ // Corrupt file — skip
79
+ }
80
+ }
81
+ }
82
+ return false;
83
+ }
84
+ /**
85
+ * Read a local collection JSON file
86
+ */
87
+ function readLocalCollection(dir, collection) {
88
+ const filePath = path.join(dir, `${collection}.json`);
89
+ if (!fs.existsSync(filePath))
90
+ return [];
91
+ try {
92
+ const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
93
+ return Array.isArray(data) ? data : [];
94
+ }
95
+ catch {
96
+ return [];
97
+ }
98
+ }
99
+ /**
100
+ * Columns that are TEXT in Supabase but may be stored as arrays locally.
101
+ * During migration, arrays are joined with newlines to fit the TEXT column.
102
+ * This prevents PostgREST 400 errors from type mismatches.
103
+ */
104
+ const TEXT_COERCE_FIELDS = new Set([
105
+ "action_protocol",
106
+ "self_check_criteria",
107
+ "why_this_matters",
108
+ ]);
109
+ /**
110
+ * Clean a record for Supabase insertion:
111
+ * - Strip local-only fields
112
+ * - Remove null values for non-nullable columns
113
+ * - Only include fields that exist in the target table schema
114
+ * - Coerce arrays to strings for TEXT columns
115
+ * - Ensure id exists
116
+ */
117
+ function cleanRecord(record, collection) {
118
+ if (!record.id)
119
+ return null;
120
+ const knownCols = KNOWN_COLUMNS[collection];
121
+ const cleaned = {};
122
+ const droppedFields = [];
123
+ for (const [key, value] of Object.entries(record)) {
124
+ if (STRIP_FIELDS.has(key))
125
+ continue;
126
+ if (NULLABLE_STRIP.has(key) && (value === null || value === undefined))
127
+ continue;
128
+ // Only include fields that exist in the target table
129
+ if (knownCols && !knownCols.has(key)) {
130
+ droppedFields.push(key);
131
+ continue;
132
+ }
133
+ // Coerce arrays to strings for TEXT columns (schema says TEXT, code uses string[])
134
+ if (TEXT_COERCE_FIELDS.has(key) && Array.isArray(value)) {
135
+ cleaned[key] = value.join("\n");
136
+ continue;
137
+ }
138
+ cleaned[key] = value;
139
+ }
140
+ if (droppedFields.length > 0) {
141
+ console.error(`[migrate] Record ${String(record.id).substring(0, 8)}: dropped unknown fields: ${droppedFields.join(", ")}`);
142
+ }
143
+ return cleaned;
144
+ }
145
+ /**
146
+ * Migrate local .gitmem data to Supabase
147
+ *
148
+ * @param supabaseUrl - User's Supabase project URL
149
+ * @param supabaseKey - User's service role key
150
+ * @param tablePrefix - Table prefix (default: "gitmem_")
151
+ * @param gitmemDir - Override .gitmem directory path
152
+ * @param onProgress - Callback for progress reporting
153
+ */
154
+ export async function migrateLocalToSupabase(opts) {
155
+ const { supabaseUrl, supabaseKey, tablePrefix = "gitmem_", onProgress } = opts;
156
+ const dir = opts.gitmemDir || getGitmemDir();
157
+ const log = onProgress || ((msg) => console.log(msg));
158
+ // Migration log file — captures all details for debugging
159
+ const logLines = [];
160
+ const logFile = path.join(dir, "migration.log");
161
+ const mlog = (msg) => {
162
+ const ts = new Date().toISOString();
163
+ logLines.push(`[${ts}] ${msg}`);
164
+ };
165
+ mlog(`Migration started: ${supabaseUrl} (prefix: ${tablePrefix})`);
166
+ const result = {
167
+ migrated: {},
168
+ skipped: {},
169
+ errors: {},
170
+ total: 0,
171
+ hasLocalData: false,
172
+ };
173
+ const restUrl = `${supabaseUrl}/rest/v1`;
174
+ for (const collection of MIGRATABLE_COLLECTIONS) {
175
+ const records = readLocalCollection(dir, collection);
176
+ const tableName = `${tablePrefix}${collection}`;
177
+ result.migrated[collection] = 0;
178
+ result.skipped[collection] = 0;
179
+ result.errors[collection] = [];
180
+ if (records.length === 0) {
181
+ mlog(`${collection}: no local data`);
182
+ continue;
183
+ }
184
+ result.hasLocalData = true;
185
+ mlog(`${collection}: ${records.length} records to migrate → ${tableName}`);
186
+ log(` Migrating ${records.length} ${collection}...`);
187
+ for (const record of records) {
188
+ const cleaned = cleanRecord(record, collection);
189
+ if (!cleaned) {
190
+ result.skipped[collection]++;
191
+ const errMsg = `${String(record.id || "no-id").substring(0, 8)}: skipped (missing id)`;
192
+ result.errors[collection].push(errMsg);
193
+ mlog(`${collection} SKIP: ${errMsg}`);
194
+ result.total++;
195
+ continue;
196
+ }
197
+ try {
198
+ const response = await fetch(`${restUrl}/${tableName}`, {
199
+ method: "POST",
200
+ headers: {
201
+ "apikey": supabaseKey,
202
+ "Authorization": `Bearer ${supabaseKey}`,
203
+ "Content-Type": "application/json",
204
+ "Prefer": "return=minimal,resolution=merge-duplicates",
205
+ "Content-Profile": "public",
206
+ },
207
+ body: JSON.stringify(cleaned),
208
+ signal: AbortSignal.timeout(10_000),
209
+ });
210
+ if (response.ok) {
211
+ result.migrated[collection]++;
212
+ mlog(`${collection} OK: ${String(cleaned.id).substring(0, 8)} (${cleaned.title || ""})`);
213
+ }
214
+ else {
215
+ const text = await response.text();
216
+ const errMsg = `${String(cleaned.id).substring(0, 8)}: ${response.status} - ${text.substring(0, 200)}`;
217
+ result.errors[collection].push(errMsg);
218
+ mlog(`${collection} FAIL: ${errMsg}`);
219
+ mlog(`${collection} FAIL payload: ${JSON.stringify(Object.keys(cleaned))}`);
220
+ result.skipped[collection]++;
221
+ }
222
+ }
223
+ catch (err) {
224
+ const errMsg = `${String(cleaned.id).substring(0, 8)}: ${err instanceof Error ? err.message : "Unknown error"}`;
225
+ result.errors[collection].push(errMsg);
226
+ mlog(`${collection} ERROR: ${errMsg}`);
227
+ result.skipped[collection]++;
228
+ }
229
+ result.total++;
230
+ }
231
+ }
232
+ // Write migration log to disk for debugging
233
+ mlog(`Migration complete: migrated=${JSON.stringify(result.migrated)} skipped=${JSON.stringify(result.skipped)}`);
234
+ try {
235
+ fs.writeFileSync(logFile, logLines.join("\n") + "\n", "utf-8");
236
+ log(` Migration log: ${logFile}`);
237
+ }
238
+ catch {
239
+ // Non-fatal — log file write failure shouldn't block migration
240
+ }
241
+ return result;
242
+ }
243
+ /**
244
+ * Check if there are .pre-migration backup files that can be re-imported.
245
+ * This handles the case where a previous migration partially failed —
246
+ * the user runs `activate` again and we pick up from the backups.
247
+ */
248
+ export function hasPreMigrationData(gitmemDir) {
249
+ const dir = gitmemDir || getGitmemDir();
250
+ for (const collection of MIGRATABLE_COLLECTIONS) {
251
+ const backupPath = path.join(dir, `${collection}.json.pre-migration`);
252
+ if (fs.existsSync(backupPath)) {
253
+ try {
254
+ const data = JSON.parse(fs.readFileSync(backupPath, "utf-8"));
255
+ if (Array.isArray(data) && data.length > 0)
256
+ return true;
257
+ }
258
+ catch {
259
+ // Corrupt backup
260
+ }
261
+ }
262
+ }
263
+ return false;
264
+ }
265
+ /**
266
+ * Re-import from .pre-migration backup files.
267
+ * Same as migrateLocalToSupabase but reads from *.json.pre-migration files.
268
+ * Idempotent: uses upsert (merge-duplicates) so re-running is safe.
269
+ */
270
+ export async function reimportFromBackups(opts) {
271
+ const dir = opts.gitmemDir || getGitmemDir();
272
+ // Temporarily restore .pre-migration files as .json for the migration function
273
+ const restored = [];
274
+ for (const collection of MIGRATABLE_COLLECTIONS) {
275
+ const backupPath = path.join(dir, `${collection}.json.pre-migration`);
276
+ const livePath = path.join(dir, `${collection}.json`);
277
+ if (fs.existsSync(backupPath) && !fs.existsSync(livePath)) {
278
+ // Copy (not move) backup to live path for migration
279
+ fs.copyFileSync(backupPath, livePath);
280
+ restored.push(collection);
281
+ }
282
+ }
283
+ // Run migration
284
+ const result = await migrateLocalToSupabase(opts);
285
+ // Clean up: remove the restored copies (backups remain untouched)
286
+ for (const collection of restored) {
287
+ const livePath = path.join(dir, `${collection}.json`);
288
+ try {
289
+ fs.unlinkSync(livePath);
290
+ }
291
+ catch {
292
+ // Non-fatal
293
+ }
294
+ }
295
+ return result;
296
+ }
297
+ /**
298
+ * Rename local collection files after successful migration
299
+ * Adds .pre-migration suffix so data isn't lost but won't be re-read by free tier
300
+ */
301
+ export function archiveLocalData(gitmemDir) {
302
+ const dir = gitmemDir || getGitmemDir();
303
+ const archived = [];
304
+ for (const collection of MIGRATABLE_COLLECTIONS) {
305
+ const filePath = path.join(dir, `${collection}.json`);
306
+ if (fs.existsSync(filePath)) {
307
+ const archivePath = `${filePath}.pre-migration`;
308
+ // Don't overwrite existing archive
309
+ if (!fs.existsSync(archivePath)) {
310
+ fs.renameSync(filePath, archivePath);
311
+ archived.push(collection);
312
+ }
313
+ }
314
+ }
315
+ return archived;
316
+ }
317
+ //# sourceMappingURL=migrate-local.js.map
@@ -13,14 +13,14 @@ export declare const LogParamsSchema: z.ZodObject<{
13
13
  since: z.ZodOptional<z.ZodNumber>;
14
14
  }, "strip", z.ZodTypeAny, {
15
15
  limit?: number | undefined;
16
- project?: string | undefined;
17
16
  learning_type?: "scar" | "win" | "pattern" | "anti_pattern" | undefined;
17
+ project?: string | undefined;
18
18
  severity?: "critical" | "high" | "medium" | "low" | undefined;
19
19
  since?: number | undefined;
20
20
  }, {
21
21
  limit?: number | undefined;
22
- project?: string | undefined;
23
22
  learning_type?: "scar" | "win" | "pattern" | "anti_pattern" | undefined;
23
+ project?: string | undefined;
24
24
  severity?: "critical" | "high" | "medium" | "low" | undefined;
25
25
  since?: number | undefined;
26
26
  }>;
@@ -14,14 +14,14 @@ export declare const SearchParamsSchema: z.ZodObject<{
14
14
  }, "strip", z.ZodTypeAny, {
15
15
  query: string;
16
16
  match_count?: number | undefined;
17
- project?: string | undefined;
18
17
  learning_type?: "scar" | "win" | "pattern" | "anti_pattern" | undefined;
18
+ project?: string | undefined;
19
19
  severity?: "critical" | "high" | "medium" | "low" | undefined;
20
20
  }, {
21
21
  query: string;
22
22
  match_count?: number | undefined;
23
- project?: string | undefined;
24
23
  learning_type?: "scar" | "win" | "pattern" | "anti_pattern" | undefined;
24
+ project?: string | undefined;
25
25
  severity?: "critical" | "high" | "medium" | "low" | undefined;
26
26
  }>;
27
27
  export type SearchParams = z.infer<typeof SearchParamsSchema>;
@@ -20,20 +20,20 @@ export declare const ClosingReflectionSchema: z.ZodObject<{
20
20
  rapport_notes: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodEffects<z.ZodArray<z.ZodString, "many">, string, string[]>]>>;
21
21
  }, "strip", z.ZodTypeAny, {
22
22
  what_broke: string;
23
- what_took_longer: string;
24
- do_differently: string;
25
23
  what_worked: string;
26
24
  wrong_assumption: string;
25
+ do_differently: string;
26
+ what_took_longer: string;
27
27
  scars_applied: string | string[];
28
28
  institutional_memory_items?: string | undefined;
29
29
  collaborative_dynamic?: string | undefined;
30
30
  rapport_notes?: string | undefined;
31
31
  }, {
32
32
  what_broke: string;
33
- what_took_longer: string;
34
- do_differently: string;
35
33
  what_worked: string;
36
34
  wrong_assumption: string;
35
+ do_differently: string;
36
+ what_took_longer: string;
37
37
  scars_applied: string | string[];
38
38
  institutional_memory_items?: string | string[] | undefined;
39
39
  collaborative_dynamic?: string | string[] | undefined;
@@ -161,20 +161,20 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
161
161
  rapport_notes: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodEffects<z.ZodArray<z.ZodString, "many">, string, string[]>]>>;
162
162
  }, "strip", z.ZodTypeAny, {
163
163
  what_broke: string;
164
- what_took_longer: string;
165
- do_differently: string;
166
164
  what_worked: string;
167
165
  wrong_assumption: string;
166
+ do_differently: string;
167
+ what_took_longer: string;
168
168
  scars_applied: string | string[];
169
169
  institutional_memory_items?: string | undefined;
170
170
  collaborative_dynamic?: string | undefined;
171
171
  rapport_notes?: string | undefined;
172
172
  }, {
173
173
  what_broke: string;
174
- what_took_longer: string;
175
- do_differently: string;
176
174
  what_worked: string;
177
175
  wrong_assumption: string;
176
+ do_differently: string;
177
+ what_took_longer: string;
178
178
  scars_applied: string | string[];
179
179
  institutional_memory_items?: string | string[] | undefined;
180
180
  collaborative_dynamic?: string | string[] | undefined;
@@ -281,10 +281,10 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
281
281
  linear_issue?: string | undefined;
282
282
  closing_reflection?: {
283
283
  what_broke: string;
284
- what_took_longer: string;
285
- do_differently: string;
286
284
  what_worked: string;
287
285
  wrong_assumption: string;
286
+ do_differently: string;
287
+ what_took_longer: string;
288
288
  scars_applied: string | string[];
289
289
  institutional_memory_items?: string | undefined;
290
290
  collaborative_dynamic?: string | undefined;
@@ -338,10 +338,10 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
338
338
  linear_issue?: string | undefined;
339
339
  closing_reflection?: {
340
340
  what_broke: string;
341
- what_took_longer: string;
342
- do_differently: string;
343
341
  what_worked: string;
344
342
  wrong_assumption: string;
343
+ do_differently: string;
344
+ what_took_longer: string;
345
345
  scars_applied: string | string[];
346
346
  institutional_memory_items?: string | string[] | undefined;
347
347
  collaborative_dynamic?: string | string[] | undefined;
package/dist/server.js CHANGED
@@ -43,7 +43,9 @@ import { getEffectTracker } from "./services/effect-tracker.js";
43
43
  import { RIPPLE, ANSI } from "./services/display-protocol.js";
44
44
  import { getProject } from "./services/session-state.js";
45
45
  import { checkEnforcement } from "./services/enforcement.js";
46
- import { getTier, hasSupabase, hasCacheManagement, hasBatchOperations, hasTranscripts, } from "./services/tier.js";
46
+ import { getTier, setTier, hasSupabase, hasCacheManagement, hasBatchOperations, hasTranscripts, } from "./services/tier.js";
47
+ import { getLicenseKey, validateLicense } from "./services/license.js";
48
+ import { getInstallId } from "./services/gitmem-dir.js";
47
49
  import { getRegisteredTools } from "./tools/definitions.js";
48
50
  import { validateToolArgs } from "./schemas/registry.js";
49
51
  /**
@@ -393,13 +395,29 @@ export function createServer() {
393
395
  */
394
396
  export async function runServer() {
395
397
  const tier = getTier();
396
- // Start server immediately (don't block on cache loading)
398
+ // Start server immediately (don't block on cache loading or license validation)
397
399
  const server = createServer();
398
400
  const transport = new StdioServerTransport();
399
401
  await server.connect(transport);
400
402
  const toolCount = getRegisteredTools().length;
401
403
  const storage = hasSupabase() ? "supabase" : "local";
402
404
  console.error(`[gitmem] Tier: ${tier} | Storage: ${storage} | Tools: ${toolCount}`);
405
+ // Async license validation (non-blocking)
406
+ const apiKey = getLicenseKey();
407
+ if (apiKey) {
408
+ const installId = getInstallId() || "unknown";
409
+ validateLicense(apiKey, installId).then((result) => {
410
+ if (!result.valid) {
411
+ console.error(`[gitmem:license] Validation failed: ${result.message}`);
412
+ setTier("free");
413
+ }
414
+ else {
415
+ console.error(`[gitmem:license] Validated: ${result.tier} tier`);
416
+ }
417
+ }).catch((err) => {
418
+ console.error(`[gitmem:license] Validation error: ${err}`);
419
+ });
420
+ }
403
421
  if (hasSupabase()) {
404
422
  // Pro/Dev: Initialize local vector search in background (non-blocking)
405
423
  // This loads scars with embeddings directly from Supabase REST API
@@ -173,6 +173,28 @@ export declare function aggregateClosingReflections(sessions: SessionRecord[], m
173
173
  wrong_assumptions: ReflectionCategory;
174
174
  do_differently: ReflectionCategory;
175
175
  };
176
+ /**
177
+ * Lightweight summary for session_start Pro insights.
178
+ * Returns a compact object with key 30-day metrics.
179
+ */
180
+ export interface LightweightSummary {
181
+ total_sessions: number;
182
+ scars_surfaced: number;
183
+ scars_applied: number;
184
+ application_rate: number;
185
+ top_blindspot: {
186
+ title: string;
187
+ ignore_rate: string;
188
+ times: string;
189
+ } | null;
190
+ }
191
+ export declare function computeLightweightSummary(sessions: SessionRecord[], usages: ScarUsageRecord[]): LightweightSummary;
192
+ /**
193
+ * Format blindspot snippet for session_close display.
194
+ * Returns 2-3 lines showing top 2 most-ignored scars, or null if none qualify.
195
+ * Threshold: ignore_rate > 50%, surfaced >= 3 times.
196
+ */
197
+ export declare function formatBlindspotSnippet(usages: ScarUsageRecord[]): string | null;
176
198
  /**
177
199
  * Format summary analytics as compact markdown.
178
200
  */