@andespindola/brainlink 0.1.0-beta.99 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -6
- package/CHANGELOG.md +14 -0
- package/README.md +186 -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/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 +147 -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/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 +36 -13
- package/dist/mcp/tools.js +203 -14
- package/docs/AGENT_USAGE.md +50 -5
- package/docs/ARCHITECTURE.md +11 -0
- package/docs/QUICKSTART.md +3 -1
- package/docs/RELEASE.md +4 -3
- package/package.json +3 -1
|
@@ -8,15 +8,19 @@ import { buildContextPackage } from '../../application/build-context.js';
|
|
|
8
8
|
import { resolveDuplicateNotes, scanDuplicateNotes } from '../../application/dedupe-notes.js';
|
|
9
9
|
import { importLegacySqliteDatabase } from '../../application/import-legacy-sqlite.js';
|
|
10
10
|
import { indexVault, indexVaultWithOptions } from '../../application/index-vault.js';
|
|
11
|
+
import { migrateContextLinks } from '../../application/migrate-context-links.js';
|
|
12
|
+
import { canonicalizeContextLinks } from '../../application/canonical-context-links.js';
|
|
11
13
|
import { migrateVaultContent, planVaultMigration, previewVaultMigration, shouldMigrateDefaultVault } from '../../application/migrate-vault.js';
|
|
12
14
|
import { createOfflinePackBackup } from '../../application/offline-pack-backup.js';
|
|
13
15
|
import { startServer } from '../../application/start-server.js';
|
|
14
16
|
import { startVaultWatcher } from '../../application/watch-vault.js';
|
|
15
17
|
import { doctorVault, getStats, validateVault } from '../../application/analyze-vault.js';
|
|
16
|
-
import { defaultBrainlinkConfig, sanitizeSearchMode } from '../../infrastructure/config.js';
|
|
18
|
+
import { defaultBrainlinkConfig, sanitizeContextStrategy, sanitizeSearchMode } from '../../infrastructure/config.js';
|
|
17
19
|
import { loadBrainlinkConfig } from '../../infrastructure/config.js';
|
|
18
|
-
import { assertVaultAllowed, ensureVault } from '../../infrastructure/file-system-vault.js';
|
|
20
|
+
import { assertVaultAllowed, ensureVault, isBucketVaultPath } from '../../infrastructure/file-system-vault.js';
|
|
19
21
|
import { getBootstrapPolicy, getBootstrapSessionStatus, touchBootstrapSession } from '../../infrastructure/session-state.js';
|
|
22
|
+
import { addVolatileMemory, clearVolatileMemory } from '../../infrastructure/volatile-memory.js';
|
|
23
|
+
import { startRemoteMcpServer } from '../../mcp/http-server.js';
|
|
20
24
|
import { installAgentIntegration } from './agent-commands.js';
|
|
21
25
|
import { parsePositiveInteger, print, resolveOptions } from '../runtime.js';
|
|
22
26
|
const resolveAddContent = (options) => {
|
|
@@ -698,6 +702,68 @@ export const registerWriteCommands = (program) => {
|
|
|
698
702
|
return `${summary}${indexMessage}${reportMessage}`;
|
|
699
703
|
});
|
|
700
704
|
});
|
|
705
|
+
program
|
|
706
|
+
.command('migrate-context-links')
|
|
707
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
708
|
+
.option('-a, --agent <agent>', 'agent memory namespace')
|
|
709
|
+
.option('-l, --limit <limit>', 'maximum context links to add per note', '5')
|
|
710
|
+
.option('--dry-run', 'preview context-link migration without writing files')
|
|
711
|
+
.option('--no-index', 'skip reindexing after migration')
|
|
712
|
+
.option('--json', 'print machine-readable JSON')
|
|
713
|
+
.description('add concise Context Links sections from existing wiki-link mentions')
|
|
714
|
+
.action(async (options) => {
|
|
715
|
+
const resolved = await resolveOptions(options);
|
|
716
|
+
const result = await migrateContextLinks(resolved.vault, {
|
|
717
|
+
dryRun: options.dryRun === true,
|
|
718
|
+
limit: parsePositiveInteger(options.limit ?? '5', 5),
|
|
719
|
+
agentId: resolved.agent
|
|
720
|
+
});
|
|
721
|
+
const shouldIndex = options.index !== false && !result.dryRun && result.changed > 0;
|
|
722
|
+
const index = shouldIndex ? await indexVault(resolved.vault, { full: true }) : undefined;
|
|
723
|
+
print(options.json, {
|
|
724
|
+
vault: resolved.vault,
|
|
725
|
+
agent: resolved.agent ?? 'shared',
|
|
726
|
+
...result,
|
|
727
|
+
...(index ? { index } : {})
|
|
728
|
+
}, () => {
|
|
729
|
+
const mode = result.dryRun ? 'Previewed' : 'Migrated';
|
|
730
|
+
const indexMessage = index
|
|
731
|
+
? ` Fully reindexed ${index.documentCount} documents, ${index.chunkCount} chunks and ${index.linkCount} context links.`
|
|
732
|
+
: '';
|
|
733
|
+
return `${mode} ${result.scanned} notes: changed=${result.changed}, skipped=${result.skipped}, limit=${result.limit}.${indexMessage}`;
|
|
734
|
+
});
|
|
735
|
+
});
|
|
736
|
+
program
|
|
737
|
+
.command('canonicalize-context-links')
|
|
738
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
739
|
+
.option('-a, --agent <agent>', 'agent memory namespace')
|
|
740
|
+
.option('--dry-run', 'preview canonical context links without writing files')
|
|
741
|
+
.option('--no-create-hubs', 'do not create missing context hub notes')
|
|
742
|
+
.option('--no-index', 'skip reindexing after canonicalization')
|
|
743
|
+
.option('--json', 'print machine-readable JSON')
|
|
744
|
+
.description('ensure notes have canonical Context Links to their inferred context hubs')
|
|
745
|
+
.action(async (options) => {
|
|
746
|
+
const resolved = await resolveOptions(options);
|
|
747
|
+
const result = await canonicalizeContextLinks(resolved.vault, {
|
|
748
|
+
dryRun: options.dryRun === true,
|
|
749
|
+
agentId: resolved.agent,
|
|
750
|
+
createMissingHubs: options.createHubs !== false
|
|
751
|
+
});
|
|
752
|
+
const shouldIndex = options.index !== false && !result.dryRun && result.changed > 0;
|
|
753
|
+
const index = shouldIndex ? await indexVault(resolved.vault, { full: true }) : undefined;
|
|
754
|
+
print(options.json, {
|
|
755
|
+
vault: resolved.vault,
|
|
756
|
+
agent: resolved.agent ?? 'shared',
|
|
757
|
+
...result,
|
|
758
|
+
...(index ? { index } : {})
|
|
759
|
+
}, () => {
|
|
760
|
+
const mode = result.dryRun ? 'Previewed' : 'Canonicalized';
|
|
761
|
+
const indexMessage = index
|
|
762
|
+
? ` Fully reindexed ${index.documentCount} documents, ${index.chunkCount} chunks and ${index.linkCount} context links.`
|
|
763
|
+
: '';
|
|
764
|
+
return `${mode} ${result.scanned} notes: changed=${result.changed}, createdHubs=${result.createdHubs}, skipped=${result.skipped}.${indexMessage}`;
|
|
765
|
+
});
|
|
766
|
+
});
|
|
701
767
|
program
|
|
702
768
|
.command('db-import')
|
|
703
769
|
.option('-v, --vault <vault>', 'vault directory')
|
|
@@ -729,6 +795,29 @@ export const registerWriteCommands = (program) => {
|
|
|
729
795
|
return `${summary}${indexMessage}${dryRunMessage}`;
|
|
730
796
|
});
|
|
731
797
|
});
|
|
798
|
+
program
|
|
799
|
+
.command('volatile')
|
|
800
|
+
.option('-c, --content <content>', 'temporary memory content to add')
|
|
801
|
+
.option('--ttl <minutes>', 'time-to-live in minutes', '240')
|
|
802
|
+
.option('--tag <tag...>', 'volatile memory tag')
|
|
803
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
804
|
+
.option('-a, --agent <agent>', 'agent memory namespace')
|
|
805
|
+
.option('--clear', 'clear volatile memory for the current agent namespace')
|
|
806
|
+
.option('--json', 'print machine-readable JSON')
|
|
807
|
+
.description('add or clear temporary agent-decided memory')
|
|
808
|
+
.action(async (options) => {
|
|
809
|
+
const resolved = await resolveOptions(options);
|
|
810
|
+
if (options.clear) {
|
|
811
|
+
const cleared = await clearVolatileMemory(resolved.vault, resolved.agent);
|
|
812
|
+
print(options.json, { cleared, agent: resolved.agent ?? 'shared' }, () => `Cleared ${cleared} volatile memories.`);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
if (!options.content || options.content.trim().length === 0) {
|
|
816
|
+
throw new Error('Use --content to add volatile memory, or --clear to remove it.');
|
|
817
|
+
}
|
|
818
|
+
const entry = await addVolatileMemory(resolved.vault, options.content, resolved.agent ?? 'shared', parsePositiveInteger(options.ttl ?? '240', 240), options.tag ?? []);
|
|
819
|
+
print(options.json, { entry }, () => `Stored volatile memory until ${entry.expiresAt}.`);
|
|
820
|
+
});
|
|
732
821
|
program
|
|
733
822
|
.command('add')
|
|
734
823
|
.argument('<title>', 'note title')
|
|
@@ -737,6 +826,7 @@ export const registerWriteCommands = (program) => {
|
|
|
737
826
|
.option('-v, --vault <vault>', 'vault directory')
|
|
738
827
|
.option('-a, --agent <agent>', 'agent memory namespace')
|
|
739
828
|
.option('--allow-sensitive', 'allow writing content that looks like a secret')
|
|
829
|
+
.option('--no-auto-context-links', 'skip canonical Context Links for this note')
|
|
740
830
|
.option('--no-auto-index', 'skip reindexing after add')
|
|
741
831
|
.option('--json', 'print machine-readable JSON')
|
|
742
832
|
.description('add a markdown note to the vault')
|
|
@@ -744,7 +834,8 @@ export const registerWriteCommands = (program) => {
|
|
|
744
834
|
const resolved = await resolveOptions(options);
|
|
745
835
|
const content = resolveAddContent(options);
|
|
746
836
|
const added = await addNoteWithMetadata(resolved.vault, title, content, resolved.agent, {
|
|
747
|
-
allowSensitive: Boolean(options.allowSensitive)
|
|
837
|
+
allowSensitive: Boolean(options.allowSensitive),
|
|
838
|
+
autoContextLinks: options.autoContextLinks !== false && resolved.config.autoCanonicalContextLinks
|
|
748
839
|
});
|
|
749
840
|
const shouldAutoIndex = options.autoIndex !== false && resolved.config.autoIndexOnWrite;
|
|
750
841
|
const index = shouldAutoIndex ? await indexVault(resolved.vault) : undefined;
|
|
@@ -768,7 +859,9 @@ export const registerWriteCommands = (program) => {
|
|
|
768
859
|
writeConnectivity: {
|
|
769
860
|
autoLinked: added.autoLinked,
|
|
770
861
|
linkTarget: added.linkTarget,
|
|
771
|
-
|
|
862
|
+
context: added.context,
|
|
863
|
+
hubCreated: added.hubCreated,
|
|
864
|
+
guaranteedEdge: added.autoLinked
|
|
772
865
|
},
|
|
773
866
|
possibleDuplicates,
|
|
774
867
|
...(index ? { index } : {})
|
|
@@ -776,7 +869,8 @@ export const registerWriteCommands = (program) => {
|
|
|
776
869
|
const duplicateMessage = possibleDuplicates.length > 0
|
|
777
870
|
? `\nPotential duplicates: ${possibleDuplicates.length}. Use "blink dedupe --json" or "blink dedupe-resolve".`
|
|
778
871
|
: '';
|
|
779
|
-
|
|
872
|
+
const linkMessage = added.autoLinked ? ` Linked to [[${added.linkTarget}]].` : '';
|
|
873
|
+
return `Created note at ${added.path}.${linkMessage}${duplicateMessage}`;
|
|
780
874
|
});
|
|
781
875
|
});
|
|
782
876
|
program
|
|
@@ -836,12 +930,17 @@ export const registerWriteCommands = (program) => {
|
|
|
836
930
|
program
|
|
837
931
|
.command('index')
|
|
838
932
|
.option('-v, --vault <vault>', 'vault directory')
|
|
933
|
+
.option('--full', 'force a complete reindex from Markdown source without reusing unchanged index entries')
|
|
839
934
|
.option('--json', 'print machine-readable JSON')
|
|
840
935
|
.description('index markdown notes, links, tags and chunks')
|
|
841
936
|
.action(async (options) => {
|
|
842
937
|
const resolved = await resolveOptions(options);
|
|
843
|
-
const result = await indexVault(resolved.vault
|
|
844
|
-
|
|
938
|
+
const result = await indexVault(resolved.vault, {
|
|
939
|
+
full: options.full === true
|
|
940
|
+
});
|
|
941
|
+
print(options.json, result, () => options.full === true
|
|
942
|
+
? `Fully reindexed ${result.documentCount} documents, ${result.chunkCount} chunks and ${result.linkCount} links`
|
|
943
|
+
: `Indexed ${result.documentCount} documents, ${result.chunkCount} chunks and ${result.linkCount} links`);
|
|
845
944
|
});
|
|
846
945
|
program
|
|
847
946
|
.command('bench')
|
|
@@ -976,26 +1075,28 @@ export const registerWriteCommands = (program) => {
|
|
|
976
1075
|
.option('-p, --port <port>', 'server port', '4321')
|
|
977
1076
|
.option('--no-index', 'skip indexing before starting the server')
|
|
978
1077
|
.option('--no-open', 'do not open the graph UI automatically')
|
|
979
|
-
.option('-w, --watch', 'watch markdown files and reindex on changes')
|
|
1078
|
+
.option('-w, --watch', 'watch markdown files and reindex on changes', true)
|
|
1079
|
+
.option('--no-watch', 'disable markdown file watching')
|
|
980
1080
|
.option('--json', 'print machine-readable JSON')
|
|
981
1081
|
.description('start a local web UI for the knowledge graph')
|
|
982
1082
|
.action(async (options) => {
|
|
983
1083
|
const resolved = await resolveOptions(options);
|
|
1084
|
+
const shouldWatch = options.watch !== false && !isBucketVaultPath(resolved.vault);
|
|
984
1085
|
const server = await startServer({
|
|
985
1086
|
vaultPath: resolved.vault,
|
|
986
1087
|
host: options.host ?? resolved.config.host,
|
|
987
1088
|
port: parsePositiveInteger(options.port ?? String(resolved.config.port), resolved.config.port),
|
|
988
1089
|
shouldIndex: options.index,
|
|
989
|
-
shouldWatch
|
|
1090
|
+
shouldWatch
|
|
990
1091
|
});
|
|
991
1092
|
const openResult = options.open !== false ? openUrlInUi(server.url, process.pid) : { opened: false, mode: 'none' };
|
|
992
1093
|
print(options.json, {
|
|
993
1094
|
url: server.url,
|
|
994
|
-
watch:
|
|
1095
|
+
watch: shouldWatch,
|
|
995
1096
|
readonly: true,
|
|
996
1097
|
openedUi: openResult.opened,
|
|
997
1098
|
openMode: openResult.mode
|
|
998
|
-
}, () => `Brainlink graph server running at ${server.url}${openResult.opened
|
|
1099
|
+
}, () => `Brainlink graph server running at ${server.url} (${shouldWatch ? 'watching for changes' : 'watch disabled'})${openResult.opened
|
|
999
1100
|
? openResult.mode === 'native-gui'
|
|
1000
1101
|
? ' (opened in native desktop GUI)'
|
|
1001
1102
|
: openResult.mode === 'app-window'
|
|
@@ -1005,12 +1106,45 @@ export const registerWriteCommands = (program) => {
|
|
|
1005
1106
|
? ' (auto-open disabled)'
|
|
1006
1107
|
: ''}`);
|
|
1007
1108
|
});
|
|
1109
|
+
program
|
|
1110
|
+
.command('mcp-server')
|
|
1111
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
1112
|
+
.option('-a, --agent <agent>', 'agent memory namespace')
|
|
1113
|
+
.option('-h, --host <host>', 'remote MCP server host', '0.0.0.0')
|
|
1114
|
+
.option('-p, --port <port>', 'remote MCP server port', '3333')
|
|
1115
|
+
.option('--path <path>', 'remote MCP endpoint path', '/mcp')
|
|
1116
|
+
.option('--token <token>', 'bearer token required for MCP requests')
|
|
1117
|
+
.option('--no-index', 'skip indexing before starting the MCP server')
|
|
1118
|
+
.option('--json', 'print machine-readable JSON')
|
|
1119
|
+
.description('start a remote MCP server for centralized cluster access')
|
|
1120
|
+
.action(async (options) => {
|
|
1121
|
+
const resolved = await resolveOptions(options);
|
|
1122
|
+
const token = options.token ?? process.env.BRAINLINK_MCP_TOKEN;
|
|
1123
|
+
const server = await startRemoteMcpServer({
|
|
1124
|
+
vaultPath: resolved.vault,
|
|
1125
|
+
agent: resolved.agent,
|
|
1126
|
+
host: options.host ?? '0.0.0.0',
|
|
1127
|
+
port: parsePositiveInteger(options.port ?? '3333', 3333),
|
|
1128
|
+
path: options.path ?? '/mcp',
|
|
1129
|
+
token,
|
|
1130
|
+
shouldIndex: options.index
|
|
1131
|
+
});
|
|
1132
|
+
print(options.json, {
|
|
1133
|
+
url: server.url,
|
|
1134
|
+
healthUrl: server.healthUrl,
|
|
1135
|
+
readyUrl: server.readyUrl,
|
|
1136
|
+
vault: resolved.vault,
|
|
1137
|
+
agent: resolved.agent ?? '*',
|
|
1138
|
+
auth: token === undefined ? 'disabled' : 'bearer'
|
|
1139
|
+
}, () => `Brainlink remote MCP server running at ${server.url} (health: ${server.healthUrl}, readiness: ${server.readyUrl})`);
|
|
1140
|
+
});
|
|
1008
1141
|
program
|
|
1009
1142
|
.command('quickstart')
|
|
1010
1143
|
.option('-v, --vault <vault>', 'vault directory')
|
|
1011
1144
|
.option('-a, --agent <agent>', 'agent memory namespace')
|
|
1012
1145
|
.option('--query <query>', 'optional task query to return immediate grounded context')
|
|
1013
1146
|
.option('--mode <mode>', 'search mode for context (fts|semantic|hybrid)')
|
|
1147
|
+
.option('--strategy <strategy>', 'context strategy for context (rag|cag|auto)')
|
|
1014
1148
|
.option('--limit <limit>', 'maximum context sections')
|
|
1015
1149
|
.option('--tokens <tokens>', 'maximum context token budget')
|
|
1016
1150
|
.option('--no-install-agent', 'skip agent MCP/plugin installation and upgrade automation')
|
|
@@ -1025,6 +1159,7 @@ export const registerWriteCommands = (program) => {
|
|
|
1025
1159
|
const limit = parsePositiveInteger(options.limit ?? String(resolved.defaults.defaultSearchLimit), resolved.defaults.defaultSearchLimit);
|
|
1026
1160
|
const tokens = parsePositiveInteger(options.tokens ?? String(resolved.defaults.defaultContextTokens), resolved.defaults.defaultContextTokens);
|
|
1027
1161
|
const mode = sanitizeSearchMode(options.mode, resolved.defaults.defaultSearchMode);
|
|
1162
|
+
const strategy = sanitizeContextStrategy(options.strategy, resolved.defaults.defaultContextStrategy);
|
|
1028
1163
|
const index = await indexVault(resolved.vault);
|
|
1029
1164
|
const stats = await getStats(resolved.vault, resolved.agent);
|
|
1030
1165
|
const validation = await validateVault(resolved.vault, resolved.agent);
|
|
@@ -1033,7 +1168,7 @@ export const registerWriteCommands = (program) => {
|
|
|
1033
1168
|
const policy = await getBootstrapPolicy();
|
|
1034
1169
|
const bootstrapStatus = await getBootstrapSessionStatus(resolved.vault, resolved.agent);
|
|
1035
1170
|
const context = options.query
|
|
1036
|
-
? await buildContextPackage(resolved.vault, options.query, limit, tokens, resolved.agent, mode)
|
|
1171
|
+
? await buildContextPackage(resolved.vault, options.query, limit, tokens, resolved.agent, mode, strategy, resolved.defaults.defaultContextCacheTtlMs)
|
|
1037
1172
|
: null;
|
|
1038
1173
|
const agentIntegration = options.installAgent === false
|
|
1039
1174
|
? 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
|
+
};
|