@andespindola/brainlink 0.1.0-beta.42 → 0.1.0-beta.43
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 +2 -0
- package/CHANGELOG.md +1 -0
- package/README.md +19 -0
- package/dist/application/index-vault.js +110 -4
- package/dist/application/watch-vault.js +23 -2
- package/dist/cli/commands/write-commands.js +99 -1
- package/dist/infrastructure/search-packs.js +16 -1
- package/docs/AGENT_USAGE.md +18 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
- Improved Linux browser fallback launch stability by auto-applying Chromium compatibility flags (`--ozone-platform=x11`, `--disable-gpu`, `--disable-features=Vulkan,VaapiVideoDecoder`, `--disable-background-networking`) for app-window/browser modes.
|
|
39
39
|
- Improved massive-graph UI responsiveness with stricter render budgets, adaptive heavy-graph frame throttling, reduced interaction hit-test frequency, and URL-first agent selection on initial graph load.
|
|
40
40
|
- Improved 50k+ graph LOD behavior so zoomed-out views render lightweight cluster overviews and progressively reveal nodes/edges only as zoom increases.
|
|
41
|
+
- Added `blink bench` with realtime index phase telemetry and per-run compressed-pack analysis (input/output bytes, ratio, saved space, rebuild reason and duration), including continuous watch mode.
|
|
41
42
|
|
|
42
43
|
## 0.1.0-beta.3
|
|
43
44
|
|
package/README.md
CHANGED
|
@@ -760,6 +760,25 @@ blink index --vault ./vault
|
|
|
760
760
|
|
|
761
761
|
Rebuilds the local index from Markdown files.
|
|
762
762
|
|
|
763
|
+
### `bench`
|
|
764
|
+
|
|
765
|
+
```bash
|
|
766
|
+
blink bench --vault ./vault
|
|
767
|
+
blink bench --vault ./vault --watch
|
|
768
|
+
blink bench --vault ./vault --watch --debounce 500
|
|
769
|
+
blink bench --vault ./vault --json
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
Runs indexing with realtime phase telemetry (`start`, `scan`, `parse`, `embed`, `persist`, `packs`, `complete`) and prints a benchmark summary at the end of each run.
|
|
773
|
+
|
|
774
|
+
Summary includes compression behavior for `.blpk` packs when rebuild happens:
|
|
775
|
+
- pack rebuild reason
|
|
776
|
+
- pack count and pack build duration
|
|
777
|
+
- uncompressed input bytes vs compressed output bytes
|
|
778
|
+
- saved percentage
|
|
779
|
+
|
|
780
|
+
Use `--watch` to keep benchmarking incremental reindex runs after Markdown changes (local filesystem vaults only).
|
|
781
|
+
|
|
763
782
|
### `agents`
|
|
764
783
|
|
|
765
784
|
```bash
|
|
@@ -97,12 +97,33 @@ const readChangedDocuments = async (absoluteVaultPath, changedSummaries) => {
|
|
|
97
97
|
return new Map(parsed.map((document) => [document.path, document]));
|
|
98
98
|
};
|
|
99
99
|
export const indexVault = async (vaultPath) => {
|
|
100
|
+
return indexVaultWithOptions(vaultPath, {});
|
|
101
|
+
};
|
|
102
|
+
export const indexVaultWithOptions = async (vaultPath, options) => {
|
|
103
|
+
const startedAt = process.hrtime.bigint();
|
|
104
|
+
const elapsedMs = () => Number(process.hrtime.bigint() - startedAt) / 1_000_000;
|
|
105
|
+
const emit = (phase, status, message, details) => {
|
|
106
|
+
options.onProgress?.({
|
|
107
|
+
phase,
|
|
108
|
+
status,
|
|
109
|
+
message,
|
|
110
|
+
elapsedMs: elapsedMs(),
|
|
111
|
+
timestamp: new Date().toISOString(),
|
|
112
|
+
details
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
emit('start', 'start', 'Indexing started');
|
|
100
116
|
const absoluteVaultPath = await ensureVault(vaultPath);
|
|
101
117
|
const config = await loadBrainlinkConfig();
|
|
118
|
+
emit('scan', 'start', 'Scanning markdown files');
|
|
102
119
|
const [summaries, previousState] = await Promise.all([
|
|
103
120
|
readMarkdownFileSummaries(absoluteVaultPath),
|
|
104
121
|
readIndexState(absoluteVaultPath)
|
|
105
122
|
]);
|
|
123
|
+
emit('scan', 'finish', 'Scan complete', {
|
|
124
|
+
markdownFiles: summaries.length,
|
|
125
|
+
hasPreviousState: previousState != null
|
|
126
|
+
});
|
|
106
127
|
const index = openFileIndex(absoluteVaultPath);
|
|
107
128
|
try {
|
|
108
129
|
const existingIndexedDocuments = await index.getIndexedDocuments();
|
|
@@ -133,10 +154,28 @@ export const indexVault = async (vaultPath) => {
|
|
|
133
154
|
!hasDeletes &&
|
|
134
155
|
existingIndexedDocuments.length === summaries.length &&
|
|
135
156
|
previousState != null) {
|
|
136
|
-
|
|
157
|
+
const result = {
|
|
158
|
+
...toIndexResult(existingIndexedDocuments),
|
|
159
|
+
elapsedMs: elapsedMs(),
|
|
160
|
+
changedDocumentCount: 0,
|
|
161
|
+
packs: {
|
|
162
|
+
rebuilt: false,
|
|
163
|
+
reason: 'No changes detected'
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
emit('complete', 'skip', 'Index skipped: no changes detected', {
|
|
167
|
+
elapsedMs: result.elapsedMs
|
|
168
|
+
});
|
|
169
|
+
return result;
|
|
137
170
|
}
|
|
138
171
|
const changedSummaries = summaries.filter((summary) => changedPaths.has(summary.relativePath));
|
|
172
|
+
emit('parse', 'start', 'Parsing changed markdown files', {
|
|
173
|
+
changedFiles: changedSummaries.length
|
|
174
|
+
});
|
|
139
175
|
const changedDocumentsByPath = await readChangedDocuments(absoluteVaultPath, changedSummaries);
|
|
176
|
+
emit('parse', 'finish', 'Parse complete', {
|
|
177
|
+
changedDocuments: changedDocumentsByPath.size
|
|
178
|
+
});
|
|
140
179
|
const documents = summaries.flatMap((summary) => {
|
|
141
180
|
const changed = changedDocumentsByPath.get(summary.relativePath);
|
|
142
181
|
if (changed) {
|
|
@@ -146,9 +185,15 @@ export const indexVault = async (vaultPath) => {
|
|
|
146
185
|
return existing ? [existing.document] : [];
|
|
147
186
|
});
|
|
148
187
|
const titleMaps = createTitleMaps(documents);
|
|
188
|
+
emit('embed', 'start', 'Embedding changed chunks', {
|
|
189
|
+
changedDocuments: changedDocumentsByPath.size
|
|
190
|
+
});
|
|
149
191
|
const changedIndexedDocuments = changedDocumentsByPath.size > 0
|
|
150
192
|
? await embedIndexedDocuments(Array.from(changedDocumentsByPath.values()).map((document) => createIndexedDocument(document, createScopedTitleResolver(document, titleMaps), config.chunkSize)), config.embeddingProvider)
|
|
151
193
|
: [];
|
|
194
|
+
emit('embed', changedDocumentsByPath.size > 0 ? 'finish' : 'skip', changedDocumentsByPath.size > 0 ? 'Embedding complete' : 'Embedding skipped', {
|
|
195
|
+
changedIndexedDocuments: changedIndexedDocuments.length
|
|
196
|
+
});
|
|
152
197
|
const changedIndexedByPath = new Map(changedIndexedDocuments.map((document) => [document.document.path, document]));
|
|
153
198
|
const needsRelink = settingsChanged || hasDeletes || changedPaths.size > 0;
|
|
154
199
|
const indexedDocuments = documents.map((document) => {
|
|
@@ -162,8 +207,12 @@ export const indexVault = async (vaultPath) => {
|
|
|
162
207
|
}
|
|
163
208
|
return needsRelink ? relinkIndexedDocument(existing, titleMaps) : existing;
|
|
164
209
|
});
|
|
210
|
+
emit('persist', 'start', 'Persisting index');
|
|
165
211
|
await index.reset();
|
|
166
212
|
await index.saveDocuments(indexedDocuments);
|
|
213
|
+
emit('persist', 'finish', 'Index persisted', {
|
|
214
|
+
indexedDocuments: indexedDocuments.length
|
|
215
|
+
});
|
|
167
216
|
const existingPackManifest = await hasPackManifest(absoluteVaultPath);
|
|
168
217
|
const changedCount = changedPaths.size;
|
|
169
218
|
const documentCount = Math.max(indexedDocuments.length, 1);
|
|
@@ -176,21 +225,78 @@ export const indexVault = async (vaultPath) => {
|
|
|
176
225
|
changedCount >= 400 ||
|
|
177
226
|
changeRatio >= 0.04 ||
|
|
178
227
|
pendingPackChanges >= 1200;
|
|
228
|
+
let packResult;
|
|
229
|
+
const packReason = !existingPackManifest
|
|
230
|
+
? 'Missing pack manifest'
|
|
231
|
+
: settingsChanged
|
|
232
|
+
? 'Index settings changed'
|
|
233
|
+
: hasDeletes
|
|
234
|
+
? 'Document deletions detected'
|
|
235
|
+
: changedCount >= 400
|
|
236
|
+
? 'Changed file count threshold reached'
|
|
237
|
+
: changeRatio >= 0.04
|
|
238
|
+
? 'Change ratio threshold reached'
|
|
239
|
+
: pendingPackChanges >= 1200
|
|
240
|
+
? 'Pending pack changes threshold reached'
|
|
241
|
+
: 'Pack rebuild skipped';
|
|
179
242
|
if (shouldRebuildPacks) {
|
|
243
|
+
emit('packs', 'start', 'Rebuilding compressed search packs', {
|
|
244
|
+
reason: packReason
|
|
245
|
+
});
|
|
180
246
|
try {
|
|
181
|
-
await buildSearchPacks(absoluteVaultPath, indexedDocuments);
|
|
247
|
+
packResult = await buildSearchPacks(absoluteVaultPath, indexedDocuments);
|
|
248
|
+
emit('packs', 'finish', 'Compressed packs rebuilt', {
|
|
249
|
+
reason: packReason,
|
|
250
|
+
packCount: packResult.packCount,
|
|
251
|
+
recordCount: packResult.recordCount,
|
|
252
|
+
durationMs: packResult.durationMs,
|
|
253
|
+
compressionRatio: packResult.compression.ratio
|
|
254
|
+
});
|
|
182
255
|
}
|
|
183
256
|
catch {
|
|
184
257
|
// Pack generation is best-effort. The JSON index remains the primary path.
|
|
258
|
+
emit('packs', 'skip', 'Pack rebuild failed; continuing with JSON index', {
|
|
259
|
+
reason: packReason
|
|
260
|
+
});
|
|
185
261
|
}
|
|
186
262
|
}
|
|
263
|
+
else {
|
|
264
|
+
emit('packs', 'skip', 'Pack rebuild not required', {
|
|
265
|
+
reason: packReason
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
const packsRebuilt = packResult != null;
|
|
269
|
+
const packResultReason = shouldRebuildPacks && !packsRebuilt ? `${packReason} (failed)` : packReason;
|
|
187
270
|
await writeIndexState(absoluteVaultPath, {
|
|
188
271
|
chunkSize: config.chunkSize,
|
|
189
272
|
embeddingProvider: config.embeddingProvider,
|
|
190
273
|
files: currentSnapshot,
|
|
191
|
-
pendingPackChanges:
|
|
274
|
+
pendingPackChanges: packsRebuilt ? 0 : pendingPackChanges
|
|
275
|
+
});
|
|
276
|
+
const result = {
|
|
277
|
+
...toIndexResult(indexedDocuments),
|
|
278
|
+
elapsedMs: elapsedMs(),
|
|
279
|
+
changedDocumentCount: changedDocumentsByPath.size,
|
|
280
|
+
packs: {
|
|
281
|
+
rebuilt: packsRebuilt,
|
|
282
|
+
reason: packResultReason,
|
|
283
|
+
...(packResult
|
|
284
|
+
? {
|
|
285
|
+
packCount: packResult.packCount,
|
|
286
|
+
recordCount: packResult.recordCount,
|
|
287
|
+
durationMs: packResult.durationMs,
|
|
288
|
+
compression: packResult.compression
|
|
289
|
+
}
|
|
290
|
+
: {})
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
emit('complete', 'finish', 'Indexing complete', {
|
|
294
|
+
documentCount: result.documentCount,
|
|
295
|
+
chunkCount: result.chunkCount,
|
|
296
|
+
linkCount: result.linkCount,
|
|
297
|
+
elapsedMs: result.elapsedMs
|
|
192
298
|
});
|
|
193
|
-
return
|
|
299
|
+
return result;
|
|
194
300
|
}
|
|
195
301
|
finally {
|
|
196
302
|
index.close();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { watch } from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { indexVaultWithOptions } from './index-vault.js';
|
|
3
3
|
import { isBucketVaultPath, resolveVaultPath } from '../infrastructure/file-system-vault.js';
|
|
4
4
|
const shouldIgnore = (filename) => {
|
|
5
5
|
if (!filename) {
|
|
@@ -14,6 +14,27 @@ export const startVaultWatcher = (input) => {
|
|
|
14
14
|
const absoluteVaultPath = resolveVaultPath(input.vaultPath);
|
|
15
15
|
const debounceMs = input.debounceMs ?? 350;
|
|
16
16
|
let timeout = null;
|
|
17
|
+
let running = false;
|
|
18
|
+
let pending = false;
|
|
19
|
+
const runIndex = () => {
|
|
20
|
+
if (running) {
|
|
21
|
+
pending = true;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
running = true;
|
|
25
|
+
indexVaultWithOptions(absoluteVaultPath, {
|
|
26
|
+
onProgress: input.onProgress
|
|
27
|
+
})
|
|
28
|
+
.then(input.onIndex)
|
|
29
|
+
.catch(input.onError)
|
|
30
|
+
.finally(() => {
|
|
31
|
+
running = false;
|
|
32
|
+
if (pending) {
|
|
33
|
+
pending = false;
|
|
34
|
+
runIndex();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
};
|
|
17
38
|
const schedule = (filename) => {
|
|
18
39
|
if (shouldIgnore(filename)) {
|
|
19
40
|
return;
|
|
@@ -22,7 +43,7 @@ export const startVaultWatcher = (input) => {
|
|
|
22
43
|
clearTimeout(timeout);
|
|
23
44
|
}
|
|
24
45
|
timeout = setTimeout(() => {
|
|
25
|
-
|
|
46
|
+
runIndex();
|
|
26
47
|
}, debounceMs);
|
|
27
48
|
};
|
|
28
49
|
const watcher = watch(absoluteVaultPath, { recursive: true }, (_eventType, filename) => {
|
|
@@ -7,7 +7,7 @@ import { addNoteWithMetadata } from '../../application/add-note.js';
|
|
|
7
7
|
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
|
-
import { indexVault } from '../../application/index-vault.js';
|
|
10
|
+
import { indexVault, indexVaultWithOptions } from '../../application/index-vault.js';
|
|
11
11
|
import { migrateVaultContent, planVaultMigration, previewVaultMigration, shouldMigrateDefaultVault } from '../../application/migrate-vault.js';
|
|
12
12
|
import { startServer } from '../../application/start-server.js';
|
|
13
13
|
import { startVaultWatcher } from '../../application/watch-vault.js';
|
|
@@ -37,6 +37,52 @@ const parseScore = (value, fallback) => {
|
|
|
37
37
|
}
|
|
38
38
|
return parsed;
|
|
39
39
|
};
|
|
40
|
+
const formatBytes = (bytes) => {
|
|
41
|
+
if (!Number.isFinite(bytes) || bytes == null) {
|
|
42
|
+
return 'n/a';
|
|
43
|
+
}
|
|
44
|
+
if (bytes < 1024)
|
|
45
|
+
return `${bytes} B`;
|
|
46
|
+
const units = ['KB', 'MB', 'GB', 'TB'];
|
|
47
|
+
let value = bytes / 1024;
|
|
48
|
+
let unitIndex = 0;
|
|
49
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
50
|
+
value /= 1024;
|
|
51
|
+
unitIndex += 1;
|
|
52
|
+
}
|
|
53
|
+
return `${value.toFixed(value >= 10 ? 1 : 2)} ${units[unitIndex]}`;
|
|
54
|
+
};
|
|
55
|
+
const formatMs = (value) => Number.isFinite(value) && value != null ? `${value.toFixed(value >= 100 ? 0 : 1)}ms` : 'n/a';
|
|
56
|
+
const benchEventLabel = (event) => `${event.phase}:${event.status}`;
|
|
57
|
+
const printBenchRealtimeEvent = (json, event) => {
|
|
58
|
+
print(json, {
|
|
59
|
+
event: 'bench-progress',
|
|
60
|
+
...event
|
|
61
|
+
}, () => `[bench] ${benchEventLabel(event)} ${event.message} (${formatMs(event.elapsedMs)})`);
|
|
62
|
+
};
|
|
63
|
+
const printBenchSummary = (json, trigger, vault, result) => {
|
|
64
|
+
print(json, {
|
|
65
|
+
event: 'bench-result',
|
|
66
|
+
trigger,
|
|
67
|
+
vault,
|
|
68
|
+
result
|
|
69
|
+
}, () => {
|
|
70
|
+
const packs = result.packs;
|
|
71
|
+
const compression = packs?.compression;
|
|
72
|
+
const savedPercent = compression && compression.inputBytes > 0
|
|
73
|
+
? `${((1 - compression.ratio) * 100).toFixed(1)}%`
|
|
74
|
+
: 'n/a';
|
|
75
|
+
return [
|
|
76
|
+
`[bench] trigger=${trigger}`,
|
|
77
|
+
`documents=${result.documentCount} chunks=${result.chunkCount} links=${result.linkCount}`,
|
|
78
|
+
`changedDocuments=${result.changedDocumentCount ?? 0} totalElapsed=${formatMs(result.elapsedMs)}`,
|
|
79
|
+
`packsRebuilt=${packs?.rebuilt ? 'yes' : 'no'} reason=${packs?.reason ?? 'n/a'}`,
|
|
80
|
+
packs?.rebuilt
|
|
81
|
+
? `packCount=${packs.packCount ?? 0} packDuration=${formatMs(packs.durationMs)} input=${formatBytes(compression?.inputBytes)} output=${formatBytes(compression?.outputBytes)} saved=${savedPercent}`
|
|
82
|
+
: 'packCompression=n/a'
|
|
83
|
+
].join('\n');
|
|
84
|
+
});
|
|
85
|
+
};
|
|
40
86
|
const spawnDetached = (command, args, envOverrides) => {
|
|
41
87
|
try {
|
|
42
88
|
const child = spawn(command, args, {
|
|
@@ -634,6 +680,58 @@ export const registerWriteCommands = (program) => {
|
|
|
634
680
|
const result = await indexVault(resolved.vault);
|
|
635
681
|
print(options.json, result, () => `Indexed ${result.documentCount} documents, ${result.chunkCount} chunks and ${result.linkCount} links`);
|
|
636
682
|
});
|
|
683
|
+
program
|
|
684
|
+
.command('bench')
|
|
685
|
+
.option('-v, --vault <vault>', 'vault directory')
|
|
686
|
+
.option('-w, --watch', 'watch markdown changes and re-run benchmark in realtime')
|
|
687
|
+
.option('--debounce <ms>', 'watch debounce in milliseconds', '350')
|
|
688
|
+
.option('--json', 'print machine-readable JSON events')
|
|
689
|
+
.description('benchmark indexing in realtime, including compressed pack behavior')
|
|
690
|
+
.action(async (options) => {
|
|
691
|
+
const resolved = await resolveOptions(options);
|
|
692
|
+
const emitProgress = (event) => {
|
|
693
|
+
printBenchRealtimeEvent(options.json, event);
|
|
694
|
+
};
|
|
695
|
+
const printBenchError = (error) => {
|
|
696
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
697
|
+
print(options.json, { event: 'bench-error', message }, () => `[bench] error ${message}`);
|
|
698
|
+
};
|
|
699
|
+
const runAndPrint = async (trigger) => {
|
|
700
|
+
const result = await indexVaultWithOptions(resolved.vault, {
|
|
701
|
+
onProgress: emitProgress
|
|
702
|
+
});
|
|
703
|
+
printBenchSummary(options.json, trigger, resolved.vault, result);
|
|
704
|
+
return result;
|
|
705
|
+
};
|
|
706
|
+
if (!options.watch) {
|
|
707
|
+
await runAndPrint('manual');
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const debounceMs = parsePositiveInteger(options.debounce ?? '350', 350);
|
|
711
|
+
await runAndPrint('manual');
|
|
712
|
+
print(options.json, {
|
|
713
|
+
event: 'bench-watching',
|
|
714
|
+
vault: resolved.vault,
|
|
715
|
+
debounceMs
|
|
716
|
+
}, () => `[bench] watching ${resolved.vault} (debounce=${debounceMs}ms)`);
|
|
717
|
+
const watcher = startVaultWatcher({
|
|
718
|
+
vaultPath: resolved.vault,
|
|
719
|
+
debounceMs,
|
|
720
|
+
onProgress: emitProgress,
|
|
721
|
+
onIndex: (result) => {
|
|
722
|
+
printBenchSummary(options.json, 'watch', resolved.vault, result);
|
|
723
|
+
},
|
|
724
|
+
onError: printBenchError
|
|
725
|
+
});
|
|
726
|
+
await new Promise((resolveSignal) => {
|
|
727
|
+
const shutdown = () => {
|
|
728
|
+
watcher.close();
|
|
729
|
+
resolveSignal();
|
|
730
|
+
};
|
|
731
|
+
process.once('SIGINT', shutdown);
|
|
732
|
+
process.once('SIGTERM', shutdown);
|
|
733
|
+
});
|
|
734
|
+
});
|
|
637
735
|
program
|
|
638
736
|
.command('doctor')
|
|
639
737
|
.option('-v, --vault <vault>', 'vault directory')
|
|
@@ -221,6 +221,7 @@ const sortedPackFiles = async (vaultPath) => {
|
|
|
221
221
|
}
|
|
222
222
|
};
|
|
223
223
|
const writeRowsAsPrivatePacks = async (vaultPath, rows, clearExisting) => {
|
|
224
|
+
const startedAt = process.hrtime.bigint();
|
|
224
225
|
const directory = toPackDirectory(vaultPath);
|
|
225
226
|
await mkdir(directory, { recursive: true });
|
|
226
227
|
if (clearExisting) {
|
|
@@ -231,6 +232,8 @@ const writeRowsAsPrivatePacks = async (vaultPath, rows, clearExisting) => {
|
|
|
231
232
|
}
|
|
232
233
|
const chunks = chunkRows(rows, rowChunkSize);
|
|
233
234
|
const packIndex = [];
|
|
235
|
+
let inputBytes = 0;
|
|
236
|
+
let outputBytes = 0;
|
|
234
237
|
for (let index = 0; index < chunks.length; index += 1) {
|
|
235
238
|
const chunk = chunks[index];
|
|
236
239
|
const fileName = `pack-${String(index + 1).padStart(4, '0')}.blpk`;
|
|
@@ -238,6 +241,8 @@ const writeRowsAsPrivatePacks = async (vaultPath, rows, clearExisting) => {
|
|
|
238
241
|
const compressed = await encodePrivatePack(vaultPath, Buffer.from(serialized, 'utf8'));
|
|
239
242
|
const tokenBloomB64 = bloomToBase64(bloomFromRows(chunk));
|
|
240
243
|
await writeFile(join(directory, fileName), compressed);
|
|
244
|
+
inputBytes += Buffer.byteLength(serialized, 'utf8');
|
|
245
|
+
outputBytes += compressed.byteLength;
|
|
241
246
|
packIndex.push({
|
|
242
247
|
fileName,
|
|
243
248
|
recordCount: chunk.length,
|
|
@@ -253,9 +258,19 @@ const writeRowsAsPrivatePacks = async (vaultPath, rows, clearExisting) => {
|
|
|
253
258
|
format: 'private-v2',
|
|
254
259
|
packIndex
|
|
255
260
|
});
|
|
261
|
+
const durationMs = Number(process.hrtime.bigint() - startedAt) / 1_000_000;
|
|
262
|
+
const safeInput = Math.max(inputBytes, 1);
|
|
263
|
+
const savedBytes = Math.max(inputBytes - outputBytes, 0);
|
|
256
264
|
return {
|
|
257
265
|
packCount: chunks.length,
|
|
258
|
-
recordCount: rows.length
|
|
266
|
+
recordCount: rows.length,
|
|
267
|
+
compression: {
|
|
268
|
+
inputBytes,
|
|
269
|
+
outputBytes,
|
|
270
|
+
ratio: outputBytes / safeInput,
|
|
271
|
+
savedBytes
|
|
272
|
+
},
|
|
273
|
+
durationMs
|
|
259
274
|
};
|
|
260
275
|
};
|
|
261
276
|
const selectCandidatePackFiles = async (vaultPath, tokens, agentId) => {
|
package/docs/AGENT_USAGE.md
CHANGED
|
@@ -477,6 +477,24 @@ This scans Markdown files and rebuilds:
|
|
|
477
477
|
- links
|
|
478
478
|
- full-text search records
|
|
479
479
|
|
|
480
|
+
### Benchmark Indexing Realtime
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
blink bench --vault ./vault
|
|
484
|
+
blink bench --vault ./vault --watch
|
|
485
|
+
blink bench --vault ./vault --watch --debounce 500
|
|
486
|
+
blink bench --vault ./vault --json
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
`bench` runs indexing with realtime phase events and prints a run summary with:
|
|
490
|
+
|
|
491
|
+
- indexed totals (documents, chunks, links)
|
|
492
|
+
- elapsed time and changed document count
|
|
493
|
+
- pack rebuild status and reason
|
|
494
|
+
- pack compression metrics (`inputBytes`, `outputBytes`, ratio/saved percentage)
|
|
495
|
+
|
|
496
|
+
Use `--watch` for continuous benchmark runs while editing notes. Watch mode is supported only for local filesystem vaults.
|
|
497
|
+
|
|
480
498
|
### Search Knowledge
|
|
481
499
|
|
|
482
500
|
```bash
|
package/package.json
CHANGED