@andespindola/brainlink 0.1.0-beta.16 → 0.1.0-beta.160
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 +9 -6
- package/CHANGELOG.md +27 -0
- package/COPYRIGHT.md +5 -0
- package/README.md +177 -20
- package/dist/application/add-note.js +13 -44
- package/dist/application/auto-migrate-configured-vault.js +37 -0
- package/dist/application/build-context.js +64 -3
- package/dist/application/canonical-context-links.js +209 -0
- package/dist/application/dedupe-notes.js +226 -0
- package/dist/application/frontend/client-css.js +241 -51
- package/dist/application/frontend/client-html.js +50 -27
- package/dist/application/frontend/client-js.js +1369 -605
- package/dist/application/frontend/client-render-worker-js.js +622 -0
- package/dist/application/frontend/client-worker-js.js +66 -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 +266 -0
- package/dist/application/index-vault.js +262 -23
- package/dist/application/migrate-context-links.js +79 -0
- package/dist/application/offline-pack-backup.js +44 -0
- package/dist/application/search-graph-node-ids.js +63 -3
- package/dist/application/server/routes.js +247 -7
- package/dist/application/start-server.js +75 -4
- package/dist/application/watch-vault.js +23 -2
- package/dist/cli/commands/agent-commands.js +7 -0
- package/dist/cli/commands/write-commands.js +924 -14
- package/dist/cli/runtime.js +10 -2
- package/dist/domain/context.js +54 -11
- package/dist/domain/graph-contexts.js +180 -0
- package/dist/domain/graph-layout.js +389 -18
- package/dist/domain/markdown.js +53 -9
- package/dist/domain/middle-out.js +18 -0
- package/dist/infrastructure/config.js +121 -4
- package/dist/infrastructure/file-index.js +76 -6
- package/dist/infrastructure/file-system-vault.js +15 -0
- package/dist/infrastructure/index-state.js +58 -0
- package/dist/infrastructure/private-pack-codec.js +71 -10
- package/dist/infrastructure/search-packs.js +286 -15
- package/dist/infrastructure/vault-migration-state.js +69 -0
- package/dist/infrastructure/volatile-memory.js +100 -0
- package/dist/mcp/runtime.js +20 -0
- package/dist/mcp/server.js +39 -11
- package/dist/mcp/tools.js +183 -7
- package/docs/AGENT_USAGE.md +96 -5
- package/docs/ARCHITECTURE.md +8 -0
- package/docs/QUICKSTART.md +7 -0
- package/package.json +7 -2
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
|
@@ -1,13 +1,50 @@
|
|
|
1
|
+
import { middleOutIndices } from './middle-out.js';
|
|
2
|
+
const maxSectionsPerDocument = 3;
|
|
3
|
+
const byScore = (left, right) => right.score - left.score || left.title.localeCompare(right.title);
|
|
4
|
+
const byOrdinal = (left, right) => (left.chunkOrdinal ?? Number.MAX_SAFE_INTEGER) - (right.chunkOrdinal ?? Number.MAX_SAFE_INTEGER);
|
|
5
|
+
const middleOutDocumentResults = (results) => {
|
|
6
|
+
if (results.length <= 1) {
|
|
7
|
+
return results;
|
|
8
|
+
}
|
|
9
|
+
const sortedByOrdinal = [...results].sort(byOrdinal);
|
|
10
|
+
const pivotChunkId = [...results].sort(byScore)[0]?.chunkId;
|
|
11
|
+
const pivotIndex = sortedByOrdinal.findIndex((result) => result.chunkId === pivotChunkId);
|
|
12
|
+
if (pivotIndex < 0) {
|
|
13
|
+
return [...results].sort(byScore);
|
|
14
|
+
}
|
|
15
|
+
return middleOutIndices(sortedByOrdinal.length, pivotIndex).map((index) => sortedByOrdinal[index]);
|
|
16
|
+
};
|
|
1
17
|
export const selectContextSections = (results, maxTokens) => {
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
18
|
+
const grouped = results.reduce((state, result) => {
|
|
19
|
+
const current = state.get(result.documentId) ?? [];
|
|
20
|
+
state.set(result.documentId, [...current, result]);
|
|
21
|
+
return state;
|
|
22
|
+
}, new Map());
|
|
23
|
+
const documentOrder = Array.from(results.reduce((state, result) => {
|
|
24
|
+
if (!state.has(result.documentId)) {
|
|
25
|
+
state.set(result.documentId, result.score);
|
|
6
26
|
}
|
|
7
|
-
return
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
27
|
+
return state;
|
|
28
|
+
}, new Map()).entries())
|
|
29
|
+
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
|
|
30
|
+
.map(([documentId]) => documentId);
|
|
31
|
+
const selected = documentOrder.reduce((state, documentId) => {
|
|
32
|
+
const ordered = middleOutDocumentResults(grouped.get(documentId) ?? []);
|
|
33
|
+
let usedTokens = state.usedTokens;
|
|
34
|
+
let sections = state.sections;
|
|
35
|
+
let seenChunks = state.seenChunks;
|
|
36
|
+
for (let index = 0; index < ordered.length && index < maxSectionsPerDocument; index += 1) {
|
|
37
|
+
const result = ordered[index];
|
|
38
|
+
if (seenChunks.has(result.chunkId)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const tokenCost = Math.ceil(result.content.length / 4);
|
|
42
|
+
if (usedTokens + tokenCost > maxTokens) {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
usedTokens += tokenCost;
|
|
46
|
+
sections = [
|
|
47
|
+
...sections,
|
|
11
48
|
{
|
|
12
49
|
title: result.title,
|
|
13
50
|
path: result.path,
|
|
@@ -16,13 +53,18 @@ export const selectContextSections = (results, maxTokens) => {
|
|
|
16
53
|
searchMode: result.searchMode,
|
|
17
54
|
tags: result.tags
|
|
18
55
|
}
|
|
19
|
-
]
|
|
20
|
-
|
|
56
|
+
];
|
|
57
|
+
seenChunks = new Set([...seenChunks, result.chunkId]);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
usedTokens,
|
|
61
|
+
sections,
|
|
62
|
+
seenChunks
|
|
21
63
|
};
|
|
22
64
|
}, {
|
|
23
65
|
usedTokens: 0,
|
|
24
66
|
sections: [],
|
|
25
|
-
|
|
67
|
+
seenChunks: new Set()
|
|
26
68
|
});
|
|
27
69
|
return selected.sections;
|
|
28
70
|
};
|
|
@@ -34,6 +76,7 @@ export const formatContextPackage = (query, sections) => {
|
|
|
34
76
|
section.tags.length > 0 ? `Tags: ${section.tags.map((tag) => `#${tag}`).join(' ')}` : null,
|
|
35
77
|
`Score: ${section.score.toFixed(3)}`,
|
|
36
78
|
`Mode: ${section.searchMode}`,
|
|
79
|
+
section.volatile ? `Volatile: true${section.expiresAt ? `, expires ${section.expiresAt}` : ''}` : null,
|
|
37
80
|
'',
|
|
38
81
|
section.content
|
|
39
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
|
+
};
|