bluera-knowledge 0.9.32 → 0.9.36
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/hooks/post-edit-check.sh +5 -3
- package/.claude/skills/atomic-commits/SKILL.md +3 -1
- package/.husky/pre-commit +3 -2
- package/.prettierrc +9 -0
- package/.versionrc.json +1 -1
- package/CHANGELOG.md +70 -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-RST4XGRL.js → chunk-DC7CGSGT.js} +288 -241
- package/dist/chunk-DC7CGSGT.js.map +1 -0
- package/dist/{chunk-6PBP5DVD.js → chunk-WFNPNAAP.js} +3212 -3054
- package/dist/chunk-WFNPNAAP.js.map +1 -0
- package/dist/{chunk-WT2DAEO7.js → chunk-Z2KKVH45.js} +548 -482
- package/dist/chunk-Z2KKVH45.js.map +1 -0
- package/dist/index.js +871 -758
- 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 +97 -71
- 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 -166
- 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 +4 -4
- package/src/db/lance.ts +16 -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 +6 -9
- 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 +1 -1
- 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 +28 -30
- package/src/workers/background-worker.test.ts +54 -40
- package/src/workers/background-worker.ts +76 -60
- package/src/workers/pid-file.test.ts +167 -0
- package/src/workers/pid-file.ts +82 -0
- 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 +6 -5
- 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/dist/chunk-6PBP5DVD.js.map +0 -1
- package/dist/chunk-L2YVNC63.js.map +0 -1
- package/dist/chunk-RST4XGRL.js.map +0 -1
- package/dist/chunk-WT2DAEO7.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
|
@@ -13,10 +13,10 @@ type SimplifyBodyData<T> = {
|
|
|
13
13
|
[K in keyof T]: string | File | (string | File)[] | BodyDataValueDotAll extends T[K]
|
|
14
14
|
? string | File | (string | File)[] | BodyDataValueDotAll
|
|
15
15
|
: string | File | BodyDataValueDot extends T[K]
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
? string | File | BodyDataValueDot
|
|
17
|
+
: string | File | (string | File)[] extends T[K]
|
|
18
|
+
? string | File | (string | File)[]
|
|
19
|
+
: string | File
|
|
20
20
|
} & {}
|
|
21
21
|
|
|
22
22
|
type BodyDataValueComponent<T> =
|
|
@@ -25,16 +25,16 @@ type BodyDataValueComponent<T> =
|
|
|
25
25
|
| (T extends { all: false }
|
|
26
26
|
? never // explicitly set to false
|
|
27
27
|
: T extends { all: true } | { all: boolean }
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
? (string | File)[] // use all option
|
|
29
|
+
: never) // without options
|
|
30
30
|
type BodyDataValueObject<T> = { [key: string]: BodyDataValueComponent<T> | BodyDataValueObject<T> }
|
|
31
31
|
type BodyDataValue<T> =
|
|
32
32
|
| BodyDataValueComponent<T>
|
|
33
33
|
| (T extends { dot: false }
|
|
34
34
|
? never // explicitly set to false
|
|
35
35
|
: T extends { dot: true } | { dot: boolean }
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
? BodyDataValueObject<T> // use dot option
|
|
37
|
+
: never) // without options
|
|
38
38
|
export type BodyData<T extends Partial<ParseBodyOptions> = {}> = SimplifyBodyData<
|
|
39
39
|
Record<string, BodyDataValue<T>>
|
|
40
40
|
>
|
|
@@ -18,9 +18,9 @@ export function getColorEnabled(): boolean {
|
|
|
18
18
|
typeof Deno?.noColor === 'boolean'
|
|
19
19
|
? (Deno.noColor as boolean)
|
|
20
20
|
: typeof process !== 'undefined'
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
? // eslint-disable-next-line no-unsafe-optional-chaining
|
|
22
|
+
'NO_COLOR' in process?.env
|
|
23
|
+
: false
|
|
24
24
|
|
|
25
25
|
return !isNoColor
|
|
26
26
|
}
|
|
@@ -31,8 +31,8 @@ export type CookiePrefixOptions = 'host' | 'secure'
|
|
|
31
31
|
export type CookieConstraint<Name> = Name extends `__Secure-${string}`
|
|
32
32
|
? CookieOptions & SecureCookieConstraint
|
|
33
33
|
: Name extends `__Host-${string}`
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
? CookieOptions & HostCookieConstraint
|
|
35
|
+
: CookieOptions
|
|
36
36
|
|
|
37
37
|
const algorithm = { name: 'HMAC', hash: 'SHA-256' }
|
|
38
38
|
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export const decodeBase64Url = (str: string): Uint8Array => {
|
|
7
|
-
return decodeBase64(str.replace(/_|-/g, (m) => ({ _: '/', '-': '+' }[m] ?? m))
|
|
7
|
+
return decodeBase64(str.replace(/_|-/g, (m) => ({ _: '/', '-': '+' })[m] ?? m))
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export const encodeBase64Url = (buf: ArrayBufferLike): string =>
|
|
11
|
-
encodeBase64(buf).replace(/\/|\+/g, (m) => ({ '/': '_', '+': '-' }[m] ?? m)
|
|
11
|
+
encodeBase64(buf).replace(/\/|\+/g, (m) => ({ '/': '_', '+': '-' })[m] ?? m)
|
|
12
12
|
|
|
13
13
|
// This approach is written in MDN.
|
|
14
14
|
// btoa does not support utf-8 characters. So we need a little bit hack.
|
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
7
|
export type Expect<T extends true> = T
|
|
8
|
-
export type Equal<X, Y> =
|
|
9
|
-
? true
|
|
10
|
-
: false
|
|
8
|
+
export type Equal<X, Y> =
|
|
9
|
+
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false
|
|
11
10
|
export type NotEqual<X, Y> = true extends Equal<X, Y> ? false : true
|
|
12
11
|
|
|
13
12
|
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
|
|
@@ -16,11 +15,8 @@ export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) ex
|
|
|
16
15
|
? I
|
|
17
16
|
: never
|
|
18
17
|
|
|
19
|
-
export type RemoveBlankRecord<T> =
|
|
20
|
-
? K extends string
|
|
21
|
-
? T
|
|
22
|
-
: never
|
|
23
|
-
: never
|
|
18
|
+
export type RemoveBlankRecord<T> =
|
|
19
|
+
T extends Record<infer K, unknown> ? (K extends string ? T : never) : never
|
|
24
20
|
|
|
25
21
|
export type IfAnyThenEmptyObject<T> = 0 extends 1 & T ? {} : T
|
|
26
22
|
|
|
@@ -46,29 +42,31 @@ export type JSONParsed<T> = T extends { toJSON(): infer J }
|
|
|
46
42
|
? (() => J) extends () => JSONPrimitive
|
|
47
43
|
? J
|
|
48
44
|
: (() => J) extends () => { toJSON(): unknown }
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
? {}
|
|
46
|
+
: JSONParsed<J>
|
|
51
47
|
: T extends JSONPrimitive
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
48
|
+
? T
|
|
49
|
+
: T extends InvalidJSONValue
|
|
50
|
+
? never
|
|
51
|
+
: T extends []
|
|
52
|
+
? []
|
|
53
|
+
: T extends readonly [infer R, ...infer U]
|
|
54
|
+
? [JSONParsed<InvalidToNull<R>>, ...JSONParsed<U>]
|
|
55
|
+
: T extends Array<infer U>
|
|
56
|
+
? Array<JSONParsed<InvalidToNull<U>>>
|
|
57
|
+
: T extends ReadonlyArray<infer U>
|
|
58
|
+
? ReadonlyArray<JSONParsed<InvalidToNull<U>>>
|
|
59
|
+
: T extends Set<unknown> | Map<unknown, unknown>
|
|
60
|
+
? {}
|
|
61
|
+
: T extends object
|
|
62
|
+
? {
|
|
63
|
+
[K in keyof OmitSymbolKeys<T> as IsInvalid<T[K]> extends true
|
|
64
|
+
? never
|
|
65
|
+
: K]: boolean extends IsInvalid<T[K]>
|
|
66
|
+
? JSONParsed<T[K]> | undefined
|
|
67
|
+
: JSONParsed<T[K]>
|
|
68
|
+
}
|
|
69
|
+
: never
|
|
72
70
|
|
|
73
71
|
/**
|
|
74
72
|
* Useful to flatten the type output to improve type hints shown in editors. And also to transform an interface into a type to aide with assignability.
|
|
@@ -92,9 +90,8 @@ export type RequiredKeysOf<BaseType extends object> = Exclude<
|
|
|
92
90
|
undefined
|
|
93
91
|
>
|
|
94
92
|
|
|
95
|
-
export type HasRequiredKeys<BaseType extends object> =
|
|
96
|
-
? false
|
|
97
|
-
: true
|
|
93
|
+
export type HasRequiredKeys<BaseType extends object> =
|
|
94
|
+
RequiredKeysOf<BaseType> extends never ? false : true
|
|
98
95
|
|
|
99
96
|
export type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false
|
|
100
97
|
|
|
@@ -14,7 +14,7 @@ export type ValidationFunction<
|
|
|
14
14
|
InputType,
|
|
15
15
|
OutputType,
|
|
16
16
|
E extends Env = {},
|
|
17
|
-
P extends string = string
|
|
17
|
+
P extends string = string,
|
|
18
18
|
> = (
|
|
19
19
|
value: InputType,
|
|
20
20
|
c: Context<E, P>
|
|
@@ -55,7 +55,7 @@ export const validator = <
|
|
|
55
55
|
out: { [K in U]: OutputTypeExcludeResponseType }
|
|
56
56
|
},
|
|
57
57
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
-
E extends Env = any
|
|
58
|
+
E extends Env = any,
|
|
59
59
|
>(
|
|
60
60
|
target: U,
|
|
61
61
|
validationFunc: ValidationFunction<
|
|
@@ -171,8 +171,9 @@ export class TestHTMLServer {
|
|
|
171
171
|
// Page with many links (for testing link extraction)
|
|
172
172
|
if (url === '/many-links') {
|
|
173
173
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
174
|
-
const links = Array.from(
|
|
175
|
-
|
|
174
|
+
const links = Array.from(
|
|
175
|
+
{ length: 20 },
|
|
176
|
+
(_, i) => `<a href="/link${String(i)}">Link ${String(i)}</a>`
|
|
176
177
|
).join('\n');
|
|
177
178
|
res.end(`
|
|
178
179
|
<!DOCTYPE html>
|
|
@@ -108,9 +108,7 @@ export function measureSync<T>(
|
|
|
108
108
|
* @param measurements - Array of measurements
|
|
109
109
|
* @returns Statistical summary
|
|
110
110
|
*/
|
|
111
|
-
export function calculateStats(
|
|
112
|
-
measurements: PerformanceMeasurement[]
|
|
113
|
-
): PerformanceStats {
|
|
111
|
+
export function calculateStats(measurements: PerformanceMeasurement[]): PerformanceStats {
|
|
114
112
|
if (measurements.length === 0) {
|
|
115
113
|
return {
|
|
116
114
|
count: 0,
|
|
@@ -131,8 +129,7 @@ export function calculateStats(
|
|
|
131
129
|
|
|
132
130
|
// Calculate standard deviation
|
|
133
131
|
const squaredDiffs = durations.map((d) => Math.pow(d - mean, 2));
|
|
134
|
-
const avgSquaredDiff =
|
|
135
|
-
squaredDiffs.reduce((sum, d) => sum + d, 0) / durations.length;
|
|
132
|
+
const avgSquaredDiff = squaredDiffs.reduce((sum, d) => sum + d, 0) / durations.length;
|
|
136
133
|
const stdDev = Math.sqrt(avgSquaredDiff);
|
|
137
134
|
|
|
138
135
|
return {
|
|
@@ -173,10 +170,7 @@ function percentile(sortedValues: number[], p: number): number {
|
|
|
173
170
|
* @param measurement - Measurement to check
|
|
174
171
|
* @param maxDuration - Maximum acceptable duration in ms
|
|
175
172
|
*/
|
|
176
|
-
export function assertDuration(
|
|
177
|
-
measurement: PerformanceMeasurement,
|
|
178
|
-
maxDuration: number
|
|
179
|
-
): void {
|
|
173
|
+
export function assertDuration(measurement: PerformanceMeasurement, maxDuration: number): void {
|
|
180
174
|
if (measurement.duration > maxDuration) {
|
|
181
175
|
throw new Error(
|
|
182
176
|
`${measurement.name} took ${measurement.duration.toFixed(2)}ms, ` +
|
|
@@ -203,27 +197,19 @@ export function assertPerformanceTargets(
|
|
|
203
197
|
const failures: string[] = [];
|
|
204
198
|
|
|
205
199
|
if (targets.maxMean !== undefined && stats.mean > targets.maxMean) {
|
|
206
|
-
failures.push(
|
|
207
|
-
`Mean ${stats.mean.toFixed(2)}ms exceeds target ${targets.maxMean}ms`
|
|
208
|
-
);
|
|
200
|
+
failures.push(`Mean ${stats.mean.toFixed(2)}ms exceeds target ${targets.maxMean}ms`);
|
|
209
201
|
}
|
|
210
202
|
|
|
211
203
|
if (targets.maxP95 !== undefined && stats.p95 > targets.maxP95) {
|
|
212
|
-
failures.push(
|
|
213
|
-
`P95 ${stats.p95.toFixed(2)}ms exceeds target ${targets.maxP95}ms`
|
|
214
|
-
);
|
|
204
|
+
failures.push(`P95 ${stats.p95.toFixed(2)}ms exceeds target ${targets.maxP95}ms`);
|
|
215
205
|
}
|
|
216
206
|
|
|
217
207
|
if (targets.maxP99 !== undefined && stats.p99 > targets.maxP99) {
|
|
218
|
-
failures.push(
|
|
219
|
-
`P99 ${stats.p99.toFixed(2)}ms exceeds target ${targets.maxP99}ms`
|
|
220
|
-
);
|
|
208
|
+
failures.push(`P99 ${stats.p99.toFixed(2)}ms exceeds target ${targets.maxP99}ms`);
|
|
221
209
|
}
|
|
222
210
|
|
|
223
211
|
if (targets.maxMax !== undefined && stats.max > targets.maxMax) {
|
|
224
|
-
failures.push(
|
|
225
|
-
`Max ${stats.max.toFixed(2)}ms exceeds target ${targets.maxMax}ms`
|
|
226
|
-
);
|
|
212
|
+
failures.push(`Max ${stats.max.toFixed(2)}ms exceeds target ${targets.maxMax}ms`);
|
|
227
213
|
}
|
|
228
214
|
|
|
229
215
|
if (failures.length > 0) {
|
|
@@ -263,10 +249,7 @@ export class Benchmark {
|
|
|
263
249
|
/**
|
|
264
250
|
* Run multiple iterations
|
|
265
251
|
*/
|
|
266
|
-
async runIterations<T>(
|
|
267
|
-
iterations: number,
|
|
268
|
-
fn: () => Promise<T>
|
|
269
|
-
): Promise<T[]> {
|
|
252
|
+
async runIterations<T>(iterations: number, fn: () => Promise<T>): Promise<T[]> {
|
|
270
253
|
const results: T[] = [];
|
|
271
254
|
for (let i = 0; i < iterations; i++) {
|
|
272
255
|
results.push(await this.run(fn));
|
|
@@ -71,7 +71,9 @@ export function parseSearchOutput(output: string): SearchResult[] {
|
|
|
71
71
|
const headerContent = headerMatch[3].trim();
|
|
72
72
|
|
|
73
73
|
// Check if this is new format (contains type prefix like "function:", "class:", etc.)
|
|
74
|
-
const typeMatch = headerContent.match(
|
|
74
|
+
const typeMatch = headerContent.match(
|
|
75
|
+
/^(function|class|interface|type|const|documentation):\s+(.+)$/
|
|
76
|
+
);
|
|
75
77
|
if (typeMatch) {
|
|
76
78
|
// New format - next line should contain the location
|
|
77
79
|
currentResult = {
|
|
@@ -140,18 +142,13 @@ export function parseSearchOutput(output: string): SearchResult[] {
|
|
|
140
142
|
* @param expected - Expected match criteria
|
|
141
143
|
* @returns true if result matches all criteria
|
|
142
144
|
*/
|
|
143
|
-
export function matchesExpectation(
|
|
144
|
-
result: SearchResult,
|
|
145
|
-
expected: ExpectedMatch
|
|
146
|
-
): boolean {
|
|
145
|
+
export function matchesExpectation(result: SearchResult, expected: ExpectedMatch): boolean {
|
|
147
146
|
const contentLower = result.content.toLowerCase();
|
|
148
147
|
const sourceLower = result.source.toLowerCase();
|
|
149
148
|
|
|
150
149
|
// Check keywords (any match)
|
|
151
150
|
if (expected.keywords && expected.keywords.length > 0) {
|
|
152
|
-
const hasKeyword = expected.keywords.some((kw) =>
|
|
153
|
-
contentLower.includes(kw.toLowerCase())
|
|
154
|
-
);
|
|
151
|
+
const hasKeyword = expected.keywords.some((kw) => contentLower.includes(kw.toLowerCase()));
|
|
155
152
|
if (!hasKeyword) return false;
|
|
156
153
|
}
|
|
157
154
|
|
|
@@ -190,16 +187,11 @@ export function matchesExpectation(
|
|
|
190
187
|
* @param expected - Expected match criteria
|
|
191
188
|
* @returns The first matching result, or throws if none found
|
|
192
189
|
*/
|
|
193
|
-
export function assertHasMatch(
|
|
194
|
-
results: SearchResult[],
|
|
195
|
-
expected: ExpectedMatch
|
|
196
|
-
): SearchResult {
|
|
190
|
+
export function assertHasMatch(results: SearchResult[], expected: ExpectedMatch): SearchResult {
|
|
197
191
|
const match = results.find((r) => matchesExpectation(r, expected));
|
|
198
192
|
if (!match) {
|
|
199
193
|
const criteria = JSON.stringify(expected, null, 2);
|
|
200
|
-
const resultsSummary = results
|
|
201
|
-
.map((r) => ` ${r.rank}. [${r.score}] ${r.source}`)
|
|
202
|
-
.join('\n');
|
|
194
|
+
const resultsSummary = results.map((r) => ` ${r.rank}. [${r.score}] ${r.source}`).join('\n');
|
|
203
195
|
throw new Error(
|
|
204
196
|
`No result matches expected criteria:\n${criteria}\n\nResults:\n${resultsSummary}`
|
|
205
197
|
);
|
|
@@ -256,9 +248,7 @@ export function calculateRelevanceMetrics(
|
|
|
256
248
|
relevantCount: relevant.length,
|
|
257
249
|
totalCount: results.length,
|
|
258
250
|
averageScore:
|
|
259
|
-
results.length > 0
|
|
260
|
-
? results.reduce((sum, r) => sum + r.score, 0) / results.length
|
|
261
|
-
: 0,
|
|
251
|
+
results.length > 0 ? results.reduce((sum, r) => sum + r.score, 0) / results.length : 0,
|
|
262
252
|
topScore: results.length > 0 ? results[0].score : 0,
|
|
263
253
|
};
|
|
264
254
|
}
|
|
@@ -298,10 +288,7 @@ export function compareRelevance(
|
|
|
298
288
|
* @param results - Search results
|
|
299
289
|
* @param minScore - Minimum acceptable score
|
|
300
290
|
*/
|
|
301
|
-
export function assertMinimumScores(
|
|
302
|
-
results: SearchResult[],
|
|
303
|
-
minScore: number
|
|
304
|
-
): void {
|
|
291
|
+
export function assertMinimumScores(results: SearchResult[], minScore: number): void {
|
|
305
292
|
for (const result of results) {
|
|
306
293
|
if (result.score < minScore) {
|
|
307
294
|
throw new Error(
|
|
@@ -330,52 +317,10 @@ export function assertProperOrdering(results: SearchResult[]): void {
|
|
|
330
317
|
* Common keyword sets for testing
|
|
331
318
|
*/
|
|
332
319
|
export const CommonKeywords = {
|
|
333
|
-
AUTHENTICATION: [
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
'token',
|
|
338
|
-
'password',
|
|
339
|
-
'session',
|
|
340
|
-
'oauth',
|
|
341
|
-
'credential',
|
|
342
|
-
],
|
|
343
|
-
API: [
|
|
344
|
-
'endpoint',
|
|
345
|
-
'request',
|
|
346
|
-
'response',
|
|
347
|
-
'rest',
|
|
348
|
-
'http',
|
|
349
|
-
'route',
|
|
350
|
-
'controller',
|
|
351
|
-
'middleware',
|
|
352
|
-
],
|
|
353
|
-
DATABASE: [
|
|
354
|
-
'query',
|
|
355
|
-
'database',
|
|
356
|
-
'repository',
|
|
357
|
-
'entity',
|
|
358
|
-
'model',
|
|
359
|
-
'schema',
|
|
360
|
-
'sql',
|
|
361
|
-
'orm',
|
|
362
|
-
],
|
|
363
|
-
TYPESCRIPT: [
|
|
364
|
-
'typescript',
|
|
365
|
-
'type',
|
|
366
|
-
'interface',
|
|
367
|
-
'generic',
|
|
368
|
-
'compiler',
|
|
369
|
-
'tsc',
|
|
370
|
-
],
|
|
320
|
+
AUTHENTICATION: ['auth', 'login', 'jwt', 'token', 'password', 'session', 'oauth', 'credential'],
|
|
321
|
+
API: ['endpoint', 'request', 'response', 'rest', 'http', 'route', 'controller', 'middleware'],
|
|
322
|
+
DATABASE: ['query', 'database', 'repository', 'entity', 'model', 'schema', 'sql', 'orm'],
|
|
323
|
+
TYPESCRIPT: ['typescript', 'type', 'interface', 'generic', 'compiler', 'tsc'],
|
|
371
324
|
REACT: ['react', 'component', 'jsx', 'hook', 'state', 'props', 'render'],
|
|
372
|
-
ERROR_HANDLING: [
|
|
373
|
-
'error',
|
|
374
|
-
'exception',
|
|
375
|
-
'catch',
|
|
376
|
-
'throw',
|
|
377
|
-
'try',
|
|
378
|
-
'handler',
|
|
379
|
-
'validation',
|
|
380
|
-
],
|
|
325
|
+
ERROR_HANDLING: ['error', 'exception', 'catch', 'throw', 'try', 'handler', 'validation'],
|
|
381
326
|
} as const;
|
|
@@ -50,7 +50,10 @@ describe('CLI Consistency', () => {
|
|
|
50
50
|
* Helper to run CLI and capture output + exit code
|
|
51
51
|
* Properly handles quoted arguments
|
|
52
52
|
*/
|
|
53
|
-
const runCli = (
|
|
53
|
+
const runCli = (
|
|
54
|
+
args: string,
|
|
55
|
+
options: { expectError?: boolean; stdin?: string } = {}
|
|
56
|
+
): {
|
|
54
57
|
stdout: string;
|
|
55
58
|
stderr: string;
|
|
56
59
|
exitCode: number;
|
|
@@ -107,7 +110,7 @@ describe('CLI Consistency', () => {
|
|
|
107
110
|
it('returns exit code 0 on success', () => {
|
|
108
111
|
const result = runCli('store list');
|
|
109
112
|
expect(result.exitCode).toBe(0);
|
|
110
|
-
});
|
|
113
|
+
}, 15000);
|
|
111
114
|
|
|
112
115
|
it('returns non-zero exit code when store not found', () => {
|
|
113
116
|
const result = runCli('store info nonexistent-store');
|
|
@@ -133,7 +136,6 @@ describe('CLI Consistency', () => {
|
|
|
133
136
|
expect(result.exitCode).not.toBe(0);
|
|
134
137
|
expect(result.stderr).toContain('Error: Store not found');
|
|
135
138
|
});
|
|
136
|
-
|
|
137
139
|
});
|
|
138
140
|
|
|
139
141
|
describe('--format json Support', () => {
|
|
@@ -262,7 +264,7 @@ describe('CLI Consistency', () => {
|
|
|
262
264
|
const result = runCli('store delete delete-force-store --force');
|
|
263
265
|
expect(result.exitCode).toBe(0);
|
|
264
266
|
expect(result.stdout).toContain('Deleted');
|
|
265
|
-
});
|
|
267
|
+
}, 15000);
|
|
266
268
|
|
|
267
269
|
it('accepts -y as alias for --force', () => {
|
|
268
270
|
try {
|
|
@@ -291,6 +293,5 @@ describe('CLI Consistency', () => {
|
|
|
291
293
|
expect(result.stdout).not.toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
|
|
292
294
|
expect(result.exitCode).toBe(0);
|
|
293
295
|
}, 120000);
|
|
294
|
-
|
|
295
296
|
});
|
|
296
297
|
});
|
|
@@ -33,6 +33,18 @@ describe('Python Bridge Integration Tests', () => {
|
|
|
33
33
|
expect(result.pages.length).toBeGreaterThan(0);
|
|
34
34
|
}, 30000);
|
|
35
35
|
|
|
36
|
+
it('stop() waits for process to actually exit', async () => {
|
|
37
|
+
// Make a request to ensure process is running
|
|
38
|
+
await bridge.crawl(baseUrl);
|
|
39
|
+
|
|
40
|
+
// Stop should wait for process exit
|
|
41
|
+
await bridge.stop();
|
|
42
|
+
|
|
43
|
+
// After stop() resolves, the process should be fully terminated
|
|
44
|
+
// Calling stop() again should be a no-op (process already null)
|
|
45
|
+
await bridge.stop(); // Should not hang or error
|
|
46
|
+
}, 30000);
|
|
47
|
+
|
|
36
48
|
it('can create new bridge after stopping', async () => {
|
|
37
49
|
// Stop the current bridge
|
|
38
50
|
await bridge.stop();
|
|
@@ -143,9 +155,7 @@ describe('Python Bridge Integration Tests', () => {
|
|
|
143
155
|
describe('Error Handling', () => {
|
|
144
156
|
it('handles invalid URLs gracefully', async () => {
|
|
145
157
|
// Python worker should handle invalid URLs and return error
|
|
146
|
-
await expect(
|
|
147
|
-
bridge.crawl('not-a-valid-url')
|
|
148
|
-
).rejects.toThrow();
|
|
158
|
+
await expect(bridge.crawl('not-a-valid-url')).rejects.toThrow();
|
|
149
159
|
}, 30000);
|
|
150
160
|
|
|
151
161
|
it('handles 404 pages', async () => {
|
package/tests/mcp/server.test.ts
CHANGED
|
@@ -26,6 +26,54 @@ export function parseToken(token: string): object {
|
|
|
26
26
|
expect(unit.endLine).toBe(5);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
it('should extract function with Promise return type', () => {
|
|
30
|
+
const code = `
|
|
31
|
+
export async function getData(id: string): Promise<User> {
|
|
32
|
+
return await db.find(id);
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
const service = new CodeUnitService();
|
|
37
|
+
const unit = service.extractCodeUnit(code, 'getData', 'typescript');
|
|
38
|
+
|
|
39
|
+
expect(unit).toBeDefined();
|
|
40
|
+
expect(unit.type).toBe('function');
|
|
41
|
+
expect(unit.name).toBe('getData');
|
|
42
|
+
// Should include the generic type
|
|
43
|
+
expect(unit.signature).toContain('getData(id: string)');
|
|
44
|
+
expect(unit.signature).toContain('Promise<User>');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should extract function with array return type', () => {
|
|
48
|
+
const code = `
|
|
49
|
+
export function getUsers(): User[] {
|
|
50
|
+
return users;
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
const service = new CodeUnitService();
|
|
55
|
+
const unit = service.extractCodeUnit(code, 'getUsers', 'typescript');
|
|
56
|
+
|
|
57
|
+
expect(unit).toBeDefined();
|
|
58
|
+
expect(unit.signature).toContain('getUsers()');
|
|
59
|
+
expect(unit.signature).toContain('User[]');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should extract function with union return type', () => {
|
|
63
|
+
const code = `
|
|
64
|
+
export function findUser(id: string): User | null {
|
|
65
|
+
return users.find(u => u.id === id) || null;
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
const service = new CodeUnitService();
|
|
70
|
+
const unit = service.extractCodeUnit(code, 'findUser', 'typescript');
|
|
71
|
+
|
|
72
|
+
expect(unit).toBeDefined();
|
|
73
|
+
expect(unit.signature).toContain('findUser(id: string)');
|
|
74
|
+
expect(unit.signature).toContain('User | null');
|
|
75
|
+
});
|
|
76
|
+
|
|
29
77
|
it('should extract class with methods', () => {
|
|
30
78
|
const code = `
|
|
31
79
|
export class UserService {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { JobService } from '../../src/services/job.service.js';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
|
|
7
|
+
describe('JobService', () => {
|
|
8
|
+
let jobService: JobService;
|
|
9
|
+
let tempDir: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'job-service-test-'));
|
|
13
|
+
jobService = new JobService(tempDir);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
// Clean up temp directory
|
|
18
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('cancelJob', () => {
|
|
22
|
+
it('handles invalid PID in PID file gracefully', () => {
|
|
23
|
+
// Create a job
|
|
24
|
+
const job = jobService.createJob({
|
|
25
|
+
type: 'index',
|
|
26
|
+
message: 'Test job',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Write an invalid PID to the PID file
|
|
30
|
+
const pidFile = path.join(tempDir, 'jobs', `${job.id}.pid`);
|
|
31
|
+
fs.writeFileSync(pidFile, 'not-a-number', 'utf-8');
|
|
32
|
+
|
|
33
|
+
// Cancel should succeed without throwing
|
|
34
|
+
const result = jobService.cancelJob(job.id);
|
|
35
|
+
expect(result.success).toBe(true);
|
|
36
|
+
|
|
37
|
+
// Job should be marked as cancelled
|
|
38
|
+
const cancelled = jobService.getJob(job.id);
|
|
39
|
+
expect(cancelled?.status).toBe('cancelled');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('handles NaN PID gracefully', () => {
|
|
43
|
+
// Create a job
|
|
44
|
+
const job = jobService.createJob({
|
|
45
|
+
type: 'index',
|
|
46
|
+
message: 'Test job',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Write NaN-producing content to PID file
|
|
50
|
+
const pidFile = path.join(tempDir, 'jobs', `${job.id}.pid`);
|
|
51
|
+
fs.writeFileSync(pidFile, '', 'utf-8');
|
|
52
|
+
|
|
53
|
+
// Cancel should succeed without throwing
|
|
54
|
+
const result = jobService.cancelJob(job.id);
|
|
55
|
+
expect(result.success).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('handles negative PID gracefully', () => {
|
|
59
|
+
// Create a job
|
|
60
|
+
const job = jobService.createJob({
|
|
61
|
+
type: 'index',
|
|
62
|
+
message: 'Test job',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Write negative PID to file (invalid)
|
|
66
|
+
const pidFile = path.join(tempDir, 'jobs', `${job.id}.pid`);
|
|
67
|
+
fs.writeFileSync(pidFile, '-1', 'utf-8');
|
|
68
|
+
|
|
69
|
+
// Cancel should succeed without throwing
|
|
70
|
+
const result = jobService.cancelJob(job.id);
|
|
71
|
+
expect(result.success).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('handles zero PID gracefully', () => {
|
|
75
|
+
// Create a job
|
|
76
|
+
const job = jobService.createJob({
|
|
77
|
+
type: 'index',
|
|
78
|
+
message: 'Test job',
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Write zero PID to file (invalid - would kill current process group)
|
|
82
|
+
const pidFile = path.join(tempDir, 'jobs', `${job.id}.pid`);
|
|
83
|
+
fs.writeFileSync(pidFile, '0', 'utf-8');
|
|
84
|
+
|
|
85
|
+
// Cancel should succeed without throwing or killing process group
|
|
86
|
+
const result = jobService.cancelJob(job.id);
|
|
87
|
+
expect(result.success).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('handles non-existent process gracefully', () => {
|
|
91
|
+
// Create a job
|
|
92
|
+
const job = jobService.createJob({
|
|
93
|
+
type: 'index',
|
|
94
|
+
message: 'Test job',
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Write a PID that almost certainly doesn't exist
|
|
98
|
+
const pidFile = path.join(tempDir, 'jobs', `${job.id}.pid`);
|
|
99
|
+
fs.writeFileSync(pidFile, '999999999', 'utf-8');
|
|
100
|
+
|
|
101
|
+
// Cancel should succeed without throwing
|
|
102
|
+
const result = jobService.cancelJob(job.id);
|
|
103
|
+
expect(result.success).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('cleans up PID file after cancel', () => {
|
|
107
|
+
// Create a job
|
|
108
|
+
const job = jobService.createJob({
|
|
109
|
+
type: 'index',
|
|
110
|
+
message: 'Test job',
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Write a valid-looking PID
|
|
114
|
+
const pidFile = path.join(tempDir, 'jobs', `${job.id}.pid`);
|
|
115
|
+
fs.writeFileSync(pidFile, '12345', 'utf-8');
|
|
116
|
+
|
|
117
|
+
// Cancel the job
|
|
118
|
+
jobService.cancelJob(job.id);
|
|
119
|
+
|
|
120
|
+
// PID file should be deleted
|
|
121
|
+
expect(fs.existsSync(pidFile)).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|