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.
- package/.claude/skills/atomic-commits/SKILL.md +1 -1
- package/.claude-plugin/plugin.json +18 -0
- package/.versionrc.json +1 -1
- package/CHANGELOG.md +29 -0
- package/dist/{chunk-TIGPI3BE.js → chunk-CUHYSPRV.js} +2 -2
- package/dist/{chunk-HUEWT6U5.js → chunk-DWAIT2OD.js} +3 -2
- package/dist/{chunk-HUEWT6U5.js.map → chunk-DWAIT2OD.js.map} +1 -1
- package/dist/{chunk-IZWOEBFM.js → chunk-MQE32YY6.js} +2 -2
- package/dist/index.js +31 -8
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +2 -2
- package/dist/workers/background-worker-cli.js +2 -2
- package/package.json +1 -1
- package/src/cli/commands/search.test.ts +139 -12
- package/src/cli/commands/search.ts +34 -5
- package/src/db/lance.ts +2 -1
- package/src/services/index.ts +3 -0
- package/mcp.plugin.json +0 -12
- package/plugin.json +0 -8
- /package/dist/{chunk-TIGPI3BE.js.map → chunk-CUHYSPRV.js.map} +0 -0
- /package/dist/{chunk-IZWOEBFM.js.map → chunk-MQE32YY6.js.map} +0 -0
package/dist/mcp/server.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
IntelligentCrawler
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-MQE32YY6.js";
|
|
5
5
|
import {
|
|
6
6
|
JobService,
|
|
7
7
|
createDocumentId,
|
|
8
8
|
createServices,
|
|
9
9
|
createStoreId
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-DWAIT2OD.js";
|
|
11
11
|
import "../chunk-6FHWC36B.js";
|
|
12
12
|
|
|
13
13
|
// src/workers/background-worker.ts
|
package/package.json
CHANGED
|
@@ -373,7 +373,7 @@ describe('search command execution', () => {
|
|
|
373
373
|
});
|
|
374
374
|
|
|
375
375
|
describe('error handling', () => {
|
|
376
|
-
it('
|
|
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
|
-
|
|
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
|
|
395
|
+
// Must call destroyServices before setting exitCode per CLAUDE.md
|
|
392
396
|
expect(destroyServices).toHaveBeenCalled();
|
|
393
397
|
});
|
|
394
398
|
|
|
395
|
-
it('
|
|
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
|
-
|
|
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
|
|
416
|
+
// Must call destroyServices before setting exitCode per CLAUDE.md
|
|
409
417
|
expect(destroyServices).toHaveBeenCalled();
|
|
410
418
|
});
|
|
411
419
|
|
|
412
|
-
it('
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
+
// Increased from 100ms to 200ms for more reliable cleanup
|
|
166
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
|
package/src/services/index.ts
CHANGED
|
@@ -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
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
|
-
}
|
|
File without changes
|
|
File without changes
|