@andespindola/brainlink 1.0.3 → 1.0.5

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.
@@ -3,6 +3,7 @@ import { buildContextPackage, readContextDataSignature } from '../../application
3
3
  import { getGraph } from '../../application/get-graph.js';
4
4
  import { listAgents } from '../../application/list-agents.js';
5
5
  import { listBacklinks, listLinks } from '../../application/list-links.js';
6
+ import { explainSearchResults } from '../../application/memory-suggestions.js';
6
7
  import { searchKnowledge } from '../../application/search-knowledge.js';
7
8
  import { sanitizeContextStrategy, sanitizeSearchMode } from '../../infrastructure/config.js';
8
9
  import { clearContextPacks, listContextPacks } from '../../infrastructure/context-packs.js';
@@ -15,12 +16,24 @@ export const registerReadCommands = (program) => {
15
16
  .option('-a, --agent <agent>', 'filter by agent memory namespace')
16
17
  .option('-l, --limit <limit>', 'maximum results')
17
18
  .option('-m, --mode <mode>', 'search mode: fts, semantic or hybrid')
19
+ .option('--explain', 'include match reasons and score components')
18
20
  .option('--json', 'print machine-readable JSON')
19
21
  .description('search indexed knowledge')
20
22
  .action(async (query, options) => {
21
23
  const resolved = await resolveOptions(options);
22
24
  const limit = parsePositiveInteger(options.limit ?? String(resolved.defaults.defaultSearchLimit), resolved.defaults.defaultSearchLimit);
23
25
  const mode = sanitizeSearchMode(options.mode, resolved.defaults.defaultSearchMode);
26
+ if (options.explain) {
27
+ const results = await explainSearchResults(resolved.vault, query, limit, resolved.agent, mode);
28
+ print(options.json, { query, agent: resolved.agent, limit, mode, results }, () => results
29
+ .map((result, index) => [
30
+ `${index + 1}. ${result.title} (${result.path}) score=${result.score.toFixed(3)} mode=${result.searchMode}`,
31
+ ...result.reasons.map((reason) => ` - ${reason}`),
32
+ result.content
33
+ ].join('\n'))
34
+ .join('\n\n'));
35
+ return;
36
+ }
24
37
  const results = await searchKnowledge(resolved.vault, query, limit, resolved.agent, mode);
25
38
  print(options.json, { query, agent: resolved.agent, limit, mode, results }, () => results
26
39
  .map((result, index) => [`${index + 1}. ${result.title} (${result.path}) score=${result.score.toFixed(3)} mode=${result.searchMode}`, result.content].join('\n'))
@@ -17,6 +17,7 @@ import { createOfflinePackBackup } from '../../application/offline-pack-backup.j
17
17
  import { startServer } from '../../application/start-server.js';
18
18
  import { startVaultWatcher } from '../../application/watch-vault.js';
19
19
  import { doctorVault, getStats, validateVault } from '../../application/analyze-vault.js';
20
+ import { buildActionableDoctor } from '../../application/operational-workflows.js';
20
21
  import { defaultBrainlinkConfig, sanitizeContextStrategy, sanitizeSearchMode } from '../../infrastructure/config.js';
21
22
  import { loadBrainlinkConfig } from '../../infrastructure/config.js';
22
23
  import { assertVaultAllowed, ensureVault, isBucketVaultPath } from '../../infrastructure/file-system-vault.js';
@@ -1060,10 +1061,22 @@ export const registerWriteCommands = (program) => {
1060
1061
  program
1061
1062
  .command('doctor')
1062
1063
  .option('-v, --vault <vault>', 'vault directory')
1064
+ .option('--actionable', 'include prioritized commands for fixing or improving vault health')
1063
1065
  .option('--json', 'print machine-readable JSON')
1064
1066
  .description('run Brainlink environment and vault checks')
1065
1067
  .action(async (options) => {
1066
1068
  const resolved = await resolveOptions(options);
1069
+ if (options.actionable) {
1070
+ const report = await buildActionableDoctor(resolved.vault);
1071
+ print(options.json, report, () => [
1072
+ ...report.doctor.checks.map((check) => `${check.ok ? 'OK' : 'FAIL'} ${check.name}: ${check.message}`),
1073
+ '',
1074
+ 'Actionable next steps:',
1075
+ ...report.actions.map((action) => `- [${action.severity}] ${action.command} (${action.reason})`)
1076
+ ].join('\n'));
1077
+ process.exitCode = report.doctor.ok ? 0 : 1;
1078
+ return;
1079
+ }
1067
1080
  const report = await doctorVault(resolved.vault);
1068
1081
  print(options.json, report, () => {
1069
1082
  const checks = report.checks.map((check) => `${check.ok ? 'OK' : 'FAIL'} ${check.name}: ${check.message}`).join('\n');
package/dist/cli/main.js CHANGED
@@ -5,6 +5,7 @@ import { basename, dirname, join } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { registerAgentCommands } from './commands/agent-commands.js';
7
7
  import { registerConfigCommands } from './commands/config-commands.js';
8
+ import { registerPracticalCommands } from './commands/practical-commands.js';
8
9
  import { registerReadCommands } from './commands/read-commands.js';
9
10
  import { registerVaultCommands } from './commands/vault-commands.js';
10
11
  import { registerWriteCommands } from './commands/write-commands.js';
@@ -24,6 +25,7 @@ program
24
25
  .version(readPackageVersion());
25
26
  registerWriteCommands(program);
26
27
  registerReadCommands(program);
28
+ registerPracticalCommands(program);
27
29
  registerConfigCommands(program);
28
30
  registerVaultCommands(program);
29
31
  registerAgentCommands(program);
@@ -11,9 +11,9 @@ export const defaultBrainlinkConfig = {
11
11
  defaultAgent: undefined,
12
12
  autoIndexOnWrite: true,
13
13
  autoCanonicalContextLinks: true,
14
- defaultSearchLimit: 10,
15
- defaultContextTokens: 2000,
16
- defaultContextStrategy: 'rag',
14
+ defaultSearchLimit: 8,
15
+ defaultContextTokens: 1500,
16
+ defaultContextStrategy: 'auto',
17
17
  defaultContextCacheTtlMs: 120_000,
18
18
  embeddingProvider: 'local',
19
19
  defaultSearchMode: 'hybrid',
@@ -46,7 +46,7 @@ const searchModes = new Set(['fts', 'semantic', 'hybrid']);
46
46
  const contextStrategies = new Set(['rag', 'cag', 'auto']);
47
47
  const sanitizeEmbeddingProvider = (value) => typeof value === 'string' && embeddingProviders.has(value) ? value : defaultBrainlinkConfig.embeddingProvider;
48
48
  export const sanitizeSearchMode = (value, fallback = defaultBrainlinkConfig.defaultSearchMode) => typeof value === 'string' && searchModes.has(value) ? value : fallback;
49
- export const sanitizeContextStrategy = (value, fallback = 'rag') => typeof value === 'string' && contextStrategies.has(value) ? value : fallback;
49
+ export const sanitizeContextStrategy = (value, fallback = defaultBrainlinkConfig.defaultContextStrategy) => typeof value === 'string' && contextStrategies.has(value) ? value : fallback;
50
50
  const sanitizeAllowedVaults = (value) => Array.isArray(value) ? value.filter((item) => typeof item === 'string' && item.trim().length > 0) : [];
51
51
  const sanitizePositiveNumber = (value) => typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : undefined;
52
52
  const sanitizeIntegerInRange = (value, fallback, minimum, maximum) => {
@@ -1,5 +1,5 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { addNoteInputSchema, addFileInputSchema, addFileTool, addNoteTool, deleteNoteInputSchema, deleteNoteTool, volatileAddInputSchema, volatileAddTool, volatileClearInputSchema, volatileClearTool, dedupeInputSchema, dedupeResolveInputSchema, dedupeResolveTool, dedupeTool, brokenLinksInputSchema, brokenLinksTool, bootstrapInputSchema, bootstrapTool, canonicalizeContextLinksInputSchema, canonicalizeContextLinksTool, contextInputSchema, contextPacksInputSchema, contextPacksTool, contextTool, graphContextsInputSchema, graphContextsTool, graphInputSchema, graphTool, indexInputSchema, indexTool, orphansInputSchema, orphansTool, policyInputSchema, policyTool, recommendationsInputSchema, recommendationsTool, searchInputSchema, searchTool, statsInputSchema, statsTool, syncInputSchema, syncTool, validateInputSchema, validateTool, versionInputSchema, versionTool } from './tools.js';
2
+ import { addNoteInputSchema, addFileInputSchema, addFileTool, addNoteTool, deleteNoteInputSchema, deleteNoteTool, volatileAddInputSchema, volatileAddTool, volatileClearInputSchema, volatileClearTool, dedupeInputSchema, dedupeResolveInputSchema, dedupeResolveTool, dedupeTool, brokenLinksInputSchema, brokenLinksTool, bootstrapInputSchema, bootstrapTool, canonicalizeContextLinksInputSchema, canonicalizeContextLinksTool, contextInputSchema, contextPacksInputSchema, contextPacksTool, contextTool, doctorActionsInputSchema, doctorActionsTool, graphContextsInputSchema, graphContextsTool, graphInputSchema, graphTool, explainInputSchema, explainTool, indexInputSchema, indexTool, inboxAddInputSchema, inboxAddTool, inboxListInputSchema, inboxListTool, inboxProcessInputSchema, inboxProcessTool, orphansInputSchema, orphansTool, policyInputSchema, policyTool, projectInitInputSchema, projectInitTool, recommendationsInputSchema, recommendationsTool, rememberInputSchema, rememberTool, repairLinksInputSchema, repairLinksTool, searchInputSchema, searchTool, sessionCloseInputSchema, sessionCloseTool, statsInputSchema, statsTool, suggestLinksInputSchema, suggestLinksTool, syncInputSchema, syncTool, validateInputSchema, validateTool, versionInputSchema, versionTool } from './tools.js';
3
3
  import { getRuntimeVersion } from './runtime.js';
4
4
  export const createBrainlinkMcpServer = () => {
5
5
  const server = new McpServer({
@@ -43,6 +43,11 @@ export const createBrainlinkMcpServer = () => {
43
43
  description: 'Search indexed Brainlink notes with FTS, semantic or hybrid retrieval.',
44
44
  inputSchema: searchInputSchema
45
45
  }, searchTool);
46
+ server.registerTool('brainlink_explain', {
47
+ title: 'Explain Brainlink Search Results',
48
+ description: 'Explain why indexed notes matched a query, including score components and match reasons.',
49
+ inputSchema: explainInputSchema
50
+ }, explainTool);
46
51
  server.registerTool('brainlink_dedupe', {
47
52
  title: 'Detect Duplicate Notes',
48
53
  description: 'Detect possible duplicate notes using exact content hash and semantic similarity scoring.',
@@ -58,6 +63,26 @@ export const createBrainlinkMcpServer = () => {
58
63
  description: 'Write durable Markdown memory, then reindex the vault. Include explicit [[wiki links]] for connected graph memory. Add priority markers near links, such as priority: high, #important or #critical, when a relationship should be weighted higher.',
59
64
  inputSchema: addNoteInputSchema
60
65
  }, addNoteTool);
66
+ server.registerTool('brainlink_remember', {
67
+ title: 'Capture Assisted Brainlink Memory',
68
+ description: 'Capture durable memory with inferred title, tags and Context Links. Supports dry-run preview before writing.',
69
+ inputSchema: rememberInputSchema
70
+ }, rememberTool);
71
+ server.registerTool('brainlink_inbox_add', {
72
+ title: 'Add Brainlink Inbox Item',
73
+ description: 'Capture a quick untriaged memory item tagged for later processing.',
74
+ inputSchema: inboxAddInputSchema
75
+ }, inboxAddTool);
76
+ server.registerTool('brainlink_inbox_list', {
77
+ title: 'List Brainlink Inbox Items',
78
+ description: 'List quick untriaged memory items from the vault.',
79
+ inputSchema: inboxListInputSchema
80
+ }, inboxListTool);
81
+ server.registerTool('brainlink_inbox_process', {
82
+ title: 'Process Brainlink Inbox Items',
83
+ description: 'Suggest titles, tags and Context Links for untriaged inbox items.',
84
+ inputSchema: inboxProcessInputSchema
85
+ }, inboxProcessTool);
61
86
  server.registerTool('brainlink_delete_note', {
62
87
  title: 'Delete Brainlink Note',
63
88
  description: 'Delete a durable Markdown note from the vault after explicit confirmation. Select by title or path; reindexes by default.',
@@ -93,6 +118,11 @@ export const createBrainlinkMcpServer = () => {
93
118
  description: 'Read indexed vault statistics, including node, edge and tag totals.',
94
119
  inputSchema: statsInputSchema
95
120
  }, statsTool);
121
+ server.registerTool('brainlink_doctor_actions', {
122
+ title: 'Get Actionable Brainlink Doctor Plan',
123
+ description: 'Run vault readiness checks and return prioritized executable next actions.',
124
+ inputSchema: doctorActionsInputSchema
125
+ }, doctorActionsTool);
96
126
  server.registerTool('brainlink_validate', {
97
127
  title: 'Validate Brainlink Vault',
98
128
  description: 'Validate indexed graph health, including broken links and orphan notes.',
@@ -118,10 +148,30 @@ export const createBrainlinkMcpServer = () => {
118
148
  description: 'List unresolved indexed wiki links.',
119
149
  inputSchema: brokenLinksInputSchema
120
150
  }, brokenLinksTool);
151
+ server.registerTool('brainlink_suggest_links', {
152
+ title: 'Suggest Brainlink Links',
153
+ description: 'Suggest Context Links for content or likely fixes for unresolved wiki links.',
154
+ inputSchema: suggestLinksInputSchema
155
+ }, suggestLinksTool);
156
+ server.registerTool('brainlink_repair_links', {
157
+ title: 'Repair Brainlink Broken Links',
158
+ description: 'Repair unresolved wiki links by retargeting safe high-confidence matches or creating placeholder target notes.',
159
+ inputSchema: repairLinksInputSchema
160
+ }, repairLinksTool);
121
161
  server.registerTool('brainlink_orphans', {
122
162
  title: 'List Brainlink Orphans',
123
163
  description: 'List indexed notes without incoming or outgoing graph links.',
124
164
  inputSchema: orphansInputSchema
125
165
  }, orphansTool);
166
+ server.registerTool('brainlink_session_close', {
167
+ title: 'Write Brainlink Session Handoff',
168
+ description: 'Write or preview a session handoff note with vault health and workspace git status.',
169
+ inputSchema: sessionCloseInputSchema
170
+ }, sessionCloseTool);
171
+ server.registerTool('brainlink_project_init', {
172
+ title: 'Initialize Project Memory',
173
+ description: 'Seed Brainlink memory from project documents such as AGENTS.md, README.md and architecture docs.',
174
+ inputSchema: projectInitInputSchema
175
+ }, projectInitTool);
126
176
  return server;
127
177
  };
package/dist/mcp/tools.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readFile } from 'node:fs/promises';
2
- import { basename, extname } from 'node:path';
2
+ import { basename, extname, resolve } from 'node:path';
3
3
  import { z } from 'zod';
4
4
  import { getBrokenLinksReport, getOrphansReport, getStats, validateVault } from '../application/analyze-vault.js';
5
5
  import { addNoteWithMetadata } from '../application/add-note.js';
@@ -9,7 +9,11 @@ import { deleteNote } from '../application/delete-note.js';
9
9
  import { resolveDuplicateNotes, scanDuplicateNotes } from '../application/dedupe-notes.js';
10
10
  import { getGraph } from '../application/get-graph.js';
11
11
  import { getGraphContexts } from '../application/get-graph-contexts.js';
12
+ import { addInboxItem, listInboxItems, processInboxItems } from '../application/inbox.js';
12
13
  import { indexVault } from '../application/index-vault.js';
14
+ import { buildRememberSuggestion, explainSearchResults, suggestBrokenLinkFixes, suggestContextLinks } from '../application/memory-suggestions.js';
15
+ import { buildActionableDoctor, closeSession, initializeProjectMemory } from '../application/operational-workflows.js';
16
+ import { repairBrokenLinks } from '../application/repair-broken-links.js';
13
17
  import { searchKnowledge } from '../application/search-knowledge.js';
14
18
  import { resolveAgentRuntimeDefaults, sanitizeContextStrategy, sanitizeSearchMode } from '../infrastructure/config.js';
15
19
  import { clearContextPacks, listContextPacks } from '../infrastructure/context-packs.js';
@@ -249,6 +253,13 @@ export const searchInputSchema = {
249
253
  query: z.string().min(1).describe('Search query.'),
250
254
  limit: optionalPositiveInteger().describe('Maximum result count.')
251
255
  };
256
+ export const explainInputSchema = {
257
+ ...vaultInput,
258
+ ...agentInput,
259
+ ...searchModeInput,
260
+ query: z.string().min(1).describe('Search query to explain.'),
261
+ limit: optionalPositiveInteger().describe('Maximum result count.')
262
+ };
252
263
  export const addNoteInputSchema = {
253
264
  ...vaultInput,
254
265
  title: z.string().min(1).describe('Markdown note title.'),
@@ -264,6 +275,34 @@ export const addNoteInputSchema = {
264
275
  .optional()
265
276
  .describe('Automatically add canonical Context Links to the inferred visual context hub. Defaults to Brainlink config.')
266
277
  };
278
+ export const rememberInputSchema = {
279
+ ...vaultInput,
280
+ ...agentInput,
281
+ title: z.string().min(1).optional().describe('Optional note title. When omitted, Brainlink infers it from content.'),
282
+ content: z.string().min(1).describe('Memory content to capture as a durable Markdown note.'),
283
+ tags: z.array(z.string()).optional().default([]).describe('Extra tags to include.'),
284
+ links: z.array(z.string()).optional().default([]).describe('Explicit Context Links to include.'),
285
+ linkLimit: positiveInteger(5).describe('Maximum suggested Context Links to include.'),
286
+ dryRun: z.boolean().optional().default(false).describe('Preview inferred note without writing it.'),
287
+ allowSensitive: z.boolean().optional().default(false).describe('Allow content that looks like a secret.'),
288
+ autoIndex: z.boolean().optional().default(true).describe('Reindex vault after writing note.')
289
+ };
290
+ export const inboxAddInputSchema = {
291
+ ...vaultInput,
292
+ ...agentInput,
293
+ content: z.string().min(1).describe('Quick memory content to store as an untriaged inbox note.'),
294
+ autoIndex: z.boolean().optional().default(true).describe('Reindex vault after writing inbox item.')
295
+ };
296
+ export const inboxListInputSchema = {
297
+ ...vaultInput,
298
+ ...agentInput,
299
+ limit: positiveInteger(20).describe('Maximum inbox items to return.')
300
+ };
301
+ export const inboxProcessInputSchema = {
302
+ ...vaultInput,
303
+ ...agentInput,
304
+ limit: positiveInteger(10).describe('Maximum inbox items to inspect.')
305
+ };
267
306
  export const deleteNoteInputSchema = {
268
307
  ...vaultInput,
269
308
  ...agentInput,
@@ -313,6 +352,10 @@ export const validateInputSchema = {
313
352
  ...vaultInput,
314
353
  ...agentInput
315
354
  };
355
+ export const doctorActionsInputSchema = {
356
+ ...vaultInput,
357
+ ...agentInput
358
+ };
316
359
  export const graphInputSchema = {
317
360
  ...vaultInput,
318
361
  ...agentInput
@@ -325,6 +368,22 @@ export const brokenLinksInputSchema = {
325
368
  ...vaultInput,
326
369
  ...agentInput
327
370
  };
371
+ export const suggestLinksInputSchema = {
372
+ ...vaultInput,
373
+ ...agentInput,
374
+ content: z.string().min(1).optional().describe('Content to inspect for Context Link suggestions. Required unless broken=true.'),
375
+ broken: z.boolean().optional().default(false).describe('Suggest fixes for unresolved wiki links instead of content links.'),
376
+ limit: positiveInteger(5).describe('Maximum suggestions to return.')
377
+ };
378
+ export const repairLinksInputSchema = {
379
+ ...vaultInput,
380
+ ...agentInput,
381
+ dryRun: z.boolean().optional().default(false).describe('Preview repairs without writing files.'),
382
+ createMissing: z.boolean().optional().default(true).describe('Create placeholder notes for unresolved targets without a safe existing match.'),
383
+ autoIndex: z.boolean().optional().default(true).describe('Reindex vault after repairs.'),
384
+ minScore: z.number().min(0).max(1).optional().default(0.88).describe('Minimum similarity score for automatic retargeting.'),
385
+ margin: z.number().min(0).max(1).optional().default(0.12).describe('Minimum score gap between first and second candidate.')
386
+ };
328
387
  export const orphansInputSchema = {
329
388
  ...vaultInput,
330
389
  ...agentInput
@@ -375,6 +434,19 @@ export const versionInputSchema = {
375
434
  ...vaultInput,
376
435
  ...agentInput
377
436
  };
437
+ export const sessionCloseInputSchema = {
438
+ ...vaultInput,
439
+ ...agentInput,
440
+ content: z.string().min(1).optional().describe('Optional extra session notes to include in the handoff.'),
441
+ cwd: z.string().min(1).optional().describe('Workspace path used for git status. Defaults to current process working directory.'),
442
+ dryRun: z.boolean().optional().default(false).describe('Preview handoff note without writing it.'),
443
+ autoIndex: z.boolean().optional().default(true).describe('Reindex vault after writing handoff.')
444
+ };
445
+ export const projectInitInputSchema = {
446
+ ...vaultInput,
447
+ ...agentInput,
448
+ projectPath: z.string().min(1).optional().describe('Project path to inspect. Defaults to current process working directory.')
449
+ };
378
450
  export const recommendationsInputSchema = {
379
451
  ...vaultInput,
380
452
  ...agentInput,
@@ -472,6 +544,30 @@ export const searchTool = async (input) => {
472
544
  results
473
545
  });
474
546
  };
547
+ export const explainTool = async (input) => {
548
+ const context = await resolveExecutionContext(input);
549
+ const readiness = await ensureBootstrapReady(context, input, 'brainlink_explain');
550
+ if (readiness.preflight) {
551
+ return readiness.preflight;
552
+ }
553
+ const contextReadiness = await ensureContextReady(context, input, 'brainlink_explain');
554
+ if (contextReadiness.preflight) {
555
+ return contextReadiness.preflight;
556
+ }
557
+ const mode = sanitizeSearchMode(input.mode, context.defaults.defaultSearchMode);
558
+ const limit = input.limit ?? context.defaults.defaultSearchLimit;
559
+ const results = await explainSearchResults(context.vault, input.query, limit, context.agent, mode);
560
+ return jsonResult({
561
+ vault: context.vault,
562
+ agent: context.agent,
563
+ query: input.query,
564
+ limit,
565
+ mode,
566
+ ...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
567
+ ...(contextReadiness.context ? { contextReadiness: contextReadiness.context } : {}),
568
+ results
569
+ });
570
+ };
475
571
  export const addNoteTool = async (input) => {
476
572
  const context = await resolveExecutionContext(input);
477
573
  const shouldIndex = isTruthy(input.autoIndex);
@@ -504,6 +600,75 @@ export const addNoteTool = async (input) => {
504
600
  ...(index ? { index } : {})
505
601
  });
506
602
  };
603
+ export const rememberTool = async (input) => {
604
+ const context = await resolveExecutionContext(input);
605
+ const suggestion = await buildRememberSuggestion({
606
+ vaultPath: context.vault,
607
+ content: input.content,
608
+ agentId: context.agent,
609
+ title: input.title,
610
+ tags: input.tags,
611
+ links: input.links,
612
+ linkLimit: input.linkLimit
613
+ });
614
+ if (input.dryRun) {
615
+ return jsonResult({
616
+ dryRun: true,
617
+ vault: context.vault,
618
+ agent: context.agent,
619
+ suggestion
620
+ });
621
+ }
622
+ const added = await addNoteWithMetadata(context.vault, suggestion.title, suggestion.content, context.agent, {
623
+ allowSensitive: input.allowSensitive,
624
+ autoContextLinks: false
625
+ });
626
+ const index = input.autoIndex ? await indexVault(context.vault) : undefined;
627
+ return jsonResult({
628
+ dryRun: false,
629
+ vault: context.vault,
630
+ agent: context.agent,
631
+ suggestion,
632
+ path: added.path,
633
+ ...(index ? { index } : {})
634
+ });
635
+ };
636
+ export const inboxAddTool = async (input) => {
637
+ const context = await resolveExecutionContext(input);
638
+ const result = await addInboxItem({
639
+ vaultPath: context.vault,
640
+ content: input.content,
641
+ agentId: context.agent,
642
+ autoIndex: input.autoIndex
643
+ });
644
+ return jsonResult({
645
+ vault: context.vault,
646
+ agent: context.agent,
647
+ ...result
648
+ });
649
+ };
650
+ export const inboxListTool = async (input) => {
651
+ const context = await resolveExecutionContext(input);
652
+ const items = await listInboxItems(context.vault, input.limit);
653
+ return jsonResult({
654
+ vault: context.vault,
655
+ agent: context.agent,
656
+ items
657
+ });
658
+ };
659
+ export const inboxProcessTool = async (input) => {
660
+ const context = await resolveExecutionContext(input);
661
+ const items = await processInboxItems({
662
+ vaultPath: context.vault,
663
+ agentId: context.agent,
664
+ limit: input.limit
665
+ });
666
+ return jsonResult({
667
+ vault: context.vault,
668
+ agent: context.agent,
669
+ items
670
+ });
671
+ };
507
672
  export const deleteNoteTool = async (input) => {
508
673
  const context = await resolveExecutionContext(input);
509
674
  const result = await deleteNote(context.vault, {
@@ -613,6 +778,25 @@ export const validateTool = async (input) => {
613
778
  ...validation
614
779
  });
615
780
  };
781
+ export const doctorActionsTool = async (input) => {
782
+ const context = await resolveExecutionContext(input);
783
+ const readiness = await ensureBootstrapReady(context, input, 'brainlink_doctor_actions');
784
+ if (readiness.preflight) {
785
+ return readiness.preflight;
786
+ }
787
+ const contextReadiness = await ensureContextReady(context, input, 'brainlink_doctor_actions');
788
+ if (contextReadiness.preflight) {
789
+ return contextReadiness.preflight;
790
+ }
791
+ const report = await buildActionableDoctor(context.vault);
792
+ return jsonResult({
793
+ vault: context.vault,
794
+ agent: context.agent,
795
+ ...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
796
+ ...(contextReadiness.context ? { contextReadiness: contextReadiness.context } : {}),
797
+ ...report
798
+ });
799
+ };
616
800
  export const graphTool = async (input) => {
617
801
  const context = await resolveExecutionContext(input);
618
802
  const readiness = await ensureBootstrapReady(context, input, 'brainlink_graph');
@@ -670,6 +854,56 @@ export const brokenLinksTool = async (input) => {
670
854
  brokenLinks
671
855
  });
672
856
  };
857
+ export const suggestLinksTool = async (input) => {
858
+ const context = await resolveExecutionContext(input);
859
+ const readiness = await ensureBootstrapReady(context, input, 'brainlink_suggest_links');
860
+ if (readiness.preflight) {
861
+ return readiness.preflight;
862
+ }
863
+ const contextReadiness = await ensureContextReady(context, input, 'brainlink_suggest_links');
864
+ if (contextReadiness.preflight) {
865
+ return contextReadiness.preflight;
866
+ }
867
+ if (input.broken) {
868
+ const suggestions = await suggestBrokenLinkFixes(context.vault, context.agent, input.limit);
869
+ return jsonResult({
870
+ vault: context.vault,
871
+ agent: context.agent,
872
+ mode: 'broken-links',
873
+ ...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
874
+ ...(contextReadiness.context ? { contextReadiness: contextReadiness.context } : {}),
875
+ suggestions
876
+ });
877
+ }
878
+ if (!input.content || input.content.trim().length === 0) {
879
+ throw new Error('content is required when broken=false.');
880
+ }
881
+ const suggestions = await suggestContextLinks(context.vault, input.content, context.agent, input.limit);
882
+ return jsonResult({
883
+ vault: context.vault,
884
+ agent: context.agent,
885
+ mode: 'content',
886
+ ...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
887
+ ...(contextReadiness.context ? { contextReadiness: contextReadiness.context } : {}),
888
+ suggestions
889
+ });
890
+ };
891
+ export const repairLinksTool = async (input) => {
892
+ const context = await resolveExecutionContext(input);
893
+ const result = await repairBrokenLinks(context.vault, {
894
+ agentId: context.agent,
895
+ dryRun: input.dryRun,
896
+ createMissing: input.createMissing,
897
+ autoIndex: input.autoIndex,
898
+ minScore: input.minScore,
899
+ margin: input.margin
900
+ });
901
+ return jsonResult({
902
+ vault: context.vault,
903
+ agent: context.agent,
904
+ ...result
905
+ });
906
+ };
673
907
  export const orphansTool = async (input) => {
674
908
  const context = await resolveExecutionContext(input);
675
909
  const readiness = await ensureBootstrapReady(context, input, 'brainlink_orphans');
@@ -924,6 +1158,35 @@ export const versionTool = async (input) => {
924
1158
  runtime: getRuntimeMetadata()
925
1159
  });
926
1160
  };
1161
+ export const sessionCloseTool = async (input) => {
1162
+ const context = await resolveExecutionContext(input);
1163
+ const result = await closeSession({
1164
+ vaultPath: context.vault,
1165
+ agentId: context.agent,
1166
+ cwd: resolve(input.cwd ?? process.cwd()),
1167
+ content: input.content,
1168
+ write: input.dryRun !== true,
1169
+ autoIndex: input.autoIndex
1170
+ });
1171
+ return jsonResult({
1172
+ vault: context.vault,
1173
+ agent: context.agent,
1174
+ dryRun: input.dryRun === true,
1175
+ ...result
1176
+ });
1177
+ };
1178
+ export const projectInitTool = async (input) => {
1179
+ const context = await resolveExecutionContext(input);
1180
+ const result = await initializeProjectMemory({
1181
+ vaultPath: context.vault,
1182
+ projectPath: resolve(input.projectPath ?? process.cwd()),
1183
+ agentId: context.agent
1184
+ });
1185
+ return jsonResult({
1186
+ agent: context.agent,
1187
+ ...result
1188
+ });
1189
+ };
927
1190
  export const recommendationsTool = async (input) => {
928
1191
  const context = await resolveExecutionContext(input);
929
1192
  const policy = await getBootstrapPolicy();
@@ -585,7 +585,7 @@ Context assembly uses middle-out ordering inside each note: the highest-scoring
585
585
  ### Build Agent Context
586
586
 
587
587
  ```bash
588
- blink context "how does authentication work?" --vault ./vault --limit 12 --tokens 2000
588
+ blink context "how does authentication work?" --vault ./vault --limit 8 --tokens 1500
589
589
  blink context "how does authentication work?" --vault ./vault --json
590
590
  blink context "how does authentication work?" --vault ./vault --agent coding-agent --json
591
591
  blink context "how does authentication work?" --vault ./vault --agent coding-agent --mode hybrid --json
@@ -596,7 +596,7 @@ blink context-packs --vault ./vault --stale --clear
596
596
  ```
597
597
 
598
598
  This returns a Markdown context package optimized for prompt injection.
599
- The default strategy is configured by `defaultContextStrategy` and starts as `rag`. Use `--strategy cag` when the same agent/task context should be served from a persisted context pack when the index and volatile-memory signatures are unchanged. Use `--strategy auto` when Brainlink should use CAG on fresh pack hits and RAG otherwise. Context responses include `cache`, `metrics`, `requestedStrategy` and `recommendedStrategy` metadata. CAG packs live under `.brainlink/context-packs` and are rebuildable derived artifacts.
599
+ The default strategy is configured by `defaultContextStrategy` and starts as `auto`, so repeated stable context can reuse fresh CAG packs while misses fall back to RAG and refresh a pack for later calls. Use `--strategy cag` when the same agent/task context must be served from a persisted context pack when the index and volatile-memory signatures are unchanged. Use `--strategy rag` when fresh retrieval is required. Context responses include `cache`, `metrics`, `requestedStrategy` and `recommendedStrategy` metadata. CAG packs live under `.brainlink/context-packs` and are rebuildable derived artifacts.
600
600
 
601
601
  ### Inspect Links
602
602
 
@@ -724,9 +724,14 @@ Available MCP tools:
724
724
  - `brainlink_context`
725
725
  - `brainlink_context_packs`
726
726
  - `brainlink_search`
727
+ - `brainlink_explain`
727
728
  - `brainlink_dedupe`
728
729
  - `brainlink_resolve_duplicate`
729
730
  - `brainlink_add_note`
731
+ - `brainlink_remember`
732
+ - `brainlink_inbox_add`
733
+ - `brainlink_inbox_list`
734
+ - `brainlink_inbox_process`
730
735
  - `brainlink_delete_note`
731
736
  - `brainlink_add_file`
732
737
  - `brainlink_canonicalize_context_links`
@@ -734,14 +739,20 @@ Available MCP tools:
734
739
  - `brainlink_volatile_clear`
735
740
  - `brainlink_index`
736
741
  - `brainlink_stats`
742
+ - `brainlink_doctor_actions`
737
743
  - `brainlink_validate`
738
744
  - `brainlink_sync`
739
745
  - `brainlink_graph`
740
746
  - `brainlink_graph_contexts`
741
747
  - `brainlink_broken_links`
748
+ - `brainlink_suggest_links`
749
+ - `brainlink_repair_links`
742
750
  - `brainlink_orphans`
751
+ - `brainlink_session_close`
752
+ - `brainlink_project_init`
743
753
 
744
754
  Recommended start of every memory-dependent task: call `brainlink_bootstrap` first, then `brainlink_context`. By default, Brainlink enforces context-first for non-context MCP reads (`enforceContextFirst=true`), and also enforces bootstrap with auto-bootstrap on reads when state is missing or stale (`autoBootstrapOnRead=true`).
755
+ MCP is expected to stay functionally aligned with practical Brainlink CLI workflows whenever the operation is safe and useful for tool clients.
745
756
  MCP startup also bootstraps the configured default vault/agent automatically (`autoBootstrapOnStartup=true`), so sessions start warm without manual calls.
746
757
  If `autoBootstrapOnRead` or `enforceContextFirst` are disabled through `brainlink_policy`, behavior is relaxed accordingly; otherwise read tools return preflight-required responses when requirements are not satisfied.
747
758
  `brainlink_bootstrap`, `brainlink_policy` and preflight responses include structured `nextActions` so clients can continue tool flows automatically.
@@ -767,8 +778,8 @@ GET /api/graph-layout
767
778
  GET /api/graph-view?x=<x>&y=<y>&w=<width>&h=<height>&scale=<scale>
768
779
  GET /api/graph-node?id=<node-id>
769
780
  GET /api/graph-filter?q=<query>&limit=<n>
770
- GET /api/search?q=<query>&limit=10&mode=hybrid
771
- GET /api/context?q=<query>&limit=12&tokens=2000&mode=hybrid
781
+ GET /api/search?q=<query>&limit=8&mode=hybrid
782
+ GET /api/context?q=<query>&limit=8&tokens=1500&mode=hybrid
772
783
  GET /api/links
773
784
  GET /api/backlinks?title=<title>
774
785
  GET /api/stats
@@ -50,7 +50,7 @@ Optional per-agent retrieval defaults in `brainlink.config.json`:
50
50
  "coding-agent": {
51
51
  "defaultSearchMode": "semantic",
52
52
  "defaultSearchLimit": 8,
53
- "defaultContextTokens": 2400,
53
+ "defaultContextTokens": 1500,
54
54
  "defaultContextStrategy": "auto",
55
55
  "defaultContextCacheTtlMs": 120000
56
56
  }
@@ -75,6 +75,15 @@ blink context "what should I know before this task?" --mode hybrid --json
75
75
 
76
76
  ```bash
77
77
  blink add "Architecture Decision" --content "Use explicit [[Bounded Context]] links and #tags. #architecture #decision"
78
+ blink remember --content "Use explicit [[Bounded Context]] links and #tags. #architecture #decision"
79
+ ```
80
+
81
+ Use `remember` when you want Brainlink to infer a title, tags and Context Links. Use `inbox add` for quick capture, then `inbox process` to triage later.
82
+
83
+ ```bash
84
+ blink inbox add --content "Quick note to classify later"
85
+ blink inbox process --json
86
+ blink session-close --content "Validated the current task"
78
87
  ```
79
88
 
80
89
  ## 6) Validate Health
@@ -82,7 +91,11 @@ blink add "Architecture Decision" --content "Use explicit [[Bounded Context]] li
82
91
  ```bash
83
92
  blink validate
84
93
  blink doctor
94
+ blink doctor --actionable
85
95
  blink stats --extended --json
96
+ blink suggest-links --broken
97
+ blink repair-links
98
+ blink search "architecture" --explain
86
99
  ```
87
100
 
88
101
  ## 7) Migrate Existing Memory (Optional)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",