@cachly-dev/mcp-server 0.10.129 → 0.10.130
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/README.md +2 -2
- package/dist/index.js +1 -7
- package/dist/packages/telegram-notify/client.js +2 -75
- package/dist/src/ambient-cli.js +7 -253
- package/dist/src/ambient-deps.js +1 -55
- package/dist/src/ambient-hooks.js +5 -278
- package/dist/src/ambient-ledger.js +6 -95
- package/dist/src/ambient-memory.js +1 -67
- package/dist/src/ambient-recall.js +1 -221
- package/dist/src/aussetzer.js +1 -225
- package/dist/src/auth.js +9 -140
- package/dist/src/autor-abzeichen.js +1 -56
- package/dist/src/autor.js +1 -136
- package/dist/src/bauabdruck.js +1 -93
- package/dist/src/bedeutung.js +2 -323
- package/dist/src/bench/cachly-bench.js +4 -249
- package/dist/src/bench/external-corpus.js +4 -135
- package/dist/src/bench/fixtures.js +1 -225
- package/dist/src/brain-export.js +16 -119
- package/dist/src/brain-watch-hook.js +8 -86
- package/dist/src/cachly-url.js +1 -21
- package/dist/src/ckg.js +1 -108
- package/dist/src/cls-hook.js +8 -94
- package/dist/src/confidence.js +1 -50
- package/dist/src/credentials.js +1 -85
- package/dist/src/deutsch.js +1 -223
- package/dist/src/doctor.js +2 -248
- package/dist/src/editor.js +1 -63
- package/dist/src/eingaenge-nachruesten.js +1 -290
- package/dist/src/eingaenge.js +2 -260
- package/dist/src/embeddings.js +13 -223
- package/dist/src/feldreparatur.js +3 -113
- package/dist/src/first-contact.js +4 -108
- package/dist/src/first-recall-proof.js +4 -41
- package/dist/src/funnel-milestones.js +2 -48
- package/dist/src/geliefert.js +1 -114
- package/dist/src/handlers/advanced.js +38 -537
- package/dist/src/handlers/brain.js +69 -4397
- package/dist/src/handlers/cache.js +59 -638
- package/dist/src/handlers/context.js +17 -182
- package/dist/src/handlers/fedbrain.js +40 -1436
- package/dist/src/handlers/instances.js +16 -201
- package/dist/src/handlers/roadmap.js +13 -175
- package/dist/src/handlers/share.js +19 -530
- package/dist/src/handlers/syndicate.js +38 -904
- package/dist/src/handlers/team.js +89 -1865
- package/dist/src/handlers/viz.js +3 -148
- package/dist/src/index.js +321 -4281
- package/dist/src/interferenz.js +1 -141
- package/dist/src/kostprobe.js +7 -125
- package/dist/src/lesson-preview.js +2 -133
- package/dist/src/namespace.js +1 -128
- package/dist/src/notifier.js +1 -6
- package/dist/src/rangfolge-stellschrauben.js +1 -0
- package/dist/src/rangfolge.js +1 -249
- package/dist/src/raumkorrektur.js +1 -197
- package/dist/src/recall-tiefe.js +1 -42
- package/dist/src/rerank.js +1 -154
- package/dist/src/resonanz.js +1 -140
- package/dist/src/search.js +1 -2104
- package/dist/src/seltenheitsbestand.js +1 -92
- package/dist/src/speicherlage.js +1 -84
- package/dist/src/spuren.js +1 -150
- package/dist/src/starter-corpus.js +1 -165
- package/dist/src/telemetry-types.js +0 -13
- package/dist/src/tools.js +10 -2465
- package/dist/src/toolspecs.js +1 -121
- package/dist/src/upgrade-nudge.js +1 -29
- package/dist/src/utils.js +1 -89
- package/dist/src/versuch.js +1 -264
- package/dist/src/vorspann.js +1 -146
- package/dist/src/wartung/vektoren-verdichten.js +1 -200
- package/dist/src/werkzeug-nutzung.js +1 -72
- package/dist/src/wertbeitrag.js +1 -203
- package/dist/src/wirkungsspur.js +1 -0
- package/glama.json +1 -1
- package/package.json +8 -7
- package/server.json +3 -3
- package/dist/index.d.ts +0 -1
- package/dist/index.js.map +0 -1
- package/dist/packages/telegram-notify/client.d.ts +0 -19
- package/dist/packages/telegram-notify/client.js.map +0 -1
- package/dist/src/ambient-cli.d.ts +0 -101
- package/dist/src/ambient-cli.js.map +0 -1
- package/dist/src/ambient-deps.d.ts +0 -26
- package/dist/src/ambient-deps.js.map +0 -1
- package/dist/src/ambient-hooks.d.ts +0 -137
- package/dist/src/ambient-hooks.js.map +0 -1
- package/dist/src/ambient-ledger.d.ts +0 -19
- package/dist/src/ambient-ledger.js.map +0 -1
- package/dist/src/ambient-memory.d.ts +0 -12
- package/dist/src/ambient-memory.js.map +0 -1
- package/dist/src/ambient-recall.d.ts +0 -127
- package/dist/src/ambient-recall.js.map +0 -1
- package/dist/src/aussetzer.d.ts +0 -156
- package/dist/src/aussetzer.js.map +0 -1
- package/dist/src/auth.d.ts +0 -43
- package/dist/src/auth.js.map +0 -1
- package/dist/src/autor-abzeichen.d.ts +0 -41
- package/dist/src/autor-abzeichen.js.map +0 -1
- package/dist/src/autor.d.ts +0 -101
- package/dist/src/autor.js.map +0 -1
- package/dist/src/bauabdruck-daten.generiert.json +0 -5
- package/dist/src/bauabdruck.d.ts +0 -38
- package/dist/src/bauabdruck.js.map +0 -1
- package/dist/src/bedeutung.d.ts +0 -168
- package/dist/src/bedeutung.js.map +0 -1
- package/dist/src/bench/cachly-bench.d.ts +0 -61
- package/dist/src/bench/cachly-bench.js.map +0 -1
- package/dist/src/bench/external-corpus.d.ts +0 -44
- package/dist/src/bench/external-corpus.js.map +0 -1
- package/dist/src/bench/fixtures.d.ts +0 -37
- package/dist/src/bench/fixtures.js.map +0 -1
- package/dist/src/brain-export.d.ts +0 -64
- package/dist/src/brain-export.js.map +0 -1
- package/dist/src/brain-watch-hook.d.ts +0 -21
- package/dist/src/brain-watch-hook.js.map +0 -1
- package/dist/src/cachly-url.d.ts +0 -68
- package/dist/src/cachly-url.js.map +0 -1
- package/dist/src/ckg.d.ts +0 -54
- package/dist/src/ckg.js.map +0 -1
- package/dist/src/cls-hook.d.ts +0 -15
- package/dist/src/cls-hook.js.map +0 -1
- package/dist/src/confidence.d.ts +0 -19
- package/dist/src/confidence.js.map +0 -1
- package/dist/src/credentials.d.ts +0 -37
- package/dist/src/credentials.js.map +0 -1
- package/dist/src/deutsch.d.ts +0 -98
- package/dist/src/deutsch.js.map +0 -1
- package/dist/src/doctor.d.ts +0 -47
- package/dist/src/doctor.js.map +0 -1
- package/dist/src/editor.d.ts +0 -9
- package/dist/src/editor.js.map +0 -1
- package/dist/src/eingaenge-nachruesten.d.ts +0 -41
- package/dist/src/eingaenge-nachruesten.js.map +0 -1
- package/dist/src/eingaenge.d.ts +0 -159
- package/dist/src/eingaenge.js.map +0 -1
- package/dist/src/embeddings.d.ts +0 -48
- package/dist/src/embeddings.js.map +0 -1
- package/dist/src/feldreparatur.d.ts +0 -58
- package/dist/src/feldreparatur.js.map +0 -1
- package/dist/src/first-contact.d.ts +0 -56
- package/dist/src/first-contact.js.map +0 -1
- package/dist/src/first-recall-proof.d.ts +0 -19
- package/dist/src/first-recall-proof.js.map +0 -1
- package/dist/src/funnel-milestones.d.ts +0 -5
- package/dist/src/funnel-milestones.js.map +0 -1
- package/dist/src/geliefert.d.ts +0 -63
- package/dist/src/geliefert.js.map +0 -1
- package/dist/src/handlers/advanced.d.ts +0 -6
- package/dist/src/handlers/advanced.js.map +0 -1
- package/dist/src/handlers/brain.d.ts +0 -41
- package/dist/src/handlers/brain.js.map +0 -1
- package/dist/src/handlers/cache.d.ts +0 -6
- package/dist/src/handlers/cache.js.map +0 -1
- package/dist/src/handlers/context.d.ts +0 -6
- package/dist/src/handlers/context.js.map +0 -1
- package/dist/src/handlers/fedbrain.d.ts +0 -18
- package/dist/src/handlers/fedbrain.js.map +0 -1
- package/dist/src/handlers/instances.d.ts +0 -33
- package/dist/src/handlers/instances.js.map +0 -1
- package/dist/src/handlers/roadmap.d.ts +0 -6
- package/dist/src/handlers/roadmap.js.map +0 -1
- package/dist/src/handlers/share.d.ts +0 -8
- package/dist/src/handlers/share.js.map +0 -1
- package/dist/src/handlers/syndicate.d.ts +0 -6
- package/dist/src/handlers/syndicate.js.map +0 -1
- package/dist/src/handlers/team.d.ts +0 -45
- package/dist/src/handlers/team.js.map +0 -1
- package/dist/src/handlers/viz.d.ts +0 -6
- package/dist/src/handlers/viz.js.map +0 -1
- package/dist/src/index.d.ts +0 -93
- package/dist/src/index.js.map +0 -1
- package/dist/src/interferenz.d.ts +0 -111
- package/dist/src/interferenz.js.map +0 -1
- package/dist/src/kostprobe.d.ts +0 -25
- package/dist/src/kostprobe.js.map +0 -1
- package/dist/src/lesson-preview.d.ts +0 -81
- package/dist/src/lesson-preview.js.map +0 -1
- package/dist/src/namespace.d.ts +0 -20
- package/dist/src/namespace.js.map +0 -1
- package/dist/src/notifier.d.ts +0 -5
- package/dist/src/notifier.js.map +0 -1
- package/dist/src/rangfolge.d.ts +0 -205
- package/dist/src/rangfolge.js.map +0 -1
- package/dist/src/raumkorrektur.d.ts +0 -100
- package/dist/src/raumkorrektur.js.map +0 -1
- package/dist/src/recall-tiefe.d.ts +0 -48
- package/dist/src/recall-tiefe.js.map +0 -1
- package/dist/src/rerank.d.ts +0 -48
- package/dist/src/rerank.js.map +0 -1
- package/dist/src/resonanz.d.ts +0 -85
- package/dist/src/resonanz.js.map +0 -1
- package/dist/src/search.d.ts +0 -153
- package/dist/src/search.js.map +0 -1
- package/dist/src/seltenheitsbestand.d.ts +0 -53
- package/dist/src/seltenheitsbestand.js.map +0 -1
- package/dist/src/speicherlage.d.ts +0 -51
- package/dist/src/speicherlage.js.map +0 -1
- package/dist/src/spuren.d.ts +0 -97
- package/dist/src/spuren.js.map +0 -1
- package/dist/src/starter-corpus.d.ts +0 -28
- package/dist/src/starter-corpus.js.map +0 -1
- package/dist/src/telemetry-types.d.ts +0 -87
- package/dist/src/telemetry-types.js.map +0 -1
- package/dist/src/tools.d.ts +0 -2933
- package/dist/src/tools.js.map +0 -1
- package/dist/src/toolspecs.d.ts +0 -43
- package/dist/src/toolspecs.js.map +0 -1
- package/dist/src/upgrade-nudge.d.ts +0 -15
- package/dist/src/upgrade-nudge.js.map +0 -1
- package/dist/src/utils.d.ts +0 -34
- package/dist/src/utils.js.map +0 -1
- package/dist/src/versuch.d.ts +0 -187
- package/dist/src/versuch.js.map +0 -1
- package/dist/src/vorspann.d.ts +0 -73
- package/dist/src/vorspann.js.map +0 -1
- package/dist/src/wartung/vektoren-verdichten.d.ts +0 -50
- package/dist/src/wartung/vektoren-verdichten.js.map +0 -1
- package/dist/src/werkzeug-nutzung.d.ts +0 -62
- package/dist/src/werkzeug-nutzung.js.map +0 -1
- package/dist/src/wertbeitrag.d.ts +0 -148
- package/dist/src/wertbeitrag.js.map +0 -1
package/dist/src/embeddings.js
CHANGED
|
@@ -1,223 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* │ Brain works WITHOUT embedding (keyword search + exact key lookup). │
|
|
15
|
-
* │ Embedding is an optional boost for semantic_search/index_project. │
|
|
16
|
-
* └──────────────────────────────────────────────────────────────────────┘
|
|
17
|
-
*
|
|
18
|
-
* Mutable config — call setEmbedJwt() when the JWT changes (device flow).
|
|
19
|
-
*/
|
|
20
|
-
// ── Mutable config (synced with index.ts JWT on device-flow auth) ────────────
|
|
21
|
-
export const embedConfig = {
|
|
22
|
-
apiUrl: process.env.CACHLY_API_URL ?? 'https://api.cachly.dev',
|
|
23
|
-
jwt: process.env.CACHLY_JWT ?? '',
|
|
24
|
-
model: process.env.CACHLY_EMBED_MODEL ?? '',
|
|
25
|
-
};
|
|
26
|
-
/** Call this when JWT is refreshed (e.g. after device flow). */
|
|
27
|
-
export function setEmbedJwt(jwt) {
|
|
28
|
-
embedConfig.jwt = jwt;
|
|
29
|
-
}
|
|
30
|
-
// Hard timeout for any embedding-provider HTTP call. Embedding is an OPTIONAL boost
|
|
31
|
-
// for semantic recall — a slow/unreachable provider must never hang the agent turn.
|
|
32
|
-
// Callers already treat embedding failures as non-fatal (keyword search still works).
|
|
33
|
-
const EMBED_TIMEOUT_MS = Number(process.env.CACHLY_EMBED_TIMEOUT_MS ?? 8_000);
|
|
34
|
-
// ── Provider auto-detection ───────────────────────────────────────────────────
|
|
35
|
-
function detectEmbedProvider() {
|
|
36
|
-
// Client-side keys first (direct, fast, no server roundtrip)
|
|
37
|
-
if (process.env.OPENAI_API_KEY)
|
|
38
|
-
return 'openai';
|
|
39
|
-
if (process.env.GEMINI_API_KEY)
|
|
40
|
-
return 'gemini';
|
|
41
|
-
if (process.env.MISTRAL_API_KEY)
|
|
42
|
-
return 'mistral';
|
|
43
|
-
if (process.env.COHERE_API_KEY)
|
|
44
|
-
return 'cohere';
|
|
45
|
-
if (process.env.OLLAMA_BASE_URL)
|
|
46
|
-
return 'ollama';
|
|
47
|
-
// Server-side fallback — no API key needed, but adds latency
|
|
48
|
-
if (process.env.CACHLY_JWT)
|
|
49
|
-
return 'cachly';
|
|
50
|
-
return 'none'; // no provider → embedding disabled, brain still works via exact keys
|
|
51
|
-
}
|
|
52
|
-
export const EMBED_PROVIDER = (process.env.CACHLY_EMBED_PROVIDER ?? detectEmbedProvider()).toLowerCase();
|
|
53
|
-
// ── Multi-provider embedding ─────────────────────────────────────────────────
|
|
54
|
-
/**
|
|
55
|
-
* Compute an embedding vector for `text` using the configured provider.
|
|
56
|
-
*
|
|
57
|
-
* Client-side (recommended — set one API key in your .env):
|
|
58
|
-
* openai – OPENAI_API_KEY · text-embedding-3-small
|
|
59
|
-
* gemini – GEMINI_API_KEY · text-embedding-004
|
|
60
|
-
* mistral – MISTRAL_API_KEY · mistral-embed
|
|
61
|
-
* cohere – COHERE_API_KEY · embed-english-v3.0
|
|
62
|
-
* ollama – OLLAMA_BASE_URL · nomic-embed-text (local, free)
|
|
63
|
-
*
|
|
64
|
-
* Server-side fallback (no key needed on client):
|
|
65
|
-
* cachly – POST /api/v1/embed (requires CACHLY_JWT)
|
|
66
|
-
*
|
|
67
|
-
* Note: Brain works fully WITHOUT embedding (keyword search + exact keys).
|
|
68
|
-
* Embedding is an OPTIONAL boost for semantic_search and index_project.
|
|
69
|
-
*/
|
|
70
|
-
export async function computeEmbedding(text) {
|
|
71
|
-
switch (EMBED_PROVIDER) {
|
|
72
|
-
case 'cachly': {
|
|
73
|
-
// Server-side embedding — the Cachly API computes the embedding
|
|
74
|
-
// using whatever provider is configured on the server. No client-side API key needed.
|
|
75
|
-
if (!embedConfig.jwt)
|
|
76
|
-
throw new Error('CACHLY_JWT not set.\n\n' +
|
|
77
|
-
'The "cachly" provider uses server-side embeddings via the Cachly API.\n' +
|
|
78
|
-
'Set CACHLY_JWT, or use another provider via CACHLY_EMBED_PROVIDER:\n' +
|
|
79
|
-
' openai → OPENAI_API_KEY\n' +
|
|
80
|
-
' gemini → GEMINI_API_KEY\n' +
|
|
81
|
-
' ollama → OLLAMA_BASE_URL (local, no key needed)');
|
|
82
|
-
const url = `${embedConfig.apiUrl}/api/v1/embed`;
|
|
83
|
-
const body = { text };
|
|
84
|
-
if (embedConfig.model)
|
|
85
|
-
body.model = embedConfig.model;
|
|
86
|
-
const res = await fetch(url, {
|
|
87
|
-
method: 'POST',
|
|
88
|
-
headers: {
|
|
89
|
-
'Content-Type': 'application/json',
|
|
90
|
-
'Authorization': `Bearer ${embedConfig.jwt}`,
|
|
91
|
-
},
|
|
92
|
-
body: JSON.stringify(body),
|
|
93
|
-
signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),
|
|
94
|
-
});
|
|
95
|
-
if (!res.ok) {
|
|
96
|
-
const errBody = await res.text();
|
|
97
|
-
throw new Error(`Cachly embed API error ${res.status}: ${errBody}`);
|
|
98
|
-
}
|
|
99
|
-
const json = (await res.json());
|
|
100
|
-
return json.embedding;
|
|
101
|
-
}
|
|
102
|
-
case 'mistral': {
|
|
103
|
-
const key = process.env.MISTRAL_API_KEY;
|
|
104
|
-
if (!key)
|
|
105
|
-
throw new Error('MISTRAL_API_KEY not set');
|
|
106
|
-
const model = embedConfig.model !== 'text-embedding-3-small' ? embedConfig.model : 'mistral-embed';
|
|
107
|
-
const res = await fetch('https://api.mistral.ai/v1/embeddings', {
|
|
108
|
-
method: 'POST',
|
|
109
|
-
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${key}` },
|
|
110
|
-
body: JSON.stringify({ model, input: [text] }),
|
|
111
|
-
signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),
|
|
112
|
-
});
|
|
113
|
-
if (!res.ok)
|
|
114
|
-
throw new Error(`Mistral embedding error: ${res.statusText}`);
|
|
115
|
-
const json = (await res.json());
|
|
116
|
-
if (!json.data?.[0]?.embedding)
|
|
117
|
-
throw new Error('Mistral returned empty embedding response');
|
|
118
|
-
return json.data[0].embedding;
|
|
119
|
-
}
|
|
120
|
-
case 'cohere': {
|
|
121
|
-
const key = process.env.COHERE_API_KEY;
|
|
122
|
-
if (!key)
|
|
123
|
-
throw new Error('COHERE_API_KEY not set');
|
|
124
|
-
const model = embedConfig.model !== 'text-embedding-3-small' ? embedConfig.model : 'embed-english-v3.0';
|
|
125
|
-
const res = await fetch('https://api.cohere.com/v2/embed', {
|
|
126
|
-
method: 'POST',
|
|
127
|
-
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${key}` },
|
|
128
|
-
body: JSON.stringify({ model, texts: [text], input_type: 'search_query', embedding_types: ['float'] }),
|
|
129
|
-
signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),
|
|
130
|
-
});
|
|
131
|
-
if (!res.ok)
|
|
132
|
-
throw new Error(`Cohere embedding error: ${res.statusText}`);
|
|
133
|
-
const json = (await res.json());
|
|
134
|
-
return json.embeddings.float[0];
|
|
135
|
-
}
|
|
136
|
-
case 'ollama': {
|
|
137
|
-
const base = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';
|
|
138
|
-
const model = embedConfig.model !== 'text-embedding-3-small' ? embedConfig.model : 'nomic-embed-text';
|
|
139
|
-
const res = await fetch(`${base}/api/embeddings`, {
|
|
140
|
-
method: 'POST',
|
|
141
|
-
headers: { 'Content-Type': 'application/json' },
|
|
142
|
-
body: JSON.stringify({ model, prompt: text }),
|
|
143
|
-
signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),
|
|
144
|
-
});
|
|
145
|
-
if (!res.ok)
|
|
146
|
-
throw new Error(`Ollama embedding error: ${res.statusText}`);
|
|
147
|
-
const json = (await res.json());
|
|
148
|
-
return json.embedding;
|
|
149
|
-
}
|
|
150
|
-
case 'gemini': {
|
|
151
|
-
const key = process.env.GEMINI_API_KEY;
|
|
152
|
-
if (!key)
|
|
153
|
-
throw new Error('GEMINI_API_KEY not set');
|
|
154
|
-
const model = embedConfig.model !== 'text-embedding-3-small' ? embedConfig.model : 'text-embedding-004';
|
|
155
|
-
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:embedContent?key=${key}`, {
|
|
156
|
-
method: 'POST',
|
|
157
|
-
headers: { 'Content-Type': 'application/json' },
|
|
158
|
-
body: JSON.stringify({ model: `models/${model}`, content: { parts: [{ text }] } }),
|
|
159
|
-
signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),
|
|
160
|
-
});
|
|
161
|
-
if (!res.ok)
|
|
162
|
-
throw new Error(`Gemini embedding error: ${res.statusText}`);
|
|
163
|
-
const json = (await res.json());
|
|
164
|
-
return json.embedding.values;
|
|
165
|
-
}
|
|
166
|
-
case 'openai': {
|
|
167
|
-
const key = process.env.OPENAI_API_KEY;
|
|
168
|
-
if (!key)
|
|
169
|
-
throw new Error('OPENAI_API_KEY not set.\n\n' +
|
|
170
|
-
'Set OPENAI_API_KEY in your .env, or switch provider:\n' +
|
|
171
|
-
' CACHLY_EMBED_PROVIDER=gemini (+ GEMINI_API_KEY)\n' +
|
|
172
|
-
' CACHLY_EMBED_PROVIDER=ollama (local, free)\n' +
|
|
173
|
-
' CACHLY_EMBED_PROVIDER=cachly (server-side, no key needed)');
|
|
174
|
-
const model = embedConfig.model || 'text-embedding-3-small';
|
|
175
|
-
const res = await fetch('https://api.openai.com/v1/embeddings', {
|
|
176
|
-
method: 'POST',
|
|
177
|
-
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${key}` },
|
|
178
|
-
body: JSON.stringify({ model, input: text }),
|
|
179
|
-
signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),
|
|
180
|
-
});
|
|
181
|
-
if (!res.ok)
|
|
182
|
-
throw new Error(`OpenAI embedding error: ${res.statusText}`);
|
|
183
|
-
const json = (await res.json());
|
|
184
|
-
if (!json.data?.[0]?.embedding)
|
|
185
|
-
throw new Error('OpenAI returned empty embedding response');
|
|
186
|
-
return json.data[0].embedding;
|
|
187
|
-
}
|
|
188
|
-
default: {
|
|
189
|
-
throw new Error(`Unknown CACHLY_EMBED_PROVIDER="${EMBED_PROVIDER}".\n` +
|
|
190
|
-
'Supported: cachly (default), openai, mistral, cohere, ollama, gemini');
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
/** Returns true if the configured embedding provider has its required key/URL set. */
|
|
195
|
-
export function hasEmbedProvider() {
|
|
196
|
-
switch (EMBED_PROVIDER) {
|
|
197
|
-
case 'cachly': return !!embedConfig.jwt;
|
|
198
|
-
case 'mistral': return !!process.env.MISTRAL_API_KEY;
|
|
199
|
-
case 'cohere': return !!process.env.COHERE_API_KEY;
|
|
200
|
-
case 'ollama': return true; // OLLAMA_BASE_URL is optional (defaults to localhost)
|
|
201
|
-
case 'gemini': return !!process.env.GEMINI_API_KEY;
|
|
202
|
-
case 'openai': return !!process.env.OPENAI_API_KEY;
|
|
203
|
-
case 'none': return false;
|
|
204
|
-
default: return false;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
/** Returns a human-readable description of the current embed provider/model for error messages. */
|
|
208
|
-
export function embedProviderHint() {
|
|
209
|
-
const providerKeys = {
|
|
210
|
-
cachly: 'CACHLY_JWT (server-side, no extra key needed)',
|
|
211
|
-
openai: 'OPENAI_API_KEY',
|
|
212
|
-
mistral: 'MISTRAL_API_KEY',
|
|
213
|
-
cohere: 'COHERE_API_KEY',
|
|
214
|
-
ollama: 'OLLAMA_BASE_URL (optional, default: http://localhost:11434)',
|
|
215
|
-
gemini: 'GEMINI_API_KEY',
|
|
216
|
-
};
|
|
217
|
-
if (EMBED_PROVIDER === 'none') {
|
|
218
|
-
return 'No embedding provider configured. Set OPENAI_API_KEY, GEMINI_API_KEY, or another provider key in .env. Brain works without embedding.';
|
|
219
|
-
}
|
|
220
|
-
const key = providerKeys[EMBED_PROVIDER] ?? 'CACHLY_JWT';
|
|
221
|
-
return `CACHLY_EMBED_PROVIDER=${EMBED_PROVIDER} → requires ${key}`;
|
|
222
|
-
}
|
|
223
|
-
//# sourceMappingURL=embeddings.js.map
|
|
1
|
+
const t={apiUrl:process.env.CACHLY_API_URL??"https://api.cachly.dev",jwt:process.env.CACHLY_JWT??"",model:process.env.CACHLY_EMBED_MODEL??""};function m(r){t.jwt=r}const i=Number(process.env.CACHLY_EMBED_TIMEOUT_MS??8e3);function c(){return process.env.CACHLY_JWT?"cachly":process.env.OLLAMA_BASE_URL?"ollama":"none"}const a=(process.env.CACHLY_EMBED_PROVIDER??c()).toLowerCase();async function l(r){switch(a){case"cachly":{if(!t.jwt)throw new Error(`CACHLY_JWT not set.
|
|
2
|
+
|
|
3
|
+
The "cachly" provider uses server-side embeddings via the Cachly API.
|
|
4
|
+
Set CACHLY_JWT, or use another provider via CACHLY_EMBED_PROVIDER:
|
|
5
|
+
openai \u2192 OPENAI_API_KEY
|
|
6
|
+
gemini \u2192 GEMINI_API_KEY
|
|
7
|
+
ollama \u2192 OLLAMA_BASE_URL (local, no key needed)`);const o=`${t.apiUrl}/api/v1/embed`,n={text:r};t.model&&(n.model=t.model);const e=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.jwt}`},body:JSON.stringify(n),signal:AbortSignal.timeout(i)});if(!e.ok){const d=await e.text();throw new Error(`Cachly embed API error ${e.status}: ${d}`)}return(await e.json()).embedding}case"mistral":{const o=process.env.MISTRAL_API_KEY;if(!o)throw new Error("MISTRAL_API_KEY not set");const n=t.model!=="text-embedding-3-small"?t.model:"mistral-embed",e=await fetch("https://api.mistral.ai/v1/embeddings",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({model:n,input:[r]}),signal:AbortSignal.timeout(i)});if(!e.ok)throw new Error(`Mistral embedding error: ${e.statusText}`);const s=await e.json();if(!s.data?.[0]?.embedding)throw new Error("Mistral returned empty embedding response");return s.data[0].embedding}case"cohere":{const o=process.env.COHERE_API_KEY;if(!o)throw new Error("COHERE_API_KEY not set");const n=t.model!=="text-embedding-3-small"?t.model:"embed-english-v3.0",e=await fetch("https://api.cohere.com/v2/embed",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({model:n,texts:[r],input_type:"search_query",embedding_types:["float"]}),signal:AbortSignal.timeout(i)});if(!e.ok)throw new Error(`Cohere embedding error: ${e.statusText}`);return(await e.json()).embeddings.float[0]}case"ollama":{const o=process.env.OLLAMA_BASE_URL??"http://localhost:11434",n=t.model!=="text-embedding-3-small"?t.model:"nomic-embed-text",e=await fetch(`${o}/api/embeddings`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:n,prompt:r}),signal:AbortSignal.timeout(i)});if(!e.ok)throw new Error(`Ollama embedding error: ${e.statusText}`);return(await e.json()).embedding}case"gemini":{const o=process.env.GEMINI_API_KEY;if(!o)throw new Error("GEMINI_API_KEY not set");const n=t.model!=="text-embedding-3-small"?t.model:"text-embedding-004",e=await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${n}:embedContent?key=${o}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:`models/${n}`,content:{parts:[{text:r}]}}),signal:AbortSignal.timeout(i)});if(!e.ok)throw new Error(`Gemini embedding error: ${e.statusText}`);return(await e.json()).embedding.values}case"openai":{const o=process.env.OPENAI_API_KEY;if(!o)throw new Error(`OPENAI_API_KEY not set.
|
|
8
|
+
|
|
9
|
+
Set OPENAI_API_KEY in your .env, or switch provider:
|
|
10
|
+
CACHLY_EMBED_PROVIDER=gemini (+ GEMINI_API_KEY)
|
|
11
|
+
CACHLY_EMBED_PROVIDER=ollama (local, free)
|
|
12
|
+
CACHLY_EMBED_PROVIDER=cachly (server-side, no key needed)`);const n=t.model||"text-embedding-3-small",e=await fetch("https://api.openai.com/v1/embeddings",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({model:n,input:r}),signal:AbortSignal.timeout(i)});if(!e.ok)throw new Error(`OpenAI embedding error: ${e.statusText}`);const s=await e.json();if(!s.data?.[0]?.embedding)throw new Error("OpenAI returned empty embedding response");return s.data[0].embedding}default:throw new Error(`Unknown CACHLY_EMBED_PROVIDER="${a}".
|
|
13
|
+
Supported: cachly (default), openai, mistral, cohere, ollama, gemini`)}}function E(){switch(a){case"cachly":return!!t.jwt;case"mistral":return!!process.env.MISTRAL_API_KEY;case"cohere":return!!process.env.COHERE_API_KEY;case"ollama":return!0;case"gemini":return!!process.env.GEMINI_API_KEY;case"openai":return!!process.env.OPENAI_API_KEY;case"none":return!1;default:return!1}}function p(){const r={cachly:"CACHLY_JWT (server-side, no extra key needed)",openai:"OPENAI_API_KEY",mistral:"MISTRAL_API_KEY",cohere:"COHERE_API_KEY",ollama:"OLLAMA_BASE_URL (optional, default: http://localhost:11434)",gemini:"GEMINI_API_KEY"};if(a==="none")return"No embedding provider configured. Set OPENAI_API_KEY, GEMINI_API_KEY, or another provider key in .env. Brain works without embedding.";const o=r[a]??"CACHLY_JWT";return`CACHLY_EMBED_PROVIDER=${a} \u2192 requires ${o}`}export{a as EMBED_PROVIDER,l as computeEmbedding,t as embedConfig,p as embedProviderHint,E as hasEmbedProvider,m as setEmbedJwt};
|
|
@@ -1,113 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* ── Der Anlass, gemessen am 22.08.2026 ──────────────────────────────────────
|
|
5
|
-
*
|
|
6
|
-
* `ohneMarkierungen()` in eingaenge.ts filtert beim Einbetten die Zeichenkette
|
|
7
|
-
* `</what_worked>\n<what_failed>` aus dem Text. Der Kommentar dort sagt, viele
|
|
8
|
-
* Lektionen enthielten das "woertlich". Was er NICHT sagt: wo es herkommt.
|
|
9
|
-
*
|
|
10
|
-
* Es kommt beim SCHREIBEN herein. Wer `learn_from_attempts` aufruft und ein
|
|
11
|
-
* Feld mit dem falschen Tag schliesst, uebergibt den gesamten Rest — inklusive
|
|
12
|
-
* der Marke des naechsten Feldes — als Inhalt von `what_worked`. Das Ergebnis
|
|
13
|
-
* sieht aus wie eine gefuellte Lektion, hat aber ein leeres `what_failed` und
|
|
14
|
-
* einen Fliesstext mit Steuerzeichen darin.
|
|
15
|
-
*
|
|
16
|
-
* Belegt an derselben Lektion zweimal hintereinander, beide Male unbemerkt:
|
|
17
|
-
* die Rueckmeldung des Werkzeugs zeigte den verklebten Text an, ohne dass
|
|
18
|
-
* irgendetwas rot wurde.
|
|
19
|
-
*
|
|
20
|
-
* ── Warum hier und nicht beim Lesen ─────────────────────────────────────────
|
|
21
|
-
*
|
|
22
|
-
* Beim Lesen wird gefiltert, damit der Vektor nicht auf Feldnamen zeigt. Das
|
|
23
|
-
* ist richtig und bleibt. Es heilt aber nur die Einbettung — im Bestand steht
|
|
24
|
-
* der Text weiter falsch, `what_failed` bleibt leer, und das Briefing zeigt
|
|
25
|
-
* Steuerzeichen. Zwei Stellen fuer dasselbe Problem waeren die uebliche
|
|
26
|
-
* Fehlerklasse dieses Hauses: eine Information an zwei Orten gepflegt.
|
|
27
|
-
*
|
|
28
|
-
* Deshalb wird HIER repariert, an der einzigen Stelle, an der der Fehler
|
|
29
|
-
* entsteht.
|
|
30
|
-
*
|
|
31
|
-
* ── Was es ausdruecklich NICHT tut ──────────────────────────────────────────
|
|
32
|
-
*
|
|
33
|
-
* Es fasst Text NICHT an, der die Feldnamen bloss ERWAEHNT. "der what_failed-
|
|
34
|
-
* Text stand in what_worked" ist ein voellig normaler Satz in einer Lektion
|
|
35
|
-
* ueber genau diesen Fehler. Getrennt wird nur an einer echten Marke, also an
|
|
36
|
-
* spitzen Klammern.
|
|
37
|
-
*
|
|
38
|
-
* Und es wirft nie Text weg. Findet es keinen Platz fuer ein Stueck, bleibt
|
|
39
|
-
* es stehen, wo es war — nur ohne die Marke.
|
|
40
|
-
*/
|
|
41
|
-
/** Nur diese drei. `topic` und `outcome` sind kurz und kleben nicht. */
|
|
42
|
-
const FELDNAMEN = ['what_worked', 'what_failed', 'context'];
|
|
43
|
-
/**
|
|
44
|
-
* Eine Marke ist ein Feldname in spitzen Klammern — in allen Formen, die
|
|
45
|
-
* tatsaechlich vorkommen:
|
|
46
|
-
*
|
|
47
|
-
* </what_worked> schliessend
|
|
48
|
-
* <what_failed> oeffnend
|
|
49
|
-
* <parameter name="what_failed"> die Form, die den Fehler ausloest
|
|
50
|
-
*
|
|
51
|
-
* Ohne spitze Klammern ist es Fliesstext und bleibt unberuehrt.
|
|
52
|
-
*/
|
|
53
|
-
const MARKE = new RegExp(String.raw `<\/?(?:parameter\s+name\s*=\s*["']?)?(${FELDNAMEN.join('|')})["']?\s*\/?>`, 'gi');
|
|
54
|
-
/** Ist in diesem Text ueberhaupt eine Marke? Billig, fuer den Normalfall. */
|
|
55
|
-
export function hatFeldmarke(text) {
|
|
56
|
-
if (!text)
|
|
57
|
-
return false;
|
|
58
|
-
MARKE.lastIndex = 0;
|
|
59
|
-
return MARKE.test(text);
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Trennt verklebte Felder auf und gibt sie einzeln zurueck.
|
|
63
|
-
*
|
|
64
|
-
* Der Text VOR der ersten Marke bleibt in dem Feld, in dem er stand. Jedes
|
|
65
|
-
* Stueck dahinter geht in das Feld, dessen Marke davorstand — aber nur, wenn
|
|
66
|
-
* dieses Feld noch leer ist. Ein bereits gefuelltes Feld wird nie
|
|
67
|
-
* ueberschrieben; das Stueck haengt sich dann hinten an, damit nichts
|
|
68
|
-
* verlorengeht.
|
|
69
|
-
*/
|
|
70
|
-
export function repariereFelder(f) {
|
|
71
|
-
if (!hatFeldmarke(f.what_worked) && !hatFeldmarke(f.what_failed) && !hatFeldmarke(f.context)) {
|
|
72
|
-
return f;
|
|
73
|
-
}
|
|
74
|
-
const aus = {
|
|
75
|
-
what_worked: '',
|
|
76
|
-
what_failed: f.what_failed ?? '',
|
|
77
|
-
context: f.context ?? '',
|
|
78
|
-
};
|
|
79
|
-
// Nur what_worked wird zerlegt — dort landet der Rest, weil es das erste
|
|
80
|
-
// lange Feld ist. Die anderen beiden werden nur von Marken befreit.
|
|
81
|
-
const text = f.what_worked ?? '';
|
|
82
|
-
const stuecke = [];
|
|
83
|
-
let letzterIndex = 0;
|
|
84
|
-
let aktuellesZiel = 'what_worked';
|
|
85
|
-
MARKE.lastIndex = 0;
|
|
86
|
-
for (let m = MARKE.exec(text); m !== null; m = MARKE.exec(text)) {
|
|
87
|
-
stuecke.push({ ziel: aktuellesZiel, text: text.slice(letzterIndex, m.index) });
|
|
88
|
-
const genannt = m[1].toLowerCase();
|
|
89
|
-
// Eine SCHLIESSENDE Marke sagt nur "hier endet etwas" und nennt kein
|
|
90
|
-
// neues Ziel. Der Text danach gehoert dem naechsten genannten Feld —
|
|
91
|
-
// bis dahin bleibt er beim bisherigen.
|
|
92
|
-
aktuellesZiel = m[0].startsWith('</') ? aktuellesZiel : genannt;
|
|
93
|
-
letzterIndex = m.index + m[0].length;
|
|
94
|
-
}
|
|
95
|
-
stuecke.push({ ziel: aktuellesZiel, text: text.slice(letzterIndex) });
|
|
96
|
-
for (const s of stuecke) {
|
|
97
|
-
const t = s.text.trim();
|
|
98
|
-
if (!t)
|
|
99
|
-
continue;
|
|
100
|
-
if (aus[s.ziel])
|
|
101
|
-
aus[s.ziel] = `${aus[s.ziel]}\n\n${t}`;
|
|
102
|
-
else
|
|
103
|
-
aus[s.ziel] = t;
|
|
104
|
-
}
|
|
105
|
-
// Reste in den anderen beiden Feldern: Marke raus, Text behalten.
|
|
106
|
-
const entmarken = (s) => s.replace(MARKE, ' ').replace(/[ \t]{2,}/g, ' ').trim();
|
|
107
|
-
return {
|
|
108
|
-
what_worked: entmarken(aus.what_worked),
|
|
109
|
-
what_failed: aus.what_failed ? entmarken(aus.what_failed) : f.what_failed,
|
|
110
|
-
context: aus.context ? entmarken(aus.context) : f.context,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
//# sourceMappingURL=feldreparatur.js.map
|
|
1
|
+
const d=["what_worked","what_failed","context"],a=new RegExp(String.raw`<\/?(?:parameter\s+name\s*=\s*["']?)?(${d.join("|")})["']?\s*\/?>`,"gi");function w(t){return t?(a.lastIndex=0,a.test(t)):!1}function x(t){if(!w(t.what_worked)&&!w(t.what_failed)&&!w(t.context))return t;const n={what_worked:"",what_failed:t.what_failed??"",context:t.context??""},o=t.what_worked??"",i=[];let s=0,l="what_worked";a.lastIndex=0;for(let e=a.exec(o);e!==null;e=a.exec(o)){i.push({ziel:l,text:o.slice(s,e.index)});const r=e[1].toLowerCase();l=e[0].startsWith("</")?l:r,s=e.index+e[0].length}i.push({ziel:l,text:o.slice(s)});for(const e of i){const r=e.text.trim();r&&(n[e.ziel]?n[e.ziel]=`${n[e.ziel]}
|
|
2
|
+
|
|
3
|
+
${r}`:n[e.ziel]=r)}const c=e=>e.replace(a," ").replace(/[ \t]{2,}/g," ").trim();return{what_worked:c(n.what_worked),what_failed:n.what_failed?c(n.what_failed):t.what_failed,context:n.context?c(n.context):t.context}}export{w as hatFeldmarke,x as repariereFelder};
|
|
@@ -1,108 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* The very first `brain_from_git` run is a new user's first contact with the
|
|
6
|
-
* Brain, and it decides whether they ever reach the "aha". The response must,
|
|
7
|
-
* in one message:
|
|
8
|
-
* (a) summarize what was just learned (counts by category + severity),
|
|
9
|
-
* (b) prove recall already works — show the top hit of a real search that
|
|
10
|
-
* ran against a topic seeded seconds ago,
|
|
11
|
-
* (c) hand the user 2-3 copy-pasteable next queries, and
|
|
12
|
-
* (d) degrade gracefully for empty/small repos (clear guidance, not empty stats).
|
|
13
|
-
*
|
|
14
|
-
* Pure string building — no I/O, no schema/tool changes — so the onboarding
|
|
15
|
-
* bench and its CI test can assert the exact first-contact response shape.
|
|
16
|
-
*/
|
|
17
|
-
/** Below this many ingested lessons we treat the repo as "small" and add growth guidance. */
|
|
18
|
-
export const SMALL_SEED_THRESHOLD = 5;
|
|
19
|
-
export function formatSeedDuration(ms) {
|
|
20
|
-
if (ms < 1000)
|
|
21
|
-
return `${Math.max(ms, 1)}ms`;
|
|
22
|
-
if (ms < 60_000)
|
|
23
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
24
|
-
return `${Math.floor(ms / 60_000)}m ${Math.round((ms % 60_000) / 1000)}s`;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Turn seeded topics (`category:some-domain`) into up to `max` copy-pasteable
|
|
28
|
-
* `smart_recall` calls a brand-new user can paste as their literal next message.
|
|
29
|
-
* Deduplicates by domain so three "fix:*" topics don't yield three identical queries.
|
|
30
|
-
*/
|
|
31
|
-
export function suggestRecallQueries(topics, instanceId, max = 3) {
|
|
32
|
-
const seen = new Set();
|
|
33
|
-
const out = [];
|
|
34
|
-
for (const topic of topics) {
|
|
35
|
-
const domain = (topic.includes(':') ? topic.slice(topic.indexOf(':') + 1) : topic)
|
|
36
|
-
.replace(/[-_]+/g, ' ')
|
|
37
|
-
.trim();
|
|
38
|
-
if (!domain || seen.has(domain))
|
|
39
|
-
continue;
|
|
40
|
-
seen.add(domain);
|
|
41
|
-
out.push(`smart_recall(instance_id="${instanceId}", query="${domain}")`);
|
|
42
|
-
if (out.length >= max)
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
45
|
-
return out;
|
|
46
|
-
}
|
|
47
|
-
/** Steps that take an empty/tiny Brain to a first recall hit in under a minute. */
|
|
48
|
-
function growthSteps(instanceId) {
|
|
49
|
-
return [
|
|
50
|
-
` 1. \`brain_seed_starter(instance_id="${instanceId}")\` — load 16 curated engineering lessons instantly`,
|
|
51
|
-
` 2. \`smart_recall(instance_id="${instanceId}", query="docker build slow")\` — see your first recall hit`,
|
|
52
|
-
` 3. \`learn_from_attempts(...)\` after your next fix — the Brain grows with every lesson from here on`,
|
|
53
|
-
];
|
|
54
|
-
}
|
|
55
|
-
export function buildFirstContactReport(r) {
|
|
56
|
-
const lines = [];
|
|
57
|
-
lines.push(`🔁 **brain_from_git: ${r.repoDir}**`);
|
|
58
|
-
if (r.isIncremental && r.lastSha) {
|
|
59
|
-
lines.push(`🔄 Incremental mode — only new commits since \`${r.lastSha.slice(0, 8)}\` were processed`);
|
|
60
|
-
}
|
|
61
|
-
lines.push('');
|
|
62
|
-
lines.push(`📂 Branch: \`${r.revRange}\` | Processed: **${r.processed}** commits | ` +
|
|
63
|
-
`Ingested: **${r.ingested}** lessons | Skipped: ${r.skipped} | ` +
|
|
64
|
-
`⏱️ Seeding took ${formatSeedDuration(r.durationMs)}`);
|
|
65
|
-
lines.push('');
|
|
66
|
-
// ── Empty repo / nothing ingested — guidance instead of empty stats ─────────
|
|
67
|
-
if (r.ingested === 0) {
|
|
68
|
-
if (r.emptyReason)
|
|
69
|
-
lines.push(`⚠️ ${r.emptyReason}`);
|
|
70
|
-
lines.push(`🌱 **Nothing to learn from this git history yet** — normal for fresh, shallow, or squash-only repos.`);
|
|
71
|
-
lines.push(`Get to your first recall in under a minute:`);
|
|
72
|
-
lines.push(...growthSteps(r.instanceId));
|
|
73
|
-
return lines.join('\n');
|
|
74
|
-
}
|
|
75
|
-
// ── (a) Seeded summary — what the Brain just learned ────────────────────────
|
|
76
|
-
lines.push(`**What your Brain just learned (by category):**`);
|
|
77
|
-
lines.push(r.categories.map(([k, v]) => ` • **${k}** (${v})`).join('\n'));
|
|
78
|
-
if (r.severities && r.severities.length > 0) {
|
|
79
|
-
lines.push(`**By severity:** ${r.severities.map(([k, v]) => `${k} ${v}`).join(' · ')}`);
|
|
80
|
-
}
|
|
81
|
-
// ── (b) Proof of value — a real recall against a just-seeded topic ──────────
|
|
82
|
-
if (r.proof) {
|
|
83
|
-
lines.push('');
|
|
84
|
-
lines.push(`🎯 **Proof — your first recall already works.** We just searched your fresh Brain for:`);
|
|
85
|
-
lines.push(` "${r.proof.query}"`);
|
|
86
|
-
lines.push(` → Top hit: **${r.proof.topic}** — ${r.proof.snippet}`);
|
|
87
|
-
}
|
|
88
|
-
// ── (c) Suggested next queries — copy-pasteable ──────────────────────────────
|
|
89
|
-
if (r.suggestedQueries.length > 0) {
|
|
90
|
-
lines.push('');
|
|
91
|
-
lines.push(`**Try these next (copy-paste):**`);
|
|
92
|
-
for (const q of r.suggestedQueries)
|
|
93
|
-
lines.push(` • \`${q}\``);
|
|
94
|
-
}
|
|
95
|
-
// ── (d) Small-repo guidance ──────────────────────────────────────────────────
|
|
96
|
-
if (r.ingested < SMALL_SEED_THRESHOLD) {
|
|
97
|
-
lines.push('');
|
|
98
|
-
lines.push(`🌱 **Small git history — only ${r.ingested} lesson${r.ingested === 1 ? '' : 's'} so far.** Top it up:`);
|
|
99
|
-
lines.push(...growthSteps(r.instanceId));
|
|
100
|
-
}
|
|
101
|
-
lines.push('');
|
|
102
|
-
lines.push(`💡 New lessons are stored with confidence 0.55 (auto-inferred).`);
|
|
103
|
-
lines.push(`💡 As you confirm them via \`learn_from_attempts\`, confidence rises automatically.`);
|
|
104
|
-
lines.push(`🔍 Explore: \`brain_search(query="fix")\` | \`ckg_inspect(concept="deploy")\``);
|
|
105
|
-
lines.push(`💾 Next run will automatically continue from the latest commit (incremental mode).`);
|
|
106
|
-
return lines.join('\n');
|
|
107
|
-
}
|
|
108
|
-
//# sourceMappingURL=first-contact.js.map
|
|
1
|
+
const p=5;function u(e){return e<1e3?`${Math.max(e,1)}ms`:e<6e4?`${(e/1e3).toFixed(1)}s`:`${Math.floor(e/6e4)}m ${Math.round(e%6e4/1e3)}s`}function c(e,s,t=3){const o=new Set,r=[];for(const n of e){const i=(n.includes(":")?n.slice(n.indexOf(":")+1):n).replace(/[-_]+/g," ").trim();if(!(!i||o.has(i))&&(o.add(i),r.push(`smart_recall(instance_id="${s}", query="${i}")`),r.length>=t))break}return r}function a(e){return[` 1. \`brain_seed_starter(instance_id="${e}")\` \u2014 load 16 curated engineering lessons instantly`,` 2. \`smart_recall(instance_id="${e}", query="docker build slow")\` \u2014 see your first recall hit`," 3. `learn_from_attempts(...)` after your next fix \u2014 the Brain grows with every lesson from here on"]}function h(e){const s=[];if(s.push(`\u{1F501} **brain_from_git: ${e.repoDir}**`),e.isIncremental&&e.lastSha&&s.push(`\u{1F504} Incremental mode \u2014 only new commits since \`${e.lastSha.slice(0,8)}\` were processed`),s.push(""),s.push(`\u{1F4C2} Branch: \`${e.revRange}\` | Processed: **${e.processed}** commits | Ingested: **${e.ingested}** lessons | Skipped: ${e.skipped} | \u23F1\uFE0F Seeding took ${u(e.durationMs)}`),s.push(""),e.ingested===0)return e.emptyReason&&s.push(`\u26A0\uFE0F ${e.emptyReason}`),s.push("\u{1F331} **Nothing to learn from this git history yet** \u2014 normal for fresh, shallow, or squash-only repos."),s.push("Get to your first recall in under a minute:"),s.push(...a(e.instanceId)),s.join(`
|
|
2
|
+
`);if(s.push("**What your Brain just learned (by category):**"),s.push(e.categories.map(([t,o])=>` \u2022 **${t}** (${o})`).join(`
|
|
3
|
+
`)),e.severities&&e.severities.length>0&&s.push(`**By severity:** ${e.severities.map(([t,o])=>`${t} ${o}`).join(" \xB7 ")}`),e.proof&&(s.push(""),s.push("\u{1F3AF} **Proof \u2014 your first recall already works.** We just searched your fresh Brain for:"),s.push(` "${e.proof.query}"`),s.push(` \u2192 Top hit: **${e.proof.topic}** \u2014 ${e.proof.snippet}`)),e.suggestedQueries.length>0){s.push(""),s.push("**Try these next (copy-paste):**");for(const t of e.suggestedQueries)s.push(` \u2022 \`${t}\``)}return e.ingested<5&&(s.push(""),s.push(`\u{1F331} **Small git history \u2014 only ${e.ingested} lesson${e.ingested===1?"":"s"} so far.** Top it up:`),s.push(...a(e.instanceId))),s.push(""),s.push("\u{1F4A1} New lessons are stored with confidence 0.55 (auto-inferred)."),s.push("\u{1F4A1} As you confirm them via `learn_from_attempts`, confidence rises automatically."),s.push('\u{1F50D} Explore: `brain_search(query="fix")` | `ckg_inspect(concept="deploy")`'),s.push("\u{1F4BE} Next run will automatically continue from the latest commit (incremental mode)."),s.join(`
|
|
4
|
+
`)}export{p as SMALL_SEED_THRESHOLD,h as buildFirstContactReport,u as formatSeedDuration,c as suggestRecallQueries};
|
|
@@ -1,41 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* can call them without risking the 3s onboarding budget.
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Finds the "Proof — your first recall already works" block inside a
|
|
9
|
-
* `brain_from_git` result text and returns it verbatim (the block runs from
|
|
10
|
-
* the matching line up to the next blank line). Returns `null` when the text
|
|
11
|
-
* carries no such block — never invents a proof.
|
|
12
|
-
*/
|
|
13
|
-
export function extractFirstRecallProof(text) {
|
|
14
|
-
if (!text)
|
|
15
|
-
return null;
|
|
16
|
-
const lines = text.split('\n');
|
|
17
|
-
const startIdx = lines.findIndex((line) => /first recall already works/i.test(line));
|
|
18
|
-
if (startIdx === -1)
|
|
19
|
-
return null;
|
|
20
|
-
const block = [];
|
|
21
|
-
for (let i = startIdx; i < lines.length; i++) {
|
|
22
|
-
if (i > startIdx && lines[i].trim() === '')
|
|
23
|
-
break;
|
|
24
|
-
block.push(lines[i]);
|
|
25
|
-
}
|
|
26
|
-
const joined = block.join('\n').trim();
|
|
27
|
-
return joined || null;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Renders the first-recall proof for the seed path: a real recall hit
|
|
31
|
-
* against the just-seeded starter corpus, shown as evidence instead of a
|
|
32
|
-
* bare "seeded ✓".
|
|
33
|
-
*/
|
|
34
|
-
export function renderFirstRecallProof(topic, what) {
|
|
35
|
-
const snippet = what.trim();
|
|
36
|
-
return [
|
|
37
|
-
'🎯 Proof — your first recall already works.',
|
|
38
|
-
` Top hit: **${topic}** — ${snippet}`,
|
|
39
|
-
].join('\n');
|
|
40
|
-
}
|
|
41
|
-
//# sourceMappingURL=first-recall-proof.js.map
|
|
1
|
+
function l(o){if(!o)return null;const n=o.split(`
|
|
2
|
+
`),t=n.findIndex(r=>/first recall already works/i.test(r));if(t===-1)return null;const e=[];for(let r=t;r<n.length&&!(r>t&&n[r].trim()==="");r++)e.push(n[r]);return e.join(`
|
|
3
|
+
`).trim()||null}function s(o,n){const t=n.trim();return["\u{1F3AF} Proof \u2014 your first recall already works.",` Top hit: **${o}** \u2014 ${t}`].join(`
|
|
4
|
+
`)}export{l as extractFirstRecallProof,s as renderFirstRecallProof};
|
|
@@ -1,48 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// Milestones like first_call_success were guarded by an in-memory flag only —
|
|
4
|
-
// but ambient hooks spawn a FRESH server process per prompt, so every hook
|
|
5
|
-
// invocation re-sent "first call" and the backend saw 66 "first" calls from
|
|
6
|
-
// one user (and Telegram pinged the founder on every one). Persisting the
|
|
7
|
-
// marker in ~/.cachly/ makes "first" mean first-on-this-machine across
|
|
8
|
-
// process restarts. Best-effort: any filesystem problem degrades to the old
|
|
9
|
-
// in-memory behaviour (the backend now dedupes too), never throws.
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
|
-
import { homedir } from 'node:os';
|
|
12
|
-
import { dirname, join } from 'node:path';
|
|
13
|
-
export function defaultMilestonePath() {
|
|
14
|
-
return join(homedir(), '.cachly', 'funnel-milestones.json');
|
|
15
|
-
}
|
|
16
|
-
function readAll(path) {
|
|
17
|
-
try {
|
|
18
|
-
if (!existsSync(path))
|
|
19
|
-
return {};
|
|
20
|
-
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
21
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
22
|
-
return parsed;
|
|
23
|
-
}
|
|
24
|
-
return {};
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return {}; // corrupt file → treat as empty, next mark rewrites it
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/** True when this milestone was already sent from this machine. */
|
|
31
|
-
export function milestoneSent(name, path = defaultMilestonePath()) {
|
|
32
|
-
return name in readAll(path);
|
|
33
|
-
}
|
|
34
|
-
/** Record a milestone (idempotent). Never throws. */
|
|
35
|
-
export function markMilestoneSent(name, path = defaultMilestonePath()) {
|
|
36
|
-
try {
|
|
37
|
-
const all = readAll(path);
|
|
38
|
-
if (name in all)
|
|
39
|
-
return;
|
|
40
|
-
all[name] = new Date().toISOString();
|
|
41
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
42
|
-
writeFileSync(path, JSON.stringify(all, null, 2) + '\n', 'utf-8');
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
/* best-effort — the server-side dedup is the safety net */
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
//# sourceMappingURL=funnel-milestones.js.map
|
|
1
|
+
import{existsSync as o,mkdirSync as c,readFileSync as u,writeFileSync as f}from"node:fs";import{homedir as l}from"node:os";import{dirname as s,join as a}from"node:path";function n(){return a(l(),".cachly","funnel-milestones.json")}function i(t){try{if(!o(t))return{};const r=JSON.parse(u(t,"utf-8"));return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}catch{return{}}}function d(t,r=n()){return t in i(r)}function p(t,r=n()){try{const e=i(r);if(t in e)return;e[t]=new Date().toISOString(),c(s(r),{recursive:!0}),f(r,JSON.stringify(e,null,2)+`
|
|
2
|
+
`,"utf-8")}catch{}}export{n as defaultMilestonePath,p as markMilestoneSent,d as milestoneSent};
|