bluera-knowledge 0.9.31 → 0.9.34
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/hooks/post-edit-check.sh +5 -3
- package/.claude/skills/atomic-commits/SKILL.md +3 -1
- package/.claude/skills/code-review-repo/skill.md +62 -0
- package/.husky/pre-commit +3 -2
- package/.prettierrc +9 -0
- package/.versionrc.json +1 -1
- package/CHANGELOG.md +35 -0
- package/CLAUDE.md +6 -0
- package/README.md +25 -13
- package/bun.lock +277 -33
- package/dist/{chunk-L2YVNC63.js → chunk-6FHWC36B.js} +9 -1
- package/dist/chunk-6FHWC36B.js.map +1 -0
- package/dist/{chunk-2SJHNRXD.js → chunk-DC7CGSGT.js} +288 -241
- package/dist/chunk-DC7CGSGT.js.map +1 -0
- package/dist/{chunk-RWSXP3PQ.js → chunk-WFNPNAAP.js} +3194 -3024
- package/dist/chunk-WFNPNAAP.js.map +1 -0
- package/dist/{chunk-OGEY66FZ.js → chunk-Z2KKVH45.js} +548 -482
- package/dist/chunk-Z2KKVH45.js.map +1 -0
- package/dist/index.js +871 -754
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +3 -3
- package/dist/watch.service-BJV3TI3F.js +7 -0
- package/dist/workers/background-worker-cli.js +46 -45
- package/dist/workers/background-worker-cli.js.map +1 -1
- package/eslint.config.js +43 -1
- package/package.json +18 -11
- package/plugin.json +8 -0
- package/python/requirements.txt +1 -1
- package/src/analysis/ast-parser.test.ts +12 -11
- package/src/analysis/ast-parser.ts +28 -22
- package/src/analysis/code-graph.test.ts +52 -62
- package/src/analysis/code-graph.ts +9 -13
- package/src/analysis/dependency-usage-analyzer.test.ts +91 -271
- package/src/analysis/dependency-usage-analyzer.ts +52 -24
- package/src/analysis/go-ast-parser.test.ts +22 -22
- package/src/analysis/go-ast-parser.ts +18 -25
- package/src/analysis/parser-factory.test.ts +9 -9
- package/src/analysis/parser-factory.ts +3 -3
- package/src/analysis/python-ast-parser.test.ts +27 -27
- package/src/analysis/python-ast-parser.ts +2 -2
- package/src/analysis/repo-url-resolver.test.ts +82 -82
- package/src/analysis/rust-ast-parser.test.ts +19 -19
- package/src/analysis/rust-ast-parser.ts +17 -27
- package/src/analysis/tree-sitter-parser.test.ts +3 -3
- package/src/analysis/tree-sitter-parser.ts +10 -16
- package/src/cli/commands/crawl.test.ts +40 -24
- package/src/cli/commands/crawl.ts +186 -161
- package/src/cli/commands/index-cmd.test.ts +90 -90
- package/src/cli/commands/index-cmd.ts +52 -36
- package/src/cli/commands/mcp.test.ts +6 -6
- package/src/cli/commands/mcp.ts +2 -2
- package/src/cli/commands/plugin-api.test.ts +16 -18
- package/src/cli/commands/plugin-api.ts +9 -6
- package/src/cli/commands/search.test.ts +16 -7
- package/src/cli/commands/search.ts +124 -87
- package/src/cli/commands/serve.test.ts +67 -25
- package/src/cli/commands/serve.ts +18 -3
- package/src/cli/commands/setup.test.ts +176 -101
- package/src/cli/commands/setup.ts +140 -117
- package/src/cli/commands/store.test.ts +82 -53
- package/src/cli/commands/store.ts +56 -37
- package/src/cli/program.ts +2 -2
- package/src/crawl/article-converter.test.ts +4 -1
- package/src/crawl/article-converter.ts +46 -31
- package/src/crawl/bridge.test.ts +240 -132
- package/src/crawl/bridge.ts +87 -30
- package/src/crawl/claude-client.test.ts +124 -56
- package/src/crawl/claude-client.ts +7 -15
- package/src/crawl/intelligent-crawler.test.ts +65 -22
- package/src/crawl/intelligent-crawler.ts +86 -53
- package/src/crawl/markdown-utils.ts +1 -4
- package/src/db/embeddings.ts +4 -6
- package/src/db/lance.test.ts +63 -4
- package/src/db/lance.ts +31 -12
- package/src/index.ts +26 -17
- package/src/logging/index.ts +1 -5
- package/src/logging/logger.ts +3 -5
- package/src/logging/payload.test.ts +1 -1
- package/src/logging/payload.ts +3 -5
- package/src/mcp/commands/index.ts +2 -2
- package/src/mcp/commands/job.commands.ts +12 -18
- package/src/mcp/commands/meta.commands.ts +13 -13
- package/src/mcp/commands/registry.ts +5 -8
- package/src/mcp/commands/store.commands.ts +19 -19
- package/src/mcp/handlers/execute.handler.test.ts +10 -10
- package/src/mcp/handlers/execute.handler.ts +4 -5
- package/src/mcp/handlers/index.ts +10 -14
- package/src/mcp/handlers/job.handler.test.ts +10 -10
- package/src/mcp/handlers/job.handler.ts +22 -25
- package/src/mcp/handlers/search.handler.test.ts +36 -65
- package/src/mcp/handlers/search.handler.ts +135 -104
- package/src/mcp/handlers/store.handler.test.ts +41 -52
- package/src/mcp/handlers/store.handler.ts +108 -88
- package/src/mcp/schemas/index.test.ts +73 -68
- package/src/mcp/schemas/index.ts +18 -12
- package/src/mcp/server.test.ts +1 -1
- package/src/mcp/server.ts +59 -46
- package/src/plugin/commands.test.ts +230 -95
- package/src/plugin/commands.ts +24 -25
- package/src/plugin/dependency-analyzer.test.ts +52 -52
- package/src/plugin/dependency-analyzer.ts +85 -22
- package/src/plugin/git-clone.test.ts +24 -13
- package/src/plugin/git-clone.ts +3 -7
- package/src/server/app.test.ts +109 -109
- package/src/server/app.ts +32 -23
- package/src/server/index.test.ts +64 -66
- package/src/services/chunking.service.test.ts +32 -32
- package/src/services/chunking.service.ts +16 -9
- package/src/services/code-graph.service.test.ts +30 -36
- package/src/services/code-graph.service.ts +24 -10
- package/src/services/code-unit.service.test.ts +55 -11
- package/src/services/code-unit.service.ts +85 -11
- package/src/services/config.service.test.ts +37 -18
- package/src/services/config.service.ts +30 -7
- package/src/services/index.service.test.ts +49 -18
- package/src/services/index.service.ts +98 -48
- package/src/services/index.ts +8 -10
- package/src/services/job.service.test.ts +22 -22
- package/src/services/job.service.ts +18 -18
- package/src/services/project-root.service.test.ts +1 -3
- package/src/services/search.service.test.ts +248 -120
- package/src/services/search.service.ts +286 -156
- package/src/services/services.test.ts +36 -0
- package/src/services/snippet.service.test.ts +14 -6
- package/src/services/snippet.service.ts +7 -5
- package/src/services/store.service.test.ts +68 -29
- package/src/services/store.service.ts +41 -12
- package/src/services/watch.service.test.ts +34 -14
- package/src/services/watch.service.ts +11 -1
- package/src/types/brands.test.ts +3 -1
- package/src/types/index.ts +2 -13
- package/src/types/search.ts +10 -8
- package/src/utils/type-guards.test.ts +20 -15
- package/src/utils/type-guards.ts +1 -1
- package/src/workers/background-worker-cli.ts +2 -2
- package/src/workers/background-worker.test.ts +54 -40
- package/src/workers/background-worker.ts +76 -60
- package/src/workers/spawn-worker.test.ts +22 -10
- package/src/workers/spawn-worker.ts +6 -6
- package/tests/analysis/ast-parser.test.ts +3 -3
- package/tests/analysis/code-graph.test.ts +5 -5
- package/tests/fixtures/code-snippets/api/error-handling.ts +4 -15
- package/tests/fixtures/code-snippets/api/rest-controller.ts +3 -9
- package/tests/fixtures/code-snippets/auth/jwt-auth.ts +5 -21
- package/tests/fixtures/code-snippets/auth/oauth-flow.ts +4 -4
- package/tests/fixtures/code-snippets/database/repository-pattern.ts +11 -3
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/aws-lambda/handler.ts +2 -2
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-pages/handler.ts +1 -1
- package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/serve-static.ts +2 -2
- package/tests/fixtures/corpus/oss-repos/hono/src/client/client.ts +2 -2
- package/tests/fixtures/corpus/oss-repos/hono/src/client/types.ts +22 -20
- package/tests/fixtures/corpus/oss-repos/hono/src/context.ts +13 -10
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/accepts/accepts.ts +10 -7
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/adapter/index.ts +2 -2
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/css/index.ts +1 -1
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/factory/index.ts +16 -16
- package/tests/fixtures/corpus/oss-repos/hono/src/helper/ssg/ssg.ts +2 -2
- package/tests/fixtures/corpus/oss-repos/hono/src/hono-base.ts +3 -3
- package/tests/fixtures/corpus/oss-repos/hono/src/hono.ts +1 -1
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/css.ts +2 -2
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/intrinsic-element/components.ts +1 -1
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/render.ts +7 -7
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/hooks/index.ts +3 -3
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/intrinsic-element/components.ts +1 -1
- package/tests/fixtures/corpus/oss-repos/hono/src/jsx/utils.ts +6 -6
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/jsx-renderer/index.ts +3 -3
- package/tests/fixtures/corpus/oss-repos/hono/src/middleware/serve-static/index.ts +1 -1
- package/tests/fixtures/corpus/oss-repos/hono/src/preset/quick.ts +1 -1
- package/tests/fixtures/corpus/oss-repos/hono/src/preset/tiny.ts +1 -1
- package/tests/fixtures/corpus/oss-repos/hono/src/router/pattern-router/router.ts +2 -2
- package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/node.ts +4 -4
- package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/router.ts +1 -1
- package/tests/fixtures/corpus/oss-repos/hono/src/router/trie-router/node.ts +1 -1
- package/tests/fixtures/corpus/oss-repos/hono/src/types.ts +166 -169
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/body.ts +8 -8
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/color.ts +3 -3
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/cookie.ts +2 -2
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/encode.ts +2 -2
- package/tests/fixtures/corpus/oss-repos/hono/src/utils/types.ts +30 -33
- package/tests/fixtures/corpus/oss-repos/hono/src/validator/validator.ts +2 -2
- package/tests/fixtures/test-server.ts +3 -2
- package/tests/helpers/performance-metrics.ts +8 -25
- package/tests/helpers/search-relevance.ts +14 -69
- package/tests/integration/cli-consistency.test.ts +5 -4
- package/tests/integration/e2e-workflow.test.ts +2 -0
- package/tests/integration/python-bridge.test.ts +13 -3
- package/tests/mcp/server.test.ts +1 -1
- package/tests/services/code-unit.service.test.ts +48 -0
- package/tests/services/job.service.test.ts +124 -0
- package/tests/services/search.progressive-context.test.ts +2 -2
- package/.claude-plugin/plugin.json +0 -13
- package/BUGS-FOUND.md +0 -71
- package/dist/chunk-2SJHNRXD.js.map +0 -1
- package/dist/chunk-L2YVNC63.js.map +0 -1
- package/dist/chunk-OGEY66FZ.js.map +0 -1
- package/dist/chunk-RWSXP3PQ.js.map +0 -1
- package/dist/watch.service-YAIKKDCF.js +0 -7
- package/skills/atomic-commits/SKILL.md +0 -77
- /package/dist/{watch.service-YAIKKDCF.js.map → watch.service-BJV3TI3F.js.map} +0 -0
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
CheckJobStatusArgs,
|
|
4
|
-
ListJobsArgs,
|
|
5
|
-
CancelJobArgs
|
|
6
|
-
} from '../schemas/index.js';
|
|
1
|
+
import { JobService } from '../../services/job.service.js';
|
|
7
2
|
import {
|
|
8
3
|
CheckJobStatusArgsSchema,
|
|
9
4
|
ListJobsArgsSchema,
|
|
10
|
-
CancelJobArgsSchema
|
|
5
|
+
CancelJobArgsSchema,
|
|
11
6
|
} from '../schemas/index.js';
|
|
12
|
-
import {
|
|
7
|
+
import type { CheckJobStatusArgs, ListJobsArgs, CancelJobArgs } from '../schemas/index.js';
|
|
8
|
+
import type { ToolHandler, ToolResponse } from '../types.js';
|
|
13
9
|
|
|
14
10
|
/**
|
|
15
11
|
* Handle check_job_status requests
|
|
@@ -36,9 +32,9 @@ export const handleCheckJobStatus: ToolHandler<CheckJobStatusArgs> = (
|
|
|
36
32
|
content: [
|
|
37
33
|
{
|
|
38
34
|
type: 'text',
|
|
39
|
-
text: JSON.stringify(job, null, 2)
|
|
40
|
-
}
|
|
41
|
-
]
|
|
35
|
+
text: JSON.stringify(job, null, 2),
|
|
36
|
+
},
|
|
37
|
+
],
|
|
42
38
|
});
|
|
43
39
|
};
|
|
44
40
|
|
|
@@ -47,10 +43,7 @@ export const handleCheckJobStatus: ToolHandler<CheckJobStatusArgs> = (
|
|
|
47
43
|
*
|
|
48
44
|
* Lists all jobs with optional filtering by status or active status.
|
|
49
45
|
*/
|
|
50
|
-
export const handleListJobs: ToolHandler<ListJobsArgs> = (
|
|
51
|
-
args,
|
|
52
|
-
context
|
|
53
|
-
): Promise<ToolResponse> => {
|
|
46
|
+
export const handleListJobs: ToolHandler<ListJobsArgs> = (args, context): Promise<ToolResponse> => {
|
|
54
47
|
// Validate arguments with Zod
|
|
55
48
|
const validated = ListJobsArgsSchema.parse(args);
|
|
56
49
|
|
|
@@ -71,9 +64,9 @@ export const handleListJobs: ToolHandler<ListJobsArgs> = (
|
|
|
71
64
|
content: [
|
|
72
65
|
{
|
|
73
66
|
type: 'text',
|
|
74
|
-
text: JSON.stringify({ jobs }, null, 2)
|
|
75
|
-
}
|
|
76
|
-
]
|
|
67
|
+
text: JSON.stringify({ jobs }, null, 2),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
77
70
|
});
|
|
78
71
|
};
|
|
79
72
|
|
|
@@ -105,12 +98,16 @@ export const handleCancelJob: ToolHandler<CancelJobArgs> = (
|
|
|
105
98
|
content: [
|
|
106
99
|
{
|
|
107
100
|
type: 'text',
|
|
108
|
-
text: JSON.stringify(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
101
|
+
text: JSON.stringify(
|
|
102
|
+
{
|
|
103
|
+
success: true,
|
|
104
|
+
job,
|
|
105
|
+
message: 'Job cancelled successfully',
|
|
106
|
+
},
|
|
107
|
+
null,
|
|
108
|
+
2
|
|
109
|
+
),
|
|
110
|
+
},
|
|
111
|
+
],
|
|
115
112
|
});
|
|
116
113
|
};
|
|
@@ -13,7 +13,7 @@ function parseSearchResponse(text: string): { header: string; json: Record<strin
|
|
|
13
13
|
const jsonStr = parts.slice(1).join('\n\n');
|
|
14
14
|
return {
|
|
15
15
|
header,
|
|
16
|
-
json: JSON.parse(jsonStr || '{}')
|
|
16
|
+
json: JSON.parse(jsonStr || '{}'),
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -28,18 +28,16 @@ describe('Search Handlers', () => {
|
|
|
28
28
|
// Create mock services
|
|
29
29
|
mockServices = {
|
|
30
30
|
store: {
|
|
31
|
-
list: vi.fn().mockResolvedValue([
|
|
32
|
-
{ id: 'store1', name: 'Test Store', type: 'file' }
|
|
33
|
-
]),
|
|
31
|
+
list: vi.fn().mockResolvedValue([{ id: 'store1', name: 'Test Store', type: 'file' }]),
|
|
34
32
|
getByIdOrName: vi.fn().mockResolvedValue({
|
|
35
33
|
id: 'store1',
|
|
36
34
|
name: 'Test Store',
|
|
37
35
|
type: 'file',
|
|
38
|
-
path: '/test/path'
|
|
39
|
-
})
|
|
36
|
+
path: '/test/path',
|
|
37
|
+
}),
|
|
40
38
|
},
|
|
41
39
|
lance: {
|
|
42
|
-
initialize: vi.fn().mockResolvedValue(undefined)
|
|
40
|
+
initialize: vi.fn().mockResolvedValue(undefined),
|
|
43
41
|
},
|
|
44
42
|
search: {
|
|
45
43
|
search: vi.fn().mockResolvedValue({
|
|
@@ -49,19 +47,19 @@ describe('Search Handlers', () => {
|
|
|
49
47
|
score: 0.95,
|
|
50
48
|
content: 'test content for search',
|
|
51
49
|
metadata: { storeId: 'store1' },
|
|
52
|
-
summary: { file: 'test.ts', line: 1 }
|
|
53
|
-
}
|
|
50
|
+
summary: { file: 'test.ts', line: 1 },
|
|
51
|
+
},
|
|
54
52
|
],
|
|
55
53
|
totalResults: 1,
|
|
56
54
|
mode: 'hybrid',
|
|
57
|
-
timeMs: 50
|
|
58
|
-
})
|
|
59
|
-
}
|
|
55
|
+
timeMs: 50,
|
|
56
|
+
}),
|
|
57
|
+
},
|
|
60
58
|
} as unknown as ServiceContainer;
|
|
61
59
|
|
|
62
60
|
mockContext = {
|
|
63
61
|
services: mockServices,
|
|
64
|
-
options: {}
|
|
62
|
+
options: {},
|
|
65
63
|
};
|
|
66
64
|
});
|
|
67
65
|
|
|
@@ -80,7 +78,7 @@ describe('Search Handlers', () => {
|
|
|
80
78
|
expect(mockServices.search.search).toHaveBeenCalledWith(
|
|
81
79
|
expect.objectContaining({
|
|
82
80
|
query: 'test query',
|
|
83
|
-
stores: ['store1']
|
|
81
|
+
stores: ['store1'],
|
|
84
82
|
})
|
|
85
83
|
);
|
|
86
84
|
|
|
@@ -113,10 +111,7 @@ describe('Search Handlers', () => {
|
|
|
113
111
|
});
|
|
114
112
|
|
|
115
113
|
it('should initialize all stores', async () => {
|
|
116
|
-
await handleSearch(
|
|
117
|
-
{ query: 'test', detail: 'minimal', limit: 10 },
|
|
118
|
-
mockContext
|
|
119
|
-
);
|
|
114
|
+
await handleSearch({ query: 'test', detail: 'minimal', limit: 10 }, mockContext);
|
|
120
115
|
|
|
121
116
|
expect(mockServices.lance.initialize).toHaveBeenCalledWith('store1');
|
|
122
117
|
});
|
|
@@ -127,18 +122,12 @@ describe('Search Handlers', () => {
|
|
|
127
122
|
);
|
|
128
123
|
|
|
129
124
|
await expect(
|
|
130
|
-
handleSearch(
|
|
131
|
-
{ query: 'test', detail: 'minimal', limit: 10 },
|
|
132
|
-
mockContext
|
|
133
|
-
)
|
|
125
|
+
handleSearch({ query: 'test', detail: 'minimal', limit: 10 }, mockContext)
|
|
134
126
|
).rejects.toThrow('Failed to initialize vector stores');
|
|
135
127
|
});
|
|
136
128
|
|
|
137
129
|
it('should cache search results', async () => {
|
|
138
|
-
await handleSearch(
|
|
139
|
-
{ query: 'test', detail: 'minimal', limit: 10 },
|
|
140
|
-
mockContext
|
|
141
|
-
);
|
|
130
|
+
await handleSearch({ query: 'test', detail: 'minimal', limit: 10 }, mockContext);
|
|
142
131
|
|
|
143
132
|
// Verify cache was populated
|
|
144
133
|
const cached = resultCache.get('doc1');
|
|
@@ -166,7 +155,7 @@ describe('Search Handlers', () => {
|
|
|
166
155
|
createdAt: new Date(),
|
|
167
156
|
updatedAt: new Date(),
|
|
168
157
|
status: 'ready',
|
|
169
|
-
description: 'Test'
|
|
158
|
+
description: 'Test',
|
|
170
159
|
});
|
|
171
160
|
|
|
172
161
|
const result = await handleSearch(
|
|
@@ -221,7 +210,7 @@ describe('Search Handlers', () => {
|
|
|
221
210
|
score: 0.95,
|
|
222
211
|
content: 'test content for get full context',
|
|
223
212
|
metadata: { storeId: 'store1', docId: 'doc1' },
|
|
224
|
-
summary: { file: 'test.ts', line: 1 }
|
|
213
|
+
summary: { file: 'test.ts', line: 1 },
|
|
225
214
|
});
|
|
226
215
|
});
|
|
227
216
|
|
|
@@ -233,13 +222,10 @@ describe('Search Handlers', () => {
|
|
|
233
222
|
content: 'test content',
|
|
234
223
|
metadata: { storeId: 'store1', docId: 'doc1' },
|
|
235
224
|
summary: { file: 'test.ts', line: 1 },
|
|
236
|
-
full: { code: 'full code here' }
|
|
225
|
+
full: { code: 'full code here' },
|
|
237
226
|
});
|
|
238
227
|
|
|
239
|
-
const result = await handleGetFullContext(
|
|
240
|
-
{ resultId: 'doc1' },
|
|
241
|
-
mockContext
|
|
242
|
-
);
|
|
228
|
+
const result = await handleGetFullContext({ resultId: 'doc1' }, mockContext);
|
|
243
229
|
|
|
244
230
|
const response = JSON.parse(result.content[0]?.text ?? '{}');
|
|
245
231
|
expect(response.id).toBe('doc1');
|
|
@@ -251,12 +237,9 @@ describe('Search Handlers', () => {
|
|
|
251
237
|
});
|
|
252
238
|
|
|
253
239
|
it('should throw if result not in cache', async () => {
|
|
254
|
-
await expect(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
mockContext
|
|
258
|
-
)
|
|
259
|
-
).rejects.toThrow('Result not found in cache');
|
|
240
|
+
await expect(handleGetFullContext({ resultId: 'nonexistent' }, mockContext)).rejects.toThrow(
|
|
241
|
+
'Result not found in cache'
|
|
242
|
+
);
|
|
260
243
|
});
|
|
261
244
|
|
|
262
245
|
it('should re-query for full context if not already full', async () => {
|
|
@@ -268,25 +251,22 @@ describe('Search Handlers', () => {
|
|
|
268
251
|
content: 'test content',
|
|
269
252
|
metadata: { storeId: 'store1', docId: 'doc1' },
|
|
270
253
|
summary: { file: 'test.ts', line: 1 },
|
|
271
|
-
full: { code: 'full code from re-query' }
|
|
272
|
-
}
|
|
254
|
+
full: { code: 'full code from re-query' },
|
|
255
|
+
},
|
|
273
256
|
],
|
|
274
257
|
totalResults: 1,
|
|
275
258
|
mode: 'hybrid',
|
|
276
|
-
timeMs: 50
|
|
259
|
+
timeMs: 50,
|
|
277
260
|
});
|
|
278
261
|
|
|
279
|
-
const result = await handleGetFullContext(
|
|
280
|
-
{ resultId: 'doc1' },
|
|
281
|
-
mockContext
|
|
282
|
-
);
|
|
262
|
+
const result = await handleGetFullContext({ resultId: 'doc1' }, mockContext);
|
|
283
263
|
|
|
284
264
|
expect(mockServices.store.getByIdOrName).toHaveBeenCalledWith('store1');
|
|
285
265
|
expect(mockServices.lance.initialize).toHaveBeenCalledWith('store1');
|
|
286
266
|
expect(mockServices.search.search).toHaveBeenCalledWith(
|
|
287
267
|
expect.objectContaining({
|
|
288
268
|
detail: 'full',
|
|
289
|
-
limit: 1
|
|
269
|
+
limit: 1,
|
|
290
270
|
})
|
|
291
271
|
);
|
|
292
272
|
|
|
@@ -297,12 +277,9 @@ describe('Search Handlers', () => {
|
|
|
297
277
|
it('should throw if store not found', async () => {
|
|
298
278
|
vi.mocked(mockServices.store.getByIdOrName).mockResolvedValue(undefined);
|
|
299
279
|
|
|
300
|
-
await expect(
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
mockContext
|
|
304
|
-
)
|
|
305
|
-
).rejects.toThrow('Store not found');
|
|
280
|
+
await expect(handleGetFullContext({ resultId: 'doc1' }, mockContext)).rejects.toThrow(
|
|
281
|
+
'Store not found'
|
|
282
|
+
);
|
|
306
283
|
});
|
|
307
284
|
|
|
308
285
|
it('should return cached result with warning if re-query fails', async () => {
|
|
@@ -310,13 +287,10 @@ describe('Search Handlers', () => {
|
|
|
310
287
|
results: [], // No matching result found
|
|
311
288
|
totalResults: 0,
|
|
312
289
|
mode: 'hybrid',
|
|
313
|
-
timeMs: 50
|
|
290
|
+
timeMs: 50,
|
|
314
291
|
});
|
|
315
292
|
|
|
316
|
-
const result = await handleGetFullContext(
|
|
317
|
-
{ resultId: 'doc1' },
|
|
318
|
-
mockContext
|
|
319
|
-
);
|
|
293
|
+
const result = await handleGetFullContext({ resultId: 'doc1' }, mockContext);
|
|
320
294
|
|
|
321
295
|
const response = JSON.parse(result.content[0]?.text ?? '{}');
|
|
322
296
|
expect(response.id).toBe('doc1');
|
|
@@ -332,18 +306,15 @@ describe('Search Handlers', () => {
|
|
|
332
306
|
content: 'test content',
|
|
333
307
|
metadata: { storeId: 'store1', docId: 'doc1' },
|
|
334
308
|
summary: { file: 'test.ts', line: 1 },
|
|
335
|
-
full: { code: 'full code from re-query' }
|
|
336
|
-
}
|
|
309
|
+
full: { code: 'full code from re-query' },
|
|
310
|
+
},
|
|
337
311
|
],
|
|
338
312
|
totalResults: 1,
|
|
339
313
|
mode: 'hybrid',
|
|
340
|
-
timeMs: 50
|
|
314
|
+
timeMs: 50,
|
|
341
315
|
});
|
|
342
316
|
|
|
343
|
-
await handleGetFullContext(
|
|
344
|
-
{ resultId: 'doc1' },
|
|
345
|
-
mockContext
|
|
346
|
-
);
|
|
317
|
+
await handleGetFullContext({ resultId: 'doc1' }, mockContext);
|
|
347
318
|
|
|
348
319
|
const cached = resultCache.get('doc1');
|
|
349
320
|
expect(cached?.full).toBeDefined();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { createLogger, summarizePayload } from '../../logging/index.js';
|
|
2
|
+
import { estimateTokens, formatTokenCount } from '../../services/token.service.js';
|
|
3
|
+
import { LRUCache } from '../cache.js';
|
|
3
4
|
import { SearchArgsSchema, GetFullContextArgsSchema } from '../schemas/index.js';
|
|
4
5
|
import type { SearchQuery, DocumentId, StoreId } from '../../types/index.js';
|
|
5
|
-
import { LRUCache } from '../cache.js';
|
|
6
6
|
import type { SearchResult } from '../../types/search.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import type { SearchArgs, GetFullContextArgs } from '../schemas/index.js';
|
|
8
|
+
import type { ToolHandler, ToolResponse } from '../types.js';
|
|
9
9
|
|
|
10
10
|
const logger = createLogger('mcp-search');
|
|
11
11
|
|
|
@@ -26,27 +26,33 @@ export const handleSearch: ToolHandler<SearchArgs> = async (
|
|
|
26
26
|
// Validate arguments with Zod
|
|
27
27
|
const validated = SearchArgsSchema.parse(args);
|
|
28
28
|
|
|
29
|
-
logger.info(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
logger.info(
|
|
30
|
+
{
|
|
31
|
+
query: validated.query,
|
|
32
|
+
stores: validated.stores,
|
|
33
|
+
detail: validated.detail,
|
|
34
|
+
limit: validated.limit,
|
|
35
|
+
intent: validated.intent,
|
|
36
|
+
},
|
|
37
|
+
'Search started'
|
|
38
|
+
);
|
|
36
39
|
|
|
37
40
|
const { services } = context;
|
|
38
41
|
|
|
39
42
|
// Get all stores if none specified, resolve store names to IDs
|
|
40
|
-
const storeIds: StoreId[] =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
const storeIds: StoreId[] =
|
|
44
|
+
validated.stores !== undefined
|
|
45
|
+
? await Promise.all(
|
|
46
|
+
validated.stores.map(async (s) => {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
48
|
+
const store = await services.store.getByIdOrName(s as StoreId);
|
|
49
|
+
if (!store) {
|
|
50
|
+
throw new Error(`Store not found: ${s}`);
|
|
51
|
+
}
|
|
52
|
+
return store.id;
|
|
53
|
+
})
|
|
54
|
+
)
|
|
55
|
+
: (await services.store.list()).map((s) => s.id);
|
|
50
56
|
|
|
51
57
|
// Initialize stores with error handling
|
|
52
58
|
try {
|
|
@@ -65,7 +71,7 @@ export const handleSearch: ToolHandler<SearchArgs> = async (
|
|
|
65
71
|
stores: storeIds,
|
|
66
72
|
mode: 'hybrid',
|
|
67
73
|
limit: validated.limit,
|
|
68
|
-
detail: validated.detail
|
|
74
|
+
detail: validated.detail,
|
|
69
75
|
};
|
|
70
76
|
|
|
71
77
|
const results = await services.search.search(searchQuery);
|
|
@@ -76,29 +82,35 @@ export const handleSearch: ToolHandler<SearchArgs> = async (
|
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
// Add repoRoot to results for cloned repos
|
|
79
|
-
const enhancedResults = await Promise.all(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
85
|
+
const enhancedResults = await Promise.all(
|
|
86
|
+
results.results.map(async (r) => {
|
|
87
|
+
const storeId = r.metadata.storeId;
|
|
88
|
+
const store = await services.store.getByIdOrName(storeId);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
id: r.id,
|
|
92
|
+
score: r.score,
|
|
93
|
+
summary: {
|
|
94
|
+
...r.summary,
|
|
95
|
+
storeName: store?.name,
|
|
96
|
+
repoRoot: store?.type === 'repo' ? store.path : undefined,
|
|
97
|
+
},
|
|
98
|
+
context: r.context,
|
|
99
|
+
full: r.full,
|
|
100
|
+
};
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const responseJson = JSON.stringify(
|
|
105
|
+
{
|
|
106
|
+
results: enhancedResults,
|
|
107
|
+
totalResults: results.totalResults,
|
|
108
|
+
mode: results.mode,
|
|
109
|
+
timeMs: results.timeMs,
|
|
110
|
+
},
|
|
111
|
+
null,
|
|
112
|
+
2
|
|
113
|
+
);
|
|
102
114
|
|
|
103
115
|
// Calculate actual token estimate based on response content
|
|
104
116
|
const responseTokens = estimateTokens(responseJson);
|
|
@@ -107,21 +119,24 @@ export const handleSearch: ToolHandler<SearchArgs> = async (
|
|
|
107
119
|
const header = `Search: "${validated.query}" | Results: ${String(results.totalResults)} | ${formatTokenCount(responseTokens)} tokens | ${String(results.timeMs)}ms\n\n`;
|
|
108
120
|
|
|
109
121
|
// Log the complete MCP response that will be sent to Claude Code
|
|
110
|
-
logger.info(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
logger.info(
|
|
123
|
+
{
|
|
124
|
+
query: validated.query,
|
|
125
|
+
totalResults: results.totalResults,
|
|
126
|
+
responseTokens,
|
|
127
|
+
timeMs: results.timeMs,
|
|
128
|
+
...summarizePayload(responseJson, 'mcp-response', validated.query),
|
|
129
|
+
},
|
|
130
|
+
'Search complete - context sent to Claude Code'
|
|
131
|
+
);
|
|
117
132
|
|
|
118
133
|
return {
|
|
119
134
|
content: [
|
|
120
135
|
{
|
|
121
136
|
type: 'text',
|
|
122
|
-
text: header + responseJson
|
|
123
|
-
}
|
|
124
|
-
]
|
|
137
|
+
text: header + responseJson,
|
|
138
|
+
},
|
|
139
|
+
],
|
|
125
140
|
};
|
|
126
141
|
};
|
|
127
142
|
|
|
@@ -147,35 +162,40 @@ export const handleGetFullContext: ToolHandler<GetFullContextArgs> = async (
|
|
|
147
162
|
const cachedResult = resultCache.get(resultId);
|
|
148
163
|
|
|
149
164
|
if (!cachedResult) {
|
|
150
|
-
throw new Error(
|
|
151
|
-
`Result not found in cache: ${resultId}. Run a search first to cache results.`
|
|
152
|
-
);
|
|
165
|
+
throw new Error(`Result not found in cache: ${resultId}. Run a search first to cache results.`);
|
|
153
166
|
}
|
|
154
167
|
|
|
155
168
|
// If result already has full context, return it
|
|
156
169
|
if (cachedResult.full) {
|
|
157
|
-
const responseJson = JSON.stringify(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
const responseJson = JSON.stringify(
|
|
171
|
+
{
|
|
172
|
+
id: cachedResult.id,
|
|
173
|
+
score: cachedResult.score,
|
|
174
|
+
summary: cachedResult.summary,
|
|
175
|
+
context: cachedResult.context,
|
|
176
|
+
full: cachedResult.full,
|
|
177
|
+
},
|
|
178
|
+
null,
|
|
179
|
+
2
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
logger.info(
|
|
183
|
+
{
|
|
184
|
+
resultId,
|
|
185
|
+
cached: true,
|
|
186
|
+
hasFullContext: true,
|
|
187
|
+
...summarizePayload(responseJson, 'mcp-full-context', resultId),
|
|
188
|
+
},
|
|
189
|
+
'Full context retrieved from cache'
|
|
190
|
+
);
|
|
171
191
|
|
|
172
192
|
return {
|
|
173
193
|
content: [
|
|
174
194
|
{
|
|
175
195
|
type: 'text',
|
|
176
|
-
text: responseJson
|
|
177
|
-
}
|
|
178
|
-
]
|
|
196
|
+
text: responseJson,
|
|
197
|
+
},
|
|
198
|
+
],
|
|
179
199
|
};
|
|
180
200
|
}
|
|
181
201
|
|
|
@@ -194,13 +214,13 @@ export const handleGetFullContext: ToolHandler<GetFullContextArgs> = async (
|
|
|
194
214
|
stores: [store.id],
|
|
195
215
|
mode: 'hybrid',
|
|
196
216
|
limit: 1,
|
|
197
|
-
detail: 'full'
|
|
217
|
+
detail: 'full',
|
|
198
218
|
};
|
|
199
219
|
|
|
200
220
|
const results = await services.search.search(searchQuery);
|
|
201
221
|
|
|
202
222
|
// Find matching result by ID
|
|
203
|
-
const fullResult = results.results.find(r => r.id === resultId);
|
|
223
|
+
const fullResult = results.results.find((r) => r.id === resultId);
|
|
204
224
|
|
|
205
225
|
if (!fullResult) {
|
|
206
226
|
// Return cached result even if we couldn't get full detail
|
|
@@ -208,42 +228,53 @@ export const handleGetFullContext: ToolHandler<GetFullContextArgs> = async (
|
|
|
208
228
|
content: [
|
|
209
229
|
{
|
|
210
230
|
type: 'text',
|
|
211
|
-
text: JSON.stringify(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
231
|
+
text: JSON.stringify(
|
|
232
|
+
{
|
|
233
|
+
id: cachedResult.id,
|
|
234
|
+
score: cachedResult.score,
|
|
235
|
+
summary: cachedResult.summary,
|
|
236
|
+
context: cachedResult.context,
|
|
237
|
+
warning: 'Could not retrieve full context, returning cached minimal result',
|
|
238
|
+
},
|
|
239
|
+
null,
|
|
240
|
+
2
|
|
241
|
+
),
|
|
242
|
+
},
|
|
243
|
+
],
|
|
220
244
|
};
|
|
221
245
|
}
|
|
222
246
|
|
|
223
247
|
// Update cache with full result
|
|
224
248
|
resultCache.set(resultId, fullResult);
|
|
225
249
|
|
|
226
|
-
const responseJson = JSON.stringify(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
250
|
+
const responseJson = JSON.stringify(
|
|
251
|
+
{
|
|
252
|
+
id: fullResult.id,
|
|
253
|
+
score: fullResult.score,
|
|
254
|
+
summary: fullResult.summary,
|
|
255
|
+
context: fullResult.context,
|
|
256
|
+
full: fullResult.full,
|
|
257
|
+
},
|
|
258
|
+
null,
|
|
259
|
+
2
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
logger.info(
|
|
263
|
+
{
|
|
264
|
+
resultId,
|
|
265
|
+
cached: false,
|
|
266
|
+
hasFullContext: true,
|
|
267
|
+
...summarizePayload(responseJson, 'mcp-full-context', resultId),
|
|
268
|
+
},
|
|
269
|
+
'Full context retrieved via re-query'
|
|
270
|
+
);
|
|
240
271
|
|
|
241
272
|
return {
|
|
242
273
|
content: [
|
|
243
274
|
{
|
|
244
275
|
type: 'text',
|
|
245
|
-
text: responseJson
|
|
246
|
-
}
|
|
247
|
-
]
|
|
276
|
+
text: responseJson,
|
|
277
|
+
},
|
|
278
|
+
],
|
|
248
279
|
};
|
|
249
280
|
};
|