bluera-knowledge 0.11.17 → 0.11.19

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 (42) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +48 -0
  3. package/dist/{chunk-ZDEO4WJT.js → chunk-GOAOBPOA.js} +22 -70
  4. package/dist/chunk-GOAOBPOA.js.map +1 -0
  5. package/dist/{chunk-6FHWC36B.js → chunk-HRQD3MPH.js} +8 -6
  6. package/dist/chunk-HRQD3MPH.js.map +1 -0
  7. package/dist/{chunk-ZZNABJMQ.js → chunk-QEHSDQTL.js} +79 -13
  8. package/dist/chunk-QEHSDQTL.js.map +1 -0
  9. package/dist/{chunk-5NUI6JL6.js → chunk-VP4VZULK.js} +2 -2
  10. package/dist/index.js +36 -18
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp/server.js +3 -3
  13. package/dist/watch.service-OPLKIDFQ.js +7 -0
  14. package/dist/workers/background-worker-cli.js +3 -3
  15. package/package.json +1 -1
  16. package/src/cli/commands/crawl.ts +1 -1
  17. package/src/cli/commands/index-cmd.test.ts +14 -4
  18. package/src/cli/commands/index-cmd.ts +11 -4
  19. package/src/cli/commands/store.test.ts +211 -18
  20. package/src/cli/commands/store.ts +26 -8
  21. package/src/crawl/article-converter.test.ts +30 -61
  22. package/src/crawl/article-converter.ts +2 -8
  23. package/src/crawl/bridge.test.ts +14 -0
  24. package/src/crawl/bridge.ts +17 -5
  25. package/src/crawl/intelligent-crawler.test.ts +65 -76
  26. package/src/crawl/intelligent-crawler.ts +33 -69
  27. package/src/plugin/git-clone.test.ts +44 -0
  28. package/src/plugin/git-clone.ts +4 -0
  29. package/src/services/code-unit.service.test.ts +59 -6
  30. package/src/services/code-unit.service.ts +47 -2
  31. package/src/services/index.ts +19 -3
  32. package/src/services/job.service.test.ts +10 -7
  33. package/src/services/job.service.ts +12 -6
  34. package/src/services/services.test.ts +19 -6
  35. package/src/services/watch.service.test.ts +80 -56
  36. package/src/services/watch.service.ts +9 -6
  37. package/dist/chunk-6FHWC36B.js.map +0 -1
  38. package/dist/chunk-ZDEO4WJT.js.map +0 -1
  39. package/dist/chunk-ZZNABJMQ.js.map +0 -1
  40. package/dist/watch.service-BJV3TI3F.js +0 -7
  41. /package/dist/{chunk-5NUI6JL6.js.map → chunk-VP4VZULK.js.map} +0 -0
  42. /package/dist/{watch.service-BJV3TI3F.js.map → watch.service-OPLKIDFQ.js.map} +0 -0
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  createMCPServer,
3
3
  runMCPServer
4
- } from "../chunk-5NUI6JL6.js";
5
- import "../chunk-ZZNABJMQ.js";
6
- import "../chunk-6FHWC36B.js";
4
+ } from "../chunk-VP4VZULK.js";
5
+ import "../chunk-QEHSDQTL.js";
6
+ import "../chunk-HRQD3MPH.js";
7
7
  export {
8
8
  createMCPServer,
9
9
  runMCPServer
@@ -0,0 +1,7 @@
1
+ import {
2
+ WatchService
3
+ } from "./chunk-HRQD3MPH.js";
4
+ export {
5
+ WatchService
6
+ };
7
+ //# sourceMappingURL=watch.service-OPLKIDFQ.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  IntelligentCrawler
4
- } from "../chunk-ZDEO4WJT.js";
4
+ } from "../chunk-GOAOBPOA.js";
5
5
  import {
6
6
  JobService,
7
7
  createDocumentId,
@@ -9,8 +9,8 @@ import {
9
9
  createServices,
10
10
  createStoreId,
11
11
  shutdownLogger
12
- } from "../chunk-ZZNABJMQ.js";
13
- import "../chunk-6FHWC36B.js";
12
+ } from "../chunk-QEHSDQTL.js";
13
+ import "../chunk-HRQD3MPH.js";
14
14
 
15
15
  // src/workers/background-worker.ts
16
16
  import { createHash } from "crypto";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bluera-knowledge",
3
- "version": "0.11.17",
3
+ "version": "0.11.19",
4
4
  "description": "CLI tool for managing knowledge stores with semantic search",
5
5
  "type": "module",
6
6
  "bin": {
@@ -77,7 +77,7 @@ export function createCrawlCommand(getOptions: () => GlobalOptions): Command {
77
77
  store = existingStore;
78
78
  }
79
79
 
80
- const maxPages = cmdOptions.maxPages !== undefined ? parseInt(cmdOptions.maxPages) : 50;
80
+ const maxPages = cmdOptions.maxPages !== undefined ? parseInt(cmdOptions.maxPages, 10) : 50;
81
81
 
82
82
  // Use spinner in interactive mode
83
83
  const isInteractive =
@@ -407,7 +407,12 @@ describe('createIndexCommand - Execution Tests', () => {
407
407
 
408
408
  expect(mockServices.store.getByIdOrName).toHaveBeenCalledWith('test-store');
409
409
  expect(WatchService).toHaveBeenCalledWith(mockServices.index, mockServices.lance);
410
- expect(mockWatchService.watch).toHaveBeenCalledWith(mockStore, 1000, expect.any(Function));
410
+ expect(mockWatchService.watch).toHaveBeenCalledWith(
411
+ mockStore,
412
+ 1000,
413
+ expect.any(Function),
414
+ expect.any(Function)
415
+ );
411
416
  });
412
417
 
413
418
  it('watches a repo store successfully', async () => {
@@ -467,7 +472,12 @@ describe('createIndexCommand - Execution Tests', () => {
467
472
  const action = watchCmd!._actionHandler;
468
473
  await action(['test']);
469
474
 
470
- expect(mockWatchService.watch).toHaveBeenCalledWith(mockStore, 2500, expect.any(Function));
475
+ expect(mockWatchService.watch).toHaveBeenCalledWith(
476
+ mockStore,
477
+ 2500,
478
+ expect.any(Function),
479
+ expect.any(Function)
480
+ );
471
481
  });
472
482
 
473
483
  it('outputs watching message in normal mode', async () => {
@@ -550,8 +560,8 @@ describe('createIndexCommand - Execution Tests', () => {
550
560
 
551
561
  let capturedCallback: (() => void) | undefined;
552
562
  const mockWatchService = {
553
- watch: vi.fn((store, debounce, callback) => {
554
- capturedCallback = callback;
563
+ watch: vi.fn((store, debounce, onReindex, onError) => {
564
+ capturedCallback = onReindex;
555
565
  }),
556
566
  unwatchAll: vi.fn(),
557
567
  };
@@ -100,11 +100,18 @@ export function createIndexCommand(getOptions: () => GlobalOptions): Command {
100
100
  if (globalOpts.quiet !== true) {
101
101
  console.log(`Watching ${store.name} for changes...`);
102
102
  }
103
- await watchService.watch(store, parseInt(options.debounce ?? '1000', 10), () => {
104
- if (globalOpts.quiet !== true) {
105
- console.log(`Re-indexed ${store.name}`);
103
+ await watchService.watch(
104
+ store,
105
+ parseInt(options.debounce ?? '1000', 10),
106
+ () => {
107
+ if (globalOpts.quiet !== true) {
108
+ console.log(`Re-indexed ${store.name}`);
109
+ }
110
+ },
111
+ (error: Error) => {
112
+ console.error(`Watch error: ${error.message}`);
106
113
  }
107
- });
114
+ );
108
115
 
109
116
  // Keep process alive
110
117
  process.on('SIGINT', () => {
@@ -1,6 +1,6 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
1
+ import { describe, it, expect, vi, beforeEach, afterEach, type MockInstance } from 'vitest';
2
2
  import { createStoreCommand } from './store.js';
3
- import { createServices } from '../../services/index.js';
3
+ import { createServices, destroyServices } from '../../services/index.js';
4
4
  import type { GlobalOptions } from '../program.js';
5
5
  import type { FileStore, RepoStore, WebStore } from '../../types/store.js';
6
6
  import { createStoreId } from '../../types/brands.js';
@@ -10,11 +10,22 @@ vi.mock('../../services/index.js', () => ({
10
10
  destroyServices: vi.fn().mockResolvedValue(undefined),
11
11
  }));
12
12
 
13
+ interface MockStoreService {
14
+ list: MockInstance;
15
+ getByIdOrName: MockInstance;
16
+ create: MockInstance;
17
+ delete: MockInstance;
18
+ }
19
+
20
+ interface MockServices {
21
+ store: MockStoreService;
22
+ }
23
+
13
24
  describe('store command execution', () => {
14
- let mockServices: any;
15
- let consoleLogSpy: any;
16
- let consoleErrorSpy: any;
17
- let processExitSpy: any;
25
+ let mockServices: MockServices;
26
+ let consoleLogSpy: MockInstance;
27
+ let consoleErrorSpy: MockInstance;
28
+ let processExitSpy: MockInstance;
18
29
  let getOptions: () => GlobalOptions;
19
30
 
20
31
  beforeEach(() => {
@@ -402,10 +413,12 @@ describe('store command execution', () => {
402
413
  const actionHandler = createCommand?._actionHandler;
403
414
 
404
415
  createCommand.parseOptions(['--type', 'file', '--source', '/path/to/files']);
405
- await expect(actionHandler!(['duplicate-store'])).rejects.toThrow('process.exit: 1');
416
+ await actionHandler!(['duplicate-store']);
406
417
 
407
418
  expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Store already exists');
408
- expect(processExitSpy).toHaveBeenCalledWith(1);
419
+ expect(process.exitCode).toBe(1);
420
+ // Reset for other tests
421
+ process.exitCode = undefined;
409
422
  });
410
423
  });
411
424
 
@@ -536,10 +549,12 @@ describe('store command execution', () => {
536
549
  const infoCommand = command.commands.find((c) => c.name() === 'info');
537
550
  const actionHandler = infoCommand?._actionHandler;
538
551
 
539
- await expect(actionHandler!(['nonexistent'])).rejects.toThrow('process.exit: 3');
552
+ await actionHandler!(['nonexistent']);
540
553
 
541
554
  expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Store not found: nonexistent');
542
- expect(processExitSpy).toHaveBeenCalledWith(3);
555
+ expect(process.exitCode).toBe(3);
556
+ // Reset for other tests
557
+ process.exitCode = undefined;
543
558
  });
544
559
 
545
560
  it('can lookup store by ID', async () => {
@@ -629,11 +644,13 @@ describe('store command execution', () => {
629
644
  const actionHandler = deleteCommand?._actionHandler;
630
645
 
631
646
  deleteCommand.parseOptions(['--force']);
632
- await expect(actionHandler!(['nonexistent'])).rejects.toThrow('process.exit: 3');
647
+ await actionHandler!(['nonexistent']);
633
648
 
634
649
  expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Store not found: nonexistent');
635
- expect(processExitSpy).toHaveBeenCalledWith(3);
650
+ expect(process.exitCode).toBe(3);
636
651
  expect(mockServices.store.delete).not.toHaveBeenCalled();
652
+ // Reset for other tests
653
+ process.exitCode = undefined;
637
654
  });
638
655
 
639
656
  it('exits with code 1 when deletion fails', async () => {
@@ -659,10 +676,12 @@ describe('store command execution', () => {
659
676
  const actionHandler = deleteCommand?._actionHandler;
660
677
 
661
678
  deleteCommand.parseOptions(['--force']);
662
- await expect(actionHandler!(['locked-store'])).rejects.toThrow('process.exit: 1');
679
+ await actionHandler!(['locked-store']);
663
680
 
664
681
  expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Store is locked');
665
- expect(processExitSpy).toHaveBeenCalledWith(1);
682
+ expect(process.exitCode).toBe(1);
683
+ // Reset for other tests
684
+ process.exitCode = undefined;
666
685
  });
667
686
 
668
687
  it('exits with code 1 when force not provided in non-TTY mode', async () => {
@@ -688,12 +707,12 @@ describe('store command execution', () => {
688
707
  const deleteCommand = command.commands.find((c) => c.name() === 'delete');
689
708
  const actionHandler = deleteCommand?._actionHandler;
690
709
 
691
- await expect(actionHandler!(['my-store'])).rejects.toThrow('process.exit: 1');
710
+ await actionHandler!(['my-store']);
692
711
 
693
712
  expect(consoleErrorSpy).toHaveBeenCalledWith(
694
713
  'Error: Use --force or -y to delete without confirmation in non-interactive mode'
695
714
  );
696
- expect(processExitSpy).toHaveBeenCalledWith(1);
715
+ expect(process.exitCode).toBe(1);
697
716
  expect(mockServices.store.delete).not.toHaveBeenCalled();
698
717
 
699
718
  // Restore original value
@@ -701,6 +720,8 @@ describe('store command execution', () => {
701
720
  value: originalIsTTY,
702
721
  configurable: true,
703
722
  });
723
+ // Reset for other tests
724
+ process.exitCode = undefined;
704
725
  });
705
726
 
706
727
  it('prompts for confirmation in TTY mode when user confirms', async () => {
@@ -788,10 +809,11 @@ describe('store command execution', () => {
788
809
  const deleteCommand = command.commands.find((c) => c.name() === 'delete');
789
810
  const actionHandler = deleteCommand?._actionHandler;
790
811
 
791
- await expect(actionHandler!(['my-store'])).rejects.toThrow('process.exit: 0');
812
+ await actionHandler!(['my-store']);
792
813
 
793
814
  expect(consoleLogSpy).toHaveBeenCalledWith('Cancelled.');
794
- expect(processExitSpy).toHaveBeenCalledWith(0);
815
+ // exitCode stays 0 (undefined) for user-initiated cancellation - not an error
816
+ expect(process.exitCode).toBeUndefined();
795
817
  expect(mockServices.store.delete).not.toHaveBeenCalled();
796
818
 
797
819
  // Restore original value
@@ -1096,4 +1118,175 @@ describe('store command execution', () => {
1096
1118
  });
1097
1119
  });
1098
1120
  });
1121
+
1122
+ describe('destroyServices cleanup on error paths', () => {
1123
+ it('calls destroyServices when store info returns store not found', async () => {
1124
+ mockServices.store.getByIdOrName.mockResolvedValue(undefined);
1125
+
1126
+ const command = createStoreCommand(getOptions);
1127
+ const infoCommand = command.commands.find((c) => c.name() === 'info');
1128
+ const actionHandler = infoCommand?._actionHandler;
1129
+
1130
+ await actionHandler!(['nonexistent']);
1131
+
1132
+ // destroyServices should be called before setting exitCode
1133
+ expect(destroyServices).toHaveBeenCalledWith(mockServices);
1134
+ expect(process.exitCode).toBe(3);
1135
+ process.exitCode = undefined;
1136
+ });
1137
+
1138
+ it('calls destroyServices when store delete returns store not found', async () => {
1139
+ mockServices.store.getByIdOrName.mockResolvedValue(undefined);
1140
+
1141
+ const command = createStoreCommand(getOptions);
1142
+ const deleteCommand = command.commands.find((c) => c.name() === 'delete');
1143
+ const actionHandler = deleteCommand?._actionHandler;
1144
+
1145
+ deleteCommand.parseOptions(['--force']);
1146
+ await actionHandler!(['nonexistent']);
1147
+
1148
+ // destroyServices should be called before setting exitCode
1149
+ expect(destroyServices).toHaveBeenCalledWith(mockServices);
1150
+ expect(process.exitCode).toBe(3);
1151
+ process.exitCode = undefined;
1152
+ });
1153
+
1154
+ it('calls destroyServices when delete requires --force in non-TTY mode', async () => {
1155
+ const mockStore: FileStore = {
1156
+ id: createStoreId('store-1'),
1157
+ name: 'my-store',
1158
+ type: 'file',
1159
+ path: '/path/to/files',
1160
+ createdAt: new Date(),
1161
+ updatedAt: new Date(),
1162
+ };
1163
+
1164
+ mockServices.store.getByIdOrName.mockResolvedValue(mockStore);
1165
+
1166
+ // Mock non-TTY mode
1167
+ const originalIsTTY = process.stdin.isTTY;
1168
+ Object.defineProperty(process.stdin, 'isTTY', {
1169
+ value: false,
1170
+ configurable: true,
1171
+ });
1172
+
1173
+ const command = createStoreCommand(getOptions);
1174
+ const deleteCommand = command.commands.find((c) => c.name() === 'delete');
1175
+ const actionHandler = deleteCommand?._actionHandler;
1176
+
1177
+ await actionHandler!(['my-store']);
1178
+
1179
+ // destroyServices should be called before setting exitCode
1180
+ expect(destroyServices).toHaveBeenCalledWith(mockServices);
1181
+ expect(process.exitCode).toBe(1);
1182
+
1183
+ // Restore
1184
+ Object.defineProperty(process.stdin, 'isTTY', {
1185
+ value: originalIsTTY,
1186
+ configurable: true,
1187
+ });
1188
+ process.exitCode = undefined;
1189
+ });
1190
+
1191
+ it('calls destroyServices when deletion fails', async () => {
1192
+ const mockStore: FileStore = {
1193
+ id: createStoreId('store-1'),
1194
+ name: 'locked-store',
1195
+ type: 'file',
1196
+ path: '/path/to/files',
1197
+ createdAt: new Date(),
1198
+ updatedAt: new Date(),
1199
+ };
1200
+
1201
+ mockServices.store.getByIdOrName.mockResolvedValue(mockStore);
1202
+ mockServices.store.delete.mockResolvedValue({
1203
+ success: false,
1204
+ error: {
1205
+ message: 'Store is locked',
1206
+ },
1207
+ });
1208
+
1209
+ const command = createStoreCommand(getOptions);
1210
+ const deleteCommand = command.commands.find((c) => c.name() === 'delete');
1211
+ const actionHandler = deleteCommand?._actionHandler;
1212
+
1213
+ deleteCommand.parseOptions(['--force']);
1214
+ await actionHandler!(['locked-store']);
1215
+
1216
+ // destroyServices should be called before setting exitCode
1217
+ expect(destroyServices).toHaveBeenCalledWith(mockServices);
1218
+ expect(process.exitCode).toBe(1);
1219
+ process.exitCode = undefined;
1220
+ });
1221
+
1222
+ it('calls destroyServices when user cancels delete in TTY mode', async () => {
1223
+ const mockStore: FileStore = {
1224
+ id: createStoreId('store-1'),
1225
+ name: 'my-store',
1226
+ type: 'file',
1227
+ path: '/path/to/files',
1228
+ createdAt: new Date(),
1229
+ updatedAt: new Date(),
1230
+ };
1231
+
1232
+ mockServices.store.getByIdOrName.mockResolvedValue(mockStore);
1233
+
1234
+ // Mock TTY mode
1235
+ const originalIsTTY = process.stdin.isTTY;
1236
+ Object.defineProperty(process.stdin, 'isTTY', {
1237
+ value: true,
1238
+ configurable: true,
1239
+ });
1240
+
1241
+ // Mock readline to simulate user typing 'n'
1242
+ const mockReadline = {
1243
+ question: vi.fn((prompt: string, callback: (answer: string) => void) => {
1244
+ callback('n');
1245
+ }),
1246
+ close: vi.fn(),
1247
+ };
1248
+
1249
+ vi.doMock('node:readline', () => ({
1250
+ createInterface: vi.fn(() => mockReadline),
1251
+ }));
1252
+
1253
+ const command = createStoreCommand(getOptions);
1254
+ const deleteCommand = command.commands.find((c) => c.name() === 'delete');
1255
+ const actionHandler = deleteCommand?._actionHandler;
1256
+
1257
+ await actionHandler!(['my-store']);
1258
+
1259
+ // destroyServices should be called even for user cancellation
1260
+ expect(destroyServices).toHaveBeenCalledWith(mockServices);
1261
+ // exitCode stays undefined (0) for user-initiated cancellation
1262
+ expect(process.exitCode).toBeUndefined();
1263
+
1264
+ // Restore
1265
+ Object.defineProperty(process.stdin, 'isTTY', {
1266
+ value: originalIsTTY,
1267
+ configurable: true,
1268
+ });
1269
+ });
1270
+
1271
+ it('calls destroyServices when create fails', async () => {
1272
+ mockServices.store.create.mockResolvedValue({
1273
+ success: false,
1274
+ error: {
1275
+ message: 'Store already exists',
1276
+ },
1277
+ });
1278
+
1279
+ const command = createStoreCommand(getOptions);
1280
+ const createCommand = command.commands.find((c) => c.name() === 'create');
1281
+ const actionHandler = createCommand?._actionHandler;
1282
+
1283
+ createCommand.parseOptions(['--type', 'file', '--source', '/path/to/files']);
1284
+ await actionHandler!(['duplicate-store']);
1285
+
1286
+ // destroyServices should be called before setting exitCode
1287
+ expect(destroyServices).toHaveBeenCalledWith(mockServices);
1288
+ expect(process.exitCode).toBe(1);
1289
+ process.exitCode = undefined;
1290
+ });
1291
+ });
1099
1292
  });
@@ -97,7 +97,9 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
97
97
  await destroyServices(services);
98
98
  }
99
99
  if (exitCode !== 0) {
100
- process.exit(exitCode);
100
+ // Set exit code and let Node.js exit naturally after event loop drains
101
+ // Using process.exit() causes mutex crashes from native code (LanceDB, tree-sitter)
102
+ process.exitCode = exitCode;
101
103
  }
102
104
  }
103
105
  );
@@ -108,12 +110,14 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
108
110
  .action(async (storeIdOrName: string) => {
109
111
  const globalOpts = getOptions();
110
112
  const services = await createServices(globalOpts.config, globalOpts.dataDir);
111
- try {
113
+ let exitCode = 0;
114
+ storeInfo: try {
112
115
  const s = await services.store.getByIdOrName(storeIdOrName);
113
116
 
114
117
  if (s === undefined) {
115
118
  console.error(`Error: Store not found: ${storeIdOrName}`);
116
- process.exit(3);
119
+ exitCode = 3;
120
+ break storeInfo;
117
121
  }
118
122
 
119
123
  if (globalOpts.format === 'json') {
@@ -132,6 +136,11 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
132
136
  } finally {
133
137
  await destroyServices(services);
134
138
  }
139
+ if (exitCode !== 0) {
140
+ // Set exit code and let Node.js exit naturally after event loop drains
141
+ // Using process.exit() causes mutex crashes from native code (LanceDB, tree-sitter)
142
+ process.exitCode = exitCode;
143
+ }
135
144
  });
136
145
 
137
146
  store
@@ -142,12 +151,14 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
142
151
  .action(async (storeIdOrName: string, options: { force?: boolean; yes?: boolean }) => {
143
152
  const globalOpts = getOptions();
144
153
  const services = await createServices(globalOpts.config, globalOpts.dataDir);
145
- try {
154
+ let exitCode = 0;
155
+ storeDelete: try {
146
156
  const s = await services.store.getByIdOrName(storeIdOrName);
147
157
 
148
158
  if (s === undefined) {
149
159
  console.error(`Error: Store not found: ${storeIdOrName}`);
150
- process.exit(3);
160
+ exitCode = 3;
161
+ break storeDelete;
151
162
  }
152
163
 
153
164
  // Require --force or -y in non-TTY mode, prompt in TTY mode
@@ -157,7 +168,8 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
157
168
  console.error(
158
169
  'Error: Use --force or -y to delete without confirmation in non-interactive mode'
159
170
  );
160
- process.exit(1);
171
+ exitCode = 1;
172
+ break storeDelete;
161
173
  }
162
174
  // Interactive confirmation
163
175
  const readline = await import('node:readline');
@@ -171,7 +183,8 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
171
183
  rl.close();
172
184
  if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
173
185
  console.log('Cancelled.');
174
- process.exit(0);
186
+ // exitCode stays 0 for user-initiated cancellation
187
+ break storeDelete;
175
188
  }
176
189
  }
177
190
 
@@ -181,11 +194,16 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
181
194
  console.log(`Deleted store: ${s.name}`);
182
195
  } else {
183
196
  console.error(`Error: ${result.error.message}`);
184
- process.exit(1);
197
+ exitCode = 1;
185
198
  }
186
199
  } finally {
187
200
  await destroyServices(services);
188
201
  }
202
+ if (exitCode !== 0) {
203
+ // Set exit code and let Node.js exit naturally after event loop drains
204
+ // Using process.exit() causes mutex crashes from native code (LanceDB, tree-sitter)
205
+ process.exitCode = exitCode;
206
+ }
189
207
  });
190
208
 
191
209
  return store;