sensemaking 0.21.1 → 0.22.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/README.md +1 -1
- package/dist/cjs/chunk/group.js +13 -9
- package/dist/cjs/chunk/group.js.map +1 -1
- package/dist/cjs/commands/search.js +1 -1
- package/dist/cjs/commands/search.js.map +1 -1
- package/dist/cjs/features/embed.js +9 -1
- package/dist/cjs/features/embed.js.map +1 -1
- package/dist/cjs/features/links.js +30 -8
- package/dist/cjs/features/links.js.map +1 -1
- package/dist/cjs/features/sections.js +13 -5
- package/dist/cjs/features/sections.js.map +1 -1
- package/dist/cjs/features/tags.js +7 -4
- package/dist/cjs/features/tags.js.map +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/scan/pool.d.cts +1 -0
- package/dist/cjs/scan/pool.d.ts +1 -0
- package/dist/cjs/scan/pool.js +2 -1
- package/dist/cjs/scan/pool.js.map +1 -1
- package/dist/cjs/scan/reparse.d.cts +1 -0
- package/dist/cjs/scan/reparse.d.ts +1 -0
- package/dist/cjs/scan/reparse.js +6 -3
- package/dist/cjs/scan/reparse.js.map +1 -1
- package/dist/cjs/store/builder.d.cts +2 -0
- package/dist/cjs/store/builder.d.ts +2 -0
- package/dist/cjs/store/builder.js.map +1 -1
- package/dist/cjs/store/duckdb/connection.js +120 -0
- package/dist/cjs/store/duckdb/connection.js.map +1 -1
- package/dist/cjs/store/duckdb/reconcile.js +11 -113
- package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
- package/dist/cjs/store/index.d.cts +1 -5
- package/dist/cjs/store/index.d.ts +1 -5
- package/dist/cjs/store/index.js +9 -46
- package/dist/cjs/store/index.js.map +1 -1
- package/dist/cjs/store/lock-wait.d.cts +2 -0
- package/dist/cjs/store/lock-wait.d.ts +2 -0
- package/dist/cjs/store/lock-wait.js +51 -0
- package/dist/cjs/store/lock-wait.js.map +1 -0
- package/dist/cjs/store/open.d.cts +2 -4
- package/dist/cjs/store/open.d.ts +2 -4
- package/dist/cjs/store/open.js +29 -66
- package/dist/cjs/store/open.js.map +1 -1
- package/dist/cjs/store/reconcile.d.cts +2 -0
- package/dist/cjs/store/reconcile.d.ts +2 -0
- package/dist/cjs/store/reconcile.js +384 -255
- package/dist/cjs/store/reconcile.js.map +1 -1
- package/dist/cjs/store/shared.d.cts +1 -0
- package/dist/cjs/store/shared.d.ts +1 -0
- package/dist/cjs/store/shared.js +41 -0
- package/dist/cjs/store/shared.js.map +1 -1
- package/dist/cjs/store/sqlite/lexical.js +1 -1
- package/dist/cjs/store/sqlite/lexical.js.map +1 -1
- package/dist/cjs/store/sqlite/store.js +0 -1
- package/dist/cjs/store/sqlite/store.js.map +1 -1
- package/dist/cjs/store/stages.d.cts +18 -0
- package/dist/cjs/store/stages.d.ts +18 -0
- package/dist/cjs/store/stages.js +372 -0
- package/dist/cjs/store/stages.js.map +1 -0
- package/dist/cjs/store/turso/store.js.map +1 -1
- package/dist/cjs/store/types.d.cts +2 -2
- package/dist/cjs/store/types.d.ts +2 -2
- package/dist/cjs/store/types.js +0 -1
- package/dist/cjs/store/types.js.map +1 -1
- package/dist/cjs/text/segment.js +7 -5
- package/dist/cjs/text/segment.js.map +1 -1
- package/dist/cjs/watch.js +162 -107
- package/dist/cjs/watch.js.map +1 -1
- package/dist/cjs/workers/parse.d.cts +1 -0
- package/dist/cjs/workers/parse.d.ts +1 -0
- package/dist/cjs/workers/parse.js +4 -1
- package/dist/cjs/workers/parse.js.map +1 -1
- package/dist/esm/chunk/group.js +13 -9
- package/dist/esm/chunk/group.js.map +1 -1
- package/dist/esm/commands/search.js +1 -1
- package/dist/esm/commands/search.js.map +1 -1
- package/dist/esm/features/embed.js +9 -1
- package/dist/esm/features/embed.js.map +1 -1
- package/dist/esm/features/links.js +26 -9
- package/dist/esm/features/links.js.map +1 -1
- package/dist/esm/features/sections.js +13 -3
- package/dist/esm/features/sections.js.map +1 -1
- package/dist/esm/features/tags.js +7 -2
- package/dist/esm/features/tags.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/scan/pool.d.ts +1 -0
- package/dist/esm/scan/pool.js +2 -1
- package/dist/esm/scan/pool.js.map +1 -1
- package/dist/esm/scan/reparse.d.ts +1 -0
- package/dist/esm/scan/reparse.js +5 -2
- package/dist/esm/scan/reparse.js.map +1 -1
- package/dist/esm/store/builder.d.ts +2 -0
- package/dist/esm/store/builder.js.map +1 -1
- package/dist/esm/store/duckdb/connection.js +35 -0
- package/dist/esm/store/duckdb/connection.js.map +1 -1
- package/dist/esm/store/duckdb/reconcile.js +12 -37
- package/dist/esm/store/duckdb/reconcile.js.map +1 -1
- package/dist/esm/store/index.d.ts +1 -5
- package/dist/esm/store/index.js +6 -28
- package/dist/esm/store/index.js.map +1 -1
- package/dist/esm/store/lock-wait.d.ts +2 -0
- package/dist/esm/store/lock-wait.js +36 -0
- package/dist/esm/store/lock-wait.js.map +1 -0
- package/dist/esm/store/open.d.ts +2 -4
- package/dist/esm/store/open.js +17 -32
- package/dist/esm/store/open.js.map +1 -1
- package/dist/esm/store/reconcile.d.ts +2 -0
- package/dist/esm/store/reconcile.js +71 -44
- package/dist/esm/store/reconcile.js.map +1 -1
- package/dist/esm/store/shared.d.ts +1 -0
- package/dist/esm/store/shared.js +8 -0
- package/dist/esm/store/shared.js.map +1 -1
- package/dist/esm/store/sqlite/lexical.js +1 -1
- package/dist/esm/store/sqlite/lexical.js.map +1 -1
- package/dist/esm/store/sqlite/store.js +0 -1
- package/dist/esm/store/sqlite/store.js.map +1 -1
- package/dist/esm/store/stages.d.ts +18 -0
- package/dist/esm/store/stages.js +63 -0
- package/dist/esm/store/stages.js.map +1 -0
- package/dist/esm/store/turso/store.js +1 -1
- package/dist/esm/store/turso/store.js.map +1 -1
- package/dist/esm/store/types.d.ts +2 -2
- package/dist/esm/store/types.js +0 -1
- package/dist/esm/store/types.js.map +1 -1
- package/dist/esm/text/segment.js +7 -5
- package/dist/esm/text/segment.js.map +1 -1
- package/dist/esm/watch.js +63 -38
- package/dist/esm/watch.js.map +1 -1
- package/dist/esm/workers/parse.d.ts +1 -0
- package/dist/esm/workers/parse.js +4 -1
- package/dist/esm/workers/parse.js.map +1 -1
- package/package.json +1 -1
- package/skills/sense/SKILL.md +1 -1
- package/skills/sense-setup/SKILL.md +1 -1
package/dist/cjs/watch.js
CHANGED
|
@@ -144,93 +144,186 @@ var HEARTBEAT_INTERVAL_MS = 5000;
|
|
|
144
144
|
var STALE_HEARTBEAT_MS = 15000;
|
|
145
145
|
function runWatch(_0) {
|
|
146
146
|
return _async_to_generator(function(cfg) {
|
|
147
|
-
var opts, _opts_onEvent, _opts_debounceMs, _opts_heartbeatIntervalMs, onEvent, debounceMs, heartbeatIntervalMs,
|
|
147
|
+
var opts, _opts_onEvent, _opts_debounceMs, _opts_heartbeatIntervalMs, onEvent, debounceMs, heartbeatIntervalMs, baseDir, _ref, initialStore, dbPath, initialWarnings, initialParsed, existingHeartbeat, age, initialTotal, _tmp, stopping, running, queuedAlways, tick, startCycle, requestCycle, debounceTimer, scheduleReconcile, watcher, heartbeatTimer;
|
|
148
148
|
var _arguments = arguments;
|
|
149
149
|
return _ts_generator(this, function(_state) {
|
|
150
150
|
switch(_state.label){
|
|
151
151
|
case 0:
|
|
152
152
|
opts = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
|
|
153
|
-
(0, _indexts1.requireWatchConcurrency)(cfg);
|
|
154
153
|
onEvent = (_opts_onEvent = opts.onEvent) !== null && _opts_onEvent !== void 0 ? _opts_onEvent : function() {};
|
|
155
154
|
debounceMs = (_opts_debounceMs = opts.debounceMs) !== null && _opts_debounceMs !== void 0 ? _opts_debounceMs : DEBOUNCE_MS;
|
|
156
155
|
heartbeatIntervalMs = (_opts_heartbeatIntervalMs = opts.heartbeatIntervalMs) !== null && _opts_heartbeatIntervalMs !== void 0 ? _opts_heartbeatIntervalMs : HEARTBEAT_INTERVAL_MS;
|
|
156
|
+
baseDir = cfg.baseDir;
|
|
157
157
|
return [
|
|
158
158
|
4,
|
|
159
|
-
(0, _indexts1.
|
|
159
|
+
(0, _indexts1.openStore)(cfg)
|
|
160
160
|
];
|
|
161
161
|
case 1:
|
|
162
|
-
_ref = _state.sent(),
|
|
163
|
-
baseDir = cfg.baseDir;
|
|
162
|
+
_ref = _state.sent(), initialStore = _ref.store, dbPath = _ref.dbPath, initialWarnings = _ref.warnings, initialParsed = _ref.parsed;
|
|
164
163
|
return [
|
|
165
164
|
4,
|
|
166
|
-
(0, _indexts1.getMeta)(
|
|
165
|
+
(0, _indexts1.getMeta)(initialStore, 'watch_heartbeat')
|
|
167
166
|
];
|
|
168
167
|
case 2:
|
|
169
168
|
existingHeartbeat = _state.sent();
|
|
170
169
|
if (!(existingHeartbeat && !opts.force)) return [
|
|
171
170
|
3,
|
|
172
|
-
|
|
171
|
+
4
|
|
173
172
|
];
|
|
174
173
|
age = Date.now() - Date.parse(existingHeartbeat);
|
|
175
174
|
if (!(age >= 0 && age < STALE_HEARTBEAT_MS)) return [
|
|
176
175
|
3,
|
|
177
|
-
|
|
176
|
+
4
|
|
178
177
|
];
|
|
179
178
|
return [
|
|
180
179
|
4,
|
|
181
|
-
|
|
180
|
+
initialStore.close()
|
|
182
181
|
];
|
|
183
182
|
case 3:
|
|
184
|
-
_state.sent();
|
|
185
|
-
return [
|
|
186
|
-
4,
|
|
187
|
-
store.close()
|
|
188
|
-
];
|
|
189
|
-
case 4:
|
|
190
183
|
_state.sent();
|
|
191
184
|
throw new _errorsts.SenseError('WATCH_ACTIVE', "another watcher appears active (heartbeat ".concat(Math.round(age / 1000), "s ago); use --force to override"));
|
|
192
|
-
case
|
|
193
|
-
onEvent({
|
|
194
|
-
type: 'started',
|
|
195
|
-
baseDir: baseDir,
|
|
196
|
-
dbPath: dbPath
|
|
197
|
-
});
|
|
185
|
+
case 4:
|
|
198
186
|
if (!(initialWarnings.length > 0 || initialParsed > 0)) return [
|
|
199
187
|
3,
|
|
200
|
-
|
|
188
|
+
6
|
|
201
189
|
];
|
|
202
|
-
_tmp = {
|
|
203
|
-
type: 'reconciled',
|
|
204
|
-
parsed: initialParsed
|
|
205
|
-
};
|
|
206
190
|
return [
|
|
207
191
|
4,
|
|
208
|
-
(0, _indexts1.docCount)(
|
|
192
|
+
(0, _indexts1.docCount)(initialStore)
|
|
193
|
+
];
|
|
194
|
+
case 5:
|
|
195
|
+
_tmp = _state.sent();
|
|
196
|
+
return [
|
|
197
|
+
3,
|
|
198
|
+
7
|
|
209
199
|
];
|
|
210
200
|
case 6:
|
|
211
|
-
|
|
212
|
-
(_tmp.total = _state.sent(), _tmp.warnings = initialWarnings, _tmp)
|
|
213
|
-
]);
|
|
201
|
+
_tmp = 0;
|
|
214
202
|
_state.label = 7;
|
|
215
203
|
case 7:
|
|
204
|
+
initialTotal = _tmp;
|
|
205
|
+
return [
|
|
206
|
+
4,
|
|
207
|
+
(0, _indexts1.setMeta)(initialStore, 'watch_heartbeat', new Date().toISOString())
|
|
208
|
+
];
|
|
209
|
+
case 8:
|
|
210
|
+
_state.sent();
|
|
211
|
+
return [
|
|
212
|
+
4,
|
|
213
|
+
(0, _indexts1.setMeta)(initialStore, 'watch_pid', String(process.pid))
|
|
214
|
+
];
|
|
215
|
+
case 9:
|
|
216
|
+
_state.sent();
|
|
217
|
+
return [
|
|
218
|
+
4,
|
|
219
|
+
initialStore.close()
|
|
220
|
+
];
|
|
221
|
+
case 10:
|
|
222
|
+
_state.sent();
|
|
223
|
+
onEvent({
|
|
224
|
+
type: 'started',
|
|
225
|
+
baseDir: baseDir,
|
|
226
|
+
dbPath: dbPath
|
|
227
|
+
});
|
|
228
|
+
if (initialWarnings.length > 0 || initialParsed > 0) {
|
|
229
|
+
onEvent({
|
|
230
|
+
type: 'reconciled',
|
|
231
|
+
parsed: initialParsed,
|
|
232
|
+
total: initialTotal,
|
|
233
|
+
warnings: initialWarnings
|
|
234
|
+
});
|
|
235
|
+
}
|
|
216
236
|
stopping = false;
|
|
217
|
-
|
|
237
|
+
// At most one open+reconcile+close cycle runs at a time; a trigger that arrives mid-cycle is
|
|
238
|
+
// coalesced into a single rerun instead of opening a second, overlapping connection.
|
|
239
|
+
running = null;
|
|
240
|
+
queuedAlways = null;
|
|
241
|
+
tick = function tick(alwaysEmit) {
|
|
218
242
|
return _async_to_generator(function() {
|
|
243
|
+
var _ref, store, parsed, warnings, _tmp, err;
|
|
219
244
|
return _ts_generator(this, function(_state) {
|
|
220
245
|
switch(_state.label){
|
|
221
246
|
case 0:
|
|
247
|
+
_state.trys.push([
|
|
248
|
+
0,
|
|
249
|
+
10,
|
|
250
|
+
,
|
|
251
|
+
11
|
|
252
|
+
]);
|
|
222
253
|
return [
|
|
223
254
|
4,
|
|
224
|
-
(0, _indexts1.
|
|
255
|
+
(0, _indexts1.openStore)(cfg)
|
|
225
256
|
];
|
|
226
257
|
case 1:
|
|
258
|
+
_ref = _state.sent(), store = _ref.store, parsed = _ref.parsed, warnings = _ref.warnings;
|
|
259
|
+
_state.label = 2;
|
|
260
|
+
case 2:
|
|
261
|
+
_state.trys.push([
|
|
262
|
+
2,
|
|
263
|
+
,
|
|
264
|
+
7,
|
|
265
|
+
9
|
|
266
|
+
]);
|
|
267
|
+
return [
|
|
268
|
+
4,
|
|
269
|
+
(0, _indexts1.setMeta)(store, 'watch_heartbeat', new Date().toISOString())
|
|
270
|
+
];
|
|
271
|
+
case 3:
|
|
227
272
|
_state.sent();
|
|
228
273
|
return [
|
|
229
274
|
4,
|
|
230
275
|
(0, _indexts1.setMeta)(store, 'watch_pid', String(process.pid))
|
|
231
276
|
];
|
|
232
|
-
case
|
|
277
|
+
case 4:
|
|
233
278
|
_state.sent();
|
|
279
|
+
if (!(alwaysEmit || parsed > 0 || warnings.length > 0)) return [
|
|
280
|
+
3,
|
|
281
|
+
6
|
|
282
|
+
];
|
|
283
|
+
_tmp = {
|
|
284
|
+
type: 'reconciled',
|
|
285
|
+
parsed: parsed
|
|
286
|
+
};
|
|
287
|
+
return [
|
|
288
|
+
4,
|
|
289
|
+
(0, _indexts1.docCount)(store)
|
|
290
|
+
];
|
|
291
|
+
case 5:
|
|
292
|
+
onEvent.apply(void 0, [
|
|
293
|
+
(_tmp.total = _state.sent(), _tmp.warnings = warnings, _tmp)
|
|
294
|
+
]);
|
|
295
|
+
_state.label = 6;
|
|
296
|
+
case 6:
|
|
297
|
+
return [
|
|
298
|
+
3,
|
|
299
|
+
9
|
|
300
|
+
];
|
|
301
|
+
case 7:
|
|
302
|
+
return [
|
|
303
|
+
4,
|
|
304
|
+
store.close()
|
|
305
|
+
];
|
|
306
|
+
case 8:
|
|
307
|
+
_state.sent();
|
|
308
|
+
return [
|
|
309
|
+
7
|
|
310
|
+
];
|
|
311
|
+
case 9:
|
|
312
|
+
return [
|
|
313
|
+
3,
|
|
314
|
+
11
|
|
315
|
+
];
|
|
316
|
+
case 10:
|
|
317
|
+
err = _state.sent();
|
|
318
|
+
onEvent({
|
|
319
|
+
type: 'reconcile-error',
|
|
320
|
+
message: err.message
|
|
321
|
+
});
|
|
322
|
+
return [
|
|
323
|
+
3,
|
|
324
|
+
11
|
|
325
|
+
];
|
|
326
|
+
case 11:
|
|
234
327
|
return [
|
|
235
328
|
2
|
|
236
329
|
];
|
|
@@ -238,77 +331,30 @@ function runWatch(_0) {
|
|
|
238
331
|
});
|
|
239
332
|
})();
|
|
240
333
|
};
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
334
|
+
startCycle = function startCycle1(alwaysEmit) {
|
|
335
|
+
running = tick(alwaysEmit).then(function() {
|
|
336
|
+
running = null;
|
|
337
|
+
if (queuedAlways !== null && !stopping) {
|
|
338
|
+
var again = queuedAlways;
|
|
339
|
+
queuedAlways = null;
|
|
340
|
+
startCycle(again);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
requestCycle = function requestCycle(alwaysEmit) {
|
|
345
|
+
if (stopping) return;
|
|
346
|
+
if (running) {
|
|
347
|
+
queuedAlways = queuedAlways || alwaysEmit;
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
startCycle(alwaysEmit);
|
|
351
|
+
};
|
|
247
352
|
debounceTimer = null;
|
|
248
|
-
// A reconcile that has already started owns the store, and on a bulk reparse a live worker
|
|
249
|
-
// pool as well. Shutdown waits on this rather than closing the connection underneath it.
|
|
250
|
-
inFlight = null;
|
|
251
353
|
scheduleReconcile = function scheduleReconcile() {
|
|
252
354
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
253
355
|
debounceTimer = setTimeout(function() {
|
|
254
356
|
debounceTimer = null;
|
|
255
|
-
|
|
256
|
-
return _async_to_generator(function() {
|
|
257
|
-
var _ref, parsed, warnings, _tmp, err;
|
|
258
|
-
return _ts_generator(this, function(_state) {
|
|
259
|
-
switch(_state.label){
|
|
260
|
-
case 0:
|
|
261
|
-
_state.trys.push([
|
|
262
|
-
0,
|
|
263
|
-
3,
|
|
264
|
-
4,
|
|
265
|
-
5
|
|
266
|
-
]);
|
|
267
|
-
return [
|
|
268
|
-
4,
|
|
269
|
-
builder.build()
|
|
270
|
-
];
|
|
271
|
-
case 1:
|
|
272
|
-
_ref = _state.sent(), parsed = _ref.parsed, warnings = _ref.warnings;
|
|
273
|
-
_tmp = {
|
|
274
|
-
type: 'reconciled',
|
|
275
|
-
parsed: parsed
|
|
276
|
-
};
|
|
277
|
-
return [
|
|
278
|
-
4,
|
|
279
|
-
(0, _indexts1.docCount)(store)
|
|
280
|
-
];
|
|
281
|
-
case 2:
|
|
282
|
-
onEvent.apply(void 0, [
|
|
283
|
-
(_tmp.total = _state.sent(), _tmp.warnings = warnings, _tmp)
|
|
284
|
-
]);
|
|
285
|
-
return [
|
|
286
|
-
3,
|
|
287
|
-
5
|
|
288
|
-
];
|
|
289
|
-
case 3:
|
|
290
|
-
err = _state.sent();
|
|
291
|
-
onEvent({
|
|
292
|
-
type: 'reconcile-error',
|
|
293
|
-
message: err.message
|
|
294
|
-
});
|
|
295
|
-
return [
|
|
296
|
-
3,
|
|
297
|
-
5
|
|
298
|
-
];
|
|
299
|
-
case 4:
|
|
300
|
-
inFlight = null;
|
|
301
|
-
return [
|
|
302
|
-
7
|
|
303
|
-
];
|
|
304
|
-
case 5:
|
|
305
|
-
return [
|
|
306
|
-
2
|
|
307
|
-
];
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
})();
|
|
311
|
-
}();
|
|
357
|
+
requestCycle(true);
|
|
312
358
|
}, debounceMs);
|
|
313
359
|
};
|
|
314
360
|
// Ignore our own state dir, or the heartbeat write would retrigger itself forever. An event with
|
|
@@ -319,7 +365,16 @@ function runWatch(_0) {
|
|
|
319
365
|
if (typeof filename === 'string' && filename.startsWith(_indexts.STATE_DIR)) return;
|
|
320
366
|
scheduleReconcile();
|
|
321
367
|
});
|
|
322
|
-
heartbeatTimer = setInterval((0, _guardedtickts.guardedTick)(
|
|
368
|
+
heartbeatTimer = setInterval((0, _guardedtickts.guardedTick)(function() {
|
|
369
|
+
return _async_to_generator(function() {
|
|
370
|
+
return _ts_generator(this, function(_state) {
|
|
371
|
+
return [
|
|
372
|
+
2,
|
|
373
|
+
requestCycle(false)
|
|
374
|
+
];
|
|
375
|
+
});
|
|
376
|
+
})();
|
|
377
|
+
}, function() {
|
|
323
378
|
return stopping;
|
|
324
379
|
}), heartbeatIntervalMs);
|
|
325
380
|
return [
|
|
@@ -330,7 +385,7 @@ function runWatch(_0) {
|
|
|
330
385
|
// unregistered here too so a second runWatch call in the same process starts clean.
|
|
331
386
|
var shutdown = function shutdown1() {
|
|
332
387
|
return _async_to_generator(function() {
|
|
333
|
-
var _opts_signal;
|
|
388
|
+
var _opts_signal, _ref, finalStore;
|
|
334
389
|
return _ts_generator(this, function(_state) {
|
|
335
390
|
switch(_state.label){
|
|
336
391
|
case 0:
|
|
@@ -346,31 +401,31 @@ function runWatch(_0) {
|
|
|
346
401
|
watcher.close();
|
|
347
402
|
return [
|
|
348
403
|
4,
|
|
349
|
-
|
|
404
|
+
running
|
|
350
405
|
];
|
|
351
406
|
case 1:
|
|
352
407
|
_state.sent();
|
|
353
408
|
return [
|
|
354
409
|
4,
|
|
355
|
-
|
|
410
|
+
(0, _indexts1.openStore)(cfg)
|
|
356
411
|
];
|
|
357
412
|
case 2:
|
|
358
|
-
_state.sent();
|
|
413
|
+
_ref = _state.sent(), finalStore = _ref.store;
|
|
359
414
|
return [
|
|
360
415
|
4,
|
|
361
|
-
(0, _indexts1.setMeta)(
|
|
416
|
+
(0, _indexts1.setMeta)(finalStore, 'watch_heartbeat', null)
|
|
362
417
|
];
|
|
363
418
|
case 3:
|
|
364
419
|
_state.sent();
|
|
365
420
|
return [
|
|
366
421
|
4,
|
|
367
|
-
(0, _indexts1.setMeta)(
|
|
422
|
+
(0, _indexts1.setMeta)(finalStore, 'watch_pid', null)
|
|
368
423
|
];
|
|
369
424
|
case 4:
|
|
370
425
|
_state.sent();
|
|
371
426
|
return [
|
|
372
427
|
4,
|
|
373
|
-
|
|
428
|
+
finalStore.close()
|
|
374
429
|
];
|
|
375
430
|
case 5:
|
|
376
431
|
_state.sent();
|
package/dist/cjs/watch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config/index.ts';\nimport { STATE_DIR } from './config/index.ts';\nimport { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { docCount, getMeta, openStoreForWatch, requireWatchConcurrency, setMeta } from './store/index.ts';\n\n// Watch is a cache pre-warmer, not a correctness mechanism: open() always reconciles anyway, so any fs event just triggers a debounced full reconcile.\nconst DEBOUNCE_MS = 200;\nconst HEARTBEAT_INTERVAL_MS = 5000;\nconst STALE_HEARTBEAT_MS = 15000;\n\nexport type WatchEvent = { type: 'started'; baseDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n // Aborting runs the same shutdown path as SIGINT/SIGTERM.\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\n// Runs in the foreground until SIGINT/SIGTERM/signal abort. Throws WATCH_ACTIVE if another watcher's heartbeat is still fresh and --force wasn't given.\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n requireWatchConcurrency(cfg);\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const heartbeatIntervalMs = opts.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS;\n const { store, builder, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStoreForWatch(cfg);\n const baseDir = cfg.baseDir;\n\n const existingHeartbeat = await getMeta(store, 'watch_heartbeat');\n if (existingHeartbeat && !opts.force) {\n const age = Date.now() - Date.parse(existingHeartbeat);\n if (age >= 0 && age < STALE_HEARTBEAT_MS) {\n await builder.close();\n await store.close();\n throw new SenseError('WATCH_ACTIVE', `another watcher appears active (heartbeat ${Math.round(age / 1000)}s ago); use --force to override`);\n }\n }\n\n onEvent({ type: 'started', baseDir, dbPath });\n if (initialWarnings.length > 0 || initialParsed > 0) {\n onEvent({ type: 'reconciled', parsed: initialParsed, total: await docCount(store), warnings: initialWarnings });\n }\n\n let stopping = false;\n const touchHeartbeat = async () => {\n await setMeta(store, 'watch_heartbeat', new Date().toISOString());\n await setMeta(store, 'watch_pid', String(process.pid));\n };\n await touchHeartbeat();\n\n let debounceTimer: NodeJS.Timeout | null = null;\n // A reconcile that has already started owns the store, and on a bulk reparse a live worker\n // pool as well. Shutdown waits on this rather than closing the connection underneath it.\n let inFlight: Promise<void> | null = null;\n const scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n inFlight = (async () => {\n try {\n const { parsed, warnings } = await builder.build();\n onEvent({ type: 'reconciled', parsed, total: await docCount(store), warnings });\n } catch (err) {\n onEvent({ type: 'reconcile-error', message: (err as Error).message });\n } finally {\n inFlight = null;\n }\n })();\n }, debounceMs);\n };\n\n // Ignore our own state dir, or the heartbeat write would retrigger itself forever. An event with\n // an unresolvable filename (null, which fs.watch delivers under load) reconciles: parsing nothing costs less than missing a real edit.\n const watcher = fsWatch(baseDir, { recursive: true }, (_event, filename) => {\n if (typeof filename === 'string' && filename.startsWith(STATE_DIR)) return;\n scheduleReconcile();\n });\n const heartbeatTimer = setInterval(\n guardedTick(touchHeartbeat, () => stopping),\n heartbeatIntervalMs\n );\n\n return new Promise<void>((resolveShutdown) => {\n // SIGINT/SIGTERM and an aborted signal all run this same path exactly once; each is\n // unregistered here too so a second runWatch call in the same process starts clean.\n const shutdown = async () => {\n if (stopping) return;\n stopping = true;\n process.off('SIGINT', shutdown);\n process.off('SIGTERM', shutdown);\n opts.signal?.removeEventListener('abort', shutdown);\n clearInterval(heartbeatTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n await inFlight;\n await builder.close();\n await setMeta(store, 'watch_heartbeat', null);\n await setMeta(store, 'watch_pid', null);\n await store.close();\n resolveShutdown();\n };\n process.once('SIGINT', shutdown);\n process.once('SIGTERM', shutdown);\n if (opts.signal?.aborted) shutdown();\n else opts.signal?.addEventListener('abort', shutdown, { once: true });\n });\n}\n"],"names":["runWatch","DEBOUNCE_MS","HEARTBEAT_INTERVAL_MS","STALE_HEARTBEAT_MS","cfg","opts","onEvent","debounceMs","heartbeatIntervalMs","store","builder","dbPath","initialWarnings","initialParsed","baseDir","existingHeartbeat","age","stopping","touchHeartbeat","debounceTimer","inFlight","scheduleReconcile","watcher","heartbeatTimer","requireWatchConcurrency","openStoreForWatch","warnings","parsed","getMeta","force","Date","now","parse","close","SenseError","Math","round","type","length","docCount","total","setMeta","toISOString","String","process","pid","clearTimeout","setTimeout","err","build","message","fsWatch","recursive","_event","filename","startsWith","STATE_DIR","setInterval","guardedTick","Promise","resolveShutdown","shutdown","off","signal","removeEventListener","clearInterval","once","aborted","addEventListener"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config/index.ts';\nimport { STATE_DIR } from './config/index.ts';\nimport { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { docCount, getMeta, openStore, setMeta } from './store/index.ts';\n\n// Watch is a cache pre-warmer, not a correctness mechanism: open() always reconciles anyway, so any fs event just triggers a debounced full reconcile.\nconst DEBOUNCE_MS = 200;\nconst HEARTBEAT_INTERVAL_MS = 5000;\nconst STALE_HEARTBEAT_MS = 15000;\n\nexport type WatchEvent = { type: 'started'; baseDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n // Aborting runs the same shutdown path as SIGINT/SIGTERM.\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\n// Runs in the foreground until SIGINT/SIGTERM/signal abort. duckdb/turso hand the cache file to\n// one process at a time, so every cycle opens, reconciles, writes the heartbeat/pid rows, and closes.\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const heartbeatIntervalMs = opts.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS;\n const baseDir = cfg.baseDir;\n\n const { store: initialStore, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStore(cfg);\n const existingHeartbeat = await getMeta(initialStore, 'watch_heartbeat');\n if (existingHeartbeat && !opts.force) {\n const age = Date.now() - Date.parse(existingHeartbeat);\n if (age >= 0 && age < STALE_HEARTBEAT_MS) {\n await initialStore.close();\n throw new SenseError('WATCH_ACTIVE', `another watcher appears active (heartbeat ${Math.round(age / 1000)}s ago); use --force to override`);\n }\n }\n\n // total is read before close: an event fires only once nothing is held open again.\n const initialTotal = initialWarnings.length > 0 || initialParsed > 0 ? await docCount(initialStore) : 0;\n await setMeta(initialStore, 'watch_heartbeat', new Date().toISOString());\n await setMeta(initialStore, 'watch_pid', String(process.pid));\n await initialStore.close();\n\n onEvent({ type: 'started', baseDir, dbPath });\n if (initialWarnings.length > 0 || initialParsed > 0) {\n onEvent({ type: 'reconciled', parsed: initialParsed, total: initialTotal, warnings: initialWarnings });\n }\n\n let stopping = false;\n // At most one open+reconcile+close cycle runs at a time; a trigger that arrives mid-cycle is\n // coalesced into a single rerun instead of opening a second, overlapping connection.\n let running: Promise<void> | null = null;\n let queuedAlways: boolean | null = null;\n\n const tick = async (alwaysEmit: boolean): Promise<void> => {\n try {\n const { store, parsed, warnings } = await openStore(cfg);\n try {\n await setMeta(store, 'watch_heartbeat', new Date().toISOString());\n await setMeta(store, 'watch_pid', String(process.pid));\n if (alwaysEmit || parsed > 0 || warnings.length > 0) {\n onEvent({ type: 'reconciled', parsed, total: await docCount(store), warnings });\n }\n } finally {\n await store.close();\n }\n } catch (err) {\n onEvent({ type: 'reconcile-error', message: (err as Error).message });\n }\n };\n\n const startCycle = (alwaysEmit: boolean) => {\n running = tick(alwaysEmit).then(() => {\n running = null;\n if (queuedAlways !== null && !stopping) {\n const again = queuedAlways;\n queuedAlways = null;\n startCycle(again);\n }\n });\n };\n\n const requestCycle = (alwaysEmit: boolean) => {\n if (stopping) return;\n if (running) {\n queuedAlways = queuedAlways || alwaysEmit;\n return;\n }\n startCycle(alwaysEmit);\n };\n\n let debounceTimer: NodeJS.Timeout | null = null;\n const scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n requestCycle(true);\n }, debounceMs);\n };\n\n // Ignore our own state dir, or the heartbeat write would retrigger itself forever. An event with\n // an unresolvable filename (null, which fs.watch delivers under load) reconciles: parsing nothing costs less than missing a real edit.\n const watcher = fsWatch(baseDir, { recursive: true }, (_event, filename) => {\n if (typeof filename === 'string' && filename.startsWith(STATE_DIR)) return;\n scheduleReconcile();\n });\n const heartbeatTimer = setInterval(\n guardedTick(\n async () => requestCycle(false),\n () => stopping\n ),\n heartbeatIntervalMs\n );\n\n return new Promise<void>((resolveShutdown) => {\n // SIGINT/SIGTERM and an aborted signal all run this same path exactly once; each is\n // unregistered here too so a second runWatch call in the same process starts clean.\n const shutdown = async () => {\n if (stopping) return;\n stopping = true;\n process.off('SIGINT', shutdown);\n process.off('SIGTERM', shutdown);\n opts.signal?.removeEventListener('abort', shutdown);\n clearInterval(heartbeatTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n await running;\n const { store: finalStore } = await openStore(cfg);\n await setMeta(finalStore, 'watch_heartbeat', null);\n await setMeta(finalStore, 'watch_pid', null);\n await finalStore.close();\n resolveShutdown();\n };\n process.once('SIGINT', shutdown);\n process.once('SIGTERM', shutdown);\n if (opts.signal?.aborted) shutdown();\n else opts.signal?.addEventListener('abort', shutdown, { once: true });\n });\n}\n"],"names":["runWatch","DEBOUNCE_MS","HEARTBEAT_INTERVAL_MS","STALE_HEARTBEAT_MS","cfg","opts","onEvent","debounceMs","heartbeatIntervalMs","baseDir","initialStore","dbPath","initialWarnings","initialParsed","existingHeartbeat","age","initialTotal","stopping","running","queuedAlways","tick","startCycle","requestCycle","debounceTimer","scheduleReconcile","watcher","heartbeatTimer","openStore","store","warnings","parsed","getMeta","force","Date","now","parse","close","SenseError","Math","round","length","docCount","setMeta","toISOString","String","process","pid","type","total","alwaysEmit","err","message","then","again","clearTimeout","setTimeout","fsWatch","recursive","_event","filename","startsWith","STATE_DIR","setInterval","guardedTick","Promise","resolveShutdown","shutdown","finalStore","off","signal","removeEventListener","clearInterval","once","aborted","addEventListener"],"mappings":"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"}
|
|
@@ -27,11 +27,14 @@ var selected = _indexts.FEATURES.filter(function(feature) {
|
|
|
27
27
|
});
|
|
28
28
|
function parseTask(file) {
|
|
29
29
|
try {
|
|
30
|
+
var start = process.hrtime.bigint();
|
|
30
31
|
var _parseFile = (0, _indexts1.parseFile)(file, (0, _reparsets.featuresForFile)(selected, cfg, file), cfg), doc = _parseFile.doc, warnings = _parseFile.warnings;
|
|
32
|
+
var parseMs = Number(process.hrtime.bigint() - start) / 1e6;
|
|
31
33
|
return {
|
|
32
34
|
ok: true,
|
|
33
35
|
doc: doc,
|
|
34
|
-
warnings: warnings
|
|
36
|
+
warnings: warnings,
|
|
37
|
+
parseMs: parseMs
|
|
35
38
|
};
|
|
36
39
|
} catch (err) {
|
|
37
40
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/workers/parse.ts"],"sourcesContent":["import Tinypool from 'tinypool';\nimport type { Config, FeatureName } from '../config/index.ts';\nimport { FEATURES } from '../features/index.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\nimport { parseFile } from '../scan/index.ts';\nimport type { FileStat } from '../scan/list.ts';\nimport { featuresForFile } from '../scan/reparse.ts';\nimport type { WorkerErrorPayload } from '../scan/worker-error.ts';\nimport { serializeError } from '../scan/worker-error.ts';\n\n// Constant for the whole dispatch, so it crosses once per worker instead of once per task. A\n// Feature carries closures and cannot cross the thread boundary; its name can, and the registry here resolves it back.\nexport interface ParseWorkerData {\n cfg: Config;\n featureNames: FeatureName[];\n}\n\n// tinypool's task, in and out. The task itself is one FileStat. Result carries only what\n// parseFile already returns -- extracted text and per-feature values, never the mdast tree.\nexport type ParseTask = FileStat;\n\nexport type ParseTaskResult = { ok: true; doc: ParsedDoc; warnings: string[] } | { ok: false; error: WorkerErrorPayload };\n\n// Read once per worker, not per task.\nconst { cfg, featureNames } = Tinypool.workerData as ParseWorkerData;\n// Filtering the registry (rather than mapping the names) keeps registry order, which is the\n// order `extracted` keys land in on the serial path.\nconst selected = FEATURES.filter((feature) => featureNames.includes(feature.name));\n\nexport default function parseTask(file: ParseTask): ParseTaskResult {\n try {\n const { doc, warnings } = parseFile(file, featuresForFile(selected, cfg, file), cfg);\n return { ok: true, doc, warnings };\n } catch (err) {\n return { ok: false, error: serializeError(err) };\n }\n}\n"],"names":["parseTask","Tinypool","workerData","cfg","featureNames","selected","FEATURES","filter","feature","includes","name","file","parseFile","featuresForFile","doc","warnings","ok","err","error","serializeError"],"mappings":";;;;+
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/workers/parse.ts"],"sourcesContent":["import Tinypool from 'tinypool';\nimport type { Config, FeatureName } from '../config/index.ts';\nimport { FEATURES } from '../features/index.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\nimport { parseFile } from '../scan/index.ts';\nimport type { FileStat } from '../scan/list.ts';\nimport { featuresForFile } from '../scan/reparse.ts';\nimport type { WorkerErrorPayload } from '../scan/worker-error.ts';\nimport { serializeError } from '../scan/worker-error.ts';\n\n// Constant for the whole dispatch, so it crosses once per worker instead of once per task. A\n// Feature carries closures and cannot cross the thread boundary; its name can, and the registry here resolves it back.\nexport interface ParseWorkerData {\n cfg: Config;\n featureNames: FeatureName[];\n}\n\n// tinypool's task, in and out. The task itself is one FileStat. Result carries only what\n// parseFile already returns -- extracted text and per-feature values, never the mdast tree.\nexport type ParseTask = FileStat;\n\n// parseMs is the worker's own hrtime for parseFile, excluding dispatch and the return clone --\n// what separates it from the pool's dispatch-to-drain `parse` stage.\nexport type ParseTaskResult = { ok: true; doc: ParsedDoc; warnings: string[]; parseMs: number } | { ok: false; error: WorkerErrorPayload };\n\n// Read once per worker, not per task.\nconst { cfg, featureNames } = Tinypool.workerData as ParseWorkerData;\n// Filtering the registry (rather than mapping the names) keeps registry order, which is the\n// order `extracted` keys land in on the serial path.\nconst selected = FEATURES.filter((feature) => featureNames.includes(feature.name));\n\nexport default function parseTask(file: ParseTask): ParseTaskResult {\n try {\n const start = process.hrtime.bigint();\n const { doc, warnings } = parseFile(file, featuresForFile(selected, cfg, file), cfg);\n const parseMs = Number(process.hrtime.bigint() - start) / 1e6;\n return { ok: true, doc, warnings, parseMs };\n } catch (err) {\n return { ok: false, error: serializeError(err) };\n }\n}\n"],"names":["parseTask","Tinypool","workerData","cfg","featureNames","selected","FEATURES","filter","feature","includes","name","file","start","process","hrtime","bigint","parseFile","featuresForFile","doc","warnings","parseMs","Number","ok","err","error","serializeError"],"mappings":";;;;+BA+BA;;;eAAwBA;;;+DA/BH;uBAEI;wBAEC;yBAEM;6BAED;;;;;;AAiB/B,sCAAsC;AACtC,IAA8BC,uBAAAA,iBAAQ,CAACC,UAAU,EAAzCC,MAAsBF,qBAAtBE,KAAKC,eAAiBH,qBAAjBG;AACb,4FAA4F;AAC5F,qDAAqD;AACrD,IAAMC,WAAWC,iBAAQ,CAACC,MAAM,CAAC,SAACC;WAAYJ,aAAaK,QAAQ,CAACD,QAAQE,IAAI;;AAEjE,SAASV,UAAUW,IAAe;IAC/C,IAAI;QACF,IAAMC,QAAQC,QAAQC,MAAM,CAACC,MAAM;QACnC,IAA0BC,aAAAA,IAAAA,mBAAS,EAACL,MAAMM,IAAAA,0BAAe,EAACZ,UAAUF,KAAKQ,OAAOR,MAAxEe,MAAkBF,WAAlBE,KAAKC,WAAaH,WAAbG;QACb,IAAMC,UAAUC,OAAOR,QAAQC,MAAM,CAACC,MAAM,KAAKH,SAAS;QAC1D,OAAO;YAAEU,IAAI;YAAMJ,KAAAA;YAAKC,UAAAA;YAAUC,SAAAA;QAAQ;IAC5C,EAAE,OAAOG,KAAK;QACZ,OAAO;YAAED,IAAI;YAAOE,OAAOC,IAAAA,6BAAc,EAACF;QAAK;IACjD;AACF"}
|
package/dist/esm/chunk/group.js
CHANGED
|
@@ -37,13 +37,17 @@ function finalize(parts) {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
const NEWLINE_TOKENS = estimateTokens('\n');
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
granularity
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
// Built on first use and kept: each construction is ~3.5 ms, and only an oversize block is ever
|
|
41
|
+
// split, so no command pays for a segmenter it never reaches.
|
|
42
|
+
const SEGMENTERS = new Map();
|
|
43
|
+
function segmentsOf(text, granularity) {
|
|
44
|
+
let segmenter = SEGMENTERS.get(granularity);
|
|
45
|
+
if (!segmenter) {
|
|
46
|
+
segmenter = new Intl.Segmenter(undefined, {
|
|
47
|
+
granularity
|
|
48
|
+
});
|
|
49
|
+
SEGMENTERS.set(granularity, segmenter);
|
|
50
|
+
}
|
|
47
51
|
return Array.from(segmenter.segment(text), (s)=>s.segment);
|
|
48
52
|
}
|
|
49
53
|
// Greedily packs segments (already contiguous, tiling the source text with no gaps) into groups
|
|
@@ -68,7 +72,7 @@ function pack(segments, working) {
|
|
|
68
72
|
// Line-split alone can't shrink a lone dense line (the CJK case): falls back to sentence then
|
|
69
73
|
// word boundaries (Intl.Segmenter, the same grapheme-safe engine as segment.ts), mode-agnostic on `text`.
|
|
70
74
|
function splitLineText(text, working) {
|
|
71
|
-
const sentences = segmentsOf(text,
|
|
75
|
+
const sentences = segmentsOf(text, 'sentence');
|
|
72
76
|
const out = [];
|
|
73
77
|
let current = '';
|
|
74
78
|
let tokens = 0;
|
|
@@ -83,7 +87,7 @@ function splitLineText(text, working) {
|
|
|
83
87
|
const sentenceTokens = estimateTokens(sentence);
|
|
84
88
|
if (sentenceTokens > working) {
|
|
85
89
|
flush();
|
|
86
|
-
out.push(...pack(segmentsOf(sentence,
|
|
90
|
+
out.push(...pack(segmentsOf(sentence, 'word'), working));
|
|
87
91
|
continue;
|
|
88
92
|
}
|
|
89
93
|
if (current.length > 0 && tokens + sentenceTokens > working) flush();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/chunk/group.ts"],"sourcesContent":["import { extractText } from './extract.ts';\nimport { parse } from './parse.ts';\nimport { DEFAULT_TARGET_TOKENS, estimateTokens } from './tokens.ts';\nimport type { Block, BlockType, Chunk, ChunkOptions } from './types.ts';\n\nconst PGC_GROUP_SIZE = 2;\nconst OVERSIZE_TRIGGER_MULTIPLE = 2;\n\ninterface ResolvedOptions {\n targetTokens: number;\n text: 'extracted' | 'raw';\n}\n\nfunction resolveOptions(opts?: ChunkOptions): ResolvedOptions {\n return {\n targetTokens: opts?.targetTokens ?? DEFAULT_TARGET_TOKENS,\n text: opts?.text ?? 'raw',\n };\n}\n\n// A heading of any depth ends the current scope and starts a new one (D1); the heading block\n// itself is carried into the new scope, where it joins that scope's first group (F7/F10).\nfunction splitScopes(blocks: Block[]): Block[][] {\n const scopes: Block[][] = [];\n let current: Block[] = [];\n for (const block of blocks) {\n if (block.type === 'heading' && current.length > 0) {\n scopes.push(current);\n current = [];\n }\n current.push(block);\n }\n if (current.length > 0) scopes.push(current);\n return scopes;\n}\n\ninterface Part {\n startLine: number;\n endLine: number;\n text: string;\n // True for a sub-line split piece: its text is already the final slice, not the whole line --\n // group()'s extent-based raw re-slice must not touch it (siblings share startLine === endLine).\n final?: boolean;\n}\n\nfunction finalize(parts: Part[]): (Chunk & { final?: boolean }) | undefined {\n if (parts.length === 0) return undefined;\n const first = parts[0];\n const last = parts[parts.length - 1];\n return { startLine: first.startLine, endLine: last.endLine, text: parts.map((p) => p.text).join('\\n'), final: parts.some((p) => p.final) };\n}\n\nconst NEWLINE_TOKENS = estimateTokens('\\n');\nconst SENTENCE_SEGMENTER = new Intl.Segmenter(undefined, { granularity: 'sentence' });\nconst WORD_SEGMENTER = new Intl.Segmenter(undefined, { granularity: 'word' });\n\nfunction segmentsOf(text: string, segmenter: Intl.Segmenter): string[] {\n return Array.from(segmenter.segment(text), (s) => s.segment);\n}\n\n// Greedily packs segments (already contiguous, tiling the source text with no gaps) into groups\n// of at most `working` estimated tokens; a lone segment over `working` still stands alone.\nfunction pack(segments: string[], working: number): string[] {\n const groups: string[] = [];\n let current = '';\n let tokens = 0;\n for (const segment of segments) {\n const segmentTokens = estimateTokens(segment);\n if (current.length > 0 && tokens + segmentTokens > working) {\n groups.push(current);\n current = '';\n tokens = 0;\n }\n current += segment;\n tokens += segmentTokens;\n }\n if (current.length > 0) groups.push(current);\n return groups;\n}\n\n// Line-split alone can't shrink a lone dense line (the CJK case): falls back to sentence then\n// word boundaries (Intl.Segmenter, the same grapheme-safe engine as segment.ts), mode-agnostic on `text`.\nfunction splitLineText(text: string, working: number): string[] {\n const sentences = segmentsOf(text, SENTENCE_SEGMENTER);\n const out: string[] = [];\n let current = '';\n let tokens = 0;\n const flush = () => {\n if (current.length > 0) {\n out.push(current);\n current = '';\n tokens = 0;\n }\n };\n for (const sentence of sentences) {\n const sentenceTokens = estimateTokens(sentence);\n if (sentenceTokens > working) {\n flush();\n out.push(...pack(segmentsOf(sentence, WORD_SEGMENTER), working));\n continue;\n }\n if (current.length > 0 && tokens + sentenceTokens > working) flush();\n current += sentence;\n tokens += sentenceTokens;\n }\n flush();\n return out;\n}\n\nconst ATOMIC_TYPES: ReadonlySet<BlockType> = new Set(['code', 'table', 'list']);\n\n// Atomic (code/table/list) pieces are always a raw line slice: re-parsing a table's later pieces\n// without their header/delimiter rows would demote them to paragraph text.\nfunction piece(pieceLines: string[], startLine: number, endLine: number, blockType: BlockType, textMode: 'extracted' | 'raw'): Part {\n const text =\n ATOMIC_TYPES.has(blockType) || textMode === 'raw'\n ? pieceLines.join('\\n')\n : parse(pieceLines.join('\\n'))\n .map((b) => extractText(b.node))\n .join('\\n');\n return { startLine, endLine, text };\n}\n\n// A one-line piece over working can't shrink via another line-boundary pass (rule 5's gap), so it\n// splits at sentence/word boundaries instead; `final` stops group() re-deriving its text by extent (F5).\nfunction finalizePiece(pieceLines: string[], startLine: number, endLine: number, working: number, blockType: BlockType, textMode: 'extracted' | 'raw'): Part[] {\n const p = piece(pieceLines, startLine, endLine, blockType, textMode);\n if (pieceLines.length === 1 && estimateTokens(p.text) > working) {\n return splitLineText(p.text, working).map((text) => ({ startLine, endLine, text, final: true }));\n }\n return [p];\n}\n\n// A block over 2x working size splits at line boundaries into pieces each <= working size, never\n// mid-line. `seed`: pending tokens (e.g. a heading) the first piece must join, checked against the limit.\nfunction splitOversizeBlock(lines: string[], startLine: number, endLine: number, working: number, blockType: BlockType, textMode: 'extracted' | 'raw', seed = 0): Part[] {\n const pieces: Part[] = [];\n let pieceLines: string[] = [];\n let pieceStart = startLine;\n let tokens = seed;\n for (let line = startLine; line <= endLine; line++) {\n const lineText = lines[line - 1];\n const sep = pieceLines.length > 0 || tokens > 0 ? NEWLINE_TOKENS : 0;\n const lineTokens = estimateTokens(lineText);\n if (pieceLines.length > 0 && tokens + sep + lineTokens > working) {\n pieces.push(...finalizePiece(pieceLines, pieceStart, line - 1, working, blockType, textMode));\n pieceLines = [];\n tokens = 0;\n pieceStart = line;\n pieceLines.push(lineText);\n tokens += lineTokens;\n continue;\n }\n pieceLines.push(lineText);\n tokens += sep + lineTokens;\n }\n if (pieceLines.length > 0) pieces.push(...finalizePiece(pieceLines, pieceStart, endLine, working, blockType, textMode));\n return pieces;\n}\n\n// One heading scope's groups (D1): a heading opens the first group, and an oversize block\n// (rule 5, including an oversize heading) splits into pieces that each close their own group.\nfunction groupScope(scopeBlocks: Block[], lines: string[], resolved: ResolvedOptions): (Chunk & { final?: boolean })[] {\n const working = resolved.targetTokens;\n const trigger = working * OVERSIZE_TRIGGER_MULTIPLE;\n const finished: (Chunk & { final?: boolean })[] = [];\n let parts: Part[] = [];\n let paragraphCount = 0;\n let tokens = 0;\n\n function close(): void {\n const group = finalize(parts);\n if (group) finished.push(group);\n parts = [];\n paragraphCount = 0;\n tokens = 0;\n }\n\n // tokens tracks the active text mode's own estimate (a newline between parts costs\n // NEWLINE_TOKENS too), so packing decisions size the text the chunk will actually ship as.\n function addPart(text: string, sizeText: string, startLine: number, endLine: number): void {\n tokens += (parts.length > 0 ? NEWLINE_TOKENS : 0) + estimateTokens(sizeText);\n parts.push({ startLine, endLine, text });\n }\n\n for (const block of scopeBlocks) {\n const raw = lines.slice(block.startLine - 1, block.endLine).join('\\n');\n const blockTokens = estimateTokens(raw);\n\n if (blockTokens > trigger) {\n const seed = parts.length > 0 ? tokens : 0;\n for (const p of splitOversizeBlock(lines, block.startLine, block.endLine, working, block.type, resolved.text, seed)) {\n parts.push(p);\n close();\n }\n continue;\n }\n\n const extracted = extractText(block.node);\n const sizeText = resolved.text === 'raw' ? raw : extracted;\n\n if (block.type === 'heading') {\n addPart(extracted, sizeText, block.startLine, block.endLine);\n continue;\n }\n\n // The 2x-working invariant holds even under pgc's paper-faithful 2-paragraph pairing --\n // close first if the pair about to form would cross it.\n const pairOversize = parts.length > 0 && tokens + NEWLINE_TOKENS + blockTokens > trigger;\n if (pairOversize) close();\n\n addPart(extracted, sizeText, block.startLine, block.endLine);\n paragraphCount++;\n\n if (paragraphCount >= PGC_GROUP_SIZE) close();\n }\n close();\n\n return finished;\n}\n\n// Groups already-parsed blocks per opts (D1/D3), against the same body the blocks were parsed\n// from (line lookups for oversize splitting).\nexport function group(blocks: Block[], body: string, opts?: ChunkOptions): Chunk[] {\n const resolved = resolveOptions(opts);\n const lines = body.split('\\n');\n const chunks: (Chunk & { final?: boolean })[] = [];\n for (const scope of splitScopes(blocks)) chunks.push(...groupScope(scope, lines, resolved));\n // 'raw': the chunk's own source lines verbatim, replacing the flavor-resolved join above (D9).\n // A `final` chunk already carries its own slice's raw text; re-slicing by extent would return the whole shared line.\n const texted =\n resolved.text === 'raw'\n ? chunks.map((c) =>\n c.final\n ? c\n : {\n ...c,\n text: lines\n .slice(c.startLine - 1, c.endLine)\n .join('\\n')\n .trim(),\n }\n )\n : chunks;\n // A group can be all-blank (flavor-stripped to nothing, or a raw slice of pure syntax); it never produces a chunk.\n return texted.filter((c) => c.text.trim().length > 0).map((c) => ({ startLine: c.startLine, endLine: c.endLine, text: c.text }));\n}\n"],"names":["extractText","parse","DEFAULT_TARGET_TOKENS","estimateTokens","PGC_GROUP_SIZE","OVERSIZE_TRIGGER_MULTIPLE","resolveOptions","opts","targetTokens","text","splitScopes","blocks","scopes","current","block","type","length","push","finalize","parts","undefined","first","last","startLine","endLine","map","p","join","final","some","NEWLINE_TOKENS","SENTENCE_SEGMENTER","Intl","Segmenter","granularity","WORD_SEGMENTER","segmentsOf","segmenter","Array","from","segment","s","pack","segments","working","groups","tokens","segmentTokens","splitLineText","sentences","out","flush","sentence","sentenceTokens","ATOMIC_TYPES","Set","piece","pieceLines","blockType","textMode","has","b","node","finalizePiece","splitOversizeBlock","lines","seed","pieces","pieceStart","line","lineText","sep","lineTokens","groupScope","scopeBlocks","resolved","trigger","finished","paragraphCount","close","group","addPart","sizeText","raw","slice","blockTokens","extracted","pairOversize","body","split","chunks","scope","texted","c","trim","filter"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/chunk/group.ts"],"sourcesContent":["import { extractText } from './extract.ts';\nimport { parse } from './parse.ts';\nimport { DEFAULT_TARGET_TOKENS, estimateTokens } from './tokens.ts';\nimport type { Block, BlockType, Chunk, ChunkOptions } from './types.ts';\n\nconst PGC_GROUP_SIZE = 2;\nconst OVERSIZE_TRIGGER_MULTIPLE = 2;\n\ninterface ResolvedOptions {\n targetTokens: number;\n text: 'extracted' | 'raw';\n}\n\nfunction resolveOptions(opts?: ChunkOptions): ResolvedOptions {\n return {\n targetTokens: opts?.targetTokens ?? DEFAULT_TARGET_TOKENS,\n text: opts?.text ?? 'raw',\n };\n}\n\n// A heading of any depth ends the current scope and starts a new one (D1); the heading block\n// itself is carried into the new scope, where it joins that scope's first group (F7/F10).\nfunction splitScopes(blocks: Block[]): Block[][] {\n const scopes: Block[][] = [];\n let current: Block[] = [];\n for (const block of blocks) {\n if (block.type === 'heading' && current.length > 0) {\n scopes.push(current);\n current = [];\n }\n current.push(block);\n }\n if (current.length > 0) scopes.push(current);\n return scopes;\n}\n\ninterface Part {\n startLine: number;\n endLine: number;\n text: string;\n // True for a sub-line split piece: its text is already the final slice, not the whole line --\n // group()'s extent-based raw re-slice must not touch it (siblings share startLine === endLine).\n final?: boolean;\n}\n\nfunction finalize(parts: Part[]): (Chunk & { final?: boolean }) | undefined {\n if (parts.length === 0) return undefined;\n const first = parts[0];\n const last = parts[parts.length - 1];\n return { startLine: first.startLine, endLine: last.endLine, text: parts.map((p) => p.text).join('\\n'), final: parts.some((p) => p.final) };\n}\n\nconst NEWLINE_TOKENS = estimateTokens('\\n');\n// Built on first use and kept: each construction is ~3.5 ms, and only an oversize block is ever\n// split, so no command pays for a segmenter it never reaches.\nconst SEGMENTERS = new Map<string, Intl.Segmenter>();\n\nfunction segmentsOf(text: string, granularity: 'sentence' | 'word'): string[] {\n let segmenter = SEGMENTERS.get(granularity);\n if (!segmenter) {\n segmenter = new Intl.Segmenter(undefined, { granularity });\n SEGMENTERS.set(granularity, segmenter);\n }\n return Array.from(segmenter.segment(text), (s) => s.segment);\n}\n\n// Greedily packs segments (already contiguous, tiling the source text with no gaps) into groups\n// of at most `working` estimated tokens; a lone segment over `working` still stands alone.\nfunction pack(segments: string[], working: number): string[] {\n const groups: string[] = [];\n let current = '';\n let tokens = 0;\n for (const segment of segments) {\n const segmentTokens = estimateTokens(segment);\n if (current.length > 0 && tokens + segmentTokens > working) {\n groups.push(current);\n current = '';\n tokens = 0;\n }\n current += segment;\n tokens += segmentTokens;\n }\n if (current.length > 0) groups.push(current);\n return groups;\n}\n\n// Line-split alone can't shrink a lone dense line (the CJK case): falls back to sentence then\n// word boundaries (Intl.Segmenter, the same grapheme-safe engine as segment.ts), mode-agnostic on `text`.\nfunction splitLineText(text: string, working: number): string[] {\n const sentences = segmentsOf(text, 'sentence');\n const out: string[] = [];\n let current = '';\n let tokens = 0;\n const flush = () => {\n if (current.length > 0) {\n out.push(current);\n current = '';\n tokens = 0;\n }\n };\n for (const sentence of sentences) {\n const sentenceTokens = estimateTokens(sentence);\n if (sentenceTokens > working) {\n flush();\n out.push(...pack(segmentsOf(sentence, 'word'), working));\n continue;\n }\n if (current.length > 0 && tokens + sentenceTokens > working) flush();\n current += sentence;\n tokens += sentenceTokens;\n }\n flush();\n return out;\n}\n\nconst ATOMIC_TYPES: ReadonlySet<BlockType> = new Set(['code', 'table', 'list']);\n\n// Atomic (code/table/list) pieces are always a raw line slice: re-parsing a table's later pieces\n// without their header/delimiter rows would demote them to paragraph text.\nfunction piece(pieceLines: string[], startLine: number, endLine: number, blockType: BlockType, textMode: 'extracted' | 'raw'): Part {\n const text =\n ATOMIC_TYPES.has(blockType) || textMode === 'raw'\n ? pieceLines.join('\\n')\n : parse(pieceLines.join('\\n'))\n .map((b) => extractText(b.node))\n .join('\\n');\n return { startLine, endLine, text };\n}\n\n// A one-line piece over working can't shrink via another line-boundary pass (rule 5's gap), so it\n// splits at sentence/word boundaries instead; `final` stops group() re-deriving its text by extent (F5).\nfunction finalizePiece(pieceLines: string[], startLine: number, endLine: number, working: number, blockType: BlockType, textMode: 'extracted' | 'raw'): Part[] {\n const p = piece(pieceLines, startLine, endLine, blockType, textMode);\n if (pieceLines.length === 1 && estimateTokens(p.text) > working) {\n return splitLineText(p.text, working).map((text) => ({ startLine, endLine, text, final: true }));\n }\n return [p];\n}\n\n// A block over 2x working size splits at line boundaries into pieces each <= working size, never\n// mid-line. `seed`: pending tokens (e.g. a heading) the first piece must join, checked against the limit.\nfunction splitOversizeBlock(lines: string[], startLine: number, endLine: number, working: number, blockType: BlockType, textMode: 'extracted' | 'raw', seed = 0): Part[] {\n const pieces: Part[] = [];\n let pieceLines: string[] = [];\n let pieceStart = startLine;\n let tokens = seed;\n for (let line = startLine; line <= endLine; line++) {\n const lineText = lines[line - 1];\n const sep = pieceLines.length > 0 || tokens > 0 ? NEWLINE_TOKENS : 0;\n const lineTokens = estimateTokens(lineText);\n if (pieceLines.length > 0 && tokens + sep + lineTokens > working) {\n pieces.push(...finalizePiece(pieceLines, pieceStart, line - 1, working, blockType, textMode));\n pieceLines = [];\n tokens = 0;\n pieceStart = line;\n pieceLines.push(lineText);\n tokens += lineTokens;\n continue;\n }\n pieceLines.push(lineText);\n tokens += sep + lineTokens;\n }\n if (pieceLines.length > 0) pieces.push(...finalizePiece(pieceLines, pieceStart, endLine, working, blockType, textMode));\n return pieces;\n}\n\n// One heading scope's groups (D1): a heading opens the first group, and an oversize block\n// (rule 5, including an oversize heading) splits into pieces that each close their own group.\nfunction groupScope(scopeBlocks: Block[], lines: string[], resolved: ResolvedOptions): (Chunk & { final?: boolean })[] {\n const working = resolved.targetTokens;\n const trigger = working * OVERSIZE_TRIGGER_MULTIPLE;\n const finished: (Chunk & { final?: boolean })[] = [];\n let parts: Part[] = [];\n let paragraphCount = 0;\n let tokens = 0;\n\n function close(): void {\n const group = finalize(parts);\n if (group) finished.push(group);\n parts = [];\n paragraphCount = 0;\n tokens = 0;\n }\n\n // tokens tracks the active text mode's own estimate (a newline between parts costs\n // NEWLINE_TOKENS too), so packing decisions size the text the chunk will actually ship as.\n function addPart(text: string, sizeText: string, startLine: number, endLine: number): void {\n tokens += (parts.length > 0 ? NEWLINE_TOKENS : 0) + estimateTokens(sizeText);\n parts.push({ startLine, endLine, text });\n }\n\n for (const block of scopeBlocks) {\n const raw = lines.slice(block.startLine - 1, block.endLine).join('\\n');\n const blockTokens = estimateTokens(raw);\n\n if (blockTokens > trigger) {\n const seed = parts.length > 0 ? tokens : 0;\n for (const p of splitOversizeBlock(lines, block.startLine, block.endLine, working, block.type, resolved.text, seed)) {\n parts.push(p);\n close();\n }\n continue;\n }\n\n const extracted = extractText(block.node);\n const sizeText = resolved.text === 'raw' ? raw : extracted;\n\n if (block.type === 'heading') {\n addPart(extracted, sizeText, block.startLine, block.endLine);\n continue;\n }\n\n // The 2x-working invariant holds even under pgc's paper-faithful 2-paragraph pairing --\n // close first if the pair about to form would cross it.\n const pairOversize = parts.length > 0 && tokens + NEWLINE_TOKENS + blockTokens > trigger;\n if (pairOversize) close();\n\n addPart(extracted, sizeText, block.startLine, block.endLine);\n paragraphCount++;\n\n if (paragraphCount >= PGC_GROUP_SIZE) close();\n }\n close();\n\n return finished;\n}\n\n// Groups already-parsed blocks per opts (D1/D3), against the same body the blocks were parsed\n// from (line lookups for oversize splitting).\nexport function group(blocks: Block[], body: string, opts?: ChunkOptions): Chunk[] {\n const resolved = resolveOptions(opts);\n const lines = body.split('\\n');\n const chunks: (Chunk & { final?: boolean })[] = [];\n for (const scope of splitScopes(blocks)) chunks.push(...groupScope(scope, lines, resolved));\n // 'raw': the chunk's own source lines verbatim, replacing the flavor-resolved join above (D9).\n // A `final` chunk already carries its own slice's raw text; re-slicing by extent would return the whole shared line.\n const texted =\n resolved.text === 'raw'\n ? chunks.map((c) =>\n c.final\n ? c\n : {\n ...c,\n text: lines\n .slice(c.startLine - 1, c.endLine)\n .join('\\n')\n .trim(),\n }\n )\n : chunks;\n // A group can be all-blank (flavor-stripped to nothing, or a raw slice of pure syntax); it never produces a chunk.\n return texted.filter((c) => c.text.trim().length > 0).map((c) => ({ startLine: c.startLine, endLine: c.endLine, text: c.text }));\n}\n"],"names":["extractText","parse","DEFAULT_TARGET_TOKENS","estimateTokens","PGC_GROUP_SIZE","OVERSIZE_TRIGGER_MULTIPLE","resolveOptions","opts","targetTokens","text","splitScopes","blocks","scopes","current","block","type","length","push","finalize","parts","undefined","first","last","startLine","endLine","map","p","join","final","some","NEWLINE_TOKENS","SEGMENTERS","Map","segmentsOf","granularity","segmenter","get","Intl","Segmenter","set","Array","from","segment","s","pack","segments","working","groups","tokens","segmentTokens","splitLineText","sentences","out","flush","sentence","sentenceTokens","ATOMIC_TYPES","Set","piece","pieceLines","blockType","textMode","has","b","node","finalizePiece","splitOversizeBlock","lines","seed","pieces","pieceStart","line","lineText","sep","lineTokens","groupScope","scopeBlocks","resolved","trigger","finished","paragraphCount","close","group","addPart","sizeText","raw","slice","blockTokens","extracted","pairOversize","body","split","chunks","scope","texted","c","trim","filter"],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ import { searchError } from '../output/search-error.js';
|
|
|
7
7
|
import { materializeScope, narrowByWhere, rawScope, scopeHasEmbeddings } from './scope.js';
|
|
8
8
|
import { linksCandidates, vectorsCandidates, wordsCandidates } from './signals.js';
|
|
9
9
|
// snippet() re-tokenizes each candidate doc, superlinearly: ~10s for one 1MB doc
|
|
10
|
-
// (benchmark/reports/2026-08-23-hub-release-battery.md). Past this bound, rows get the JS excerpt.
|
|
10
|
+
// (benchmark/reports/2026-08-23-0.13.2-hub-release-battery.md). Past this bound, rows get the JS excerpt.
|
|
11
11
|
const EXCERPT_WINDOW = 160;
|
|
12
12
|
// Bare terms from an FTS5 query string: strips operators/quoting so the oversized-doc excerpt
|
|
13
13
|
// scan matches the same words the query matched on, not FTS5 syntax.
|