@andespindola/brainlink 0.1.0-beta.1 → 0.1.0-beta.11
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/CHANGELOG.md +46 -0
- package/README.md +241 -10
- package/dist/application/add-note.js +62 -13
- package/dist/application/analyze-vault.js +104 -9
- package/dist/application/frontend/client-css.js +154 -71
- package/dist/application/frontend/client-html.js +42 -33
- package/dist/application/frontend/client-js.js +316 -84
- package/dist/application/get-graph-layout.js +22 -7
- package/dist/application/get-graph-node.js +12 -0
- package/dist/application/get-graph-summary.js +12 -0
- package/dist/application/index-vault.js +7 -0
- package/dist/application/migrate-vault.js +91 -0
- package/dist/application/search-graph-node-ids.js +12 -0
- package/dist/application/search-knowledge.js +74 -4
- package/dist/application/server/routes.js +27 -1
- package/dist/cli/commands/agent-commands.js +412 -0
- package/dist/cli/commands/config-commands.js +167 -0
- package/dist/cli/commands/read-commands.js +25 -8
- package/dist/cli/commands/write-commands.js +173 -4
- package/dist/cli/main.js +4 -0
- package/dist/cli/runtime.js +5 -2
- package/dist/domain/embeddings.js +2 -1
- package/dist/domain/graph-layout.js +20 -14
- package/dist/domain/markdown.js +36 -4
- package/dist/infrastructure/config.js +94 -8
- package/dist/infrastructure/file-system-vault.js +15 -0
- package/dist/infrastructure/paths.js +9 -1
- package/dist/infrastructure/search-packs.js +151 -0
- package/dist/infrastructure/session-state.js +172 -0
- package/dist/infrastructure/sqlite/graph-reader.js +252 -105
- package/dist/infrastructure/sqlite/recovery.js +83 -0
- package/dist/infrastructure/sqlite/schema.js +4 -1
- package/dist/infrastructure/sqlite/search-reader.js +104 -72
- package/dist/infrastructure/sqlite-index.js +16 -3
- package/dist/mcp/main.js +11 -3
- package/dist/mcp/server.js +17 -2
- package/dist/mcp/startup.js +35 -0
- package/dist/mcp/tools.js +571 -19
- package/docs/AGENT_USAGE.md +87 -3
- package/docs/ARCHITECTURE.md +16 -1
- package/docs/QUICKSTART.md +104 -0
- package/docs/RELEASE.md +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0-beta.4
|
|
4
|
+
|
|
5
|
+
- Added bootstrap session-state persistence in `$BRAINLINK_HOME/session-state.json` for vault/agent readiness tracking.
|
|
6
|
+
- Added MCP `brainlink_policy` tool and default bootstrap enforcement for read tools.
|
|
7
|
+
- Added `agent install --self-test` diagnostics and bootstrap readiness details in `agent status`.
|
|
8
|
+
- Added `agent upgrade` for legacy installations to reapply latest MCP/plugin defaults with self-test diagnostics.
|
|
9
|
+
- Added `config doctor --fix` safe autofix mode with dry-run default behavior.
|
|
10
|
+
- Added detailed per-file migration reporting through `migrate-vault --report`.
|
|
11
|
+
- Added `quickstart` command to run plug-and-play vault + bootstrap + agent setup in one flow.
|
|
12
|
+
- Added structured MCP `nextActions` in bootstrap/policy/preflight responses for automatic client continuation.
|
|
13
|
+
- Added default MCP read auto-bootstrap behavior controlled by `brainlink_policy.autoBootstrapOnRead`.
|
|
14
|
+
- Added default MCP startup bootstrap behavior controlled by `brainlink_policy.autoBootstrapOnStartup`.
|
|
15
|
+
- Added CLI MCP policy presets through `blink agent policy --preset fully-auto|strict`.
|
|
16
|
+
- Added write-time non-orphan enforcement by auto-linking notes without wiki edges to agent hub notes.
|
|
17
|
+
- Added MCP `brainlink_policy` presets (`fully-auto`, `strict`) for one-call policy switching.
|
|
18
|
+
- Added MCP write connectivity metadata in `brainlink_add_note`/`brainlink_add_file` responses.
|
|
19
|
+
- Added MCP `brainlink_recommendations` tool for plug-and-play workflow guidance.
|
|
20
|
+
- Improved graph/index robustness by splitting oversized paragraphs into bounded chunks and dropping self-referential links.
|
|
21
|
+
- Added `agentProfiles` configuration support so CLI and MCP can resolve per-agent defaults for mode/limit/tokens.
|
|
22
|
+
- Added short-lived hybrid search cache with automatic invalidation on index changes.
|
|
23
|
+
- Added `stats --extended` observability output with storage, quality and latency probes.
|
|
24
|
+
- Added `docs/QUICKSTART.md` and aligned README/agent docs with the latest CLI/MCP flows.
|
|
25
|
+
|
|
26
|
+
## 0.1.0-beta.3
|
|
27
|
+
|
|
28
|
+
- Added CLI configuration commands for effective vault management, including `config where`, `config get`, `config doctor` and `config set-vault`.
|
|
29
|
+
- Added explicit `migrate-vault` command with `--dry-run` preview and conflict-preserving copy behavior.
|
|
30
|
+
- Added one-command agent setup through `agent install` plus `agent status` diagnostics.
|
|
31
|
+
- Added MCP `brainlink_bootstrap` default entrypoint guidance for plug-and-play agent memory flows.
|
|
32
|
+
- Added migration coverage for S3 bucket vault targets.
|
|
33
|
+
- Updated architecture and agent-usage documentation to reflect current CLI/MCP behavior and configuration precedence.
|
|
34
|
+
|
|
35
|
+
## 0.1.0-beta.2
|
|
36
|
+
|
|
37
|
+
- Added MCP installation guidance for direct server configuration and local client stores.
|
|
38
|
+
- Documented MCP vault allowlisting with `BRAINLINK_ALLOWED_VAULTS`.
|
|
39
|
+
- Aligned the documented MCP tool list with the current server tools.
|
|
40
|
+
- Updated release documentation for the beta package line.
|
|
41
|
+
|
|
42
|
+
## 0.1.0-beta.0
|
|
43
|
+
|
|
44
|
+
- Promoted the package to the beta prerelease channel.
|
|
45
|
+
- Added built-in MCP stdio server distribution through `brainlink-mcp`.
|
|
46
|
+
- Added agent namespaces, auto-indexing on writes and file ingestion flows.
|
|
47
|
+
- Added S3-compatible bucket vault support and weighted graph relationships.
|
|
48
|
+
|
|
3
49
|
## 0.1.0-alpha.0
|
|
4
50
|
|
|
5
51
|
- Added local-first Markdown vault indexing.
|
package/README.md
CHANGED
|
@@ -57,6 +57,8 @@ LLMs do not have infinite context. Brainlink gives agents an external memory lay
|
|
|
57
57
|
6. Brainlink returns compact, source-backed context.
|
|
58
58
|
|
|
59
59
|
Markdown is the source of truth. `.brainlink/brainlink.db` is only a rebuildable index.
|
|
60
|
+
Brainlink now keeps an automatic rollback snapshot at `.brainlink/brainlink.db.backup`. If the main SQLite file is corrupted, Brainlink automatically restores from snapshot (or recreates a clean index when no snapshot exists).
|
|
61
|
+
After each index run, Brainlink also writes compressed search packs at `.brainlink/search-packs/*.jsonl.gz`. If SQLite is unavailable, search falls back to these packs automatically.
|
|
60
62
|
|
|
61
63
|
## Features
|
|
62
64
|
|
|
@@ -106,6 +108,8 @@ npx @andespindola/brainlink@latest --help
|
|
|
106
108
|
|
|
107
109
|
This section is written for agents. If you are an AI agent operating in a user workspace, use this workflow to install and use Brainlink without requiring the user to run commands manually.
|
|
108
110
|
|
|
111
|
+
For a condensed setup path, see [docs/QUICKSTART.md](./docs/QUICKSTART.md).
|
|
112
|
+
|
|
109
113
|
### 1. Check Whether Brainlink Is Installed
|
|
110
114
|
|
|
111
115
|
```bash
|
|
@@ -380,19 +384,154 @@ Example MCP client configuration:
|
|
|
380
384
|
}
|
|
381
385
|
```
|
|
382
386
|
|
|
387
|
+
### One-Command Agent Setup
|
|
388
|
+
|
|
389
|
+
If your agent runtime is Codex-compatible, run:
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
blink agent install --self-test
|
|
393
|
+
blink agent upgrade
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
This configures `~/.codex/config.toml` with Brainlink MCP (`brainlink-mcp`) so Brainlink is available by default in agent sessions.
|
|
397
|
+
|
|
398
|
+
If you are inside this repository and want plugin gallery setup too:
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
blink agent install --plugin-path ./plugins/brainlink
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
To verify:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
blink agent status
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
For fully automated first run (vault index + health + bootstrap readiness + agent integration):
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
blink quickstart --query "what should I know before this task?" --json
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
For a locked-down setup, allowlist the vaults that MCP clients may access:
|
|
417
|
+
|
|
418
|
+
```json
|
|
419
|
+
{
|
|
420
|
+
"mcpServers": {
|
|
421
|
+
"brainlink": {
|
|
422
|
+
"command": "brainlink-mcp",
|
|
423
|
+
"env": {
|
|
424
|
+
"BRAINLINK_ALLOWED_VAULTS": "/absolute/path/to/project-vault,/absolute/path/to/team-vault"
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Install In MCP Client Stores
|
|
432
|
+
|
|
433
|
+
Brainlink can be exposed to MCP-compatible client stores in two ways:
|
|
434
|
+
|
|
435
|
+
1. Register the stdio server directly when the client accepts `mcpServers` configuration.
|
|
436
|
+
2. Register the local plugin from this repository when the client supports a plugin gallery or local marketplace.
|
|
437
|
+
|
|
438
|
+
Direct MCP server setup:
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
npm install -g @andespindola/brainlink@latest
|
|
442
|
+
command -v brainlink-mcp
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Use this server configuration in any MCP-compatible client that reads a JSON MCP manifest:
|
|
446
|
+
|
|
447
|
+
```json
|
|
448
|
+
{
|
|
449
|
+
"mcpServers": {
|
|
450
|
+
"brainlink": {
|
|
451
|
+
"command": "brainlink-mcp"
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Local plugin gallery setup:
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
npm install -g @andespindola/brainlink@latest
|
|
461
|
+
git clone https://github.com/andersonflima/brainlink.git "$HOME/brainlink"
|
|
462
|
+
mkdir -p "$HOME/plugins"
|
|
463
|
+
ln -s "$HOME/brainlink/plugins/brainlink" "$HOME/plugins/brainlink"
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Then register the plugin in the local marketplace file used by compatible clients:
|
|
467
|
+
|
|
468
|
+
```bash
|
|
469
|
+
node <<'NODE'
|
|
470
|
+
const fs = require('node:fs')
|
|
471
|
+
const os = require('node:os')
|
|
472
|
+
const path = require('node:path')
|
|
473
|
+
|
|
474
|
+
const marketplacePath = path.join(os.homedir(), '.agents', 'plugins', 'marketplace.json')
|
|
475
|
+
const pluginEntry = {
|
|
476
|
+
name: 'brainlink',
|
|
477
|
+
source: {
|
|
478
|
+
source: 'local',
|
|
479
|
+
path: './plugins/brainlink'
|
|
480
|
+
},
|
|
481
|
+
policy: {
|
|
482
|
+
installation: 'AVAILABLE',
|
|
483
|
+
authentication: 'ON_INSTALL'
|
|
484
|
+
},
|
|
485
|
+
category: 'Productivity'
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
fs.mkdirSync(path.dirname(marketplacePath), { recursive: true })
|
|
489
|
+
|
|
490
|
+
const marketplace = fs.existsSync(marketplacePath)
|
|
491
|
+
? JSON.parse(fs.readFileSync(marketplacePath, 'utf8'))
|
|
492
|
+
: {
|
|
493
|
+
name: 'local',
|
|
494
|
+
interface: {
|
|
495
|
+
displayName: 'Local'
|
|
496
|
+
},
|
|
497
|
+
plugins: []
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const plugins = Array.isArray(marketplace.plugins) ? marketplace.plugins : []
|
|
501
|
+
marketplace.plugins = [...plugins.filter((plugin) => plugin?.name !== 'brainlink'), pluginEntry]
|
|
502
|
+
|
|
503
|
+
fs.writeFileSync(marketplacePath, `${JSON.stringify(marketplace, null, 2)}\n`)
|
|
504
|
+
NODE
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Restart the client after changing marketplace or MCP configuration so it reloads the Brainlink entry. The plugin starts `brainlink-mcp` and exposes the same tool set listed below.
|
|
508
|
+
|
|
383
509
|
Available tools:
|
|
384
510
|
|
|
511
|
+
- `brainlink_bootstrap`: plug-and-play entrypoint that runs index + health checks and can return context in one call.
|
|
512
|
+
- `brainlink_policy`: read or update bootstrap/context-first policy, including presets (`preset: "fully-auto" | "strict"`).
|
|
513
|
+
- `brainlink_recommendations`: return an automatic action plan so agents can run Brainlink in the recommended order.
|
|
385
514
|
- `brainlink_context`: read indexed context for a task or question.
|
|
386
515
|
- `brainlink_search`: search indexed notes.
|
|
387
516
|
- `brainlink_add_note`: write durable Markdown memory and reindex.
|
|
388
517
|
- `brainlink_add_file`: ingest a local file as a note and reindex.
|
|
389
518
|
- `brainlink_index`: rebuild the vault index.
|
|
519
|
+
- `brainlink_stats`: read indexed vault statistics.
|
|
390
520
|
- `brainlink_validate`: validate broken links and orphan notes.
|
|
521
|
+
- `brainlink_sync`: run index, stats, validation, broken-link and orphan checks in one call.
|
|
391
522
|
- `brainlink_graph`: read indexed graph nodes and weighted links.
|
|
392
523
|
- `brainlink_broken_links`: list unresolved wiki links.
|
|
393
524
|
- `brainlink_orphans`: list disconnected notes.
|
|
394
525
|
|
|
395
|
-
|
|
526
|
+
For the most automatic workflow, start MCP sessions with `brainlink_bootstrap` (optionally with `query`) and then continue with `brainlink_context`/`brainlink_add_note`.
|
|
527
|
+
By default, Brainlink enforces context-first for MCP reads (`enforceContextFirst=true`): non-context read tools return preflight until `brainlink_context` is called for the vault/agent session.
|
|
528
|
+
By default, MCP startup already runs bootstrap on the configured default vault/agent (`autoBootstrapOnStartup=true`), so sessions begin warm.
|
|
529
|
+
By default, Brainlink enforces bootstrap and auto-runs it for read tools when session state is missing or stale (`autoBootstrapOnRead=true`).
|
|
530
|
+
If you disable `autoBootstrapOnRead` through `brainlink_policy`, read tools return a preflight instruction with suggested `brainlink_bootstrap` arguments.
|
|
531
|
+
`brainlink_bootstrap`, `brainlink_policy` and preflight responses include structured `nextActions` so MCP clients can continue automatically without custom parsing.
|
|
532
|
+
For one-call planning, use `brainlink_recommendations` to get the recommended tool sequence for the current vault/agent/query.
|
|
533
|
+
|
|
534
|
+
The same linking rule applies through MCP: `brainlink_context` is read-only, and real graph links require Markdown notes with explicit `[[wiki links]]`. `brainlink_add_note` and `brainlink_add_file` reindex by default and include index + `writeConnectivity` metadata. Brainlink guarantees at least one edge per new note by auto-linking when needed.
|
|
396
535
|
|
|
397
536
|
Agents can raise the importance of a relationship by putting priority markers on the same line as a wiki link:
|
|
398
537
|
|
|
@@ -417,11 +556,15 @@ The graph UI shows:
|
|
|
417
556
|
|
|
418
557
|
- notes as nodes
|
|
419
558
|
- `[[wiki links]]` as weighted edges
|
|
420
|
-
-
|
|
421
|
-
- full Markdown content for the selected note
|
|
559
|
+
- details opened on node click (tags, outgoing links, backlinks, full Markdown content)
|
|
422
560
|
- neutral graph nodes with segment/group metadata
|
|
423
561
|
- agent selector for isolated views
|
|
562
|
+
- graph filter matches title, path, tags and note content
|
|
424
563
|
- realtime refresh while `--watch` is enabled
|
|
564
|
+
- graph controls for zoom in, zoom out, fit visible nodes and reset-to-fit-all
|
|
565
|
+
- wheel zoom anchored to cursor position for faster navigation in large graphs
|
|
566
|
+
- floating graph totals (notes, links, tags) below the Brainlink title
|
|
567
|
+
- large-graph rendering safeguards (edge draw caps, lower redraw rate, zoom-aware interaction)
|
|
425
568
|
|
|
426
569
|
The server indexes before starting by default. Use `--no-index` to skip that step:
|
|
427
570
|
|
|
@@ -440,6 +583,7 @@ Routes:
|
|
|
440
583
|
- `GET /api/agents`
|
|
441
584
|
- `GET /api/graph`
|
|
442
585
|
- `GET /api/graph-layout`
|
|
586
|
+
- `GET /api/graph-node?id=<node-id>`
|
|
443
587
|
- `GET /api/search?q=<query>&limit=10&mode=hybrid`
|
|
444
588
|
- `GET /api/context?q=<query>&limit=12&tokens=2000&mode=hybrid`
|
|
445
589
|
- `GET /api/links`
|
|
@@ -461,14 +605,77 @@ Read routes accept `agent=<agent-id>`:
|
|
|
461
605
|
|
|
462
606
|
Every command works with either `brainlink` or `blink`.
|
|
463
607
|
|
|
608
|
+
### `agent`
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
blink agent install
|
|
612
|
+
blink agent install --self-test
|
|
613
|
+
blink agent upgrade
|
|
614
|
+
blink agent policy --preset fully-auto
|
|
615
|
+
blink agent policy --preset strict
|
|
616
|
+
blink agent policy --enforce-context-first false
|
|
617
|
+
blink agent install --plugin-path ./plugins/brainlink
|
|
618
|
+
blink agent install --mcp-only --allowed-vaults "/absolute/vault,/absolute/team-vault"
|
|
619
|
+
blink agent status
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
Installs/checks agent integration. `install` writes Brainlink MCP config into `~/.codex/config.toml`.
|
|
623
|
+
When plugin files are available, it also links Brainlink plugin files into `~/plugins/brainlink` and updates `~/.agents/plugins/marketplace.json`.
|
|
624
|
+
With `--self-test`, install also validates MCP block presence, command wiring and local plugin registration signals.
|
|
625
|
+
Use `agent upgrade` on legacy installations to reapply current defaults and run the same self-test diagnostics.
|
|
626
|
+
Use `agent policy --preset fully-auto` for plug-and-play defaults, or `agent policy --preset strict` to require explicit bootstrap calls.
|
|
627
|
+
Both presets keep `enforceContextFirst=true` so Brainlink stays the primary context source for MCP sessions.
|
|
628
|
+
|
|
629
|
+
### `quickstart`
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
blink quickstart --json
|
|
633
|
+
blink quickstart --vault ./team-vault --agent coding-agent --query "architecture decisions" --json
|
|
634
|
+
blink quickstart --vault ./team-vault --mcp-only --json
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
Runs index + doctor + stats + validation, refreshes bootstrap session readiness, optionally returns context for a query, and (by default) upgrades local agent integration for plug-and-play MCP usage.
|
|
638
|
+
When `--mode`, `--limit` or `--tokens` are omitted, quickstart uses agent profile defaults when available.
|
|
639
|
+
|
|
640
|
+
### `config`
|
|
641
|
+
|
|
642
|
+
```bash
|
|
643
|
+
blink config where
|
|
644
|
+
blink config get vault
|
|
645
|
+
blink config doctor
|
|
646
|
+
blink config doctor --fix
|
|
647
|
+
blink config set-vault /absolute/path/to/existing-vault
|
|
648
|
+
blink config set-vault /absolute/path/to/existing-vault --migrate-from ~/.brainlink/vault
|
|
649
|
+
blink config set-vault "s3://my-memory-bucket/brainlink" --global
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
`config set-vault` writes configuration through CLI (no manual file edits required).
|
|
653
|
+
By default it writes local config (`./brainlink.config.json`), appends the vault to `allowedVaults`, and migrates Markdown memory from the current configured vault when the target is empty.
|
|
654
|
+
Use `--global` to write to `$BRAINLINK_HOME/brainlink.config.json`, `--no-migrate` to skip migration, and `--no-index` to skip post-migration indexing.
|
|
655
|
+
`config doctor` is dry-run by default; use `--fix` to apply safe config normalization and allowlist fixes.
|
|
656
|
+
|
|
657
|
+
### `migrate-vault`
|
|
658
|
+
|
|
659
|
+
```bash
|
|
660
|
+
blink migrate-vault --from ~/.brainlink/vault --to ./team-vault --dry-run
|
|
661
|
+
blink migrate-vault --from ~/.brainlink/vault --to ./team-vault
|
|
662
|
+
blink migrate-vault --from ~/.brainlink/vault --to "s3://my-memory-bucket/brainlink"
|
|
663
|
+
blink migrate-vault --from ~/.brainlink/vault --to ./team-vault --report ./migration-report.json
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
Runs explicit markdown migration between vaults while preserving conflicts as `.conflict-<timestamp>` files.
|
|
667
|
+
Use `--dry-run` to preview `copied`, `conflicted` and `unchanged` counts before writing.
|
|
668
|
+
|
|
464
669
|
### `init`
|
|
465
670
|
|
|
466
671
|
```bash
|
|
467
672
|
blink init
|
|
468
673
|
blink init ./vault
|
|
674
|
+
blink init ./team-vault --migrate-from ~/.brainlink/vault
|
|
469
675
|
```
|
|
470
676
|
|
|
471
677
|
Initializes vault metadata. Without an argument, Brainlink initializes the default vault at `$HOME/.brainlink/vault`.
|
|
678
|
+
When initializing an empty custom vault, existing Markdown content from the default vault is copied into it and reindexed so context is not left behind. Use `--no-migrate-existing` to start with an empty custom vault, or `--migrate-from <vault>` to copy from a specific source. Existing target files are never overwritten; conflicting source files are preserved with a `.conflict-<timestamp>` suffix.
|
|
472
679
|
|
|
473
680
|
### `add`
|
|
474
681
|
|
|
@@ -482,6 +689,7 @@ blink add "Note Title" --vault ./vault --content-file ./notes.md --no-auto-index
|
|
|
482
689
|
`--content` and `--content-file` are mutually exclusive. Add `--no-auto-index` when you want to defer reindexing.
|
|
483
690
|
|
|
484
691
|
Creates a Markdown note under `agents/<agent-id>/`. Common secret patterns are blocked by default; use `--allow-sensitive` only for an intentionally protected vault.
|
|
692
|
+
To avoid disconnected memory, Brainlink auto-adds a fallback wiki edge when a note is written without links, creating agent hub notes when needed.
|
|
485
693
|
|
|
486
694
|
### `index`
|
|
487
695
|
|
|
@@ -510,6 +718,7 @@ blink search "query" --vault ./vault --mode semantic --json
|
|
|
510
718
|
```
|
|
511
719
|
|
|
512
720
|
Runs retrieval over indexed chunks.
|
|
721
|
+
If `--mode` or `--limit` is omitted, Brainlink resolves values from the current agent profile before falling back to global defaults.
|
|
513
722
|
|
|
514
723
|
Modes:
|
|
515
724
|
|
|
@@ -517,6 +726,8 @@ Modes:
|
|
|
517
726
|
- `fts`: exact lexical retrieval through SQLite FTS.
|
|
518
727
|
- `semantic`: local deterministic embedding similarity only.
|
|
519
728
|
|
|
729
|
+
Hybrid results are cached in-memory for a short TTL and invalidated automatically when the local index file changes.
|
|
730
|
+
|
|
520
731
|
### `context`
|
|
521
732
|
|
|
522
733
|
```bash
|
|
@@ -559,9 +770,11 @@ Prints indexed graph data. Edges include `weight` and `priority` so agents can c
|
|
|
559
770
|
```bash
|
|
560
771
|
blink stats --vault ./vault
|
|
561
772
|
blink stats --vault ./vault --agent coding-agent --json
|
|
773
|
+
blink stats --vault ./vault --agent coding-agent --extended --json
|
|
562
774
|
```
|
|
563
775
|
|
|
564
776
|
Prints vault metrics.
|
|
777
|
+
Use `--extended` to include storage footprint, link quality ratios and observability probes (`index`, `search`, `context` latencies).
|
|
565
778
|
|
|
566
779
|
### `broken-links`
|
|
567
780
|
|
|
@@ -593,7 +806,7 @@ Validates graph health. The command exits non-zero when required checks fail.
|
|
|
593
806
|
blink doctor --vault ./vault
|
|
594
807
|
```
|
|
595
808
|
|
|
596
|
-
Runs environment and vault checks.
|
|
809
|
+
Runs environment and vault checks. When vault has zero markdown and zero indexed documents, `doctor` prints recommended next steps (add note, inspect config source, migrate memory).
|
|
597
810
|
|
|
598
811
|
### `watch`
|
|
599
812
|
|
|
@@ -630,7 +843,13 @@ npm run --silent dev -- context "question" --vault ./vault --json
|
|
|
630
843
|
|
|
631
844
|
## Configuration
|
|
632
845
|
|
|
633
|
-
Brainlink
|
|
846
|
+
Brainlink merges configuration in this order:
|
|
847
|
+
|
|
848
|
+
1. Global: `$BRAINLINK_HOME/brainlink.config.json` (or `$HOME/.brainlink/brainlink.config.json` by default)
|
|
849
|
+
2. Local: `brainlink.config.json` in the current working directory
|
|
850
|
+
3. Local legacy compatibility: `.brainlink.json` in the current working directory
|
|
851
|
+
|
|
852
|
+
If no `vault` is configured and no `--vault` flag is passed, Brainlink uses `$HOME/.brainlink/vault`.
|
|
634
853
|
|
|
635
854
|
```json
|
|
636
855
|
{
|
|
@@ -644,13 +863,24 @@ Brainlink reads `brainlink.config.json` or `.brainlink.json` from the current wo
|
|
|
644
863
|
"defaultContextTokens": 2000,
|
|
645
864
|
"embeddingProvider": "local",
|
|
646
865
|
"defaultSearchMode": "hybrid",
|
|
647
|
-
"chunkSize": 1200
|
|
866
|
+
"chunkSize": 1200,
|
|
867
|
+
"agentProfiles": {
|
|
868
|
+
"coding-agent": {
|
|
869
|
+
"defaultSearchMode": "semantic",
|
|
870
|
+
"defaultSearchLimit": 8,
|
|
871
|
+
"defaultContextTokens": 2400
|
|
872
|
+
},
|
|
873
|
+
"*": {
|
|
874
|
+
"defaultSearchMode": "hybrid"
|
|
875
|
+
}
|
|
876
|
+
}
|
|
648
877
|
}
|
|
878
|
+
```
|
|
649
879
|
|
|
650
880
|
`defaultAgent` is optional. When set, CLI and MCP calls that omit `--agent`/`agent` use this value automatically. If not set, behavior remains as before.
|
|
881
|
+
`agentProfiles` is optional. When present, CLI and MCP resolve `mode`, `limit` and `tokens` per agent automatically, then fallback to global defaults.
|
|
651
882
|
|
|
652
883
|
`autoIndexOnWrite` is optional and defaults to `true`. Set it to `false` to defer indexing after writes.
|
|
653
|
-
```
|
|
654
884
|
|
|
655
885
|
Use `"embeddingProvider": "none"` when you want FTS-only indexing.
|
|
656
886
|
|
|
@@ -763,17 +993,18 @@ Detailed notes:
|
|
|
763
993
|
- HTTP API is local and unauthenticated.
|
|
764
994
|
- Watch mode depends on the platform filesystem watcher.
|
|
765
995
|
|
|
766
|
-
##
|
|
996
|
+
## Beta Scope
|
|
767
997
|
|
|
768
|
-
`0.1.0-
|
|
998
|
+
The `0.1.0-beta` line is intended to stabilize the local-first memory loop:
|
|
769
999
|
|
|
770
1000
|
- Markdown as durable memory.
|
|
771
1001
|
- SQLite FTS plus local embeddings and semantic buckets as rebuildable retrieval index.
|
|
772
1002
|
- CLI as the primary agent interface.
|
|
773
1003
|
- HTTP graph API and frontend as inspection tools.
|
|
774
1004
|
- Agent namespaces to avoid context mixing.
|
|
1005
|
+
- MCP tools for context retrieval, durable memory writes and graph maintenance.
|
|
775
1006
|
|
|
776
|
-
The
|
|
1007
|
+
The beta includes local semantic retrieval. Remote embedding providers, remote auth, advanced deduplication and graph editing are future milestones.
|
|
777
1008
|
|
|
778
1009
|
## Security
|
|
779
1010
|
|
|
@@ -1,30 +1,79 @@
|
|
|
1
|
+
import { access } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
1
3
|
import { writeMarkdownFile } from '../infrastructure/file-system-vault.js';
|
|
2
4
|
import { sanitizeAgentId, sharedAgentId } from '../domain/agents.js';
|
|
5
|
+
import { extractWikiLinks } from '../domain/markdown.js';
|
|
3
6
|
import { validateNoteInput } from '../domain/note-safety.js';
|
|
7
|
+
import { ensureVault } from '../infrastructure/file-system-vault.js';
|
|
4
8
|
const slugify = (title) => title
|
|
5
9
|
.normalize('NFKD')
|
|
6
10
|
.replace(/[\u0300-\u036f]/g, '')
|
|
7
11
|
.toLowerCase()
|
|
8
12
|
.replace(/[^a-z0-9]+/g, '-')
|
|
9
13
|
.replace(/^-+|-+$/g, '');
|
|
10
|
-
|
|
14
|
+
const systemHubTitle = 'Memory Hub';
|
|
15
|
+
const systemRootTitle = 'Knowledge Root';
|
|
16
|
+
const normalizeTitle = (title) => title.trim().replace(/\.md$/i, '').toLowerCase();
|
|
17
|
+
const noteFilename = (agentId, title) => `agents/${agentId}/${slugify(title) || 'untitled'}.md`;
|
|
18
|
+
const buildNote = (title, content, agentId) => [
|
|
19
|
+
`---`,
|
|
20
|
+
`title: "${title.replaceAll('"', '\\"')}"`,
|
|
21
|
+
`agent: "${agentId}"`,
|
|
22
|
+
`---`,
|
|
23
|
+
'',
|
|
24
|
+
`# ${title}`,
|
|
25
|
+
'',
|
|
26
|
+
content.trim(),
|
|
27
|
+
''
|
|
28
|
+
].join('\n');
|
|
29
|
+
const ensureSystemNote = async (vaultPath, absoluteVaultPath, agentId, title, content) => {
|
|
30
|
+
const filename = noteFilename(agentId, title);
|
|
31
|
+
const absolutePath = join(absoluteVaultPath, filename);
|
|
32
|
+
try {
|
|
33
|
+
await access(absolutePath);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
catch { }
|
|
37
|
+
await writeMarkdownFile(vaultPath, filename, buildNote(title, content, agentId));
|
|
38
|
+
};
|
|
39
|
+
const ensureNonOrphanContent = async (vaultPath, absoluteVaultPath, title, content, agentId) => {
|
|
40
|
+
const links = extractWikiLinks(content).filter((link) => normalizeTitle(link) !== normalizeTitle(title));
|
|
41
|
+
if (links.length > 0) {
|
|
42
|
+
return {
|
|
43
|
+
content: content.trim(),
|
|
44
|
+
autoLinked: false,
|
|
45
|
+
linkTarget: null
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const fallbackTitle = normalizeTitle(title) === normalizeTitle(systemHubTitle) ? systemRootTitle : systemHubTitle;
|
|
49
|
+
if (fallbackTitle === systemRootTitle) {
|
|
50
|
+
await ensureSystemNote(vaultPath, absoluteVaultPath, agentId, systemRootTitle, `Entry point for agent memory. [[${systemHubTitle}]] #memory #root`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
await ensureSystemNote(vaultPath, absoluteVaultPath, agentId, systemHubTitle, 'Central memory index for this agent namespace. #memory #hub');
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
content: `${content.trim()}\n\nRelated: [[${fallbackTitle}]]`,
|
|
57
|
+
autoLinked: true,
|
|
58
|
+
linkTarget: fallbackTitle
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
export const addNoteWithMetadata = async (vaultPath, title, content, agentId = sharedAgentId, options = {}) => {
|
|
11
62
|
validateNoteInput({
|
|
12
63
|
title,
|
|
13
64
|
content,
|
|
14
65
|
allowSensitive: options.allowSensitive
|
|
15
66
|
});
|
|
16
67
|
const sanitizedAgentId = sanitizeAgentId(agentId);
|
|
68
|
+
const absoluteVaultPath = await ensureVault(vaultPath);
|
|
17
69
|
const filename = `agents/${sanitizedAgentId}/${slugify(title) || 'untitled'}.md`;
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
content.trim(),
|
|
27
|
-
''
|
|
28
|
-
].join('\n');
|
|
29
|
-
return writeMarkdownFile(vaultPath, filename, note);
|
|
70
|
+
const linkedContent = await ensureNonOrphanContent(vaultPath, absoluteVaultPath, title, content, sanitizedAgentId);
|
|
71
|
+
const note = buildNote(title, linkedContent.content, sanitizedAgentId);
|
|
72
|
+
const path = await writeMarkdownFile(vaultPath, filename, note);
|
|
73
|
+
return {
|
|
74
|
+
path,
|
|
75
|
+
autoLinked: linkedContent.autoLinked,
|
|
76
|
+
linkTarget: linkedContent.linkTarget
|
|
77
|
+
};
|
|
30
78
|
};
|
|
79
|
+
export const addNote = async (vaultPath, title, content, agentId = sharedAgentId, options = {}) => (await addNoteWithMetadata(vaultPath, title, content, agentId, options)).path;
|
|
@@ -1,10 +1,91 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { performance } from 'node:perf_hooks';
|
|
4
|
+
import { join } from 'node:path';
|
|
1
5
|
import { validateGraph, getBrokenLinks, getOrphanNodes, getVaultStats } from '../domain/graph-analysis.js';
|
|
2
|
-
import { ensureVault, readMarkdownFiles } from '../infrastructure/file-system-vault.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { ensureVault, listVaultFiles, readMarkdownFiles } from '../infrastructure/file-system-vault.js';
|
|
7
|
+
import { resolveAgentRuntimeDefaults } from '../infrastructure/config.js';
|
|
8
|
+
import { getGraphSummary } from './get-graph-summary.js';
|
|
9
|
+
import { buildContextPackage } from './build-context.js';
|
|
10
|
+
import { indexVault } from './index-vault.js';
|
|
11
|
+
import { searchKnowledge } from './search-knowledge.js';
|
|
12
|
+
import { loadBrainlinkConfig } from '../infrastructure/config.js';
|
|
13
|
+
export const getStats = async (vaultPath, agentId) => getVaultStats(await getGraphSummary(vaultPath, agentId));
|
|
14
|
+
export const getBrokenLinksReport = async (vaultPath, agentId) => getBrokenLinks(await getGraphSummary(vaultPath, agentId));
|
|
15
|
+
export const getOrphansReport = async (vaultPath, agentId) => getOrphanNodes(await getGraphSummary(vaultPath, agentId));
|
|
16
|
+
export const validateVault = async (vaultPath, agentId) => validateGraph(await getGraphSummary(vaultPath, agentId));
|
|
17
|
+
const toRatio = (part, total) => total === 0 ? 0 : Number((part / total).toFixed(4));
|
|
18
|
+
export const getExtendedStats = async (vaultPath, agentId) => {
|
|
19
|
+
const absoluteVaultPath = await ensureVault(vaultPath);
|
|
20
|
+
const graph = await getGraphSummary(absoluteVaultPath, agentId);
|
|
21
|
+
const stats = getVaultStats(graph);
|
|
22
|
+
const markdownFiles = await readMarkdownFiles(absoluteVaultPath);
|
|
23
|
+
const allFiles = await listVaultFiles(absoluteVaultPath);
|
|
24
|
+
const totalBytes = (await Promise.all(allFiles.map(async (filePath) => {
|
|
25
|
+
try {
|
|
26
|
+
return (await stat(filePath)).size;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
}))).reduce((sum, value) => sum + value, 0);
|
|
32
|
+
const updatedAt = markdownFiles
|
|
33
|
+
.map((file) => file.updatedAt.getTime())
|
|
34
|
+
.filter((time) => Number.isFinite(time))
|
|
35
|
+
.sort((left, right) => left - right);
|
|
36
|
+
const priorities = graph.edges.reduce((state, edge) => ({
|
|
37
|
+
...state,
|
|
38
|
+
[edge.priority]: state[edge.priority] + 1
|
|
39
|
+
}), {
|
|
40
|
+
low: 0,
|
|
41
|
+
normal: 0,
|
|
42
|
+
high: 0,
|
|
43
|
+
critical: 0
|
|
44
|
+
});
|
|
45
|
+
const config = await loadBrainlinkConfig();
|
|
46
|
+
const defaults = resolveAgentRuntimeDefaults(config, agentId);
|
|
47
|
+
const probeQuery = graph.nodes[0]?.title ?? 'architecture';
|
|
48
|
+
const indexStart = performance.now();
|
|
49
|
+
await indexVault(absoluteVaultPath);
|
|
50
|
+
const indexLatency = performance.now() - indexStart;
|
|
51
|
+
const searchStart = performance.now();
|
|
52
|
+
await searchKnowledge(absoluteVaultPath, probeQuery, Math.min(defaults.defaultSearchLimit, 8), agentId, 'hybrid');
|
|
53
|
+
const searchLatency = performance.now() - searchStart;
|
|
54
|
+
const contextStart = performance.now();
|
|
55
|
+
await buildContextPackage(absoluteVaultPath, probeQuery, Math.min(defaults.defaultSearchLimit, 8), defaults.defaultContextTokens, agentId, 'hybrid');
|
|
56
|
+
const contextLatency = performance.now() - contextStart;
|
|
57
|
+
return {
|
|
58
|
+
stats,
|
|
59
|
+
storage: {
|
|
60
|
+
markdownFileCount: markdownFiles.length,
|
|
61
|
+
totalFileCount: allFiles.length,
|
|
62
|
+
totalBytes,
|
|
63
|
+
averageMarkdownBytes: markdownFiles.length === 0
|
|
64
|
+
? 0
|
|
65
|
+
: Math.round(markdownFiles.reduce((sum, file) => sum + Buffer.byteLength(file.content, 'utf8'), 0) / markdownFiles.length),
|
|
66
|
+
...(updatedAt.length > 0
|
|
67
|
+
? {
|
|
68
|
+
oldestNoteUpdatedAt: new Date(updatedAt[0]).toISOString(),
|
|
69
|
+
newestNoteUpdatedAt: new Date(updatedAt[updatedAt.length - 1]).toISOString()
|
|
70
|
+
}
|
|
71
|
+
: {})
|
|
72
|
+
},
|
|
73
|
+
quality: {
|
|
74
|
+
resolvedLinkRatio: toRatio(stats.resolvedLinkCount, stats.linkCount),
|
|
75
|
+
brokenLinkRatio: toRatio(stats.brokenLinkCount, stats.linkCount),
|
|
76
|
+
orphanRatio: toRatio(stats.orphanCount, Math.max(stats.documentCount, 1)),
|
|
77
|
+
priorityDistribution: priorities
|
|
78
|
+
},
|
|
79
|
+
observability: {
|
|
80
|
+
probeQuery,
|
|
81
|
+
latenciesMs: {
|
|
82
|
+
index: Number(indexLatency.toFixed(2)),
|
|
83
|
+
search: Number(searchLatency.toFixed(2)),
|
|
84
|
+
context: Number(contextLatency.toFixed(2))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
};
|
|
8
89
|
const createCheck = (name, ok, message) => ({
|
|
9
90
|
name,
|
|
10
91
|
ok,
|
|
@@ -13,16 +94,30 @@ const createCheck = (name, ok, message) => ({
|
|
|
13
94
|
export const doctorVault = async (vaultPath) => {
|
|
14
95
|
const absoluteVaultPath = await ensureVault(vaultPath);
|
|
15
96
|
const files = await readMarkdownFiles(absoluteVaultPath);
|
|
16
|
-
const graph = await
|
|
97
|
+
const graph = await getGraphSummary(absoluteVaultPath);
|
|
17
98
|
const validation = validateGraph(graph);
|
|
99
|
+
const backupPath = join(absoluteVaultPath, '.brainlink', 'brainlink.db.backup');
|
|
100
|
+
const hasBackup = existsSync(backupPath);
|
|
101
|
+
const backupReady = graph.nodes.length === 0 || hasBackup;
|
|
18
102
|
const checks = [
|
|
19
103
|
createCheck('vault', true, `Vault ready at ${absoluteVaultPath}`),
|
|
20
104
|
createCheck('markdown-files', files.length > 0, `${files.length} markdown files found`),
|
|
21
105
|
createCheck('index', graph.nodes.length > 0, `${graph.nodes.length} indexed documents found`),
|
|
22
|
-
createCheck('broken-links', validation.brokenLinks.length === 0, `${validation.brokenLinks.length} broken links found`)
|
|
106
|
+
createCheck('broken-links', validation.brokenLinks.length === 0, `${validation.brokenLinks.length} broken links found`),
|
|
107
|
+
createCheck('index-backup', backupReady, backupReady
|
|
108
|
+
? (hasBackup ? 'SQLite recovery snapshot is available' : 'No index yet. Snapshot will be created after first indexing run')
|
|
109
|
+
: 'Recovery snapshot missing. Run blink index to create a rollback snapshot')
|
|
23
110
|
];
|
|
111
|
+
const recommendations = files.length === 0 && graph.nodes.length === 0
|
|
112
|
+
? [
|
|
113
|
+
`Vault is empty. Add your first note: blink add "Architecture" --vault "${absoluteVaultPath}" --content "Markdown source of truth. #architecture"`,
|
|
114
|
+
`If this path is not the expected vault, inspect active config: blink config where`,
|
|
115
|
+
`If you changed vault recently, migrate existing memory: blink migrate-vault --from ~/.brainlink/vault --to "${absoluteVaultPath}"`
|
|
116
|
+
]
|
|
117
|
+
: [];
|
|
24
118
|
return {
|
|
25
119
|
ok: checks.every((check) => check.ok),
|
|
26
|
-
checks
|
|
120
|
+
checks,
|
|
121
|
+
...(recommendations.length > 0 ? { recommendations } : {})
|
|
27
122
|
};
|
|
28
123
|
};
|