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.
Files changed (36) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/README.md +42 -5
  4. package/commands/crawl.md +7 -7
  5. package/commands/search.md +9 -2
  6. package/dist/{chunk-MQGRQ2EG.js → chunk-C4SYGLAI.js} +27 -7
  7. package/dist/chunk-C4SYGLAI.js.map +1 -0
  8. package/dist/{chunk-ZSKQIMD7.js → chunk-CC6EGZ4D.js} +48 -8
  9. package/dist/chunk-CC6EGZ4D.js.map +1 -0
  10. package/dist/{chunk-Q2ZGPJ66.js → chunk-QCSFBMYW.js} +2 -2
  11. package/dist/index.js +64 -12
  12. package/dist/index.js.map +1 -1
  13. package/dist/mcp/server.js +2 -2
  14. package/dist/workers/background-worker-cli.js +2 -2
  15. package/package.json +1 -1
  16. package/src/analysis/code-graph.test.ts +30 -0
  17. package/src/analysis/code-graph.ts +10 -2
  18. package/src/cli/commands/store.test.ts +78 -0
  19. package/src/cli/commands/store.ts +19 -0
  20. package/src/cli/commands/sync.test.ts +1 -1
  21. package/src/cli/commands/sync.ts +50 -1
  22. package/src/mcp/commands/sync.commands.test.ts +94 -6
  23. package/src/mcp/commands/sync.commands.ts +36 -6
  24. package/src/mcp/handlers/search.handler.ts +3 -1
  25. package/src/mcp/handlers/store.handler.test.ts +3 -0
  26. package/src/mcp/handlers/store.handler.ts +5 -2
  27. package/src/mcp/schemas/index.test.ts +36 -0
  28. package/src/mcp/schemas/index.ts +6 -0
  29. package/src/mcp/server.ts +11 -0
  30. package/src/services/code-graph.service.ts +11 -1
  31. package/src/services/job.service.test.ts +23 -0
  32. package/src/services/job.service.ts +10 -6
  33. package/vitest.config.ts +1 -1
  34. package/dist/chunk-MQGRQ2EG.js.map +0 -1
  35. package/dist/chunk-ZSKQIMD7.js.map +0 -1
  36. /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'
@@ -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
- const baseDir =
13
- dataDir ??
14
- path.join(
15
- process.env['HOME'] ?? process.env['USERPROFILE'] ?? '.',
16
- '.local/share/bluera-knowledge'
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
package/vitest.config.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defineConfig } from 'vitest/config';
2
2
 
3
- const coverageThreshold = 81;
3
+ const coverageThreshold = 80.5;
4
4
 
5
5
  export default defineConfig({
6
6
  test: {