refactor(app): consume shared indexing core, remove duplicated indexer (Phase 5d-3c-i)

The desktop app now indexes through the shared provider adapters + persist
layer (scripts/providers/{claude,codex}, scripts/persist, scripts/parsing)
instead of maintaining its own parallel indexer. buildIndex shrinks from
~1173 to ~592 lines, eliminating the skill<->app parse duplication that
Phase 5 set out to remove. electron-vite bundles the .ts core from source
with better-sqlite3 injected; the provider->parsing graph stays
node:sqlite-free so nothing drags node:sqlite into the app.

Also fix a misleading log: when a manual rebuild tears down the worker
mid-build, the cancelled background build is a deliberate stop, not a
failure. Guard the service's failure log with the stopped flag so it no
longer prints "Obelisk index build failed: Indexer worker stopped" on
every rebuild.

- CONTEXT.md: provider-adapter + single-persist + node:sqlite-free parsing.
- docs/adr/0005: app builds with electron-vite (TS+ESM), packages with
  electron-builder; preload CJS for sandbox; app consumes core from source.

Verified: full suite 121/121; a node:sqlite-adapter dogfood of the rebuild
path over real data (969 files, 285 sessions, FTS rebuilt) runs clean; app
Rebuild confirmed in real Electron/better-sqlite3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tommy0103
2026-07-09 12:46:02 +08:00
co-authored by Claude Opus 4.8
parent 905c10789a
commit 60d47a852e
6 changed files with 152 additions and 644 deletions
+42
View File
@@ -73,6 +73,48 @@ test('indexer service runs one pending build after an in-flight build finishes',
assert.deepEqual(calls, ['first', 'pending']);
});
test('indexer service does not log a build cancelled by a service stop', async () => {
const timers = manualTimers();
const warnings = [];
let rejectBuild;
const service = createIndexerService({
buildIndex: () => new Promise((_resolve, reject) => { rejectBuild = reject; }),
watchProjects: () => null,
writeHeartbeat: () => {},
timers,
stabilityMs: 0,
logger: { warn: (msg) => warnings.push(msg) },
});
const build = service.runBuildNow('startup');
service.stop(); // manual rebuild path tears the worker down mid-build
rejectBuild(new Error('Indexer worker stopped'));
await build;
assert.deepEqual(warnings, []);
});
test('indexer service logs a build that fails while running', async () => {
const timers = manualTimers();
const warnings = [];
let rejectBuild;
const service = createIndexerService({
buildIndex: () => new Promise((_resolve, reject) => { rejectBuild = reject; }),
watchProjects: () => null,
writeHeartbeat: () => {},
timers,
stabilityMs: 0,
logger: { warn: (msg) => warnings.push(msg) },
});
const build = service.runBuildNow('watch');
rejectBuild(new Error('disk on fire'));
await build;
assert.equal(warnings.length, 1);
assert.match(warnings[0], /Obelisk index build failed: disk on fire/);
});
test('indexer service waits for a stability window before building', async () => {
const timers = manualTimers();
const calls = [];