@andespindola/brainlink 1.1.0 → 1.3.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/README.md +31 -5
  3. package/dist/application/analyze-vault.js +1 -1
  4. package/dist/application/build-context.js +14 -7
  5. package/dist/application/index-vault-phases.js +1 -0
  6. package/dist/application/index-vault.js +14 -0
  7. package/dist/application/provision-vault.js +74 -0
  8. package/dist/application/ranking/run-search.js +38 -3
  9. package/dist/application/search-knowledge.js +7 -16
  10. package/dist/application/server/routes.js +6 -1
  11. package/dist/application/vault-encryption.js +64 -0
  12. package/dist/application/vault-git-core.js +168 -0
  13. package/dist/application/vault-git.js +36 -141
  14. package/dist/cli/commands/read-commands.js +9 -5
  15. package/dist/cli/commands/vault-sync-commands.js +22 -0
  16. package/dist/cli/commands/write/vault-lifecycle-commands.js +1 -1
  17. package/dist/infrastructure/config.js +13 -2
  18. package/dist/infrastructure/context-packs.js +57 -18
  19. package/dist/infrastructure/file-index.js +2 -2
  20. package/dist/infrastructure/index-signature.js +27 -0
  21. package/dist/infrastructure/index-state.js +6 -0
  22. package/dist/infrastructure/knowledge-store/binary-store.js +173 -3
  23. package/dist/infrastructure/knowledge-store/index.js +3 -2
  24. package/dist/infrastructure/knowledge-store/stored-index.js +2 -2
  25. package/dist/infrastructure/vault-crypto.js +78 -0
  26. package/dist/mcp/server-instructions.js +22 -0
  27. package/dist/mcp/server.js +48 -2
  28. package/dist/mcp/tools/maintenance-tools.js +2 -2
  29. package/dist/mcp/tools/read-tools.js +8 -4
  30. package/dist/mcp/tools/vault-tools.js +163 -0
  31. package/dist/mcp/tools.js +1 -0
  32. package/docs/AGENT_USAGE.md +4 -0
  33. package/package.json +1 -1
@@ -176,7 +176,7 @@ export const syncTool = async (input) => {
176
176
  const strategy = sanitizeContextStrategy(input.strategy, context.defaults.defaultContextStrategy);
177
177
  const contextLimit = input.contextLimit ?? context.defaults.defaultSearchLimit;
178
178
  const contextTokens = input.contextTokens ?? context.defaults.defaultContextTokens;
179
- const contextPackage = await buildContextPackage(context.vault, input.contextQuery, contextLimit, contextTokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs);
179
+ const contextPackage = await buildContextPackage(context.vault, input.contextQuery, contextLimit, contextTokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs, context.defaults.cagPackTtlMs);
180
180
  const contextSession = await touchContextSession(context.vault, context.agent);
181
181
  return jsonResult({
182
182
  ...response,
@@ -198,7 +198,7 @@ export const bootstrapTool = async (input) => {
198
198
  const limit = input.limit ?? context.defaults.defaultSearchLimit;
199
199
  const tokens = input.tokens ?? context.defaults.defaultContextTokens;
200
200
  const contextPackage = input.query
201
- ? await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs)
201
+ ? await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs, context.defaults.cagPackTtlMs)
202
202
  : undefined;
203
203
  const contextSession = input.query ? await touchContextSession(context.vault, context.agent) : undefined;
204
204
  const guidance = stats.documentCount === 0
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { getBrokenLinksReport, getOrphansReport, getStats, validateVault } from '../../application/analyze-vault.js';
3
- import { buildContextPackage, readContextDataSignature } from '../../application/build-context.js';
3
+ import { buildContextPackage, readContextDataSignature, readVolatileSignature } from '../../application/build-context.js';
4
4
  import { getGraph } from '../../application/get-graph.js';
5
5
  import { getGraphContexts } from '../../application/get-graph-contexts.js';
6
6
  import { explainSearchResults, suggestBrokenLinkFixes, suggestContextLinks } from '../../application/memory-suggestions.js';
@@ -107,7 +107,7 @@ export const contextTool = async (input) => {
107
107
  const strategy = sanitizeContextStrategy(input.strategy, context.defaults.defaultContextStrategy);
108
108
  const limit = input.limit ?? context.defaults.defaultSearchLimit;
109
109
  const tokens = input.tokens ?? context.defaults.defaultContextTokens;
110
- const contextPackage = await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs);
110
+ const contextPackage = await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs, context.defaults.cagPackTtlMs);
111
111
  const contextSession = await touchContextSession(context.vault, context.agent);
112
112
  return jsonResult({
113
113
  vault: context.vault,
@@ -124,10 +124,14 @@ export const contextTool = async (input) => {
124
124
  export const contextPacksTool = async (input) => {
125
125
  const context = await resolveExecutionContext(input);
126
126
  const dataSignature = await readContextDataSignature(context.vault);
127
+ const freshness = {
128
+ volatileSignature: await readVolatileSignature(context.vault),
129
+ ttlMs: context.defaults.cagPackTtlMs
130
+ };
127
131
  if (input.action === 'clear') {
128
132
  const result = await clearContextPacks(context.vault, {
129
133
  staleOnly: input.staleOnly === true,
130
- dataSignature
134
+ freshness
131
135
  });
132
136
  return jsonResult({
133
137
  vault: context.vault,
@@ -138,7 +142,7 @@ export const contextPacksTool = async (input) => {
138
142
  ...result
139
143
  });
140
144
  }
141
- const packs = await listContextPacks(context.vault, dataSignature);
145
+ const packs = await listContextPacks(context.vault, freshness);
142
146
  return jsonResult({
143
147
  vault: context.vault,
144
148
  agent: context.agent,
@@ -0,0 +1,163 @@
1
+ import { z } from 'zod';
2
+ import { commitVault, initVaultGit, pullVault, pushVault, restoreVault, vaultGitStatus, vaultHistory } from '../../application/vault-git.js';
3
+ import { provisionVaultRepo } from '../../application/provision-vault.js';
4
+ import { exportVaultSnapshot, importVaultSnapshot } from '../../application/vault-snapshot.js';
5
+ import { jsonResult, optionalPositiveInteger, resolveExecutionContext, vaultInput } from './shared.js';
6
+ export const vaultStatusInputSchema = { ...vaultInput };
7
+ export const vaultInitInputSchema = {
8
+ ...vaultInput,
9
+ remote: z.string().min(1).optional().describe('Optional git remote URL for origin. Inline credentials are rejected; use SSH or a git credential helper.'),
10
+ branch: z.string().min(1).optional().describe('Optional branch name to use.')
11
+ };
12
+ export const vaultCommitInputSchema = {
13
+ ...vaultInput,
14
+ message: z.string().min(1).describe('Commit message. Only Markdown and .gitignore are staged; the derived index is git-ignored.'),
15
+ push: z.boolean().optional().default(false).describe('Push to origin after committing.'),
16
+ branch: z.string().min(1).optional().describe('Branch to push when push is true. Defaults to the current branch.')
17
+ };
18
+ export const vaultHistoryInputSchema = {
19
+ ...vaultInput,
20
+ limit: optionalPositiveInteger().describe('Maximum number of Markdown commits to return. Defaults to 20.')
21
+ };
22
+ export const vaultPullInputSchema = {
23
+ ...vaultInput,
24
+ skipIndex: z.boolean().optional().default(false).describe('Skip the automatic reindex after pulling changed Markdown.')
25
+ };
26
+ export const vaultRestoreInputSchema = {
27
+ ...vaultInput,
28
+ ref: z.string().min(1).describe('Git ref (commit, tag or branch) to restore the Markdown to.'),
29
+ skipIndex: z.boolean().optional().default(false).describe('Skip the automatic reindex after restoring Markdown.')
30
+ };
31
+ export const vaultExportInputSchema = {
32
+ ...vaultInput,
33
+ outputFile: z.string().min(1).describe('Path to write the portable .blvault snapshot to.')
34
+ };
35
+ export const vaultImportInputSchema = {
36
+ ...vaultInput,
37
+ snapshotFile: z.string().min(1).describe('Path to a .blvault snapshot to import. Divergent local files are preserved as *.conflict-<ts>.md.'),
38
+ skipIndex: z.boolean().optional().default(false).describe('Skip the automatic reindex after importing Markdown.')
39
+ };
40
+ export const vaultProvisionInputSchema = {
41
+ ...vaultInput,
42
+ name: z.string().min(1).optional().describe('Repository name to create. Defaults to brainlink-vault.'),
43
+ private: z.boolean().optional().default(true).describe('Create a private repository (default true).'),
44
+ enableAutoVersion: z.boolean().optional().default(true).describe('Enable autoVersion so later indexed changes auto-commit and push the vault.'),
45
+ encrypt: z.boolean().optional().default(true).describe('Encrypt the versioned vault: commit AES-256-GCM ciphertext and git-ignore plaintext Markdown. Only a Brainlink instance holding the local key can decrypt.'),
46
+ message: z.string().min(1).optional().describe('Initial commit message.')
47
+ };
48
+ // Vault versioning shells out to the `git` binary and touches the filesystem, so
49
+ // a missing binary or a git failure is surfaced as an actionable result rather
50
+ // than an unhandled exception.
51
+ const failure = (vault, error) => jsonResult({ vault, ok: false, error: error instanceof Error ? error.message : String(error) });
52
+ const reindexSummary = (result) => ({
53
+ changedMarkdown: result.changedMarkdown,
54
+ reindexed: result.index != null
55
+ });
56
+ export const vaultStatusTool = async (input) => {
57
+ const context = await resolveExecutionContext(input);
58
+ try {
59
+ return jsonResult({ vault: context.vault, ok: true, ...(await vaultGitStatus(context.vault)) });
60
+ }
61
+ catch (error) {
62
+ return failure(context.vault, error);
63
+ }
64
+ };
65
+ export const vaultInitTool = async (input) => {
66
+ const context = await resolveExecutionContext(input);
67
+ try {
68
+ const result = await initVaultGit(context.vault, { remote: input.remote, branch: input.branch });
69
+ return jsonResult({ vault: context.vault, ok: true, ...result });
70
+ }
71
+ catch (error) {
72
+ return failure(context.vault, error);
73
+ }
74
+ };
75
+ export const vaultCommitTool = async (input) => {
76
+ const context = await resolveExecutionContext(input);
77
+ try {
78
+ const commit = await commitVault(context.vault, input.message);
79
+ if (commit && input.push === true) {
80
+ await pushVault(context.vault, input.branch);
81
+ }
82
+ return jsonResult({
83
+ vault: context.vault,
84
+ ok: true,
85
+ commit,
86
+ committed: commit != null,
87
+ pushed: commit != null && input.push === true
88
+ });
89
+ }
90
+ catch (error) {
91
+ return failure(context.vault, error);
92
+ }
93
+ };
94
+ export const vaultHistoryTool = async (input) => {
95
+ const context = await resolveExecutionContext(input);
96
+ try {
97
+ return jsonResult({ vault: context.vault, ok: true, history: await vaultHistory(context.vault, input.limit ?? 20) });
98
+ }
99
+ catch (error) {
100
+ return failure(context.vault, error);
101
+ }
102
+ };
103
+ export const vaultPullTool = async (input) => {
104
+ const context = await resolveExecutionContext(input);
105
+ try {
106
+ const result = await pullVault(context.vault, { skipIndex: input.skipIndex === true });
107
+ return jsonResult({ vault: context.vault, ok: true, ...reindexSummary(result) });
108
+ }
109
+ catch (error) {
110
+ return failure(context.vault, error);
111
+ }
112
+ };
113
+ export const vaultRestoreTool = async (input) => {
114
+ const context = await resolveExecutionContext(input);
115
+ try {
116
+ const result = await restoreVault(context.vault, input.ref, { skipIndex: input.skipIndex === true });
117
+ return jsonResult({ vault: context.vault, ok: true, ref: input.ref, ...reindexSummary(result) });
118
+ }
119
+ catch (error) {
120
+ return failure(context.vault, error);
121
+ }
122
+ };
123
+ export const vaultExportTool = async (input) => {
124
+ const context = await resolveExecutionContext(input);
125
+ try {
126
+ return jsonResult({ vault: context.vault, ok: true, ...(await exportVaultSnapshot(context.vault, input.outputFile)) });
127
+ }
128
+ catch (error) {
129
+ return failure(context.vault, error);
130
+ }
131
+ };
132
+ export const vaultImportTool = async (input) => {
133
+ const context = await resolveExecutionContext(input);
134
+ try {
135
+ const result = await importVaultSnapshot(input.snapshotFile, context.vault, { skipIndex: input.skipIndex === true });
136
+ return jsonResult({
137
+ vault: context.vault,
138
+ ok: true,
139
+ imported: result.imported,
140
+ conflicted: result.conflicted,
141
+ reindexed: result.index != null
142
+ });
143
+ }
144
+ catch (error) {
145
+ return failure(context.vault, error);
146
+ }
147
+ };
148
+ export const vaultProvisionTool = async (input) => {
149
+ const context = await resolveExecutionContext(input);
150
+ try {
151
+ const result = await provisionVaultRepo(context.vault, {
152
+ name: input.name,
153
+ private: input.private,
154
+ enableAutoVersion: input.enableAutoVersion,
155
+ encrypt: input.encrypt,
156
+ message: input.message
157
+ });
158
+ return jsonResult({ vault: context.vault, ok: true, ...result });
159
+ }
160
+ catch (error) {
161
+ return failure(context.vault, error);
162
+ }
163
+ };
package/dist/mcp/tools.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { brokenLinksInputSchema, brokenLinksTool, contextInputSchema, contextPacksInputSchema, contextPacksTool, contextTool, doctorActionsInputSchema, doctorActionsTool, explainInputSchema, explainTool, graphContextsInputSchema, graphContextsTool, graphInputSchema, graphTool, orphansInputSchema, orphansTool, recommendationsInputSchema, recommendationsTool, searchInputSchema, searchTool, similarNotesInputSchema, similarNotesTool, statsInputSchema, statsTool, suggestLinksInputSchema, suggestLinksTool, validateInputSchema, validateTool, versionInputSchema, versionTool } from './tools/read-tools.js';
2
2
  export { addFileInputSchema, addFileTool, addNoteInputSchema, addNoteTool, addNotesInputSchema, addNotesTool, deleteNoteInputSchema, deleteNoteTool, deleteNotesInputSchema, deleteNotesTool, inboxAddInputSchema, inboxAddTool, inboxListInputSchema, inboxListTool, inboxProcessInputSchema, inboxProcessTool, rememberInputSchema, rememberTool, volatileAddInputSchema, volatileAddTool, volatileClearInputSchema, volatileClearTool } from './tools/write-tools.js';
3
3
  export { bootstrapInputSchema, bootstrapTool, canonicalizeContextLinksInputSchema, canonicalizeContextLinksTool, dedupeInputSchema, dedupeResolveInputSchema, dedupeResolveTool, dedupeTool, indexInputSchema, indexTool, policyInputSchema, policyTool, projectInitInputSchema, projectInitTool, repairLinksInputSchema, repairLinksTool, sessionCloseInputSchema, sessionCloseTool, syncInputSchema, syncTool } from './tools/maintenance-tools.js';
4
+ export { vaultCommitInputSchema, vaultCommitTool, vaultExportInputSchema, vaultExportTool, vaultHistoryInputSchema, vaultHistoryTool, vaultImportInputSchema, vaultImportTool, vaultInitInputSchema, vaultInitTool, vaultProvisionInputSchema, vaultProvisionTool, vaultPullInputSchema, vaultPullTool, vaultRestoreInputSchema, vaultRestoreTool, vaultStatusInputSchema, vaultStatusTool } from './tools/vault-tools.js';
@@ -72,6 +72,10 @@ BRAINLINK_MCP_TOKEN="change-me" brainlink mcp-server \
72
72
 
73
73
  The central service exposes `/mcp` for MCP clients plus `/healthz` and `/readyz` for platform probes. Use a Kubernetes `Service` for internal discovery, mount the Markdown vault through a persistent volume, and pass `Authorization: Bearer <token>` from clients when `BRAINLINK_MCP_TOKEN` or `--token` is configured.
74
74
 
75
+ ## Server Instructions On Connect
76
+
77
+ The MCP server advertises `instructions` in its initialize response, so any client that surfaces them (for example Claude Code) injects Brainlink's memory discipline into the agent's context on connect — no per-agent prompt configuration required. The instructions tell the agent to treat Brainlink as its default context source: call `brainlink_bootstrap` first with a task `query`, prefer `strategy: auto` and `mode: hybrid`, stash within-session facts with `brainlink_volatile_add`, and persist durable learnings with `brainlink_remember` / `brainlink_add_note` (connected with `[[wiki links]]` and tags), reindexing afterwards. The field is advisory: clients that ignore `instructions` are unaffected and no tool contract depends on it.
78
+
75
79
  ## Agent Namespaces
76
80
 
77
81
  Each agent writes into a dedicated namespace under `agents/<agent-id>/`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",