@nitpicker/crawler 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/lib/append-retry-failed-common-setup-phases.d.ts +1 -1
  2. package/lib/append-retry-failed-common-setup-phases.js +5 -1
  3. package/lib/append-setup-phases.d.ts +1 -1
  4. package/lib/archive/archive.d.ts +57 -8
  5. package/lib/archive/archive.js +83 -12
  6. package/lib/archive/create-adjunct-tables.d.ts +3 -2
  7. package/lib/archive/create-adjunct-tables.js +12 -9
  8. package/lib/archive/create-entity-tables.js +3 -0
  9. package/lib/archive/database.d.ts +39 -7
  10. package/lib/archive/database.js +49 -9
  11. package/lib/archive/db-ops/_shared/keyset-paginate-by-id.d.ts +49 -0
  12. package/lib/archive/db-ops/_shared/keyset-paginate-by-id.js +69 -0
  13. package/lib/archive/db-ops/_shared/resolve-content-item-id.d.ts +13 -12
  14. package/lib/archive/db-ops/_shared/resolve-content-item-id.js +63 -31
  15. package/lib/archive/db-ops/_shared/types.d.ts +39 -0
  16. package/lib/archive/db-ops/analysis/replace-analysis-violations.js +7 -4
  17. package/lib/archive/db-ops/config/get-created-cwd.d.ts +11 -0
  18. package/lib/archive/db-ops/config/get-created-cwd.js +13 -0
  19. package/lib/archive/db-ops/config/info-column-allowlist.js +1 -0
  20. package/lib/archive/db-ops/dedupe-cap/list-dedupe-cap-observations.d.ts +57 -0
  21. package/lib/archive/db-ops/dedupe-cap/list-dedupe-cap-observations.js +87 -0
  22. package/lib/archive/db-ops/errors/insert-page-error.js +3 -1
  23. package/lib/archive/db-ops/lifecycle/init.d.ts +13 -6
  24. package/lib/archive/db-ops/lifecycle/init.js +30 -10
  25. package/lib/archive/db-ops/list-reconcile/record-list-reconcile-run.d.ts +22 -0
  26. package/lib/archive/db-ops/{inventory/record-inventory-run.js → list-reconcile/record-list-reconcile-run.js} +11 -10
  27. package/lib/archive/db-ops/pages/read/build-page-query.js +1 -1
  28. package/lib/archive/db-ops/pages/read/get-crawling-state.d.ts +9 -2
  29. package/lib/archive/db-ops/pages/read/get-crawling-state.js +11 -3
  30. package/lib/archive/db-ops/pages/reset/clear-page-derived-rows.d.ts +20 -0
  31. package/lib/archive/db-ops/pages/reset/clear-page-derived-rows.js +37 -0
  32. package/lib/archive/db-ops/pages/reset/repromote-external-pages.d.ts +3 -2
  33. package/lib/archive/db-ops/pages/reset/repromote-external-pages.js +17 -37
  34. package/lib/archive/db-ops/pages/reset/reset-failed-pages.d.ts +22 -1
  35. package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +79 -33
  36. package/lib/archive/db-ops/pages/reset/reset-pages-by-urls.d.ts +78 -0
  37. package/lib/archive/db-ops/pages/reset/reset-pages-by-urls.js +146 -0
  38. package/lib/archive/db-ops/pages/reset/retryable-image-scan-codes.d.ts +24 -0
  39. package/lib/archive/db-ops/pages/reset/retryable-image-scan-codes.js +24 -0
  40. package/lib/archive/db-ops/pages/write/insert-inventory-content-items.js +2 -1
  41. package/lib/archive/db-ops/pages/write/insert-page.js +4 -2
  42. package/lib/archive/db-ops/pages/write/link-redirect-sources.js +3 -1
  43. package/lib/archive/db-ops/pages/write/record-redirect.js +1 -1
  44. package/lib/archive/db-ops/pages/write/set-skipped-page.js +3 -1
  45. package/lib/archive/db-ops/pages/write/update-page.d.ts +9 -1
  46. package/lib/archive/db-ops/pages/write/update-page.js +28 -6
  47. package/lib/archive/db-ops/resources/get-resource-url-list.d.ts +3 -6
  48. package/lib/archive/db-ops/resources/get-resource-url-list.js +10 -35
  49. package/lib/archive/init-schema.d.ts +1 -1
  50. package/lib/archive/init-schema.js +3 -2
  51. package/lib/archive/meta/compute-main-contents-denormalized.d.ts +11 -3
  52. package/lib/archive/meta/compute-main-contents-denormalized.js +14 -2
  53. package/lib/archive/meta/types.d.ts +24 -0
  54. package/lib/archive/migrate-content-items-is-metadata-only.d.ts +44 -0
  55. package/lib/archive/migrate-content-items-is-metadata-only.js +60 -0
  56. package/lib/archive/migrate-info-created-cwd.d.ts +17 -0
  57. package/lib/archive/migrate-info-created-cwd.js +36 -0
  58. package/lib/archive/migrate-inventory-runs-to-list-reconcile-runs.d.ts +34 -0
  59. package/lib/archive/migrate-inventory-runs-to-list-reconcile-runs.js +51 -0
  60. package/lib/archive/{migrate-inventory-runs-exclude-skipped.d.ts → migrate-list-reconcile-runs-exclude-skipped.d.ts} +8 -8
  61. package/lib/archive/{migrate-inventory-runs-exclude-skipped.js → migrate-list-reconcile-runs-exclude-skipped.js} +12 -12
  62. package/lib/archive/migrate-list-reconcile-runs-invalid-skipped.d.ts +16 -0
  63. package/lib/archive/migrate-list-reconcile-runs-invalid-skipped.js +35 -0
  64. package/lib/archive/migrate-page-meta-image-scan.d.ts +27 -0
  65. package/lib/archive/migrate-page-meta-image-scan.js +52 -0
  66. package/lib/archive/page.d.ts +12 -0
  67. package/lib/archive/page.js +16 -0
  68. package/lib/archive/types.d.ts +97 -10
  69. package/lib/compute-auto-retry-backoff-delay.d.ts +30 -0
  70. package/lib/compute-auto-retry-backoff-delay.js +36 -0
  71. package/lib/crawler/apply-crawl-runtime-options-patch.d.ts +32 -0
  72. package/lib/crawler/apply-crawl-runtime-options-patch.js +129 -0
  73. package/lib/crawler/crawler.d.ts +35 -3
  74. package/lib/crawler/crawler.js +486 -338
  75. package/lib/crawler/dedupe/build-dedupe-cap-observation.d.ts +74 -0
  76. package/lib/crawler/dedupe/build-dedupe-cap-observation.js +97 -0
  77. package/lib/crawler/dedupe/compute-meta-signature.d.ts +7 -5
  78. package/lib/crawler/dedupe/compute-meta-signature.js +0 -0
  79. package/lib/crawler/dedupe/compute-shape-key.d.ts +20 -0
  80. package/lib/crawler/dedupe/compute-shape-key.js +73 -1
  81. package/lib/crawler/dedupe/dedupe-cap-tracker.d.ts +6 -1
  82. package/lib/crawler/dedupe/dedupe-cap-tracker.js +6 -1
  83. package/lib/crawler/dedupe/resolve-og-url-mismatch.d.ts +7 -5
  84. package/lib/crawler/dedupe/resolve-og-url-mismatch.js +5 -3
  85. package/lib/crawler/dedupe/types.d.ts +18 -0
  86. package/lib/crawler/fetch-destination.js +1 -0
  87. package/lib/crawler/link-list.d.ts +9 -1
  88. package/lib/crawler/link-list.js +12 -2
  89. package/lib/crawler/link-to-page-data.js +1 -0
  90. package/lib/crawler/resource-to-page-data.js +1 -0
  91. package/lib/crawler/types.d.ts +75 -0
  92. package/lib/crawler-orchestrator.d.ts +171 -8
  93. package/lib/crawler-orchestrator.js +1177 -234
  94. package/lib/crawler.d.ts +3 -0
  95. package/lib/crawler.js +3 -0
  96. package/lib/delay-or-abort.d.ts +20 -0
  97. package/lib/delay-or-abort.js +35 -0
  98. package/lib/pending-urls-remain-error.d.ts +35 -0
  99. package/lib/pending-urls-remain-error.js +43 -0
  100. package/lib/recrawl-setup-phases.d.ts +38 -0
  101. package/lib/recrawl-setup-phases.js +52 -0
  102. package/lib/resume-setup-phases.d.ts +1 -1
  103. package/lib/resume-setup-phases.js +5 -1
  104. package/lib/retry-failed-setup-phases.d.ts +1 -1
  105. package/lib/setup-recovery-phase-labels.d.ts +17 -8
  106. package/lib/setup-recovery-phase-labels.js +17 -8
  107. package/lib/types.d.ts +62 -12
  108. package/lib/utils/compute-file-sha256.d.ts +1 -1
  109. package/lib/utils/compute-file-sha256.js +1 -1
  110. package/package.json +4 -4
  111. package/lib/archive/db-ops/inventory/record-inventory-run.d.ts +0 -21
  112. package/lib/archive/migrate-inventory-runs-invalid-skipped.d.ts +0 -16
  113. package/lib/archive/migrate-inventory-runs-invalid-skipped.js +0 -35
@@ -1,7 +1,9 @@
1
1
  import type { Config } from './archive/types.js';
2
+ import type { DedupeCapObservation } from './crawler/dedupe/types.js';
2
3
  import type { NetworkProbe } from './crawler/probe-network.js';
3
- import type { InventoryMode } from './crawler/types.js';
4
+ import type { CrawlRuntimeOptions, CrawlRuntimeOptionsPatch, InventoryMode } from './crawler/types.js';
4
5
  import type { CrawlEvent, SetupProgressCallbacks } from './types.js';
6
+ import type { Lanes } from '@d-zero/dealer';
5
7
  import type { ExURL } from '@d-zero/shared/parse-url';
6
8
  import { TypedAwaitEventEmitter as EventEmitter } from '@d-zero/shared/typed-await-event-emitter';
7
9
  import Archive from './archive/archive.js';
@@ -37,6 +39,17 @@ interface CrawlConfig extends Config {
37
39
  list: boolean;
38
40
  /** Maximum number of retry attempts per URL on scrape failure. */
39
41
  retry: number;
42
+ /**
43
+ * Maximum number of whole-session auto-retry attempts (issue #350) when
44
+ * a crawl session ends with pages still pending — each attempt re-queues
45
+ * the current pending set and re-runs the crawl loop, with an
46
+ * exponential backoff between attempts (see
47
+ * `computeAutoRetryBackoffDelayMs`). `0` disables auto-retry entirely:
48
+ * any pending pages after the session's first (and only) crawl pass
49
+ * abort the session immediately. See
50
+ * `CrawlerOrchestrator`'s `#crawlUntilPendingClears` for the full loop.
51
+ */
52
+ maxAutoRetry: number;
40
53
  /** Whether to enable verbose logging output. */
41
54
  verbose: boolean;
42
55
  /** Custom User-Agent string for HTTP requests. */
@@ -75,12 +88,29 @@ interface CrawlConfig extends Config {
75
88
  dedupeMapCap: number;
76
89
  /**
77
90
  * See {@link CrawlerOptions.preloadedStickyShapeKeys}. Set internally by
78
- * the four resuming-session static methods
79
- * (`append`/`inventory`/`retryFailed`/`resume`), each independently
80
- * calling `archive.listDedupeCapShapeKeys()`; not part of the public
81
- * options a caller of those methods passes directly.
91
+ * the five resuming-session static methods
92
+ * (`append`/`inventory`/`recrawl`/`retryFailed`/`resume`), each
93
+ * independently calling `archive.listDedupeCapShapeKeys()`; not part of
94
+ * the public options a caller of those methods passes directly.
82
95
  */
83
96
  preloadedStickyShapeKeys: readonly string[];
97
+ /**
98
+ * See {@link CrawlerOptions.preloadedDedupeObservations}. Set internally
99
+ * by the same five resuming-session static methods as
100
+ * {@link preloadedStickyShapeKeys}, each calling
101
+ * `archive.listDedupeCapObservations()` and mapping the rows through
102
+ * `buildDedupeCapObservation`; not part of the public options a caller
103
+ * of those methods passes directly.
104
+ */
105
+ preloadedDedupeObservations: readonly DedupeCapObservation[];
106
+ /**
107
+ * A `Lanes` instance owned by the caller (typically the CLI), forwarded
108
+ * to {@link Crawler} so `deal()` reuses it instead of creating its own —
109
+ * see {@link CrawlerOptions.lanes}. Not part of {@link Config}: it is a
110
+ * live object, never persisted to (or read back from) the archive's
111
+ * `info` row.
112
+ */
113
+ lanes?: Lanes;
84
114
  }
85
115
  /**
86
116
  * Callback invoked after the CrawlerOrchestrator instance is fully initialized
@@ -100,7 +130,7 @@ interface InventorySource {
100
130
  sha256: string;
101
131
  /** The exact bytes of the source list file, archived verbatim. */
102
132
  bytes: Buffer;
103
- /** Number of source-file lines the CLI warned-and-dropped for failing URL validation, before `inventoryUrls` was ever built. Recorded on the audit row as `inventory_runs.invalid_skipped`. */
133
+ /** Number of source-file lines the CLI warned-and-dropped for failing URL validation, before `inventoryUrls` was ever built. Recorded on the audit row as `list_reconcile_runs.invalid_skipped`. */
104
134
  invalidLineCount: number;
105
135
  }
106
136
  /**
@@ -159,16 +189,43 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
159
189
  * Sets up event listeners on the crawler, starts crawling, and resolves
160
190
  * when the crawl completes. Discovered pages, external pages, skipped pages,
161
191
  * and resources are forwarded to the archive for storage.
192
+ *
193
+ * Safe to call more than once on the same instance —
194
+ * `#crawlUntilPendingClears` (issue #350) re-invokes this for each
195
+ * auto-retry attempt against the same long-lived `#crawler`. `Crawler`
196
+ * (`TypedAwaitEventEmitter`) has no listener-removal API, so a second
197
+ * call cannot replace the first call's listeners — it can only stack
198
+ * another set alongside them. Every listener this method attaches is
199
+ * instead guarded by a monotonic generation counter (`isCurrent()`,
200
+ * defined below): once a later call bumps it, every earlier call's
201
+ * listeners permanently fail the check and become inert no-ops, leaving
202
+ * exactly the latest call's listeners actually writing anything.
162
203
  * @param list - The list of parsed URLs to crawl. May be empty when a resumed
163
204
  * session already has pending pages queued (for example `--retry-failed`).
164
205
  * @param opts - Optional crawl overrides.
165
206
  * @param opts.recursive - Whether discovered URLs are followed. Defaults to
166
207
  * `!fromList` (recursive unless the archive was created from a URL list), so
167
208
  * existing callers keep their behaviour; the retry flow passes it explicitly.
209
+ * @param opts.suppressFlushNotice - Skip emitting `flushingPendingWrites`
210
+ * (issue #350). Set by `#crawlUntilPendingClears` for every auto-retry
211
+ * attempt after the first: that event starts the CLI's crawl-tail
212
+ * `TaskList` (`attach-crawl-display.ts`), which must stay closed until
213
+ * the whole retry loop is done — a second `deal()`/`Lanes` cycle
214
+ * starting while that `TaskList` is still open would corrupt the
215
+ * display (see ARCHITECTURE.md's `Lanes`/`Display` single-instance
216
+ * invariant). The write-queue drain itself is unaffected; only the
217
+ * CLI-facing progress event is skipped.
218
+ * @param opts.isRetryContinuation - Forwarded to `Crawler#start()`
219
+ * (issue #350). Set by `#crawlUntilPendingClears` for every auto-retry
220
+ * attempt after the first, so `#runDeal` preserves cross-attempt
221
+ * learned state (known-good hosts, outage-detector window) instead of
222
+ * discarding it as if this were an unrelated fresh session.
168
223
  * @returns A promise that resolves when crawling is complete.
169
224
  */
170
225
  crawling(list: ExURL[], opts?: {
171
226
  recursive?: boolean;
227
+ suppressFlushNotice?: boolean;
228
+ isRetryContinuation?: boolean;
172
229
  }): Promise<void>;
173
230
  /**
174
231
  * Kill any zombie Chromium processes that were not properly cleaned up.
@@ -184,6 +241,34 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
184
241
  * @returns An array of process IDs that should be terminated.
185
242
  */
186
243
  getUndeadPid(): never[];
244
+ /**
245
+ * Applies a runtime change to the in-progress crawl's tunable options
246
+ * (`parallels`/`interval`/the three exclude arrays — see
247
+ * {@link Crawler.updateRuntimeOptions}) and persists the resulting
248
+ * values to the archive's `info` row so a later `--resume` / `--append`
249
+ * / `--retry-failed` reuses them (`Archive#updateConfig`).
250
+ *
251
+ * The in-memory change takes effect immediately, before this method
252
+ * returns. The archive write is enqueued on {@link #writeQueue} — so it
253
+ * is ordered relative to the crawl's own page/resource writes — but not
254
+ * awaited: this method is synchronous so a caller (e.g. the CLI reading
255
+ * a console command) can report the new value back without waiting on
256
+ * disk I/O. A failure in that write does NOT abort the crawl or set
257
+ * {@link #archiveFailure} — see {@link #persistingRuntimeOptionsPatch}'s
258
+ * JSDoc for why this one write is deliberately exempted from the fatal
259
+ * handling every other archive write gets; it is only debug-logged
260
+ * (`DEBUG=Nitpicker:*`).
261
+ * @param patch - The runtime change to apply.
262
+ * @returns A snapshot of the tunable options after applying `patch`.
263
+ * @throws {RangeError} If `parallels` is present and not an integer `>= 1`, or `interval` is present and not an integer `>= 0`.
264
+ * @throws {TypeError} If any exclude entry is present and not a non-empty string.
265
+ * @example
266
+ * ```ts
267
+ * const snapshot = orchestrator.updateRuntimeOptions({ parallels: 4 });
268
+ * console.log(snapshot.parallels); // 4
269
+ * ```
270
+ */
271
+ updateRuntimeOptions(patch: CrawlRuntimeOptionsPatch): CrawlRuntimeOptions;
187
272
  /**
188
273
  * Write the archive to its configured file path.
189
274
  *
@@ -215,6 +300,7 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
215
300
  * @param initializedCallback - Optional callback invoked after initialization but before crawling starts.
216
301
  * @returns A promise that resolves to the CrawlerOrchestrator instance after crawling completes.
217
302
  * @throws {Error} If the URL list is empty or contains no valid URLs.
303
+ * @throws {PendingUrlsRemainError} When the crawl session ends with pages still pending after exhausting auto-retry.
218
304
  */
219
305
  static crawling(url: string[], options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback): Promise<CrawlerOrchestrator>;
220
306
  /**
@@ -226,7 +312,11 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
226
312
  * the expanded scope are demoted back to "needs scraping" so the next pass
227
313
  * re-fetches them as full internal pages. A `<archive>.bak` is created
228
314
  * before the crawl and removed on success; if the crawl throws, the backup
229
- * is restored to keep the original archive intact.
315
+ * is restored to keep the original archive intact — except when the crawl
316
+ * ends with {@link PendingUrlsRemainError} (issue #350), where the
317
+ * un-packaged stub itself is the recovery path and the backup is instead
318
+ * left untouched (deleted, not restored — see
319
+ * {@link CrawlerOrchestrator.#abandonBackupOnPendingRemains}).
230
320
  *
231
321
  * List-mode archives (`info.fromList === true`) are rejected because their
232
322
  * pages are all metadata-only and cannot host a recursive append.
@@ -240,6 +330,7 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
240
330
  * this can't go through the orchestrator's event emitter (issue #294).
241
331
  * @returns The orchestrator instance after the append crawl completes.
242
332
  * @throws {Error} When `newUrls` is empty, the archive is in list mode, or it cannot be parsed.
333
+ * @throws {PendingUrlsRemainError} When the crawl session ends with pages still pending after exhausting auto-retry.
243
334
  */
244
335
  static append(archivePath: string, newUrls: string[], options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback, setupProgress?: SetupProgressCallbacks): Promise<CrawlerOrchestrator>;
245
336
  /**
@@ -326,8 +417,75 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
326
417
  * #294).
327
418
  * @returns The orchestrator instance after a successful inventory pass.
328
419
  * @throws {Error} When `inventoryUrls` is empty or the archive is in list mode. Unresolved pending URLs from a previous crawl do NOT throw — see step 3.
420
+ * @throws {PendingUrlsRemainError} When the crawl session ends with pages still pending after exhausting auto-retry.
329
421
  */
330
422
  static inventory(archivePath: string, inventoryUrls: string[], options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback, source?: InventorySource | null, setupProgress?: SetupProgressCallbacks): Promise<CrawlerOrchestrator>;
423
+ /**
424
+ * Re-fetch pages named by an operator-supplied URL list, importing any
425
+ * URL the archive does not yet track as a new inventory seed.
426
+ *
427
+ * `recrawl` is `retryFailed`'s un-scrape combined with `inventory`'s
428
+ * novel-URL ingestion, run inside one `.bak`-protected window: URLs in
429
+ * `recrawlUrls` that already exist as `content_items` rows are reset back
430
+ * to pending via {@link Archive.resetPagesByUrls} (see that method for the
431
+ * conservative exclusion rules — redirect sources, intentionally-skipped
432
+ * pages, and external pages are matched but never reset), while URLs the
433
+ * archive has never seen are ingested exactly as `inventory` does (see
434
+ * {@link CrawlerOrchestrator.inventory}'s JSDoc for that half's contract).
435
+ * Existing *resources* matched by the list are neither resettable nor
436
+ * novel — `resource_items` is first-write-wins (a known deviation, see
437
+ * ARCHITECTURE.md), so a resource re-fetch would not update anything; the
438
+ * function reports how many list entries fell into this bucket via
439
+ * `setupProgress.onLog` without acting on them.
440
+ *
441
+ * Unlike `inventory`, whose sole early-return condition is "no novel
442
+ * URLs", `recrawl` also has existing pages to act on — the `.bak` is
443
+ * skipped only when BOTH `existingPageUrls` (reset candidates) AND
444
+ * `novelUrls` (ingestion candidates) are empty.
445
+ *
446
+ * **Strict-pending gap**: `getCrawlingState()`'s pending set only includes
447
+ * a `scraped = 0` row that is either anchor-referenced or explicitly
448
+ * labelled (see that function's JSDoc). When `recrawlUrls` contains pages
449
+ * that link to each other, resetting one page also deletes its outgoing
450
+ * `anchor_edges` — so a `source = 'crawled'` sibling that was reset in the
451
+ * same pass can lose its only anchor referrer and fall out of the strict
452
+ * pending set, silently skipping its re-fetch. `retryFailed` never hits
453
+ * this because a failed page's referrers are not themselves reset. The
454
+ * fix: every URL `Archive.resetPagesByUrls` actually reset is merged into
455
+ * the pending list handed to `Crawler#resume` regardless of what the
456
+ * strict scan finds, deduplicated by `LinkList.add`'s `protocolAgnosticKey`
457
+ * check. A Ctrl+C between the reset and the scrape phase loses this
458
+ * synthetic merge (it lives only in memory) — `crawl --resume` recovers
459
+ * whatever the strict-pending scan finds on its own, and re-running
460
+ * `--recrawl` with the same list recovers the rest, matching the
461
+ * "un-picked seeds" recovery contract `getCrawlingState`'s JSDoc already
462
+ * documents for `inventory`.
463
+ *
464
+ * **Stale analyze findings**: resetting a page deletes its
465
+ * `analysis_violations` rows (see {@link resetPagesByUrls}'s JSDoc) so a
466
+ * re-fetched page never shows findings from HTML that no longer exists,
467
+ * but other `analyze` outputs (e.g. Discrepancies plugin reports) are not
468
+ * page-scoped and cannot be selectively invalidated. When at least one
469
+ * page was reset, a `crawlSessionNotice` is emitted after the crawl
470
+ * completes recommending `analyze` be re-run before the next `report`.
471
+ * @param archivePath - Absolute or relative path to the existing `.nitpicker`.
472
+ * @param recrawlUrls - URLs to match against the archive (existing pages
473
+ * are reset; unknown URLs are ingested as new inventory seeds).
474
+ * @param options - Optional config overrides applied on top of the archived config.
475
+ * @param initializedCallback - Optional callback invoked after initialization but before crawling resumes.
476
+ * @param source - The CLI's already-read URL list source bytes, archived
477
+ * for audit purposes — see {@link InventorySource}. `null` for
478
+ * programmatic callers with no source file.
479
+ * @param setupProgress - Optional progress callbacks for the setup phase
480
+ * (untar, `.bak` copy, URL classification, reset, seed ingestion, state
481
+ * rebuild) that runs before `initializedCallback` — see
482
+ * {@link SetupProgressCallbacks} for why this can't go through the
483
+ * orchestrator's event emitter (issue #294).
484
+ * @returns The orchestrator instance after the recrawl completes.
485
+ * @throws {Error} When `recrawlUrls` is empty or the archive is in list mode.
486
+ * @throws {PendingUrlsRemainError} When the crawl session ends with pages still pending after exhausting auto-retry.
487
+ */
488
+ static recrawl(archivePath: string, recrawlUrls: string[], options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback, source?: InventorySource | null, setupProgress?: SetupProgressCallbacks): Promise<CrawlerOrchestrator>;
331
489
  /**
332
490
  * Re-fetch previously-failed pages in an existing `.nitpicker` archive.
333
491
  *
@@ -351,7 +509,10 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
351
509
  *
352
510
  * A `<archive>.bak` is created before any DB mutation and removed on success;
353
511
  * if the crawl throws, the backup is restored to keep the original archive
354
- * intact.
512
+ * intact — except when the crawl ends with {@link PendingUrlsRemainError}
513
+ * (issue #350), where the un-packaged stub itself is the recovery path and
514
+ * the backup is instead left untouched (deleted, not restored — see
515
+ * {@link CrawlerOrchestrator.#abandonBackupOnPendingRemains}).
355
516
  *
356
517
  * List-mode archives (`info.fromList === true`) are rejected for the same
357
518
  * reason as {@link CrawlerOrchestrator.append}: their pages are metadata-only.
@@ -364,6 +525,7 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
364
525
  * this can't go through the orchestrator's event emitter (issue #294).
365
526
  * @returns The orchestrator instance after the retry crawl completes.
366
527
  * @throws {Error} When the archive is in list mode or has no parseable roots.
528
+ * @throws {PendingUrlsRemainError} When the crawl session ends with pages still pending after exhausting auto-retry.
367
529
  */
368
530
  static retryFailed(archivePath: string, options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback, setupProgress?: SetupProgressCallbacks): Promise<CrawlerOrchestrator>;
369
531
  /**
@@ -385,6 +547,7 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
385
547
  * tmpDir IS the source of truth).
386
548
  * @returns A promise that resolves to the CrawlerOrchestrator instance after crawling completes.
387
549
  * @throws {Error} If the archived URL is invalid.
550
+ * @throws {PendingUrlsRemainError} When the crawl session ends with pages still pending after exhausting auto-retry.
388
551
  */
389
552
  static resume(stubPath: string, options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback, setupProgress?: SetupProgressCallbacks): Promise<CrawlerOrchestrator>;
390
553
  }