@cachly-dev/mcp-server 0.10.129 → 0.10.131

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +1 -7
  3. package/dist/packages/telegram-notify/client.js +2 -75
  4. package/dist/src/ambient-cli.js +7 -253
  5. package/dist/src/ambient-deps.js +1 -55
  6. package/dist/src/ambient-hooks.js +5 -278
  7. package/dist/src/ambient-ledger.js +6 -95
  8. package/dist/src/ambient-memory.js +1 -67
  9. package/dist/src/ambient-recall.js +1 -221
  10. package/dist/src/aussetzer.js +1 -225
  11. package/dist/src/auth.js +9 -140
  12. package/dist/src/autor-abzeichen.js +1 -56
  13. package/dist/src/autor.js +1 -136
  14. package/dist/src/bauabdruck.js +1 -93
  15. package/dist/src/bedeutung.js +2 -323
  16. package/dist/src/bench/cachly-bench.js +4 -249
  17. package/dist/src/bench/external-corpus.js +4 -135
  18. package/dist/src/bench/fixtures.js +1 -225
  19. package/dist/src/brain-export.js +16 -119
  20. package/dist/src/brain-watch-hook.js +8 -86
  21. package/dist/src/cachly-url.js +1 -21
  22. package/dist/src/ckg.js +1 -108
  23. package/dist/src/cls-hook.js +8 -94
  24. package/dist/src/confidence.js +1 -50
  25. package/dist/src/credentials.js +1 -85
  26. package/dist/src/deutsch.js +1 -223
  27. package/dist/src/doctor.js +2 -248
  28. package/dist/src/editor.js +1 -63
  29. package/dist/src/eingaenge-nachruesten.js +1 -290
  30. package/dist/src/eingaenge.js +2 -260
  31. package/dist/src/embeddings.js +13 -223
  32. package/dist/src/feldreparatur.js +3 -113
  33. package/dist/src/first-contact.js +4 -108
  34. package/dist/src/first-recall-proof.js +4 -41
  35. package/dist/src/funnel-milestones.js +2 -48
  36. package/dist/src/geliefert.js +1 -114
  37. package/dist/src/handlers/advanced.js +38 -537
  38. package/dist/src/handlers/brain.js +69 -4397
  39. package/dist/src/handlers/cache.js +59 -638
  40. package/dist/src/handlers/context.js +17 -182
  41. package/dist/src/handlers/fedbrain.js +40 -1436
  42. package/dist/src/handlers/instances.js +16 -201
  43. package/dist/src/handlers/roadmap.js +13 -175
  44. package/dist/src/handlers/share.js +19 -530
  45. package/dist/src/handlers/syndicate.js +38 -904
  46. package/dist/src/handlers/team.js +89 -1865
  47. package/dist/src/handlers/viz.js +3 -148
  48. package/dist/src/index.js +321 -4281
  49. package/dist/src/interferenz.js +1 -141
  50. package/dist/src/kostprobe.js +7 -125
  51. package/dist/src/lesson-preview.js +2 -133
  52. package/dist/src/namespace.js +1 -128
  53. package/dist/src/notifier.js +1 -6
  54. package/dist/src/rangfolge-stellschrauben.js +1 -0
  55. package/dist/src/rangfolge.js +1 -249
  56. package/dist/src/raumkorrektur.js +1 -197
  57. package/dist/src/recall-tiefe.js +1 -42
  58. package/dist/src/rerank.js +1 -154
  59. package/dist/src/resonanz.js +1 -140
  60. package/dist/src/search.js +1 -2104
  61. package/dist/src/seltenheitsbestand.js +1 -92
  62. package/dist/src/speicherlage.js +1 -84
  63. package/dist/src/spuren.js +1 -150
  64. package/dist/src/starter-corpus.js +1 -165
  65. package/dist/src/telemetry-types.js +0 -13
  66. package/dist/src/tools.js +10 -2465
  67. package/dist/src/toolspecs.js +1 -121
  68. package/dist/src/upgrade-nudge.js +1 -29
  69. package/dist/src/utils.js +1 -89
  70. package/dist/src/versuch.js +1 -264
  71. package/dist/src/vorspann.js +1 -146
  72. package/dist/src/wartung/vektoren-verdichten.js +1 -200
  73. package/dist/src/werkzeug-nutzung.js +1 -72
  74. package/dist/src/wertbeitrag.js +1 -203
  75. package/dist/src/wirkungsspur.js +1 -0
  76. package/glama.json +1 -1
  77. package/package.json +8 -7
  78. package/server.json +3 -3
  79. package/dist/index.d.ts +0 -1
  80. package/dist/index.js.map +0 -1
  81. package/dist/packages/telegram-notify/client.d.ts +0 -19
  82. package/dist/packages/telegram-notify/client.js.map +0 -1
  83. package/dist/src/ambient-cli.d.ts +0 -101
  84. package/dist/src/ambient-cli.js.map +0 -1
  85. package/dist/src/ambient-deps.d.ts +0 -26
  86. package/dist/src/ambient-deps.js.map +0 -1
  87. package/dist/src/ambient-hooks.d.ts +0 -137
  88. package/dist/src/ambient-hooks.js.map +0 -1
  89. package/dist/src/ambient-ledger.d.ts +0 -19
  90. package/dist/src/ambient-ledger.js.map +0 -1
  91. package/dist/src/ambient-memory.d.ts +0 -12
  92. package/dist/src/ambient-memory.js.map +0 -1
  93. package/dist/src/ambient-recall.d.ts +0 -127
  94. package/dist/src/ambient-recall.js.map +0 -1
  95. package/dist/src/aussetzer.d.ts +0 -156
  96. package/dist/src/aussetzer.js.map +0 -1
  97. package/dist/src/auth.d.ts +0 -43
  98. package/dist/src/auth.js.map +0 -1
  99. package/dist/src/autor-abzeichen.d.ts +0 -41
  100. package/dist/src/autor-abzeichen.js.map +0 -1
  101. package/dist/src/autor.d.ts +0 -101
  102. package/dist/src/autor.js.map +0 -1
  103. package/dist/src/bauabdruck-daten.generiert.json +0 -5
  104. package/dist/src/bauabdruck.d.ts +0 -38
  105. package/dist/src/bauabdruck.js.map +0 -1
  106. package/dist/src/bedeutung.d.ts +0 -168
  107. package/dist/src/bedeutung.js.map +0 -1
  108. package/dist/src/bench/cachly-bench.d.ts +0 -61
  109. package/dist/src/bench/cachly-bench.js.map +0 -1
  110. package/dist/src/bench/external-corpus.d.ts +0 -44
  111. package/dist/src/bench/external-corpus.js.map +0 -1
  112. package/dist/src/bench/fixtures.d.ts +0 -37
  113. package/dist/src/bench/fixtures.js.map +0 -1
  114. package/dist/src/brain-export.d.ts +0 -64
  115. package/dist/src/brain-export.js.map +0 -1
  116. package/dist/src/brain-watch-hook.d.ts +0 -21
  117. package/dist/src/brain-watch-hook.js.map +0 -1
  118. package/dist/src/cachly-url.d.ts +0 -68
  119. package/dist/src/cachly-url.js.map +0 -1
  120. package/dist/src/ckg.d.ts +0 -54
  121. package/dist/src/ckg.js.map +0 -1
  122. package/dist/src/cls-hook.d.ts +0 -15
  123. package/dist/src/cls-hook.js.map +0 -1
  124. package/dist/src/confidence.d.ts +0 -19
  125. package/dist/src/confidence.js.map +0 -1
  126. package/dist/src/credentials.d.ts +0 -37
  127. package/dist/src/credentials.js.map +0 -1
  128. package/dist/src/deutsch.d.ts +0 -98
  129. package/dist/src/deutsch.js.map +0 -1
  130. package/dist/src/doctor.d.ts +0 -47
  131. package/dist/src/doctor.js.map +0 -1
  132. package/dist/src/editor.d.ts +0 -9
  133. package/dist/src/editor.js.map +0 -1
  134. package/dist/src/eingaenge-nachruesten.d.ts +0 -41
  135. package/dist/src/eingaenge-nachruesten.js.map +0 -1
  136. package/dist/src/eingaenge.d.ts +0 -159
  137. package/dist/src/eingaenge.js.map +0 -1
  138. package/dist/src/embeddings.d.ts +0 -48
  139. package/dist/src/embeddings.js.map +0 -1
  140. package/dist/src/feldreparatur.d.ts +0 -58
  141. package/dist/src/feldreparatur.js.map +0 -1
  142. package/dist/src/first-contact.d.ts +0 -56
  143. package/dist/src/first-contact.js.map +0 -1
  144. package/dist/src/first-recall-proof.d.ts +0 -19
  145. package/dist/src/first-recall-proof.js.map +0 -1
  146. package/dist/src/funnel-milestones.d.ts +0 -5
  147. package/dist/src/funnel-milestones.js.map +0 -1
  148. package/dist/src/geliefert.d.ts +0 -63
  149. package/dist/src/geliefert.js.map +0 -1
  150. package/dist/src/handlers/advanced.d.ts +0 -6
  151. package/dist/src/handlers/advanced.js.map +0 -1
  152. package/dist/src/handlers/brain.d.ts +0 -41
  153. package/dist/src/handlers/brain.js.map +0 -1
  154. package/dist/src/handlers/cache.d.ts +0 -6
  155. package/dist/src/handlers/cache.js.map +0 -1
  156. package/dist/src/handlers/context.d.ts +0 -6
  157. package/dist/src/handlers/context.js.map +0 -1
  158. package/dist/src/handlers/fedbrain.d.ts +0 -18
  159. package/dist/src/handlers/fedbrain.js.map +0 -1
  160. package/dist/src/handlers/instances.d.ts +0 -33
  161. package/dist/src/handlers/instances.js.map +0 -1
  162. package/dist/src/handlers/roadmap.d.ts +0 -6
  163. package/dist/src/handlers/roadmap.js.map +0 -1
  164. package/dist/src/handlers/share.d.ts +0 -8
  165. package/dist/src/handlers/share.js.map +0 -1
  166. package/dist/src/handlers/syndicate.d.ts +0 -6
  167. package/dist/src/handlers/syndicate.js.map +0 -1
  168. package/dist/src/handlers/team.d.ts +0 -45
  169. package/dist/src/handlers/team.js.map +0 -1
  170. package/dist/src/handlers/viz.d.ts +0 -6
  171. package/dist/src/handlers/viz.js.map +0 -1
  172. package/dist/src/index.d.ts +0 -93
  173. package/dist/src/index.js.map +0 -1
  174. package/dist/src/interferenz.d.ts +0 -111
  175. package/dist/src/interferenz.js.map +0 -1
  176. package/dist/src/kostprobe.d.ts +0 -25
  177. package/dist/src/kostprobe.js.map +0 -1
  178. package/dist/src/lesson-preview.d.ts +0 -81
  179. package/dist/src/lesson-preview.js.map +0 -1
  180. package/dist/src/namespace.d.ts +0 -20
  181. package/dist/src/namespace.js.map +0 -1
  182. package/dist/src/notifier.d.ts +0 -5
  183. package/dist/src/notifier.js.map +0 -1
  184. package/dist/src/rangfolge.d.ts +0 -205
  185. package/dist/src/rangfolge.js.map +0 -1
  186. package/dist/src/raumkorrektur.d.ts +0 -100
  187. package/dist/src/raumkorrektur.js.map +0 -1
  188. package/dist/src/recall-tiefe.d.ts +0 -48
  189. package/dist/src/recall-tiefe.js.map +0 -1
  190. package/dist/src/rerank.d.ts +0 -48
  191. package/dist/src/rerank.js.map +0 -1
  192. package/dist/src/resonanz.d.ts +0 -85
  193. package/dist/src/resonanz.js.map +0 -1
  194. package/dist/src/search.d.ts +0 -153
  195. package/dist/src/search.js.map +0 -1
  196. package/dist/src/seltenheitsbestand.d.ts +0 -53
  197. package/dist/src/seltenheitsbestand.js.map +0 -1
  198. package/dist/src/speicherlage.d.ts +0 -51
  199. package/dist/src/speicherlage.js.map +0 -1
  200. package/dist/src/spuren.d.ts +0 -97
  201. package/dist/src/spuren.js.map +0 -1
  202. package/dist/src/starter-corpus.d.ts +0 -28
  203. package/dist/src/starter-corpus.js.map +0 -1
  204. package/dist/src/telemetry-types.d.ts +0 -87
  205. package/dist/src/telemetry-types.js.map +0 -1
  206. package/dist/src/tools.d.ts +0 -2933
  207. package/dist/src/tools.js.map +0 -1
  208. package/dist/src/toolspecs.d.ts +0 -43
  209. package/dist/src/toolspecs.js.map +0 -1
  210. package/dist/src/upgrade-nudge.d.ts +0 -15
  211. package/dist/src/upgrade-nudge.js.map +0 -1
  212. package/dist/src/utils.d.ts +0 -34
  213. package/dist/src/utils.js.map +0 -1
  214. package/dist/src/versuch.d.ts +0 -187
  215. package/dist/src/versuch.js.map +0 -1
  216. package/dist/src/vorspann.d.ts +0 -73
  217. package/dist/src/vorspann.js.map +0 -1
  218. package/dist/src/wartung/vektoren-verdichten.d.ts +0 -50
  219. package/dist/src/wartung/vektoren-verdichten.js.map +0 -1
  220. package/dist/src/werkzeug-nutzung.d.ts +0 -62
  221. package/dist/src/werkzeug-nutzung.js.map +0 -1
  222. package/dist/src/wertbeitrag.d.ts +0 -148
  223. package/dist/src/wertbeitrag.js.map +0 -1
@@ -1,223 +1,13 @@
1
- /**
2
- * ┌──────────────────────────────────────────────────────────────────────┐
3
- * │ EMBEDDING PROVIDER — pluggable, client-side first │
4
- * │ │
5
- * │ Auto-detects from env vars. To force a provider, set: │
6
- * │ CACHLY_EMBED_PROVIDER=openai (+ OPENAI_API_KEY) │
7
- * │ CACHLY_EMBED_PROVIDER=gemini (+ GEMINI_API_KEY) │
8
- * │ CACHLY_EMBED_PROVIDER=mistral (+ MISTRAL_API_KEY) │
9
- * │ CACHLY_EMBED_PROVIDER=cohere (+ COHERE_API_KEY) │
10
- * │ CACHLY_EMBED_PROVIDER=ollama (+ OLLAMA_BASE_URL, local) │
11
- * │ CACHLY_EMBED_PROVIDER=cachly (server-side fallback, no key) │
12
- * │ │
13
- * │ Priority: openai > gemini > mistral > cohere > ollama > cachly │
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
- * Verklebte Felder trennen, BEVOR eine Lektion geschrieben wird.
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
- * First-contact response builder for `brain_from_git` (roadmap P1-5:
3
- * "Onboarding-Magie messen & polieren").
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
- * The Aha-moment of the first minute (GROW-013): a new user must see the
3
- * Brain recall something real from their OWN repo, not just a lesson count.
4
- * Both functions are pure — no I/O, no network, no state — so setup/autopilot
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
- // Persistent once-per-machine funnel milestones.
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};