@andespindola/brainlink 0.1.0-beta.16 → 0.1.0-beta.161

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 (50) hide show
  1. package/AGENTS.md +9 -6
  2. package/CHANGELOG.md +27 -0
  3. package/COPYRIGHT.md +5 -0
  4. package/README.md +177 -20
  5. package/dist/application/add-note.js +13 -44
  6. package/dist/application/auto-migrate-configured-vault.js +37 -0
  7. package/dist/application/build-context.js +64 -3
  8. package/dist/application/canonical-context-links.js +209 -0
  9. package/dist/application/dedupe-notes.js +226 -0
  10. package/dist/application/frontend/client-css.js +258 -51
  11. package/dist/application/frontend/client-html.js +50 -27
  12. package/dist/application/frontend/client-js.js +1369 -605
  13. package/dist/application/frontend/client-render-worker-js.js +645 -0
  14. package/dist/application/frontend/client-worker-js.js +66 -0
  15. package/dist/application/get-graph-contexts.js +33 -0
  16. package/dist/application/get-graph-layout.js +62 -8
  17. package/dist/application/get-graph-stream-chunk.js +326 -0
  18. package/dist/application/get-graph-view.js +246 -0
  19. package/dist/application/graph-view-state.js +66 -0
  20. package/dist/application/import-legacy-sqlite.js +266 -0
  21. package/dist/application/index-vault.js +262 -23
  22. package/dist/application/migrate-context-links.js +79 -0
  23. package/dist/application/offline-pack-backup.js +44 -0
  24. package/dist/application/search-graph-node-ids.js +63 -3
  25. package/dist/application/server/routes.js +247 -7
  26. package/dist/application/start-server.js +75 -4
  27. package/dist/application/watch-vault.js +23 -2
  28. package/dist/cli/commands/agent-commands.js +7 -0
  29. package/dist/cli/commands/write-commands.js +924 -14
  30. package/dist/cli/runtime.js +10 -2
  31. package/dist/domain/context.js +54 -11
  32. package/dist/domain/graph-contexts.js +180 -0
  33. package/dist/domain/graph-layout.js +389 -18
  34. package/dist/domain/markdown.js +53 -9
  35. package/dist/domain/middle-out.js +18 -0
  36. package/dist/infrastructure/config.js +121 -4
  37. package/dist/infrastructure/file-index.js +76 -6
  38. package/dist/infrastructure/file-system-vault.js +15 -0
  39. package/dist/infrastructure/index-state.js +58 -0
  40. package/dist/infrastructure/private-pack-codec.js +71 -10
  41. package/dist/infrastructure/search-packs.js +286 -15
  42. package/dist/infrastructure/vault-migration-state.js +69 -0
  43. package/dist/infrastructure/volatile-memory.js +100 -0
  44. package/dist/mcp/runtime.js +20 -0
  45. package/dist/mcp/server.js +39 -11
  46. package/dist/mcp/tools.js +183 -7
  47. package/docs/AGENT_USAGE.md +96 -5
  48. package/docs/ARCHITECTURE.md +8 -0
  49. package/docs/QUICKSTART.md +7 -0
  50. package/package.json +7 -2
package/dist/mcp/tools.js CHANGED
@@ -4,13 +4,18 @@ 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';
6
6
  import { buildContextPackage } from '../application/build-context.js';
7
+ import { canonicalizeContextLinks } from '../application/canonical-context-links.js';
8
+ import { resolveDuplicateNotes, scanDuplicateNotes } from '../application/dedupe-notes.js';
7
9
  import { getGraph } from '../application/get-graph.js';
10
+ import { getGraphContexts } from '../application/get-graph-contexts.js';
8
11
  import { indexVault } from '../application/index-vault.js';
9
12
  import { searchKnowledge } from '../application/search-knowledge.js';
10
13
  import { resolveAgentRuntimeDefaults, sanitizeSearchMode } from '../infrastructure/config.js';
11
14
  import { loadBrainlinkConfig } from '../infrastructure/config.js';
12
15
  import { assertVaultAllowed } from '../infrastructure/file-system-vault.js';
16
+ import { addVolatileMemory, clearVolatileMemory } from '../infrastructure/volatile-memory.js';
13
17
  import { getBootstrapPolicy, getBootstrapSessionStatus, getContextSessionStatus, setBootstrapPolicy, touchBootstrapSession, touchContextSession } from '../infrastructure/session-state.js';
18
+ import { getRuntimeMetadata } from './runtime.js';
14
19
  const positiveInteger = (fallback) => z
15
20
  .number()
16
21
  .int()
@@ -234,7 +239,25 @@ export const addNoteInputSchema = {
234
239
  .describe('Durable Markdown memory. Include explicit [[wiki links]] and #tags when the memory should be connected. Put priority markers near important links, for example priority: high, #important or #critical.'),
235
240
  ...agentInput,
236
241
  allowSensitive: z.boolean().optional().default(false).describe('Allow content that looks like a secret.'),
237
- autoIndex: z.boolean().optional().default(true).describe('Reindex vault after writing note.')
242
+ autoIndex: z.boolean().optional().default(true).describe('Reindex vault after writing note.'),
243
+ autoContextLinks: z
244
+ .boolean()
245
+ .optional()
246
+ .describe('Automatically add canonical Context Links to the inferred visual context hub. Defaults to Brainlink config.')
247
+ };
248
+ export const volatileAddInputSchema = {
249
+ ...vaultInput,
250
+ ...agentInput,
251
+ content: z
252
+ .string()
253
+ .min(1)
254
+ .describe('Temporary agent-decided memory. Use for current task state, hypotheses, transient user preferences and unconfirmed findings.'),
255
+ ttlMinutes: optionalPositiveInteger().describe('Minutes before this volatile memory expires. Defaults to 240.'),
256
+ tags: z.array(z.string()).optional().default([]).describe('Optional tags for volatile retrieval.')
257
+ };
258
+ export const volatileClearInputSchema = {
259
+ ...vaultInput,
260
+ ...agentInput
238
261
  };
239
262
  export const addFileInputSchema = {
240
263
  ...vaultInput,
@@ -244,8 +267,20 @@ export const addFileInputSchema = {
244
267
  autoIndex: z.boolean().optional().default(true).describe('Reindex vault after ingesting file.'),
245
268
  allowSensitive: z.boolean().optional().default(false).describe('Allow content that looks like a secret.')
246
269
  };
270
+ export const canonicalizeContextLinksInputSchema = {
271
+ ...vaultInput,
272
+ ...agentInput,
273
+ dryRun: z.boolean().optional().default(false).describe('Preview canonical context-link writes without changing Markdown.'),
274
+ createHubs: z.boolean().optional().default(true).describe('Create missing context hub notes when needed.'),
275
+ autoIndex: z.boolean().optional().default(true).describe('Reindex after canonicalization when files changed.')
276
+ };
247
277
  export const indexInputSchema = {
248
- ...vaultInput
278
+ ...vaultInput,
279
+ full: z
280
+ .boolean()
281
+ .optional()
282
+ .default(false)
283
+ .describe('Force a complete reindex from Markdown source without reusing unchanged index entries.')
249
284
  };
250
285
  export const validateInputSchema = {
251
286
  ...vaultInput,
@@ -255,6 +290,10 @@ export const graphInputSchema = {
255
290
  ...vaultInput,
256
291
  ...agentInput
257
292
  };
293
+ export const graphContextsInputSchema = {
294
+ ...vaultInput,
295
+ ...agentInput
296
+ };
258
297
  export const brokenLinksInputSchema = {
259
298
  ...vaultInput,
260
299
  ...agentInput
@@ -303,6 +342,10 @@ export const policyInputSchema = {
303
342
  .describe('Run automatic bootstrap during MCP server startup using configured default vault/agent.'),
304
343
  staleAfterMinutes: positiveInteger(120).describe('Bootstrap freshness window in minutes before read tools require a new bootstrap.')
305
344
  };
345
+ export const versionInputSchema = {
346
+ ...vaultInput,
347
+ ...agentInput
348
+ };
306
349
  export const recommendationsInputSchema = {
307
350
  ...vaultInput,
308
351
  ...agentInput,
@@ -311,6 +354,20 @@ export const recommendationsInputSchema = {
311
354
  limit: optionalPositiveInteger().describe('Optional context limit override for generated recommendations.'),
312
355
  tokens: optionalPositiveInteger().describe('Optional context token budget override for generated recommendations.')
313
356
  };
357
+ export const dedupeInputSchema = {
358
+ ...vaultInput,
359
+ ...agentInput,
360
+ limit: optionalPositiveInteger().describe('Maximum duplicate candidate pairs to return.'),
361
+ minScore: z.number().min(0).max(1).optional().describe('Minimum semantic similarity score between 0 and 1.'),
362
+ semantic: z.boolean().optional().default(true).describe('Enable semantic duplicate detection in addition to exact content hash matches.')
363
+ };
364
+ export const dedupeResolveInputSchema = {
365
+ ...vaultInput,
366
+ leftPath: z.string().min(1).describe('Left note path from dedupe results.'),
367
+ rightPath: z.string().min(1).describe('Right note path from dedupe results.'),
368
+ action: z.enum(['merge', 'link', 'ignore']).describe('Resolution action.'),
369
+ autoIndex: z.boolean().optional().default(true).describe('Reindex after duplicate resolution.')
370
+ };
314
371
  export const contextTool = async (input) => {
315
372
  const context = await resolveExecutionContext(input);
316
373
  const readiness = await ensureBootstrapReady(context, input, 'brainlink_context');
@@ -361,9 +418,18 @@ export const addNoteTool = async (input) => {
361
418
  const context = await resolveExecutionContext(input);
362
419
  const shouldIndex = isTruthy(input.autoIndex);
363
420
  const added = await addNoteWithMetadata(context.vault, input.title, input.content, context.agent, {
364
- allowSensitive: input.allowSensitive
421
+ allowSensitive: input.allowSensitive,
422
+ autoContextLinks: input.autoContextLinks ?? context.config.autoCanonicalContextLinks
365
423
  });
366
424
  const index = shouldIndex ? await indexVault(context.vault) : undefined;
425
+ const focusPath = added.path.includes('agents/') ? added.path.slice(added.path.indexOf('agents/')).replaceAll('\\', '/') : undefined;
426
+ const possibleDuplicates = await scanDuplicateNotes(context.vault, {
427
+ agentId: context.agent,
428
+ focusPath,
429
+ limit: 5,
430
+ minSemanticScore: 0.92,
431
+ includeSemantic: true
432
+ });
367
433
  return jsonResult({
368
434
  vault: context.vault,
369
435
  title: input.title,
@@ -372,11 +438,33 @@ export const addNoteTool = async (input) => {
372
438
  writeConnectivity: {
373
439
  autoLinked: added.autoLinked,
374
440
  linkTarget: added.linkTarget,
375
- guaranteedEdge: true
441
+ context: added.context,
442
+ hubCreated: added.hubCreated,
443
+ guaranteedEdge: added.autoLinked
376
444
  },
445
+ possibleDuplicates,
377
446
  ...(index ? { index } : {})
378
447
  });
379
448
  };
449
+ export const volatileAddTool = async (input) => {
450
+ const context = await resolveExecutionContext(input);
451
+ const entry = await addVolatileMemory(context.vault, input.content, context.agent ?? 'shared', input.ttlMinutes ?? 240, input.tags);
452
+ return jsonResult({
453
+ vault: context.vault,
454
+ agent: context.agent,
455
+ volatile: true,
456
+ entry
457
+ });
458
+ };
459
+ export const volatileClearTool = async (input) => {
460
+ const context = await resolveExecutionContext(input);
461
+ const cleared = await clearVolatileMemory(context.vault, context.agent);
462
+ return jsonResult({
463
+ vault: context.vault,
464
+ agent: context.agent,
465
+ cleared
466
+ });
467
+ };
380
468
  export const addFileTool = async (input) => {
381
469
  const context = await resolveExecutionContext(input);
382
470
  const content = await readFile(input.filePath, 'utf8');
@@ -387,7 +475,8 @@ export const addFileTool = async (input) => {
387
475
  }
388
476
  const shouldIndex = isTruthy(input.autoIndex);
389
477
  const added = await addNoteWithMetadata(context.vault, title, content, context.agent, {
390
- allowSensitive: input.allowSensitive
478
+ allowSensitive: input.allowSensitive,
479
+ autoContextLinks: context.config.autoCanonicalContextLinks
391
480
  });
392
481
  const index = shouldIndex ? await indexVault(context.vault) : undefined;
393
482
  return jsonResult({
@@ -399,14 +488,35 @@ export const addFileTool = async (input) => {
399
488
  writeConnectivity: {
400
489
  autoLinked: added.autoLinked,
401
490
  linkTarget: added.linkTarget,
402
- guaranteedEdge: true
491
+ context: added.context,
492
+ hubCreated: added.hubCreated,
493
+ guaranteedEdge: added.autoLinked
403
494
  },
404
495
  ...(index ? { index } : {})
405
496
  });
406
497
  };
498
+ export const canonicalizeContextLinksTool = async (input) => {
499
+ const context = await resolveExecutionContext(input);
500
+ const result = await canonicalizeContextLinks(context.vault, {
501
+ agentId: context.agent,
502
+ dryRun: input.dryRun === true,
503
+ createMissingHubs: input.createHubs !== false
504
+ });
505
+ const index = input.autoIndex !== false && !result.dryRun && result.changed > 0
506
+ ? await indexVault(context.vault, { full: true })
507
+ : undefined;
508
+ return jsonResult({
509
+ vault: context.vault,
510
+ agent: context.agent,
511
+ ...result,
512
+ ...(index ? { index } : {})
513
+ });
514
+ };
407
515
  export const indexTool = async (input) => {
408
516
  const context = await resolveExecutionContext(input);
409
- const result = await indexVault(context.vault);
517
+ const result = await indexVault(context.vault, {
518
+ full: input.full === true
519
+ });
410
520
  return jsonResult({
411
521
  vault: context.vault,
412
522
  ...result
@@ -450,6 +560,25 @@ export const graphTool = async (input) => {
450
560
  ...graph
451
561
  });
452
562
  };
563
+ export const graphContextsTool = async (input) => {
564
+ const context = await resolveExecutionContext(input);
565
+ const readiness = await ensureBootstrapReady(context, input, 'brainlink_graph_contexts');
566
+ if (readiness.preflight) {
567
+ return readiness.preflight;
568
+ }
569
+ const contextReadiness = await ensureContextReady(context, input, 'brainlink_graph_contexts');
570
+ if (contextReadiness.preflight) {
571
+ return contextReadiness.preflight;
572
+ }
573
+ const contexts = await getGraphContexts(context.vault, context.agent);
574
+ return jsonResult({
575
+ vault: context.vault,
576
+ agent: context.agent,
577
+ ...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
578
+ ...(contextReadiness.context ? { contextReadiness: contextReadiness.context } : {}),
579
+ contexts
580
+ });
581
+ };
453
582
  export const brokenLinksTool = async (input) => {
454
583
  const context = await resolveExecutionContext(input);
455
584
  const readiness = await ensureBootstrapReady(context, input, 'brainlink_broken_links');
@@ -701,12 +830,21 @@ export const policyTool = async (input) => {
701
830
  return jsonResult(withNextActions({
702
831
  vault: context.vault,
703
832
  agent: context.agent,
833
+ runtime: getRuntimeMetadata(),
704
834
  policy,
705
835
  bootstrapStatus,
706
836
  contextStatus,
707
837
  ...(input.preset ? { presetApplied: input.preset } : {})
708
838
  }, withContextAction));
709
839
  };
840
+ export const versionTool = async (input) => {
841
+ const context = await resolveExecutionContext(input);
842
+ return jsonResult({
843
+ vault: context.vault,
844
+ agent: context.agent,
845
+ runtime: getRuntimeMetadata()
846
+ });
847
+ };
710
848
  export const recommendationsTool = async (input) => {
711
849
  const context = await resolveExecutionContext(input);
712
850
  const policy = await getBootstrapPolicy();
@@ -792,6 +930,17 @@ export const recommendationsTool = async (input) => {
792
930
  tokens
793
931
  }
794
932
  },
933
+ {
934
+ tool: 'brainlink_dedupe',
935
+ reason: 'Detect and resolve duplicate durable notes to keep memory quality high.',
936
+ args: {
937
+ vault: context.vault,
938
+ ...(context.agent ? { agent: context.agent } : {}),
939
+ limit: 10,
940
+ minScore: 0.92,
941
+ semantic: true
942
+ }
943
+ },
795
944
  {
796
945
  tool: 'brainlink_add_note',
797
946
  reason: 'Persist durable outcomes after task completion (write responses include connectivity metadata).',
@@ -818,3 +967,30 @@ export const recommendationsTool = async (input) => {
818
967
  recommendations
819
968
  });
820
969
  };
970
+ export const dedupeTool = async (input) => {
971
+ const context = await resolveExecutionContext(input);
972
+ const duplicates = await scanDuplicateNotes(context.vault, {
973
+ agentId: context.agent,
974
+ limit: input.limit ?? 25,
975
+ minSemanticScore: input.minScore ?? 0.92,
976
+ includeSemantic: input.semantic !== false
977
+ });
978
+ return jsonResult({
979
+ vault: context.vault,
980
+ agent: context.agent,
981
+ duplicates
982
+ });
983
+ };
984
+ export const dedupeResolveTool = async (input) => {
985
+ const context = await resolveExecutionContext(input);
986
+ const result = await resolveDuplicateNotes(context.vault, {
987
+ leftPath: input.leftPath,
988
+ rightPath: input.rightPath,
989
+ action: input.action,
990
+ autoIndex: isTruthy(input.autoIndex)
991
+ });
992
+ return jsonResult({
993
+ vault: context.vault,
994
+ ...result
995
+ });
996
+ };
@@ -52,8 +52,11 @@ Use `blink config where` and `blink config doctor` to inspect active paths and e
52
52
 
53
53
  You can also set `defaultAgent` in `brainlink.config.json` / `.brainlink.json` (for example `"defaultAgent": "coding-agent"`). When set, CLI commands and MCP calls reuse it when `--agent`/`agent` is not passed.
54
54
  You can set `agentProfiles` to define per-agent defaults for `defaultSearchMode`, `defaultSearchLimit` and `defaultContextTokens`.
55
+ You can tune search-pack compression with `searchPack.rowChunkSize`, `searchPack.compressionLevel` and `searchPack.useDictionary`.
56
+ Guardrails for benchmark acceptance are configured with `searchPack.guardrailMinSavingsPercent` and `searchPack.guardrailMaxLatencyRegressionPercent`.
55
57
 
56
58
  `autoIndexOnWrite` (default: `true`) controls whether `add` and MCP write tools index right after writing.
59
+ `autoCanonicalContextLinks` (default: `true`) controls whether CLI/MCP write tools add canonical `## Context Links` to inferred context hubs.
57
60
 
58
61
  ## Agent Namespaces
59
62
 
@@ -159,7 +162,7 @@ Brainlink only builds graph edges from Markdown `[[wiki links]]`.
159
162
 
160
163
  The `context` command is read-only. It retrieves indexed notes and returns a compact package for the model, but it does not write memory, create backlinks, infer relationships or modify the graph. If an agent reads context and then learns something durable, the agent must write a note with explicit links before that knowledge becomes connected memory.
161
164
 
162
- Graph edges are weighted during indexing. Repeated links increase weight. Links inside headings or task-list lines receive a small boost. Priority markers on the same line as a link raise its priority:
165
+ Graph edges are weighted during indexing. Repeated links increase weight. Links inside headings or task-list lines receive a small boost. Priority markers on the same line as a link raise its priority. The graph relationship model indexes every non-self Markdown `[[wiki link]]` as an edge, including structural hub links such as `Memory Hub`, `Knowledge Root`, `MOC` and map notes. Older indexes without the current graph link model version are automatically rebuilt on the next index run.
163
166
 
164
167
  ```md
165
168
  - [ ] Review [[Architecture]] priority: high
@@ -290,6 +293,8 @@ blink add "Implementation Boundary" \
290
293
  blink index
291
294
  ```
292
295
 
296
+ Use `blink index --full` when you need to rebuild every note from Markdown source. Full reindexing builds the replacement index before persisting it, preserving the previous context until the new index is ready. Brainlink also runs this complete source reindex automatically when it detects an older stored graph link model.
297
+
293
298
  ### Claude Code-Style Agent
294
299
 
295
300
  Use Brainlink as a preflight memory read before editing files:
@@ -377,6 +382,18 @@ blink migrate-vault --from ~/.brainlink/vault --to ./team-vault --report ./migra
377
382
 
378
383
  Use `--dry-run` to preview `copied`, `conflicted`, `unchanged` before writing files.
379
384
 
385
+ ### Import Legacy SQLite DB
386
+
387
+ ```bash
388
+ blink db-import --vault ./team-vault
389
+ blink db-import --vault ./team-vault --db ./legacy/brainlink.db
390
+ blink db-import --vault ./team-vault --db ./legacy/brainlink.db --table legacy_notes --dry-run
391
+ ```
392
+
393
+ `db-import` migrates rows from legacy SQLite memory into Markdown notes in the current vault and indexes the result by default.
394
+ Without `--db`, Brainlink auto-detects common legacy database paths.
395
+ Use `--agent` to force namespace, `--limit` for staged migration, `--dry-run` to preview writes, and `--no-index` to postpone indexing.
396
+
380
397
  ### Install Agent Integration
381
398
 
382
399
  ```bash
@@ -390,6 +407,7 @@ blink agent status
390
407
  ```
391
408
 
392
409
  `agent install` configures Brainlink MCP in `~/.codex/config.toml` so compatible agents can use Brainlink by default.
410
+ `agent install` and `agent upgrade` automatically apply the `fully-auto` MCP bootstrap policy (`enforceBootstrap=true`, `enforceContextFirst=true`, `autoBootstrapOnRead=true`, `autoBootstrapOnStartup=true`) so all plug-and-play Brainlink features start enabled.
393
411
  Use `agent upgrade` on legacy installations to reapply the latest defaults and run self-test diagnostics.
394
412
  Use `agent policy --preset fully-auto` to keep startup/read auto-bootstrap enabled, or `agent policy --preset strict` to force explicit bootstrap calls.
395
413
 
@@ -417,6 +435,25 @@ This creates a slugged Markdown file with frontmatter and a heading.
417
435
 
418
436
  The CLI blocks common secret patterns by default. Do not use `--allow-sensitive` unless the vault is intentionally protected.
419
437
  Brainlink also auto-connects notes that have no `[[wiki links]]` by adding a fallback edge to an agent hub note, so new memory does not stay disconnected.
438
+ `add` also returns `possibleDuplicates` (exact hash + semantic candidates) so agents can decide duplicate resolution immediately.
439
+
440
+ ### Detect Duplicate Notes
441
+
442
+ ```bash
443
+ blink dedupe --vault ./vault --json
444
+ blink dedupe --vault ./vault --agent coding-agent --limit 20 --min-score 0.92 --json
445
+ blink dedupe --vault ./vault --no-semantic --json
446
+ ```
447
+
448
+ ### Resolve Duplicate Notes
449
+
450
+ ```bash
451
+ blink dedupe-resolve --vault ./vault --left agents/shared/a.md --right agents/shared/b.md --action merge --json
452
+ blink dedupe-resolve --vault ./vault --left agents/shared/a.md --right agents/shared/b.md --action link --json
453
+ blink dedupe-resolve --vault ./vault --left agents/shared/a.md --right agents/shared/b.md --action ignore --json
454
+ ```
455
+
456
+ `dedupe-resolve` keeps connectivity: non-merge actions still create a low-priority related edge (`#related-to`).
420
457
 
421
458
  For agent-private memory:
422
459
 
@@ -446,6 +483,37 @@ This scans Markdown files and rebuilds:
446
483
  - links
447
484
  - full-text search records
448
485
 
486
+ ### Benchmark Indexing Realtime
487
+
488
+ ```bash
489
+ blink bench --vault ./vault
490
+ blink bench --vault ./vault --watch
491
+ blink bench --vault ./vault --watch --debounce 500
492
+ blink bench --vault ./vault --json
493
+ ```
494
+
495
+ `bench` runs indexing with realtime phase events and prints a run summary with:
496
+
497
+ - indexed totals (documents, chunks, links)
498
+ - elapsed time and changed document count
499
+ - pack rebuild status and reason
500
+ - pack compression metrics (`inputBytes`, `outputBytes`, ratio/saved percentage)
501
+ - objective guardrails (`guardrailMinSavingsPercent`, `guardrailMaxLatencyRegressionPercent`)
502
+
503
+ Use `--watch` for continuous benchmark runs while editing notes. Watch mode is supported only for local filesystem vaults.
504
+ If pack manifest metadata is missing but encrypted `.blpk` files are present, Brainlink repairs manifest metadata before deciding rebuild policy to avoid unnecessary full repacks on small updates.
505
+
506
+ ### Create Offline Pack Backup
507
+
508
+ ```bash
509
+ blink pack-backup --vault ./vault
510
+ blink pack-backup --vault ./vault --output ./vault/.brainlink/backups/custom.blpkbak.gz
511
+ blink pack-backup --vault ./vault --json
512
+ ```
513
+
514
+ `pack-backup` creates an offline artifact with second-stage compression on top of encrypted `.blpk` packs.
515
+ This is outside the online retrieval path (`index`, `search`, `context`), which keeps a single compression stage.
516
+
449
517
  ### Search Knowledge
450
518
 
451
519
  ```bash
@@ -465,6 +533,7 @@ Search modes:
465
533
  - `semantic`: local deterministic embedding similarity.
466
534
 
467
535
  Hybrid results are cached in-memory for a short TTL and invalidated when `.brainlink/index.json` changes.
536
+ Context assembly uses middle-out ordering inside each note: the highest-scoring chunk is selected first, then nearby chunks are expanded while token budget allows.
468
537
 
469
538
  ### Build Agent Context
470
539
 
@@ -523,15 +592,26 @@ shared: 30 documents
523
592
  ```bash
524
593
  blink server --host 127.0.0.1 --port 4321
525
594
  blink server --vault ./vault --host 127.0.0.1 --port 4321
595
+ blink server --vault ./vault --host 127.0.0.1 --port 4321 --no-open
526
596
  ```
527
597
 
528
598
  This starts a local frontend for inspecting the knowledge graph.
599
+ By default it tries to open the graph in a native desktop GUI window:
600
+ - macOS: Swift + WebKit
601
+ - Windows: PowerShell WinForms WebBrowser
602
+ - Linux: optional Python GTK + WebKit2 (requires `python3` + `gi` + `WebKit2`)
603
+
604
+ On Linux, native GUI is disabled by default for better startup performance. Enable it with `BRAINLINK_LINUX_NATIVE_GUI=1`.
605
+ If native GUI launch is unavailable, it falls back to dedicated app-window mode and then to the default browser.
606
+ Use `--no-open` to keep the server headless.
607
+ When native GUI is active, the GUI window closes automatically when the `blink server` process stops.
529
608
 
530
609
  Without `--vault`, the graph UI serves `$HOME/.brainlink/vault`.
531
610
 
532
- The frontend includes an agent selector. Selecting an agent calls the same read APIs with `agent=<agent-id>` and renders that namespace instead of merging every agent into one graph.
611
+ The frontend includes an agent selector that shows only the agent id. Selecting an agent calls the same read APIs with `agent=<agent-id>` and renders that namespace instead of merging every agent into one graph.
533
612
 
534
- Graph navigation controls include zoom in, zoom out, fit visible nodes and reset-to-fit-all nodes. Mouse wheel zoom is anchored to the cursor. Totals for notes, links and tags stay visible as floating metrics under the Brainlink title, and node details open on click in a modal (tags, outgoing links, backlinks and Markdown content).
613
+ Graph navigation controls include zoom in, zoom out, fit visible nodes and reset-to-fit-all nodes. Mouse wheel zoom (including `cmd+scroll` and `ctrl+scroll`) is anchored to the cursor and applied immediately without delayed focus interpolation. Keyboard shortcuts are `+` (zoom in), `-` (zoom out) and `0` (reset fit). Double-click on empty canvas zooms in at cursor position. Clicking a node opens its details panel. Totals for notes, links and tags stay visible as floating metrics under the Brainlink title, and node details open in a non-modal side panel (tags, outgoing links, backlinks and Markdown content), so zoom and pan remain available during inspection. The visual layout is a cauliflower hub layout: the primary hub stays centered, segment hubs anchor surrounding lobes, and visual edges are simplified to root hub -> segment hubs -> local context nodes. Indexed weighted wiki-link edges remain available for backlinks, ranking and context traversal outside the render layer. At high zoom-out, graph streams show segment hub clusters first; zooming in progressively reveals the individual nodes inside those lobes. Node titles appear as zoom approaches readable scale, limited to on-screen nodes in very large graphs.
614
+ During graph filtering, Brainlink keeps hub context nodes visible (`Memory Hub`/`MOC`/high-degree fallback) so filtered views still show relationship anchors.
535
615
 
536
616
  The command reindexes by default, then serves:
537
617
 
@@ -548,12 +628,14 @@ Use `--no-index` when you need to inspect the current index without rebuilding i
548
628
  blink server --vault ./vault --no-index
549
629
  ```
550
630
 
551
- Use `--watch` to keep the graph updated after Markdown edits:
631
+ The server watches Markdown files by default and keeps the graph updated after edits:
552
632
 
553
633
  ```bash
554
- blink server --vault ./vault --watch
634
+ blink server --vault ./vault
555
635
  ```
556
636
 
637
+ Use `--no-watch` when you need to run the graph server without realtime reindexing.
638
+
557
639
  ### Watch A Vault
558
640
 
559
641
  ```bash
@@ -589,13 +671,19 @@ Available MCP tools:
589
671
  - `brainlink_recommendations`
590
672
  - `brainlink_context`
591
673
  - `brainlink_search`
674
+ - `brainlink_dedupe`
675
+ - `brainlink_resolve_duplicate`
592
676
  - `brainlink_add_note`
593
677
  - `brainlink_add_file`
678
+ - `brainlink_canonicalize_context_links`
679
+ - `brainlink_volatile_add`
680
+ - `brainlink_volatile_clear`
594
681
  - `brainlink_index`
595
682
  - `brainlink_stats`
596
683
  - `brainlink_validate`
597
684
  - `brainlink_sync`
598
685
  - `brainlink_graph`
686
+ - `brainlink_graph_contexts`
599
687
  - `brainlink_broken_links`
600
688
  - `brainlink_orphans`
601
689
 
@@ -610,6 +698,7 @@ MCP clients can pass `vault` and `agent` arguments per tool call. Set `BRAINLINK
610
698
 
611
699
  `brainlink_graph` returns weighted edges. Agents should prefer higher `weight` and stronger `priority` when deciding which related notes matter most.
612
700
  `brainlink_add_note` and `brainlink_add_file` return `writeConnectivity` metadata and guarantee at least one edge for new notes.
701
+ Agents should use `brainlink_volatile_add` for temporary task state, hypotheses, local execution details and unconfirmed findings. Volatile memory is included in `brainlink_context` with `Volatile: true`, expires by TTL and does not create durable Markdown notes or graph edges.
613
702
 
614
703
  ```bash
615
704
  export BRAINLINK_ALLOWED_VAULTS="/absolute/path/to/project-vault"
@@ -620,6 +709,7 @@ export BRAINLINK_ALLOWED_VAULTS="/absolute/path/to/project-vault"
620
709
  ```txt
621
710
  GET /api/graph
622
711
  GET /api/graph-layout
712
+ GET /api/graph-view?x=<x>&y=<y>&w=<width>&h=<height>&scale=<scale>
623
713
  GET /api/graph-node?id=<node-id>
624
714
  GET /api/graph-filter?q=<query>&limit=<n>
625
715
  GET /api/search?q=<query>&limit=10&mode=hybrid
@@ -635,6 +725,7 @@ GET /api/validate
635
725
  The HTTP API is read-only. Use the CLI for writes and indexing.
636
726
 
637
727
  Indexing writes private encrypted search packs at `.brainlink/search-packs/*.blpk` for resilient retrieval and portability.
728
+ Pack search now uses compressed-space prefiltering (token bloom index per pack) before decrypting/reading pack payloads.
638
729
  Pack decryption keys are resolved from `$BRAINLINK_HOME/keys` (or `BRAINLINK_SEARCH_PACK_KEY` when explicitly set).
639
730
 
640
731
  ## Agent Integration Contract
@@ -138,8 +138,10 @@ read markdown files
138
138
  question
139
139
  -> selected mode: fts | semantic | hybrid
140
140
  -> optional query embedding
141
+ -> optional compressed pack prefilter (token bloom)
141
142
  -> lexical scoring and/or semantic cosine scoring
142
143
  -> cosine similarity over candidate chunks
144
+ -> middle-out context expansion around strongest chunk
143
145
  -> ranked chunks with textScore and semanticScore
144
146
  -> token-budget selection
145
147
  -> Markdown context package
@@ -152,11 +154,15 @@ server command
152
154
  -> optional index rebuild
153
155
  -> HTTP server
154
156
  -> /api/agents lists indexed namespaces
157
+ -> /api/graph-contexts lists visual graph contexts
155
158
  -> /api/graph reads indexed documents and links
159
+ -> /api/graph-layout derives a cauliflower hub layout from indexed graph data
160
+ -> optional context query narrows the layout to a segment-scoped cauliflower subgraph
156
161
  -> browser renders graph canvas
157
162
  ```
158
163
 
159
164
  The graph UI is intentionally read-only. Markdown remains the write interface and index artifacts remain derived data.
165
+ The cauliflower layout is a visual projection over the indexed graph. Indexed weighted wiki-link edges remain unchanged for backlinks, ranking, graph reads and context traversal. The browser layout renders a simplified hierarchy instead: primary hub -> segment hubs -> local context nodes. This avoids unstable cross-context visual edges while preserving the real relationship model outside the render layer. Zoomed-out graph streams summarize those lobes as segment clusters before revealing individual nodes on zoom-in.
160
166
 
161
167
  ## HTTP API Flow
162
168
 
@@ -169,6 +175,7 @@ HTTP request
169
175
  ```
170
176
 
171
177
  The HTTP API is local-first and unauthenticated. It is meant for local agents, browser UI, and development workflows.
178
+ The route adapter caches generated frontend assets (`/`, `/styles.css`, `/app.js`, `/app-worker.js`) and graph-layout JSON payloads by layout signature to reduce repeated CPU and allocation pressure during navigation.
172
179
 
173
180
  ## MCP Flow
174
181
 
@@ -293,6 +300,7 @@ Markdown keeps the system portable, inspectable, Git-friendly, and compatible wi
293
300
  Brainlink uses a local JSON index plus encrypted pack exports for fast rebuildable retrieval without external infrastructure.
294
301
  Hybrid retrieval also uses a short-lived in-memory cache keyed by vault/query/agent and invalidated by index file mtime to reduce repeated query latency.
295
302
  Indexing exports private encrypted pack files (`.brainlink/search-packs/*.blpk`) from indexed chunks for fast retrieval and recovery continuity.
303
+ Pack manifests include compressed-space token bloom metadata so retrieval can skip unrelated packs before decryption.
296
304
  Pack encryption keys are resolved from `$BRAINLINK_HOME/keys` or from `BRAINLINK_SEARCH_PACK_KEY` when configured.
297
305
  Legacy `.jsonl.gz` search packs are auto-upgraded to `.blpk` on first retrieval flow.
298
306
 
@@ -102,3 +102,10 @@ S3 target:
102
102
  ```bash
103
103
  blink migrate-vault --from ~/.brainlink/vault --to "s3://my-memory-bucket/brainlink" --dry-run
104
104
  ```
105
+
106
+ Legacy SQLite import:
107
+
108
+ ```bash
109
+ blink db-import --vault ./team-vault
110
+ blink db-import --vault ./team-vault --db ./legacy/brainlink.db --dry-run
111
+ ```
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.16",
3
+ "version": "0.1.0-beta.161",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
- "author": "Anderson Espindola",
7
+ "author": "Substructa",
8
8
  "homepage": "https://github.com/andersonflima/brainlink#readme",
9
9
  "repository": {
10
10
  "type": "git",
@@ -32,6 +32,7 @@
32
32
  "dist",
33
33
  "assets",
34
34
  "README.md",
35
+ "COPYRIGHT.md",
35
36
  "LICENSE",
36
37
  "CHANGELOG.md",
37
38
  "CONTRIBUTING.md",
@@ -61,6 +62,10 @@
61
62
  "commander": "^14.0.2",
62
63
  "zod": "^4.3.6"
63
64
  },
65
+ "overrides": {
66
+ "hono": "4.12.21",
67
+ "qs": "6.15.2"
68
+ },
64
69
  "devDependencies": {
65
70
  "@types/node": "^24.9.2",
66
71
  "tsx": "^4.21.0",