bluera-knowledge 0.11.20 → 0.11.21
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-plugin/plugin.json +1 -1
- package/CHANGELOG.md +16 -0
- package/README.md +42 -5
- package/commands/crawl.md +7 -7
- package/commands/search.md +9 -2
- package/dist/{chunk-MQGRQ2EG.js → chunk-C4SYGLAI.js} +27 -7
- package/dist/chunk-C4SYGLAI.js.map +1 -0
- package/dist/{chunk-ZSKQIMD7.js → chunk-CC6EGZ4D.js} +48 -8
- package/dist/chunk-CC6EGZ4D.js.map +1 -0
- package/dist/{chunk-Q2ZGPJ66.js → chunk-QCSFBMYW.js} +2 -2
- package/dist/index.js +64 -12
- 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/analysis/code-graph.test.ts +30 -0
- package/src/analysis/code-graph.ts +10 -2
- package/src/cli/commands/store.test.ts +78 -0
- package/src/cli/commands/store.ts +19 -0
- package/src/cli/commands/sync.test.ts +1 -1
- package/src/cli/commands/sync.ts +50 -1
- package/src/mcp/commands/sync.commands.test.ts +94 -6
- package/src/mcp/commands/sync.commands.ts +36 -6
- package/src/mcp/handlers/search.handler.ts +3 -1
- package/src/mcp/handlers/store.handler.test.ts +3 -0
- package/src/mcp/handlers/store.handler.ts +5 -2
- package/src/mcp/schemas/index.test.ts +36 -0
- package/src/mcp/schemas/index.ts +6 -0
- package/src/mcp/server.ts +11 -0
- package/src/services/code-graph.service.ts +11 -1
- package/src/services/job.service.test.ts +23 -0
- package/src/services/job.service.ts +10 -6
- package/vitest.config.ts +1 -1
- package/dist/chunk-MQGRQ2EG.js.map +0 -1
- package/dist/chunk-ZSKQIMD7.js.map +0 -1
- /package/dist/{chunk-Q2ZGPJ66.js.map → chunk-QCSFBMYW.js.map} +0 -0
|
@@ -29,10 +29,46 @@ describe('MCP Schema Validation', () => {
|
|
|
29
29
|
it('should use defaults for optional fields', () => {
|
|
30
30
|
const result = SearchArgsSchema.parse({ query: 'test' });
|
|
31
31
|
|
|
32
|
+
expect(result.mode).toBe('hybrid');
|
|
32
33
|
expect(result.detail).toBe('minimal');
|
|
33
34
|
expect(result.limit).toBe(10);
|
|
34
35
|
});
|
|
35
36
|
|
|
37
|
+
it('should validate mode enum', () => {
|
|
38
|
+
expect(() => SearchArgsSchema.parse({ query: 'test', mode: 'invalid' })).toThrow();
|
|
39
|
+
|
|
40
|
+
const vector = SearchArgsSchema.parse({ query: 'test', mode: 'vector' });
|
|
41
|
+
expect(vector.mode).toBe('vector');
|
|
42
|
+
|
|
43
|
+
const fts = SearchArgsSchema.parse({ query: 'test', mode: 'fts' });
|
|
44
|
+
expect(fts.mode).toBe('fts');
|
|
45
|
+
|
|
46
|
+
const hybrid = SearchArgsSchema.parse({ query: 'test', mode: 'hybrid' });
|
|
47
|
+
expect(hybrid.mode).toBe('hybrid');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should validate threshold', () => {
|
|
51
|
+
const result = SearchArgsSchema.parse({ query: 'test', threshold: 0.5 });
|
|
52
|
+
expect(result.threshold).toBe(0.5);
|
|
53
|
+
|
|
54
|
+
// Edge cases
|
|
55
|
+
const min = SearchArgsSchema.parse({ query: 'test', threshold: 0 });
|
|
56
|
+
expect(min.threshold).toBe(0);
|
|
57
|
+
|
|
58
|
+
const max = SearchArgsSchema.parse({ query: 'test', threshold: 1 });
|
|
59
|
+
expect(max.threshold).toBe(1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should reject invalid threshold', () => {
|
|
63
|
+
expect(() => SearchArgsSchema.parse({ query: 'test', threshold: -0.1 })).toThrow(
|
|
64
|
+
'threshold must be between 0 and 1'
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(() => SearchArgsSchema.parse({ query: 'test', threshold: 1.1 })).toThrow(
|
|
68
|
+
'threshold must be between 0 and 1'
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
36
72
|
it('should reject empty query', () => {
|
|
37
73
|
expect(() => SearchArgsSchema.parse({ query: '' })).toThrow(
|
|
38
74
|
'Query must be a non-empty string'
|
package/src/mcp/schemas/index.ts
CHANGED
|
@@ -25,9 +25,15 @@ export const SearchArgsSchema = z.object({
|
|
|
25
25
|
'find-documentation',
|
|
26
26
|
])
|
|
27
27
|
.optional(),
|
|
28
|
+
mode: z.enum(['vector', 'fts', 'hybrid']).default('hybrid'),
|
|
28
29
|
detail: z.enum(['minimal', 'contextual', 'full']).default('minimal'),
|
|
29
30
|
limit: z.number().int().positive().default(10),
|
|
30
31
|
stores: z.array(z.string()).optional(),
|
|
32
|
+
threshold: z
|
|
33
|
+
.number()
|
|
34
|
+
.min(0, 'threshold must be between 0 and 1')
|
|
35
|
+
.max(1, 'threshold must be between 0 and 1')
|
|
36
|
+
.optional(),
|
|
31
37
|
minRelevance: z
|
|
32
38
|
.number()
|
|
33
39
|
.min(0, 'minRelevance must be between 0 and 1')
|
package/src/mcp/server.ts
CHANGED
|
@@ -61,6 +61,13 @@ export function createMCPServer(options: MCPServerOptions): Server {
|
|
|
61
61
|
],
|
|
62
62
|
description: 'Search intent for better ranking',
|
|
63
63
|
},
|
|
64
|
+
mode: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
enum: ['vector', 'fts', 'hybrid'],
|
|
67
|
+
default: 'hybrid',
|
|
68
|
+
description:
|
|
69
|
+
'Search mode: vector (embeddings only), fts (full-text only), hybrid (both, default)',
|
|
70
|
+
},
|
|
64
71
|
detail: {
|
|
65
72
|
type: 'string',
|
|
66
73
|
enum: ['minimal', 'contextual', 'full'],
|
|
@@ -78,6 +85,10 @@ export function createMCPServer(options: MCPServerOptions): Server {
|
|
|
78
85
|
items: { type: 'string' },
|
|
79
86
|
description: 'Specific store IDs to search (optional)',
|
|
80
87
|
},
|
|
88
|
+
threshold: {
|
|
89
|
+
type: 'number',
|
|
90
|
+
description: 'Minimum normalized score (0-1). Filters out low-relevance results.',
|
|
91
|
+
},
|
|
81
92
|
minRelevance: {
|
|
82
93
|
type: 'number',
|
|
83
94
|
description:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
1
|
+
import { readFile, writeFile, mkdir, rm } from 'node:fs/promises';
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
3
|
import { ASTParser } from '../analysis/ast-parser.js';
|
|
4
4
|
import { CodeGraph, type GraphNode } from '../analysis/code-graph.js';
|
|
@@ -120,6 +120,16 @@ export class CodeGraphService {
|
|
|
120
120
|
await writeFile(graphPath, JSON.stringify(serialized, null, 2));
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Delete the code graph file for a store.
|
|
125
|
+
* Silently succeeds if the file doesn't exist.
|
|
126
|
+
*/
|
|
127
|
+
async deleteGraph(storeId: StoreId): Promise<void> {
|
|
128
|
+
const graphPath = this.getGraphPath(storeId);
|
|
129
|
+
await rm(graphPath, { force: true });
|
|
130
|
+
this.graphCache.delete(storeId);
|
|
131
|
+
}
|
|
132
|
+
|
|
123
133
|
/**
|
|
124
134
|
* Load a code graph for a store.
|
|
125
135
|
* Returns undefined if no graph exists.
|
|
@@ -24,6 +24,29 @@ describe('JobService', () => {
|
|
|
24
24
|
const jobsDir = join(tempDir, 'jobs');
|
|
25
25
|
expect(existsSync(jobsDir)).toBe(true);
|
|
26
26
|
});
|
|
27
|
+
|
|
28
|
+
it('throws when dataDir not provided and HOME/USERPROFILE undefined', () => {
|
|
29
|
+
const originalHome = process.env['HOME'];
|
|
30
|
+
const originalUserProfile = process.env['USERPROFILE'];
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
delete process.env['HOME'];
|
|
34
|
+
delete process.env['USERPROFILE'];
|
|
35
|
+
|
|
36
|
+
// Should throw instead of falling back to current directory
|
|
37
|
+
expect(() => new JobService()).toThrow(
|
|
38
|
+
'HOME or USERPROFILE environment variable is required'
|
|
39
|
+
);
|
|
40
|
+
} finally {
|
|
41
|
+
// Restore environment
|
|
42
|
+
if (originalHome !== undefined) {
|
|
43
|
+
process.env['HOME'] = originalHome;
|
|
44
|
+
}
|
|
45
|
+
if (originalUserProfile !== undefined) {
|
|
46
|
+
process.env['USERPROFILE'] = originalUserProfile;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
27
50
|
});
|
|
28
51
|
|
|
29
52
|
describe('createJob', () => {
|
|
@@ -9,12 +9,16 @@ export class JobService {
|
|
|
9
9
|
|
|
10
10
|
constructor(dataDir?: string) {
|
|
11
11
|
// Default to ~/.local/share/bluera-knowledge/jobs
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
)
|
|
12
|
+
let baseDir: string;
|
|
13
|
+
if (dataDir !== undefined) {
|
|
14
|
+
baseDir = dataDir;
|
|
15
|
+
} else {
|
|
16
|
+
const homeDir = process.env['HOME'] ?? process.env['USERPROFILE'];
|
|
17
|
+
if (homeDir === undefined) {
|
|
18
|
+
throw new Error('HOME or USERPROFILE environment variable is required');
|
|
19
|
+
}
|
|
20
|
+
baseDir = path.join(homeDir, '.local/share/bluera-knowledge');
|
|
21
|
+
}
|
|
18
22
|
this.jobsDir = path.join(baseDir, 'jobs');
|
|
19
23
|
|
|
20
24
|
// Ensure jobs directory exists
|