@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.
- package/README.md +41 -9
- package/dist/application/frontend/client-css.js +34 -0
- package/dist/application/frontend/client-html.js +9 -0
- package/dist/application/frontend/client-js.js +85 -1
- package/dist/application/inbox.js +54 -0
- package/dist/application/memory-suggestions.js +220 -0
- package/dist/application/operational-workflows.js +153 -0
- package/dist/application/repair-broken-links.js +157 -0
- package/dist/application/server/routes.js +20 -9
- package/dist/cli/commands/practical-commands.js +278 -0
- package/dist/cli/commands/read-commands.js +13 -0
- package/dist/cli/commands/write-commands.js +13 -0
- package/dist/cli/main.js +2 -0
- package/dist/infrastructure/config.js +4 -4
- package/dist/mcp/server.js +51 -1
- package/dist/mcp/tools.js +264 -1
- package/docs/AGENT_USAGE.md +15 -4
- package/docs/QUICKSTART.md +14 -1
- package/package.json +1 -1
|
@@ -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:
|
|
15
|
-
defaultContextTokens:
|
|
16
|
-
defaultContextStrategy: '
|
|
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 =
|
|
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) => {
|
package/dist/mcp/server.js
CHANGED
|
@@ -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();
|
package/docs/AGENT_USAGE.md
CHANGED
|
@@ -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
|
|
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 `
|
|
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=
|
|
771
|
-
GET /api/context?q=<query>&limit=
|
|
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
|
package/docs/QUICKSTART.md
CHANGED
|
@@ -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":
|
|
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