@autorag/librarian 2.13.5 → 2.13.6

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.
@@ -952,7 +952,7 @@ export declare class AutoRAGAgent {
952
952
  */
953
953
  startWatchRefresh(options?: AutoRAGWatchRefreshOptions): AutoRAGWatchRefreshHandle;
954
954
  private defaultWatcherFactory;
955
- syncParsedMirrors(force?: boolean, options?: Pick<ParsedMirrorSyncOptions, "signal" | "onProgress" | "retrySkipped">): Promise<ParsedMirrorSyncResult>;
955
+ syncParsedMirrors(force?: boolean, options?: Pick<ParsedMirrorSyncOptions, "signal" | "onProgress" | "retrySkipped" | "onBatch" | "batchSize">): Promise<ParsedMirrorSyncResult>;
956
956
  /**
957
957
  * Lightweight stat-only staleness scan used when `refresh` is called with
958
958
  * methods that exclude parsed mirrors (e.g. only `datasources` or `jikji`).
package/dist/cli/index.js CHANGED
@@ -24141,195 +24141,225 @@ async function syncWithRegistry(options, registry, isolated) {
24141
24141
  });
24142
24142
  const pending = new Set(plan.decisions.filter((decision) => decision.action === "process" || decision.action === "retry").map((decision) => decision.key));
24143
24143
  const deferred = new Map(plan.decisions.filter((decision) => decision.deferred === true).map((decision) => [decision.key, decision]));
24144
- for (const decision of plan.decisions) {
24145
- if (decision.action !== "delete")
24146
- continue;
24147
- const path = pathsByKey.get(decision.key);
24148
- await session.delete(decision.key);
24149
- if (path === undefined)
24150
- continue;
24151
- if (previous.entries[path])
24152
- deleted += 1;
24153
- if (!currentPaths.has(path))
24154
- diagnostics.push({
24155
- code: "deleted-mirror",
24156
- severity: "info",
24157
- message: "A previously indexed document is gone; its parsed mirror was removed.",
24158
- source: path
24159
- });
24160
- }
24161
- await gate.classify(current.filter((entry) => pending.has(mirrorStageKey(entry.virtualPath))).map((entry) => entry.sourcePath));
24162
- for (const [position, entry] of current.entries()) {
24163
- options.onProgress?.({
24164
- processed: position,
24165
- total: current.length,
24166
- written,
24167
- deleted,
24168
- skipped,
24169
- currentVirtualPath: entry.virtualPath
24170
- });
24171
- if (options.signal?.aborted)
24172
- throw abortReason(options.signal);
24173
- const omission = omitted.get(entry.virtualPath);
24174
- if (omission !== undefined) {
24175
- if (pending.has(mirrorStageKey(entry.virtualPath))) {
24176
- if (previous.entries[entry.virtualPath])
24177
- deleted += 1;
24178
- await session.skip(mirrorStageKey(entry.virtualPath), versions.get(entry.virtualPath), omission.reason);
24179
- }
24180
- skipped += 1;
24181
- if (omission.diagnostic)
24182
- diagnostics.push({ ...omission.diagnostic, source: entry.virtualPath });
24183
- continue;
24184
- }
24185
- const deferredFailure = deferred.get(mirrorStageKey(entry.virtualPath));
24186
- if (deferredFailure !== undefined) {
24187
- skipped += 1;
24188
- const code = deferredFailure.error === "source-unreadable" ? "source-unreadable" : "parser-failed";
24189
- diagnostics.push({
24190
- code,
24191
- severity: "warning",
24192
- source: entry.virtualPath,
24193
- message: code === "parser-failed" ? PARSER_FAILURE_MESSAGE : "The source could not be read; retry is deferred until the source or settings change, or an explicit retry is requested."
24194
- });
24195
- continue;
24196
- }
24197
- const parser = registry.getForVirtualPath(entry.virtualPath);
24198
- const outputPath = parsedOutputPath(options.root, entry.virtualPath);
24199
- const cloudDecision = pending.has(mirrorStageKey(entry.virtualPath)) ? await gate.decide(entry.sourcePath, entry.sizeBytes, dirname22(entry.sourcePath)) : { kind: "local" };
24200
- if (cloudDecision.kind === "skip") {
24201
- skipped += 1;
24202
- diagnostics.push({
24203
- code: "cloud-placeholder-skipped",
24204
- severity: "info",
24205
- message: `The file is not downloaded and its provider has no verified release path, so it was not read; its last indexed copy was kept. Make it available offline to index it.`,
24206
- source: entry.virtualPath,
24207
- ...cloudDecision.provider === undefined ? {} : { reason: cloudDecision.provider }
24208
- });
24209
- continue;
24210
- }
24211
- if (cloudDecision.kind === "defer") {
24212
- skipped += 1;
24213
- diagnostics.push({
24214
- code: "cloud-download-deferred",
24215
- severity: "info",
24216
- message: "The download budget for this refresh is used up; the file stays queued for the next refresh.",
24217
- source: entry.virtualPath
24218
- });
24219
- continue;
24220
- }
24221
- if (pending.has(mirrorStageKey(entry.virtualPath))) {
24222
- let bytes;
24223
- try {
24224
- bytes = await withTransientRetry(() => readFile(entry.sourcePath), transientRetries, transientBackoffMs);
24225
- } catch (error) {
24226
- const failure = sourceReadFailure(error);
24227
- await skipFile(entry, failure, failure.code === "source-unreadable" ? "source-unreadable" : undefined);
24144
+ const applyDeleteDecisions = async () => {
24145
+ for (const decision of plan.decisions) {
24146
+ if (decision.action !== "delete")
24228
24147
  continue;
24229
- }
24230
- const tooSmallImage = standaloneImageTooSmallMessage(entry.virtualPath, bytes);
24231
- if (tooSmallImage !== undefined) {
24232
- await skipFile(entry, { code: "parser-skipped", severity: "warning", message: tooSmallImage }, "parser-skipped");
24148
+ const path = pathsByKey.get(decision.key);
24149
+ await session.delete(decision.key);
24150
+ if (path === undefined)
24233
24151
  continue;
24234
- }
24235
- let parsed;
24236
- try {
24237
- const input = { virtualPath: entry.virtualPath, sourcePath: entry.sourcePath, bytes };
24238
- parsed = isolated !== undefined && ISOLATED_PARSER_NAMES.has(parser.name) ? await isolated.parse(parser.name, input, options.signal) : await parser.parse(input);
24239
- } catch (error) {
24240
- if (options.signal?.aborted)
24241
- throw abortReason(options.signal);
24242
- if (error instanceof ParseError)
24243
- await skipFile(entry, {
24244
- code: "parser-failed",
24245
- severity: "warning",
24246
- message: error.cause instanceof ParseIsolationError ? `${PARSER_FAILURE_MESSAGE} ${error.cause.message}.` : PARSER_FAILURE_MESSAGE
24247
- }, "parser-failed");
24248
- else
24249
- await skipFile(entry, {
24250
- code: "parser-crashed",
24251
- severity: "warning",
24252
- message: PARSER_CRASH_MESSAGE,
24253
- ...error instanceof Error ? { reason: error.name } : {}
24254
- });
24152
+ if (previous.entries[path])
24153
+ deleted += 1;
24154
+ if (!currentPaths.has(path))
24155
+ diagnostics.push({
24156
+ code: "deleted-mirror",
24157
+ severity: "info",
24158
+ message: "A previously indexed document is gone; its parsed mirror was removed.",
24159
+ source: path
24160
+ });
24161
+ }
24162
+ };
24163
+ const deferDeletions = options.onBatch !== undefined;
24164
+ const batchSize = Math.max(1, Math.floor(options.batchSize ?? DEFAULT_MIRROR_BATCH_SIZE));
24165
+ const batchCount = Math.max(1, Math.ceil(current.length / batchSize));
24166
+ let batchIndex = 0;
24167
+ let batchStopped = false;
24168
+ const runBatch = async (virtualPaths, final) => {
24169
+ if (options.onBatch === undefined)
24170
+ return;
24171
+ batchIndex += 1;
24172
+ const outcome = await options.onBatch({ index: batchIndex, total: batchCount, virtualPaths, final });
24173
+ if (outcome?.stop === true)
24174
+ batchStopped = true;
24175
+ };
24176
+ if (!deferDeletions)
24177
+ await applyDeleteDecisions();
24178
+ await gate.classify(current.filter((entry) => pending.has(mirrorStageKey(entry.virtualPath))).map((entry) => entry.sourcePath));
24179
+ for (let batchStart = 0;batchStart < Math.max(current.length, 1); batchStart += batchSize) {
24180
+ const batchEnd = Math.min(batchStart + batchSize, current.length);
24181
+ const finalBatch = batchEnd >= current.length;
24182
+ const changedBefore = written + deleted;
24183
+ for (let position = batchStart;position < batchEnd; position += 1) {
24184
+ const entry = current[position];
24185
+ options.onProgress?.({
24186
+ processed: position,
24187
+ total: current.length,
24188
+ written,
24189
+ deleted,
24190
+ skipped,
24191
+ currentVirtualPath: entry.virtualPath
24192
+ });
24193
+ if (options.signal?.aborted)
24194
+ throw abortReason(options.signal);
24195
+ const omission = omitted.get(entry.virtualPath);
24196
+ if (omission !== undefined) {
24197
+ if (pending.has(mirrorStageKey(entry.virtualPath))) {
24198
+ if (previous.entries[entry.virtualPath])
24199
+ deleted += 1;
24200
+ await session.skip(mirrorStageKey(entry.virtualPath), versions.get(entry.virtualPath), omission.reason);
24201
+ }
24202
+ skipped += 1;
24203
+ if (omission.diagnostic)
24204
+ diagnostics.push({ ...omission.diagnostic, source: entry.virtualPath });
24255
24205
  continue;
24256
24206
  }
24257
- let info;
24258
- try {
24259
- info = await stat4(entry.sourcePath, { bigint: true });
24260
- } catch (error) {
24261
- await skipFile(entry, sourceReadFailure(error));
24207
+ const deferredFailure = deferred.get(mirrorStageKey(entry.virtualPath));
24208
+ if (deferredFailure !== undefined) {
24209
+ skipped += 1;
24210
+ const code = deferredFailure.error === "source-unreadable" ? "source-unreadable" : "parser-failed";
24211
+ diagnostics.push({
24212
+ code,
24213
+ severity: "warning",
24214
+ source: entry.virtualPath,
24215
+ message: code === "parser-failed" ? PARSER_FAILURE_MESSAGE : "The source could not be read; retry is deferred until the source or settings change, or an explicit retry is requested."
24216
+ });
24262
24217
  continue;
24263
24218
  }
24264
- const unchanged = cloudDecision.kind === "download" ? contentIdentity(info) === entry.contentIdentity : statIdentity(info) === entry.statIdentity;
24265
- if (!unchanged) {
24219
+ const parser = registry.getForVirtualPath(entry.virtualPath);
24220
+ const outputPath = parsedOutputPath(options.root, entry.virtualPath);
24221
+ const cloudDecision = pending.has(mirrorStageKey(entry.virtualPath)) ? await gate.decide(entry.sourcePath, entry.sizeBytes, dirname22(entry.sourcePath)) : { kind: "local" };
24222
+ if (cloudDecision.kind === "skip") {
24266
24223
  skipped += 1;
24267
24224
  diagnostics.push({
24268
- code: "source-changed",
24269
- severity: "warning",
24270
- message: "The source changed while parsing; its last good mirror was kept and it will be retried on the next refresh.",
24271
- source: entry.virtualPath
24225
+ code: "cloud-placeholder-skipped",
24226
+ severity: "info",
24227
+ message: `The file is not downloaded and its provider has no verified release path, so it was not read; its last indexed copy was kept. Make it available offline to index it.`,
24228
+ source: entry.virtualPath,
24229
+ ...cloudDecision.provider === undefined ? {} : { reason: cloudDecision.provider }
24272
24230
  });
24273
- await session.fail(mirrorStageKey(entry.virtualPath), versions.get(entry.virtualPath), "source-changed");
24274
24231
  continue;
24275
24232
  }
24276
- const markdown = normalizeMarkdown(parsed.markdown);
24277
- try {
24278
- await withTransientRetry(() => writeFileAtomic(outputPath, markdown), transientRetries, transientBackoffMs);
24279
- } catch (error) {
24280
- const code = errorCode(error);
24281
- if (code !== undefined && Object.hasOwn(WORKSPACE_ERROR_CODES, code))
24282
- throw new MirrorWorkspaceError(options.root, error);
24283
- await skipFile(entry, {
24284
- code: "mirror-write-failed",
24285
- severity: "warning",
24286
- message: error instanceof Error ? error.message : String(error),
24287
- ...code !== undefined ? { reason: code } : {}
24233
+ if (cloudDecision.kind === "defer") {
24234
+ skipped += 1;
24235
+ diagnostics.push({
24236
+ code: "cloud-download-deferred",
24237
+ severity: "info",
24238
+ message: "The download budget for this refresh is used up; the file stays queued for the next refresh.",
24239
+ source: entry.virtualPath
24288
24240
  });
24289
24241
  continue;
24290
24242
  }
24291
- await session.commit(mirrorStageKey(entry.virtualPath), versions.get(entry.virtualPath), {
24292
- path: basename14(outputPath),
24293
- size: Buffer.byteLength(markdown)
24294
- });
24295
- for (const diagnostic of parsed.diagnostics ?? [])
24296
- diagnostics.push({ ...diagnostic, source: entry.virtualPath });
24297
- written += 1;
24298
- if (fingerprintCloud && cloudFilePaths.has(entry.sourcePath)) {
24299
- const existing = cloudFingerprintEntries[entry.sourcePath];
24300
- if (existing === undefined || existing.sourceVersion !== entry.sourceVersion) {
24301
- try {
24302
- const fingerprintResult = await fingerprintWithDupey(entry.sourcePath, options.cloudFingerprints?.dupey ?? {});
24303
- cloudFingerprintEntries[entry.sourcePath] = {
24304
- sourceVersion: entry.sourceVersion,
24305
- exact: fingerprintResult.exact,
24306
- chars: fingerprintResult.chars,
24307
- modified: fingerprintResult.modified,
24308
- revision: fingerprintResult.revision
24309
- };
24310
- cloudFingerprintsDirty = true;
24311
- } catch (error) {
24312
- diagnostics.push({
24313
- code: "cloud-fingerprint-failed",
24243
+ if (pending.has(mirrorStageKey(entry.virtualPath))) {
24244
+ let bytes;
24245
+ try {
24246
+ bytes = await withTransientRetry(() => readFile(entry.sourcePath), transientRetries, transientBackoffMs);
24247
+ } catch (error) {
24248
+ const failure = sourceReadFailure(error);
24249
+ await skipFile(entry, failure, failure.code === "source-unreadable" ? "source-unreadable" : undefined);
24250
+ continue;
24251
+ }
24252
+ const tooSmallImage = standaloneImageTooSmallMessage(entry.virtualPath, bytes);
24253
+ if (tooSmallImage !== undefined) {
24254
+ await skipFile(entry, { code: "parser-skipped", severity: "warning", message: tooSmallImage }, "parser-skipped");
24255
+ continue;
24256
+ }
24257
+ let parsed;
24258
+ try {
24259
+ const input = { virtualPath: entry.virtualPath, sourcePath: entry.sourcePath, bytes };
24260
+ parsed = isolated !== undefined && ISOLATED_PARSER_NAMES.has(parser.name) ? await isolated.parse(parser.name, input, options.signal) : await parser.parse(input);
24261
+ } catch (error) {
24262
+ if (options.signal?.aborted)
24263
+ throw abortReason(options.signal);
24264
+ if (error instanceof ParseError)
24265
+ await skipFile(entry, {
24266
+ code: "parser-failed",
24314
24267
  severity: "warning",
24315
- message: `The cloud or network file was indexed but its duplicate fingerprint could not be read, so it will not take part in exact-duplicate exclusion: ${error instanceof Error ? error.message : String(error)}`,
24316
- source: entry.virtualPath
24268
+ message: error.cause instanceof ParseIsolationError ? `${PARSER_FAILURE_MESSAGE} ${error.cause.message}.` : PARSER_FAILURE_MESSAGE
24269
+ }, "parser-failed");
24270
+ else
24271
+ await skipFile(entry, {
24272
+ code: "parser-crashed",
24273
+ severity: "warning",
24274
+ message: PARSER_CRASH_MESSAGE,
24275
+ ...error instanceof Error ? { reason: error.name } : {}
24317
24276
  });
24318
- }
24277
+ continue;
24319
24278
  }
24320
- }
24321
- if (cloudDecision.kind === "download" && gate.evictAfterParse) {
24322
- const released = await gate.release(entry.sourcePath);
24323
- if (!released.ok) {
24279
+ let info;
24280
+ try {
24281
+ info = await stat4(entry.sourcePath, { bigint: true });
24282
+ } catch (error) {
24283
+ await skipFile(entry, sourceReadFailure(error));
24284
+ continue;
24285
+ }
24286
+ const unchanged = cloudDecision.kind === "download" ? contentIdentity(info) === entry.contentIdentity : statIdentity(info) === entry.statIdentity;
24287
+ if (!unchanged) {
24288
+ skipped += 1;
24324
24289
  diagnostics.push({
24325
- code: "cloud-evict-failed",
24290
+ code: "source-changed",
24326
24291
  severity: "warning",
24327
- message: `The file was indexed but its local copy could not be released: ${released.message}`,
24292
+ message: "The source changed while parsing; its last good mirror was kept and it will be retried on the next refresh.",
24328
24293
  source: entry.virtualPath
24329
24294
  });
24295
+ await session.fail(mirrorStageKey(entry.virtualPath), versions.get(entry.virtualPath), "source-changed");
24296
+ continue;
24297
+ }
24298
+ const markdown = normalizeMarkdown(parsed.markdown);
24299
+ try {
24300
+ await withTransientRetry(() => writeFileAtomic(outputPath, markdown), transientRetries, transientBackoffMs);
24301
+ } catch (error) {
24302
+ const code = errorCode(error);
24303
+ if (code !== undefined && Object.hasOwn(WORKSPACE_ERROR_CODES, code))
24304
+ throw new MirrorWorkspaceError(options.root, error);
24305
+ await skipFile(entry, {
24306
+ code: "mirror-write-failed",
24307
+ severity: "warning",
24308
+ message: error instanceof Error ? error.message : String(error),
24309
+ ...code !== undefined ? { reason: code } : {}
24310
+ });
24311
+ continue;
24312
+ }
24313
+ await session.commit(mirrorStageKey(entry.virtualPath), versions.get(entry.virtualPath), {
24314
+ path: basename14(outputPath),
24315
+ size: Buffer.byteLength(markdown)
24316
+ });
24317
+ for (const diagnostic of parsed.diagnostics ?? [])
24318
+ diagnostics.push({ ...diagnostic, source: entry.virtualPath });
24319
+ written += 1;
24320
+ if (fingerprintCloud && cloudFilePaths.has(entry.sourcePath)) {
24321
+ const existing = cloudFingerprintEntries[entry.sourcePath];
24322
+ if (existing === undefined || existing.sourceVersion !== entry.sourceVersion) {
24323
+ try {
24324
+ const fingerprintResult = await fingerprintWithDupey(entry.sourcePath, options.cloudFingerprints?.dupey ?? {});
24325
+ cloudFingerprintEntries[entry.sourcePath] = {
24326
+ sourceVersion: entry.sourceVersion,
24327
+ exact: fingerprintResult.exact,
24328
+ chars: fingerprintResult.chars,
24329
+ modified: fingerprintResult.modified,
24330
+ revision: fingerprintResult.revision
24331
+ };
24332
+ cloudFingerprintsDirty = true;
24333
+ } catch (error) {
24334
+ diagnostics.push({
24335
+ code: "cloud-fingerprint-failed",
24336
+ severity: "warning",
24337
+ message: `The cloud or network file was indexed but its duplicate fingerprint could not be read, so it will not take part in exact-duplicate exclusion: ${error instanceof Error ? error.message : String(error)}`,
24338
+ source: entry.virtualPath
24339
+ });
24340
+ }
24341
+ }
24342
+ }
24343
+ if (cloudDecision.kind === "download" && gate.evictAfterParse) {
24344
+ const released = await gate.release(entry.sourcePath);
24345
+ if (!released.ok) {
24346
+ diagnostics.push({
24347
+ code: "cloud-evict-failed",
24348
+ severity: "warning",
24349
+ message: `The file was indexed but its local copy could not be released: ${released.message}`,
24350
+ source: entry.virtualPath
24351
+ });
24352
+ }
24330
24353
  }
24331
24354
  }
24332
24355
  }
24356
+ if (finalBatch && deferDeletions)
24357
+ await applyDeleteDecisions();
24358
+ if (finalBatch || written + deleted !== changedBefore) {
24359
+ await runBatch(current.slice(batchStart, batchEnd).map((item) => item.virtualPath), finalBatch);
24360
+ }
24361
+ if (batchStopped)
24362
+ break;
24333
24363
  }
24334
24364
  await session.finish();
24335
24365
  if (fingerprintCloud) {
@@ -24621,7 +24651,7 @@ async function collectFiles(state, directory, depth) {
24621
24651
  warnDirectoryUnreadable(state, directory, code);
24622
24652
  }
24623
24653
  }
24624
- var DEFAULT_MAX_SOURCE_BYTES, DEFAULT_MAX_DELETE_RATIO = 0.3, MASS_DELETE_FLOOR = 100, DEFAULT_MAX_SCANNED_ENTRIES = 2000000, DEFAULT_MAX_WALK_DEPTH = 64, PRODUCT_ARTIFACT_FILE_NAMES, SKIP_DIR_NAMES, PARSER_FAILURE_MESSAGE = "The registered parser failed on this file; it was skipped during indexing.", PARSER_CRASH_MESSAGE = "The registered parser crashed unexpectedly on this file; it was skipped during indexing.", DEFAULT_TRANSIENT_RETRIES = 3, DEFAULT_TRANSIENT_BACKOFF_MS = 50, TRANSIENT_ERROR_CODES, WORKSPACE_ERROR_CODES, MirrorWorkspaceError, STANDALONE_OCR_IMAGE_EXTENSIONS, SEARCH_ROOT_UNAVAILABLE_MESSAGES;
24654
+ var DEFAULT_MAX_SOURCE_BYTES, DEFAULT_MAX_DELETE_RATIO = 0.3, MASS_DELETE_FLOOR = 100, DEFAULT_MAX_SCANNED_ENTRIES = 2000000, DEFAULT_MAX_WALK_DEPTH = 64, DEFAULT_MIRROR_BATCH_SIZE = 25, PRODUCT_ARTIFACT_FILE_NAMES, SKIP_DIR_NAMES, PARSER_FAILURE_MESSAGE = "The registered parser failed on this file; it was skipped during indexing.", PARSER_CRASH_MESSAGE = "The registered parser crashed unexpectedly on this file; it was skipped during indexing.", DEFAULT_TRANSIENT_RETRIES = 3, DEFAULT_TRANSIENT_BACKOFF_MS = 50, TRANSIENT_ERROR_CODES, WORKSPACE_ERROR_CODES, MirrorWorkspaceError, STANDALONE_OCR_IMAGE_EXTENSIONS, SEARCH_ROOT_UNAVAILABLE_MESSAGES;
24625
24655
  var init_sync = __esm(() => {
24626
24656
  init_cli2();
24627
24657
  init_cloud_placeholder();
@@ -38268,11 +38298,52 @@ ${this.discoveryHint((tools) => `When exploring local files and folders, activel
38268
38298
  };
38269
38299
  try {
38270
38300
  writeRefreshProgress(this.workspaceProjectRoot, progress);
38301
+ const batchedMinSync = needsParsed && wants("minsync");
38302
+ const minSyncBatches = [];
38303
+ const minSyncBatchFailures = [];
38304
+ let minSyncSyncedBase = 0;
38305
+ let minSyncTotalBase = 0;
38306
+ const minSyncBatch = async (batch) => {
38307
+ signal?.throwIfAborted();
38308
+ progress = updateRefreshProgress(progress, { phase: "minsync" });
38309
+ writeRefreshProgress(this.workspaceProjectRoot, progress);
38310
+ let batchTotal = minSyncTotalBase;
38311
+ const result = await this.syncMinSync(force && batch.index === 1, signal, (minsyncProgress) => {
38312
+ batchTotal = minSyncTotalBase + minsyncProgress.total;
38313
+ progress = updateRefreshProgress(progress, {
38314
+ minsync: { synced: minSyncSyncedBase + minsyncProgress.processed, total: batchTotal }
38315
+ });
38316
+ writeRefreshProgress(this.workspaceProjectRoot, progress);
38317
+ emitProgress({
38318
+ phase: "minsync",
38319
+ processed: minSyncSyncedBase + minsyncProgress.processed,
38320
+ total: batchTotal
38321
+ });
38322
+ });
38323
+ minSyncBatches.push(result);
38324
+ if (result.ok)
38325
+ minSyncBatchFailures.length = 0;
38326
+ else if (result.reason !== "lock-held") {
38327
+ for (const virtualPath of batch.virtualPaths) {
38328
+ minSyncBatchFailures.push({
38329
+ code: "minsync-sync-failed",
38330
+ severity: "warning",
38331
+ message: `This file stayed at its previous index entry: its batch could not be indexed and the next sync retries it. ${sanitizeDiagnosticMessage(result.diagnostic?.message ?? result.reason ?? "MinSync sync failed.")}`,
38332
+ source: virtualPath
38333
+ });
38334
+ }
38335
+ }
38336
+ minSyncSyncedBase = batchTotal;
38337
+ minSyncTotalBase = batchTotal;
38338
+ return !result.ok && result.reason === "lock-held" ? { stop: true } : undefined;
38339
+ };
38271
38340
  const summary = needsParsed ? await this.syncParsedMirrors(force, {
38272
38341
  ...opts?.retrySkipped === true ? { retrySkipped: true } : {},
38273
38342
  ...signal === undefined ? {} : { signal },
38343
+ ...batchedMinSync ? { onBatch: minSyncBatch } : {},
38274
38344
  onProgress: (parsedProgress) => {
38275
38345
  progress = updateRefreshProgress(progress, {
38346
+ phase: "parsed",
38276
38347
  sourceFiles: { total: parsedProgress.total },
38277
38348
  parsedCounts: {
38278
38349
  scanned: parsedProgress.processed,
@@ -38294,7 +38365,7 @@ ${this.discoveryHint((tools) => `When exploring local files and folders, activel
38294
38365
  }) : await this.scanMirrorStaleness();
38295
38366
  signal?.throwIfAborted();
38296
38367
  progress = updateRefreshProgress(progress, {
38297
- phase: wants("minsync") ? "minsync" : wants("datasources") ? "datasources" : wants("jikji") ? "jikji" : "finalizing",
38368
+ phase: wants("datasources") ? "datasources" : wants("jikji") ? "jikji" : "finalizing",
38298
38369
  sourceFiles: { total: summary.scanned },
38299
38370
  parsedCounts: {
38300
38371
  scanned: summary.scanned,
@@ -38304,24 +38375,12 @@ ${this.discoveryHint((tools) => `When exploring local files and folders, activel
38304
38375
  }
38305
38376
  });
38306
38377
  writeRefreshProgress(this.workspaceProjectRoot, progress);
38307
- const minsync = wants("minsync") ? await this.syncMinSync(force, signal, (minsyncProgress) => {
38308
- progress = updateRefreshProgress(progress, {
38309
- minsync: { synced: minsyncProgress.processed, total: minsyncProgress.total }
38310
- });
38311
- writeRefreshProgress(this.workspaceProjectRoot, progress);
38312
- emitProgress({
38313
- phase: "minsync",
38314
- processed: minsyncProgress.processed,
38315
- total: minsyncProgress.total
38316
- });
38317
- }) : undefined;
38318
- signal?.throwIfAborted();
38378
+ const minsync = batchedMinSync ? aggregateMinSyncBatches(minSyncBatches) : undefined;
38319
38379
  if (minsync !== undefined) {
38320
38380
  progress = updateRefreshProgress(progress, {
38321
- phase: wants("datasources") ? "datasources" : wants("jikji") ? "jikji" : "finalizing",
38322
38381
  minsync: {
38323
- ...progress.minsync,
38324
38382
  synced: minsync.synced,
38383
+ total: Math.max(minSyncTotalBase, minsync.synced),
38325
38384
  ...minsync.filesFailed !== undefined && minsync.filesFailed.length > 0 ? { filesSkipped: minsync.filesFailed.length } : {}
38326
38385
  }
38327
38386
  });
@@ -38383,7 +38442,7 @@ ${this.discoveryHint((tools) => `When exploring local files and folders, activel
38383
38442
  finishedAt: new Date().toISOString()
38384
38443
  });
38385
38444
  writeRefreshProgress(this.workspaceProjectRoot, progress);
38386
- const minsyncDiagnostics = minSyncRefreshDiagnostics(minsync);
38445
+ const minsyncDiagnostics = [...minSyncRefreshDiagnostics(minsync), ...minSyncBatchFailures];
38387
38446
  const publicMinsync = minsync ? {
38388
38447
  ok: minsync.ok,
38389
38448
  synced: minsync.synced,
@@ -39188,6 +39247,12 @@ function fsearchRefreshDiagnostics(fsearch) {
39188
39247
  }
39189
39248
  ];
39190
39249
  }
39250
+ function aggregateMinSyncBatches(batches) {
39251
+ const last = batches.at(-1);
39252
+ if (last === undefined)
39253
+ return;
39254
+ return { ...last, synced: batches.reduce((total, batch) => total + batch.synced, 0) };
39255
+ }
39191
39256
  function minSyncRefreshDiagnostics(minsync) {
39192
39257
  if (!minsync)
39193
39258
  return [];