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
|
@@ -4,7 +4,7 @@ import type { GlobalOptions } from '../program.js';
|
|
|
4
4
|
|
|
5
5
|
// Mock all dependencies
|
|
6
6
|
vi.mock('../../mcp/server.js', () => ({
|
|
7
|
-
runMCPServer: vi.fn()
|
|
7
|
+
runMCPServer: vi.fn(),
|
|
8
8
|
}));
|
|
9
9
|
|
|
10
10
|
describe('MCP Command - Execution Tests', () => {
|
|
@@ -35,7 +35,7 @@ describe('MCP Command - Execution Tests', () => {
|
|
|
35
35
|
|
|
36
36
|
expect(runMCPServer).toHaveBeenCalledWith({
|
|
37
37
|
dataDir: '/tmp/test-data',
|
|
38
|
-
config: undefined
|
|
38
|
+
config: undefined,
|
|
39
39
|
});
|
|
40
40
|
});
|
|
41
41
|
|
|
@@ -55,7 +55,7 @@ describe('MCP Command - Execution Tests', () => {
|
|
|
55
55
|
|
|
56
56
|
expect(runMCPServer).toHaveBeenCalledWith({
|
|
57
57
|
dataDir: '/tmp/test-data',
|
|
58
|
-
config: '/custom/config.json'
|
|
58
|
+
config: '/custom/config.json',
|
|
59
59
|
});
|
|
60
60
|
});
|
|
61
61
|
|
|
@@ -75,7 +75,7 @@ describe('MCP Command - Execution Tests', () => {
|
|
|
75
75
|
|
|
76
76
|
expect(runMCPServer).toHaveBeenCalledWith({
|
|
77
77
|
dataDir: '/custom/data',
|
|
78
|
-
config: undefined
|
|
78
|
+
config: undefined,
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
81
|
|
|
@@ -95,7 +95,7 @@ describe('MCP Command - Execution Tests', () => {
|
|
|
95
95
|
|
|
96
96
|
expect(runMCPServer).toHaveBeenCalledWith({
|
|
97
97
|
dataDir: '/custom/data',
|
|
98
|
-
config: '/custom/config.json'
|
|
98
|
+
config: '/custom/config.json',
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
101
|
});
|
|
@@ -204,7 +204,7 @@ describe('MCP Command - Execution Tests', () => {
|
|
|
204
204
|
let serverStarted = false;
|
|
205
205
|
|
|
206
206
|
vi.mocked(runMCPServer).mockImplementation(async () => {
|
|
207
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
207
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
208
208
|
serverStarted = true;
|
|
209
209
|
});
|
|
210
210
|
|
package/src/cli/commands/mcp.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import type { GlobalOptions } from '../program.js';
|
|
3
2
|
import { runMCPServer } from '../../mcp/server.js';
|
|
3
|
+
import type { GlobalOptions } from '../program.js';
|
|
4
4
|
|
|
5
5
|
export function createMCPCommand(getOptions: () => GlobalOptions): Command {
|
|
6
6
|
const mcp = new Command('mcp')
|
|
@@ -10,7 +10,7 @@ export function createMCPCommand(getOptions: () => GlobalOptions): Command {
|
|
|
10
10
|
|
|
11
11
|
await runMCPServer({
|
|
12
12
|
dataDir: opts.dataDir,
|
|
13
|
-
config: opts.config
|
|
13
|
+
config: opts.config,
|
|
14
14
|
});
|
|
15
15
|
});
|
|
16
16
|
|
|
@@ -12,7 +12,7 @@ vi.mock('../../plugin/commands.js', () => ({
|
|
|
12
12
|
handleAddRepo: vi.fn(),
|
|
13
13
|
handleAddFolder: vi.fn(),
|
|
14
14
|
handleStores: vi.fn(),
|
|
15
|
-
handleSuggest: vi.fn()
|
|
15
|
+
handleSuggest: vi.fn(),
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
18
|
describe('Plugin API Commands - Execution Tests', () => {
|
|
@@ -24,12 +24,8 @@ describe('Plugin API Commands - Execution Tests', () => {
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
beforeEach(async () => {
|
|
27
|
-
const {
|
|
28
|
-
|
|
29
|
-
handleAddFolder,
|
|
30
|
-
handleStores,
|
|
31
|
-
handleSuggest
|
|
32
|
-
} = await import('../../plugin/commands.js');
|
|
27
|
+
const { handleAddRepo, handleAddFolder, handleStores, handleSuggest } =
|
|
28
|
+
await import('../../plugin/commands.js');
|
|
33
29
|
|
|
34
30
|
vi.mocked(handleAddRepo).mockClear().mockResolvedValue(undefined);
|
|
35
31
|
vi.mocked(handleAddFolder).mockClear().mockResolvedValue(undefined);
|
|
@@ -50,7 +46,7 @@ describe('Plugin API Commands - Execution Tests', () => {
|
|
|
50
46
|
await actionHandler(['https://github.com/user/repo.git']);
|
|
51
47
|
|
|
52
48
|
expect(handleAddRepo).toHaveBeenCalledWith({
|
|
53
|
-
url: 'https://github.com/user/repo.git'
|
|
49
|
+
url: 'https://github.com/user/repo.git',
|
|
54
50
|
});
|
|
55
51
|
});
|
|
56
52
|
|
|
@@ -64,7 +60,7 @@ describe('Plugin API Commands - Execution Tests', () => {
|
|
|
64
60
|
|
|
65
61
|
expect(handleAddRepo).toHaveBeenCalledWith({
|
|
66
62
|
url: 'https://github.com/user/repo.git',
|
|
67
|
-
name: 'my-repo'
|
|
63
|
+
name: 'my-repo',
|
|
68
64
|
});
|
|
69
65
|
});
|
|
70
66
|
|
|
@@ -78,7 +74,7 @@ describe('Plugin API Commands - Execution Tests', () => {
|
|
|
78
74
|
|
|
79
75
|
expect(handleAddRepo).toHaveBeenCalledWith({
|
|
80
76
|
url: 'https://github.com/user/repo.git',
|
|
81
|
-
branch: 'develop'
|
|
77
|
+
branch: 'develop',
|
|
82
78
|
});
|
|
83
79
|
});
|
|
84
80
|
|
|
@@ -93,7 +89,7 @@ describe('Plugin API Commands - Execution Tests', () => {
|
|
|
93
89
|
expect(handleAddRepo).toHaveBeenCalledWith({
|
|
94
90
|
url: 'https://github.com/user/repo.git',
|
|
95
91
|
name: 'custom-name',
|
|
96
|
-
branch: 'main'
|
|
92
|
+
branch: 'main',
|
|
97
93
|
});
|
|
98
94
|
});
|
|
99
95
|
|
|
@@ -105,7 +101,9 @@ describe('Plugin API Commands - Execution Tests', () => {
|
|
|
105
101
|
const command = createAddRepoCommand(getOptions);
|
|
106
102
|
const actionHandler = (command as any)._actionHandler;
|
|
107
103
|
|
|
108
|
-
await expect(actionHandler(['https://github.com/user/repo.git'])).rejects.toThrow(
|
|
104
|
+
await expect(actionHandler(['https://github.com/user/repo.git'])).rejects.toThrow(
|
|
105
|
+
'Clone failed'
|
|
106
|
+
);
|
|
109
107
|
});
|
|
110
108
|
});
|
|
111
109
|
|
|
@@ -118,7 +116,7 @@ describe('Plugin API Commands - Execution Tests', () => {
|
|
|
118
116
|
await actionHandler(['/path/to/folder']);
|
|
119
117
|
|
|
120
118
|
expect(handleAddFolder).toHaveBeenCalledWith({
|
|
121
|
-
path: '/path/to/folder'
|
|
119
|
+
path: '/path/to/folder',
|
|
122
120
|
});
|
|
123
121
|
});
|
|
124
122
|
|
|
@@ -132,7 +130,7 @@ describe('Plugin API Commands - Execution Tests', () => {
|
|
|
132
130
|
|
|
133
131
|
expect(handleAddFolder).toHaveBeenCalledWith({
|
|
134
132
|
path: '/path/to/folder',
|
|
135
|
-
name: 'my-folder'
|
|
133
|
+
name: 'my-folder',
|
|
136
134
|
});
|
|
137
135
|
});
|
|
138
136
|
|
|
@@ -144,7 +142,7 @@ describe('Plugin API Commands - Execution Tests', () => {
|
|
|
144
142
|
await actionHandler(['./relative/path']);
|
|
145
143
|
|
|
146
144
|
expect(handleAddFolder).toHaveBeenCalledWith({
|
|
147
|
-
path: './relative/path'
|
|
145
|
+
path: './relative/path',
|
|
148
146
|
});
|
|
149
147
|
});
|
|
150
148
|
|
|
@@ -156,7 +154,7 @@ describe('Plugin API Commands - Execution Tests', () => {
|
|
|
156
154
|
await actionHandler(['/path/with spaces/folder']);
|
|
157
155
|
|
|
158
156
|
expect(handleAddFolder).toHaveBeenCalledWith({
|
|
159
|
-
path: '/path/with spaces/folder'
|
|
157
|
+
path: '/path/with spaces/folder',
|
|
160
158
|
});
|
|
161
159
|
});
|
|
162
160
|
|
|
@@ -294,7 +292,7 @@ describe('Plugin API Commands - Execution Tests', () => {
|
|
|
294
292
|
await actionHandler(['https://github.com/user/repo.git']);
|
|
295
293
|
|
|
296
294
|
expect(handleAddRepo).toHaveBeenCalledWith({
|
|
297
|
-
url: 'https://github.com/user/repo.git'
|
|
295
|
+
url: 'https://github.com/user/repo.git',
|
|
298
296
|
});
|
|
299
297
|
});
|
|
300
298
|
|
|
@@ -306,7 +304,7 @@ describe('Plugin API Commands - Execution Tests', () => {
|
|
|
306
304
|
await actionHandler(['/path']);
|
|
307
305
|
|
|
308
306
|
expect(handleAddFolder).toHaveBeenCalledWith({
|
|
309
|
-
path: '/path'
|
|
307
|
+
path: '/path',
|
|
310
308
|
});
|
|
311
309
|
});
|
|
312
310
|
});
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
handleAddRepo,
|
|
4
|
+
handleAddFolder,
|
|
5
|
+
handleStores,
|
|
6
|
+
handleSuggest,
|
|
7
|
+
} from '../../plugin/commands.js';
|
|
3
8
|
import type { GlobalOptions } from '../program.js';
|
|
4
9
|
|
|
5
10
|
/**
|
|
@@ -29,11 +34,9 @@ export function createAddFolderCommand(_getOptions: () => GlobalOptions): Comman
|
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
export function createStoresCommand(_getOptions: () => GlobalOptions): Command {
|
|
32
|
-
return new Command('stores')
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
await handleStores();
|
|
36
|
-
});
|
|
37
|
+
return new Command('stores').description('List all indexed library stores').action(async () => {
|
|
38
|
+
await handleStores();
|
|
39
|
+
});
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export function createSuggestCommand(_getOptions: () => GlobalOptions): Command {
|
|
@@ -374,6 +374,8 @@ describe('search command execution', () => {
|
|
|
374
374
|
|
|
375
375
|
describe('error handling', () => {
|
|
376
376
|
it('exits with code 3 when specified store not found', async () => {
|
|
377
|
+
const { destroyServices } = await import('../../services/index.js');
|
|
378
|
+
|
|
377
379
|
mockServices.store.list.mockResolvedValue([]);
|
|
378
380
|
mockServices.store.getByIdOrName.mockResolvedValue(undefined);
|
|
379
381
|
|
|
@@ -386,9 +388,13 @@ describe('search command execution', () => {
|
|
|
386
388
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Store not found: nonexistent');
|
|
387
389
|
expect(processExitSpy).toHaveBeenCalledWith(3);
|
|
388
390
|
expect(mockServices.search.search).not.toHaveBeenCalled();
|
|
391
|
+
// Must call destroyServices before process.exit per CLAUDE.md
|
|
392
|
+
expect(destroyServices).toHaveBeenCalled();
|
|
389
393
|
});
|
|
390
394
|
|
|
391
395
|
it('exits with code 1 when no stores exist', async () => {
|
|
396
|
+
const { destroyServices } = await import('../../services/index.js');
|
|
397
|
+
|
|
392
398
|
mockServices.store.list.mockResolvedValue([]);
|
|
393
399
|
|
|
394
400
|
const command = createSearchCommand(getOptions);
|
|
@@ -399,9 +405,13 @@ describe('search command execution', () => {
|
|
|
399
405
|
expect(consoleErrorSpy).toHaveBeenCalledWith('No stores to search. Create a store first.');
|
|
400
406
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
401
407
|
expect(mockServices.search.search).not.toHaveBeenCalled();
|
|
408
|
+
// Must call destroyServices before process.exit per CLAUDE.md
|
|
409
|
+
expect(destroyServices).toHaveBeenCalled();
|
|
402
410
|
});
|
|
403
411
|
|
|
404
412
|
it('exits with code 3 when one of multiple stores not found', async () => {
|
|
413
|
+
const { destroyServices } = await import('../../services/index.js');
|
|
414
|
+
|
|
405
415
|
const mockStores = [{ id: createStoreId('store-1'), name: 'store1', type: 'file' as const }];
|
|
406
416
|
|
|
407
417
|
mockServices.store.list.mockResolvedValue(mockStores);
|
|
@@ -418,6 +428,8 @@ describe('search command execution', () => {
|
|
|
418
428
|
|
|
419
429
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Store not found: nonexistent');
|
|
420
430
|
expect(processExitSpy).toHaveBeenCalledWith(3);
|
|
431
|
+
// Must call destroyServices before process.exit per CLAUDE.md
|
|
432
|
+
expect(destroyServices).toHaveBeenCalled();
|
|
421
433
|
});
|
|
422
434
|
});
|
|
423
435
|
|
|
@@ -461,9 +473,7 @@ describe('search command execution', () => {
|
|
|
461
473
|
|
|
462
474
|
await actionHandler(['test query']);
|
|
463
475
|
|
|
464
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
465
|
-
expect.stringContaining('"query": "test query"')
|
|
466
|
-
);
|
|
476
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('"query": "test query"'));
|
|
467
477
|
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('"mode": "hybrid"'));
|
|
468
478
|
});
|
|
469
479
|
|
|
@@ -561,9 +571,7 @@ describe('search command execution', () => {
|
|
|
561
571
|
await actionHandler(['test query']);
|
|
562
572
|
|
|
563
573
|
expect(consoleLogSpy).toHaveBeenCalledWith('\nSearch: "test query"');
|
|
564
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
565
|
-
expect.stringContaining('Mode: hybrid')
|
|
566
|
-
);
|
|
574
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Mode: hybrid'));
|
|
567
575
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
568
576
|
expect.stringContaining('1. [0.95] function: testFunction')
|
|
569
577
|
);
|
|
@@ -580,7 +588,8 @@ describe('search command execution', () => {
|
|
|
580
588
|
{
|
|
581
589
|
id: createDocumentId('doc-1'),
|
|
582
590
|
score: 0.95,
|
|
583
|
-
content:
|
|
591
|
+
content:
|
|
592
|
+
'This is a long piece of content that should be truncated in the preview because it exceeds the maximum length allowed for display in the search results view.',
|
|
584
593
|
highlight: 'This is a long piece of content',
|
|
585
594
|
metadata: {
|
|
586
595
|
type: 'file',
|
|
@@ -1,113 +1,150 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { createServices, destroyServices } from '../../services/index.js';
|
|
3
|
-
import type { GlobalOptions } from '../program.js';
|
|
4
3
|
import type { SearchMode, DetailLevel } from '../../types/search.js';
|
|
4
|
+
import type { GlobalOptions } from '../program.js';
|
|
5
5
|
|
|
6
6
|
export function createSearchCommand(getOptions: () => GlobalOptions): Command {
|
|
7
7
|
const search = new Command('search')
|
|
8
8
|
.description('Search indexed documents using vector similarity + full-text matching')
|
|
9
9
|
.argument('<query>', 'Search query')
|
|
10
|
-
.option(
|
|
11
|
-
|
|
10
|
+
.option(
|
|
11
|
+
'-s, --stores <stores>',
|
|
12
|
+
'Limit search to specific stores (comma-separated IDs or names)'
|
|
13
|
+
)
|
|
14
|
+
.option(
|
|
15
|
+
'-m, --mode <mode>',
|
|
16
|
+
'vector (embeddings only), fts (text only), hybrid (both, default)',
|
|
17
|
+
'hybrid'
|
|
18
|
+
)
|
|
12
19
|
.option('-n, --limit <count>', 'Maximum results to return (default: 10)', '10')
|
|
13
20
|
.option('-t, --threshold <score>', 'Minimum score 0-1; omit low-relevance results')
|
|
14
21
|
.option('--include-content', 'Show full document content, not just preview snippet')
|
|
15
|
-
.option(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (options.stores !== undefined) {
|
|
31
|
-
const requestedStores = options.stores.split(',').map((s) => s.trim());
|
|
32
|
-
const resolvedStores = [];
|
|
33
|
-
|
|
34
|
-
for (const requested of requestedStores) {
|
|
35
|
-
const store = await services.store.getByIdOrName(requested);
|
|
36
|
-
if (store !== undefined) {
|
|
37
|
-
resolvedStores.push(store.id);
|
|
38
|
-
} else {
|
|
39
|
-
console.error(`Error: Store not found: ${requested}`);
|
|
40
|
-
process.exit(3);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
storeIds = resolvedStores;
|
|
22
|
+
.option(
|
|
23
|
+
'--detail <level>',
|
|
24
|
+
'Context detail: minimal, contextual, full (default: minimal)',
|
|
25
|
+
'minimal'
|
|
26
|
+
)
|
|
27
|
+
.action(
|
|
28
|
+
async (
|
|
29
|
+
query: string,
|
|
30
|
+
options: {
|
|
31
|
+
stores?: string;
|
|
32
|
+
mode?: SearchMode;
|
|
33
|
+
limit?: string;
|
|
34
|
+
threshold?: string;
|
|
35
|
+
includeContent?: boolean;
|
|
36
|
+
detail?: DetailLevel;
|
|
45
37
|
}
|
|
38
|
+
) => {
|
|
39
|
+
const globalOpts = getOptions();
|
|
40
|
+
const services = await createServices(globalOpts.config, globalOpts.dataDir);
|
|
41
|
+
let exitCode = 0;
|
|
42
|
+
try {
|
|
43
|
+
// Get store IDs
|
|
44
|
+
let storeIds = (await services.store.list()).map((s) => s.id);
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
searchLogic: {
|
|
47
|
+
if (options.stores !== undefined) {
|
|
48
|
+
const requestedStores = options.stores.split(',').map((s) => s.trim());
|
|
49
|
+
const resolvedStores = [];
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
for (const requested of requestedStores) {
|
|
52
|
+
const store = await services.store.getByIdOrName(requested);
|
|
53
|
+
if (store !== undefined) {
|
|
54
|
+
resolvedStores.push(store.id);
|
|
55
|
+
} else {
|
|
56
|
+
console.error(`Error: Store not found: ${requested}`);
|
|
57
|
+
exitCode = 3;
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
query,
|
|
59
|
-
stores: storeIds,
|
|
60
|
-
mode: options.mode ?? 'hybrid',
|
|
61
|
-
limit: parseInt(options.limit ?? '10', 10),
|
|
62
|
-
threshold: options.threshold !== undefined ? parseFloat(options.threshold) : undefined,
|
|
63
|
-
includeContent: options.includeContent,
|
|
64
|
-
detail: options.detail ?? 'minimal',
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (globalOpts.format === 'json') {
|
|
68
|
-
console.log(JSON.stringify(results, null, 2));
|
|
69
|
-
} else if (globalOpts.quiet === true) {
|
|
70
|
-
// Quiet mode: just list matching paths/URLs, one per line
|
|
71
|
-
for (const r of results.results) {
|
|
72
|
-
const path = r.metadata.path ?? r.metadata.url ?? 'unknown';
|
|
73
|
-
console.log(path);
|
|
74
|
-
}
|
|
75
|
-
} else {
|
|
76
|
-
console.log(`\nSearch: "${query}"`);
|
|
77
|
-
console.log(`Mode: ${results.mode} | Detail: ${String(options.detail)} | Stores: ${String(results.stores.length)} | Results: ${String(results.totalResults)} | Time: ${String(results.timeMs)}ms\n`);
|
|
78
|
-
|
|
79
|
-
if (results.results.length === 0) {
|
|
80
|
-
console.log('No results found.\n');
|
|
81
|
-
} else {
|
|
82
|
-
for (let i = 0; i < results.results.length; i++) {
|
|
83
|
-
const r = results.results[i];
|
|
84
|
-
if (r === undefined) continue;
|
|
85
|
-
|
|
86
|
-
if (r.summary) {
|
|
87
|
-
console.log(`${String(i + 1)}. [${r.score.toFixed(2)}] ${r.summary.type}: ${r.summary.name}`);
|
|
88
|
-
console.log(` ${r.summary.location}`);
|
|
89
|
-
console.log(` ${r.summary.purpose}`);
|
|
90
|
-
|
|
91
|
-
if (r.context && options.detail !== 'minimal') {
|
|
92
|
-
console.log(` Imports: ${r.context.keyImports.slice(0, 3).join(', ')}`);
|
|
93
|
-
console.log(` Related: ${r.context.relatedConcepts.slice(0, 3).join(', ')}`);
|
|
59
|
+
break searchLogic;
|
|
94
60
|
}
|
|
61
|
+
}
|
|
95
62
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
63
|
+
storeIds = resolvedStores;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (storeIds.length === 0) {
|
|
67
|
+
console.error('No stores to search. Create a store first.');
|
|
68
|
+
exitCode = 1;
|
|
69
|
+
|
|
70
|
+
break searchLogic;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Initialize LanceDB for each store
|
|
74
|
+
for (const storeId of storeIds) {
|
|
75
|
+
await services.lance.initialize(storeId);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const results = await services.search.search({
|
|
79
|
+
query,
|
|
80
|
+
stores: storeIds,
|
|
81
|
+
mode: options.mode ?? 'hybrid',
|
|
82
|
+
limit: parseInt(options.limit ?? '10', 10),
|
|
83
|
+
threshold:
|
|
84
|
+
options.threshold !== undefined ? parseFloat(options.threshold) : undefined,
|
|
85
|
+
includeContent: options.includeContent,
|
|
86
|
+
detail: options.detail ?? 'minimal',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (globalOpts.format === 'json') {
|
|
90
|
+
console.log(JSON.stringify(results, null, 2));
|
|
91
|
+
} else if (globalOpts.quiet === true) {
|
|
92
|
+
// Quiet mode: just list matching paths/URLs, one per line
|
|
93
|
+
for (const r of results.results) {
|
|
99
94
|
const path = r.metadata.path ?? r.metadata.url ?? 'unknown';
|
|
100
|
-
console.log(
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
console.log(path);
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
console.log(`\nSearch: "${query}"`);
|
|
99
|
+
console.log(
|
|
100
|
+
`Mode: ${results.mode} | Detail: ${String(options.detail)} | Stores: ${String(results.stores.length)} | Results: ${String(results.totalResults)} | Time: ${String(results.timeMs)}ms\n`
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (results.results.length === 0) {
|
|
104
|
+
console.log('No results found.\n');
|
|
105
|
+
} else {
|
|
106
|
+
for (let i = 0; i < results.results.length; i++) {
|
|
107
|
+
const r = results.results[i];
|
|
108
|
+
if (r === undefined) continue;
|
|
109
|
+
|
|
110
|
+
if (r.summary) {
|
|
111
|
+
console.log(
|
|
112
|
+
`${String(i + 1)}. [${r.score.toFixed(2)}] ${r.summary.type}: ${r.summary.name}`
|
|
113
|
+
);
|
|
114
|
+
console.log(` ${r.summary.location}`);
|
|
115
|
+
console.log(` ${r.summary.purpose}`);
|
|
116
|
+
|
|
117
|
+
if (r.context && options.detail !== 'minimal') {
|
|
118
|
+
console.log(` Imports: ${r.context.keyImports.slice(0, 3).join(', ')}`);
|
|
119
|
+
console.log(
|
|
120
|
+
` Related: ${r.context.relatedConcepts.slice(0, 3).join(', ')}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log();
|
|
125
|
+
} else {
|
|
126
|
+
// Display without summary
|
|
127
|
+
const path = r.metadata.path ?? r.metadata.url ?? 'unknown';
|
|
128
|
+
console.log(`${String(i + 1)}. [${r.score.toFixed(2)}] ${path}`);
|
|
129
|
+
const preview =
|
|
130
|
+
r.highlight ??
|
|
131
|
+
r.content.slice(0, 150).replace(/\n/g, ' ') +
|
|
132
|
+
(r.content.length > 150 ? '...' : '');
|
|
133
|
+
console.log(` ${preview}\n`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
103
136
|
}
|
|
104
137
|
}
|
|
105
138
|
}
|
|
139
|
+
} finally {
|
|
140
|
+
await destroyServices(services);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (exitCode !== 0) {
|
|
144
|
+
process.exit(exitCode);
|
|
106
145
|
}
|
|
107
|
-
} finally {
|
|
108
|
-
await destroyServices(services);
|
|
109
146
|
}
|
|
110
|
-
|
|
147
|
+
);
|
|
111
148
|
|
|
112
149
|
return search;
|
|
113
150
|
}
|