gitmem-mcp 0.2.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 +47 -0
- package/CLAUDE.md.template +65 -0
- package/LICENSE +21 -0
- package/README.md +221 -0
- package/bin/gitmem.js +383 -0
- package/dist/commands/check.d.ts +33 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +492 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/constants/closing-questions.d.ts +40 -0
- package/dist/constants/closing-questions.d.ts.map +1 -0
- package/dist/constants/closing-questions.js +107 -0
- package/dist/constants/closing-questions.js.map +1 -0
- package/dist/diagnostics/anonymizer.d.ts +55 -0
- package/dist/diagnostics/anonymizer.d.ts.map +1 -0
- package/dist/diagnostics/anonymizer.js +191 -0
- package/dist/diagnostics/anonymizer.js.map +1 -0
- package/dist/diagnostics/channels.d.ts +132 -0
- package/dist/diagnostics/channels.d.ts.map +1 -0
- package/dist/diagnostics/channels.js +150 -0
- package/dist/diagnostics/channels.js.map +1 -0
- package/dist/diagnostics/collector.d.ts +183 -0
- package/dist/diagnostics/collector.d.ts.map +1 -0
- package/dist/diagnostics/collector.js +227 -0
- package/dist/diagnostics/collector.js.map +1 -0
- package/dist/diagnostics/index.d.ts +28 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +31 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/absorb-observations.d.ts +63 -0
- package/dist/schemas/absorb-observations.d.ts.map +1 -0
- package/dist/schemas/absorb-observations.js +25 -0
- package/dist/schemas/absorb-observations.js.map +1 -0
- package/dist/schemas/active-sessions.d.ts +71 -0
- package/dist/schemas/active-sessions.d.ts.map +1 -0
- package/dist/schemas/active-sessions.js +19 -0
- package/dist/schemas/active-sessions.js.map +1 -0
- package/dist/schemas/analyze.d.ts +38 -0
- package/dist/schemas/analyze.d.ts.map +1 -0
- package/dist/schemas/analyze.js +30 -0
- package/dist/schemas/analyze.js.map +1 -0
- package/dist/schemas/common.d.ts +55 -0
- package/dist/schemas/common.d.ts.map +1 -0
- package/dist/schemas/common.js +65 -0
- package/dist/schemas/common.js.map +1 -0
- package/dist/schemas/create-decision.d.ts +48 -0
- package/dist/schemas/create-decision.d.ts.map +1 -0
- package/dist/schemas/create-decision.js +31 -0
- package/dist/schemas/create-decision.js.map +1 -0
- package/dist/schemas/create-learning.d.ts +107 -0
- package/dist/schemas/create-learning.d.ts.map +1 -0
- package/dist/schemas/create-learning.js +64 -0
- package/dist/schemas/create-learning.js.map +1 -0
- package/dist/schemas/get-transcript.d.ts +24 -0
- package/dist/schemas/get-transcript.d.ts.map +1 -0
- package/dist/schemas/get-transcript.js +22 -0
- package/dist/schemas/get-transcript.js.map +1 -0
- package/dist/schemas/index.d.ts +23 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +23 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/log.d.ts +36 -0
- package/dist/schemas/log.d.ts.map +1 -0
- package/dist/schemas/log.js +27 -0
- package/dist/schemas/log.js.map +1 -0
- package/dist/schemas/prepare-context.d.ts +41 -0
- package/dist/schemas/prepare-context.d.ts.map +1 -0
- package/dist/schemas/prepare-context.js +31 -0
- package/dist/schemas/prepare-context.js.map +1 -0
- package/dist/schemas/recall.d.ts +41 -0
- package/dist/schemas/recall.d.ts.map +1 -0
- package/dist/schemas/recall.js +47 -0
- package/dist/schemas/recall.js.map +1 -0
- package/dist/schemas/record-scar-usage-batch.d.ts +82 -0
- package/dist/schemas/record-scar-usage-batch.d.ts.map +1 -0
- package/dist/schemas/record-scar-usage-batch.js +25 -0
- package/dist/schemas/record-scar-usage-batch.js.map +1 -0
- package/dist/schemas/record-scar-usage.d.ts +51 -0
- package/dist/schemas/record-scar-usage.d.ts.map +1 -0
- package/dist/schemas/record-scar-usage.js +32 -0
- package/dist/schemas/record-scar-usage.js.map +1 -0
- package/dist/schemas/save-transcript.d.ts +38 -0
- package/dist/schemas/save-transcript.d.ts.map +1 -0
- package/dist/schemas/save-transcript.js +30 -0
- package/dist/schemas/save-transcript.js.map +1 -0
- package/dist/schemas/search.d.ts +36 -0
- package/dist/schemas/search.d.ts.map +1 -0
- package/dist/schemas/search.js +27 -0
- package/dist/schemas/search.js.map +1 -0
- package/dist/schemas/session-close.d.ts +371 -0
- package/dist/schemas/session-close.d.ts.map +1 -0
- package/dist/schemas/session-close.js +95 -0
- package/dist/schemas/session-close.js.map +1 -0
- package/dist/schemas/session-start.d.ts +46 -0
- package/dist/schemas/session-start.d.ts.map +1 -0
- package/dist/schemas/session-start.js +33 -0
- package/dist/schemas/session-start.js.map +1 -0
- package/dist/schemas/thread.d.ts +72 -0
- package/dist/schemas/thread.d.ts.map +1 -0
- package/dist/schemas/thread.js +39 -0
- package/dist/schemas/thread.js.map +1 -0
- package/dist/server.d.ts +22 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +313 -0
- package/dist/server.js.map +1 -0
- package/dist/services/active-sessions.d.ts +66 -0
- package/dist/services/active-sessions.d.ts.map +1 -0
- package/dist/services/active-sessions.js +311 -0
- package/dist/services/active-sessions.js.map +1 -0
- package/dist/services/agent-detection.d.ts +25 -0
- package/dist/services/agent-detection.d.ts.map +1 -0
- package/dist/services/agent-detection.js +93 -0
- package/dist/services/agent-detection.js.map +1 -0
- package/dist/services/analytics.d.ts +201 -0
- package/dist/services/analytics.d.ts.map +1 -0
- package/dist/services/analytics.js +483 -0
- package/dist/services/analytics.js.map +1 -0
- package/dist/services/cache.d.ts +148 -0
- package/dist/services/cache.d.ts.map +1 -0
- package/dist/services/cache.js +384 -0
- package/dist/services/cache.js.map +1 -0
- package/dist/services/cache.test.d.ts +8 -0
- package/dist/services/cache.test.d.ts.map +1 -0
- package/dist/services/cache.test.js +267 -0
- package/dist/services/cache.test.js.map +1 -0
- package/dist/services/compliance-validator.d.ts +30 -0
- package/dist/services/compliance-validator.d.ts.map +1 -0
- package/dist/services/compliance-validator.js +257 -0
- package/dist/services/compliance-validator.js.map +1 -0
- package/dist/services/config.d.ts +48 -0
- package/dist/services/config.d.ts.map +1 -0
- package/dist/services/config.js +128 -0
- package/dist/services/config.js.map +1 -0
- package/dist/services/embedding.d.ts +58 -0
- package/dist/services/embedding.d.ts.map +1 -0
- package/dist/services/embedding.js +243 -0
- package/dist/services/embedding.js.map +1 -0
- package/dist/services/gitmem-dir.d.ts +38 -0
- package/dist/services/gitmem-dir.d.ts.map +1 -0
- package/dist/services/gitmem-dir.js +84 -0
- package/dist/services/gitmem-dir.js.map +1 -0
- package/dist/services/local-file-storage.d.ts +56 -0
- package/dist/services/local-file-storage.d.ts.map +1 -0
- package/dist/services/local-file-storage.js +213 -0
- package/dist/services/local-file-storage.js.map +1 -0
- package/dist/services/local-vector-search.d.ts +137 -0
- package/dist/services/local-vector-search.d.ts.map +1 -0
- package/dist/services/local-vector-search.js +311 -0
- package/dist/services/local-vector-search.js.map +1 -0
- package/dist/services/metrics.d.ts +104 -0
- package/dist/services/metrics.d.ts.map +1 -0
- package/dist/services/metrics.js +264 -0
- package/dist/services/metrics.js.map +1 -0
- package/dist/services/session-state.d.ts +113 -0
- package/dist/services/session-state.d.ts.map +1 -0
- package/dist/services/session-state.js +203 -0
- package/dist/services/session-state.js.map +1 -0
- package/dist/services/startup.d.ts +112 -0
- package/dist/services/startup.d.ts.map +1 -0
- package/dist/services/startup.js +436 -0
- package/dist/services/startup.js.map +1 -0
- package/dist/services/storage.d.ts +43 -0
- package/dist/services/storage.d.ts.map +1 -0
- package/dist/services/storage.js +92 -0
- package/dist/services/storage.js.map +1 -0
- package/dist/services/supabase-client.d.ts +163 -0
- package/dist/services/supabase-client.d.ts.map +1 -0
- package/dist/services/supabase-client.js +510 -0
- package/dist/services/supabase-client.js.map +1 -0
- package/dist/services/thread-dedup.d.ts +44 -0
- package/dist/services/thread-dedup.d.ts.map +1 -0
- package/dist/services/thread-dedup.js +113 -0
- package/dist/services/thread-dedup.js.map +1 -0
- package/dist/services/thread-manager.d.ts +77 -0
- package/dist/services/thread-manager.d.ts.map +1 -0
- package/dist/services/thread-manager.js +250 -0
- package/dist/services/thread-manager.js.map +1 -0
- package/dist/services/thread-suggestions.d.ts +66 -0
- package/dist/services/thread-suggestions.d.ts.map +1 -0
- package/dist/services/thread-suggestions.js +243 -0
- package/dist/services/thread-suggestions.js.map +1 -0
- package/dist/services/thread-supabase.d.ts +111 -0
- package/dist/services/thread-supabase.d.ts.map +1 -0
- package/dist/services/thread-supabase.js +459 -0
- package/dist/services/thread-supabase.js.map +1 -0
- package/dist/services/thread-vitality.d.ts +65 -0
- package/dist/services/thread-vitality.d.ts.map +1 -0
- package/dist/services/thread-vitality.js +143 -0
- package/dist/services/thread-vitality.js.map +1 -0
- package/dist/services/tier.d.ts +52 -0
- package/dist/services/tier.d.ts.map +1 -0
- package/dist/services/tier.js +109 -0
- package/dist/services/tier.js.map +1 -0
- package/dist/services/timezone.d.ts +37 -0
- package/dist/services/timezone.d.ts.map +1 -0
- package/dist/services/timezone.js +147 -0
- package/dist/services/timezone.js.map +1 -0
- package/dist/services/transcript-chunker.d.ts +18 -0
- package/dist/services/transcript-chunker.d.ts.map +1 -0
- package/dist/services/transcript-chunker.js +237 -0
- package/dist/services/transcript-chunker.js.map +1 -0
- package/dist/services/triple-writer.d.ts +128 -0
- package/dist/services/triple-writer.d.ts.map +1 -0
- package/dist/services/triple-writer.js +338 -0
- package/dist/services/triple-writer.js.map +1 -0
- package/dist/services/variant-assignment.d.ts +92 -0
- package/dist/services/variant-assignment.d.ts.map +1 -0
- package/dist/services/variant-assignment.js +196 -0
- package/dist/services/variant-assignment.js.map +1 -0
- package/dist/tools/absorb-observations.d.ts +16 -0
- package/dist/tools/absorb-observations.d.ts.map +1 -0
- package/dist/tools/absorb-observations.js +82 -0
- package/dist/tools/absorb-observations.js.map +1 -0
- package/dist/tools/analyze.d.ts +55 -0
- package/dist/tools/analyze.d.ts.map +1 -0
- package/dist/tools/analyze.js +139 -0
- package/dist/tools/analyze.js.map +1 -0
- package/dist/tools/cleanup-threads.d.ts +47 -0
- package/dist/tools/cleanup-threads.d.ts.map +1 -0
- package/dist/tools/cleanup-threads.js +127 -0
- package/dist/tools/cleanup-threads.js.map +1 -0
- package/dist/tools/confirm-scars.d.ts +23 -0
- package/dist/tools/confirm-scars.d.ts.map +1 -0
- package/dist/tools/confirm-scars.js +209 -0
- package/dist/tools/confirm-scars.js.map +1 -0
- package/dist/tools/create-decision.d.ts +15 -0
- package/dist/tools/create-decision.d.ts.map +1 -0
- package/dist/tools/create-decision.js +138 -0
- package/dist/tools/create-decision.js.map +1 -0
- package/dist/tools/create-learning.d.ts +15 -0
- package/dist/tools/create-learning.d.ts.map +1 -0
- package/dist/tools/create-learning.js +226 -0
- package/dist/tools/create-learning.js.map +1 -0
- package/dist/tools/create-thread.d.ts +42 -0
- package/dist/tools/create-thread.d.ts.map +1 -0
- package/dist/tools/create-thread.js +180 -0
- package/dist/tools/create-thread.js.map +1 -0
- package/dist/tools/definitions.d.ts +5013 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +2017 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/dismiss-suggestion.d.ts +20 -0
- package/dist/tools/dismiss-suggestion.d.ts.map +1 -0
- package/dist/tools/dismiss-suggestion.js +40 -0
- package/dist/tools/dismiss-suggestion.js.map +1 -0
- package/dist/tools/get-transcript.d.ts +24 -0
- package/dist/tools/get-transcript.d.ts.map +1 -0
- package/dist/tools/get-transcript.js +52 -0
- package/dist/tools/get-transcript.js.map +1 -0
- package/dist/tools/graph-traverse.d.ts +83 -0
- package/dist/tools/graph-traverse.d.ts.map +1 -0
- package/dist/tools/graph-traverse.js +394 -0
- package/dist/tools/graph-traverse.js.map +1 -0
- package/dist/tools/list-threads.d.ts +15 -0
- package/dist/tools/list-threads.d.ts.map +1 -0
- package/dist/tools/list-threads.js +114 -0
- package/dist/tools/list-threads.js.map +1 -0
- package/dist/tools/log.d.ts +43 -0
- package/dist/tools/log.d.ts.map +1 -0
- package/dist/tools/log.js +157 -0
- package/dist/tools/log.js.map +1 -0
- package/dist/tools/prepare-context.d.ts +36 -0
- package/dist/tools/prepare-context.d.ts.map +1 -0
- package/dist/tools/prepare-context.js +353 -0
- package/dist/tools/prepare-context.js.map +1 -0
- package/dist/tools/promote-suggestion.d.ts +25 -0
- package/dist/tools/promote-suggestion.d.ts.map +1 -0
- package/dist/tools/promote-suggestion.js +60 -0
- package/dist/tools/promote-suggestion.js.map +1 -0
- package/dist/tools/recall.d.ts +77 -0
- package/dist/tools/recall.d.ts.map +1 -0
- package/dist/tools/recall.js +423 -0
- package/dist/tools/recall.js.map +1 -0
- package/dist/tools/recall.test.d.ts +5 -0
- package/dist/tools/recall.test.d.ts.map +1 -0
- package/dist/tools/recall.test.js +155 -0
- package/dist/tools/recall.test.js.map +1 -0
- package/dist/tools/record-scar-usage-batch.d.ts +10 -0
- package/dist/tools/record-scar-usage-batch.d.ts.map +1 -0
- package/dist/tools/record-scar-usage-batch.js +153 -0
- package/dist/tools/record-scar-usage-batch.js.map +1 -0
- package/dist/tools/record-scar-usage.d.ts +14 -0
- package/dist/tools/record-scar-usage.d.ts.map +1 -0
- package/dist/tools/record-scar-usage.js +94 -0
- package/dist/tools/record-scar-usage.js.map +1 -0
- package/dist/tools/resolve-thread.d.ts +16 -0
- package/dist/tools/resolve-thread.d.ts.map +1 -0
- package/dist/tools/resolve-thread.js +102 -0
- package/dist/tools/resolve-thread.js.map +1 -0
- package/dist/tools/save-transcript.d.ts +29 -0
- package/dist/tools/save-transcript.d.ts.map +1 -0
- package/dist/tools/save-transcript.js +97 -0
- package/dist/tools/save-transcript.js.map +1 -0
- package/dist/tools/search.d.ts +46 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +186 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/session-close.d.ts +14 -0
- package/dist/tools/session-close.d.ts.map +1 -0
- package/dist/tools/session-close.js +881 -0
- package/dist/tools/session-close.js.map +1 -0
- package/dist/tools/session-start.d.ts +38 -0
- package/dist/tools/session-start.d.ts.map +1 -0
- package/dist/tools/session-start.js +1104 -0
- package/dist/tools/session-start.js.map +1 -0
- package/dist/types/index.d.ts +456 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +76 -0
- package/schema/setup.sql +193 -0
- package/schema/starter-scars.json +206 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedding Service — Multi-provider embedding abstraction
|
|
3
|
+
*
|
|
4
|
+
* Generates text embeddings for semantic search using multiple providers.
|
|
5
|
+
* Auto-detects provider from available API keys.
|
|
6
|
+
*
|
|
7
|
+
* Provider detection (in priority order):
|
|
8
|
+
* GITMEM_EMBEDDING_PROVIDER=openai|openrouter|ollama — force provider
|
|
9
|
+
* OPENAI_API_KEY → OpenAI direct (text-embedding-3-small)
|
|
10
|
+
* OPENROUTER_API_KEY → OpenRouter (text-embedding-3-small via openrouter.ai)
|
|
11
|
+
* OLLAMA_URL → Ollama local (nomic-embed-text, 768-dim — requires pgvector dimension match)
|
|
12
|
+
*
|
|
13
|
+
* If no provider is configured, embed() returns null (graceful degradation).
|
|
14
|
+
* Records stored without embeddings can still be retrieved by ID/filters,
|
|
15
|
+
* but won't appear in semantic search results.
|
|
16
|
+
*/
|
|
17
|
+
// Default embedding dimensions per provider
|
|
18
|
+
const OPENAI_EMBEDDING_DIM = 1536;
|
|
19
|
+
const OLLAMA_DEFAULT_DIM = 768;
|
|
20
|
+
// Model configuration
|
|
21
|
+
const OPENAI_EMBEDDING_MODEL = "text-embedding-3-small";
|
|
22
|
+
const OPENROUTER_EMBEDDING_MODEL = "openai/text-embedding-3-small";
|
|
23
|
+
const OLLAMA_EMBEDDING_MODEL = "nomic-embed-text";
|
|
24
|
+
// API URLs
|
|
25
|
+
const OPENAI_API_URL = "https://api.openai.com/v1/embeddings";
|
|
26
|
+
const OPENROUTER_API_URL = "https://openrouter.ai/api/v1/embeddings";
|
|
27
|
+
/**
|
|
28
|
+
* Normalize a vector to unit length
|
|
29
|
+
*/
|
|
30
|
+
function normalize(vec) {
|
|
31
|
+
let magnitude = 0;
|
|
32
|
+
for (const v of vec) {
|
|
33
|
+
magnitude += v * v;
|
|
34
|
+
}
|
|
35
|
+
magnitude = Math.sqrt(magnitude);
|
|
36
|
+
if (magnitude === 0)
|
|
37
|
+
return vec;
|
|
38
|
+
return vec.map((v) => v / magnitude);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Detect the best available embedding provider from environment
|
|
42
|
+
*/
|
|
43
|
+
export function detectProvider() {
|
|
44
|
+
const forced = process.env.GITMEM_EMBEDDING_PROVIDER?.toLowerCase();
|
|
45
|
+
// Forced provider
|
|
46
|
+
if (forced && forced !== "auto") {
|
|
47
|
+
switch (forced) {
|
|
48
|
+
case "openai": {
|
|
49
|
+
const key = process.env.OPENAI_API_KEY;
|
|
50
|
+
if (!key) {
|
|
51
|
+
console.warn("[embedding] GITMEM_EMBEDDING_PROVIDER=openai but OPENAI_API_KEY not set");
|
|
52
|
+
return { provider: "none", apiUrl: "", apiKey: "", model: "", expectedDim: 0 };
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
provider: "openai",
|
|
56
|
+
apiUrl: OPENAI_API_URL,
|
|
57
|
+
apiKey: key,
|
|
58
|
+
model: OPENAI_EMBEDDING_MODEL,
|
|
59
|
+
expectedDim: OPENAI_EMBEDDING_DIM,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
case "openrouter": {
|
|
63
|
+
const key = process.env.OPENROUTER_API_KEY;
|
|
64
|
+
if (!key) {
|
|
65
|
+
console.warn("[embedding] GITMEM_EMBEDDING_PROVIDER=openrouter but OPENROUTER_API_KEY not set");
|
|
66
|
+
return { provider: "none", apiUrl: "", apiKey: "", model: "", expectedDim: 0 };
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
provider: "openrouter",
|
|
70
|
+
apiUrl: OPENROUTER_API_URL,
|
|
71
|
+
apiKey: key,
|
|
72
|
+
model: OPENROUTER_EMBEDDING_MODEL,
|
|
73
|
+
expectedDim: OPENAI_EMBEDDING_DIM,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
case "ollama": {
|
|
77
|
+
const url = process.env.OLLAMA_URL || "http://localhost:11434";
|
|
78
|
+
return {
|
|
79
|
+
provider: "ollama",
|
|
80
|
+
apiUrl: `${url}/api/embed`,
|
|
81
|
+
apiKey: "",
|
|
82
|
+
model: process.env.GITMEM_OLLAMA_MODEL || OLLAMA_EMBEDDING_MODEL,
|
|
83
|
+
expectedDim: parseInt(process.env.GITMEM_EMBEDDING_DIM || String(OLLAMA_DEFAULT_DIM), 10),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
default:
|
|
87
|
+
console.warn(`[embedding] Unknown provider: ${forced}`);
|
|
88
|
+
return { provider: "none", apiUrl: "", apiKey: "", model: "", expectedDim: 0 };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Auto-detect from available keys (priority: OpenAI > OpenRouter > Ollama)
|
|
92
|
+
if (process.env.OPENAI_API_KEY) {
|
|
93
|
+
return {
|
|
94
|
+
provider: "openai",
|
|
95
|
+
apiUrl: OPENAI_API_URL,
|
|
96
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
97
|
+
model: OPENAI_EMBEDDING_MODEL,
|
|
98
|
+
expectedDim: OPENAI_EMBEDDING_DIM,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (process.env.OPENROUTER_API_KEY) {
|
|
102
|
+
return {
|
|
103
|
+
provider: "openrouter",
|
|
104
|
+
apiUrl: OPENROUTER_API_URL,
|
|
105
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
106
|
+
model: OPENROUTER_EMBEDDING_MODEL,
|
|
107
|
+
expectedDim: OPENAI_EMBEDDING_DIM,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (process.env.OLLAMA_URL) {
|
|
111
|
+
const url = process.env.OLLAMA_URL;
|
|
112
|
+
return {
|
|
113
|
+
provider: "ollama",
|
|
114
|
+
apiUrl: `${url}/api/embed`,
|
|
115
|
+
apiKey: "",
|
|
116
|
+
model: process.env.GITMEM_OLLAMA_MODEL || OLLAMA_EMBEDDING_MODEL,
|
|
117
|
+
expectedDim: parseInt(process.env.GITMEM_EMBEDDING_DIM || String(OLLAMA_DEFAULT_DIM), 10),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return { provider: "none", apiUrl: "", apiKey: "", model: "", expectedDim: 0 };
|
|
121
|
+
}
|
|
122
|
+
// Cached config (loaded once per process)
|
|
123
|
+
let _config = null;
|
|
124
|
+
/**
|
|
125
|
+
* Get the current embedding configuration (cached)
|
|
126
|
+
*/
|
|
127
|
+
export function getEmbeddingConfig() {
|
|
128
|
+
if (!_config) {
|
|
129
|
+
_config = detectProvider();
|
|
130
|
+
if (_config.provider !== "none") {
|
|
131
|
+
console.error(`[embedding] Provider: ${_config.provider} (model: ${_config.model}, dim: ${_config.expectedDim})`);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.error("[embedding] No embedding provider configured — scars will be stored without embeddings");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return _config;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Reset cached config (for testing)
|
|
141
|
+
*/
|
|
142
|
+
export function resetEmbeddingConfig() {
|
|
143
|
+
_config = null;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Generate an embedding for text using OpenAI-compatible API (OpenAI or OpenRouter)
|
|
147
|
+
*/
|
|
148
|
+
async function embedOpenAI(text, config) {
|
|
149
|
+
const response = await fetch(config.apiUrl, {
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers: {
|
|
152
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
153
|
+
"Content-Type": "application/json",
|
|
154
|
+
},
|
|
155
|
+
body: JSON.stringify({
|
|
156
|
+
model: config.model,
|
|
157
|
+
input: text,
|
|
158
|
+
}),
|
|
159
|
+
});
|
|
160
|
+
if (!response.ok) {
|
|
161
|
+
const errorText = await response.text();
|
|
162
|
+
throw new Error(`${config.provider} embedding error (${response.status}): ${errorText}`);
|
|
163
|
+
}
|
|
164
|
+
const data = (await response.json());
|
|
165
|
+
if (!data.data || data.data.length === 0) {
|
|
166
|
+
throw new Error(`No embedding data in ${config.provider} response`);
|
|
167
|
+
}
|
|
168
|
+
return data.data[0].embedding;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Generate an embedding using Ollama local API
|
|
172
|
+
*/
|
|
173
|
+
async function embedOllama(text, config) {
|
|
174
|
+
const response = await fetch(config.apiUrl, {
|
|
175
|
+
method: "POST",
|
|
176
|
+
headers: {
|
|
177
|
+
"Content-Type": "application/json",
|
|
178
|
+
},
|
|
179
|
+
body: JSON.stringify({
|
|
180
|
+
model: config.model,
|
|
181
|
+
input: text,
|
|
182
|
+
}),
|
|
183
|
+
});
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
const errorText = await response.text();
|
|
186
|
+
throw new Error(`Ollama embedding error (${response.status}): ${errorText}`);
|
|
187
|
+
}
|
|
188
|
+
const data = (await response.json());
|
|
189
|
+
if (!data.embeddings || data.embeddings.length === 0) {
|
|
190
|
+
throw new Error("No embedding data in Ollama response");
|
|
191
|
+
}
|
|
192
|
+
return data.embeddings[0];
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Generate an embedding for the given text.
|
|
196
|
+
*
|
|
197
|
+
* Returns normalized embedding vector, or null if no provider is configured.
|
|
198
|
+
* Throws on API errors (network, auth, rate limit).
|
|
199
|
+
*/
|
|
200
|
+
export async function embed(text) {
|
|
201
|
+
const config = getEmbeddingConfig();
|
|
202
|
+
if (config.provider === "none") {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
let raw;
|
|
206
|
+
switch (config.provider) {
|
|
207
|
+
case "openai":
|
|
208
|
+
case "openrouter":
|
|
209
|
+
raw = await embedOpenAI(text, config);
|
|
210
|
+
break;
|
|
211
|
+
case "ollama":
|
|
212
|
+
raw = await embedOllama(text, config);
|
|
213
|
+
break;
|
|
214
|
+
default:
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
// Validate dimensions
|
|
218
|
+
if (raw.length !== config.expectedDim) {
|
|
219
|
+
console.warn(`[embedding] Unexpected dimensions: got ${raw.length}, expected ${config.expectedDim}`);
|
|
220
|
+
// Don't throw — store what we got, let pgvector validate
|
|
221
|
+
}
|
|
222
|
+
return normalize(raw);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get the expected embedding dimension for the current provider.
|
|
226
|
+
* Returns 0 if no provider configured.
|
|
227
|
+
*/
|
|
228
|
+
export function getEmbeddingDim() {
|
|
229
|
+
return getEmbeddingConfig().expectedDim;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Check if embedding generation is available.
|
|
233
|
+
*/
|
|
234
|
+
export function isEmbeddingAvailable() {
|
|
235
|
+
return getEmbeddingConfig().provider !== "none";
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get the current provider name.
|
|
239
|
+
*/
|
|
240
|
+
export function getProviderName() {
|
|
241
|
+
return getEmbeddingConfig().provider;
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=embedding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding.js","sourceRoot":"","sources":["../../src/services/embedding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,4CAA4C;AAC5C,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAClC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,sBAAsB;AACtB,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AACxD,MAAM,0BAA0B,GAAG,+BAA+B,CAAC;AACnE,MAAM,sBAAsB,GAAG,kBAAkB,CAAC;AAElD,WAAW;AACX,MAAM,cAAc,GAAG,sCAAsC,CAAC;AAC9D,MAAM,kBAAkB,GAAG,yCAAyC,CAAC;AAYrE;;GAEG;AACH,SAAS,SAAS,CAAC,GAAa;IAC9B,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;QACpB,SAAS,IAAI,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IACD,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEjC,IAAI,SAAS,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IAEhC,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,EAAE,WAAW,EAAE,CAAC;IAEpE,kBAAkB;IAClB,IAAI,MAAM,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QAChC,QAAQ,MAAM,EAAE,CAAC;YACf,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;gBACvC,IAAI,CAAC,GAAG,EAAE,CAAC;oBACT,OAAO,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAC;oBACxF,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;gBACjF,CAAC;gBACD,OAAO;oBACL,QAAQ,EAAE,QAAQ;oBAClB,MAAM,EAAE,cAAc;oBACtB,MAAM,EAAE,GAAG;oBACX,KAAK,EAAE,sBAAsB;oBAC7B,WAAW,EAAE,oBAAoB;iBAClC,CAAC;YACJ,CAAC;YACD,KAAK,YAAY,CAAC,CAAC,CAAC;gBAClB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;gBAC3C,IAAI,CAAC,GAAG,EAAE,CAAC;oBACT,OAAO,CAAC,IAAI,CAAC,iFAAiF,CAAC,CAAC;oBAChG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;gBACjF,CAAC;gBACD,OAAO;oBACL,QAAQ,EAAE,YAAY;oBACtB,MAAM,EAAE,kBAAkB;oBAC1B,MAAM,EAAE,GAAG;oBACX,KAAK,EAAE,0BAA0B;oBACjC,WAAW,EAAE,oBAAoB;iBAClC,CAAC;YACJ,CAAC;YACD,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,wBAAwB,CAAC;gBAC/D,OAAO;oBACL,QAAQ,EAAE,QAAQ;oBAClB,MAAM,EAAE,GAAG,GAAG,YAAY;oBAC1B,MAAM,EAAE,EAAE;oBACV,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,sBAAsB;oBAChE,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,MAAM,CAAC,kBAAkB,CAAC,EAAE,EAAE,CAAC;iBAC1F,CAAC;YACJ,CAAC;YACD;gBACE,OAAO,CAAC,IAAI,CAAC,iCAAiC,MAAM,EAAE,CAAC,CAAC;gBACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;QACnF,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;QAC/B,OAAO;YACL,QAAQ,EAAE,QAAQ;YAClB,MAAM,EAAE,cAAc;YACtB,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;YAClC,KAAK,EAAE,sBAAsB;YAC7B,WAAW,EAAE,oBAAoB;SAClC,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC;QACnC,OAAO;YACL,QAAQ,EAAE,YAAY;YACtB,MAAM,EAAE,kBAAkB;YAC1B,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB;YACtC,KAAK,EAAE,0BAA0B;YACjC,WAAW,EAAE,oBAAoB;SAClC,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;QACnC,OAAO;YACL,QAAQ,EAAE,QAAQ;YAClB,MAAM,EAAE,GAAG,GAAG,YAAY;YAC1B,MAAM,EAAE,EAAE;YACV,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,sBAAsB;YAChE,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,MAAM,CAAC,kBAAkB,CAAC,EAAE,EAAE,CAAC;SAC1F,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;AACjF,CAAC;AAED,0CAA0C;AAC1C,IAAI,OAAO,GAA2B,IAAI,CAAC;AAE3C;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,GAAG,cAAc,EAAE,CAAC;QAC3B,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YAChC,OAAO,CAAC,KAAK,CAAC,yBAAyB,OAAO,CAAC,QAAQ,YAAY,OAAO,CAAC,KAAK,UAAU,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC;QACpH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,wFAAwF,CAAC,CAAC;QAC1G,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,GAAG,IAAI,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,MAAuB;IAC9D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE;QAC1C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,MAAM,CAAC,MAAM,EAAE;YACxC,cAAc,EAAE,kBAAkB;SACnC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,KAAK,EAAE,IAAI;SACZ,CAAC;KACH,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,CAAC,QAAQ,qBAAqB,QAAQ,CAAC,MAAM,MAAM,SAAS,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAElC,CAAC;IAEF,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,wBAAwB,MAAM,CAAC,QAAQ,WAAW,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,MAAuB;IAC9D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE;QAC1C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;SACnC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,KAAK,EAAE,IAAI;SACZ,CAAC;KACH,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,CAAC,MAAM,MAAM,SAAS,EAAE,CAAC,CAAC;IAC/E,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAElC,CAAC;IAEF,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAY;IACtC,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAC;IAEpC,IAAI,MAAM,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,GAAa,CAAC;IAElB,QAAQ,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxB,KAAK,QAAQ,CAAC;QACd,KAAK,YAAY;YACf,GAAG,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACtC,MAAM;QACR,KAAK,QAAQ;YACX,GAAG,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACtC,MAAM;QACR;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,sBAAsB;IACtB,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC;QACtC,OAAO,CAAC,IAAI,CACV,0CAA0C,GAAG,CAAC,MAAM,cAAc,MAAM,CAAC,WAAW,EAAE,CACvF,CAAC;QACF,yDAAyD;IAC3D,CAAC;IAED,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,kBAAkB,EAAE,CAAC,WAAW,CAAC;AAC1C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,kBAAkB,EAAE,CAAC,QAAQ,KAAK,MAAM,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,kBAAkB,EAAE,CAAC,QAAQ,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolved .gitmem directory path
|
|
3
|
+
*
|
|
4
|
+
* Solves: process.cwd() changes when agents cd into other repos (e.g., /workspace/gitmem),
|
|
5
|
+
* but .gitmem/ was created in the project root (e.g., /workspace/orchestra/).
|
|
6
|
+
* The MCP server is long-running, so we resolve the path once and cache it.
|
|
7
|
+
*
|
|
8
|
+
* Resolution order:
|
|
9
|
+
* 1. Cached path from session_start (most reliable — session_start created the directory)
|
|
10
|
+
* 2. Walk up from process.cwd() looking for existing .gitmem/ sentinels
|
|
11
|
+
* 3. Fall back to process.cwd()/.gitmem (original behavior)
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Set the .gitmem directory path (called by session_start after creating it)
|
|
15
|
+
*/
|
|
16
|
+
export declare function setGitmemDir(dir: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* Get the resolved .gitmem directory path
|
|
19
|
+
*/
|
|
20
|
+
export declare function getGitmemDir(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Get a file path within the .gitmem directory
|
|
23
|
+
*/
|
|
24
|
+
export declare function getGitmemPath(filename: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Get the per-session directory path: .gitmem/sessions/<sessionId>/
|
|
27
|
+
* Creates the directory if it doesn't exist.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getSessionDir(sessionId: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Get a file path within a per-session directory.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getSessionPath(sessionId: string, filename: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Clear the cached path (for testing)
|
|
36
|
+
*/
|
|
37
|
+
export declare function clearGitmemDirCache(): void;
|
|
38
|
+
//# sourceMappingURL=gitmem-dir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitmem-dir.d.ts","sourceRoot":"","sources":["../../src/services/gitmem-dir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAG9C;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CA6BrC;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAOvD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolved .gitmem directory path
|
|
3
|
+
*
|
|
4
|
+
* Solves: process.cwd() changes when agents cd into other repos (e.g., /workspace/gitmem),
|
|
5
|
+
* but .gitmem/ was created in the project root (e.g., /workspace/orchestra/).
|
|
6
|
+
* The MCP server is long-running, so we resolve the path once and cache it.
|
|
7
|
+
*
|
|
8
|
+
* Resolution order:
|
|
9
|
+
* 1. Cached path from session_start (most reliable — session_start created the directory)
|
|
10
|
+
* 2. Walk up from process.cwd() looking for existing .gitmem/ sentinels
|
|
11
|
+
* 3. Fall back to process.cwd()/.gitmem (original behavior)
|
|
12
|
+
*/
|
|
13
|
+
import * as path from "path";
|
|
14
|
+
import * as fs from "fs";
|
|
15
|
+
let cachedGitmemDir = null;
|
|
16
|
+
/**
|
|
17
|
+
* Set the .gitmem directory path (called by session_start after creating it)
|
|
18
|
+
*/
|
|
19
|
+
export function setGitmemDir(dir) {
|
|
20
|
+
cachedGitmemDir = dir;
|
|
21
|
+
console.error(`[gitmem-dir] Cached .gitmem path: ${dir}`);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get the resolved .gitmem directory path
|
|
25
|
+
*/
|
|
26
|
+
export function getGitmemDir() {
|
|
27
|
+
// 1. Use cached path from session_start
|
|
28
|
+
if (cachedGitmemDir && fs.existsSync(cachedGitmemDir)) {
|
|
29
|
+
return cachedGitmemDir;
|
|
30
|
+
}
|
|
31
|
+
// 2. Walk up from CWD looking for existing .gitmem directory
|
|
32
|
+
// Sentinel files checked in priority order:
|
|
33
|
+
// - active-sessions.json (multi-session registry, GIT-19)
|
|
34
|
+
// - config.json (project-level gitmem config)
|
|
35
|
+
const sentinels = ["active-sessions.json", "config.json"];
|
|
36
|
+
let dir = process.cwd();
|
|
37
|
+
const root = path.parse(dir).root;
|
|
38
|
+
while (dir !== root) {
|
|
39
|
+
const candidate = path.join(dir, ".gitmem");
|
|
40
|
+
for (const sentinel of sentinels) {
|
|
41
|
+
if (fs.existsSync(path.join(candidate, sentinel))) {
|
|
42
|
+
cachedGitmemDir = candidate;
|
|
43
|
+
console.error(`[gitmem-dir] Found .gitmem via walk-up (${sentinel}): ${candidate}`);
|
|
44
|
+
return candidate;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
dir = path.dirname(dir);
|
|
48
|
+
}
|
|
49
|
+
// 3. Fall back to CWD (original behavior)
|
|
50
|
+
const fallback = path.join(process.cwd(), ".gitmem");
|
|
51
|
+
console.error(`[gitmem-dir] Falling back to CWD: ${fallback}`);
|
|
52
|
+
return fallback;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get a file path within the .gitmem directory
|
|
56
|
+
*/
|
|
57
|
+
export function getGitmemPath(filename) {
|
|
58
|
+
return path.join(getGitmemDir(), filename);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the per-session directory path: .gitmem/sessions/<sessionId>/
|
|
62
|
+
* Creates the directory if it doesn't exist.
|
|
63
|
+
*/
|
|
64
|
+
export function getSessionDir(sessionId) {
|
|
65
|
+
const sessionsDir = path.join(getGitmemDir(), "sessions", sessionId);
|
|
66
|
+
if (!fs.existsSync(sessionsDir)) {
|
|
67
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
68
|
+
console.error(`[gitmem-dir] Created session directory: ${sessionsDir}`);
|
|
69
|
+
}
|
|
70
|
+
return sessionsDir;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get a file path within a per-session directory.
|
|
74
|
+
*/
|
|
75
|
+
export function getSessionPath(sessionId, filename) {
|
|
76
|
+
return path.join(getSessionDir(sessionId), filename);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Clear the cached path (for testing)
|
|
80
|
+
*/
|
|
81
|
+
export function clearGitmemDirCache() {
|
|
82
|
+
cachedGitmemDir = null;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=gitmem-dir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitmem-dir.js","sourceRoot":"","sources":["../../src/services/gitmem-dir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAEzB,IAAI,eAAe,GAAkB,IAAI,CAAC;AAE1C;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,eAAe,GAAG,GAAG,CAAC;IACtB,OAAO,CAAC,KAAK,CAAC,qCAAqC,GAAG,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY;IAC1B,wCAAwC;IACxC,IAAI,eAAe,IAAI,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACtD,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,6DAA6D;IAC7D,+CAA+C;IAC/C,8DAA8D;IAC9D,2DAA2D;IAC3D,MAAM,SAAS,GAAG,CAAC,sBAAsB,EAAE,aAAa,CAAC,CAAC;IAC1D,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IACxB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IAClC,OAAO,GAAG,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC5C,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;gBAClD,eAAe,GAAG,SAAS,CAAC;gBAC5B,OAAO,CAAC,KAAK,CAAC,2CAA2C,QAAQ,MAAM,SAAS,EAAE,CAAC,CAAC;gBACpF,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;QACD,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAED,0CAA0C;IAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC;IACrD,OAAO,CAAC,KAAK,CAAC,qCAAqC,QAAQ,EAAE,CAAC,CAAC;IAC/D,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC7C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,SAAiB;IAC7C,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;IACrE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAChC,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,OAAO,CAAC,KAAK,CAAC,2CAA2C,WAAW,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,QAAgB;IAChE,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;AACvD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,eAAe,GAAG,IAAI,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local File Storage — Free Tier Backend
|
|
3
|
+
*
|
|
4
|
+
* Stores scars, sessions, decisions, and scar usage as JSON files
|
|
5
|
+
* in the .gitmem/ directory of the current project.
|
|
6
|
+
*
|
|
7
|
+
* Provides keyword-based search (no embeddings needed).
|
|
8
|
+
*/
|
|
9
|
+
import type { RelevantScar } from "../types/index.js";
|
|
10
|
+
export declare class LocalFileStorage {
|
|
11
|
+
private basePath;
|
|
12
|
+
constructor(basePath?: string);
|
|
13
|
+
private ensureDir;
|
|
14
|
+
private getFilePath;
|
|
15
|
+
private readCollection;
|
|
16
|
+
private writeCollection;
|
|
17
|
+
/**
|
|
18
|
+
* List records with optional filters
|
|
19
|
+
*/
|
|
20
|
+
list<T extends Record<string, unknown>>(collection: string, options?: {
|
|
21
|
+
filters?: Record<string, string>;
|
|
22
|
+
order?: string;
|
|
23
|
+
limit?: number;
|
|
24
|
+
}): Promise<T[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Get a single record by ID
|
|
27
|
+
*/
|
|
28
|
+
get<T extends Record<string, unknown>>(collection: string, id: string): Promise<T | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Upsert a record (insert or update by ID)
|
|
31
|
+
*/
|
|
32
|
+
upsert<T extends Record<string, unknown>>(collection: string, data: T & {
|
|
33
|
+
id: string;
|
|
34
|
+
}): Promise<T>;
|
|
35
|
+
/**
|
|
36
|
+
* Delete a record by ID
|
|
37
|
+
*/
|
|
38
|
+
delete(collection: string, id: string): Promise<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Keyword-based search for scars (free tier alternative to semantic search)
|
|
41
|
+
*
|
|
42
|
+
* Scores by: title match (3x), keyword match (2x), description match (1x)
|
|
43
|
+
*/
|
|
44
|
+
keywordSearch(query: string, k?: number): Promise<RelevantScar[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Get the count of learnings stored locally
|
|
47
|
+
*/
|
|
48
|
+
getLearningCount(): number;
|
|
49
|
+
/**
|
|
50
|
+
* Load starter scars from a JSON file into local storage
|
|
51
|
+
*/
|
|
52
|
+
loadStarterScars(scarsPath: string): Promise<number>;
|
|
53
|
+
}
|
|
54
|
+
export declare function getLocalFileStorage(): LocalFileStorage;
|
|
55
|
+
export declare function resetLocalFileStorage(): void;
|
|
56
|
+
//# sourceMappingURL=local-file-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-file-storage.d.ts","sourceRoot":"","sources":["../../src/services/local-file-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAKtD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAS;gBAEb,QAAQ,CAAC,EAAE,MAAM;IAK7B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,eAAe;IAoBvB;;OAEG;IACG,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1C,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KACX,GACL,OAAO,CAAC,CAAC,EAAE,CAAC;IA8Bf;;OAEG;IACG,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzC,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAKpB;;OAEG;IACG,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,CAAC,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GACvB,OAAO,CAAC,CAAC,CAAC;IAcb;;OAEG;IACG,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ9D;;;;OAIG;IACG,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,SAAI,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAyClE;;OAEG;IACH,gBAAgB,IAAI,MAAM;IAI1B;;OAEG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAyB3D;AAeD,wBAAgB,mBAAmB,IAAI,gBAAgB,CAKtD;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local File Storage — Free Tier Backend
|
|
3
|
+
*
|
|
4
|
+
* Stores scars, sessions, decisions, and scar usage as JSON files
|
|
5
|
+
* in the .gitmem/ directory of the current project.
|
|
6
|
+
*
|
|
7
|
+
* Provides keyword-based search (no embeddings needed).
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
12
|
+
const WARN_FILE_SIZE = 1 * 1024 * 1024; // 1MB
|
|
13
|
+
export class LocalFileStorage {
|
|
14
|
+
basePath;
|
|
15
|
+
constructor(basePath) {
|
|
16
|
+
this.basePath = basePath || path.join(process.cwd(), ".gitmem");
|
|
17
|
+
this.ensureDir();
|
|
18
|
+
}
|
|
19
|
+
ensureDir() {
|
|
20
|
+
if (!fs.existsSync(this.basePath)) {
|
|
21
|
+
fs.mkdirSync(this.basePath, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
getFilePath(collection) {
|
|
25
|
+
return path.join(this.basePath, `${collection}.json`);
|
|
26
|
+
}
|
|
27
|
+
readCollection(collection) {
|
|
28
|
+
const filePath = this.getFilePath(collection);
|
|
29
|
+
if (!fs.existsSync(filePath))
|
|
30
|
+
return [];
|
|
31
|
+
try {
|
|
32
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
33
|
+
return JSON.parse(raw);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
console.error(`[local-storage] Failed to read ${collection}.json, starting fresh`);
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
writeCollection(collection, data) {
|
|
41
|
+
const filePath = this.getFilePath(collection);
|
|
42
|
+
const json = JSON.stringify(data, null, 2);
|
|
43
|
+
const size = Buffer.byteLength(json, "utf-8");
|
|
44
|
+
if (size > WARN_FILE_SIZE) {
|
|
45
|
+
console.error(`[local-storage] Warning: ${collection}.json is ${(size / 1024).toFixed(0)}KB`);
|
|
46
|
+
}
|
|
47
|
+
if (size > MAX_FILE_SIZE) {
|
|
48
|
+
console.error(`[local-storage] ${collection}.json exceeds 10MB, evicting oldest entries`);
|
|
49
|
+
// Keep most recent 80% of entries
|
|
50
|
+
const trimmed = data.slice(Math.floor(data.length * 0.2));
|
|
51
|
+
fs.writeFileSync(filePath, JSON.stringify(trimmed, null, 2), "utf-8");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
fs.writeFileSync(filePath, json, "utf-8");
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* List records with optional filters
|
|
58
|
+
*/
|
|
59
|
+
async list(collection, options = {}) {
|
|
60
|
+
let records = this.readCollection(collection);
|
|
61
|
+
// Apply simple filters
|
|
62
|
+
if (options.filters) {
|
|
63
|
+
for (const [key, value] of Object.entries(options.filters)) {
|
|
64
|
+
const cleanValue = value.startsWith("eq.") ? value.slice(3) : value;
|
|
65
|
+
records = records.filter((r) => String(r[key]) === cleanValue);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Apply ordering
|
|
69
|
+
if (options.order) {
|
|
70
|
+
const [field, direction] = options.order.split(".");
|
|
71
|
+
const asc = direction !== "desc";
|
|
72
|
+
records.sort((a, b) => {
|
|
73
|
+
const av = String(a[field] || "");
|
|
74
|
+
const bv = String(b[field] || "");
|
|
75
|
+
return asc ? av.localeCompare(bv) : bv.localeCompare(av);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
// Apply limit
|
|
79
|
+
if (options.limit) {
|
|
80
|
+
records = records.slice(0, options.limit);
|
|
81
|
+
}
|
|
82
|
+
return records;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get a single record by ID
|
|
86
|
+
*/
|
|
87
|
+
async get(collection, id) {
|
|
88
|
+
const records = this.readCollection(collection);
|
|
89
|
+
return records.find((r) => r.id === id) || null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Upsert a record (insert or update by ID)
|
|
93
|
+
*/
|
|
94
|
+
async upsert(collection, data) {
|
|
95
|
+
const records = this.readCollection(collection);
|
|
96
|
+
const existingIndex = records.findIndex((r) => r.id === data.id);
|
|
97
|
+
if (existingIndex >= 0) {
|
|
98
|
+
records[existingIndex] = { ...records[existingIndex], ...data };
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
records.push(data);
|
|
102
|
+
}
|
|
103
|
+
this.writeCollection(collection, records);
|
|
104
|
+
return data;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Delete a record by ID
|
|
108
|
+
*/
|
|
109
|
+
async delete(collection, id) {
|
|
110
|
+
const records = this.readCollection(collection);
|
|
111
|
+
const filtered = records.filter((r) => r.id !== id);
|
|
112
|
+
if (filtered.length === records.length)
|
|
113
|
+
return false;
|
|
114
|
+
this.writeCollection(collection, filtered);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Keyword-based search for scars (free tier alternative to semantic search)
|
|
119
|
+
*
|
|
120
|
+
* Scores by: title match (3x), keyword match (2x), description match (1x)
|
|
121
|
+
*/
|
|
122
|
+
async keywordSearch(query, k = 5) {
|
|
123
|
+
const learnings = this.readCollection("learnings");
|
|
124
|
+
const queryTokens = tokenize(query.toLowerCase());
|
|
125
|
+
if (queryTokens.length === 0)
|
|
126
|
+
return [];
|
|
127
|
+
const maxScore = queryTokens.length * 6; // max possible per token: 3+2+1
|
|
128
|
+
const scored = learnings.map((l) => {
|
|
129
|
+
let score = 0;
|
|
130
|
+
const titleTokens = tokenize(String(l.title || "").toLowerCase());
|
|
131
|
+
const descTokens = tokenize(String(l.description || "").toLowerCase());
|
|
132
|
+
const kwTokens = (l.keywords || []).map((k) => k.toLowerCase());
|
|
133
|
+
for (const qt of queryTokens) {
|
|
134
|
+
if (titleTokens.some((t) => t.includes(qt) || qt.includes(t)))
|
|
135
|
+
score += 3;
|
|
136
|
+
if (kwTokens.some((k) => k.includes(qt) || qt.includes(k)))
|
|
137
|
+
score += 2;
|
|
138
|
+
if (descTokens.some((t) => t.includes(qt) || qt.includes(t)))
|
|
139
|
+
score += 1;
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
learning: l,
|
|
143
|
+
score,
|
|
144
|
+
similarity: Math.round((score / maxScore) * 1000) / 1000,
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
return scored
|
|
148
|
+
.filter((s) => s.score > 0)
|
|
149
|
+
.sort((a, b) => b.score - a.score)
|
|
150
|
+
.slice(0, k)
|
|
151
|
+
.map((s) => ({
|
|
152
|
+
id: String(s.learning.id),
|
|
153
|
+
title: String(s.learning.title),
|
|
154
|
+
description: String(s.learning.description),
|
|
155
|
+
severity: String(s.learning.severity || "medium"),
|
|
156
|
+
counter_arguments: s.learning.counter_arguments || [],
|
|
157
|
+
similarity: s.similarity,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get the count of learnings stored locally
|
|
162
|
+
*/
|
|
163
|
+
getLearningCount() {
|
|
164
|
+
return this.readCollection("learnings").length;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Load starter scars from a JSON file into local storage
|
|
168
|
+
*/
|
|
169
|
+
async loadStarterScars(scarsPath) {
|
|
170
|
+
try {
|
|
171
|
+
const raw = fs.readFileSync(scarsPath, "utf-8");
|
|
172
|
+
const scars = JSON.parse(raw);
|
|
173
|
+
const existing = this.readCollection("learnings");
|
|
174
|
+
const existingIds = new Set(existing.map((e) => e.id));
|
|
175
|
+
let loaded = 0;
|
|
176
|
+
for (const scar of scars) {
|
|
177
|
+
if (!existingIds.has(scar.id)) {
|
|
178
|
+
existing.push(scar);
|
|
179
|
+
loaded++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (loaded > 0) {
|
|
183
|
+
this.writeCollection("learnings", existing);
|
|
184
|
+
}
|
|
185
|
+
return loaded;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
console.error("[local-storage] Failed to load starter scars:", error);
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Tokenize text into words, stripping punctuation
|
|
195
|
+
*/
|
|
196
|
+
function tokenize(text) {
|
|
197
|
+
return text
|
|
198
|
+
.replace(/[^\w\s-]/g, " ")
|
|
199
|
+
.split(/\s+/)
|
|
200
|
+
.filter((t) => t.length > 1);
|
|
201
|
+
}
|
|
202
|
+
// Singleton instance
|
|
203
|
+
let _instance = null;
|
|
204
|
+
export function getLocalFileStorage() {
|
|
205
|
+
if (!_instance) {
|
|
206
|
+
_instance = new LocalFileStorage();
|
|
207
|
+
}
|
|
208
|
+
return _instance;
|
|
209
|
+
}
|
|
210
|
+
export function resetLocalFileStorage() {
|
|
211
|
+
_instance = null;
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=local-file-storage.js.map
|