bluera-knowledge 0.9.40 → 0.9.41

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.
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  createMCPServer,
3
3
  runMCPServer
4
- } from "../chunk-TIGPI3BE.js";
5
- import "../chunk-HUEWT6U5.js";
4
+ } from "../chunk-CUHYSPRV.js";
5
+ import "../chunk-DWAIT2OD.js";
6
6
  import "../chunk-6FHWC36B.js";
7
7
  export {
8
8
  createMCPServer,
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  IntelligentCrawler
4
- } from "../chunk-IZWOEBFM.js";
4
+ } from "../chunk-MQE32YY6.js";
5
5
  import {
6
6
  JobService,
7
7
  createDocumentId,
8
8
  createServices,
9
9
  createStoreId
10
- } from "../chunk-HUEWT6U5.js";
10
+ } from "../chunk-DWAIT2OD.js";
11
11
  import "../chunk-6FHWC36B.js";
12
12
 
13
13
  // src/workers/background-worker.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bluera-knowledge",
3
- "version": "0.9.40",
3
+ "version": "0.9.41",
4
4
  "description": "CLI tool for managing knowledge stores with semantic search",
5
5
  "type": "module",
6
6
  "bin": {
@@ -373,7 +373,7 @@ describe('search command execution', () => {
373
373
  });
374
374
 
375
375
  describe('error handling', () => {
376
- it('exits with code 3 when specified store not found', async () => {
376
+ it('sets exitCode to 3 when specified store not found', async () => {
377
377
  const { destroyServices } = await import('../../services/index.js');
378
378
 
379
379
  mockServices.store.list.mockResolvedValue([]);
@@ -383,16 +383,20 @@ describe('search command execution', () => {
383
383
  const actionHandler = command._actionHandler;
384
384
  command.parseOptions(['--stores', 'nonexistent']);
385
385
 
386
- await expect(actionHandler(['test query'])).rejects.toThrow('process.exit: 3');
386
+ // Reset exitCode before test
387
+ process.exitCode = undefined;
387
388
 
389
+ // Action handler completes normally but sets exitCode
390
+ await actionHandler(['test query']);
391
+
392
+ expect(process.exitCode).toBe(3);
388
393
  expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Store not found: nonexistent');
389
- expect(processExitSpy).toHaveBeenCalledWith(3);
390
394
  expect(mockServices.search.search).not.toHaveBeenCalled();
391
- // Must call destroyServices before process.exit per CLAUDE.md
395
+ // Must call destroyServices before setting exitCode per CLAUDE.md
392
396
  expect(destroyServices).toHaveBeenCalled();
393
397
  });
394
398
 
395
- it('exits with code 1 when no stores exist', async () => {
399
+ it('sets exitCode to 1 when no stores exist', async () => {
396
400
  const { destroyServices } = await import('../../services/index.js');
397
401
 
398
402
  mockServices.store.list.mockResolvedValue([]);
@@ -400,16 +404,20 @@ describe('search command execution', () => {
400
404
  const command = createSearchCommand(getOptions);
401
405
  const actionHandler = command._actionHandler;
402
406
 
403
- await expect(actionHandler(['test query'])).rejects.toThrow('process.exit: 1');
407
+ // Reset exitCode before test
408
+ process.exitCode = undefined;
409
+
410
+ // Action handler completes normally but sets exitCode
411
+ await actionHandler(['test query']);
404
412
 
413
+ expect(process.exitCode).toBe(1);
405
414
  expect(consoleErrorSpy).toHaveBeenCalledWith('No stores to search. Create a store first.');
406
- expect(processExitSpy).toHaveBeenCalledWith(1);
407
415
  expect(mockServices.search.search).not.toHaveBeenCalled();
408
- // Must call destroyServices before process.exit per CLAUDE.md
416
+ // Must call destroyServices before setting exitCode per CLAUDE.md
409
417
  expect(destroyServices).toHaveBeenCalled();
410
418
  });
411
419
 
412
- it('exits with code 3 when one of multiple stores not found', async () => {
420
+ it('sets exitCode to 3 when one of multiple stores not found', async () => {
413
421
  const { destroyServices } = await import('../../services/index.js');
414
422
 
415
423
  const mockStores = [{ id: createStoreId('store-1'), name: 'store1', type: 'file' as const }];
@@ -424,11 +432,15 @@ describe('search command execution', () => {
424
432
  const actionHandler = command._actionHandler;
425
433
  command.parseOptions(['--stores', 'store1,nonexistent']);
426
434
 
427
- await expect(actionHandler(['test query'])).rejects.toThrow('process.exit: 3');
435
+ // Reset exitCode before test
436
+ process.exitCode = undefined;
428
437
 
438
+ // Action handler completes normally but sets exitCode
439
+ await actionHandler(['test query']);
440
+
441
+ expect(process.exitCode).toBe(3);
429
442
  expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Store not found: nonexistent');
430
- expect(processExitSpy).toHaveBeenCalledWith(3);
431
- // Must call destroyServices before process.exit per CLAUDE.md
443
+ // Must call destroyServices before setting exitCode per CLAUDE.md
432
444
  expect(destroyServices).toHaveBeenCalled();
433
445
  });
434
446
  });
@@ -701,6 +713,121 @@ describe('search command execution', () => {
701
713
  );
702
714
  });
703
715
 
716
+ it('displays usage stats in contextual detail mode', async () => {
717
+ const mockStores = [{ id: createStoreId('store-1'), name: 'store1', type: 'file' as const }];
718
+
719
+ const mockSearchResponse: SearchResponse = {
720
+ query: 'test query',
721
+ mode: 'hybrid',
722
+ stores: [createStoreId('store-1')],
723
+ results: [
724
+ {
725
+ id: createDocumentId('doc-1'),
726
+ score: 0.95,
727
+ content: 'Test content',
728
+ metadata: {
729
+ type: 'file',
730
+ storeId: createStoreId('store-1'),
731
+ path: '/test/file.ts',
732
+ },
733
+ summary: {
734
+ type: 'function',
735
+ name: 'testFunction',
736
+ signature: 'function testFunction(): void',
737
+ purpose: 'Tests something',
738
+ location: '/test/file.ts:10',
739
+ relevanceReason: 'Matches test query',
740
+ },
741
+ context: {
742
+ interfaces: [],
743
+ keyImports: [],
744
+ relatedConcepts: [],
745
+ usage: {
746
+ calledBy: 5,
747
+ calls: 3,
748
+ },
749
+ },
750
+ },
751
+ ],
752
+ totalResults: 1,
753
+ timeMs: 50,
754
+ };
755
+
756
+ mockServices.store.list.mockResolvedValue(mockStores);
757
+ mockServices.lance.initialize.mockResolvedValue(undefined);
758
+ mockServices.search.search.mockResolvedValue(mockSearchResponse);
759
+
760
+ const command = createSearchCommand(getOptions);
761
+ const actionHandler = command._actionHandler;
762
+ command.parseOptions(['--detail', 'contextual']);
763
+
764
+ await actionHandler(['test query']);
765
+
766
+ // Should display usage stats from code graph
767
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringMatching(/Called by.*5/));
768
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringMatching(/Calls.*3/));
769
+ });
770
+
771
+ it('displays complete code in full detail mode', async () => {
772
+ const mockStores = [{ id: createStoreId('store-1'), name: 'store1', type: 'file' as const }];
773
+
774
+ const mockSearchResponse: SearchResponse = {
775
+ query: 'test query',
776
+ mode: 'hybrid',
777
+ stores: [createStoreId('store-1')],
778
+ results: [
779
+ {
780
+ id: createDocumentId('doc-1'),
781
+ score: 0.95,
782
+ content: 'Test content',
783
+ metadata: {
784
+ type: 'file',
785
+ storeId: createStoreId('store-1'),
786
+ path: '/test/file.ts',
787
+ },
788
+ summary: {
789
+ type: 'function',
790
+ name: 'testFunction',
791
+ signature: 'function testFunction(): void',
792
+ purpose: 'Tests something',
793
+ location: '/test/file.ts:10',
794
+ relevanceReason: 'Matches test query',
795
+ },
796
+ context: {
797
+ interfaces: [],
798
+ keyImports: [],
799
+ relatedConcepts: [],
800
+ usage: { calledBy: 0, calls: 0 },
801
+ },
802
+ full: {
803
+ completeCode: 'function testFunction(): void {\n console.log("test");\n return;\n}',
804
+ relatedCode: { callers: [], callees: [] },
805
+ documentation: 'This function tests something important.',
806
+ },
807
+ },
808
+ ],
809
+ totalResults: 1,
810
+ timeMs: 50,
811
+ };
812
+
813
+ mockServices.store.list.mockResolvedValue(mockStores);
814
+ mockServices.lance.initialize.mockResolvedValue(undefined);
815
+ mockServices.search.search.mockResolvedValue(mockSearchResponse);
816
+
817
+ const command = createSearchCommand(getOptions);
818
+ const actionHandler = command._actionHandler;
819
+ command.parseOptions(['--detail', 'full']);
820
+
821
+ await actionHandler(['test query']);
822
+
823
+ // Should display complete code
824
+ expect(consoleLogSpy).toHaveBeenCalledWith(
825
+ expect.stringContaining('function testFunction()')
826
+ );
827
+ // Should display documentation
828
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Doc:'));
829
+ });
830
+
704
831
  it('handles results with fallback path display when path is missing', async () => {
705
832
  const mockStores = [{ id: createStoreId('store-1'), name: 'store1', type: 'file' as const }];
706
833
 
@@ -132,11 +132,38 @@ export function createSearchCommand(getOptions: () => GlobalOptions): Command {
132
132
  console.log(` ${r.summary.location}`);
133
133
  console.log(` ${r.summary.purpose}`);
134
134
 
135
+ // Contextual: Show imports, concepts, and usage stats
135
136
  if (r.context && options.detail !== 'minimal') {
136
- console.log(` Imports: ${r.context.keyImports.slice(0, 3).join(', ')}`);
137
- console.log(
138
- ` Related: ${r.context.relatedConcepts.slice(0, 3).join(', ')}`
139
- );
137
+ if (r.context.keyImports.length > 0) {
138
+ console.log(` Imports: ${r.context.keyImports.slice(0, 3).join(', ')}`);
139
+ }
140
+ if (r.context.relatedConcepts.length > 0) {
141
+ console.log(
142
+ ` Related: ${r.context.relatedConcepts.slice(0, 3).join(', ')}`
143
+ );
144
+ }
145
+ // Show usage stats from code graph
146
+ const { calledBy, calls } = r.context.usage;
147
+ if (calledBy > 0 || calls > 0) {
148
+ console.log(
149
+ ` Usage: Called by ${String(calledBy)} | Calls ${String(calls)}`
150
+ );
151
+ }
152
+ }
153
+
154
+ // Full: Show complete code and documentation
155
+ if (r.full && options.detail === 'full') {
156
+ if (r.full.completeCode) {
157
+ console.log(` ---`);
158
+ const codeLines = r.full.completeCode.split('\n');
159
+ console.log(` ${codeLines.slice(0, 10).join('\n ')}`);
160
+ if (codeLines.length > 10) {
161
+ console.log(` ... (truncated)`);
162
+ }
163
+ }
164
+ if (r.full.documentation) {
165
+ console.log(` Doc: ${r.full.documentation.slice(0, 100)}`);
166
+ }
140
167
  }
141
168
 
142
169
  console.log();
@@ -159,7 +186,9 @@ export function createSearchCommand(getOptions: () => GlobalOptions): Command {
159
186
  }
160
187
 
161
188
  if (exitCode !== 0) {
162
- process.exit(exitCode);
189
+ // Set exit code and let Node.js exit naturally after event loop drains
190
+ // Using process.exit() causes mutex crashes from native code (LanceDB, tree-sitter)
191
+ process.exitCode = exitCode;
163
192
  }
164
193
  }
165
194
  );
package/src/db/lance.ts CHANGED
@@ -162,7 +162,8 @@ export class LanceStore {
162
162
  this.connection = null;
163
163
  // Allow native threads time to complete cleanup
164
164
  // LanceDB's native code has background threads that need time
165
- await new Promise((resolve) => setTimeout(resolve, 100));
165
+ // Increased from 100ms to 200ms for more reliable cleanup
166
+ await new Promise((resolve) => setTimeout(resolve, 200));
166
167
  }
167
168
  }
168
169
 
@@ -87,5 +87,8 @@ export async function destroyServices(services: ServiceContainer): Promise<void>
87
87
  } catch (e) {
88
88
  logger.error({ error: e }, 'Error stopping Python bridge');
89
89
  }
90
+ // Additional delay to allow native threads (LanceDB, tree-sitter, transformers)
91
+ // to fully complete their cleanup before process exit
92
+ await new Promise((resolve) => setTimeout(resolve, 100));
90
93
  await shutdownLogger();
91
94
  }
package/mcp.plugin.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "bluera-knowledge": {
4
- "command": "node",
5
- "args": ["${CLAUDE_PLUGIN_ROOT}/dist/mcp/server.js"],
6
- "env": {
7
- "DATA_DIR": ".bluera/bluera-knowledge/data",
8
- "CONFIG_PATH": ".bluera/bluera-knowledge/config.json"
9
- }
10
- }
11
- }
12
- }
package/plugin.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "name": "bluera-knowledge",
3
- "version": "0.9.40",
4
- "description": "Clone repos, crawl docs, search locally. Fast, authoritative answers for AI coding agents.",
5
- "commands": "./commands",
6
- "hooks": "./hooks/hooks.json",
7
- "mcpServers": "./mcp.plugin.json"
8
- }