@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.
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,1436 +1,40 @@
1
- import { createHmac } from 'node:crypto';
2
- import { ckgSlug, extractProblemConcept, ckgUpsertNode, ckgUpdateEdge, ckgUpsertPersonNode, ckgUpsertFileNode, ckgRecordCollaboration } from '../ckg.js';
3
- import { safeJsonParse, normalizeGitPath } from '../utils.js';
4
- import { buildClsPostCommitHook } from '../cls-hook.js';
5
- import { installBrainWatchHook } from '../brain-watch-hook.js';
6
- import { keywordSearch } from '../search.js';
7
- import { buildFirstContactReport, suggestRecallQueries } from '../first-contact.js';
8
- // Last brain_from_git category counts — set after each run so index.ts can include them in telemetry
9
- export let _lastBrainFromGitCounts = null;
10
- // Last brain_from_ci counts — set after each run so index.ts can include them in telemetry
11
- export let _lastBrainFromCiCounts = null;
12
- // Git concurrency semaphore (for brain_from_git parallel workers)
13
- let _gitSemCount = 0;
14
- const _GIT_SEM_MAX = 10;
15
- const _gitSemQueue = [];
16
- function _gitSemAcquire() {
17
- if (_gitSemCount < _GIT_SEM_MAX) {
18
- _gitSemCount++;
19
- return Promise.resolve();
20
- }
21
- return new Promise(resolve => _gitSemQueue.push(resolve));
22
- }
23
- function _gitSemRelease() {
24
- const next = _gitSemQueue.shift();
25
- if (next) {
26
- next();
27
- }
28
- else {
29
- _gitSemCount--;
30
- }
31
- }
32
- export const FEDBRAIN_TOOL_NAMES = new Set([
33
- 'madc_deliberate', 'cls_ingest', 'cls_install_hooks', 'fedbrain_contribute', 'fedbrain_search',
34
- 'fedbrain_confirm', 'fedbrain_status', 'brain_federate', 'crystal_view', 'compact_recover',
35
- 'brain_from_git', 'brain_from_ci', 'brain_predict_failures',
36
- 'brain_contribute_signal', 'brain_import_meta', 'brain_watch',
37
- ]);
38
- export async function handleFedbrainTool(name, args, getConnection, apiFetch) {
39
- switch (name) {
40
- case 'madc_deliberate': {
41
- const { instance_id, topic } = args;
42
- const redis = await getConnection(instance_id);
43
- const historyRaw = await redis.lrange(`cachly:lessons:${topic}`, 0, -1);
44
- const history = historyRaw.map(r => { try {
45
- return JSON.parse(r);
46
- }
47
- catch {
48
- return null;
49
- } }).filter(Boolean);
50
- if (history.length < 2) {
51
- return [
52
- `🗳️ **MADC: "${topic}"**`, '',
53
- `Not enough history for deliberation (need ≥ 2 entries, found ${history.length}).`,
54
- '', `Call \`learn_from_attempts\` with conflicting outcomes to trigger deliberation.`,
55
- ].join('\n');
56
- }
57
- // Specialist agents and their domain keywords
58
- const AGENTS = [
59
- { name: 'InfraAgent', domains: ['infra', 'k8s', 'docker', 'server', 'wireguard', 'helm'] },
60
- { name: 'AuthAgent', domains: ['auth', 'jwt', 'keycloak', 'oauth', 'oidc', 'token'] },
61
- { name: 'DeployAgent', domains: ['deploy', 'ci', 'pipeline', 'rsync', 'release'] },
62
- { name: 'DatabaseAgent', domains: ['db', 'gorm', 'migration', 'postgres', 'clickhouse', 'redis'] },
63
- { name: 'DebugAgent', domains: ['debug', 'panic', 'race', 'nil', 'fix', 'error'] },
64
- { name: 'APIAgent', domains: ['api', 'http', 'grpc', 'rest', 'fiber', 'web'] },
65
- ];
66
- const topicDomain = topic.split(':')[0] ?? '';
67
- const relevantAgents = AGENTS.filter(a => a.domains.some(d => topicDomain === d || topic.includes(d)));
68
- const votingAgents = relevantAgents.length > 0 ? relevantAgents : AGENTS;
69
- // Measure each agent's CKG coverage in their domains
70
- const agentCoverage = new Map();
71
- for (const agent of votingAgents) {
72
- let edgeCount = 0;
73
- for (const domain of agent.domains) {
74
- const nodeKeys = [];
75
- const nStream = redis.scanStream({ match: `cachly:ckg:node:${domain}*`, count: 50 });
76
- await new Promise((res, rej) => { nStream.on('data', (b) => nodeKeys.push(...b)); nStream.on('end', res); nStream.on('error', rej); });
77
- edgeCount += nodeKeys.length;
78
- }
79
- agentCoverage.set(agent.name, edgeCount);
80
- }
81
- const successLessons = history.filter(l => l.outcome === 'success' || l.outcome === 'partial');
82
- const failureLessons = history.filter(l => l.outcome === 'failure');
83
- if (failureLessons.length === 0) {
84
- return [
85
- `🗳️ **MADC: "${topic}"**`, '',
86
- `No contradictions found — all ${successLessons.length} entries have non-failure outcomes.`,
87
- '', `Use \`ckg_inspect(concept="${ckgSlug(topic)}")\` to explore the confidence graph.`,
88
- ].join('\n');
89
- }
90
- // Agent voting logic
91
- const votes = [];
92
- for (const agent of votingAgents) {
93
- const coverage = agentCoverage.get(agent.name) ?? 0;
94
- let vote;
95
- let reason;
96
- if (coverage < 2) {
97
- vote = 'abstain';
98
- reason = 'insufficient domain coverage';
99
- }
100
- else if (successLessons.length >= failureLessons.length * 2) {
101
- vote = 'success';
102
- reason = `${successLessons.length}/${history.length} entries confirm success`;
103
- }
104
- else if (failureLessons.length >= successLessons.length * 2) {
105
- vote = 'failure';
106
- reason = `${failureLessons.length}/${history.length} entries confirm failure`;
107
- }
108
- else {
109
- vote = 'abstain';
110
- reason = `contested (${successLessons.length} success vs ${failureLessons.length} failure)`;
111
- }
112
- votes.push({ agent: agent.name, vote, coverage, reason });
113
- }
114
- const successVotes = votes.filter(v => v.vote === 'success').length;
115
- const failureVotes = votes.filter(v => v.vote === 'failure').length;
116
- const abstainVotes = votes.filter(v => v.vote === 'abstain').length;
117
- let resolution;
118
- let resolutionText;
119
- if (successVotes > 0 && failureVotes === 0) {
120
- resolution = 'unanimous_success';
121
- resolutionText = `✅ **Unanimous: SUCCESS** — ${successVotes} agent(s) confirm, ${abstainVotes} abstain`;
122
- }
123
- else if (failureVotes > 0 && successVotes === 0) {
124
- resolution = 'unanimous_failure';
125
- resolutionText = `❌ **Unanimous: FAILURE** — ${failureVotes} agent(s) confirm, ${abstainVotes} abstain`;
126
- }
127
- else {
128
- resolution = 'contested';
129
- resolutionText = `⚠️ **CONTESTED** — ${successVotes} success votes, ${failureVotes} failure votes, ${abstainVotes} abstain`;
130
- }
131
- // Store resolution as CKG node
132
- const resNodeId = ckgSlug(`resolution:${topic}`);
133
- const resNode = {
134
- id: resNodeId, domain: 'resolution', type: 'resolution', count: 1,
135
- ts: new Date().toISOString(), resolution, topic,
136
- votes: { success: successVotes, failure: failureVotes, abstain: abstainVotes },
137
- };
138
- await redis.set(`cachly:ckg:node:${resNodeId}`, JSON.stringify(resNode));
139
- // Write contradicts edge if contested; decay loser confidence to 0.1 if unanimous
140
- if (resolution === 'contested') {
141
- await ckgUpdateEdge(redis, ckgSlug(topic), 'contradicts', resNodeId, false);
142
- }
143
- else {
144
- // Unanimous — demote the losing side's edges to near-zero confidence
145
- const loserOutcome = resolution === 'unanimous_success' ? 'failure' : 'success';
146
- const loserLessons = (loserOutcome === 'failure' ? failureLessons : successLessons);
147
- if (loserLessons.length > 0) {
148
- // Decay all 'fixes' edges from this concept that contradict the resolution
149
- const fromKeys = await redis.smembers(`cachly:ckg:idx:from:${ckgSlug(topic)}`);
150
- for (const fk of fromKeys) {
151
- const er = await redis.get(fk);
152
- if (!er)
153
- continue;
154
- try {
155
- const edge = JSON.parse(er);
156
- if (edge.edgeType === 'fixes' && edge.confidence > 0.15) {
157
- if (resolution === 'unanimous_failure') {
158
- // Fix was wrong — decay to 0.1
159
- edge.confidence = 0.1;
160
- edge.last_updated = new Date().toISOString();
161
- await redis.set(fk, JSON.stringify(edge));
162
- }
163
- }
164
- }
165
- catch { /* skip corrupt edges */ }
166
- }
167
- }
168
- // Remove the conflict marker — deliberation resolved it
169
- await redis.del(`cachly:ckg:conflict:${ckgSlug(topic)}`);
170
- }
171
- const lines = [
172
- `🗳️ **MADC Deliberation: "${topic}"**`, '',
173
- `📊 Evidence: ${successLessons.length} success/partial vs ${failureLessons.length} failure entries (${history.length} total)`,
174
- '', `**Voting agents (${votingAgents.length}):**`,
175
- ...votes.map(v => {
176
- const icon = v.vote === 'success' ? '✅' : v.vote === 'failure' ? '❌' : '⬜';
177
- const covBar = '▓'.repeat(Math.min(v.coverage, 5)) + '░'.repeat(Math.max(0, 5 - v.coverage));
178
- return ` ${icon} **${v.agent}** [${covBar}] ${v.coverage} CKG edges — ${v.reason}`;
179
- }),
180
- '', resolutionText, '',
181
- ];
182
- if (resolution === 'unanimous_success') {
183
- lines.push(`🔧 Failure entries superseded — store confirmed lesson: \`learn_from_attempts(topic="${topic}", outcome="success", ...)\``);
184
- }
185
- else if (resolution === 'unanimous_failure') {
186
- lines.push(`🚫 Success claims unconfirmed — re-verify: \`recall_best_solution(topic="${topic}")\``);
187
- }
188
- else {
189
- lines.push(`⚠️ Contested — run \`causal_trace\` before acting. Explore: \`ckg_inspect(concept="${ckgSlug(topic)}")\``);
190
- }
191
- lines.push('', `📝 Resolution node: \`cachly:ckg:node:${resNodeId}\``);
192
- return lines.join('\n');
193
- }
194
- // ── Layer 5: CLS — cls_ingest ─────────────────────────────────────────────
195
- case 'cls_ingest': {
196
- const { instance_id, source, payload } = args;
197
- const redis = await getConnection(instance_id);
198
- const ts = new Date().toISOString();
199
- const clsKey = 'cachly:cls:events';
200
- if (source === 'git_commit') {
201
- const message = String(payload.message ?? '');
202
- const sha = String(payload.sha ?? '');
203
- const files = (Array.isArray(payload.files) ? payload.files : []);
204
- const domain = /^fix/i.test(message) ? 'fix' : /^feat/i.test(message) ? 'feat' : /^refactor/i.test(message) ? 'refactor' : /^test/i.test(message) ? 'test' : 'commit';
205
- const slug = `${domain}:${ckgSlug(message.slice(0, 60))}`;
206
- const conceptId = ckgSlug(slug);
207
- await ckgUpsertNode(redis, conceptId, domain, 'commit');
208
- for (const f of files.slice(0, 10)) {
209
- const fd = f.includes('auth') ? 'auth' : f.includes('api') ? 'api' : f.includes('infra') ? 'infra' : f.includes('web') ? 'web' : 'code';
210
- const fileId = ckgSlug(`file:${fd}`);
211
- await ckgUpsertNode(redis, fileId, 'file', fd);
212
- await ckgUpdateEdge(redis, conceptId, 'co-occurs', fileId, true);
213
- }
214
- const lessonObj = {
215
- topic: slug, outcome: 'success', what_worked: message, what_failed: '',
216
- context: `CLS/git: sha=${sha}`, severity: 'minor',
217
- file_paths: files.slice(0, 10), commands: sha ? [`git show ${sha}`] : [],
218
- tags: ['cls', 'git'], depends_on: [], recall_count: 0, ts, verified_at: ts,
219
- confidence: 0.6, audit_trail: [{ ts, action: 'cls_git_commit' }], version: 3,
220
- };
221
- await redis.rpush(`cachly:lessons:${slug}`, JSON.stringify(lessonObj));
222
- const existing = await redis.get(`cachly:lesson:best:${slug}`);
223
- if (!existing)
224
- await redis.set(`cachly:lesson:best:${slug}`, JSON.stringify(lessonObj));
225
- await redis.rpush(clsKey, JSON.stringify({ source, payload: { message, sha }, ts }));
226
- await redis.ltrim(clsKey, -200, -1);
227
- return [
228
- `📨 **CLS Ingested: git_commit**`, '',
229
- `Commit \`${sha.slice(0, 8) || '?'}\`: ${message.slice(0, 80)}`,
230
- `Concept: \`${conceptId}\` (${domain}) · Files: ${files.length}`,
231
- '', `🕸️ CKG: \`${conceptId}\` + ${files.length} file edges · Lesson: \`${slug}\``,
232
- `💡 Inspect: \`ckg_inspect(concept="${domain}")\``,
233
- ].join('\n');
234
- }
235
- if (source === 'ci_outcome') {
236
- const status = String(payload.status ?? '');
237
- const prev_status = String(payload.prev_status ?? '');
238
- const job = String(payload.job ?? 'unknown');
239
- const ciCtx = String(payload.context ?? '');
240
- const isFixed = ['failure', 'red', 'error'].includes(prev_status) && ['success', 'green', 'passed'].includes(status);
241
- const isBroken = ['success', 'green', 'passed'].includes(prev_status) && ['failure', 'red', 'error'].includes(status);
242
- const slug = `ci:${ckgSlug(job)}`;
243
- const conceptId = ckgSlug(slug);
244
- await ckgUpsertNode(redis, conceptId, 'ci', 'job');
245
- if (isFixed) {
246
- const problemId = ckgSlug(`problem:${ckgSlug(job)}`);
247
- await ckgUpsertNode(redis, problemId, 'problem', 'ci-failure');
248
- await ckgUpdateEdge(redis, conceptId, 'fixes', problemId, true);
249
- const lessonObj = {
250
- topic: slug, outcome: 'success',
251
- what_worked: `CI job "${job}" went ${prev_status} → ${status}`,
252
- what_failed: `Job "${job}" was failing`, context: `CLS/ci: ${ciCtx}`,
253
- severity: 'major', file_paths: [], commands: [], tags: ['cls', 'ci'],
254
- depends_on: [], recall_count: 0, ts, verified_at: ts, confidence: 0.75,
255
- audit_trail: [{ ts, action: 'cls_ci_fixed' }], version: 3,
256
- };
257
- await redis.rpush(`cachly:lessons:${slug}`, JSON.stringify(lessonObj));
258
- await redis.set(`cachly:lesson:best:${slug}`, JSON.stringify(lessonObj));
259
- }
260
- else if (isBroken) {
261
- const causeId = ckgSlug(`cause:${ckgSlug(job)}`);
262
- await ckgUpsertNode(redis, causeId, 'cause', 'ci-break');
263
- await ckgUpdateEdge(redis, conceptId, 'causes', causeId, false);
264
- }
265
- await redis.rpush(clsKey, JSON.stringify({ source, payload: { status, prev_status, job }, ts }));
266
- await redis.ltrim(clsKey, -200, -1);
267
- const statusIcon = isFixed ? '✅ Fixed' : isBroken ? '🔴 Broken' : '📊 Recorded';
268
- return [
269
- `📨 **CLS Ingested: ci_outcome**`, '',
270
- `${statusIcon}: \`${job}\` — ${prev_status || '?'} → ${status}`,
271
- isFixed ? `🔧 CKG \`fixes\` edge added (75% confidence)` : isBroken ? `⚡ CKG \`causes\` edge added` : `📊 State recorded`,
272
- `💡 Lesson: \`${slug}\` | Predict: \`brain_predict(context="${job}")\``,
273
- ].join('\n');
274
- }
275
- if (source === 'ide_diagnostic') {
276
- const error = String(payload.error ?? '');
277
- const fix = String(payload.fix ?? '');
278
- const file = String(payload.file ?? '');
279
- const errorConcept = extractProblemConcept(error) ?? 'unknown-error';
280
- const slug = `debug:${ckgSlug(errorConcept)}`;
281
- const conceptId = ckgSlug(slug);
282
- const problemId = ckgSlug(`problem:${errorConcept}`);
283
- await ckgUpsertNode(redis, conceptId, 'debug', 'diagnostic');
284
- await ckgUpsertNode(redis, problemId, 'problem', 'compiler-error');
285
- await ckgUpdateEdge(redis, conceptId, 'fixes', problemId, true);
286
- const lessonObj = {
287
- topic: slug, outcome: 'success', what_worked: fix, what_failed: error,
288
- context: `CLS/ide: ${file}`, severity: 'minor',
289
- file_paths: file ? [file] : [], commands: [], tags: ['cls', 'ide-diagnostic'],
290
- depends_on: [], recall_count: 0, ts, verified_at: ts, confidence: 0.65,
291
- audit_trail: [{ ts, action: 'cls_ide_diagnostic' }], version: 3,
292
- };
293
- await redis.rpush(`cachly:lessons:${slug}`, JSON.stringify(lessonObj));
294
- const existingL = await redis.get(`cachly:lesson:best:${slug}`);
295
- if (!existingL)
296
- await redis.set(`cachly:lesson:best:${slug}`, JSON.stringify(lessonObj));
297
- await redis.rpush(clsKey, JSON.stringify({ source, payload: { error: error.slice(0, 60), fix: fix.slice(0, 60), file }, ts }));
298
- await redis.ltrim(clsKey, -200, -1);
299
- return [
300
- `📨 **CLS Ingested: ide_diagnostic**`, '',
301
- `Error: \`${error.slice(0, 80)}\``,
302
- `Fix: ${fix.slice(0, 100)}`,
303
- file ? `File: \`${file}\`` : '',
304
- '', `🕸️ CKG: \`${conceptId}\` → fixes → \`${problemId}\` | Lesson: \`${slug}\``,
305
- ].filter(l => l !== '').join('\n');
306
- }
307
- return `❌ Unknown CLS source: "${source}". Valid: git_commit, ci_outcome, ide_diagnostic`;
308
- }
309
- // ── Layer 5: CLS — cls_install_hooks ─────────────────────────────────────
310
- case 'cls_install_hooks': {
311
- const { instance_id, repo_path = '.', hooks = ['git', 'ci'] } = args;
312
- const hooksArr = Array.isArray(hooks) ? hooks : ['git', 'ci'];
313
- const lines = [`🔌 **CLS Hook Installation Guide**\n`];
314
- if (hooksArr.includes('git')) {
315
- const hookScript = buildClsPostCommitHook(instance_id);
316
- lines.push(`### Git post-commit hook`);
317
- lines.push(`**Quick install (run once per repo):**`);
318
- lines.push('```sh');
319
- lines.push(`cat > ${repo_path}/.git/hooks/post-commit << 'HOOK'`);
320
- lines.push(hookScript);
321
- lines.push(`HOOK`);
322
- lines.push(`chmod +x ${repo_path}/.git/hooks/post-commit`);
323
- lines.push('```');
324
- lines.push(`After install: every \`git commit\` automatically updates your brain's CKG.`);
325
- lines.push('');
326
- }
327
- if (hooksArr.includes('ci')) {
328
- lines.push(`### GitHub Actions CI outcome hook`);
329
- lines.push(`**Add at the end of each job** (after build/test steps):`);
330
- lines.push('```yaml');
331
- lines.push(`- name: cachly CLS — record CI outcome`);
332
- lines.push(` if: always()`);
333
- lines.push(` run: |`);
334
- lines.push(` node -e "`);
335
- lines.push(` const r=require('https');`);
336
- lines.push(` const d=JSON.stringify({instance_id:'${instance_id}',source:'ci_outcome',payload:{`);
337
- lines.push(` status:'\${{ job.status }}',prev_status:'unknown',job:'\${{ github.job }}',`);
338
- lines.push(` context:'github-actions run \${{ github.run_number }}'}});`);
339
- lines.push(` r.request({hostname:'api.cachly.dev',path:'/api/v1/cls/ingest',method:'POST',`);
340
- lines.push(` headers:{'Content-Type':'application/json','Authorization':'Bearer $CACHLY_JWT',`);
341
- lines.push(` 'Content-Length':d.length}},()=>{}).end(d);`);
342
- lines.push(` " 2>/dev/null || true`);
343
- lines.push(` env:`);
344
- lines.push(` CACHLY_JWT: \${{ secrets.CACHLY_JWT }}`);
345
- lines.push('```');
346
- lines.push('');
347
- }
348
- lines.push(`💡 Once installed: \`brain_search(query="cls")\` to verify events are arriving.`);
349
- lines.push(`📊 Monitor CKG growth: \`ckg_inspect(concept="ci")\` or \`ckg_inspect(concept="fix")\``);
350
- return lines.join('\n');
351
- }
352
- // ── Layer 6: FedBrain — fedbrain_contribute ───────────────────────────────
353
- case 'fedbrain_contribute': {
354
- const { instance_id, lesson_key, visibility = 'public' } = args;
355
- const redis = await getConnection(instance_id);
356
- const raw = await redis.get(`cachly:lesson:best:${lesson_key}`);
357
- if (!raw)
358
- return `❌ Lesson \`${lesson_key}\` not found. Store it first with \`learn_from_attempts\`.`;
359
- const lesson = safeJsonParse(raw, null);
360
- if (!lesson)
361
- return `❌ Lesson \`${lesson_key}\` data is corrupted. Re-store with \`learn_from_attempts\`.`;
362
- const domainTokens = [lesson.topic.split(':')[0], ...(lesson.tags ?? [])].filter(Boolean);
363
- const domainFingerprint = [...new Set(domainTokens)].sort().join(',');
364
- // HMAC certificate ID (non-reversible, privacy-safe)
365
- const certContent = `${lesson.topic}:${lesson.outcome}:${lesson.what_worked}`;
366
- const certId = createHmac('sha256', `cachly-fedbrain:${instance_id}`).update(certContent).digest('hex').slice(0, 16);
367
- const cert = {
368
- cert_id: certId, lesson_key, visibility,
369
- domain_fingerprint: domainFingerprint,
370
- contributed_at: new Date().toISOString(),
371
- confirm_count: 0,
372
- trust_score: lesson.outcome === 'success' ? 0.85 : 0.5,
373
- };
374
- await redis.set(`cachly:fedbrain:cert:${certId}`, JSON.stringify(cert));
375
- await redis.sadd('cachly:fedbrain:contributed', certId);
376
- // Try global commons via syndication API
377
- let syndicationResult;
378
- try {
379
- await apiFetch('/api/v1/syndication/contribute', {
380
- method: 'POST',
381
- body: JSON.stringify({
382
- topic: lesson.topic, outcome: lesson.outcome,
383
- what_worked: lesson.what_worked, what_failed: lesson.what_failed ?? '',
384
- severity: lesson.severity ?? 'major', cert_id: certId,
385
- domain_fingerprint: domainFingerprint, visibility,
386
- }),
387
- });
388
- syndicationResult = `✅ Contributed to global commons`;
389
- }
390
- catch {
391
- syndicationResult = `📦 Stored locally (commons API unavailable — will sync when online)`;
392
- }
393
- return [
394
- `🌐 **FedBrain Contribute: "${lesson_key}"**`, '',
395
- `📜 Certificate: \`${certId}\``,
396
- `🏷️ Domain fingerprint: ${domainTokens.slice(0, 6).map(t => `\`${t}\``).join(', ')}`,
397
- `🔒 Visibility: ${visibility}`,
398
- '', syndicationResult, '',
399
- `💡 At 10 independent confirms → 🏆 Gold Standard`,
400
- `🔍 Search: \`fedbrain_search(query="${lesson.topic.split(':').slice(-1)[0]}")\``,
401
- ].join('\n');
402
- }
403
- // ── Layer 6: FedBrain — fedbrain_search ──────────────────────────────────
404
- case 'fedbrain_search': {
405
- const { instance_id, query, context_hints = [], limit = 10 } = args;
406
- const redis = await getConnection(instance_id);
407
- // Build local domain context from contributed certificates + explicit hints
408
- const contribIds = await redis.smembers('cachly:fedbrain:contributed');
409
- const localDomains = new Map();
410
- for (const certId of contribIds.slice(0, 30)) {
411
- const certRaw = await redis.get(`cachly:fedbrain:cert:${certId}`);
412
- if (!certRaw)
413
- continue;
414
- try {
415
- const cert = JSON.parse(certRaw);
416
- for (const d of (cert.domain_fingerprint ?? '').split(',').filter(Boolean)) {
417
- localDomains.set(d, (localDomains.get(d) ?? 0) + 1);
418
- }
419
- }
420
- catch { /* skip */ }
421
- }
422
- for (const hint of (Array.isArray(context_hints) ? context_hints : [])) {
423
- localDomains.set(hint.toLowerCase(), (localDomains.get(hint.toLowerCase()) ?? 0) + 2);
424
- }
425
- let results = [];
426
- // Set when the commons API call fails — the results below come from this
427
- // single machine's local lessons, not from the confirmed global network.
428
- // The reply must say so; printing them as unlabeled commons hits would
429
- // dress a network outage up as confirmed world knowledge.
430
- let usedLocalFallback = false;
431
- try {
432
- const params = new URLSearchParams({ q: query, limit: String(Math.min(limit * 2, 50)) });
433
- const res = await apiFetch(`/api/v1/syndication/search?${params}`);
434
- results = res.results ?? [];
435
- }
436
- catch {
437
- usedLocalFallback = true;
438
- // Fallback: search local lessons
439
- const lessonKeys = [];
440
- const lStream = redis.scanStream({ match: 'cachly:lesson:best:*', count: 200 });
441
- await new Promise((res, rej) => { lStream.on('data', (b) => lessonKeys.push(...b)); lStream.on('end', res); lStream.on('error', rej); });
442
- for (const k of lessonKeys.slice(0, 60)) {
443
- const r = await redis.get(k);
444
- if (!r)
445
- continue;
446
- try {
447
- const l = JSON.parse(r);
448
- const haystack = `${l.topic} ${l.what_worked ?? ''} ${l.what_failed ?? ''}`.toLowerCase();
449
- if (query.toLowerCase().split(/\s+/).some(t => t.length > 2 && haystack.includes(t))) {
450
- results.push({ id: k.split(':').pop() ?? k, topic: l.topic, category: l.topic.split(':')[0], outcome: l.outcome, what_worked: l.what_worked ?? '', severity: l.severity ?? 'major', confirm_count: 0, created_at: l.ts ?? new Date().toISOString() });
451
- }
452
- }
453
- catch { /* skip */ }
454
- }
455
- }
456
- const fallbackNotice = usedLocalFallback
457
- ? ` — ⚠️ Commons unreachable, local fallback (this machine only, not confirmed by the network)`
458
- : '';
459
- if (results.length === 0) {
460
- return [`🌐 **FedBrain Search: "${query}"**${fallbackNotice}`, '', `No results. Contribute: \`fedbrain_contribute(lesson_key="fix:...")\``].join('\n');
461
- }
462
- // Context-weighted ranking
463
- const ranked = results.map(r => {
464
- const rDomains = (r.domain_fingerprint ?? r.category ?? '').split(',').filter(Boolean);
465
- const overlap = rDomains.reduce((s, d) => s + (localDomains.get(d) ?? 0), 0);
466
- const contextScore = localDomains.size > 0 ? overlap / Math.max(1, localDomains.size + rDomains.length) : 0;
467
- const confirmedScore = Math.min(1, r.confirm_count / 10);
468
- const weightedScore = (contextScore * 0.4) + (confirmedScore * 0.4) + (r.outcome === 'success' ? 0.2 : 0);
469
- // A fallback result never earned network confirmations — it cannot be a
470
- // Gold Standard no matter what confirm_count says.
471
- return { ...r, weightedScore, isGoldStandard: !usedLocalFallback && r.confirm_count >= 10 };
472
- }).sort((a, b) => b.weightedScore - a.weightedScore).slice(0, limit);
473
- const lines = [`🌐 **FedBrain Search: "${query}"** — ${ranked.length} result${ranked.length !== 1 ? 's' : ''} (context-weighted)${fallbackNotice}\n`];
474
- for (const r of ranked) {
475
- const icon = r.outcome === 'success' ? '✅' : r.outcome === 'failure' ? '❌' : '⚠️';
476
- const goldBadge = r.isGoldStandard ? ' 🏆 _Gold Standard_' : (!usedLocalFallback && r.confirm_count >= 3) ? ` ✓${r.confirm_count}` : '';
477
- const ctxPct = Math.round(r.weightedScore * 100);
478
- lines.push(`${icon}${goldBadge} **\`${r.topic}\`** [ctx: ${ctxPct}%]`);
479
- if (r.what_worked)
480
- lines.push(` ✅ ${r.what_worked.slice(0, 150)}`);
481
- if (r.what_failed)
482
- lines.push(` ❌ ${r.what_failed.slice(0, 80)}`);
483
- lines.push(` _${r.confirm_count} confirm${r.confirm_count !== 1 ? 's' : ''} | id: \`${r.id}\`_`, '');
484
- }
485
- if (localDomains.size > 0) {
486
- const topDomains = [...localDomains.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([d, n]) => `\`${d}\`(${n})`).join(', ');
487
- lines.push(`🎯 Your context: ${topDomains}`);
488
- }
489
- lines.push(`💡 Confirm a result: \`fedbrain_confirm(id="<id>", topic="<topic>", outcome="worked")\``);
490
- return lines.join('\n');
491
- }
492
- // ── Layer 6: FedBrain — fedbrain_confirm ─────────────────────────────────
493
- case 'fedbrain_confirm': {
494
- // The commons confirm endpoint keys on the syndicated lesson id
495
- // (POST /api/v1/syndication/:id/confirm). `id` comes from fedbrain_search
496
- // results; `topic` is kept for the LOCAL CKG confidence edge and display.
497
- const { instance_id, id, topic, outcome } = args;
498
- if (!id) {
499
- return [
500
- `❌ **FedBrain Confirm** needs the lesson \`id\` from \`fedbrain_search\` results.`,
501
- `Run \`fedbrain_search(query="...")\`, copy the id shown next to a result,`,
502
- `then: \`fedbrain_confirm(id="<id>", topic="<topic>", outcome="worked")\`.`,
503
- ].join('\n');
504
- }
505
- const redis = await getConnection(instance_id);
506
- const ts = new Date().toISOString();
507
- const confirmEntry = JSON.stringify({ id, topic, outcome, ts });
508
- await redis.rpush('cachly:fedbrain:confirmations', confirmEntry);
509
- await redis.ltrim('cachly:fedbrain:confirmations', -200, -1);
510
- // Update local CKG confidence (only when we know which topic this maps to).
511
- const worked = outcome === 'worked';
512
- const partial = outcome === 'partially_worked';
513
- if (topic) {
514
- await ckgUpdateEdge(redis, ckgSlug(topic), 'fixes', ckgSlug(`syndicated:${topic}`), worked, partial);
515
- }
516
- // Propagate to global commons — id in the PATH, body ignored server-side.
517
- let propResult;
518
- try {
519
- await apiFetch(`/api/v1/syndication/${encodeURIComponent(id)}/confirm`, { method: 'POST' });
520
- propResult = `✅ Confirmation propagated to global commons`;
521
- }
522
- catch {
523
- await redis.rpush('cachly:fedbrain:pending_confirms', confirmEntry);
524
- await redis.ltrim('cachly:fedbrain:pending_confirms', -50, -1);
525
- propResult = `📦 Queued locally (API unavailable — will propagate on next online session)`;
526
- }
527
- const icon = worked ? '✅' : partial ? '⚠️' : '❌';
528
- const label = topic ?? id;
529
- return [
530
- `${icon} **FedBrain Confirm: "${label}"** → ${outcome}`, '',
531
- propResult, '',
532
- topic
533
- ? `🕸️ CKG confidence ${worked || partial ? 'boosted' : 'reduced'} for \`${ckgSlug(topic)}\``
534
- : `🕸️ Local CKG unchanged (pass \`topic\` to also update your own confidence graph)`,
535
- `💡 Your confirmation helps other brains worldwide.`,
536
- `📊 Status: \`fedbrain_status(instance_id="...")\``,
537
- ].join('\n');
538
- }
539
- // ── Layer 6: FedBrain — fedbrain_status ──────────────────────────────────
540
- case 'fedbrain_status': {
541
- const { instance_id } = args;
542
- const redis = await getConnection(instance_id);
543
- const contribIds = await redis.smembers('cachly:fedbrain:contributed');
544
- const confirmsRaw = await redis.lrange('cachly:fedbrain:confirmations', -10, -1);
545
- const pendingConfirms = await redis.llen('cachly:fedbrain:pending_confirms');
546
- const confirms = confirmsRaw.map(r => { try {
547
- return JSON.parse(r);
548
- }
549
- catch {
550
- return null;
551
- } }).filter(Boolean);
552
- const certDetails = [];
553
- for (const certId of contribIds.slice(0, 15)) {
554
- const raw = await redis.get(`cachly:fedbrain:cert:${certId}`);
555
- if (!raw)
556
- continue;
557
- try {
558
- const cert = JSON.parse(raw);
559
- certDetails.push({ cert_id: cert.cert_id, lesson_key: cert.lesson_key, confirm_count: cert.confirm_count ?? 0, trust_score: cert.trust_score ?? 0.5, isGold: (cert.confirm_count ?? 0) >= 10 });
560
- }
561
- catch { /* skip */ }
562
- }
563
- const lines = [
564
- `🌐 **FedBrain Status**\n`,
565
- `### 📤 Contributed Lessons: ${contribIds.length}`,
566
- ];
567
- if (certDetails.length > 0) {
568
- for (const c of certDetails) {
569
- const goldBadge = c.isGold ? ' 🏆 Gold Standard' : '';
570
- const confBar = '█'.repeat(Math.min(10, c.confirm_count)) + '░'.repeat(Math.max(0, 10 - c.confirm_count));
571
- lines.push(` \`${c.lesson_key}\` [${confBar}] ×${c.confirm_count}${goldBadge}`);
572
- }
573
- }
574
- else {
575
- lines.push(` _None yet. Contribute with \`fedbrain_contribute(lesson_key="fix:...")\`_`);
576
- }
577
- lines.push('', `### 📥 Recent Confirmations: ${confirms.length}`);
578
- if (confirms.length > 0) {
579
- for (const c of confirms.slice(-5)) {
580
- const icon = c.outcome === 'worked' ? '✅' : c.outcome === 'partially_worked' ? '⚠️' : '❌';
581
- lines.push(` ${icon} \`${c.topic}\` — ${c.outcome} (${new Date(c.ts).toLocaleDateString('de-DE')})`);
582
- }
583
- }
584
- else {
585
- lines.push(` _None yet. Confirm syndicated lessons with \`fedbrain_confirm\`_`);
586
- }
587
- if (pendingConfirms > 0) {
588
- lines.push('', `⚠️ ${pendingConfirms} confirmation${pendingConfirms !== 1 ? 's' : ''} pending propagation`);
589
- }
590
- lines.push('', '---', `**Contribute:** \`fedbrain_contribute(lesson_key="fix:...")\``, `**Search:** \`fedbrain_search(query="...")\``, `**Confirm:** \`fedbrain_confirm(id="...", topic="...", outcome="worked")\``);
591
- return lines.join('\n');
592
- }
593
- // ── Layer 6: FedBrain — brain_federate ───────────────────────────────────
594
- case 'brain_federate': {
595
- const { instance_id, source, domain, min_confidence = 0.6, dry_run = false } = args;
596
- if (instance_id === source)
597
- return `❌ Source and destination cannot be the same brain.`;
598
- const destRedis = await getConnection(instance_id);
599
- const srcRedis = await getConnection(source);
600
- const domainPattern = domain === '*' ? 'cachly:ckg:node:*' : `cachly:ckg:node:${domain}*`;
601
- // Scan source CKG nodes matching the domain
602
- const nodeKeys = [];
603
- const nStream = srcRedis.scanStream({ match: domainPattern, count: 100 });
604
- await new Promise((res, rej) => { nStream.on('data', (b) => nodeKeys.push(...b)); nStream.on('end', res); nStream.on('error', rej); });
605
- if (nodeKeys.length === 0) {
606
- return [
607
- `🧠 **brain_federate: "${domain}"**`, '',
608
- `No CKG nodes found in source brain for domain \`${domain}\`.`,
609
- `The source brain may not have knowledge in this area yet.`,
610
- `Try: \`fedbrain_search(query="${domain}")\` to find global commons knowledge instead.`,
611
- ].join('\n');
612
- }
613
- // Transfer nodes + their outgoing edges
614
- let nodesTransferred = 0;
615
- let edgesTransferred = 0;
616
- let lessonsTransferred = 0;
617
- let skippedLowConf = 0;
618
- const transferLog = [];
619
- for (const nk of nodeKeys) {
620
- const nodeRaw = await srcRedis.get(nk);
621
- if (!nodeRaw)
622
- continue;
623
- let node;
624
- try {
625
- node = JSON.parse(nodeRaw);
626
- }
627
- catch {
628
- continue;
629
- }
630
- if (!dry_run)
631
- await destRedis.set(nk, nodeRaw);
632
- nodesTransferred++;
633
- // Transfer outgoing edges for this node
634
- const edgeKeys = await srcRedis.smembers(`cachly:ckg:idx:from:${node.id}`);
635
- for (const ek of edgeKeys) {
636
- const edgeRaw = await srcRedis.get(ek);
637
- if (!edgeRaw)
638
- continue;
639
- let edge;
640
- try {
641
- edge = JSON.parse(edgeRaw);
642
- }
643
- catch {
644
- continue;
645
- }
646
- if (edge.confidence < min_confidence) {
647
- skippedLowConf++;
648
- continue;
649
- }
650
- if (!dry_run) {
651
- await destRedis.set(ek, edgeRaw);
652
- await destRedis.sadd(`cachly:ckg:idx:from:${edge.from}`, ek);
653
- await destRedis.sadd(`cachly:ckg:idx:to:${edge.to}`, ek);
654
- }
655
- edgesTransferred++;
656
- }
657
- // Transfer best-lesson for the same topic slug
658
- const lessonKey = `cachly:lesson:best:${node.id}`;
659
- const lessonRaw = await srcRedis.get(lessonKey);
660
- if (lessonRaw) {
661
- if (!dry_run)
662
- await destRedis.set(lessonKey, lessonRaw);
663
- lessonsTransferred++;
664
- if (transferLog.length < 8) {
665
- try {
666
- const l = JSON.parse(lessonRaw);
667
- const icon = l.outcome === 'success' ? '✅' : l.outcome === 'failure' ? '❌' : '⚠️';
668
- transferLog.push(` ${icon} \`${l.topic}\` — ${(l.what_worked ?? '').slice(0, 80)}`);
669
- }
670
- catch { /* skip */ }
671
- }
672
- }
673
- }
674
- // Record federation provenance
675
- if (!dry_run) {
676
- const provEntry = JSON.stringify({
677
- source, domain, transferred_at: new Date().toISOString(),
678
- nodes: nodesTransferred, edges: edgesTransferred, lessons: lessonsTransferred,
679
- });
680
- await destRedis.rpush('cachly:fedbrain:federations', provEntry);
681
- await destRedis.ltrim('cachly:fedbrain:federations', -50, -1);
682
- }
683
- const dryTag = dry_run ? ' (DRY RUN — no writes)' : '';
684
- const lines = [
685
- `🧠 **brain_federate: "${domain}"**${dryTag}`, '',
686
- `📤 Source: \`${source}\``,
687
- `📥 Destination: \`${instance_id}\``,
688
- `🔍 Domain filter: \`${domain}\` | min_confidence: ${min_confidence}`,
689
- '',
690
- `### Transfer Summary`,
691
- ` 🕸️ CKG nodes: ${nodesTransferred}`,
692
- ` 🔗 CKG edges: ${edgesTransferred} (${skippedLowConf} skipped, confidence < ${min_confidence})`,
693
- ` 📚 Lessons: ${lessonsTransferred}`,
694
- '',
695
- ];
696
- if (transferLog.length > 0) {
697
- lines.push(`### Sample lessons transferred:`, ...transferLog);
698
- if (lessonsTransferred > transferLog.length)
699
- lines.push(` _... and ${lessonsTransferred - transferLog.length} more_`);
700
- lines.push('');
701
- }
702
- if (dry_run) {
703
- lines.push(`💡 Run without \`dry_run: true\` to apply the transfer.`);
704
- }
705
- else {
706
- lines.push(`✅ Transfer complete. Your brain now has the \`${domain}\` knowledge from \`${source}\`.`);
707
- lines.push(`🔍 Explore: \`ckg_inspect(concept="${domain}")\` | \`recall_best_solution(topic="${domain}:...")\``);
708
- }
709
- return lines.join('\n');
710
- }
711
- // ── crystal_view ──────────────────────────────────────────────────────────
712
- case 'crystal_view': {
713
- const { instance_id, show_raw = false } = args;
714
- const redis = await getConnection(instance_id);
715
- const raw = await redis.get('cachly:crystal:latest');
716
- if (!raw) {
717
- return [
718
- `💎 **Memory Crystal: not yet created**`, '',
719
- `No crystal found. Create one with \`memory_crystalize()\` to compress your accumulated wisdom.`,
720
- '', `💡 Tip: run \`memory_crystalize\` monthly for best results.`,
721
- ].join('\n');
722
- }
723
- const crystal = safeJsonParse(raw, null);
724
- if (!crystal)
725
- return `❌ Memory Crystal data is corrupted. Re-create with \`memory_crystalize()\`.`;
726
- const age = Math.floor((Date.now() - new Date(crystal.ts).getTime()) / 86400000);
727
- const freshEmoji = age <= 7 ? '🟢' : age <= 30 ? '🟡' : '🔴';
728
- const lines = [
729
- `💎 **Memory Crystal: ${crystal.label}**`, '',
730
- `📅 Created: ${new Date(crystal.ts).toLocaleDateString('de-DE')} (${age}d ago ${freshEmoji})`,
731
- `📊 Compressed from: ${crystal.created_from}`,
732
- `🗂️ Categories: ${crystal.categories.slice(0, 10).map(c => `\`${c}\``).join(', ')}${crystal.categories.length > 10 ? ` +${crystal.categories.length - 10} more` : ''}`,
733
- '',
734
- `**🔑 Top patterns (${crystal.top_patterns.length}):**`,
735
- ];
736
- for (const p of crystal.top_patterns) {
737
- lines.push(` • **${p.category}** (${p.count}×): ${p.insight.slice(0, 110)}`);
738
- }
739
- if (age > 30) {
740
- lines.push('', `⚠️ Crystal is ${age}d old — run \`memory_crystalize()\` to refresh it.`);
741
- }
742
- if (show_raw) {
743
- lines.push('', '```json', JSON.stringify(crystal, null, 2), '```');
744
- }
745
- // Team Crystal (W8): the cross-person, causal layer — shown alongside the
746
- // per-brain crystal when team_crystallize has been run.
747
- const teamRaw = await redis.get('cachly:crystal:team:latest').catch(() => null);
748
- if (teamRaw) {
749
- const tc = safeJsonParse(teamRaw, null);
750
- if (tc && tc.patterns?.length) {
751
- const tcAge = Math.floor((Date.now() - new Date(tc.ts).getTime()) / 86400000);
752
- lines.push('', `---`, `💠 **Team Crystal: ${tc.label}** _(${tcAge}d ago · ${tc.contributors} contributors)_`, `_Fixes multiple teammates independently converged on — the team-wide layer:_`);
753
- for (const p of tc.patterns.slice(0, 5)) {
754
- lines.push(` • 🧩 **${p.concept}** — ${p.author_count} people (${p.authors.map(a => `@${a}`).join(', ')}): ${p.convergent_fix.slice(0, 90)}`);
755
- }
756
- }
757
- }
758
- lines.push('', `💡 Refresh: \`memory_crystalize()\` · \`team_crystallize()\` | Recover: \`compact_recover(instance_id="...")\``);
759
- return lines.join('\n');
760
- }
761
- // ── compact_recover ───────────────────────────────────────────────────────
762
- case 'compact_recover': {
763
- const { instance_id, focus = '' } = args;
764
- const redis = await getConnection(instance_id);
765
- const lines = [`🔁 **Compact Recovery Briefing**\n`];
766
- lines.push(`> *Call this first after any context limit hit. Reconstructs where you left off.*\n`);
767
- // 1. Memory Crystal
768
- const crystalRaw = await redis.get('cachly:crystal:latest');
769
- if (crystalRaw) {
770
- const crystal = safeJsonParse(crystalRaw, null);
771
- if (crystal) {
772
- lines.push(`### 💎 Memory Crystal: ${crystal.label}`);
773
- lines.push(`Compressed from ${crystal.session_count} sessions, ${crystal.lesson_count} lessons.`);
774
- const topN = focus
775
- ? crystal.top_patterns.filter(p => p.category.toLowerCase().includes(focus.toLowerCase()) || p.insight.toLowerCase().includes(focus.toLowerCase())).slice(0, 4)
776
- : crystal.top_patterns.slice(0, 4);
777
- for (const p of topN)
778
- lines.push(` • **${p.category}**: ${p.insight.slice(0, 100)}`);
779
- lines.push('');
780
- }
781
- }
782
- // 2. Last session summary
783
- const lastSession = await redis.get('cachly:session:last');
784
- if (lastSession) {
785
- const sess = safeJsonParse(lastSession, null);
786
- if (sess) {
787
- lines.push(`### 🕐 Last Session`);
788
- if (sess.focus)
789
- lines.push(`Focus: _${sess.focus}_`);
790
- if (sess.summary)
791
- lines.push(`Summary: ${sess.summary.slice(0, 300)}`);
792
- lines.push('');
793
- }
794
- }
795
- // 3. Session handoff
796
- const handoff = await redis.get('cachly:session:handoff');
797
- if (handoff) {
798
- const h = safeJsonParse(handoff, null);
799
- if (h) {
800
- lines.push(`### 📋 Handoff (from last window)`);
801
- if (h.context_summary)
802
- lines.push(`Context: ${h.context_summary.slice(0, 200)}`);
803
- if (h.remaining_tasks?.length) {
804
- lines.push(`Remaining tasks:`);
805
- for (const t of h.remaining_tasks.slice(0, 5))
806
- lines.push(` • ${t}`);
807
- }
808
- if (h.instructions)
809
- lines.push(`⚠️ Instructions: ${h.instructions.slice(0, 200)}`);
810
- if (h.blocked_on)
811
- lines.push(`🚧 Blocked on: ${h.blocked_on}`);
812
- lines.push('');
813
- }
814
- }
815
- // 4. WIP registry
816
- const wipRaw = await redis.get('cachly:ctx:wip-registry');
817
- if (wipRaw) {
818
- const wip = safeJsonParse(wipRaw, null);
819
- if (wip?.content) {
820
- lines.push(`### 🔧 WIP Registry`);
821
- lines.push(wip.content.slice(0, 400));
822
- lines.push('');
823
- }
824
- }
825
- // 5. Open failures (roadmap with status=blocked/in_progress)
826
- const roadmapKeys = [];
827
- const rStream = redis.scanStream({ match: 'cachly:roadmap:*', count: 100 });
828
- await new Promise((res, rej) => { rStream.on('data', (b) => roadmapKeys.push(...b)); rStream.on('end', res); rStream.on('error', rej); });
829
- const openItems = [];
830
- for (const k of roadmapKeys.slice(0, 30)) {
831
- const r = await redis.get(k);
832
- if (!r)
833
- continue;
834
- try {
835
- const item = JSON.parse(r);
836
- if (item.status === 'in_progress' || item.status === 'blocked')
837
- openItems.push({ title: item.title ?? k, status: item.status, priority: item.priority });
838
- }
839
- catch { /* skip */ }
840
- }
841
- if (openItems.length > 0) {
842
- lines.push(`### 🚧 Open Items`);
843
- for (const i of openItems.slice(0, 5))
844
- lines.push(` • [${i.status}] ${i.title}`);
845
- lines.push('');
846
- }
847
- // 6. Focus-relevant lessons
848
- if (focus) {
849
- const lessonKeys = [];
850
- const lStream = redis.scanStream({ match: 'cachly:lesson:best:*', count: 200 });
851
- await new Promise((res, rej) => { lStream.on('data', (b) => lessonKeys.push(...b)); lStream.on('end', res); lStream.on('error', rej); });
852
- const relevant = [];
853
- for (const k of lessonKeys) {
854
- const r = await redis.get(k);
855
- if (!r)
856
- continue;
857
- try {
858
- const l = JSON.parse(r);
859
- if (l.topic.toLowerCase().includes(focus.toLowerCase()) && l.what_worked) {
860
- relevant.push({ topic: l.topic, what_worked: l.what_worked });
861
- }
862
- }
863
- catch { /* skip */ }
864
- }
865
- if (relevant.length > 0) {
866
- lines.push(`### 💡 Relevant Lessons for "${focus}"`);
867
- for (const l of relevant.slice(0, 4))
868
- lines.push(` • **${l.topic}**: ${l.what_worked.slice(0, 100)}`);
869
- lines.push('');
870
- }
871
- }
872
- if (lines.length <= 3) {
873
- lines.push(`_No brain data found. Start accumulating knowledge with \`learn_from_attempts\` and \`session_start\`._`);
874
- }
875
- lines.push(`---`, `🧠 Brain is ready. Continue your work — full context restored.`);
876
- return lines.join('\n');
877
- }
878
- // ── brain_from_git ────────────────────────────────────────────────────────
879
- case 'brain_from_git': {
880
- const startedAt = Date.now();
881
- const { instance_id, repo_path = '.', limit = 100, branch = 'HEAD', since = '', incremental = true } = args;
882
- const redis = await getConnection(instance_id);
883
- const { execSync } = await import('node:child_process');
884
- const { resolve } = await import('node:path');
885
- const repoDir = resolve(repo_path);
886
- const maxCommits = Math.min(Number(limit) || 100, 500);
887
- // Semaphore: max 10 concurrent git subprocesses per MCP process
888
- await _gitSemAcquire();
889
- try {
890
- // Verify it's a git repo
891
- try {
892
- execSync('git rev-parse --git-dir', { cwd: repoDir, stdio: 'pipe' });
893
- }
894
- catch {
895
- return `❌ Not a git repository: \`${repoDir}\`. Pass \`repo_path\` pointing to a git checkout.`;
896
- }
897
- // Incremental mode: resume from last processed commit SHA
898
- const lastShaKey = `cachly:brain_from_git:last_sha:${Buffer.from(repoDir).toString('base64').slice(0, 32)}`;
899
- let lastSha = '';
900
- let isIncremental = false;
901
- if (incremental !== false && !since) {
902
- lastSha = (await redis.get(lastShaKey)) ?? '';
903
- isIncremental = Boolean(lastSha);
904
- }
905
- // Build git log command — use --name-only to get changed files per commit
906
- const sinceFlag = since ? `--since="${since}"` : '';
907
- const afterFlag = isIncremental ? `${lastSha}..HEAD` : '';
908
- const revRange = afterFlag || branch;
909
- const logCmd = `git log ${revRange} ${sinceFlag} --pretty=format:"COMMIT|||%H|||%s|||%ad|||%an" --date=short --no-merges --name-only -n ${maxCommits}`;
910
- let logOutput = '';
911
- try {
912
- logOutput = execSync(logCmd, { cwd: repoDir, encoding: 'utf-8', stdio: 'pipe' });
913
- }
914
- catch (e) {
915
- return `❌ git log failed: ${e.message}. Check \`repo_path\` and \`branch\`.`;
916
- }
917
- const commits = [];
918
- let current = null;
919
- for (const raw of logOutput.split('\n')) {
920
- const line = raw.trim();
921
- if (line.startsWith('COMMIT|||')) {
922
- if (current)
923
- commits.push(current);
924
- const [, sha, subject, date, author] = line.split('|||');
925
- current = { sha: (sha ?? '').trim(), subject: (subject ?? '').trim(), date: (date ?? '').trim(), author: (author ?? '').trim(), files: [] };
926
- }
927
- else if (line && current) {
928
- const norm = normalizeGitPath(line);
929
- if (norm)
930
- current.files.push(norm);
931
- }
932
- }
933
- if (current)
934
- commits.push(current);
935
- if (commits.length === 0) {
936
- if (isIncremental) {
937
- return `✅ brain_from_git: Brain is up to date — no new commits since last run (last SHA: \`${lastSha.slice(0, 8)}\`).`;
938
- }
939
- // Empty repo — first contact must still leave the user with a working path,
940
- // not a bare warning (roadmap P1-5: onboarding-magic for empty/small repos).
941
- return buildFirstContactReport({
942
- repoDir, revRange, processed: 0, ingested: 0, skipped: 0,
943
- durationMs: Date.now() - startedAt, isIncremental: false,
944
- instanceId: instance_id, categories: [], suggestedQueries: [],
945
- emptyReason: `No commits found in \`${repoDir}\` on branch \`${branch}\`${since ? ` since ${since}` : ''}.`,
946
- });
947
- }
948
- // Pattern classifiers
949
- const classifyCommit = (subject) => {
950
- const s = subject.toLowerCase();
951
- if (/\b(fix|fixed|fixes|bug|hotfix|patch|revert|resolve|closes? #\d+)\b/.test(s)) {
952
- const sev = /\b(critical|crash|security|auth|data loss|outage|prod|production)\b/.test(s) ? 'critical' : /\b(major|breaking|regression|hotfix)\b/.test(s) ? 'major' : 'minor';
953
- return { category: 'fix', outcome: 'success', severity: sev };
954
- }
955
- if (/\b(feat|feature|add|added|implement|new|introduce)\b/.test(s))
956
- return { category: 'feat', outcome: 'success', severity: 'minor' };
957
- if (/\b(refactor|clean|cleanup|improve|simplify|extract|rename)\b/.test(s))
958
- return { category: 'refactor', outcome: 'success', severity: 'minor' };
959
- if (/\b(perf|optimize|speed|cache|latency|memory|performance)\b/.test(s))
960
- return { category: 'perf', outcome: 'success', severity: 'major' };
961
- if (/\b(security|cve|auth|csrf|xss|sql|injection|sanitize|escape|encrypt)\b/.test(s))
962
- return { category: 'security', outcome: 'success', severity: 'critical' };
963
- if (/\b(deploy|ci|cd|build|docker|k8s|helm|infra|devops)\b/.test(s))
964
- return { category: 'deploy', outcome: 'success', severity: 'major' };
965
- if (/\b(test|spec|coverage|assert|mock|unit|integration)\b/.test(s))
966
- return { category: 'test', outcome: 'success', severity: 'minor' };
967
- return { category: 'chore', outcome: 'success', severity: 'minor' };
968
- };
969
- // Extract domain keywords from commit subject
970
- const extractDomain = (subject) => {
971
- const s = subject.toLowerCase();
972
- const tokens = s.replace(/[^a-z0-9\s\-_]/g, ' ').split(/\s+/).filter(t => t.length > 3 && !['that', 'this', 'with', 'from', 'when', 'into', 'also', 'some', 'were'].includes(t));
973
- return tokens.slice(0, 3).join('-') || 'general';
974
- };
975
- const ts = new Date().toISOString();
976
- let ingested = 0;
977
- let skipped = 0;
978
- const categoryCount = new Map();
979
- const severityCount = new Map();
980
- const seededTopics = [];
981
- // Best commit to use for the proof-of-value recall — prefer a fix (highest signal).
982
- let proofCandidate = null;
983
- const total = commits.length;
984
- const progressInterval = Math.max(1, Math.floor(total / 10)); // emit ~10 progress updates
985
- let lastProgressAt = 0;
986
- process.stderr.write(`\n🧠 brain_from_git: processing ${total} commits from ${repoDir}...\n`);
987
- for (let i = 0; i < commits.length; i++) {
988
- const commit = commits[i];
989
- // Emit progress to stderr so editors can display it in MCP logs
990
- if (i - lastProgressAt >= progressInterval) {
991
- process.stderr.write(` ⏳ Processing ${i}/${total} commits (${ingested} lessons so far)...\n`);
992
- lastProgressAt = i;
993
- }
994
- if (!commit.subject) {
995
- skipped++;
996
- continue;
997
- }
998
- const { category, outcome, severity } = classifyCommit(commit.subject);
999
- const domain = extractDomain(commit.subject);
1000
- const topic = `${category}:${domain}`;
1001
- categoryCount.set(category, (categoryCount.get(category) ?? 0) + 1);
1002
- severityCount.set(severity, (severityCount.get(severity) ?? 0) + 1);
1003
- seededTopics.push(topic);
1004
- if (!proofCandidate || (category === 'fix' && !proofCandidate.topic.startsWith('fix:'))) {
1005
- proofCandidate = { topic, subject: commit.subject };
1006
- }
1007
- const commitFiles = commit.files.filter(f => f.length > 0).slice(0, 12);
1008
- const lessonObj = {
1009
- topic, outcome, severity,
1010
- what_worked: commit.subject.slice(0, 200),
1011
- what_failed: '',
1012
- context: `git:${commit.sha.slice(0, 8)} by ${commit.author} on ${commit.date}`,
1013
- author: commit.author || undefined,
1014
- file_paths: commitFiles,
1015
- commands: [`git show ${commit.sha.slice(0, 8)}`],
1016
- tags: ['brain_from_git', category, 'git-history'],
1017
- visibility: 'team',
1018
- depends_on: [], recall_count: 0, ts, verified_at: ts,
1019
- confidence: 0.55, // lower confidence for auto-inferred lessons
1020
- audit_trail: [{ ts, action: 'brain_from_git', sha: commit.sha.slice(0, 8) }],
1021
- version: 3,
1022
- };
1023
- // Only store if no existing lesson for this topic (avoid overwriting higher-confidence lessons)
1024
- const existing = await redis.get(`cachly:lesson:best:${topic}`);
1025
- if (!existing) {
1026
- // Auto-inferred git lessons get a 90-day TTL — they self-refresh on the next
1027
- // brain_from_git run, and shouldn't pin memory forever if the repo goes stale.
1028
- await redis.set(`cachly:lesson:best:${topic}`, JSON.stringify(lessonObj), 'EX', 90 * 86400);
1029
- await redis.rpush(`cachly:lessons:${topic}`, JSON.stringify(lessonObj));
1030
- await redis.ltrim(`cachly:lessons:${topic}`, -100, -1);
1031
- await redis.expire(`cachly:lessons:${topic}`, 90 * 86400);
1032
- }
1033
- await redis.rpush('cachly:lessons:brain_from_git:all', JSON.stringify({ topic, sha: commit.sha.slice(0, 8), subject: commit.subject.slice(0, 60) }));
1034
- await redis.ltrim('cachly:lessons:brain_from_git:all', -500, -1);
1035
- await redis.expire('cachly:lessons:brain_from_git:all', 90 * 86400);
1036
- // Update CKG — concept node + person node + file nodes
1037
- const conceptId = ckgSlug(topic);
1038
- await ckgUpsertNode(redis, conceptId, category, 'git-derived');
1039
- // Phase 3A: auto-build person + file nodes from git history
1040
- if (commit.author) {
1041
- try {
1042
- const personId = await ckgUpsertPersonNode(redis, commit.author, category);
1043
- await ckgUpdateEdge(redis, personId, 'authored', conceptId, outcome === 'success', outcome === 'partial');
1044
- for (const fp of commitFiles.slice(0, 8)) {
1045
- const fileId = await ckgUpsertFileNode(redis, fp);
1046
- await ckgUpdateEdge(redis, personId, 'touched', fileId, true);
1047
- // Phase 3: build the person↔person collaboration graph from shared files.
1048
- await ckgRecordCollaboration(redis, fileId, personId);
1049
- }
1050
- }
1051
- catch { /* non-critical */ }
1052
- }
1053
- ingested++;
1054
- }
1055
- process.stderr.write(` ✅ brain_from_git complete: ${ingested}/${total} commits ingested\n\n`);
1056
- // Save the latest commit SHA for incremental runs
1057
- if (commits.length > 0 && commits[0]?.sha) {
1058
- await redis.set(lastShaKey, commits[0].sha, 'EX', 90 * 24 * 3600); // expire after 90 days
1059
- }
1060
- _lastBrainFromGitCounts = {
1061
- fixes: categoryCount.get('fix') ?? 0,
1062
- features: categoryCount.get('feat') ?? 0,
1063
- refactors: categoryCount.get('refactor') ?? 0,
1064
- total: ingested,
1065
- };
1066
- // ── Proof of value: run ONE real recall against a topic seeded seconds ago,
1067
- // so the first-contact response already demonstrates a working search hit.
1068
- let proof = null;
1069
- if (proofCandidate && ingested > 0) {
1070
- try {
1071
- const hits = await keywordSearch(redis, ['cachly:lesson:best:*'], proofCandidate.subject, 1);
1072
- if (hits.length > 0) {
1073
- const parsed = safeJsonParse(hits[0].content, {});
1074
- proof = {
1075
- query: proofCandidate.subject.slice(0, 80),
1076
- topic: parsed.topic ?? hits[0].key.replace('cachly:lesson:best:', ''),
1077
- snippet: (parsed.what_worked ?? '').slice(0, 120) || proofCandidate.subject.slice(0, 120),
1078
- };
1079
- }
1080
- }
1081
- catch { /* proof is best-effort — never fail the seeding response */ }
1082
- }
1083
- return buildFirstContactReport({
1084
- repoDir, revRange,
1085
- processed: commits.length, ingested, skipped,
1086
- durationMs: Date.now() - startedAt,
1087
- isIncremental, lastSha,
1088
- instanceId: instance_id,
1089
- categories: [...categoryCount.entries()].sort((a, b) => b[1] - a[1]),
1090
- severities: [...severityCount.entries()].sort((a, b) => b[1] - a[1]),
1091
- proof,
1092
- suggestedQueries: suggestRecallQueries(seededTopics, instance_id),
1093
- });
1094
- }
1095
- finally {
1096
- _gitSemRelease();
1097
- }
1098
- }
1099
- // ── brain_from_ci ─────────────────────────────────────────────────────────
1100
- case 'brain_from_ci': {
1101
- const { instance_id, outcomes } = args;
1102
- const redis = await getConnection(instance_id);
1103
- const ts = Date.now();
1104
- let fixes = 0;
1105
- let breaks = 0;
1106
- let stable = 0;
1107
- const total = outcomes.length;
1108
- for (const entry of outcomes) {
1109
- const status = String(entry.status ?? '');
1110
- const prev_status = String(entry.prev_status ?? '');
1111
- const job = String(entry.job ?? 'unknown');
1112
- const ciCtx = String(entry.context ?? '');
1113
- const isFixed = ['failure', 'red', 'error'].includes(prev_status) && ['success', 'green', 'passed'].includes(status);
1114
- const isBroken = ['success', 'green', 'passed'].includes(prev_status) && ['failure', 'red', 'error'].includes(status);
1115
- const slug = `ci:${ckgSlug(job)}`;
1116
- const conceptId = ckgSlug(slug);
1117
- await ckgUpsertNode(redis, conceptId, 'ci', 'job');
1118
- if (isFixed) {
1119
- const problemId = ckgSlug(`problem:${ckgSlug(job)}`);
1120
- await ckgUpsertNode(redis, problemId, 'problem', 'ci-failure');
1121
- await ckgUpdateEdge(redis, conceptId, 'fixes', problemId, true);
1122
- const lessonObj = {
1123
- topic: slug, outcome: 'success',
1124
- what_worked: `CI job "${job}" went ${prev_status} → ${status}`,
1125
- what_failed: `Job "${job}" was failing`, context: `brain_from_ci: ${ciCtx}`,
1126
- severity: 'major', file_paths: [], commands: [], tags: ['brain_from_ci', 'ci'],
1127
- depends_on: [], recall_count: 0, ts, verified_at: ts, confidence: 0.65,
1128
- audit_trail: [{ ts, action: 'brain_from_ci_fixed' }], version: 3,
1129
- };
1130
- await redis.rpush(`cachly:lessons:${slug}`, JSON.stringify(lessonObj));
1131
- await redis.ltrim(`cachly:lessons:${slug}`, -100, -1);
1132
- await redis.set(`cachly:lesson:best:${slug}`, JSON.stringify(lessonObj));
1133
- fixes++;
1134
- }
1135
- else if (isBroken) {
1136
- const causeId = ckgSlug(`cause:${ckgSlug(job)}`);
1137
- await ckgUpsertNode(redis, causeId, 'cause', 'ci-break');
1138
- await ckgUpdateEdge(redis, conceptId, 'causes', causeId, false);
1139
- breaks++;
1140
- }
1141
- else {
1142
- stable++;
1143
- }
1144
- const clsKey = `cachly:cls:${instance_id}`;
1145
- await redis.rpush(clsKey, JSON.stringify({ source: 'ci_outcome', payload: { status, prev_status, job }, ts }));
1146
- await redis.ltrim(clsKey, -200, -1);
1147
- }
1148
- _lastBrainFromCiCounts = { fixes, breaks, stable, total };
1149
- return [
1150
- `📥 **brain_from_ci**: Ingested ${total} outcomes — ${fixes} fixes learned, ${breaks} breaks noted, ${stable} stable`,
1151
- '',
1152
- fixes > 0 ? `✅ ${fixes} fix lesson${fixes !== 1 ? 's' : ''} written (confidence 0.65) — CKG \`fixes\` edges added` : '',
1153
- breaks > 0 ? `🔴 ${breaks} break${breaks !== 1 ? 's' : ''} noted — CKG \`causes\` edges added` : '',
1154
- stable > 0 ? `📊 ${stable} stable outcome${stable !== 1 ? 's' : ''} recorded` : '',
1155
- '',
1156
- `💡 Explore: \`brain_search(query="ci")\` | \`brain_predict(context="<job-name>")\``,
1157
- ].filter(Boolean).join('\n');
1158
- }
1159
- // ── brain_predict_failures ─────────────────────────────────────────────────
1160
- case 'brain_predict_failures': {
1161
- const { instance_id, context: ctx, top_k = 5, format = 'detailed' } = args;
1162
- const redis = await getConnection(instance_id);
1163
- const ctxTokens = ctx.toLowerCase().replace(/[^a-z0-9\s\-_:.]/g, ' ').split(/\s+/).filter(t => t.length > 2);
1164
- const failures = [];
1165
- // Step 1: CKG — find 'causes' and 'degrades_under' edges from context tokens
1166
- for (const token of ctxTokens.slice(0, 8)) {
1167
- const nodeKeys = [];
1168
- const nStream = redis.scanStream({ match: `cachly:ckg:node:*${token}*`, count: 50 });
1169
- await new Promise((res, rej) => { nStream.on('data', (b) => nodeKeys.push(...b)); nStream.on('end', res); nStream.on('error', rej); });
1170
- for (const nk of nodeKeys.slice(0, 5)) {
1171
- const nodeRaw = await redis.get(nk);
1172
- if (!nodeRaw)
1173
- continue;
1174
- const node = safeJsonParse(nodeRaw, null);
1175
- if (!node)
1176
- continue;
1177
- const edgeKeys = await redis.smembers(`cachly:ckg:idx:from:${node.id}`);
1178
- for (const ek of edgeKeys.slice(0, 20)) {
1179
- const edgeRaw = await redis.get(ek);
1180
- if (!edgeRaw)
1181
- continue;
1182
- const edge = safeJsonParse(edgeRaw, null);
1183
- if (!edge)
1184
- continue;
1185
- if (edge.edgeType !== 'causes' && edge.edgeType !== 'degrades_under')
1186
- continue;
1187
- // Look up fix for this failure from CKG 'fixes' edges
1188
- const fixEdgeKeys = await redis.smembers(`cachly:ckg:idx:from:${edge.to}`);
1189
- let fix;
1190
- for (const fek of fixEdgeKeys.slice(0, 10)) {
1191
- const feRaw = await redis.get(fek);
1192
- if (!feRaw)
1193
- continue;
1194
- const fe = safeJsonParse(feRaw, null);
1195
- if (!fe)
1196
- continue;
1197
- if (fe.edgeType === 'fixes') {
1198
- const lessonRaw = await redis.get(`cachly:lesson:best:${fe.from}`);
1199
- if (lessonRaw) {
1200
- const lesson = safeJsonParse(lessonRaw, null);
1201
- fix = lesson?.what_worked?.slice(0, 120);
1202
- break;
1203
- }
1204
- }
1205
- }
1206
- failures.push({
1207
- concept: node.id,
1208
- failure: edge.to.replace(/-/g, ' '),
1209
- probability: edge.confidence,
1210
- fix,
1211
- source: 'ckg',
1212
- });
1213
- }
1214
- }
1215
- }
1216
- // Step 2: Lesson history — find failure-outcome lessons matching context
1217
- const lessonKeys = [];
1218
- const lStream = redis.scanStream({ match: 'cachly:lesson:best:*', count: 300 });
1219
- await new Promise((res, rej) => { lStream.on('data', (b) => lessonKeys.push(...b)); lStream.on('end', res); lStream.on('error', rej); });
1220
- for (const k of lessonKeys.slice(0, 100)) {
1221
- const r = await redis.get(k);
1222
- if (!r)
1223
- continue;
1224
- try {
1225
- const l = JSON.parse(r);
1226
- if (l.outcome !== 'failure' && l.outcome !== 'partial')
1227
- continue;
1228
- if (!l.what_failed)
1229
- continue;
1230
- const haystack = `${l.topic} ${l.what_failed}`.toLowerCase();
1231
- const matchScore = ctxTokens.filter(t => haystack.includes(t)).length / Math.max(1, ctxTokens.length);
1232
- if (matchScore < 0.15)
1233
- continue;
1234
- const sevBoost = l.severity === 'critical' ? 0.15 : l.severity === 'major' ? 0.05 : 0;
1235
- failures.push({
1236
- concept: l.topic,
1237
- failure: l.what_failed.slice(0, 80),
1238
- probability: Math.min(0.97, (l.confidence ?? 0.5) * matchScore * 1.5 + sevBoost),
1239
- fix: l.what_worked?.slice(0, 120),
1240
- topic: l.topic,
1241
- source: 'lesson',
1242
- });
1243
- }
1244
- catch { /* skip */ }
1245
- }
1246
- if (failures.length === 0) {
1247
- return [
1248
- `🔮 **Failure Prediction: "${ctx}"**`, '',
1249
- `No known failure patterns found for this context.`,
1250
- `💡 The brain learns from every \`learn_from_attempts(outcome="failure")\` call.`,
1251
- `🔍 Try: \`brain_predict(context="${ctx}")\` for broader predictions.`,
1252
- ].join('\n');
1253
- }
1254
- // Deduplicate and rank by probability
1255
- const seen = new Set();
1256
- const ranked = failures.filter(f => {
1257
- const k = f.failure.slice(0, 40);
1258
- if (seen.has(k))
1259
- return false;
1260
- seen.add(k);
1261
- return true;
1262
- }).sort((a, b) => b.probability - a.probability).slice(0, Number(top_k));
1263
- const lines = [`🔮 **Failure Prediction for: "${ctx}"**\n`];
1264
- lines.push(`> Pre-deploy failure analysis based on ${failures.length} patterns. Ranked by probability.\n`);
1265
- for (let i = 0; i < ranked.length; i++) {
1266
- const f = ranked[i];
1267
- const pct = Math.round(f.probability * 100);
1268
- const bar = '█'.repeat(Math.round(pct / 10)) + '░'.repeat(10 - Math.round(pct / 10));
1269
- const icon = pct >= 70 ? '🔴' : pct >= 40 ? '🟡' : '🟢';
1270
- lines.push(`${icon} **${i + 1}. ${f.failure}**`);
1271
- lines.push(` Probability: ${bar} **${pct}%** (${f.source === 'ckg' ? 'CKG causal edge' : 'lesson history'})`);
1272
- if (format === 'detailed' && f.fix) {
1273
- lines.push(` ✅ Pre-loaded fix: _${f.fix}_`);
1274
- }
1275
- if (format === 'detailed' && f.topic) {
1276
- lines.push(` 📚 Lesson: \`${f.topic}\``);
1277
- }
1278
- lines.push('');
1279
- }
1280
- const highRisk = ranked.filter(f => f.probability >= 0.6);
1281
- if (highRisk.length > 0) {
1282
- lines.push(`⚠️ **${highRisk.length} high-risk failure${highRisk.length > 1 ? 's' : ''} detected** (≥60% probability). Review fixes before proceeding.`);
1283
- }
1284
- else {
1285
- lines.push(`✅ No high-risk failures detected. Proceed with caution and monitor closely.`);
1286
- }
1287
- lines.push('', `💡 After deploy: \`learn_from_attempts(topic="deploy:...", outcome="success|failure")\` to improve future predictions.`);
1288
- return lines.join('\n');
1289
- }
1290
- // ── Move 5: brain_contribute_signal — privacy-safe federation ────────────
1291
- case 'brain_contribute_signal': {
1292
- const { instance_id, topic_category, outcome, confidence = 0.5 } = args;
1293
- if (!topic_category?.trim())
1294
- throw new Error('topic_category is required');
1295
- if (!['success', 'failure', 'partial'].includes(outcome)) {
1296
- throw new Error('outcome must be success, failure, or partial');
1297
- }
1298
- // Bucket locally before sending — no raw confidence value leaves the instance.
1299
- const bucket = confidence >= 0.75 ? 'high' : confidence >= 0.5 ? 'medium' : 'low';
1300
- let apiResult = '📦 Signal stored locally (commons API unavailable)';
1301
- try {
1302
- await apiFetch('/api/v1/federation/signals', {
1303
- method: 'POST',
1304
- body: JSON.stringify({
1305
- topic_category: topic_category.trim().toLowerCase(),
1306
- outcome,
1307
- confidence: confidence,
1308
- }),
1309
- });
1310
- apiResult = '✅ Signal contributed to the global commons (no raw data shared)';
1311
- }
1312
- catch {
1313
- // Store in local outbox for later sync
1314
- const redis = await getConnection(instance_id);
1315
- const outboxKey = `cachly:fed:outbox:${Date.now()}`;
1316
- await redis.set(outboxKey, JSON.stringify({ topic_category, outcome, bucket, ts: new Date().toISOString() }), 'EX', 60 * 60 * 24 * 7).catch(() => { });
1317
- }
1318
- return [
1319
- `🔒 **Brain Contribute Signal: \`${topic_category}\`**`,
1320
- '',
1321
- `📊 Outcome: **${outcome}** · Confidence bucket: **${bucket}**`,
1322
- `🛡️ Privacy: topic category only — no lesson text, no org identity`,
1323
- '',
1324
- apiResult,
1325
- '',
1326
- `💡 When ≥ k orgs contribute the same pattern, a meta-lesson appears in \`brain_import_meta\`.`,
1327
- ].join('\n');
1328
- }
1329
- // ── Move 5: brain_import_meta — import k-anonymous meta-lessons ──────────
1330
- case 'brain_import_meta': {
1331
- const { instance_id, category, limit = 20 } = args;
1332
- let metas = [];
1333
- let kThreshold = 3;
1334
- try {
1335
- const qs = new URLSearchParams();
1336
- if (category)
1337
- qs.set('category', category);
1338
- qs.set('limit', String(Math.min(limit, 200)));
1339
- const resp = await apiFetch(`/api/v1/federation/meta?${qs.toString()}`);
1340
- metas = resp.meta_lessons ?? [];
1341
- kThreshold = resp.k_threshold ?? 3;
1342
- }
1343
- catch {
1344
- return '❌ Could not reach the global commons API. Try again later.';
1345
- }
1346
- if (metas.length === 0) {
1347
- return [
1348
- `🌐 **Brain Import Meta-Lessons**`,
1349
- '',
1350
- `No meta-lessons available yet (k-threshold: ${kThreshold} signals required).`,
1351
- '',
1352
- `💡 Contribute signals with \`brain_contribute_signal\` to help build the commons.`,
1353
- ].join('\n');
1354
- }
1355
- // Store meta-lessons in local Brain as state:'meta' (never overwrite real lessons).
1356
- const redis = await getConnection(instance_id);
1357
- let imported = 0;
1358
- for (const m of metas) {
1359
- const localKey = `cachly:lesson:best:meta:${m.topic_category}`;
1360
- const existing = await redis.get(localKey);
1361
- if (!existing) {
1362
- await redis.set(localKey, JSON.stringify({
1363
- topic: `meta:${m.topic_category}`,
1364
- outcome: m.dominant_outcome,
1365
- // "independent orgs" only became an honest claim once the API
1366
- // started counting DISTINCT contributors (KAI-96). Older servers
1367
- // omit the field — then say what we actually know: signal rows.
1368
- what_worked: m.contributor_count != null
1369
- ? `Meta-pattern from ${m.contributor_count} independent orgs`
1370
- : `Meta-pattern from ${m.signal_count} signals`,
1371
- what_failed: '',
1372
- recall_count: 0,
1373
- ts: m.derived_at,
1374
- state: 'meta',
1375
- avg_confidence: m.avg_confidence,
1376
- signal_count: m.signal_count,
1377
- audit_trail: [{ ts: new Date().toISOString(), action: 'imported_meta' }],
1378
- }));
1379
- imported++;
1380
- }
1381
- }
1382
- const lines = [
1383
- `🌐 **Brain Import Meta-Lessons** (k ≥ ${kThreshold})`,
1384
- '',
1385
- `| Topic Category | Outcome | Confidence | Signals |`,
1386
- `|---|---|---|---|`,
1387
- ];
1388
- for (const m of metas.slice(0, 15)) {
1389
- const conf = (m.avg_confidence * 100).toFixed(0) + '%';
1390
- lines.push(`| \`${m.topic_category}\` | ${m.dominant_outcome} | ${conf} | ${m.signal_count} |`);
1391
- }
1392
- if (metas.length > 15)
1393
- lines.push(`| _…${metas.length - 15} more_ | | | |`);
1394
- lines.push('', `✅ **${imported}** new meta-lessons imported into local Brain.`);
1395
- if (imported < metas.length) {
1396
- lines.push(`_(${metas.length - imported} already present — not overwritten)_`);
1397
- }
1398
- return lines.join('\n');
1399
- }
1400
- // ── brain_watch ───────────────────────────────────────────────────────────
1401
- case 'brain_watch': {
1402
- const { instance_id = '', project_dir = '.', api_key } = args;
1403
- const { resolve } = await import('node:path');
1404
- const projectDir = resolve(project_dir);
1405
- const { result, hookPath } = await installBrainWatchHook(projectDir, instance_id, api_key);
1406
- if (result === 'skipped-no-git') {
1407
- return [
1408
- `⚠️ **brain_watch: no git repository found**`,
1409
- '',
1410
- `No \`.git\` directory in \`${projectDir}\`.`,
1411
- `Pass \`project_dir\` pointing to a git checkout.`,
1412
- ].join('\n');
1413
- }
1414
- const statusLine = {
1415
- written: '✅ Hook installed (new)',
1416
- upgraded: '🔄 Hook upgraded to latest version',
1417
- appended: '📎 Hook appended to existing post-commit script',
1418
- unchanged: '✓ Hook already up to date — nothing changed',
1419
- };
1420
- return [
1421
- `🧠 **brain_watch ${statusLine[result] ?? result}**`,
1422
- '',
1423
- `Hook path: \`${hookPath}\``,
1424
- `Instance: \`${instance_id || '(none — set CACHLY_BRAIN_INSTANCE_ID or pass instance_id)'}\``,
1425
- '',
1426
- `Every future commit will automatically teach your Brain.`,
1427
- `No manual \`brain_from_git\` needed — the hook runs silently in the background.`,
1428
- '',
1429
- `💡 Verify: \`git commit --allow-empty -m "test brain_watch"\` then \`smart_recall(query="test")\``,
1430
- ].join('\n');
1431
- }
1432
- default:
1433
- return null;
1434
- }
1435
- }
1436
- //# sourceMappingURL=fedbrain.js.map
1
+ import{createHmac as ie}from"node:crypto";import{ckgSlug as $,extractProblemConcept as ae,ckgUpsertNode as J,ckgUpdateEdge as T,ckgUpsertPersonNode as ce,ckgUpsertFileNode as re,ckgRecordCollaboration as le}from"../ckg.js";import{safeJsonParse as A,normalizeGitPath as ue}from"../utils.js";import{buildClsPostCommitHook as de}from"../cls-hook.js";import{installBrainWatchHook as fe}from"../brain-watch-hook.js";import{keywordSearch as he}from"../search.js";import{buildFirstContactReport as te,suggestRecallQueries as me}from"../first-contact.js";let pe=null,ge=null,W=0;const _e=10,se=[];function ye(){return W<_e?(W++,Promise.resolve()):new Promise(E=>se.push(E))}function we(){const E=se.shift();E?E():W--}const Oe=new Set(["madc_deliberate","cls_ingest","cls_install_hooks","fedbrain_contribute","fedbrain_search","fedbrain_confirm","fedbrain_status","brain_federate","crystal_view","compact_recover","brain_from_git","brain_from_ci","brain_predict_failures","brain_contribute_signal","brain_import_meta","brain_watch"]);async function Ne(E,N,I,G){switch(E){case"madc_deliberate":{const{instance_id:y,topic:a}=N,n=await I(y),e=(await n.lrange(`cachly:lessons:${a}`,0,-1)).map(g=>{try{return JSON.parse(g)}catch{return null}}).filter(Boolean);if(e.length<2)return[`\u{1F5F3}\uFE0F **MADC: "${a}"**`,"",`Not enough history for deliberation (need \u2265 2 entries, found ${e.length}).`,"","Call `learn_from_attempts` with conflicting outcomes to trigger deliberation."].join(`
2
+ `);const l=[{name:"InfraAgent",domains:["infra","k8s","docker","server","wireguard","helm"]},{name:"AuthAgent",domains:["auth","jwt","keycloak","oauth","oidc","token"]},{name:"DeployAgent",domains:["deploy","ci","pipeline","rsync","release"]},{name:"DatabaseAgent",domains:["db","gorm","migration","postgres","clickhouse","redis"]},{name:"DebugAgent",domains:["debug","panic","race","nil","fix","error"]},{name:"APIAgent",domains:["api","http","grpc","rest","fiber","web"]}],u=a.split(":")[0]??"",c=l.filter(g=>g.domains.some(S=>u===S||a.includes(S))),o=c.length>0?c:l,d=new Map;for(const g of o){let S=0;for(const k of g.domains){const x=[],j=n.scanStream({match:`cachly:ckg:node:${k}*`,count:50});await new Promise((R,D)=>{j.on("data",P=>x.push(...P)),j.on("end",R),j.on("error",D)}),S+=x.length}d.set(g.name,S)}const h=e.filter(g=>g.outcome==="success"||g.outcome==="partial"),s=e.filter(g=>g.outcome==="failure");if(s.length===0)return[`\u{1F5F3}\uFE0F **MADC: "${a}"**`,"",`No contradictions found \u2014 all ${h.length} entries have non-failure outcomes.`,"",`Use \`ckg_inspect(concept="${$(a)}")\` to explore the confidence graph.`].join(`
3
+ `);const i=[];for(const g of o){const S=d.get(g.name)??0;let k,x;S<2?(k="abstain",x="insufficient domain coverage"):h.length>=s.length*2?(k="success",x=`${h.length}/${e.length} entries confirm success`):s.length>=h.length*2?(k="failure",x=`${s.length}/${e.length} entries confirm failure`):(k="abstain",x=`contested (${h.length} success vs ${s.length} failure)`),i.push({agent:g.name,vote:k,coverage:S,reason:x})}const r=i.filter(g=>g.vote==="success").length,f=i.filter(g=>g.vote==="failure").length,m=i.filter(g=>g.vote==="abstain").length;let p,w;r>0&&f===0?(p="unanimous_success",w=`\u2705 **Unanimous: SUCCESS** \u2014 ${r} agent(s) confirm, ${m} abstain`):f>0&&r===0?(p="unanimous_failure",w=`\u274C **Unanimous: FAILURE** \u2014 ${f} agent(s) confirm, ${m} abstain`):(p="contested",w=`\u26A0\uFE0F **CONTESTED** \u2014 ${r} success votes, ${f} failure votes, ${m} abstain`);const b=$(`resolution:${a}`),O={id:b,domain:"resolution",type:"resolution",count:1,ts:new Date().toISOString(),resolution:p,topic:a,votes:{success:r,failure:f,abstain:m}};if(await n.set(`cachly:ckg:node:${b}`,JSON.stringify(O)),p==="contested")await T(n,$(a),"contradicts",b,!1);else{if(((p==="unanimous_success"?"failure":"success")==="failure"?s:h).length>0){const k=await n.smembers(`cachly:ckg:idx:from:${$(a)}`);for(const x of k){const j=await n.get(x);if(j)try{const R=JSON.parse(j);R.edgeType==="fixes"&&R.confidence>.15&&p==="unanimous_failure"&&(R.confidence=.1,R.last_updated=new Date().toISOString(),await n.set(x,JSON.stringify(R)))}catch{}}}await n.del(`cachly:ckg:conflict:${$(a)}`)}const C=[`\u{1F5F3}\uFE0F **MADC Deliberation: "${a}"**`,"",`\u{1F4CA} Evidence: ${h.length} success/partial vs ${s.length} failure entries (${e.length} total)`,"",`**Voting agents (${o.length}):**`,...i.map(g=>{const S=g.vote==="success"?"\u2705":g.vote==="failure"?"\u274C":"\u2B1C",k="\u2593".repeat(Math.min(g.coverage,5))+"\u2591".repeat(Math.max(0,5-g.coverage));return` ${S} **${g.agent}** [${k}] ${g.coverage} CKG edges \u2014 ${g.reason}`}),"",w,""];return p==="unanimous_success"?C.push(`\u{1F527} Failure entries superseded \u2014 store confirmed lesson: \`learn_from_attempts(topic="${a}", outcome="success", ...)\``):p==="unanimous_failure"?C.push(`\u{1F6AB} Success claims unconfirmed \u2014 re-verify: \`recall_best_solution(topic="${a}")\``):C.push(`\u26A0\uFE0F Contested \u2014 run \`causal_trace\` before acting. Explore: \`ckg_inspect(concept="${$(a)}")\``),C.push("",`\u{1F4DD} Resolution node: \`cachly:ckg:node:${b}\``),C.join(`
4
+ `)}case"cls_ingest":{const{instance_id:y,source:a,payload:n}=N,t=await I(y),e=new Date().toISOString(),l="cachly:cls:events";if(a==="git_commit"){const u=String(n.message??""),c=String(n.sha??""),o=Array.isArray(n.files)?n.files:[],d=/^fix/i.test(u)?"fix":/^feat/i.test(u)?"feat":/^refactor/i.test(u)?"refactor":/^test/i.test(u)?"test":"commit",h=`${d}:${$(u.slice(0,60))}`,s=$(h);await J(t,s,d,"commit");for(const f of o.slice(0,10)){const m=f.includes("auth")?"auth":f.includes("api")?"api":f.includes("infra")?"infra":f.includes("web")?"web":"code",p=$(`file:${m}`);await J(t,p,"file",m),await T(t,s,"co-occurs",p,!0)}const i={topic:h,outcome:"success",what_worked:u,what_failed:"",context:`CLS/git: sha=${c}`,severity:"minor",file_paths:o.slice(0,10),commands:c?[`git show ${c}`]:[],tags:["cls","git"],depends_on:[],recall_count:0,ts:e,verified_at:e,confidence:.6,audit_trail:[{ts:e,action:"cls_git_commit"}],version:3};return await t.rpush(`cachly:lessons:${h}`,JSON.stringify(i)),await t.get(`cachly:lesson:best:${h}`)||await t.set(`cachly:lesson:best:${h}`,JSON.stringify(i)),await t.rpush(l,JSON.stringify({source:a,payload:{message:u,sha:c},ts:e})),await t.ltrim(l,-200,-1),["\u{1F4E8} **CLS Ingested: git_commit**","",`Commit \`${c.slice(0,8)||"?"}\`: ${u.slice(0,80)}`,`Concept: \`${s}\` (${d}) \xB7 Files: ${o.length}`,"",`\u{1F578}\uFE0F CKG: \`${s}\` + ${o.length} file edges \xB7 Lesson: \`${h}\``,`\u{1F4A1} Inspect: \`ckg_inspect(concept="${d}")\``].join(`
5
+ `)}if(a==="ci_outcome"){const u=String(n.status??""),c=String(n.prev_status??""),o=String(n.job??"unknown"),d=String(n.context??""),h=["failure","red","error"].includes(c)&&["success","green","passed"].includes(u),s=["success","green","passed"].includes(c)&&["failure","red","error"].includes(u),i=`ci:${$(o)}`,r=$(i);if(await J(t,r,"ci","job"),h){const m=$(`problem:${$(o)}`);await J(t,m,"problem","ci-failure"),await T(t,r,"fixes",m,!0);const p={topic:i,outcome:"success",what_worked:`CI job "${o}" went ${c} \u2192 ${u}`,what_failed:`Job "${o}" was failing`,context:`CLS/ci: ${d}`,severity:"major",file_paths:[],commands:[],tags:["cls","ci"],depends_on:[],recall_count:0,ts:e,verified_at:e,confidence:.75,audit_trail:[{ts:e,action:"cls_ci_fixed"}],version:3};await t.rpush(`cachly:lessons:${i}`,JSON.stringify(p)),await t.set(`cachly:lesson:best:${i}`,JSON.stringify(p))}else if(s){const m=$(`cause:${$(o)}`);await J(t,m,"cause","ci-break"),await T(t,r,"causes",m,!1)}return await t.rpush(l,JSON.stringify({source:a,payload:{status:u,prev_status:c,job:o},ts:e})),await t.ltrim(l,-200,-1),["\u{1F4E8} **CLS Ingested: ci_outcome**","",`${h?"\u2705 Fixed":s?"\u{1F534} Broken":"\u{1F4CA} Recorded"}: \`${o}\` \u2014 ${c||"?"} \u2192 ${u}`,h?"\u{1F527} CKG `fixes` edge added (75% confidence)":s?"\u26A1 CKG `causes` edge added":"\u{1F4CA} State recorded",`\u{1F4A1} Lesson: \`${i}\` | Predict: \`brain_predict(context="${o}")\``].join(`
6
+ `)}if(a==="ide_diagnostic"){const u=String(n.error??""),c=String(n.fix??""),o=String(n.file??""),d=ae(u)??"unknown-error",h=`debug:${$(d)}`,s=$(h),i=$(`problem:${d}`);await J(t,s,"debug","diagnostic"),await J(t,i,"problem","compiler-error"),await T(t,s,"fixes",i,!0);const r={topic:h,outcome:"success",what_worked:c,what_failed:u,context:`CLS/ide: ${o}`,severity:"minor",file_paths:o?[o]:[],commands:[],tags:["cls","ide-diagnostic"],depends_on:[],recall_count:0,ts:e,verified_at:e,confidence:.65,audit_trail:[{ts:e,action:"cls_ide_diagnostic"}],version:3};return await t.rpush(`cachly:lessons:${h}`,JSON.stringify(r)),await t.get(`cachly:lesson:best:${h}`)||await t.set(`cachly:lesson:best:${h}`,JSON.stringify(r)),await t.rpush(l,JSON.stringify({source:a,payload:{error:u.slice(0,60),fix:c.slice(0,60),file:o},ts:e})),await t.ltrim(l,-200,-1),["\u{1F4E8} **CLS Ingested: ide_diagnostic**","",`Error: \`${u.slice(0,80)}\``,`Fix: ${c.slice(0,100)}`,o?`File: \`${o}\``:"","",`\u{1F578}\uFE0F CKG: \`${s}\` \u2192 fixes \u2192 \`${i}\` | Lesson: \`${h}\``].filter(m=>m!=="").join(`
7
+ `)}return`\u274C Unknown CLS source: "${a}". Valid: git_commit, ci_outcome, ide_diagnostic`}case"cls_install_hooks":{const{instance_id:y,repo_path:a=".",hooks:n=["git","ci"]}=N,t=Array.isArray(n)?n:["git","ci"],e=[`\u{1F50C} **CLS Hook Installation Guide**
8
+ `];if(t.includes("git")){const l=de(y);e.push("### Git post-commit hook"),e.push("**Quick install (run once per repo):**"),e.push("```sh"),e.push(`cat > ${a}/.git/hooks/post-commit << 'HOOK'`),e.push(l),e.push("HOOK"),e.push(`chmod +x ${a}/.git/hooks/post-commit`),e.push("```"),e.push("After install: every `git commit` automatically updates your brain's CKG."),e.push("")}return t.includes("ci")&&(e.push("### GitHub Actions CI outcome hook"),e.push("**Add at the end of each job** (after build/test steps):"),e.push("```yaml"),e.push("- name: cachly CLS \u2014 record CI outcome"),e.push(" if: always()"),e.push(" run: |"),e.push(' node -e "'),e.push(" const r=require('https');"),e.push(` const d=JSON.stringify({instance_id:'${y}',source:'ci_outcome',payload:{`),e.push(" status:'${{ job.status }}',prev_status:'unknown',job:'${{ github.job }}',"),e.push(" context:'github-actions run ${{ github.run_number }}'}});"),e.push(" r.request({hostname:'api.cachly.dev',path:'/api/v1/cls/ingest',method:'POST',"),e.push(" headers:{'Content-Type':'application/json','Authorization':'Bearer $CACHLY_JWT',"),e.push(" 'Content-Length':d.length}},()=>{}).end(d);"),e.push(' " 2>/dev/null || true'),e.push(" env:"),e.push(" CACHLY_JWT: ${{ secrets.CACHLY_JWT }}"),e.push("```"),e.push("")),e.push('\u{1F4A1} Once installed: `brain_search(query="cls")` to verify events are arriving.'),e.push('\u{1F4CA} Monitor CKG growth: `ckg_inspect(concept="ci")` or `ckg_inspect(concept="fix")`'),e.join(`
9
+ `)}case"fedbrain_contribute":{const{instance_id:y,lesson_key:a,visibility:n="public"}=N,t=await I(y),e=await t.get(`cachly:lesson:best:${a}`);if(!e)return`\u274C Lesson \`${a}\` not found. Store it first with \`learn_from_attempts\`.`;const l=A(e,null);if(!l)return`\u274C Lesson \`${a}\` data is corrupted. Re-store with \`learn_from_attempts\`.`;const u=[l.topic.split(":")[0],...l.tags??[]].filter(Boolean),c=[...new Set(u)].sort().join(","),o=`${l.topic}:${l.outcome}:${l.what_worked}`,d=ie("sha256",`cachly-fedbrain:${y}`).update(o).digest("hex").slice(0,16),h={cert_id:d,lesson_key:a,visibility:n,domain_fingerprint:c,contributed_at:new Date().toISOString(),confirm_count:0,trust_score:l.outcome==="success"?.85:.5};await t.set(`cachly:fedbrain:cert:${d}`,JSON.stringify(h)),await t.sadd("cachly:fedbrain:contributed",d);let s;try{await G("/api/v1/syndication/contribute",{method:"POST",body:JSON.stringify({topic:l.topic,outcome:l.outcome,what_worked:l.what_worked,what_failed:l.what_failed??"",severity:l.severity??"major",cert_id:d,domain_fingerprint:c,visibility:n})}),s="\u2705 Contributed to global commons"}catch{s="\u{1F4E6} Stored locally (commons API unavailable \u2014 will sync when online)"}return[`\u{1F310} **FedBrain Contribute: "${a}"**`,"",`\u{1F4DC} Certificate: \`${d}\``,`\u{1F3F7}\uFE0F Domain fingerprint: ${u.slice(0,6).map(i=>`\`${i}\``).join(", ")}`,`\u{1F512} Visibility: ${n}`,"",s,"","\u{1F4A1} At 10 independent confirms \u2192 \u{1F3C6} Gold Standard",`\u{1F50D} Search: \`fedbrain_search(query="${l.topic.split(":").slice(-1)[0]}")\``].join(`
10
+ `)}case"fedbrain_search":{const{instance_id:y,query:a,context_hints:n=[],limit:t=10}=N,e=await I(y),l=await e.smembers("cachly:fedbrain:contributed"),u=new Map;for(const i of l.slice(0,30)){const r=await e.get(`cachly:fedbrain:cert:${i}`);if(r)try{const f=JSON.parse(r);for(const m of(f.domain_fingerprint??"").split(",").filter(Boolean))u.set(m,(u.get(m)??0)+1)}catch{}}for(const i of Array.isArray(n)?n:[])u.set(i.toLowerCase(),(u.get(i.toLowerCase())??0)+2);let c=[],o=!1;try{const i=new URLSearchParams({q:a,limit:String(Math.min(t*2,50))});c=(await G(`/api/v1/syndication/search?${i}`)).results??[]}catch{o=!0;const i=[],r=e.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((f,m)=>{r.on("data",p=>i.push(...p)),r.on("end",f),r.on("error",m)});for(const f of i.slice(0,60)){const m=await e.get(f);if(m)try{const p=JSON.parse(m),w=`${p.topic} ${p.what_worked??""} ${p.what_failed??""}`.toLowerCase();a.toLowerCase().split(/\s+/).some(b=>b.length>2&&w.includes(b))&&c.push({id:f.split(":").pop()??f,topic:p.topic,category:p.topic.split(":")[0],outcome:p.outcome,what_worked:p.what_worked??"",severity:p.severity??"major",confirm_count:0,created_at:p.ts??new Date().toISOString()})}catch{}}}const d=o?" \u2014 \u26A0\uFE0F Commons unreachable, local fallback (this machine only, not confirmed by the network)":"";if(c.length===0)return[`\u{1F310} **FedBrain Search: "${a}"**${d}`,"",'No results. Contribute: `fedbrain_contribute(lesson_key="fix:...")`'].join(`
11
+ `);const h=c.map(i=>{const r=(i.domain_fingerprint??i.category??"").split(",").filter(Boolean),f=r.reduce((b,O)=>b+(u.get(O)??0),0),m=u.size>0?f/Math.max(1,u.size+r.length):0,p=Math.min(1,i.confirm_count/10),w=m*.4+p*.4+(i.outcome==="success"?.2:0);return{...i,weightedScore:w,isGoldStandard:!o&&i.confirm_count>=10}}).sort((i,r)=>r.weightedScore-i.weightedScore).slice(0,t),s=[`\u{1F310} **FedBrain Search: "${a}"** \u2014 ${h.length} result${h.length!==1?"s":""} (context-weighted)${d}
12
+ `];for(const i of h){const r=i.outcome==="success"?"\u2705":i.outcome==="failure"?"\u274C":"\u26A0\uFE0F",f=i.isGoldStandard?" \u{1F3C6} _Gold Standard_":!o&&i.confirm_count>=3?` \u2713${i.confirm_count}`:"",m=Math.round(i.weightedScore*100);s.push(`${r}${f} **\`${i.topic}\`** [ctx: ${m}%]`),i.what_worked&&s.push(` \u2705 ${i.what_worked.slice(0,150)}`),i.what_failed&&s.push(` \u274C ${i.what_failed.slice(0,80)}`),s.push(` _${i.confirm_count} confirm${i.confirm_count!==1?"s":""} | id: \`${i.id}\`_`,"")}if(u.size>0){const i=[...u.entries()].sort((r,f)=>f[1]-r[1]).slice(0,5).map(([r,f])=>`\`${r}\`(${f})`).join(", ");s.push(`\u{1F3AF} Your context: ${i}`)}return s.push('\u{1F4A1} Confirm a result: `fedbrain_confirm(id="<id>", topic="<topic>", outcome="worked")`'),s.join(`
13
+ `)}case"fedbrain_confirm":{const{instance_id:y,id:a,topic:n,outcome:t}=N;if(!a)return["\u274C **FedBrain Confirm** needs the lesson `id` from `fedbrain_search` results.",'Run `fedbrain_search(query="...")`, copy the id shown next to a result,','then: `fedbrain_confirm(id="<id>", topic="<topic>", outcome="worked")`.'].join(`
14
+ `);const e=await I(y),l=new Date().toISOString(),u=JSON.stringify({id:a,topic:n,outcome:t,ts:l});await e.rpush("cachly:fedbrain:confirmations",u),await e.ltrim("cachly:fedbrain:confirmations",-200,-1);const c=t==="worked",o=t==="partially_worked";n&&await T(e,$(n),"fixes",$(`syndicated:${n}`),c,o);let d;try{await G(`/api/v1/syndication/${encodeURIComponent(a)}/confirm`,{method:"POST"}),d="\u2705 Confirmation propagated to global commons"}catch{await e.rpush("cachly:fedbrain:pending_confirms",u),await e.ltrim("cachly:fedbrain:pending_confirms",-50,-1),d="\u{1F4E6} Queued locally (API unavailable \u2014 will propagate on next online session)"}return[`${c?"\u2705":o?"\u26A0\uFE0F":"\u274C"} **FedBrain Confirm: "${n??a}"** \u2192 ${t}`,"",d,"",n?`\u{1F578}\uFE0F CKG confidence ${c||o?"boosted":"reduced"} for \`${$(n)}\``:"\u{1F578}\uFE0F Local CKG unchanged (pass `topic` to also update your own confidence graph)","\u{1F4A1} Your confirmation helps other brains worldwide.",'\u{1F4CA} Status: `fedbrain_status(instance_id="...")`'].join(`
15
+ `)}case"fedbrain_status":{const{instance_id:y}=N,a=await I(y),n=await a.smembers("cachly:fedbrain:contributed"),t=await a.lrange("cachly:fedbrain:confirmations",-10,-1),e=await a.llen("cachly:fedbrain:pending_confirms"),l=t.map(o=>{try{return JSON.parse(o)}catch{return null}}).filter(Boolean),u=[];for(const o of n.slice(0,15)){const d=await a.get(`cachly:fedbrain:cert:${o}`);if(d)try{const h=JSON.parse(d);u.push({cert_id:h.cert_id,lesson_key:h.lesson_key,confirm_count:h.confirm_count??0,trust_score:h.trust_score??.5,isGold:(h.confirm_count??0)>=10})}catch{}}const c=[`\u{1F310} **FedBrain Status**
16
+ `,`### \u{1F4E4} Contributed Lessons: ${n.length}`];if(u.length>0)for(const o of u){const d=o.isGold?" \u{1F3C6} Gold Standard":"",h="\u2588".repeat(Math.min(10,o.confirm_count))+"\u2591".repeat(Math.max(0,10-o.confirm_count));c.push(` \`${o.lesson_key}\` [${h}] \xD7${o.confirm_count}${d}`)}else c.push(' _None yet. Contribute with `fedbrain_contribute(lesson_key="fix:...")`_');if(c.push("",`### \u{1F4E5} Recent Confirmations: ${l.length}`),l.length>0)for(const o of l.slice(-5)){const d=o.outcome==="worked"?"\u2705":o.outcome==="partially_worked"?"\u26A0\uFE0F":"\u274C";c.push(` ${d} \`${o.topic}\` \u2014 ${o.outcome} (${new Date(o.ts).toLocaleDateString("de-DE")})`)}else c.push(" _None yet. Confirm syndicated lessons with `fedbrain_confirm`_");return e>0&&c.push("",`\u26A0\uFE0F ${e} confirmation${e!==1?"s":""} pending propagation`),c.push("","---",'**Contribute:** `fedbrain_contribute(lesson_key="fix:...")`','**Search:** `fedbrain_search(query="...")`','**Confirm:** `fedbrain_confirm(id="...", topic="...", outcome="worked")`'),c.join(`
17
+ `)}case"brain_federate":{const{instance_id:y,source:a,domain:n,min_confidence:t=.6,dry_run:e=!1}=N;if(y===a)return"\u274C Source and destination cannot be the same brain.";const l=await I(y),u=await I(a),c=n==="*"?"cachly:ckg:node:*":`cachly:ckg:node:${n}*`,o=[],d=u.scanStream({match:c,count:100});if(await new Promise((w,b)=>{d.on("data",O=>o.push(...O)),d.on("end",w),d.on("error",b)}),o.length===0)return[`\u{1F9E0} **brain_federate: "${n}"**`,"",`No CKG nodes found in source brain for domain \`${n}\`.`,"The source brain may not have knowledge in this area yet.",`Try: \`fedbrain_search(query="${n}")\` to find global commons knowledge instead.`].join(`
18
+ `);let h=0,s=0,i=0,r=0;const f=[];for(const w of o){const b=await u.get(w);if(!b)continue;let O;try{O=JSON.parse(b)}catch{continue}e||await l.set(w,b),h++;const C=await u.smembers(`cachly:ckg:idx:from:${O.id}`);for(const k of C){const x=await u.get(k);if(!x)continue;let j;try{j=JSON.parse(x)}catch{continue}if(j.confidence<t){r++;continue}e||(await l.set(k,x),await l.sadd(`cachly:ckg:idx:from:${j.from}`,k),await l.sadd(`cachly:ckg:idx:to:${j.to}`,k)),s++}const g=`cachly:lesson:best:${O.id}`,S=await u.get(g);if(S&&(e||await l.set(g,S),i++,f.length<8))try{const k=JSON.parse(S),x=k.outcome==="success"?"\u2705":k.outcome==="failure"?"\u274C":"\u26A0\uFE0F";f.push(` ${x} \`${k.topic}\` \u2014 ${(k.what_worked??"").slice(0,80)}`)}catch{}}if(!e){const w=JSON.stringify({source:a,domain:n,transferred_at:new Date().toISOString(),nodes:h,edges:s,lessons:i});await l.rpush("cachly:fedbrain:federations",w),await l.ltrim("cachly:fedbrain:federations",-50,-1)}const p=[`\u{1F9E0} **brain_federate: "${n}"**${e?" (DRY RUN \u2014 no writes)":""}`,"",`\u{1F4E4} Source: \`${a}\``,`\u{1F4E5} Destination: \`${y}\``,`\u{1F50D} Domain filter: \`${n}\` | min_confidence: ${t}`,"","### Transfer Summary",` \u{1F578}\uFE0F CKG nodes: ${h}`,` \u{1F517} CKG edges: ${s} (${r} skipped, confidence < ${t})`,` \u{1F4DA} Lessons: ${i}`,""];return f.length>0&&(p.push("### Sample lessons transferred:",...f),i>f.length&&p.push(` _... and ${i-f.length} more_`),p.push("")),e?p.push("\u{1F4A1} Run without `dry_run: true` to apply the transfer."):(p.push(`\u2705 Transfer complete. Your brain now has the \`${n}\` knowledge from \`${a}\`.`),p.push(`\u{1F50D} Explore: \`ckg_inspect(concept="${n}")\` | \`recall_best_solution(topic="${n}:...")\``)),p.join(`
19
+ `)}case"crystal_view":{const{instance_id:y,show_raw:a=!1}=N,n=await I(y),t=await n.get("cachly:crystal:latest");if(!t)return["\u{1F48E} **Memory Crystal: not yet created**","","No crystal found. Create one with `memory_crystalize()` to compress your accumulated wisdom.","","\u{1F4A1} Tip: run `memory_crystalize` monthly for best results."].join(`
20
+ `);const e=A(t,null);if(!e)return"\u274C Memory Crystal data is corrupted. Re-create with `memory_crystalize()`.";const l=Math.floor((Date.now()-new Date(e.ts).getTime())/864e5),u=l<=7?"\u{1F7E2}":l<=30?"\u{1F7E1}":"\u{1F534}",c=[`\u{1F48E} **Memory Crystal: ${e.label}**`,"",`\u{1F4C5} Created: ${new Date(e.ts).toLocaleDateString("de-DE")} (${l}d ago ${u})`,`\u{1F4CA} Compressed from: ${e.created_from}`,`\u{1F5C2}\uFE0F Categories: ${e.categories.slice(0,10).map(d=>`\`${d}\``).join(", ")}${e.categories.length>10?` +${e.categories.length-10} more`:""}`,"",`**\u{1F511} Top patterns (${e.top_patterns.length}):**`];for(const d of e.top_patterns)c.push(` \u2022 **${d.category}** (${d.count}\xD7): ${d.insight.slice(0,110)}`);l>30&&c.push("",`\u26A0\uFE0F Crystal is ${l}d old \u2014 run \`memory_crystalize()\` to refresh it.`),a&&c.push("","```json",JSON.stringify(e,null,2),"```");const o=await n.get("cachly:crystal:team:latest").catch(()=>null);if(o){const d=A(o,null);if(d&&d.patterns?.length){const h=Math.floor((Date.now()-new Date(d.ts).getTime())/864e5);c.push("","---",`\u{1F4A0} **Team Crystal: ${d.label}** _(${h}d ago \xB7 ${d.contributors} contributors)_`,"_Fixes multiple teammates independently converged on \u2014 the team-wide layer:_");for(const s of d.patterns.slice(0,5))c.push(` \u2022 \u{1F9E9} **${s.concept}** \u2014 ${s.author_count} people (${s.authors.map(i=>`@${i}`).join(", ")}): ${s.convergent_fix.slice(0,90)}`)}}return c.push("",'\u{1F4A1} Refresh: `memory_crystalize()` \xB7 `team_crystallize()` | Recover: `compact_recover(instance_id="...")`'),c.join(`
21
+ `)}case"compact_recover":{const{instance_id:y,focus:a=""}=N,n=await I(y),t=[`\u{1F501} **Compact Recovery Briefing**
22
+ `];t.push(`> *Call this first after any context limit hit. Reconstructs where you left off.*
23
+ `);const e=await n.get("cachly:crystal:latest");if(e){const s=A(e,null);if(s){t.push(`### \u{1F48E} Memory Crystal: ${s.label}`),t.push(`Compressed from ${s.session_count} sessions, ${s.lesson_count} lessons.`);const i=a?s.top_patterns.filter(r=>r.category.toLowerCase().includes(a.toLowerCase())||r.insight.toLowerCase().includes(a.toLowerCase())).slice(0,4):s.top_patterns.slice(0,4);for(const r of i)t.push(` \u2022 **${r.category}**: ${r.insight.slice(0,100)}`);t.push("")}}const l=await n.get("cachly:session:last");if(l){const s=A(l,null);s&&(t.push("### \u{1F550} Last Session"),s.focus&&t.push(`Focus: _${s.focus}_`),s.summary&&t.push(`Summary: ${s.summary.slice(0,300)}`),t.push(""))}const u=await n.get("cachly:session:handoff");if(u){const s=A(u,null);if(s){if(t.push("### \u{1F4CB} Handoff (from last window)"),s.context_summary&&t.push(`Context: ${s.context_summary.slice(0,200)}`),s.remaining_tasks?.length){t.push("Remaining tasks:");for(const i of s.remaining_tasks.slice(0,5))t.push(` \u2022 ${i}`)}s.instructions&&t.push(`\u26A0\uFE0F Instructions: ${s.instructions.slice(0,200)}`),s.blocked_on&&t.push(`\u{1F6A7} Blocked on: ${s.blocked_on}`),t.push("")}}const c=await n.get("cachly:ctx:wip-registry");if(c){const s=A(c,null);s?.content&&(t.push("### \u{1F527} WIP Registry"),t.push(s.content.slice(0,400)),t.push(""))}const o=[],d=n.scanStream({match:"cachly:roadmap:*",count:100});await new Promise((s,i)=>{d.on("data",r=>o.push(...r)),d.on("end",s),d.on("error",i)});const h=[];for(const s of o.slice(0,30)){const i=await n.get(s);if(i)try{const r=JSON.parse(i);(r.status==="in_progress"||r.status==="blocked")&&h.push({title:r.title??s,status:r.status,priority:r.priority})}catch{}}if(h.length>0){t.push("### \u{1F6A7} Open Items");for(const s of h.slice(0,5))t.push(` \u2022 [${s.status}] ${s.title}`);t.push("")}if(a){const s=[],i=n.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((f,m)=>{i.on("data",p=>s.push(...p)),i.on("end",f),i.on("error",m)});const r=[];for(const f of s){const m=await n.get(f);if(m)try{const p=JSON.parse(m);p.topic.toLowerCase().includes(a.toLowerCase())&&p.what_worked&&r.push({topic:p.topic,what_worked:p.what_worked})}catch{}}if(r.length>0){t.push(`### \u{1F4A1} Relevant Lessons for "${a}"`);for(const f of r.slice(0,4))t.push(` \u2022 **${f.topic}**: ${f.what_worked.slice(0,100)}`);t.push("")}}return t.length<=3&&t.push("_No brain data found. Start accumulating knowledge with `learn_from_attempts` and `session_start`._"),t.push("---","\u{1F9E0} Brain is ready. Continue your work \u2014 full context restored."),t.join(`
24
+ `)}case"brain_from_git":{const y=Date.now(),{instance_id:a,repo_path:n=".",limit:t=100,branch:e="HEAD",since:l="",incremental:u=!0}=N,c=await I(a),{execSync:o}=await import("node:child_process"),{resolve:d}=await import("node:path"),h=d(n),s=Math.min(Number(t)||100,500);await ye();try{try{o("git rev-parse --git-dir",{cwd:h,stdio:"pipe"})}catch{return`\u274C Not a git repository: \`${h}\`. Pass \`repo_path\` pointing to a git checkout.`}const i=`cachly:brain_from_git:last_sha:${Buffer.from(h).toString("base64").slice(0,32)}`;let r="",f=!1;u!==!1&&!l&&(r=await c.get(i)??"",f=!!r);const m=l?`--since="${l}"`:"",w=(f?`${r}..HEAD`:"")||e,b=`git log ${w} ${m} --pretty=format:"COMMIT|||%H|||%s|||%ad|||%an" --date=short --no-merges --name-only -n ${s}`;let O="";try{O=o(b,{cwd:h,encoding:"utf-8",stdio:"pipe"})}catch(v){return`\u274C git log failed: ${v.message}. Check \`repo_path\` and \`branch\`.`}const C=[];let g=null;for(const v of O.split(`
25
+ `)){const _=v.trim();if(_.startsWith("COMMIT|||")){g&&C.push(g);const[,L,B,F,U]=_.split("|||");g={sha:(L??"").trim(),subject:(B??"").trim(),date:(F??"").trim(),author:(U??"").trim(),files:[]}}else if(_&&g){const L=ue(_);L&&g.files.push(L)}}if(g&&C.push(g),C.length===0)return f?`\u2705 brain_from_git: Brain is up to date \u2014 no new commits since last run (last SHA: \`${r.slice(0,8)}\`).`:te({repoDir:h,revRange:w,processed:0,ingested:0,skipped:0,durationMs:Date.now()-y,isIncremental:!1,instanceId:a,categories:[],suggestedQueries:[],emptyReason:`No commits found in \`${h}\` on branch \`${e}\`${l?` since ${l}`:""}.`});const S=v=>{const _=v.toLowerCase();return/\b(fix|fixed|fixes|bug|hotfix|patch|revert|resolve|closes? #\d+)\b/.test(_)?{category:"fix",outcome:"success",severity:/\b(critical|crash|security|auth|data loss|outage|prod|production)\b/.test(_)?"critical":/\b(major|breaking|regression|hotfix)\b/.test(_)?"major":"minor"}:/\b(feat|feature|add|added|implement|new|introduce)\b/.test(_)?{category:"feat",outcome:"success",severity:"minor"}:/\b(refactor|clean|cleanup|improve|simplify|extract|rename)\b/.test(_)?{category:"refactor",outcome:"success",severity:"minor"}:/\b(perf|optimize|speed|cache|latency|memory|performance)\b/.test(_)?{category:"perf",outcome:"success",severity:"major"}:/\b(security|cve|auth|csrf|xss|sql|injection|sanitize|escape|encrypt)\b/.test(_)?{category:"security",outcome:"success",severity:"critical"}:/\b(deploy|ci|cd|build|docker|k8s|helm|infra|devops)\b/.test(_)?{category:"deploy",outcome:"success",severity:"major"}:/\b(test|spec|coverage|assert|mock|unit|integration)\b/.test(_)?{category:"test",outcome:"success",severity:"minor"}:{category:"chore",outcome:"success",severity:"minor"}},k=v=>v.toLowerCase().replace(/[^a-z0-9\s\-_]/g," ").split(/\s+/).filter(B=>B.length>3&&!["that","this","with","from","when","into","also","some","were"].includes(B)).slice(0,3).join("-")||"general",x=new Date().toISOString();let j=0,R=0;const D=new Map,P=new Map,z=[];let K=null;const q=C.length,oe=Math.max(1,Math.floor(q/10));let Y=0;process.stderr.write(`
26
+ \u{1F9E0} brain_from_git: processing ${q} commits from ${h}...
27
+ `);for(let v=0;v<C.length;v++){const _=C[v];if(v-Y>=oe&&(process.stderr.write(` \u23F3 Processing ${v}/${q} commits (${j} lessons so far)...
28
+ `),Y=v),!_.subject){R++;continue}const{category:L,outcome:B,severity:F}=S(_.subject),U=k(_.subject),M=`${L}:${U}`;D.set(L,(D.get(L)??0)+1),P.set(F,(P.get(F)??0)+1),z.push(M),(!K||L==="fix"&&!K.topic.startsWith("fix:"))&&(K={topic:M,subject:_.subject});const Q=_.files.filter(H=>H.length>0).slice(0,12),X={topic:M,outcome:B,severity:F,what_worked:_.subject.slice(0,200),what_failed:"",context:`git:${_.sha.slice(0,8)} by ${_.author} on ${_.date}`,author:_.author||void 0,file_paths:Q,commands:[`git show ${_.sha.slice(0,8)}`],tags:["brain_from_git",L,"git-history"],visibility:"team",depends_on:[],recall_count:0,ts:x,verified_at:x,confidence:.55,audit_trail:[{ts:x,action:"brain_from_git",sha:_.sha.slice(0,8)}],version:3};await c.get(`cachly:lesson:best:${M}`)||(await c.set(`cachly:lesson:best:${M}`,JSON.stringify(X),"EX",90*86400),await c.rpush(`cachly:lessons:${M}`,JSON.stringify(X)),await c.ltrim(`cachly:lessons:${M}`,-100,-1),await c.expire(`cachly:lessons:${M}`,90*86400)),await c.rpush("cachly:lessons:brain_from_git:all",JSON.stringify({topic:M,sha:_.sha.slice(0,8),subject:_.subject.slice(0,60)})),await c.ltrim("cachly:lessons:brain_from_git:all",-500,-1),await c.expire("cachly:lessons:brain_from_git:all",90*86400);const Z=$(M);if(await J(c,Z,L,"git-derived"),_.author)try{const H=await ce(c,_.author,L);await T(c,H,"authored",Z,B==="success",B==="partial");for(const ne of Q.slice(0,8)){const ee=await re(c,ne);await T(c,H,"touched",ee,!0),await le(c,ee,H)}}catch{}j++}process.stderr.write(` \u2705 brain_from_git complete: ${j}/${q} commits ingested
29
+
30
+ `),C.length>0&&C[0]?.sha&&await c.set(i,C[0].sha,"EX",2160*3600),pe={fixes:D.get("fix")??0,features:D.get("feat")??0,refactors:D.get("refactor")??0,total:j};let V=null;if(K&&j>0)try{const v=await he(c,["cachly:lesson:best:*"],K.subject,1);if(v.length>0){const _=A(v[0].content,{});V={query:K.subject.slice(0,80),topic:_.topic??v[0].key.replace("cachly:lesson:best:",""),snippet:(_.what_worked??"").slice(0,120)||K.subject.slice(0,120)}}}catch{}return te({repoDir:h,revRange:w,processed:C.length,ingested:j,skipped:R,durationMs:Date.now()-y,isIncremental:f,lastSha:r,instanceId:a,categories:[...D.entries()].sort((v,_)=>_[1]-v[1]),severities:[...P.entries()].sort((v,_)=>_[1]-v[1]),proof:V,suggestedQueries:me(z,a)})}finally{we()}}case"brain_from_ci":{const{instance_id:y,outcomes:a}=N,n=await I(y),t=Date.now();let e=0,l=0,u=0;const c=a.length;for(const o of a){const d=String(o.status??""),h=String(o.prev_status??""),s=String(o.job??"unknown"),i=String(o.context??""),r=["failure","red","error"].includes(h)&&["success","green","passed"].includes(d),f=["success","green","passed"].includes(h)&&["failure","red","error"].includes(d),m=`ci:${$(s)}`,p=$(m);if(await J(n,p,"ci","job"),r){const b=$(`problem:${$(s)}`);await J(n,b,"problem","ci-failure"),await T(n,p,"fixes",b,!0);const O={topic:m,outcome:"success",what_worked:`CI job "${s}" went ${h} \u2192 ${d}`,what_failed:`Job "${s}" was failing`,context:`brain_from_ci: ${i}`,severity:"major",file_paths:[],commands:[],tags:["brain_from_ci","ci"],depends_on:[],recall_count:0,ts:t,verified_at:t,confidence:.65,audit_trail:[{ts:t,action:"brain_from_ci_fixed"}],version:3};await n.rpush(`cachly:lessons:${m}`,JSON.stringify(O)),await n.ltrim(`cachly:lessons:${m}`,-100,-1),await n.set(`cachly:lesson:best:${m}`,JSON.stringify(O)),e++}else if(f){const b=$(`cause:${$(s)}`);await J(n,b,"cause","ci-break"),await T(n,p,"causes",b,!1),l++}else u++;const w=`cachly:cls:${y}`;await n.rpush(w,JSON.stringify({source:"ci_outcome",payload:{status:d,prev_status:h,job:s},ts:t})),await n.ltrim(w,-200,-1)}return ge={fixes:e,breaks:l,stable:u,total:c},[`\u{1F4E5} **brain_from_ci**: Ingested ${c} outcomes \u2014 ${e} fixes learned, ${l} breaks noted, ${u} stable`,"",e>0?`\u2705 ${e} fix lesson${e!==1?"s":""} written (confidence 0.65) \u2014 CKG \`fixes\` edges added`:"",l>0?`\u{1F534} ${l} break${l!==1?"s":""} noted \u2014 CKG \`causes\` edges added`:"",u>0?`\u{1F4CA} ${u} stable outcome${u!==1?"s":""} recorded`:"","",'\u{1F4A1} Explore: `brain_search(query="ci")` | `brain_predict(context="<job-name>")`'].filter(Boolean).join(`
31
+ `)}case"brain_predict_failures":{const{instance_id:y,context:a,top_k:n=5,format:t="detailed"}=N,e=await I(y),l=a.toLowerCase().replace(/[^a-z0-9\s\-_:.]/g," ").split(/\s+/).filter(r=>r.length>2),u=[];for(const r of l.slice(0,8)){const f=[],m=e.scanStream({match:`cachly:ckg:node:*${r}*`,count:50});await new Promise((p,w)=>{m.on("data",b=>f.push(...b)),m.on("end",p),m.on("error",w)});for(const p of f.slice(0,5)){const w=await e.get(p);if(!w)continue;const b=A(w,null);if(!b)continue;const O=await e.smembers(`cachly:ckg:idx:from:${b.id}`);for(const C of O.slice(0,20)){const g=await e.get(C);if(!g)continue;const S=A(g,null);if(!S||S.edgeType!=="causes"&&S.edgeType!=="degrades_under")continue;const k=await e.smembers(`cachly:ckg:idx:from:${S.to}`);let x;for(const j of k.slice(0,10)){const R=await e.get(j);if(!R)continue;const D=A(R,null);if(D&&D.edgeType==="fixes"){const P=await e.get(`cachly:lesson:best:${D.from}`);if(P){x=A(P,null)?.what_worked?.slice(0,120);break}}}u.push({concept:b.id,failure:S.to.replace(/-/g," "),probability:S.confidence,fix:x,source:"ckg"})}}}const c=[],o=e.scanStream({match:"cachly:lesson:best:*",count:300});await new Promise((r,f)=>{o.on("data",m=>c.push(...m)),o.on("end",r),o.on("error",f)});for(const r of c.slice(0,100)){const f=await e.get(r);if(f)try{const m=JSON.parse(f);if(m.outcome!=="failure"&&m.outcome!=="partial"||!m.what_failed)continue;const p=`${m.topic} ${m.what_failed}`.toLowerCase(),w=l.filter(O=>p.includes(O)).length/Math.max(1,l.length);if(w<.15)continue;const b=m.severity==="critical"?.15:m.severity==="major"?.05:0;u.push({concept:m.topic,failure:m.what_failed.slice(0,80),probability:Math.min(.97,(m.confidence??.5)*w*1.5+b),fix:m.what_worked?.slice(0,120),topic:m.topic,source:"lesson"})}catch{}}if(u.length===0)return[`\u{1F52E} **Failure Prediction: "${a}"**`,"","No known failure patterns found for this context.",'\u{1F4A1} The brain learns from every `learn_from_attempts(outcome="failure")` call.',`\u{1F50D} Try: \`brain_predict(context="${a}")\` for broader predictions.`].join(`
32
+ `);const d=new Set,h=u.filter(r=>{const f=r.failure.slice(0,40);return d.has(f)?!1:(d.add(f),!0)}).sort((r,f)=>f.probability-r.probability).slice(0,Number(n)),s=[`\u{1F52E} **Failure Prediction for: "${a}"**
33
+ `];s.push(`> Pre-deploy failure analysis based on ${u.length} patterns. Ranked by probability.
34
+ `);for(let r=0;r<h.length;r++){const f=h[r],m=Math.round(f.probability*100),p="\u2588".repeat(Math.round(m/10))+"\u2591".repeat(10-Math.round(m/10)),w=m>=70?"\u{1F534}":m>=40?"\u{1F7E1}":"\u{1F7E2}";s.push(`${w} **${r+1}. ${f.failure}**`),s.push(` Probability: ${p} **${m}%** (${f.source==="ckg"?"CKG causal edge":"lesson history"})`),t==="detailed"&&f.fix&&s.push(` \u2705 Pre-loaded fix: _${f.fix}_`),t==="detailed"&&f.topic&&s.push(` \u{1F4DA} Lesson: \`${f.topic}\``),s.push("")}const i=h.filter(r=>r.probability>=.6);return i.length>0?s.push(`\u26A0\uFE0F **${i.length} high-risk failure${i.length>1?"s":""} detected** (\u226560% probability). Review fixes before proceeding.`):s.push("\u2705 No high-risk failures detected. Proceed with caution and monitor closely."),s.push("",'\u{1F4A1} After deploy: `learn_from_attempts(topic="deploy:...", outcome="success|failure")` to improve future predictions.'),s.join(`
35
+ `)}case"brain_contribute_signal":{const{instance_id:y,topic_category:a,outcome:n,confidence:t=.5}=N;if(!a?.trim())throw new Error("topic_category is required");if(!["success","failure","partial"].includes(n))throw new Error("outcome must be success, failure, or partial");const e=t>=.75?"high":t>=.5?"medium":"low";let l="\u{1F4E6} Signal stored locally (commons API unavailable)";try{await G("/api/v1/federation/signals",{method:"POST",body:JSON.stringify({topic_category:a.trim().toLowerCase(),outcome:n,confidence:t})}),l="\u2705 Signal contributed to the global commons (no raw data shared)"}catch{const u=await I(y),c=`cachly:fed:outbox:${Date.now()}`;await u.set(c,JSON.stringify({topic_category:a,outcome:n,bucket:e,ts:new Date().toISOString()}),"EX",3600*24*7).catch(()=>{})}return[`\u{1F512} **Brain Contribute Signal: \`${a}\`**`,"",`\u{1F4CA} Outcome: **${n}** \xB7 Confidence bucket: **${e}**`,"\u{1F6E1}\uFE0F Privacy: topic category only \u2014 no lesson text, no org identity","",l,"","\u{1F4A1} When \u2265 k orgs contribute the same pattern, a meta-lesson appears in `brain_import_meta`."].join(`
36
+ `)}case"brain_import_meta":{const{instance_id:y,category:a,limit:n=20}=N;let t=[],e=3;try{const o=new URLSearchParams;a&&o.set("category",a),o.set("limit",String(Math.min(n,200)));const d=await G(`/api/v1/federation/meta?${o.toString()}`);t=d.meta_lessons??[],e=d.k_threshold??3}catch{return"\u274C Could not reach the global commons API. Try again later."}if(t.length===0)return["\u{1F310} **Brain Import Meta-Lessons**","",`No meta-lessons available yet (k-threshold: ${e} signals required).`,"","\u{1F4A1} Contribute signals with `brain_contribute_signal` to help build the commons."].join(`
37
+ `);const l=await I(y);let u=0;for(const o of t){const d=`cachly:lesson:best:meta:${o.topic_category}`;await l.get(d)||(await l.set(d,JSON.stringify({topic:`meta:${o.topic_category}`,outcome:o.dominant_outcome,what_worked:o.contributor_count!=null?`Meta-pattern from ${o.contributor_count} independent orgs`:`Meta-pattern from ${o.signal_count} signals`,what_failed:"",recall_count:0,ts:o.derived_at,state:"meta",avg_confidence:o.avg_confidence,signal_count:o.signal_count,audit_trail:[{ts:new Date().toISOString(),action:"imported_meta"}]})),u++)}const c=[`\u{1F310} **Brain Import Meta-Lessons** (k \u2265 ${e})`,"","| Topic Category | Outcome | Confidence | Signals |","|---|---|---|---|"];for(const o of t.slice(0,15)){const d=(o.avg_confidence*100).toFixed(0)+"%";c.push(`| \`${o.topic_category}\` | ${o.dominant_outcome} | ${d} | ${o.signal_count} |`)}return t.length>15&&c.push(`| _\u2026${t.length-15} more_ | | | |`),c.push("",`\u2705 **${u}** new meta-lessons imported into local Brain.`),u<t.length&&c.push(`_(${t.length-u} already present \u2014 not overwritten)_`),c.join(`
38
+ `)}case"brain_watch":{const{instance_id:y="",project_dir:a=".",api_key:n}=N,{resolve:t}=await import("node:path"),e=t(a),{result:l,hookPath:u}=await fe(e,y,n);return l==="skipped-no-git"?["\u26A0\uFE0F **brain_watch: no git repository found**","",`No \`.git\` directory in \`${e}\`.`,"Pass `project_dir` pointing to a git checkout."].join(`
39
+ `):[`\u{1F9E0} **brain_watch ${{written:"\u2705 Hook installed (new)",upgraded:"\u{1F504} Hook upgraded to latest version",appended:"\u{1F4CE} Hook appended to existing post-commit script",unchanged:"\u2713 Hook already up to date \u2014 nothing changed"}[l]??l}**`,"",`Hook path: \`${u}\``,`Instance: \`${y||"(none \u2014 set CACHLY_BRAIN_INSTANCE_ID or pass instance_id)"}\``,"","Every future commit will automatically teach your Brain.","No manual `brain_from_git` needed \u2014 the hook runs silently in the background.","",'\u{1F4A1} Verify: `git commit --allow-empty -m "test brain_watch"` then `smart_recall(query="test")`'].join(`
40
+ `)}default:return null}}export{Oe as FEDBRAIN_TOOL_NAMES,ge as _lastBrainFromCiCounts,pe as _lastBrainFromGitCounts,Ne as handleFedbrainTool};