@andespindola/brainlink 0.1.0-beta.99 → 1.0.1
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/AGENTS.md +6 -6
- package/CHANGELOG.md +14 -0
- package/README.md +198 -38
- package/dist/application/add-note.js +13 -44
- package/dist/application/analyze-vault.js +1 -1
- package/dist/application/auto-migrate-configured-vault.js +37 -0
- package/dist/application/build-context.js +119 -20
- package/dist/application/canonical-context-links.js +209 -0
- package/dist/application/delete-note.js +80 -0
- package/dist/application/frontend/client-css.js +212 -42
- package/dist/application/frontend/client-html.js +42 -28
- package/dist/application/frontend/client-js.js +1294 -3222
- package/dist/application/frontend/client-render-worker-js.js +676 -0
- package/dist/application/get-graph-contexts.js +33 -0
- package/dist/application/get-graph-layout.js +62 -8
- package/dist/application/get-graph-stream-chunk.js +326 -0
- package/dist/application/get-graph-view.js +246 -0
- package/dist/application/graph-view-state.js +66 -0
- package/dist/application/import-legacy-sqlite.js +3 -33
- package/dist/application/index-vault.js +35 -22
- package/dist/application/migrate-context-links.js +79 -0
- package/dist/application/search-graph-node-ids.js +63 -3
- package/dist/application/server/routes.js +197 -12
- package/dist/cli/commands/read-commands.js +39 -3
- package/dist/cli/commands/vault-commands.js +182 -0
- package/dist/cli/commands/write-commands.js +172 -12
- package/dist/cli/main.js +2 -0
- package/dist/cli/runtime.js +10 -2
- package/dist/domain/context.js +1 -0
- package/dist/domain/graph-contexts.js +180 -0
- package/dist/domain/graph-layout.js +347 -21
- package/dist/domain/markdown.js +53 -9
- package/dist/infrastructure/config.js +105 -6
- package/dist/infrastructure/context-packs.js +122 -0
- package/dist/infrastructure/file-index.js +6 -3
- package/dist/infrastructure/file-system-vault.js +21 -1
- package/dist/infrastructure/index-state.js +2 -0
- package/dist/infrastructure/vault-migration-state.js +69 -0
- package/dist/infrastructure/volatile-memory.js +100 -0
- package/dist/mcp/http-server.js +97 -0
- package/dist/mcp/runtime.js +20 -0
- package/dist/mcp/server.js +41 -13
- package/dist/mcp/tools.js +226 -14
- package/docs/AGENT_USAGE.md +60 -5
- package/docs/ARCHITECTURE.md +11 -0
- package/docs/QUICKSTART.md +3 -1
- package/docs/RELEASE.md +4 -3
- package/package.json +3 -1
|
@@ -5,18 +5,23 @@ import { platform, tmpdir } from 'node:os';
|
|
|
5
5
|
import { spawn, spawnSync } from 'node:child_process';
|
|
6
6
|
import { addNoteWithMetadata } from '../../application/add-note.js';
|
|
7
7
|
import { buildContextPackage } from '../../application/build-context.js';
|
|
8
|
+
import { deleteNote } from '../../application/delete-note.js';
|
|
8
9
|
import { resolveDuplicateNotes, scanDuplicateNotes } from '../../application/dedupe-notes.js';
|
|
9
10
|
import { importLegacySqliteDatabase } from '../../application/import-legacy-sqlite.js';
|
|
10
11
|
import { indexVault, indexVaultWithOptions } from '../../application/index-vault.js';
|
|
12
|
+
import { migrateContextLinks } from '../../application/migrate-context-links.js';
|
|
13
|
+
import { canonicalizeContextLinks } from '../../application/canonical-context-links.js';
|
|
11
14
|
import { migrateVaultContent, planVaultMigration, previewVaultMigration, shouldMigrateDefaultVault } from '../../application/migrate-vault.js';
|
|
12
15
|
import { createOfflinePackBackup } from '../../application/offline-pack-backup.js';
|
|
13
16
|
import { startServer } from '../../application/start-server.js';
|
|
14
17
|
import { startVaultWatcher } from '../../application/watch-vault.js';
|
|
15
18
|
import { doctorVault, getStats, validateVault } from '../../application/analyze-vault.js';
|
|
16
|
-
import { defaultBrainlinkConfig, sanitizeSearchMode } from '../../infrastructure/config.js';
|
|
19
|
+
import { defaultBrainlinkConfig, sanitizeContextStrategy, sanitizeSearchMode } from '../../infrastructure/config.js';
|
|
17
20
|
import { loadBrainlinkConfig } from '../../infrastructure/config.js';
|
|
18
|
-
import { assertVaultAllowed, ensureVault } from '../../infrastructure/file-system-vault.js';
|
|
21
|
+
import { assertVaultAllowed, ensureVault, isBucketVaultPath } from '../../infrastructure/file-system-vault.js';
|
|
19
22
|
import { getBootstrapPolicy, getBootstrapSessionStatus, touchBootstrapSession } from '../../infrastructure/session-state.js';
|
|
23
|
+
import { addVolatileMemory, clearVolatileMemory } from '../../infrastructure/volatile-memory.js';
|
|
24
|
+
import { startRemoteMcpServer } from '../../mcp/http-server.js';
|
|
20
25
|
import { installAgentIntegration } from './agent-commands.js';
|
|
21
26
|
import { parsePositiveInteger, print, resolveOptions } from '../runtime.js';
|
|
22
27
|
const resolveAddContent = (options) => {
|
|
@@ -698,6 +703,68 @@ export const registerWriteCommands = (program) => {
|
|
|
698
703
|
return `${summary}${indexMessage}${reportMessage}`;
|
|
699
704
|
});
|
|
700
705
|
});
|
|
706
|
+
program
|
|
707
|
+
.command('migrate-context-links')
|
|
708
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
709
|
+
.option('-a, --agent <agent>', 'agent memory namespace')
|
|
710
|
+
.option('-l, --limit <limit>', 'maximum context links to add per note', '5')
|
|
711
|
+
.option('--dry-run', 'preview context-link migration without writing files')
|
|
712
|
+
.option('--no-index', 'skip reindexing after migration')
|
|
713
|
+
.option('--json', 'print machine-readable JSON')
|
|
714
|
+
.description('add concise Context Links sections from existing wiki-link mentions')
|
|
715
|
+
.action(async (options) => {
|
|
716
|
+
const resolved = await resolveOptions(options);
|
|
717
|
+
const result = await migrateContextLinks(resolved.vault, {
|
|
718
|
+
dryRun: options.dryRun === true,
|
|
719
|
+
limit: parsePositiveInteger(options.limit ?? '5', 5),
|
|
720
|
+
agentId: resolved.agent
|
|
721
|
+
});
|
|
722
|
+
const shouldIndex = options.index !== false && !result.dryRun && result.changed > 0;
|
|
723
|
+
const index = shouldIndex ? await indexVault(resolved.vault, { full: true }) : undefined;
|
|
724
|
+
print(options.json, {
|
|
725
|
+
vault: resolved.vault,
|
|
726
|
+
agent: resolved.agent ?? 'shared',
|
|
727
|
+
...result,
|
|
728
|
+
...(index ? { index } : {})
|
|
729
|
+
}, () => {
|
|
730
|
+
const mode = result.dryRun ? 'Previewed' : 'Migrated';
|
|
731
|
+
const indexMessage = index
|
|
732
|
+
? ` Fully reindexed ${index.documentCount} documents, ${index.chunkCount} chunks and ${index.linkCount} context links.`
|
|
733
|
+
: '';
|
|
734
|
+
return `${mode} ${result.scanned} notes: changed=${result.changed}, skipped=${result.skipped}, limit=${result.limit}.${indexMessage}`;
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
program
|
|
738
|
+
.command('canonicalize-context-links')
|
|
739
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
740
|
+
.option('-a, --agent <agent>', 'agent memory namespace')
|
|
741
|
+
.option('--dry-run', 'preview canonical context links without writing files')
|
|
742
|
+
.option('--no-create-hubs', 'do not create missing context hub notes')
|
|
743
|
+
.option('--no-index', 'skip reindexing after canonicalization')
|
|
744
|
+
.option('--json', 'print machine-readable JSON')
|
|
745
|
+
.description('ensure notes have canonical Context Links to their inferred context hubs')
|
|
746
|
+
.action(async (options) => {
|
|
747
|
+
const resolved = await resolveOptions(options);
|
|
748
|
+
const result = await canonicalizeContextLinks(resolved.vault, {
|
|
749
|
+
dryRun: options.dryRun === true,
|
|
750
|
+
agentId: resolved.agent,
|
|
751
|
+
createMissingHubs: options.createHubs !== false
|
|
752
|
+
});
|
|
753
|
+
const shouldIndex = options.index !== false && !result.dryRun && result.changed > 0;
|
|
754
|
+
const index = shouldIndex ? await indexVault(resolved.vault, { full: true }) : undefined;
|
|
755
|
+
print(options.json, {
|
|
756
|
+
vault: resolved.vault,
|
|
757
|
+
agent: resolved.agent ?? 'shared',
|
|
758
|
+
...result,
|
|
759
|
+
...(index ? { index } : {})
|
|
760
|
+
}, () => {
|
|
761
|
+
const mode = result.dryRun ? 'Previewed' : 'Canonicalized';
|
|
762
|
+
const indexMessage = index
|
|
763
|
+
? ` Fully reindexed ${index.documentCount} documents, ${index.chunkCount} chunks and ${index.linkCount} context links.`
|
|
764
|
+
: '';
|
|
765
|
+
return `${mode} ${result.scanned} notes: changed=${result.changed}, createdHubs=${result.createdHubs}, skipped=${result.skipped}.${indexMessage}`;
|
|
766
|
+
});
|
|
767
|
+
});
|
|
701
768
|
program
|
|
702
769
|
.command('db-import')
|
|
703
770
|
.option('-v, --vault <vault>', 'vault directory')
|
|
@@ -729,6 +796,29 @@ export const registerWriteCommands = (program) => {
|
|
|
729
796
|
return `${summary}${indexMessage}${dryRunMessage}`;
|
|
730
797
|
});
|
|
731
798
|
});
|
|
799
|
+
program
|
|
800
|
+
.command('volatile')
|
|
801
|
+
.option('-c, --content <content>', 'temporary memory content to add')
|
|
802
|
+
.option('--ttl <minutes>', 'time-to-live in minutes', '240')
|
|
803
|
+
.option('--tag <tag...>', 'volatile memory tag')
|
|
804
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
805
|
+
.option('-a, --agent <agent>', 'agent memory namespace')
|
|
806
|
+
.option('--clear', 'clear volatile memory for the current agent namespace')
|
|
807
|
+
.option('--json', 'print machine-readable JSON')
|
|
808
|
+
.description('add or clear temporary agent-decided memory')
|
|
809
|
+
.action(async (options) => {
|
|
810
|
+
const resolved = await resolveOptions(options);
|
|
811
|
+
if (options.clear) {
|
|
812
|
+
const cleared = await clearVolatileMemory(resolved.vault, resolved.agent);
|
|
813
|
+
print(options.json, { cleared, agent: resolved.agent ?? 'shared' }, () => `Cleared ${cleared} volatile memories.`);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (!options.content || options.content.trim().length === 0) {
|
|
817
|
+
throw new Error('Use --content to add volatile memory, or --clear to remove it.');
|
|
818
|
+
}
|
|
819
|
+
const entry = await addVolatileMemory(resolved.vault, options.content, resolved.agent ?? 'shared', parsePositiveInteger(options.ttl ?? '240', 240), options.tag ?? []);
|
|
820
|
+
print(options.json, { entry }, () => `Stored volatile memory until ${entry.expiresAt}.`);
|
|
821
|
+
});
|
|
732
822
|
program
|
|
733
823
|
.command('add')
|
|
734
824
|
.argument('<title>', 'note title')
|
|
@@ -737,6 +827,7 @@ export const registerWriteCommands = (program) => {
|
|
|
737
827
|
.option('-v, --vault <vault>', 'vault directory')
|
|
738
828
|
.option('-a, --agent <agent>', 'agent memory namespace')
|
|
739
829
|
.option('--allow-sensitive', 'allow writing content that looks like a secret')
|
|
830
|
+
.option('--no-auto-context-links', 'skip canonical Context Links for this note')
|
|
740
831
|
.option('--no-auto-index', 'skip reindexing after add')
|
|
741
832
|
.option('--json', 'print machine-readable JSON')
|
|
742
833
|
.description('add a markdown note to the vault')
|
|
@@ -744,7 +835,8 @@ export const registerWriteCommands = (program) => {
|
|
|
744
835
|
const resolved = await resolveOptions(options);
|
|
745
836
|
const content = resolveAddContent(options);
|
|
746
837
|
const added = await addNoteWithMetadata(resolved.vault, title, content, resolved.agent, {
|
|
747
|
-
allowSensitive: Boolean(options.allowSensitive)
|
|
838
|
+
allowSensitive: Boolean(options.allowSensitive),
|
|
839
|
+
autoContextLinks: options.autoContextLinks !== false && resolved.config.autoCanonicalContextLinks
|
|
748
840
|
});
|
|
749
841
|
const shouldAutoIndex = options.autoIndex !== false && resolved.config.autoIndexOnWrite;
|
|
750
842
|
const index = shouldAutoIndex ? await indexVault(resolved.vault) : undefined;
|
|
@@ -768,7 +860,9 @@ export const registerWriteCommands = (program) => {
|
|
|
768
860
|
writeConnectivity: {
|
|
769
861
|
autoLinked: added.autoLinked,
|
|
770
862
|
linkTarget: added.linkTarget,
|
|
771
|
-
|
|
863
|
+
context: added.context,
|
|
864
|
+
hubCreated: added.hubCreated,
|
|
865
|
+
guaranteedEdge: added.autoLinked
|
|
772
866
|
},
|
|
773
867
|
possibleDuplicates,
|
|
774
868
|
...(index ? { index } : {})
|
|
@@ -776,8 +870,33 @@ export const registerWriteCommands = (program) => {
|
|
|
776
870
|
const duplicateMessage = possibleDuplicates.length > 0
|
|
777
871
|
? `\nPotential duplicates: ${possibleDuplicates.length}. Use "blink dedupe --json" or "blink dedupe-resolve".`
|
|
778
872
|
: '';
|
|
779
|
-
|
|
873
|
+
const linkMessage = added.autoLinked ? ` Linked to [[${added.linkTarget}]].` : '';
|
|
874
|
+
return `Created note at ${added.path}.${linkMessage}${duplicateMessage}`;
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
program
|
|
878
|
+
.command('delete-note')
|
|
879
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
880
|
+
.option('-a, --agent <agent>', 'agent memory namespace when deleting by title')
|
|
881
|
+
.option('--title <title>', 'note title to delete')
|
|
882
|
+
.option('--path <path>', 'vault-relative or absolute markdown note path to delete')
|
|
883
|
+
.option('--yes', 'confirm note deletion')
|
|
884
|
+
.option('--no-auto-index', 'skip reindexing after delete')
|
|
885
|
+
.option('--json', 'print machine-readable JSON')
|
|
886
|
+
.description('delete a Markdown note from the vault after explicit confirmation')
|
|
887
|
+
.action(async (options) => {
|
|
888
|
+
const resolved = await resolveOptions(options);
|
|
889
|
+
const result = await deleteNote(resolved.vault, {
|
|
890
|
+
title: options.title,
|
|
891
|
+
path: options.path,
|
|
892
|
+
agentId: resolved.agent,
|
|
893
|
+
confirm: Boolean(options.yes),
|
|
894
|
+
autoIndex: options.autoIndex !== false
|
|
780
895
|
});
|
|
896
|
+
print(options.json, {
|
|
897
|
+
vault: resolved.vault,
|
|
898
|
+
...result
|
|
899
|
+
}, () => `Deleted note ${result.relativePath}.`);
|
|
781
900
|
});
|
|
782
901
|
program
|
|
783
902
|
.command('dedupe')
|
|
@@ -836,12 +955,17 @@ export const registerWriteCommands = (program) => {
|
|
|
836
955
|
program
|
|
837
956
|
.command('index')
|
|
838
957
|
.option('-v, --vault <vault>', 'vault directory')
|
|
958
|
+
.option('--full', 'force a complete reindex from Markdown source without reusing unchanged index entries')
|
|
839
959
|
.option('--json', 'print machine-readable JSON')
|
|
840
960
|
.description('index markdown notes, links, tags and chunks')
|
|
841
961
|
.action(async (options) => {
|
|
842
962
|
const resolved = await resolveOptions(options);
|
|
843
|
-
const result = await indexVault(resolved.vault
|
|
844
|
-
|
|
963
|
+
const result = await indexVault(resolved.vault, {
|
|
964
|
+
full: options.full === true
|
|
965
|
+
});
|
|
966
|
+
print(options.json, result, () => options.full === true
|
|
967
|
+
? `Fully reindexed ${result.documentCount} documents, ${result.chunkCount} chunks and ${result.linkCount} links`
|
|
968
|
+
: `Indexed ${result.documentCount} documents, ${result.chunkCount} chunks and ${result.linkCount} links`);
|
|
845
969
|
});
|
|
846
970
|
program
|
|
847
971
|
.command('bench')
|
|
@@ -976,26 +1100,28 @@ export const registerWriteCommands = (program) => {
|
|
|
976
1100
|
.option('-p, --port <port>', 'server port', '4321')
|
|
977
1101
|
.option('--no-index', 'skip indexing before starting the server')
|
|
978
1102
|
.option('--no-open', 'do not open the graph UI automatically')
|
|
979
|
-
.option('-w, --watch', 'watch markdown files and reindex on changes')
|
|
1103
|
+
.option('-w, --watch', 'watch markdown files and reindex on changes', true)
|
|
1104
|
+
.option('--no-watch', 'disable markdown file watching')
|
|
980
1105
|
.option('--json', 'print machine-readable JSON')
|
|
981
1106
|
.description('start a local web UI for the knowledge graph')
|
|
982
1107
|
.action(async (options) => {
|
|
983
1108
|
const resolved = await resolveOptions(options);
|
|
1109
|
+
const shouldWatch = options.watch !== false && !isBucketVaultPath(resolved.vault);
|
|
984
1110
|
const server = await startServer({
|
|
985
1111
|
vaultPath: resolved.vault,
|
|
986
1112
|
host: options.host ?? resolved.config.host,
|
|
987
1113
|
port: parsePositiveInteger(options.port ?? String(resolved.config.port), resolved.config.port),
|
|
988
1114
|
shouldIndex: options.index,
|
|
989
|
-
shouldWatch
|
|
1115
|
+
shouldWatch
|
|
990
1116
|
});
|
|
991
1117
|
const openResult = options.open !== false ? openUrlInUi(server.url, process.pid) : { opened: false, mode: 'none' };
|
|
992
1118
|
print(options.json, {
|
|
993
1119
|
url: server.url,
|
|
994
|
-
watch:
|
|
1120
|
+
watch: shouldWatch,
|
|
995
1121
|
readonly: true,
|
|
996
1122
|
openedUi: openResult.opened,
|
|
997
1123
|
openMode: openResult.mode
|
|
998
|
-
}, () => `Brainlink graph server running at ${server.url}${openResult.opened
|
|
1124
|
+
}, () => `Brainlink graph server running at ${server.url} (${shouldWatch ? 'watching for changes' : 'watch disabled'})${openResult.opened
|
|
999
1125
|
? openResult.mode === 'native-gui'
|
|
1000
1126
|
? ' (opened in native desktop GUI)'
|
|
1001
1127
|
: openResult.mode === 'app-window'
|
|
@@ -1005,12 +1131,45 @@ export const registerWriteCommands = (program) => {
|
|
|
1005
1131
|
? ' (auto-open disabled)'
|
|
1006
1132
|
: ''}`);
|
|
1007
1133
|
});
|
|
1134
|
+
program
|
|
1135
|
+
.command('mcp-server')
|
|
1136
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
1137
|
+
.option('-a, --agent <agent>', 'agent memory namespace')
|
|
1138
|
+
.option('-h, --host <host>', 'remote MCP server host', '0.0.0.0')
|
|
1139
|
+
.option('-p, --port <port>', 'remote MCP server port', '3333')
|
|
1140
|
+
.option('--path <path>', 'remote MCP endpoint path', '/mcp')
|
|
1141
|
+
.option('--token <token>', 'bearer token required for MCP requests')
|
|
1142
|
+
.option('--no-index', 'skip indexing before starting the MCP server')
|
|
1143
|
+
.option('--json', 'print machine-readable JSON')
|
|
1144
|
+
.description('start a remote MCP server for centralized cluster access')
|
|
1145
|
+
.action(async (options) => {
|
|
1146
|
+
const resolved = await resolveOptions(options);
|
|
1147
|
+
const token = options.token ?? process.env.BRAINLINK_MCP_TOKEN;
|
|
1148
|
+
const server = await startRemoteMcpServer({
|
|
1149
|
+
vaultPath: resolved.vault,
|
|
1150
|
+
agent: resolved.agent,
|
|
1151
|
+
host: options.host ?? '0.0.0.0',
|
|
1152
|
+
port: parsePositiveInteger(options.port ?? '3333', 3333),
|
|
1153
|
+
path: options.path ?? '/mcp',
|
|
1154
|
+
token,
|
|
1155
|
+
shouldIndex: options.index
|
|
1156
|
+
});
|
|
1157
|
+
print(options.json, {
|
|
1158
|
+
url: server.url,
|
|
1159
|
+
healthUrl: server.healthUrl,
|
|
1160
|
+
readyUrl: server.readyUrl,
|
|
1161
|
+
vault: resolved.vault,
|
|
1162
|
+
agent: resolved.agent ?? '*',
|
|
1163
|
+
auth: token === undefined ? 'disabled' : 'bearer'
|
|
1164
|
+
}, () => `Brainlink remote MCP server running at ${server.url} (health: ${server.healthUrl}, readiness: ${server.readyUrl})`);
|
|
1165
|
+
});
|
|
1008
1166
|
program
|
|
1009
1167
|
.command('quickstart')
|
|
1010
1168
|
.option('-v, --vault <vault>', 'vault directory')
|
|
1011
1169
|
.option('-a, --agent <agent>', 'agent memory namespace')
|
|
1012
1170
|
.option('--query <query>', 'optional task query to return immediate grounded context')
|
|
1013
1171
|
.option('--mode <mode>', 'search mode for context (fts|semantic|hybrid)')
|
|
1172
|
+
.option('--strategy <strategy>', 'context strategy for context (rag|cag|auto)')
|
|
1014
1173
|
.option('--limit <limit>', 'maximum context sections')
|
|
1015
1174
|
.option('--tokens <tokens>', 'maximum context token budget')
|
|
1016
1175
|
.option('--no-install-agent', 'skip agent MCP/plugin installation and upgrade automation')
|
|
@@ -1025,6 +1184,7 @@ export const registerWriteCommands = (program) => {
|
|
|
1025
1184
|
const limit = parsePositiveInteger(options.limit ?? String(resolved.defaults.defaultSearchLimit), resolved.defaults.defaultSearchLimit);
|
|
1026
1185
|
const tokens = parsePositiveInteger(options.tokens ?? String(resolved.defaults.defaultContextTokens), resolved.defaults.defaultContextTokens);
|
|
1027
1186
|
const mode = sanitizeSearchMode(options.mode, resolved.defaults.defaultSearchMode);
|
|
1187
|
+
const strategy = sanitizeContextStrategy(options.strategy, resolved.defaults.defaultContextStrategy);
|
|
1028
1188
|
const index = await indexVault(resolved.vault);
|
|
1029
1189
|
const stats = await getStats(resolved.vault, resolved.agent);
|
|
1030
1190
|
const validation = await validateVault(resolved.vault, resolved.agent);
|
|
@@ -1033,7 +1193,7 @@ export const registerWriteCommands = (program) => {
|
|
|
1033
1193
|
const policy = await getBootstrapPolicy();
|
|
1034
1194
|
const bootstrapStatus = await getBootstrapSessionStatus(resolved.vault, resolved.agent);
|
|
1035
1195
|
const context = options.query
|
|
1036
|
-
? await buildContextPackage(resolved.vault, options.query, limit, tokens, resolved.agent, mode)
|
|
1196
|
+
? await buildContextPackage(resolved.vault, options.query, limit, tokens, resolved.agent, mode, strategy, resolved.defaults.defaultContextCacheTtlMs)
|
|
1037
1197
|
: null;
|
|
1038
1198
|
const agentIntegration = options.installAgent === false
|
|
1039
1199
|
? null
|
package/dist/cli/main.js
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { registerAgentCommands } from './commands/agent-commands.js';
|
|
7
7
|
import { registerConfigCommands } from './commands/config-commands.js';
|
|
8
8
|
import { registerReadCommands } from './commands/read-commands.js';
|
|
9
|
+
import { registerVaultCommands } from './commands/vault-commands.js';
|
|
9
10
|
import { registerWriteCommands } from './commands/write-commands.js';
|
|
10
11
|
const readPackageVersion = () => {
|
|
11
12
|
const packagePath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json');
|
|
@@ -24,6 +25,7 @@ program
|
|
|
24
25
|
registerWriteCommands(program);
|
|
25
26
|
registerReadCommands(program);
|
|
26
27
|
registerConfigCommands(program);
|
|
28
|
+
registerVaultCommands(program);
|
|
27
29
|
registerAgentCommands(program);
|
|
28
30
|
program.parseAsync().catch((error) => {
|
|
29
31
|
const message = error instanceof Error ? error.message : String(error);
|
package/dist/cli/runtime.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { autoMigrateConfiguredVaultIfChanged } from '../application/auto-migrate-configured-vault.js';
|
|
2
|
+
import { loadBrainlinkConfigWithSource, resolveAgentRuntimeDefaults } from '../infrastructure/config.js';
|
|
2
3
|
import { assertVaultAllowed } from '../infrastructure/file-system-vault.js';
|
|
3
4
|
export const parsePositiveInteger = (value, fallback) => {
|
|
4
5
|
const parsed = Number.parseInt(value, 10);
|
|
5
6
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
6
7
|
};
|
|
7
8
|
export const resolveOptions = async (options) => {
|
|
8
|
-
const config = await
|
|
9
|
+
const { config, vaultSource } = await loadBrainlinkConfigWithSource();
|
|
10
|
+
if (options.vault === undefined) {
|
|
11
|
+
const sourceKey = vaultSource.sourcePath ? `${vaultSource.source}:${vaultSource.sourcePath}` : vaultSource.source;
|
|
12
|
+
await autoMigrateConfiguredVaultIfChanged({
|
|
13
|
+
configKey: sourceKey,
|
|
14
|
+
configuredVault: config.vault
|
|
15
|
+
});
|
|
16
|
+
}
|
|
9
17
|
const vault = options.vault ?? config.vault;
|
|
10
18
|
const allowedVault = assertVaultAllowed(vault, config.allowedVaults);
|
|
11
19
|
const agent = options.agent ?? config.defaultAgent;
|
package/dist/domain/context.js
CHANGED
|
@@ -76,6 +76,7 @@ export const formatContextPackage = (query, sections) => {
|
|
|
76
76
|
section.tags.length > 0 ? `Tags: ${section.tags.map((tag) => `#${tag}`).join(' ')}` : null,
|
|
77
77
|
`Score: ${section.score.toFixed(3)}`,
|
|
78
78
|
`Mode: ${section.searchMode}`,
|
|
79
|
+
section.volatile ? `Volatile: true${section.expiresAt ? `, expires ${section.expiresAt}` : ''}` : null,
|
|
79
80
|
'',
|
|
80
81
|
section.content
|
|
81
82
|
]
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
const normalize = (value) => value.trim().toLowerCase();
|
|
2
|
+
const includesAny = (value, patterns) => patterns.some((pattern) => pattern.test(value));
|
|
3
|
+
const contextId = (title) => title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
4
|
+
const context = (title) => ({
|
|
5
|
+
id: contextId(title),
|
|
6
|
+
title
|
|
7
|
+
});
|
|
8
|
+
const byTitle = (left, right) => left.title.localeCompare(right.title);
|
|
9
|
+
const edgeKey = (source, target) => source < target ? `${source}|${target}` : `${target}|${source}`;
|
|
10
|
+
const nodeSearchText = (node) => normalize([node.title, node.path, ...node.tags].join(' '));
|
|
11
|
+
export const inferExplicitVisualGraphContext = (node) => {
|
|
12
|
+
const text = nodeSearchText(node);
|
|
13
|
+
const path = normalize(node.path);
|
|
14
|
+
if (includesAny(text, [/\bgithub repositories hub\b/]))
|
|
15
|
+
return context('GitHub Repositories');
|
|
16
|
+
if (includesAny(text, [/\bgithub organizations hub\b/]))
|
|
17
|
+
return context('GitHub Organizations');
|
|
18
|
+
if (includesAny(text, [/\bmachine configuration hub\b/]))
|
|
19
|
+
return context('Machine Configuration');
|
|
20
|
+
if (includesAny(text, [/\buser preferences hub\b/]))
|
|
21
|
+
return context('User Preferences');
|
|
22
|
+
if (includesAny(text, [/\bneovim lazyvim hub\b/]))
|
|
23
|
+
return context('Neovim LazyVim');
|
|
24
|
+
if (includesAny(text, [/\bgit workflow hub\b/]))
|
|
25
|
+
return context('Git Workflow');
|
|
26
|
+
if (includesAny(text, [/\bagent memory hub\b/]))
|
|
27
|
+
return context('Agent Memory');
|
|
28
|
+
if (includesAny(text, [/pingu_ai_codding_pair_programming/, /\bpingu\b/]))
|
|
29
|
+
return context('Pingu');
|
|
30
|
+
if (path.startsWith('github-repos/'))
|
|
31
|
+
return context('GitHub Repositories');
|
|
32
|
+
if (path.startsWith('github-org-repos/'))
|
|
33
|
+
return context('GitHub Organizations');
|
|
34
|
+
if (path.startsWith('machine-config/'))
|
|
35
|
+
return context('Machine Configuration');
|
|
36
|
+
if (includesAny(text, [/\bbrainlink\b/]))
|
|
37
|
+
return context('Brainlink');
|
|
38
|
+
if (includesAny(text, [/\banonspace\b/]))
|
|
39
|
+
return context('AnonSpace');
|
|
40
|
+
if (includesAny(text, [/\bsubstructa\b/]))
|
|
41
|
+
return context('Substructa');
|
|
42
|
+
if (includesAny(text, [/\bnebula\b/]))
|
|
43
|
+
return context('Nebula');
|
|
44
|
+
if (includesAny(text, [/\bsnippets?\b/, /\bupgrader\b/, /\bversion-map\b/]))
|
|
45
|
+
return context('Snippets');
|
|
46
|
+
if (includesAny(text, [
|
|
47
|
+
/\bpreference\b/,
|
|
48
|
+
/\bpreferences\b/,
|
|
49
|
+
/\bpreferencia\b/,
|
|
50
|
+
/\bpreferencias\b/,
|
|
51
|
+
/\bpreferência\b/,
|
|
52
|
+
/\bpreferências\b/,
|
|
53
|
+
/\bplaybook\b/,
|
|
54
|
+
/\bdirective\b/,
|
|
55
|
+
/\bdirectives\b/,
|
|
56
|
+
/\bdiretiva\b/,
|
|
57
|
+
/\bdiretivas\b/,
|
|
58
|
+
/\bengineering-style\b/,
|
|
59
|
+
/\bglobal-engineering\b/,
|
|
60
|
+
/\bcoding-identity\b/,
|
|
61
|
+
/\bagents\.md\b/,
|
|
62
|
+
/\bagents-md\b/,
|
|
63
|
+
/\bordem direta\b/,
|
|
64
|
+
/\bordem-direta\b/,
|
|
65
|
+
/\bman-in-the-loop\b/,
|
|
66
|
+
/\bconfig geral\b/,
|
|
67
|
+
/\bconfig-geral\b/,
|
|
68
|
+
/\bsync config_files\b/,
|
|
69
|
+
/\bsync-config-files\b/,
|
|
70
|
+
/\bregra operacional\b/,
|
|
71
|
+
/\bregras operacionais\b/,
|
|
72
|
+
/\boperational rule\b/,
|
|
73
|
+
/\boperational rules\b/,
|
|
74
|
+
/\boperational policy\b/
|
|
75
|
+
])) {
|
|
76
|
+
return context('User Preferences');
|
|
77
|
+
}
|
|
78
|
+
if (includesAny(text, [/\binkdrop\b/]))
|
|
79
|
+
return context('Inkdrop');
|
|
80
|
+
if (includesAny(text, [/\blazyvim\b/, /\bneovim\b/, /\bnvim\b/, /\bmason\b/, /\bwrapper\b/]))
|
|
81
|
+
return context('Neovim LazyVim');
|
|
82
|
+
if (includesAny(text, [/\bgit-flow\b/, /\borigin-sync\b/, /\bgit-identidade\b/, /\bcommit\b/, /\bpush\b/]))
|
|
83
|
+
return context('Git Workflow');
|
|
84
|
+
if (includesAny(text, [/\bdocker\b/, /\bkubernetes\b/, /\bdeploy\b/, /\bredeploy\b/]))
|
|
85
|
+
return context('Operations');
|
|
86
|
+
if (path.startsWith('agents/'))
|
|
87
|
+
return context('Agent Memory');
|
|
88
|
+
return null;
|
|
89
|
+
};
|
|
90
|
+
export const inferVisualGraphContext = (node) => {
|
|
91
|
+
const explicit = inferExplicitVisualGraphContext(node);
|
|
92
|
+
if (explicit) {
|
|
93
|
+
return explicit;
|
|
94
|
+
}
|
|
95
|
+
const [root] = node.path.split('/').filter(Boolean);
|
|
96
|
+
return context(root ? root.replace(/[-_]+/g, ' ') : 'Root');
|
|
97
|
+
};
|
|
98
|
+
export const groupNodesByVisualContext = (nodes) => {
|
|
99
|
+
const groups = new Map();
|
|
100
|
+
nodes.forEach((node) => {
|
|
101
|
+
const visualContext = inferVisualGraphContext(node);
|
|
102
|
+
const bucket = groups.get(visualContext.title);
|
|
103
|
+
if (bucket) {
|
|
104
|
+
bucket.push(node);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
groups.set(visualContext.title, [node]);
|
|
108
|
+
});
|
|
109
|
+
return new Map(Array.from(groups.entries(), ([title, groupedNodes]) => [title, [...groupedNodes].sort(byTitle)]));
|
|
110
|
+
};
|
|
111
|
+
const countDegrees = (edges) => edges.reduce((degrees, edge) => {
|
|
112
|
+
degrees.set(edge.source, (degrees.get(edge.source) ?? 0) + edge.weight);
|
|
113
|
+
if (edge.target) {
|
|
114
|
+
degrees.set(edge.target, (degrees.get(edge.target) ?? 0) + edge.weight);
|
|
115
|
+
}
|
|
116
|
+
return degrees;
|
|
117
|
+
}, new Map());
|
|
118
|
+
const selectVisualHub = (contextTitle, nodes, degrees) => {
|
|
119
|
+
const normalizedContext = normalize(contextTitle).replace(/\s+/g, ' ');
|
|
120
|
+
const ranked = [...nodes].sort((left, right) => {
|
|
121
|
+
const leftTitle = normalize(left.title);
|
|
122
|
+
const rightTitle = normalize(right.title);
|
|
123
|
+
const leftHubScore = leftTitle === normalizedContext || leftTitle === `${normalizedContext} hub`
|
|
124
|
+
? 4
|
|
125
|
+
: leftTitle.includes(normalizedContext) && /\bhub\b/.test(leftTitle)
|
|
126
|
+
? 3
|
|
127
|
+
: /\b(memory hub|knowledge root|moc|map|hub)\b/.test(leftTitle)
|
|
128
|
+
? 2
|
|
129
|
+
: 0;
|
|
130
|
+
const rightHubScore = rightTitle === normalizedContext || rightTitle === `${normalizedContext} hub`
|
|
131
|
+
? 4
|
|
132
|
+
: rightTitle.includes(normalizedContext) && /\bhub\b/.test(rightTitle)
|
|
133
|
+
? 3
|
|
134
|
+
: /\b(memory hub|knowledge root|moc|map|hub)\b/.test(rightTitle)
|
|
135
|
+
? 2
|
|
136
|
+
: 0;
|
|
137
|
+
const hubDelta = rightHubScore - leftHubScore;
|
|
138
|
+
if (hubDelta !== 0)
|
|
139
|
+
return hubDelta;
|
|
140
|
+
const degreeDelta = (degrees.get(right.id) ?? 0) - (degrees.get(left.id) ?? 0);
|
|
141
|
+
return degreeDelta === 0 ? left.title.localeCompare(right.title) : degreeDelta;
|
|
142
|
+
});
|
|
143
|
+
return ranked[0] ?? null;
|
|
144
|
+
};
|
|
145
|
+
export const addVisualContextEdges = (graph) => {
|
|
146
|
+
const existingPairs = new Set(graph.edges
|
|
147
|
+
.filter((edge) => Boolean(edge.target))
|
|
148
|
+
.map((edge) => edgeKey(edge.source, edge.target)));
|
|
149
|
+
const degrees = countDegrees(graph.edges);
|
|
150
|
+
const derivedEdges = [];
|
|
151
|
+
for (const [contextTitle, nodes] of groupNodesByVisualContext(graph.nodes).entries()) {
|
|
152
|
+
if (nodes.length <= 1) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const hub = selectVisualHub(contextTitle, nodes, degrees);
|
|
156
|
+
if (!hub) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
nodes
|
|
160
|
+
.filter((node) => node.id !== hub.id)
|
|
161
|
+
.forEach((node) => {
|
|
162
|
+
const key = edgeKey(hub.id, node.id);
|
|
163
|
+
if (existingPairs.has(key)) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
existingPairs.add(key);
|
|
167
|
+
derivedEdges.push({
|
|
168
|
+
source: hub.id,
|
|
169
|
+
target: node.id,
|
|
170
|
+
targetTitle: node.title,
|
|
171
|
+
weight: 0.5,
|
|
172
|
+
priority: 'low'
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
nodes: graph.nodes,
|
|
178
|
+
edges: [...graph.edges, ...derivedEdges]
|
|
179
|
+
};
|
|
180
|
+
};
|