@threadbase-sh/scanner 0.9.1 → 0.9.3
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/dist/cli.js +61 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +60 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +60 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1845,6 +1845,19 @@ var ConversationFilesRepo = class {
|
|
|
1845
1845
|
const rows = this.db.prepare("SELECT absolute_path FROM conversation_files WHERE status != 'deleted'").all();
|
|
1846
1846
|
return rows.map((r) => r.absolute_path);
|
|
1847
1847
|
}
|
|
1848
|
+
// Active file paths belonging to any of the given accounts. Backs the
|
|
1849
|
+
// deletion-reconcile so a scan that covered only some accounts can't mark
|
|
1850
|
+
// another account's files deleted (they live in the same shared index.db but
|
|
1851
|
+
// a different profile owns them). Returns [] for an empty account list.
|
|
1852
|
+
activePathsByAccounts(accounts) {
|
|
1853
|
+
if (accounts.length === 0) return [];
|
|
1854
|
+
const placeholders = accounts.map(() => "?").join(", ");
|
|
1855
|
+
const rows = this.db.prepare(
|
|
1856
|
+
`SELECT absolute_path FROM conversation_files
|
|
1857
|
+
WHERE status != 'deleted' AND account IN (${placeholders})`
|
|
1858
|
+
).all(...accounts);
|
|
1859
|
+
return rows.map((r) => r.absolute_path);
|
|
1860
|
+
}
|
|
1848
1861
|
// Active files whose immediate parent is exactly parentDir (no nested
|
|
1849
1862
|
// subdirectories). Backs the dir-mtime gate's reuse path: a project dir with
|
|
1850
1863
|
// an unchanged mtime and no nested files can skip the glob entirely.
|
|
@@ -2217,8 +2230,15 @@ var PersistentEngine = class {
|
|
|
2217
2230
|
scanned += batch.length;
|
|
2218
2231
|
options.onProgress?.(scanned, discovered.length);
|
|
2219
2232
|
}
|
|
2233
|
+
const coveredAccounts = /* @__PURE__ */ new Set();
|
|
2234
|
+
if (enabled.includes(CLAUDE_CODE_PROVIDER)) {
|
|
2235
|
+
for (const p of activeProfiles) coveredAccounts.add(p.id);
|
|
2236
|
+
}
|
|
2237
|
+
if (enabled.includes(CODEX_CLI_PROVIDER) && (options.codexRoots?.length ?? 0) > 0) {
|
|
2238
|
+
coveredAccounts.add("codex");
|
|
2239
|
+
}
|
|
2220
2240
|
const seen = new Set(discovered.map((d) => d.filePath));
|
|
2221
|
-
for (const path of this.files.
|
|
2241
|
+
for (const path of this.files.activePathsByAccounts([...coveredAccounts])) {
|
|
2222
2242
|
if (!seen.has(path)) this.markDeleted(path);
|
|
2223
2243
|
}
|
|
2224
2244
|
log.info({ scanned, indexed: this.conversations.count() }, "persistent: indexAll complete");
|
|
@@ -2528,10 +2548,21 @@ var ConversationScanner = class {
|
|
|
2528
2548
|
dbPath;
|
|
2529
2549
|
sidecarEnabled;
|
|
2530
2550
|
engineInstance = null;
|
|
2551
|
+
// The most recent scan()'s promise while it is still running, or null when
|
|
2552
|
+
// idle. close() awaits it before closing the SQLite handle so a fire-and-
|
|
2553
|
+
// forget scan can't have its DB shut mid-indexAll ("database connection is
|
|
2554
|
+
// not open"). Tracks the latest scan only — concurrent scans on one instance
|
|
2555
|
+
// aren't a supported pattern (a single writer DB), and the latest resolving
|
|
2556
|
+
// implies earlier ones already settled in practice.
|
|
2557
|
+
inFlightScan = null;
|
|
2531
2558
|
emitter = new import_events.EventEmitter();
|
|
2532
2559
|
watcher = null;
|
|
2533
2560
|
queue = null;
|
|
2534
2561
|
periodicTimer = null;
|
|
2562
|
+
// The in-flight periodicReconcile() job, if one is mid-run. unwatch() awaits
|
|
2563
|
+
// it before dropping the queue/engine so its post-await engine.files access
|
|
2564
|
+
// can't hit a closed DB (the watch-mode half of Bug #4).
|
|
2565
|
+
inFlightReconcile = null;
|
|
2535
2566
|
constructor(options) {
|
|
2536
2567
|
this.conversationLRU = new LRUCache(options?.conversationCacheSize ?? 5);
|
|
2537
2568
|
if (options?.persistent === false) {
|
|
@@ -2554,14 +2585,31 @@ var ConversationScanner = class {
|
|
|
2554
2585
|
return this.engineInstance;
|
|
2555
2586
|
}
|
|
2556
2587
|
// Release the SQLite connection. No-op in legacy mode. Safe to call
|
|
2557
|
-
// repeatedly.
|
|
2558
|
-
//
|
|
2559
|
-
close()
|
|
2560
|
-
|
|
2588
|
+
// repeatedly. Awaits watcher teardown AND any in-flight scan before closing
|
|
2589
|
+
// the DB handle, so a fire-and-forget scan() can never have its connection
|
|
2590
|
+
// shut mid-indexAll() ("database connection is not open"). This is why close()
|
|
2591
|
+
// is async — callers should await it during shutdown. (Scanner review Bug #4.)
|
|
2592
|
+
async close() {
|
|
2593
|
+
if (this.watcher || this.queue || this.periodicTimer) await this.unwatch();
|
|
2594
|
+
if (this.inFlightScan) {
|
|
2595
|
+
try {
|
|
2596
|
+
await this.inFlightScan;
|
|
2597
|
+
} catch {
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2561
2600
|
this.engineInstance?.close();
|
|
2562
2601
|
this.engineInstance = null;
|
|
2563
2602
|
}
|
|
2564
2603
|
async scan(options = {}) {
|
|
2604
|
+
const promise = this.runScan(options);
|
|
2605
|
+
this.inFlightScan = promise;
|
|
2606
|
+
try {
|
|
2607
|
+
return await promise;
|
|
2608
|
+
} finally {
|
|
2609
|
+
if (this.inFlightScan === promise) this.inFlightScan = null;
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
async runScan(options) {
|
|
2565
2613
|
const profiles = await this.resolveProfiles(options.profiles);
|
|
2566
2614
|
const activeProfiles = profiles.filter((p) => p.enabled && p.scanHistory !== false);
|
|
2567
2615
|
this.lastTier = resolveTier(options.tier ?? "standard", options.tiers);
|
|
@@ -2997,7 +3045,10 @@ var ConversationScanner = class {
|
|
|
2997
3045
|
const periodicMs = options.periodicMs ?? 6e4;
|
|
2998
3046
|
if (periodicMs > 0) {
|
|
2999
3047
|
this.periodicTimer = setInterval(() => {
|
|
3000
|
-
|
|
3048
|
+
const job = this.periodicReconcile(activeProfiles).finally(() => {
|
|
3049
|
+
if (this.inFlightReconcile === job) this.inFlightReconcile = null;
|
|
3050
|
+
});
|
|
3051
|
+
this.inFlightReconcile = job;
|
|
3001
3052
|
}, periodicMs);
|
|
3002
3053
|
this.periodicTimer.unref?.();
|
|
3003
3054
|
}
|
|
@@ -3041,6 +3092,9 @@ var ConversationScanner = class {
|
|
|
3041
3092
|
clearInterval(this.periodicTimer);
|
|
3042
3093
|
this.periodicTimer = null;
|
|
3043
3094
|
}
|
|
3095
|
+
if (this.inFlightReconcile) {
|
|
3096
|
+
await this.inFlightReconcile;
|
|
3097
|
+
}
|
|
3044
3098
|
if (this.watcher) {
|
|
3045
3099
|
await this.watcher.stop();
|
|
3046
3100
|
this.watcher = null;
|