crbro-memory 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -9
- package/dist/engine/cortex.d.ts.map +1 -1
- package/dist/engine/cortex.js +31 -1
- package/dist/engine/cortex.js.map +1 -1
- package/dist/engine/maintenance.d.ts +2 -0
- package/dist/engine/maintenance.d.ts.map +1 -1
- package/dist/engine/maintenance.js +24 -2
- package/dist/engine/maintenance.js.map +1 -1
- package/dist/engine/synapses.d.ts +6 -1
- package/dist/engine/synapses.d.ts.map +1 -1
- package/dist/engine/synapses.js +3 -3
- package/dist/engine/synapses.js.map +1 -1
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +57 -24
- package/dist/search/index.js.map +1 -1
- package/dist/search/synonyms.d.ts +2 -0
- package/dist/search/synonyms.d.ts.map +1 -0
- package/dist/search/synonyms.js +226 -0
- package/dist/search/synonyms.js.map +1 -0
- package/dist/search/tokenize.d.ts +12 -6
- package/dist/search/tokenize.d.ts.map +1 -1
- package/dist/search/tokenize.js +20 -6
- package/dist/search/tokenize.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +337 -152
- package/dist/server.js.map +1 -1
- package/dist/sync/materialize.d.ts.map +1 -1
- package/dist/sync/materialize.js +30 -1
- package/dist/sync/materialize.js.map +1 -1
- package/dist/sync/space.d.ts.map +1 -1
- package/dist/sync/space.js +6 -3
- package/dist/sync/space.js.map +1 -1
- package/dist/types/index.d.ts +30 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// ─── CRBRO MCP Server ────────────────────────────────────────────
|
|
3
|
-
// Main server with all
|
|
3
|
+
// Main server with all 23 tools registered.
|
|
4
|
+
//
|
|
5
|
+
// Every tool goes through registerTool with a title, MCP annotations
|
|
6
|
+
// (readOnlyHint / destructiveHint / idempotentHint / openWorldHint) and, for
|
|
7
|
+
// the read tools with a stable shape, an outputSchema honoured with
|
|
8
|
+
// structuredContent. The descriptions state what the tool does, when to use
|
|
9
|
+
// it over its siblings, its side effects and what it returns — and nothing
|
|
10
|
+
// else: the discipline of using the memory well is said once, at boot, in
|
|
11
|
+
// memory_discipline, because these definitions are paid on every request in
|
|
12
|
+
// clients that load all tools (measured: ~5.4k tokens for the 23).
|
|
4
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
14
|
exports.createServer = createServer;
|
|
6
15
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
@@ -35,7 +44,7 @@ function runningVersion() {
|
|
|
35
44
|
function createServer() {
|
|
36
45
|
const server = new mcp_js_1.McpServer({
|
|
37
46
|
name: 'crbro-memory',
|
|
38
|
-
version:
|
|
47
|
+
version: runningVersion(),
|
|
39
48
|
});
|
|
40
49
|
// ─── Initialize engines ──────────────────────────────────────
|
|
41
50
|
const brain = new brain_js_1.Brain();
|
|
@@ -61,7 +70,12 @@ function createServer() {
|
|
|
61
70
|
// ═══════════════════════════════════════════════════════════════
|
|
62
71
|
// TOOL 1: crbro_boot — Boot sequence
|
|
63
72
|
// ═══════════════════════════════════════════════════════════════
|
|
64
|
-
server.
|
|
73
|
+
server.registerTool('crbro_boot', {
|
|
74
|
+
title: 'Boot the brain',
|
|
75
|
+
description: 'Boot the CRBRO brain — call it FIRST in every conversation, before any other work. Loads persistent memory from earlier sessions: hot topics, active context with open_items and recently_closed (never report recently_closed as pending; verify open_items before repeating them), recent session history, counts, any active protocols as a protocol_enforcement block you must follow, and memory_discipline — the rules for using this memory well. Initializes the brain on first use, readies the search index and syncs shared team spaces (offline is a normal outcome, not an error). Skipping it means losing all accumulated context.',
|
|
76
|
+
inputSchema: {},
|
|
77
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
78
|
+
}, async () => {
|
|
65
79
|
try {
|
|
66
80
|
const result = await brain.boot();
|
|
67
81
|
// Initialize search engine
|
|
@@ -102,6 +116,20 @@ function createServer() {
|
|
|
102
116
|
summary: e.message,
|
|
103
117
|
}));
|
|
104
118
|
}
|
|
119
|
+
// How to use this memory well — said once here, at boot, instead of
|
|
120
|
+
// repeated inside every tool description. Measured with a real
|
|
121
|
+
// tools/list: the 23 definitions cost ~6.3k tokens on every request
|
|
122
|
+
// in clients that load all tools; this paragraph costs ~200, once.
|
|
123
|
+
response.memory_discipline =
|
|
124
|
+
'Before crbro_learn, crbro_recall: what you are about to save may already exist — then pass ' +
|
|
125
|
+
'supersedes instead of adding a sibling (two versions of one fact compete on recall as equals). ' +
|
|
126
|
+
'Structure — paths, what serves what, traps — goes in crbro_map, not in facts; anything derivable ' +
|
|
127
|
+
'from the repo or git history is not worth storing. Write facts dense and self-contained: they are ' +
|
|
128
|
+
'recalled without this conversation. type:error keeps a mistake with its fix; type:debt keeps a ' +
|
|
129
|
+
'deliberate deferral with its ceiling and revisit trigger. Credentials never go in the brain: ' +
|
|
130
|
+
'crbro_secret, then record only the NAME. Recall results carry confidence — "weak" means the match ' +
|
|
131
|
+
'covers little of the question, verify before relying on it — and when two facts disagree, prefer ' +
|
|
132
|
+
'the more recent. Call crbro_consolidate before the conversation ends; it logs the session too.';
|
|
105
133
|
return {
|
|
106
134
|
content: [{
|
|
107
135
|
type: 'text',
|
|
@@ -122,23 +150,37 @@ function createServer() {
|
|
|
122
150
|
// ═══════════════════════════════════════════════════════════════
|
|
123
151
|
// TOOL 2: crbro_status — Brain status
|
|
124
152
|
// ═══════════════════════════════════════════════════════════════
|
|
125
|
-
server.
|
|
153
|
+
server.registerTool('crbro_status', {
|
|
154
|
+
title: 'Brain status',
|
|
155
|
+
description: 'Read-only snapshot: CRBRO version, brain format, neuron/synapse/session totals, brain path, last boot and last consolidation. Loads no memory — that is crbro_boot.',
|
|
156
|
+
inputSchema: {},
|
|
157
|
+
outputSchema: {
|
|
158
|
+
crbro_version: zod_1.z.string(),
|
|
159
|
+
brain_format: zod_1.z.string().optional(),
|
|
160
|
+
total_neurons: zod_1.z.number(),
|
|
161
|
+
total_synapses: zod_1.z.number(),
|
|
162
|
+
total_sessions: zod_1.z.number(),
|
|
163
|
+
brain_path: zod_1.z.string().optional(),
|
|
164
|
+
last_boot: zod_1.z.string().nullable().optional(),
|
|
165
|
+
last_consolidation: zod_1.z.string().nullable().optional(),
|
|
166
|
+
},
|
|
167
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
168
|
+
}, async () => {
|
|
126
169
|
try {
|
|
127
170
|
const manifest = await brain.getManifest();
|
|
171
|
+
const payload = {
|
|
172
|
+
crbro_version: runningVersion(),
|
|
173
|
+
brain_format: manifest.version,
|
|
174
|
+
total_neurons: manifest.total_neurons,
|
|
175
|
+
total_synapses: manifest.total_synapses,
|
|
176
|
+
total_sessions: manifest.total_sessions,
|
|
177
|
+
brain_path: manifest.brain_path,
|
|
178
|
+
last_boot: manifest.last_boot,
|
|
179
|
+
last_consolidation: manifest.last_consolidation,
|
|
180
|
+
};
|
|
128
181
|
return {
|
|
129
|
-
content: [{
|
|
130
|
-
|
|
131
|
-
text: JSON.stringify({
|
|
132
|
-
crbro_version: runningVersion(),
|
|
133
|
-
brain_format: manifest.version,
|
|
134
|
-
total_neurons: manifest.total_neurons,
|
|
135
|
-
total_synapses: manifest.total_synapses,
|
|
136
|
-
total_sessions: manifest.total_sessions,
|
|
137
|
-
brain_path: manifest.brain_path,
|
|
138
|
-
last_boot: manifest.last_boot,
|
|
139
|
-
last_consolidation: manifest.last_consolidation,
|
|
140
|
-
}, null, 2),
|
|
141
|
-
}],
|
|
182
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
183
|
+
structuredContent: payload,
|
|
142
184
|
};
|
|
143
185
|
}
|
|
144
186
|
catch (err) {
|
|
@@ -154,15 +196,20 @@ function createServer() {
|
|
|
154
196
|
// ═══════════════════════════════════════════════════════════════
|
|
155
197
|
// TOOL 3: crbro_learn — Add knowledge to the brain
|
|
156
198
|
// ═══════════════════════════════════════════════════════════════
|
|
157
|
-
server.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
199
|
+
server.registerTool('crbro_learn', {
|
|
200
|
+
title: 'Learn something',
|
|
201
|
+
description: 'Store a fact, decision, pattern, preference, error or debt on a topic. The neuron is created if it does not exist; pass neuron_id (from crbro_recall) to target an exact one and skip name matching. Recall first: to replace an outdated fact pass its id in supersedes rather than adding a sibling. A fact stored verbatim before is skipped silently; decisions always append; preferences never leave this machine. Credential-like values are replaced with a marker before touching disk and listed in redacted — store the value with crbro_secret and record only its name. Returns neuron_id, action (created|updated), superseded count, near_duplicates (stored anyway; retire the old telling), supersedes_unmatched (those targets are still live — retire them with crbro_revise) and running totals.',
|
|
202
|
+
inputSchema: {
|
|
203
|
+
topic: zod_1.z.string().describe('Topic name, e.g. "OctoChat", "Firebase", "SEO Strategy".'),
|
|
204
|
+
type: zod_1.z.enum(['fact', 'decision', 'pattern', 'preference', 'error', 'debt']).describe('error = a mistake plus its correction, in one entry. debt = a deliberate deferral: what was NOT done on purpose, its ceiling, and the revisit condition, e.g. "DEFERRED: protecting the PDFs. CEILING: anyone can download them without signing up. REVISIT WHEN: the signup flow works."'),
|
|
205
|
+
content: zod_1.z.string().describe('The knowledge itself. Dense and self-contained: it is recalled without this conversation as context.'),
|
|
206
|
+
confidence: zod_1.z.number().min(0).max(1).optional().describe('0.0-1.0, default 1.0. Facts only.'),
|
|
207
|
+
domain: zod_1.z.string().optional().describe('Domain, e.g. "proyectos-web". Applied when the neuron is created; on an existing neuron it only replaces the default "general".'),
|
|
208
|
+
rationale: zod_1.z.string().optional().describe('Why the decision was taken. Stored and indexed with it; ignored for other types.'),
|
|
209
|
+
neuron_id: zod_1.z.string().optional().describe('Exact neuron id from crbro_recall, e.g. "project_octochat". Skips name matching entirely.'),
|
|
210
|
+
supersedes: zod_1.z.array(zod_1.z.string()).optional().describe('Facts this one replaces: their ids or exact text. They leave recall but stay in the file. Unmatched targets are reported and stay live.'),
|
|
211
|
+
},
|
|
212
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
166
213
|
}, async (args) => {
|
|
167
214
|
try {
|
|
168
215
|
const result = await cortex.learn(args.topic, args.type, args.content, {
|
|
@@ -238,11 +285,16 @@ function createServer() {
|
|
|
238
285
|
// ═══════════════════════════════════════════════════════════════
|
|
239
286
|
// TOOL 4: crbro_neuron — Read a specific neuron
|
|
240
287
|
// ═══════════════════════════════════════════════════════════════
|
|
241
|
-
server.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
288
|
+
server.registerTool('crbro_neuron', {
|
|
289
|
+
title: 'Read a neuron',
|
|
290
|
+
description: 'Read one neuron by id or name: facts newest first (superseded and retracted hidden unless include_superseded), decisions, patterns, preferences, errors, debts, entry dates, connections, heat and system map. Reading bumps its access stats. Big neurons are paged — use offset rather than pulling everything at once. To find the right neuron first, use crbro_recall.',
|
|
291
|
+
inputSchema: {
|
|
292
|
+
id: zod_1.z.string().describe('Neuron id (e.g. "project_octochat") or name (e.g. "OctoChat").'),
|
|
293
|
+
limit: zod_1.z.number().optional().describe('Facts to return: default 40, max 200.'),
|
|
294
|
+
offset: zod_1.z.number().optional().describe('Facts to skip. Facts come newest first.'),
|
|
295
|
+
include_superseded: zod_1.z.boolean().optional().describe('Also return superseded and retracted facts (default false).'),
|
|
296
|
+
},
|
|
297
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
246
298
|
}, async (args) => {
|
|
247
299
|
try { // Try by ID first, then by name
|
|
248
300
|
let neuron = await cortex.get(args.id);
|
|
@@ -300,11 +352,23 @@ function createServer() {
|
|
|
300
352
|
// ═══════════════════════════════════════════════════════════════
|
|
301
353
|
// TOOL 5: crbro_neurons — List neurons
|
|
302
354
|
// ═══════════════════════════════════════════════════════════════
|
|
303
|
-
server.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
355
|
+
server.registerTool('crbro_neurons', {
|
|
356
|
+
title: 'List neurons',
|
|
357
|
+
description: 'List neurons, hottest first, filtered by domain, type or min_heat. Read-only. Each row: id, name, domain, type, heat, last_accessed, facts_count. To search content rather than list topics, use crbro_recall.',
|
|
358
|
+
inputSchema: {
|
|
359
|
+
domain: zod_1.z.string().optional().describe('Only this domain, e.g. "proyectos-web".'),
|
|
360
|
+
type: zod_1.z.enum(['project', 'tech', 'lang', 'person', 'domain', 'process', 'protocol']).optional().describe('Only this neuron type.'),
|
|
361
|
+
min_heat: zod_1.z.number().optional().describe('Minimum heat, 0.0-1.0. Heat blends access frequency, recency and connectivity.'),
|
|
362
|
+
limit: zod_1.z.number().optional().describe('Max rows (default 50).'),
|
|
363
|
+
},
|
|
364
|
+
outputSchema: {
|
|
365
|
+
total: zod_1.z.number(),
|
|
366
|
+
neurons: zod_1.z.array(zod_1.z.object({
|
|
367
|
+
id: zod_1.z.string(), name: zod_1.z.string(), domain: zod_1.z.string(), type: zod_1.z.string(),
|
|
368
|
+
heat: zod_1.z.number(), last_accessed: zod_1.z.string(), facts_count: zod_1.z.number(),
|
|
369
|
+
}).loose()),
|
|
370
|
+
},
|
|
371
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
308
372
|
}, async (args) => {
|
|
309
373
|
try {
|
|
310
374
|
const neurons = await cortex.list({
|
|
@@ -313,14 +377,10 @@ function createServer() {
|
|
|
313
377
|
min_heat: args.min_heat,
|
|
314
378
|
limit: args.limit,
|
|
315
379
|
});
|
|
380
|
+
const payload = { total: neurons.length, neurons };
|
|
316
381
|
return {
|
|
317
|
-
content: [{
|
|
318
|
-
|
|
319
|
-
text: JSON.stringify({
|
|
320
|
-
total: neurons.length,
|
|
321
|
-
neurons,
|
|
322
|
-
}, null, 2),
|
|
323
|
-
}],
|
|
382
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
383
|
+
structuredContent: payload,
|
|
324
384
|
};
|
|
325
385
|
}
|
|
326
386
|
catch (err) {
|
|
@@ -336,28 +396,46 @@ function createServer() {
|
|
|
336
396
|
// ═══════════════════════════════════════════════════════════════
|
|
337
397
|
// TOOL 6: crbro_recall — Search the brain
|
|
338
398
|
// ═══════════════════════════════════════════════════════════════
|
|
339
|
-
server.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
399
|
+
server.registerTool('crbro_recall', {
|
|
400
|
+
title: 'Recall',
|
|
401
|
+
description: 'Search everything saved in earlier sessions — the full text of facts, decisions, patterns, preferences, errors, debts and system maps, not just topic names. Read-only. One result per neuron: its best matching chunk (matching_content, matched_kind, matched_added), a confidence label (weak = the match covers little of the question; verify before relying on it) and, for the top results, also_matched — the neuron\'s next best lines. Superseded and retracted facts never surface. Call it before asking the user something they may already have told you, and before crbro_learn. If nothing matches, retry with fewer, more distinctive words (names, ids, filenames). has_map:true means the neuron keeps a system map — read it with crbro_map before touching that system.',
|
|
402
|
+
inputSchema: {
|
|
403
|
+
query: zod_1.z.string().describe('What to look for, e.g. "Firebase authentication setup". Fewer, distinctive terms beat full sentences.'),
|
|
404
|
+
domain: zod_1.z.string().optional().describe('Only neurons in this domain (exact match, e.g. "proyectos-web").'),
|
|
405
|
+
limit: zod_1.z.number().optional().describe('Max neurons returned (default 10).'),
|
|
406
|
+
},
|
|
407
|
+
outputSchema: {
|
|
408
|
+
query: zod_1.z.string(),
|
|
409
|
+
total_results: zod_1.z.number(),
|
|
410
|
+
results: zod_1.z.array(zod_1.z.object({
|
|
411
|
+
neuron_id: zod_1.z.string(), name: zod_1.z.string(), domain: zod_1.z.string(),
|
|
412
|
+
relevance_score: zod_1.z.number(), matching_content: zod_1.z.string(),
|
|
413
|
+
matched_kind: zod_1.z.string().optional(), matched_added: zod_1.z.string().optional(),
|
|
414
|
+
heat: zod_1.z.number(), has_map: zod_1.z.boolean().optional(),
|
|
415
|
+
matched_terms: zod_1.z.number().optional(), query_terms: zod_1.z.number().optional(),
|
|
416
|
+
confidence: zod_1.z.enum(['strong', 'weak']).optional(),
|
|
417
|
+
also_matched: zod_1.z.array(zod_1.z.object({ text: zod_1.z.string(), kind: zod_1.z.string(), added: zod_1.z.string() })).optional(),
|
|
418
|
+
}).loose()),
|
|
419
|
+
hint: zod_1.z.string(),
|
|
420
|
+
},
|
|
421
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
343
422
|
}, async (args) => {
|
|
344
423
|
try {
|
|
345
424
|
const results = await searchEngine.search(args.query, {
|
|
346
425
|
domain: args.domain,
|
|
347
426
|
limit: args.limit,
|
|
348
427
|
});
|
|
428
|
+
const payload = {
|
|
429
|
+
query: args.query,
|
|
430
|
+
total_results: results.length,
|
|
431
|
+
results,
|
|
432
|
+
hint: results.length === 0
|
|
433
|
+
? 'Nothing matched. Try fewer, more distinctive words - names, ids, filenames - rather than a full sentence.'
|
|
434
|
+
: 'matching_content is the chunk that matched; matched_added is when it was recorded; confidence "weak" means little of the question was covered - verify before relying on it. Prefer recent facts when two disagree. has_map: true means the neuron holds a system map - read it with crbro_map before working on that system.',
|
|
435
|
+
};
|
|
349
436
|
return {
|
|
350
|
-
content: [{
|
|
351
|
-
|
|
352
|
-
text: JSON.stringify({
|
|
353
|
-
query: args.query,
|
|
354
|
-
total_results: results.length,
|
|
355
|
-
results,
|
|
356
|
-
hint: results.length === 0
|
|
357
|
-
? 'Nothing matched. Try fewer, more distinctive words - names, ids, filenames - rather than a full sentence.'
|
|
358
|
-
: 'matching_content is the chunk that matched; matched_added is when it was recorded. Prefer recent facts when two disagree. Results with has_map: true belong to neurons holding a system map - read it with crbro_map before working on that system.',
|
|
359
|
-
}, null, 2),
|
|
360
|
-
}],
|
|
437
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
438
|
+
structuredContent: payload,
|
|
361
439
|
};
|
|
362
440
|
}
|
|
363
441
|
catch (err) {
|
|
@@ -373,11 +451,16 @@ function createServer() {
|
|
|
373
451
|
// ═══════════════════════════════════════════════════════════════
|
|
374
452
|
// TOOL 7: crbro_connect — Create/strengthen a synapse
|
|
375
453
|
// ═══════════════════════════════════════════════════════════════
|
|
376
|
-
server.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
454
|
+
server.registerTool('crbro_connect', {
|
|
455
|
+
title: 'Connect two neurons',
|
|
456
|
+
description: 'Create or strengthen the undirected synapse between two neurons: created at strength 0.5, +0.1 per repeat call (cap 1.0). Idle synapses decay and crbro_maintenance prunes the weak. Returns synapse_id, action (created|strengthened) and strength. Neurons written in the same session are linked automatically by crbro_consolidate; use this for relationships that are not just co-occurrence.',
|
|
457
|
+
inputSchema: {
|
|
458
|
+
from: zod_1.z.string().describe('Source neuron id, e.g. "project_octochat". Not validated: use an exact id from crbro_recall or crbro_neurons, or the synapse points at nothing.'),
|
|
459
|
+
to: zod_1.z.string().describe('Target neuron id. Order does not matter — (a,b) and (b,a) are the same synapse.'),
|
|
460
|
+
type: zod_1.z.enum(['dependency', 'causal', 'temporal', 'conceptual', 'hierarchy', 'alternative']).describe('Relationship kind. Used only on creation; a strengthening call keeps the existing type.'),
|
|
461
|
+
context: zod_1.z.string().optional().describe('One line on the relationship. On strengthen it replaces the stored text; omit to keep it.'),
|
|
462
|
+
},
|
|
463
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
381
464
|
}, async (args) => {
|
|
382
465
|
try {
|
|
383
466
|
const result = await synapses.connect(args.from, args.to, args.type, args.context);
|
|
@@ -408,21 +491,29 @@ function createServer() {
|
|
|
408
491
|
// ═══════════════════════════════════════════════════════════════
|
|
409
492
|
// TOOL 8: crbro_connections — Get neuron connections
|
|
410
493
|
// ═══════════════════════════════════════════════════════════════
|
|
411
|
-
server.
|
|
412
|
-
|
|
413
|
-
|
|
494
|
+
server.registerTool('crbro_connections', {
|
|
495
|
+
title: 'Neuron connections',
|
|
496
|
+
description: 'List every synapse touching one neuron, strongest first — target_id, target_name, type, strength and context per entry. Read-only; an unknown or unconnected id returns an empty list, not an error.',
|
|
497
|
+
inputSchema: {
|
|
498
|
+
neuron_id: zod_1.z.string().describe('Exact neuron id, e.g. "project_octochat". Names are not resolved here — get the id from crbro_recall or crbro_neurons.'),
|
|
499
|
+
min_strength: zod_1.z.number().optional().describe('Drop connections weaker than this (0.0-1.0). Omit for all; 0 is no filter.'),
|
|
500
|
+
},
|
|
501
|
+
outputSchema: {
|
|
502
|
+
neuron_id: zod_1.z.string(),
|
|
503
|
+
total_connections: zod_1.z.number(),
|
|
504
|
+
connections: zod_1.z.array(zod_1.z.object({
|
|
505
|
+
target_id: zod_1.z.string(), target_name: zod_1.z.string(), type: zod_1.z.string(),
|
|
506
|
+
strength: zod_1.z.number(), context: zod_1.z.string(),
|
|
507
|
+
}).loose()),
|
|
508
|
+
},
|
|
509
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
414
510
|
}, async (args) => {
|
|
415
511
|
try {
|
|
416
512
|
const connections = await synapses.getConnections(args.neuron_id, args.min_strength);
|
|
513
|
+
const payload = { neuron_id: args.neuron_id, total_connections: connections.length, connections };
|
|
417
514
|
return {
|
|
418
|
-
content: [{
|
|
419
|
-
|
|
420
|
-
text: JSON.stringify({
|
|
421
|
-
neuron_id: args.neuron_id,
|
|
422
|
-
total_connections: connections.length,
|
|
423
|
-
connections,
|
|
424
|
-
}, null, 2),
|
|
425
|
-
}],
|
|
515
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
516
|
+
structuredContent: payload,
|
|
426
517
|
};
|
|
427
518
|
}
|
|
428
519
|
catch (err) {
|
|
@@ -438,11 +529,16 @@ function createServer() {
|
|
|
438
529
|
// ═══════════════════════════════════════════════════════════════
|
|
439
530
|
// TOOL 9: crbro_session_log — Log a session
|
|
440
531
|
// ═══════════════════════════════════════════════════════════════
|
|
441
|
-
server.
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
532
|
+
server.registerTool('crbro_session_log', {
|
|
533
|
+
title: 'Log a session',
|
|
534
|
+
description: 'Log a session summary to the hippocampus — one entry per calendar day; a same-day call appends to it. Also replaces the active-topics list with topics_touched. Normally unnecessary: crbro_consolidate logs the session itself.',
|
|
535
|
+
inputSchema: {
|
|
536
|
+
summary: zod_1.z.string().describe('What happened in this session. Appended if today already has an entry.'),
|
|
537
|
+
topics_touched: zod_1.z.array(zod_1.z.string()).describe('Relevant neuron ids. Merged (deduplicated) into the day entry; becomes the active-topics list.'),
|
|
538
|
+
key_facts_added: zod_1.z.number().optional().describe('New facts stored. Summed into the day total on same-day calls.'),
|
|
539
|
+
decisions_made: zod_1.z.number().optional().describe('Decisions recorded. Summed into the day total on same-day calls.'),
|
|
540
|
+
},
|
|
541
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
446
542
|
}, async (args) => {
|
|
447
543
|
try {
|
|
448
544
|
const session = await hippocampus.logSession({
|
|
@@ -479,19 +575,28 @@ function createServer() {
|
|
|
479
575
|
// ═══════════════════════════════════════════════════════════════
|
|
480
576
|
// TOOL 10: crbro_sessions — List recent sessions
|
|
481
577
|
// ═══════════════════════════════════════════════════════════════
|
|
482
|
-
server.
|
|
483
|
-
|
|
578
|
+
server.registerTool('crbro_sessions', {
|
|
579
|
+
title: 'Recent sessions',
|
|
580
|
+
description: 'List recent session logs, newest first, one per day: date, merged summary, topics_touched neuron ids, fact/decision counters. Read-only. Read them before asking the user what was already done; crbro_boot already returns the last one.',
|
|
581
|
+
inputSchema: {
|
|
582
|
+
limit: zod_1.z.number().optional().describe('Day logs to return, newest first (default 10).'),
|
|
583
|
+
},
|
|
584
|
+
outputSchema: {
|
|
585
|
+
total: zod_1.z.number(),
|
|
586
|
+
sessions: zod_1.z.array(zod_1.z.object({
|
|
587
|
+
session_id: zod_1.z.string().optional(), date: zod_1.z.string().optional(), summary: zod_1.z.string().optional(),
|
|
588
|
+
topics_touched: zod_1.z.array(zod_1.z.string()).optional(),
|
|
589
|
+
key_facts_added: zod_1.z.number().optional(), decisions_made: zod_1.z.number().optional(),
|
|
590
|
+
}).loose()),
|
|
591
|
+
},
|
|
592
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
484
593
|
}, async (args) => {
|
|
485
594
|
try {
|
|
486
595
|
const sessions = await hippocampus.listSessions(args.limit);
|
|
596
|
+
const payload = { total: sessions.length, sessions };
|
|
487
597
|
return {
|
|
488
|
-
content: [{
|
|
489
|
-
|
|
490
|
-
text: JSON.stringify({
|
|
491
|
-
total: sessions.length,
|
|
492
|
-
sessions,
|
|
493
|
-
}, null, 2),
|
|
494
|
-
}],
|
|
598
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
599
|
+
structuredContent: payload,
|
|
495
600
|
};
|
|
496
601
|
}
|
|
497
602
|
catch (err) {
|
|
@@ -507,10 +612,15 @@ function createServer() {
|
|
|
507
612
|
// ═══════════════════════════════════════════════════════════════
|
|
508
613
|
// TOOL 11: crbro_context — Active context
|
|
509
614
|
// ═══════════════════════════════════════════════════════════════
|
|
510
|
-
server.
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
615
|
+
server.registerTool('crbro_context', {
|
|
616
|
+
title: 'Working context',
|
|
617
|
+
description: 'Read or update the working context: active topics, open items, last session. Call with no arguments to read; every call returns the full state plus resolved, the items it closed. Close items as soon as they are done — an item left open is repeated back to the user in later sessions long after it was finished.',
|
|
618
|
+
inputSchema: {
|
|
619
|
+
set_topics: zod_1.z.array(zod_1.z.string()).optional().describe('Replace the whole active-topics list with these neuron ids (no merge). crbro_session_log also overwrites it.'),
|
|
620
|
+
add_pending: zod_1.z.string().optional().describe('Add an open item, written so it can be checked later. Identical text is deduplicated, so re-adding is a safe no-op.'),
|
|
621
|
+
resolve_pending: zod_1.z.string().optional().describe('Close an open item by id (e.g. "p_ab12cd") or by 8+ characters of its text (case-insensitive substring; several items can close at once). Matches move to recently_closed, newest first, capped at 15. An empty resolved in the reply means nothing matched.'),
|
|
622
|
+
},
|
|
623
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
514
624
|
}, async (args) => {
|
|
515
625
|
try {
|
|
516
626
|
const ctx = await prefrontal.updateContext({
|
|
@@ -538,16 +648,26 @@ function createServer() {
|
|
|
538
648
|
// ═══════════════════════════════════════════════════════════════
|
|
539
649
|
// TOOL 12: crbro_hot_topics — Hot topics
|
|
540
650
|
// ═══════════════════════════════════════════════════════════════
|
|
541
|
-
server.
|
|
542
|
-
|
|
651
|
+
server.registerTool('crbro_hot_topics', {
|
|
652
|
+
title: 'Hot topics',
|
|
653
|
+
description: 'The hottest neurons by heat (access frequency, recency, connectivity). Read-only, served from a cache rebuilt at consolidate and maintenance — last_recalculated says when. crbro_boot already returns this list.',
|
|
654
|
+
inputSchema: {
|
|
655
|
+
limit: zod_1.z.number().optional().describe('Topics to return (default 15; the cache never holds more than 20).'),
|
|
656
|
+
},
|
|
657
|
+
outputSchema: {
|
|
658
|
+
topics: zod_1.z.array(zod_1.z.object({
|
|
659
|
+
id: zod_1.z.string(), name: zod_1.z.string(), heat: zod_1.z.number(), last_access: zod_1.z.string(), domain: zod_1.z.string(),
|
|
660
|
+
}).loose()),
|
|
661
|
+
last_recalculated: zod_1.z.string().optional(),
|
|
662
|
+
},
|
|
663
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
543
664
|
}, async (args) => {
|
|
544
665
|
try {
|
|
545
666
|
const hotTopics = await prefrontal.getHotTopics(args.limit);
|
|
667
|
+
const payload = { ...hotTopics };
|
|
546
668
|
return {
|
|
547
|
-
content: [{
|
|
548
|
-
|
|
549
|
-
text: JSON.stringify(hotTopics, null, 2),
|
|
550
|
-
}],
|
|
669
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
670
|
+
structuredContent: payload,
|
|
551
671
|
};
|
|
552
672
|
}
|
|
553
673
|
catch (err) {
|
|
@@ -563,8 +683,13 @@ function createServer() {
|
|
|
563
683
|
// ═══════════════════════════════════════════════════════════════
|
|
564
684
|
// TOOL 13: crbro_global_map — Global neural map
|
|
565
685
|
// ═══════════════════════════════════════════════════════════════
|
|
566
|
-
server.
|
|
567
|
-
|
|
686
|
+
server.registerTool('crbro_global_map', {
|
|
687
|
+
title: 'Global map',
|
|
688
|
+
description: 'The global map: one cluster per domain (node ids, top neurons, heat) and bridges where synapses cross domains, served from a cache stamped last_rebuilt. Read-only unless rebuild:true, which rescans every neuron and rewrites that cache (derived data, no knowledge is touched). For one system\'s internals use crbro_map instead.',
|
|
689
|
+
inputSchema: {
|
|
690
|
+
rebuild: zod_1.z.boolean().optional().describe('true = rescan every neuron and rewrite the cached map (slower on big brains). Default: serve the cache, building it only if missing — it can lag recent learning; crbro_maintenance also rebuilds it.'),
|
|
691
|
+
},
|
|
692
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
568
693
|
}, async (args) => {
|
|
569
694
|
try {
|
|
570
695
|
const globalMap = await prefrontal.getGlobalMap(args.rebuild);
|
|
@@ -594,10 +719,15 @@ function createServer() {
|
|
|
594
719
|
// ═══════════════════════════════════════════════════════════════
|
|
595
720
|
// TOOL 14: crbro_maintenance — Run maintenance
|
|
596
721
|
// ═══════════════════════════════════════════════════════════════
|
|
597
|
-
server.
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
722
|
+
server.registerTool('crbro_maintenance', {
|
|
723
|
+
title: 'Brain maintenance',
|
|
724
|
+
description: 'Run brain maintenance: recalculate heat, prune weak synapses, check integrity and rebuild the search index. Returns a report (counts, integrity_issues, notes) and flags debts that never named a revisit trigger. dry_run:true reports without writing anything. Archiving cold neurons and purging miner boilerplate are OFF unless asked — on a mature brain most neurons look cold, and archived ones stop being searchable. For session close use crbro_consolidate, not this.',
|
|
725
|
+
inputSchema: {
|
|
726
|
+
dry_run: zod_1.z.boolean().optional().describe('true = report only: no heat recalc, archiving, purge, lock sweep, pruning or index rebuild. Counts, debts and integrity checks still run.'),
|
|
727
|
+
archive: zod_1.z.boolean().optional().describe('Also move cold neurons (heat < 0.05, untouched 90+ days) out of the cortex. Off by default; run dry_run first and read archivable_neurons. Restore by moving the file from archives/ back into cortex/.'),
|
|
728
|
+
purge_boilerplate: zod_1.z.boolean().optional().describe('Also delete contentless facts left by early miner versions ("Referenced in: file.md"). Off by default; every run reports how many there are. Neurons left empty are kept.'),
|
|
729
|
+
},
|
|
730
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
601
731
|
}, async (args) => {
|
|
602
732
|
try {
|
|
603
733
|
const report = await maintenance.run(args.dry_run, { archive: args.archive, purgeBoilerplate: args.purge_boilerplate });
|
|
@@ -624,8 +754,13 @@ function createServer() {
|
|
|
624
754
|
// ═══════════════════════════════════════════════════════════════
|
|
625
755
|
// TOOL 15: crbro_consolidate — End-of-session consolidation
|
|
626
756
|
// ═══════════════════════════════════════════════════════════════
|
|
627
|
-
server.
|
|
628
|
-
|
|
757
|
+
server.registerTool('crbro_consolidate', {
|
|
758
|
+
title: 'Consolidate the session',
|
|
759
|
+
description: 'Call before the conversation ends whenever significant work was done. Persists pending knowledge and index writes, logs the session from summary (no separate crbro_session_log needed), recalculates heat, links the neurons written this session with weak temporal synapses (synapses_updated), updates the manifest and syncs shared team spaces (offline is normal; notes go out next time). Returns the session\'s real write counts — facts_saved, decisions_saved, topics_touched — and per-space sync state. Not consolidating loses the session\'s knowledge.',
|
|
760
|
+
inputSchema: {
|
|
761
|
+
summary: zod_1.z.string().describe('What was accomplished: concrete work, decisions, outcomes. Stored verbatim as the session log later sessions read.'),
|
|
762
|
+
},
|
|
763
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
629
764
|
}, async (args) => {
|
|
630
765
|
try {
|
|
631
766
|
const result = await maintenance.consolidate(args.summary);
|
|
@@ -660,10 +795,15 @@ function createServer() {
|
|
|
660
795
|
// ═══════════════════════════════════════════════════════════════
|
|
661
796
|
// TOOL: crbro_map — The living map of a system
|
|
662
797
|
// ═══════════════════════════════════════════════════════════════
|
|
663
|
-
server.
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
798
|
+
server.registerTool('crbro_map', {
|
|
799
|
+
title: 'System map',
|
|
800
|
+
description: 'Read or replace a neuron\'s system map: ONE living document — where the system lives, what serves what, which pieces talk to each other, the traps that cost hours. Read it before working on a system touched in past sessions; after changing the system, rewrite the whole map — content replaces the previous version entirely (append-only maps rot). Omit content to read (map:null if none yet); an empty string clears it. Reading never creates a neuron, writing does. Credentials are redacted on write and listed in redacted. Atomic facts belong in crbro_learn — the map is the prose reference around them.',
|
|
801
|
+
inputSchema: {
|
|
802
|
+
neuron: zod_1.z.string().describe('Neuron id or name, e.g. "project_octochat" or "OctoChat".'),
|
|
803
|
+
content: zod_1.z.string().optional().describe('The new map, replacing the old one whole; omit to read. Write the reference you will need next time: paths, ids, what-serves-what, gotchas. An empty string clears the map.'),
|
|
804
|
+
domain: zod_1.z.string().optional().describe('Domain if the neuron has to be created, e.g. "proyectos-web". Ignored when it exists.'),
|
|
805
|
+
},
|
|
806
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
667
807
|
}, async (args) => {
|
|
668
808
|
try {
|
|
669
809
|
if (args.content === undefined) {
|
|
@@ -741,11 +881,16 @@ function createServer() {
|
|
|
741
881
|
// ═══════════════════════════════════════════════════════════════
|
|
742
882
|
// TOOL 16: crbro_revise — Retire knowledge that stopped being true
|
|
743
883
|
// ═══════════════════════════════════════════════════════════════
|
|
744
|
-
server.
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
884
|
+
server.registerTool('crbro_revise', {
|
|
885
|
+
title: 'Revise facts',
|
|
886
|
+
description: 'Mark stored facts as no longer current — the moment you find something saved is out of date or was wrong, since a memory that only appends keeps serving the old version with equal confidence. Superseded facts leave crbro_recall but stay in the neuron file, so the correction is auditable. Matches by fact id or exact text; anything reported in unmatched is STILL LIVE — fix and re-run. If a replacement fact exists, crbro_learn with supersedes does both in one call. To delete outright, use crbro_forget.',
|
|
887
|
+
inputSchema: {
|
|
888
|
+
neuron: zod_1.z.string().describe('Neuron id or name holding the facts, e.g. "project_octochat".'),
|
|
889
|
+
facts: zod_1.z.array(zod_1.z.string()).describe('Facts to retire: their ids (from crbro_recall) or exact text (trimmed, case-insensitive). Already-retired facts never match.'),
|
|
890
|
+
status: zod_1.z.enum(['superseded', 'retracted']).optional().describe('superseded = there is a newer truth (default); retracted = it was never true.'),
|
|
891
|
+
note: zod_1.z.string().optional().describe('Why it stopped being true. The next reader will wonder.'),
|
|
892
|
+
},
|
|
893
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
749
894
|
}, async (args) => {
|
|
750
895
|
try {
|
|
751
896
|
const result = await cortex.revise(args.neuron, args.facts, {
|
|
@@ -791,25 +936,40 @@ function createServer() {
|
|
|
791
936
|
// ═══════════════════════════════════════════════════════════════
|
|
792
937
|
// TOOL 17: crbro_audit — What should not be in the brain
|
|
793
938
|
// ═══════════════════════════════════════════════════════════════
|
|
794
|
-
server.
|
|
939
|
+
server.registerTool('crbro_audit', {
|
|
940
|
+
title: 'Audit for credentials',
|
|
941
|
+
description: 'Read-only scan of every field of every neuron (facts, decisions, patterns, preferences, errors, debts, system map) for credentials stored before the filter caught them — API keys, tokens, passwords. Reports where they sit and what kind, never the values. Findings are in the search index too, so recall can return them: remove with crbro_forget, then rotate the credential. Run it after upgrading and whenever a secret may have been pasted into a conversation.',
|
|
942
|
+
inputSchema: {},
|
|
943
|
+
outputSchema: {
|
|
944
|
+
neurons_affected: zod_1.z.number(),
|
|
945
|
+
facts_affected: zod_1.z.number(),
|
|
946
|
+
findings: zod_1.z.array(zod_1.z.object({
|
|
947
|
+
neuron_id: zod_1.z.string(), name: zod_1.z.string(), kinds: zod_1.z.array(zod_1.z.string()),
|
|
948
|
+
facts: zod_1.z.number(), decisions: zod_1.z.number(), patterns: zod_1.z.number(), preferences: zod_1.z.number(),
|
|
949
|
+
errors: zod_1.z.number(), debts: zod_1.z.number(), map: zod_1.z.number(),
|
|
950
|
+
}).loose()),
|
|
951
|
+
message: zod_1.z.string(),
|
|
952
|
+
note: zod_1.z.string().optional(),
|
|
953
|
+
},
|
|
954
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
955
|
+
}, async () => {
|
|
795
956
|
try {
|
|
796
957
|
const hallazgos = await cortex.auditSecrets();
|
|
797
|
-
const total = hallazgos.reduce((n, h) => n + h.facts + h.decisions + h.patterns + h.preferences, 0);
|
|
958
|
+
const total = hallazgos.reduce((n, h) => n + h.facts + h.decisions + h.patterns + h.preferences + h.errors + h.debts + h.map, 0);
|
|
959
|
+
const payload = {
|
|
960
|
+
neurons_affected: hallazgos.length,
|
|
961
|
+
facts_affected: total,
|
|
962
|
+
findings: hallazgos,
|
|
963
|
+
message: hallazgos.length === 0
|
|
964
|
+
? 'No credentials found in the brain.'
|
|
965
|
+
: `${total} entr(y/ies) across ${hallazgos.length} neuron(s) contain something that looks like a credential. ` +
|
|
966
|
+
'They are also inside the search index, so recall can return them. ' +
|
|
967
|
+
'Remove them with crbro_forget, then rotate the credentials — assume they are compromised.',
|
|
968
|
+
note: 'Values are never shown here, by design.',
|
|
969
|
+
};
|
|
798
970
|
return {
|
|
799
|
-
content: [{
|
|
800
|
-
|
|
801
|
-
text: JSON.stringify({
|
|
802
|
-
neurons_affected: hallazgos.length,
|
|
803
|
-
facts_affected: total,
|
|
804
|
-
findings: hallazgos,
|
|
805
|
-
message: hallazgos.length === 0
|
|
806
|
-
? 'No credentials found in the brain.'
|
|
807
|
-
: `${total} fact(s) across ${hallazgos.length} neuron(s) contain something that looks like a credential. ` +
|
|
808
|
-
'They are also inside the search index, so recall can return them. ' +
|
|
809
|
-
'Remove them with crbro_forget, then rotate the credentials — assume they are compromised.',
|
|
810
|
-
note: 'Values are never shown here, by design.',
|
|
811
|
-
}, null, 2),
|
|
812
|
-
}],
|
|
971
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
972
|
+
structuredContent: payload,
|
|
813
973
|
};
|
|
814
974
|
}
|
|
815
975
|
catch (err) {
|
|
@@ -825,12 +985,17 @@ function createServer() {
|
|
|
825
985
|
// ═══════════════════════════════════════════════════════════════
|
|
826
986
|
// TOOL 22: crbro_secret — Credentials, brokered to the OS keychain
|
|
827
987
|
// ═══════════════════════════════════════════════════════════════
|
|
828
|
-
server.
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
988
|
+
server.registerTool('crbro_secret', {
|
|
989
|
+
title: 'Keychain secret',
|
|
990
|
+
description: 'Credentials, brokered to the operating system\'s own keychain (macOS Keychain, Linux Secret Service, Windows DPAPI): CRBRO keeps no copy and invents no crypto, and no sync or team space can reach the store. The moment the user hands you a credential: set it here, then record only the NAME with crbro_learn. get returns the value for the task at hand — an environment variable of the same name wins, a missing secret returns found:false, not an error — and it must not be printed back unless the user asked for that secret. list returns names only; remove deletes one; status says which store this machine has. Names are SCREAMING_SNAKE_CASE; set updates an existing name in place and rejects empty values. A machine with no store is a normal status answer, not a failure — environment variables still work.',
|
|
991
|
+
inputSchema: {
|
|
992
|
+
action: zod_1.z.enum(['get', 'set', 'list', 'remove', 'status'])
|
|
993
|
+
.describe('get = read one, set = store or update one, list = names only, remove = delete one, status = which keychain this machine offers, or why none.'),
|
|
994
|
+
name: zod_1.z.string().optional().describe('SCREAMING_SNAKE_CASE, e.g. WORDPRESS_APP_PASSWORD. Required for get, set and remove.'),
|
|
995
|
+
value: zod_1.z.string().optional().describe('The credential itself, non-empty. Only for set.'),
|
|
996
|
+
description: zod_1.z.string().optional().describe('What it is for, e.g. "WordPress example.com - REST API". Only for set.'),
|
|
997
|
+
},
|
|
998
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
834
999
|
}, async (args) => {
|
|
835
1000
|
try {
|
|
836
1001
|
let payload;
|
|
@@ -909,9 +1074,14 @@ function createServer() {
|
|
|
909
1074
|
// ═══════════════════════════════════════════════════════════════
|
|
910
1075
|
// TOOL 18: crbro_forget — Remove knowledge for good
|
|
911
1076
|
// ═══════════════════════════════════════════════════════════════
|
|
912
|
-
server.
|
|
913
|
-
|
|
914
|
-
|
|
1077
|
+
server.registerTool('crbro_forget', {
|
|
1078
|
+
title: 'Forget for good',
|
|
1079
|
+
description: 'Permanently remove entries from a neuron — for what must not exist at all: a credential, personal data, something stored by mistake. Matches facts, decisions, patterns, preferences, errors and debts by id or exact text (trimmed, case-insensitive), and the system map by its exact full text. Destructive, with a net: the whole neuron is copied to .quarantine/ first (backup path returned) and the search index is updated; nothing matched returns removed:0, not an error. On shared neurons the removal travels. For knowledge that merely stopped being true use crbro_revise, which keeps the history. Tell the user what will be removed and get their agreement first; a removed credential must still be rotated — it existed on disk and in the index.',
|
|
1080
|
+
inputSchema: {
|
|
1081
|
+
neuron: zod_1.z.string().describe('Neuron id or name holding the entries.'),
|
|
1082
|
+
facts: zod_1.z.array(zod_1.z.string()).describe('Fact ids, or the exact text of a fact, decision, pattern, preference, error or debt. The exact full text of the map removes the map.'),
|
|
1083
|
+
},
|
|
1084
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
915
1085
|
}, async (args) => {
|
|
916
1086
|
try {
|
|
917
1087
|
const r = await cortex.forget(args.neuron, args.facts);
|
|
@@ -951,12 +1121,17 @@ function createServer() {
|
|
|
951
1121
|
// ═══════════════════════════════════════════════════════════════
|
|
952
1122
|
// TOOL 19: crbro_space — Join a team's shared memory
|
|
953
1123
|
// ═══════════════════════════════════════════════════════════════
|
|
954
|
-
server.
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1124
|
+
server.registerTool('crbro_space', {
|
|
1125
|
+
title: 'Team space',
|
|
1126
|
+
description: 'Shared memory with teammates. A space is a private git repository holding notes about the projects you choose to share — nothing else from your brain goes near it. One person runs create with the repository URL; everyone else runs join with the same URL; afterwards it syncs by itself at boot and consolidate. Joining shares nothing by itself: put each project in with crbro_share. status lists your identity and spaces. create and join need name, remote and author, and reply ok:false with the reason when git is missing or the push/clone fails.',
|
|
1127
|
+
inputSchema: {
|
|
1128
|
+
action: zod_1.z.enum(['create', 'join', 'status']).describe('create = start a new space and push it, join = clone one a teammate created, status = your identity and the spaces you are in.'),
|
|
1129
|
+
name: zod_1.z.string().optional().describe('Short name, e.g. "equipo" — the same on everyone\'s machine. Required for create and join.'),
|
|
1130
|
+
remote: zod_1.z.string().optional().describe('Git URL of a private repository — EMPTY for create, the same URL the creator used for join. E.g. git@github.com:acme/team-memory.git.'),
|
|
1131
|
+
author: zod_1.z.string().optional().describe('How your notes are signed, e.g. "ana". Lowercase, no spaces. Required for create and join.'),
|
|
1132
|
+
branch: zod_1.z.string().optional().describe('Branch to use (default "main").'),
|
|
1133
|
+
},
|
|
1134
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
960
1135
|
}, async (args) => {
|
|
961
1136
|
try {
|
|
962
1137
|
if (args.action === 'status') {
|
|
@@ -1021,10 +1196,15 @@ function createServer() {
|
|
|
1021
1196
|
// ═══════════════════════════════════════════════════════════════
|
|
1022
1197
|
// TOOL 20: crbro_share — Put one project into a space
|
|
1023
1198
|
// ═══════════════════════════════════════════════════════════════
|
|
1024
|
-
server.
|
|
1025
|
-
|
|
1026
|
-
space:
|
|
1027
|
-
|
|
1199
|
+
server.registerTool('crbro_share', {
|
|
1200
|
+
title: 'Share a project',
|
|
1201
|
+
description: 'Share one neuron with a team space. Always call it without confirm first: the dry run reports exactly what would be sent — ops_to_emit, skipped_preferences (preferences never leave this machine) — and refuses outright if it finds a credential (it will not redact and send anyway: crbro_forget it and rotate it). Show the user that report and get their agreement, then call again with the confirm_token; a stale token is refused. Entries go out on the next crbro_sync or consolidate, and from then on everything learned about that project flows to the team. Sharing cannot be undone.',
|
|
1202
|
+
inputSchema: {
|
|
1203
|
+
neuron: zod_1.z.string().describe('Neuron id or name to share.'),
|
|
1204
|
+
space: zod_1.z.string().describe('Space name, as created or joined with crbro_space.'),
|
|
1205
|
+
confirm: zod_1.z.string().optional().describe('The confirm_token from the dry run — returned only when no credential was found. Omit the first time.'),
|
|
1206
|
+
},
|
|
1207
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
1028
1208
|
}, async (args) => {
|
|
1029
1209
|
try {
|
|
1030
1210
|
if (!args.confirm) {
|
|
@@ -1067,8 +1247,13 @@ function createServer() {
|
|
|
1067
1247
|
// ═══════════════════════════════════════════════════════════════
|
|
1068
1248
|
// TOOL 21: crbro_sync — Exchange notes with the team now
|
|
1069
1249
|
// ═══════════════════════════════════════════════════════════════
|
|
1070
|
-
server.
|
|
1071
|
-
|
|
1250
|
+
server.registerTool('crbro_sync', {
|
|
1251
|
+
title: 'Sync with the team',
|
|
1252
|
+
description: 'Exchange notes with the team right now instead of waiting for the next boot or consolidate, which sync on their own — use it mid-session, e.g. right after crbro_share. Pulls what everyone else recorded and pushes yours, reporting per space: state, neurons updated, new facts, teammates seen, pushed. Being offline is a normal answer, not a failure: local memory works either way and pending notes go out next time.',
|
|
1253
|
+
inputSchema: {
|
|
1254
|
+
space: zod_1.z.string().optional().describe('One space; state comes back "not_joined" if you are not in it. Omit to sync all of them.'),
|
|
1255
|
+
},
|
|
1256
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1072
1257
|
}, async (args) => {
|
|
1073
1258
|
try {
|
|
1074
1259
|
const informes = args.space
|