bluera-knowledge 0.9.31 → 0.9.32
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/commands/code-review.md +15 -0
- package/.claude/skills/code-review-repo/skill.md +62 -0
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +2 -0
- package/dist/{chunk-RWSXP3PQ.js → chunk-6PBP5DVD.js} +14 -2
- package/dist/{chunk-RWSXP3PQ.js.map → chunk-6PBP5DVD.js.map} +1 -1
- package/dist/{chunk-2SJHNRXD.js → chunk-RST4XGRL.js} +2 -2
- package/dist/{chunk-OGEY66FZ.js → chunk-WT2DAEO7.js} +2 -2
- package/dist/index.js +8 -4
- 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/crawl.ts +6 -1
- package/src/db/lance.test.ts +59 -0
- package/src/db/lance.ts +15 -0
- package/src/services/index.ts +2 -1
- package/src/services/services.test.ts +36 -0
- package/tests/integration/e2e-workflow.test.ts +2 -0
- package/BUGS-FOUND.md +0 -71
- /package/dist/{chunk-2SJHNRXD.js.map → chunk-RST4XGRL.js.map} +0 -0
- /package/dist/{chunk-OGEY66FZ.js.map → chunk-WT2DAEO7.js.map} +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
IntelligentCrawler
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-RST4XGRL.js";
|
|
5
5
|
import {
|
|
6
6
|
JobService,
|
|
7
7
|
createDocumentId,
|
|
8
8
|
createServices,
|
|
9
9
|
createStoreId
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-6PBP5DVD.js";
|
|
11
11
|
import "../chunk-L2YVNC63.js";
|
|
12
12
|
|
|
13
13
|
// src/workers/background-worker-cli.ts
|
package/package.json
CHANGED
|
@@ -82,6 +82,7 @@ export function createCrawlCommand(getOptions: () => GlobalOptions): Command {
|
|
|
82
82
|
const webChunker = ChunkingService.forContentType('web');
|
|
83
83
|
let pagesIndexed = 0;
|
|
84
84
|
let chunksCreated = 0;
|
|
85
|
+
let exitCode = 0;
|
|
85
86
|
|
|
86
87
|
// Listen for progress events
|
|
87
88
|
crawler.on('progress', (progress: CrawlProgress) => {
|
|
@@ -184,10 +185,14 @@ export function createCrawlCommand(getOptions: () => GlobalOptions): Command {
|
|
|
184
185
|
} else {
|
|
185
186
|
console.error(`Error: ${message}`);
|
|
186
187
|
}
|
|
187
|
-
|
|
188
|
+
exitCode = 6;
|
|
188
189
|
} finally {
|
|
189
190
|
await crawler.stop();
|
|
190
191
|
await destroyServices(services);
|
|
191
192
|
}
|
|
193
|
+
|
|
194
|
+
if (exitCode !== 0) {
|
|
195
|
+
process.exit(exitCode);
|
|
196
|
+
}
|
|
192
197
|
});
|
|
193
198
|
}
|
package/src/db/lance.test.ts
CHANGED
|
@@ -323,6 +323,65 @@ describe('LanceStore', () => {
|
|
|
323
323
|
});
|
|
324
324
|
});
|
|
325
325
|
|
|
326
|
+
describe('closeAsync', () => {
|
|
327
|
+
it('returns a promise that resolves after cleanup', async () => {
|
|
328
|
+
const asyncCloseStoreId = createStoreId('async-close-test');
|
|
329
|
+
const asyncStore = new LanceStore(tempDir);
|
|
330
|
+
await asyncStore.initialize(asyncCloseStoreId);
|
|
331
|
+
|
|
332
|
+
const doc = {
|
|
333
|
+
id: createDocumentId('async-close-doc'),
|
|
334
|
+
content: 'test content for async close',
|
|
335
|
+
vector: new Array(384).fill(0.1),
|
|
336
|
+
metadata: {
|
|
337
|
+
type: 'file' as const,
|
|
338
|
+
storeId: asyncCloseStoreId,
|
|
339
|
+
indexedAt: new Date(),
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
await asyncStore.addDocuments(asyncCloseStoreId, [doc]);
|
|
344
|
+
|
|
345
|
+
// closeAsync should return a promise
|
|
346
|
+
const result = asyncStore.closeAsync();
|
|
347
|
+
expect(result).toBeInstanceOf(Promise);
|
|
348
|
+
await expect(result).resolves.not.toThrow();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('handles closeAsync when never initialized', async () => {
|
|
352
|
+
const uninitializedStore = new LanceStore(tempDir);
|
|
353
|
+
|
|
354
|
+
// Should not throw even when never initialized
|
|
355
|
+
await expect(uninitializedStore.closeAsync()).resolves.not.toThrow();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('allows native threads time to complete', async () => {
|
|
359
|
+
const timedStoreId = createStoreId('timed-close-test');
|
|
360
|
+
const timedStore = new LanceStore(tempDir);
|
|
361
|
+
await timedStore.initialize(timedStoreId);
|
|
362
|
+
|
|
363
|
+
const doc = {
|
|
364
|
+
id: createDocumentId('timed-doc'),
|
|
365
|
+
content: 'test',
|
|
366
|
+
vector: new Array(384).fill(0.1),
|
|
367
|
+
metadata: {
|
|
368
|
+
type: 'file' as const,
|
|
369
|
+
storeId: timedStoreId,
|
|
370
|
+
indexedAt: new Date(),
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
await timedStore.addDocuments(timedStoreId, [doc]);
|
|
375
|
+
|
|
376
|
+
const startTime = Date.now();
|
|
377
|
+
await timedStore.closeAsync();
|
|
378
|
+
const elapsed = Date.now() - startTime;
|
|
379
|
+
|
|
380
|
+
// Should take at least some time for native cleanup
|
|
381
|
+
expect(elapsed).toBeGreaterThanOrEqual(50);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
326
385
|
describe('multiple documents operations', () => {
|
|
327
386
|
it('adds multiple documents at once', async () => {
|
|
328
387
|
const multiStoreId = createStoreId('multi-doc-store');
|
package/src/db/lance.ts
CHANGED
|
@@ -153,6 +153,21 @@ export class LanceStore {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Async close that allows native code cleanup time.
|
|
158
|
+
* Use this before process.exit() to prevent mutex crashes.
|
|
159
|
+
*/
|
|
160
|
+
async closeAsync(): Promise<void> {
|
|
161
|
+
this.tables.clear();
|
|
162
|
+
if (this.connection !== null) {
|
|
163
|
+
this.connection.close();
|
|
164
|
+
this.connection = null;
|
|
165
|
+
// Allow native threads time to complete cleanup
|
|
166
|
+
// LanceDB's native code has background threads that need time
|
|
167
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
156
171
|
private getTableName(storeId: StoreId): string {
|
|
157
172
|
return `documents_${storeId}`;
|
|
158
173
|
}
|
package/src/services/index.ts
CHANGED
|
@@ -80,7 +80,8 @@ export async function createServices(
|
|
|
80
80
|
export async function destroyServices(services: ServiceContainer): Promise<void> {
|
|
81
81
|
logger.info('Shutting down services');
|
|
82
82
|
try {
|
|
83
|
-
|
|
83
|
+
// Use async close to allow native threads time to cleanup
|
|
84
|
+
await services.lance.closeAsync();
|
|
84
85
|
} catch (e) {
|
|
85
86
|
logger.error({ error: e }, 'Error closing LanceStore');
|
|
86
87
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { destroyServices, type ServiceContainer } from './index.js';
|
|
3
3
|
import type { PythonBridge } from '../crawl/bridge.js';
|
|
4
|
+
import type { LanceStore } from '../db/lance.js';
|
|
4
5
|
|
|
5
6
|
describe('destroyServices', () => {
|
|
6
7
|
let mockPythonBridge: { stop: ReturnType<typeof vi.fn> };
|
|
8
|
+
let mockLance: { closeAsync: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
|
|
7
9
|
let mockServices: ServiceContainer;
|
|
8
10
|
|
|
9
11
|
beforeEach(() => {
|
|
@@ -11,8 +13,14 @@ describe('destroyServices', () => {
|
|
|
11
13
|
stop: vi.fn().mockResolvedValue(undefined),
|
|
12
14
|
};
|
|
13
15
|
|
|
16
|
+
mockLance = {
|
|
17
|
+
closeAsync: vi.fn().mockResolvedValue(undefined),
|
|
18
|
+
close: vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
|
|
14
21
|
mockServices = {
|
|
15
22
|
pythonBridge: mockPythonBridge as unknown as PythonBridge,
|
|
23
|
+
lance: mockLance as unknown as LanceStore,
|
|
16
24
|
} as unknown as ServiceContainer;
|
|
17
25
|
});
|
|
18
26
|
|
|
@@ -35,4 +43,32 @@ describe('destroyServices', () => {
|
|
|
35
43
|
|
|
36
44
|
expect(mockPythonBridge.stop).toHaveBeenCalledTimes(2);
|
|
37
45
|
});
|
|
46
|
+
|
|
47
|
+
it('calls closeAsync on LanceStore for native cleanup', async () => {
|
|
48
|
+
await destroyServices(mockServices);
|
|
49
|
+
|
|
50
|
+
expect(mockLance.closeAsync).toHaveBeenCalledTimes(1);
|
|
51
|
+
// Should use async version, not sync
|
|
52
|
+
expect(mockLance.close).not.toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('handles LanceStore closeAsync errors gracefully', async () => {
|
|
56
|
+
mockLance.closeAsync.mockRejectedValue(new Error('closeAsync failed'));
|
|
57
|
+
|
|
58
|
+
// Should not throw even if closeAsync fails
|
|
59
|
+
await expect(destroyServices(mockServices)).resolves.not.toThrow();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('waits for LanceStore async cleanup before returning', async () => {
|
|
63
|
+
let closeCompleted = false;
|
|
64
|
+
mockLance.closeAsync.mockImplementation(async () => {
|
|
65
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
66
|
+
closeCompleted = true;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await destroyServices(mockServices);
|
|
70
|
+
|
|
71
|
+
// Should have waited for closeAsync to complete
|
|
72
|
+
expect(closeCompleted).toBe(true);
|
|
73
|
+
});
|
|
38
74
|
});
|
package/BUGS-FOUND.md
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# Bugs Found During API Testing (v0.9.30)
|
|
2
|
-
|
|
3
|
-
## Fix Status
|
|
4
|
-
|
|
5
|
-
| Bug | Status | Tests Added | Notes |
|
|
6
|
-
|-----|--------|-------------|-------|
|
|
7
|
-
| #1 | **FIXED** | ✅ | URL detection in store.ts |
|
|
8
|
-
| #2 | **PARTIAL** | ✅ | Mutex crash - CLI only, not affecting plugin |
|
|
9
|
-
| #3 | **FIXED** | ✅ | Empty name validation added |
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Bug #1: `store create --type repo --source <url>` doesn't clone repos
|
|
14
|
-
|
|
15
|
-
**Status:** FIXED
|
|
16
|
-
|
|
17
|
-
**File:** `src/cli/commands/store.ts:58-65`
|
|
18
|
-
|
|
19
|
-
**Fix:** Added URL detection to route to `url` parameter instead of `path`:
|
|
20
|
-
```typescript
|
|
21
|
-
const isUrl = options.source.startsWith('http://') || options.source.startsWith('https://');
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Bug #2: Non-existent store lookup causes mutex crash (CLI only)
|
|
27
|
-
|
|
28
|
-
**Status:** PARTIALLY FIXED - Does not affect Claude Code plugin
|
|
29
|
-
|
|
30
|
-
**Note:** This bug only affects the npm CLI package, not the Claude Code plugin. The MCP server is a long-running process that doesn't call `destroyServices()` on each request.
|
|
31
|
-
|
|
32
|
-
**Fixes applied:**
|
|
33
|
-
1. Added `close()` method to LanceStore that calls `connection.close()`
|
|
34
|
-
2. Added try-catch around cleanup in `destroyServices()`
|
|
35
|
-
3. Moved `process.exit()` after `finally` block
|
|
36
|
-
|
|
37
|
-
**Remaining issue:** Native code mutex crash still occurs during CLI cleanup. This appears to be a race condition in LanceDB's native code during process exit.
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## Bug #3: Empty store name accepted
|
|
42
|
-
|
|
43
|
-
**Status:** FIXED
|
|
44
|
-
|
|
45
|
-
**File:** `src/services/store.service.ts:39-42`
|
|
46
|
-
|
|
47
|
-
**Fix:** Added validation at start of `create()`:
|
|
48
|
-
```typescript
|
|
49
|
-
if (!input.name || input.name.trim() === '') {
|
|
50
|
-
return err(new Error('Store name cannot be empty'));
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## Files Modified
|
|
57
|
-
|
|
58
|
-
1. `src/cli/commands/store.ts` - Bug #1 fix (URL detection)
|
|
59
|
-
2. `src/db/lance.ts` - Bug #2 fix (added close() method)
|
|
60
|
-
3. `src/services/index.ts` - Bug #2 fix (cleanup with try-catch)
|
|
61
|
-
4. `src/services/store.service.ts` - Bug #3 fix (empty name validation)
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Test Results After Fixes
|
|
66
|
-
|
|
67
|
-
| Feature | Status |
|
|
68
|
-
|---------|--------|
|
|
69
|
-
| `store create --type repo --source <url>` | **FIXED** - Now clones correctly |
|
|
70
|
-
| Empty store name validation | **FIXED** - Returns error |
|
|
71
|
-
| Mutex crash on error (CLI) | PARTIAL - Still crashes but doesn't affect plugin |
|
|
File without changes
|
|
File without changes
|