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.
- package/CHANGELOG.md +27 -0
- package/README.md +21 -4
- package/bin/gitmem.js +10 -0
- package/dist/commands/activate.d.ts +20 -0
- package/dist/commands/activate.js +562 -0
- package/dist/commands/deactivate.d.ts +10 -0
- package/dist/commands/deactivate.js +95 -0
- package/dist/commands/migrate-local.d.ts +53 -0
- package/dist/commands/migrate-local.js +177 -0
- package/dist/hooks/format-utils.js +4 -0
- package/dist/schemas/log.d.ts +2 -2
- package/dist/schemas/search.d.ts +2 -2
- package/dist/schemas/session-close.d.ts +12 -12
- package/dist/server.js +33 -2
- package/dist/services/analytics.d.ts +22 -0
- package/dist/services/analytics.js +68 -0
- package/dist/services/doc-chunker.d.ts +45 -0
- package/dist/services/doc-chunker.js +208 -0
- package/dist/services/doc-index.d.ts +88 -0
- package/dist/services/doc-index.js +328 -0
- package/dist/services/license.d.ts +57 -0
- package/dist/services/license.js +200 -0
- package/dist/services/supabase-client.d.ts +6 -0
- package/dist/services/supabase-client.js +75 -22
- package/dist/services/tier.d.ts +13 -3
- package/dist/services/tier.js +38 -7
- package/dist/tools/definitions.d.ts +688 -0
- package/dist/tools/definitions.js +87 -0
- package/dist/tools/index-docs.d.ts +30 -0
- package/dist/tools/index-docs.js +163 -0
- package/dist/tools/prepare-context.js +7 -0
- package/dist/tools/recall.js +25 -4
- package/dist/tools/search-docs.d.ts +38 -0
- package/dist/tools/search-docs.js +94 -0
- package/dist/tools/search.js +11 -1
- package/dist/tools/session-close.js +76 -7
- package/dist/tools/session-start.js +57 -5
- package/package.json +1 -1
- package/schema/setup.sql +489 -25
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
export declare function main(_args: string[]): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=deactivate.d.ts.map
|
|
@@ -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,53 @@
|
|
|
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
|
+
* Rename local collection files after successful migration
|
|
50
|
+
* Adds .pre-migration suffix so data isn't lost but won't be re-read by free tier
|
|
51
|
+
*/
|
|
52
|
+
export declare function archiveLocalData(gitmemDir?: string): string[];
|
|
53
|
+
//# sourceMappingURL=migrate-local.d.ts.map
|
|
@@ -0,0 +1,177 @@
|
|
|
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
|
+
* Check if there is local data worth migrating
|
|
32
|
+
*/
|
|
33
|
+
export function hasLocalData(gitmemDir) {
|
|
34
|
+
const dir = gitmemDir || getGitmemDir();
|
|
35
|
+
for (const collection of MIGRATABLE_COLLECTIONS) {
|
|
36
|
+
const filePath = path.join(dir, `${collection}.json`);
|
|
37
|
+
if (fs.existsSync(filePath)) {
|
|
38
|
+
try {
|
|
39
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
40
|
+
if (Array.isArray(data) && data.length > 0)
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Corrupt file — skip
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Read a local collection JSON file
|
|
52
|
+
*/
|
|
53
|
+
function readLocalCollection(dir, collection) {
|
|
54
|
+
const filePath = path.join(dir, `${collection}.json`);
|
|
55
|
+
if (!fs.existsSync(filePath))
|
|
56
|
+
return [];
|
|
57
|
+
try {
|
|
58
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
59
|
+
return Array.isArray(data) ? data : [];
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Clean a record for Supabase insertion:
|
|
67
|
+
* - Strip local-only fields
|
|
68
|
+
* - Remove null values for non-nullable columns
|
|
69
|
+
* - Ensure id exists
|
|
70
|
+
*/
|
|
71
|
+
function cleanRecord(record) {
|
|
72
|
+
if (!record.id)
|
|
73
|
+
return null;
|
|
74
|
+
const cleaned = {};
|
|
75
|
+
for (const [key, value] of Object.entries(record)) {
|
|
76
|
+
if (STRIP_FIELDS.has(key))
|
|
77
|
+
continue;
|
|
78
|
+
if (NULLABLE_STRIP.has(key) && (value === null || value === undefined))
|
|
79
|
+
continue;
|
|
80
|
+
cleaned[key] = value;
|
|
81
|
+
}
|
|
82
|
+
return cleaned;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Migrate local .gitmem data to Supabase
|
|
86
|
+
*
|
|
87
|
+
* @param supabaseUrl - User's Supabase project URL
|
|
88
|
+
* @param supabaseKey - User's service role key
|
|
89
|
+
* @param tablePrefix - Table prefix (default: "gitmem_")
|
|
90
|
+
* @param gitmemDir - Override .gitmem directory path
|
|
91
|
+
* @param onProgress - Callback for progress reporting
|
|
92
|
+
*/
|
|
93
|
+
export async function migrateLocalToSupabase(opts) {
|
|
94
|
+
const { supabaseUrl, supabaseKey, tablePrefix = "gitmem_", onProgress } = opts;
|
|
95
|
+
const dir = opts.gitmemDir || getGitmemDir();
|
|
96
|
+
const log = onProgress || ((msg) => console.log(msg));
|
|
97
|
+
const result = {
|
|
98
|
+
migrated: {},
|
|
99
|
+
skipped: {},
|
|
100
|
+
errors: {},
|
|
101
|
+
total: 0,
|
|
102
|
+
hasLocalData: false,
|
|
103
|
+
};
|
|
104
|
+
const restUrl = `${supabaseUrl}/rest/v1`;
|
|
105
|
+
for (const collection of MIGRATABLE_COLLECTIONS) {
|
|
106
|
+
const records = readLocalCollection(dir, collection);
|
|
107
|
+
const tableName = `${tablePrefix}${collection}`;
|
|
108
|
+
result.migrated[collection] = 0;
|
|
109
|
+
result.skipped[collection] = 0;
|
|
110
|
+
result.errors[collection] = [];
|
|
111
|
+
if (records.length === 0)
|
|
112
|
+
continue;
|
|
113
|
+
result.hasLocalData = true;
|
|
114
|
+
log(` Migrating ${records.length} ${collection}...`);
|
|
115
|
+
for (const record of records) {
|
|
116
|
+
const cleaned = cleanRecord(record);
|
|
117
|
+
if (!cleaned) {
|
|
118
|
+
result.skipped[collection]++;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const response = await fetch(`${restUrl}/${tableName}`, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: {
|
|
125
|
+
"apikey": supabaseKey,
|
|
126
|
+
"Authorization": `Bearer ${supabaseKey}`,
|
|
127
|
+
"Content-Type": "application/json",
|
|
128
|
+
"Prefer": "return=minimal,resolution=merge-duplicates",
|
|
129
|
+
"Content-Profile": "public",
|
|
130
|
+
},
|
|
131
|
+
body: JSON.stringify(cleaned),
|
|
132
|
+
signal: AbortSignal.timeout(10_000),
|
|
133
|
+
});
|
|
134
|
+
if (response.ok) {
|
|
135
|
+
result.migrated[collection]++;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const text = await response.text();
|
|
139
|
+
// Only log first 3 errors per collection to avoid spam
|
|
140
|
+
if (result.errors[collection].length < 3) {
|
|
141
|
+
result.errors[collection].push(`${String(cleaned.id).substring(0, 8)}: ${response.status} - ${text.substring(0, 100)}`);
|
|
142
|
+
}
|
|
143
|
+
result.skipped[collection]++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
if (result.errors[collection].length < 3) {
|
|
148
|
+
result.errors[collection].push(`${String(cleaned.id).substring(0, 8)}: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
149
|
+
}
|
|
150
|
+
result.skipped[collection]++;
|
|
151
|
+
}
|
|
152
|
+
result.total++;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Rename local collection files after successful migration
|
|
159
|
+
* Adds .pre-migration suffix so data isn't lost but won't be re-read by free tier
|
|
160
|
+
*/
|
|
161
|
+
export function archiveLocalData(gitmemDir) {
|
|
162
|
+
const dir = gitmemDir || getGitmemDir();
|
|
163
|
+
const archived = [];
|
|
164
|
+
for (const collection of MIGRATABLE_COLLECTIONS) {
|
|
165
|
+
const filePath = path.join(dir, `${collection}.json`);
|
|
166
|
+
if (fs.existsSync(filePath)) {
|
|
167
|
+
const archivePath = `${filePath}.pre-migration`;
|
|
168
|
+
// Don't overwrite existing archive
|
|
169
|
+
if (!fs.existsSync(archivePath)) {
|
|
170
|
+
fs.renameSync(filePath, archivePath);
|
|
171
|
+
archived.push(collection);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return archived;
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=migrate-local.js.map
|
|
@@ -57,6 +57,10 @@ export function formatCompact(scars, plan, maxTokens) {
|
|
|
57
57
|
lines.push(line);
|
|
58
58
|
included++;
|
|
59
59
|
}
|
|
60
|
+
// Citation reminder for sub-agent context (compact — one line)
|
|
61
|
+
if (included > 0) {
|
|
62
|
+
lines.push("Cite record IDs for any factual claims from these scars.");
|
|
63
|
+
}
|
|
60
64
|
return { payload: lines.join("\n"), included };
|
|
61
65
|
}
|
|
62
66
|
/**
|
package/dist/schemas/log.d.ts
CHANGED
|
@@ -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
|
}>;
|
package/dist/schemas/search.d.ts
CHANGED
|
@@ -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
|
@@ -36,12 +36,16 @@ import { dismissSuggestion } from "./tools/dismiss-suggestion.js";
|
|
|
36
36
|
import { cleanupThreads } from "./tools/cleanup-threads.js";
|
|
37
37
|
import { archiveLearning } from "./tools/archive-learning.js";
|
|
38
38
|
import { contributeFeedback } from "./tools/contribute-feedback.js";
|
|
39
|
+
import { indexDocs } from "./tools/index-docs.js";
|
|
40
|
+
import { searchDocsHandler } from "./tools/search-docs.js";
|
|
39
41
|
import { getCacheStatus, checkCacheHealth, flushCache, startBackgroundInit, } from "./services/startup.js";
|
|
40
42
|
import { getEffectTracker } from "./services/effect-tracker.js";
|
|
41
43
|
import { RIPPLE, ANSI } from "./services/display-protocol.js";
|
|
42
44
|
import { getProject } from "./services/session-state.js";
|
|
43
45
|
import { checkEnforcement } from "./services/enforcement.js";
|
|
44
|
-
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";
|
|
45
49
|
import { getRegisteredTools } from "./tools/definitions.js";
|
|
46
50
|
import { validateToolArgs } from "./schemas/registry.js";
|
|
47
51
|
/**
|
|
@@ -246,6 +250,8 @@ export function createServer() {
|
|
|
246
250
|
{ alias: "gitmem-al", full: "archive_learning", description: "Archive a scar/win/pattern (is_active=false)" },
|
|
247
251
|
{ alias: "gitmem-graph", full: "graph_traverse", description: "Traverse knowledge graph over institutional memory" },
|
|
248
252
|
{ alias: "gitmem-fb", full: "contribute_feedback", description: "Submit feedback about gitmem (10/session limit)" },
|
|
253
|
+
{ alias: "gitmem-idx", full: "index_docs", description: "Index markdown docs for semantic search" },
|
|
254
|
+
{ alias: "gitmem-sd", full: "search_docs", description: "Search indexed repository docs" },
|
|
249
255
|
];
|
|
250
256
|
if (hasBatchOperations()) {
|
|
251
257
|
commands.push({ alias: "gitmem-rsb", full: "record_scar_usage_batch", description: "Track multiple scars (batch)" });
|
|
@@ -315,6 +321,15 @@ export function createServer() {
|
|
|
315
321
|
case "gm-cache-f":
|
|
316
322
|
result = await flushCache(toolArgs.project || getProject() || "default");
|
|
317
323
|
break;
|
|
324
|
+
// Doc indexing and search
|
|
325
|
+
case "index_docs":
|
|
326
|
+
case "gitmem-idx":
|
|
327
|
+
result = await indexDocs(toolArgs);
|
|
328
|
+
break;
|
|
329
|
+
case "search_docs":
|
|
330
|
+
case "gitmem-sd":
|
|
331
|
+
result = await searchDocsHandler(toolArgs);
|
|
332
|
+
break;
|
|
318
333
|
default:
|
|
319
334
|
throw new Error(`Unknown tool: ${name}`);
|
|
320
335
|
}
|
|
@@ -380,13 +395,29 @@ export function createServer() {
|
|
|
380
395
|
*/
|
|
381
396
|
export async function runServer() {
|
|
382
397
|
const tier = getTier();
|
|
383
|
-
// Start server immediately (don't block on cache loading)
|
|
398
|
+
// Start server immediately (don't block on cache loading or license validation)
|
|
384
399
|
const server = createServer();
|
|
385
400
|
const transport = new StdioServerTransport();
|
|
386
401
|
await server.connect(transport);
|
|
387
402
|
const toolCount = getRegisteredTools().length;
|
|
388
403
|
const storage = hasSupabase() ? "supabase" : "local";
|
|
389
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
|
+
}
|
|
390
421
|
if (hasSupabase()) {
|
|
391
422
|
// Pro/Dev: Initialize local vector search in background (non-blocking)
|
|
392
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
|
*/
|
|
@@ -356,6 +356,74 @@ export function aggregateClosingReflections(sessions, maxPerCategory = 30, maxTe
|
|
|
356
356
|
do_differently: truncate(all.do_differently),
|
|
357
357
|
};
|
|
358
358
|
}
|
|
359
|
+
export function computeLightweightSummary(sessions, usages) {
|
|
360
|
+
const totalSessions = sessions.length;
|
|
361
|
+
const scarsSurfaced = new Set(usages.map(u => u.scar_id)).size;
|
|
362
|
+
const applied = usages.filter(u => u.reference_type !== "none").length;
|
|
363
|
+
const applicationRate = usages.length > 0 ? applied / usages.length : 0;
|
|
364
|
+
// Find top blindspot: most-ignored scar with >= 3 surfacings
|
|
365
|
+
const scarGroups = new Map();
|
|
366
|
+
for (const u of usages) {
|
|
367
|
+
const entry = scarGroups.get(u.scar_id) || { title: u.scar_title || "Unknown", surfaced: 0, dismissed: 0 };
|
|
368
|
+
entry.surfaced++;
|
|
369
|
+
if (u.reference_type === "none")
|
|
370
|
+
entry.dismissed++;
|
|
371
|
+
if (u.scar_title)
|
|
372
|
+
entry.title = u.scar_title;
|
|
373
|
+
scarGroups.set(u.scar_id, entry);
|
|
374
|
+
}
|
|
375
|
+
let topBlindspot = null;
|
|
376
|
+
let highestIgnoreRate = 0;
|
|
377
|
+
for (const [, stats] of scarGroups) {
|
|
378
|
+
if (stats.surfaced >= 3) {
|
|
379
|
+
const rate = stats.dismissed / stats.surfaced;
|
|
380
|
+
if (rate > highestIgnoreRate && rate > 0.5) {
|
|
381
|
+
highestIgnoreRate = rate;
|
|
382
|
+
topBlindspot = {
|
|
383
|
+
title: stats.title,
|
|
384
|
+
ignore_rate: `${Math.round(rate * 100)}%`,
|
|
385
|
+
times: `${stats.dismissed}/${stats.surfaced}`,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
total_sessions: totalSessions,
|
|
392
|
+
scars_surfaced: scarsSurfaced,
|
|
393
|
+
scars_applied: applied,
|
|
394
|
+
application_rate: applicationRate,
|
|
395
|
+
top_blindspot: topBlindspot,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Format blindspot snippet for session_close display.
|
|
400
|
+
* Returns 2-3 lines showing top 2 most-ignored scars, or null if none qualify.
|
|
401
|
+
* Threshold: ignore_rate > 50%, surfaced >= 3 times.
|
|
402
|
+
*/
|
|
403
|
+
export function formatBlindspotSnippet(usages) {
|
|
404
|
+
const scarGroups = new Map();
|
|
405
|
+
for (const u of usages) {
|
|
406
|
+
const entry = scarGroups.get(u.scar_id) || { title: u.scar_title || "Unknown", surfaced: 0, dismissed: 0 };
|
|
407
|
+
entry.surfaced++;
|
|
408
|
+
if (u.reference_type === "none")
|
|
409
|
+
entry.dismissed++;
|
|
410
|
+
if (u.scar_title)
|
|
411
|
+
entry.title = u.scar_title;
|
|
412
|
+
scarGroups.set(u.scar_id, entry);
|
|
413
|
+
}
|
|
414
|
+
const blindspots = Array.from(scarGroups.values())
|
|
415
|
+
.filter(s => s.surfaced >= 3 && (s.dismissed / s.surfaced) > 0.5)
|
|
416
|
+
.sort((a, b) => (b.dismissed / b.surfaced) - (a.dismissed / a.surfaced))
|
|
417
|
+
.slice(0, 2);
|
|
418
|
+
if (blindspots.length === 0)
|
|
419
|
+
return null;
|
|
420
|
+
const lines = ["Blindspots (30d)"];
|
|
421
|
+
for (const b of blindspots) {
|
|
422
|
+
const pct = Math.round((b.dismissed / b.surfaced) * 100);
|
|
423
|
+
lines.push(` "${b.title}" — ignored ${pct}% (${b.dismissed}/${b.surfaced} times)`);
|
|
424
|
+
}
|
|
425
|
+
return lines.join("\n");
|
|
426
|
+
}
|
|
359
427
|
// --- Formatters ---
|
|
360
428
|
/**
|
|
361
429
|
* Format summary analytics as compact markdown.
|