bluera-knowledge 0.9.32 → 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.
Files changed (196) hide show
  1. package/.claude/hooks/post-edit-check.sh +5 -3
  2. package/.claude/skills/atomic-commits/SKILL.md +3 -1
  3. package/.husky/pre-commit +3 -2
  4. package/.prettierrc +9 -0
  5. package/.versionrc.json +1 -1
  6. package/CHANGELOG.md +33 -0
  7. package/CLAUDE.md +6 -0
  8. package/README.md +25 -13
  9. package/bun.lock +277 -33
  10. package/dist/{chunk-L2YVNC63.js → chunk-6FHWC36B.js} +9 -1
  11. package/dist/chunk-6FHWC36B.js.map +1 -0
  12. package/dist/{chunk-RST4XGRL.js → chunk-DC7CGSGT.js} +288 -241
  13. package/dist/chunk-DC7CGSGT.js.map +1 -0
  14. package/dist/{chunk-6PBP5DVD.js → chunk-WFNPNAAP.js} +3212 -3054
  15. package/dist/chunk-WFNPNAAP.js.map +1 -0
  16. package/dist/{chunk-WT2DAEO7.js → chunk-Z2KKVH45.js} +548 -482
  17. package/dist/chunk-Z2KKVH45.js.map +1 -0
  18. package/dist/index.js +871 -758
  19. package/dist/index.js.map +1 -1
  20. package/dist/mcp/server.js +3 -3
  21. package/dist/watch.service-BJV3TI3F.js +7 -0
  22. package/dist/workers/background-worker-cli.js +46 -45
  23. package/dist/workers/background-worker-cli.js.map +1 -1
  24. package/eslint.config.js +43 -1
  25. package/package.json +18 -11
  26. package/plugin.json +8 -0
  27. package/python/requirements.txt +1 -1
  28. package/src/analysis/ast-parser.test.ts +12 -11
  29. package/src/analysis/ast-parser.ts +28 -22
  30. package/src/analysis/code-graph.test.ts +52 -62
  31. package/src/analysis/code-graph.ts +9 -13
  32. package/src/analysis/dependency-usage-analyzer.test.ts +91 -271
  33. package/src/analysis/dependency-usage-analyzer.ts +52 -24
  34. package/src/analysis/go-ast-parser.test.ts +22 -22
  35. package/src/analysis/go-ast-parser.ts +18 -25
  36. package/src/analysis/parser-factory.test.ts +9 -9
  37. package/src/analysis/parser-factory.ts +3 -3
  38. package/src/analysis/python-ast-parser.test.ts +27 -27
  39. package/src/analysis/python-ast-parser.ts +2 -2
  40. package/src/analysis/repo-url-resolver.test.ts +82 -82
  41. package/src/analysis/rust-ast-parser.test.ts +19 -19
  42. package/src/analysis/rust-ast-parser.ts +17 -27
  43. package/src/analysis/tree-sitter-parser.test.ts +3 -3
  44. package/src/analysis/tree-sitter-parser.ts +10 -16
  45. package/src/cli/commands/crawl.test.ts +40 -24
  46. package/src/cli/commands/crawl.ts +186 -166
  47. package/src/cli/commands/index-cmd.test.ts +90 -90
  48. package/src/cli/commands/index-cmd.ts +52 -36
  49. package/src/cli/commands/mcp.test.ts +6 -6
  50. package/src/cli/commands/mcp.ts +2 -2
  51. package/src/cli/commands/plugin-api.test.ts +16 -18
  52. package/src/cli/commands/plugin-api.ts +9 -6
  53. package/src/cli/commands/search.test.ts +16 -7
  54. package/src/cli/commands/search.ts +124 -87
  55. package/src/cli/commands/serve.test.ts +67 -25
  56. package/src/cli/commands/serve.ts +18 -3
  57. package/src/cli/commands/setup.test.ts +176 -101
  58. package/src/cli/commands/setup.ts +140 -117
  59. package/src/cli/commands/store.test.ts +82 -53
  60. package/src/cli/commands/store.ts +56 -37
  61. package/src/cli/program.ts +2 -2
  62. package/src/crawl/article-converter.test.ts +4 -1
  63. package/src/crawl/article-converter.ts +46 -31
  64. package/src/crawl/bridge.test.ts +240 -132
  65. package/src/crawl/bridge.ts +87 -30
  66. package/src/crawl/claude-client.test.ts +124 -56
  67. package/src/crawl/claude-client.ts +7 -15
  68. package/src/crawl/intelligent-crawler.test.ts +65 -22
  69. package/src/crawl/intelligent-crawler.ts +86 -53
  70. package/src/crawl/markdown-utils.ts +1 -4
  71. package/src/db/embeddings.ts +4 -6
  72. package/src/db/lance.test.ts +4 -4
  73. package/src/db/lance.ts +16 -12
  74. package/src/index.ts +26 -17
  75. package/src/logging/index.ts +1 -5
  76. package/src/logging/logger.ts +3 -5
  77. package/src/logging/payload.test.ts +1 -1
  78. package/src/logging/payload.ts +3 -5
  79. package/src/mcp/commands/index.ts +2 -2
  80. package/src/mcp/commands/job.commands.ts +12 -18
  81. package/src/mcp/commands/meta.commands.ts +13 -13
  82. package/src/mcp/commands/registry.ts +5 -8
  83. package/src/mcp/commands/store.commands.ts +19 -19
  84. package/src/mcp/handlers/execute.handler.test.ts +10 -10
  85. package/src/mcp/handlers/execute.handler.ts +4 -5
  86. package/src/mcp/handlers/index.ts +10 -14
  87. package/src/mcp/handlers/job.handler.test.ts +10 -10
  88. package/src/mcp/handlers/job.handler.ts +22 -25
  89. package/src/mcp/handlers/search.handler.test.ts +36 -65
  90. package/src/mcp/handlers/search.handler.ts +135 -104
  91. package/src/mcp/handlers/store.handler.test.ts +41 -52
  92. package/src/mcp/handlers/store.handler.ts +108 -88
  93. package/src/mcp/schemas/index.test.ts +73 -68
  94. package/src/mcp/schemas/index.ts +18 -12
  95. package/src/mcp/server.test.ts +1 -1
  96. package/src/mcp/server.ts +59 -46
  97. package/src/plugin/commands.test.ts +230 -95
  98. package/src/plugin/commands.ts +24 -25
  99. package/src/plugin/dependency-analyzer.test.ts +52 -52
  100. package/src/plugin/dependency-analyzer.ts +85 -22
  101. package/src/plugin/git-clone.test.ts +24 -13
  102. package/src/plugin/git-clone.ts +3 -7
  103. package/src/server/app.test.ts +109 -109
  104. package/src/server/app.ts +32 -23
  105. package/src/server/index.test.ts +64 -66
  106. package/src/services/chunking.service.test.ts +32 -32
  107. package/src/services/chunking.service.ts +16 -9
  108. package/src/services/code-graph.service.test.ts +30 -36
  109. package/src/services/code-graph.service.ts +24 -10
  110. package/src/services/code-unit.service.test.ts +55 -11
  111. package/src/services/code-unit.service.ts +85 -11
  112. package/src/services/config.service.test.ts +37 -18
  113. package/src/services/config.service.ts +30 -7
  114. package/src/services/index.service.test.ts +49 -18
  115. package/src/services/index.service.ts +98 -48
  116. package/src/services/index.ts +6 -9
  117. package/src/services/job.service.test.ts +22 -22
  118. package/src/services/job.service.ts +18 -18
  119. package/src/services/project-root.service.test.ts +1 -3
  120. package/src/services/search.service.test.ts +248 -120
  121. package/src/services/search.service.ts +286 -156
  122. package/src/services/services.test.ts +1 -1
  123. package/src/services/snippet.service.test.ts +14 -6
  124. package/src/services/snippet.service.ts +7 -5
  125. package/src/services/store.service.test.ts +68 -29
  126. package/src/services/store.service.ts +41 -12
  127. package/src/services/watch.service.test.ts +34 -14
  128. package/src/services/watch.service.ts +11 -1
  129. package/src/types/brands.test.ts +3 -1
  130. package/src/types/index.ts +2 -13
  131. package/src/types/search.ts +10 -8
  132. package/src/utils/type-guards.test.ts +20 -15
  133. package/src/utils/type-guards.ts +1 -1
  134. package/src/workers/background-worker-cli.ts +2 -2
  135. package/src/workers/background-worker.test.ts +54 -40
  136. package/src/workers/background-worker.ts +76 -60
  137. package/src/workers/spawn-worker.test.ts +22 -10
  138. package/src/workers/spawn-worker.ts +6 -6
  139. package/tests/analysis/ast-parser.test.ts +3 -3
  140. package/tests/analysis/code-graph.test.ts +5 -5
  141. package/tests/fixtures/code-snippets/api/error-handling.ts +4 -15
  142. package/tests/fixtures/code-snippets/api/rest-controller.ts +3 -9
  143. package/tests/fixtures/code-snippets/auth/jwt-auth.ts +5 -21
  144. package/tests/fixtures/code-snippets/auth/oauth-flow.ts +4 -4
  145. package/tests/fixtures/code-snippets/database/repository-pattern.ts +11 -3
  146. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/aws-lambda/handler.ts +2 -2
  147. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-pages/handler.ts +1 -1
  148. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/serve-static.ts +2 -2
  149. package/tests/fixtures/corpus/oss-repos/hono/src/client/client.ts +2 -2
  150. package/tests/fixtures/corpus/oss-repos/hono/src/client/types.ts +22 -20
  151. package/tests/fixtures/corpus/oss-repos/hono/src/context.ts +13 -10
  152. package/tests/fixtures/corpus/oss-repos/hono/src/helper/accepts/accepts.ts +10 -7
  153. package/tests/fixtures/corpus/oss-repos/hono/src/helper/adapter/index.ts +2 -2
  154. package/tests/fixtures/corpus/oss-repos/hono/src/helper/css/index.ts +1 -1
  155. package/tests/fixtures/corpus/oss-repos/hono/src/helper/factory/index.ts +16 -16
  156. package/tests/fixtures/corpus/oss-repos/hono/src/helper/ssg/ssg.ts +2 -2
  157. package/tests/fixtures/corpus/oss-repos/hono/src/hono-base.ts +3 -3
  158. package/tests/fixtures/corpus/oss-repos/hono/src/hono.ts +1 -1
  159. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/css.ts +2 -2
  160. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/intrinsic-element/components.ts +1 -1
  161. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/render.ts +7 -7
  162. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/hooks/index.ts +3 -3
  163. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/intrinsic-element/components.ts +1 -1
  164. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/utils.ts +6 -6
  165. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/jsx-renderer/index.ts +3 -3
  166. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/serve-static/index.ts +1 -1
  167. package/tests/fixtures/corpus/oss-repos/hono/src/preset/quick.ts +1 -1
  168. package/tests/fixtures/corpus/oss-repos/hono/src/preset/tiny.ts +1 -1
  169. package/tests/fixtures/corpus/oss-repos/hono/src/router/pattern-router/router.ts +2 -2
  170. package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/node.ts +4 -4
  171. package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/router.ts +1 -1
  172. package/tests/fixtures/corpus/oss-repos/hono/src/router/trie-router/node.ts +1 -1
  173. package/tests/fixtures/corpus/oss-repos/hono/src/types.ts +166 -169
  174. package/tests/fixtures/corpus/oss-repos/hono/src/utils/body.ts +8 -8
  175. package/tests/fixtures/corpus/oss-repos/hono/src/utils/color.ts +3 -3
  176. package/tests/fixtures/corpus/oss-repos/hono/src/utils/cookie.ts +2 -2
  177. package/tests/fixtures/corpus/oss-repos/hono/src/utils/encode.ts +2 -2
  178. package/tests/fixtures/corpus/oss-repos/hono/src/utils/types.ts +30 -33
  179. package/tests/fixtures/corpus/oss-repos/hono/src/validator/validator.ts +2 -2
  180. package/tests/fixtures/test-server.ts +3 -2
  181. package/tests/helpers/performance-metrics.ts +8 -25
  182. package/tests/helpers/search-relevance.ts +14 -69
  183. package/tests/integration/cli-consistency.test.ts +5 -4
  184. package/tests/integration/python-bridge.test.ts +13 -3
  185. package/tests/mcp/server.test.ts +1 -1
  186. package/tests/services/code-unit.service.test.ts +48 -0
  187. package/tests/services/job.service.test.ts +124 -0
  188. package/tests/services/search.progressive-context.test.ts +2 -2
  189. package/.claude-plugin/plugin.json +0 -13
  190. package/dist/chunk-6PBP5DVD.js.map +0 -1
  191. package/dist/chunk-L2YVNC63.js.map +0 -1
  192. package/dist/chunk-RST4XGRL.js.map +0 -1
  193. package/dist/chunk-WT2DAEO7.js.map +0 -1
  194. package/dist/watch.service-YAIKKDCF.js +0 -7
  195. package/skills/atomic-commits/SKILL.md +0 -77
  196. /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
- ? string | File | BodyDataValueDot
17
- : string | File | (string | File)[] extends T[K]
18
- ? string | File | (string | File)[]
19
- : string | File
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
- ? (string | File)[] // use all option
29
- : never) // without options
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
- ? BodyDataValueObject<T> // use dot option
37
- : never) // without options
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
- ? // eslint-disable-next-line no-unsafe-optional-chaining
22
- 'NO_COLOR' in process?.env
23
- : false
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
- ? CookieOptions & HostCookieConstraint
35
- : CookieOptions
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> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
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> = T extends Record<infer K, unknown>
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
- : JSONParsed<J>
45
+ ? {}
46
+ : JSONParsed<J>
51
47
  : T extends JSONPrimitive
52
- ? T
53
- : T extends InvalidJSONValue
54
- ? never
55
- : T extends []
56
- ? []
57
- : T extends readonly [infer R, ...infer U]
58
- ? [JSONParsed<InvalidToNull<R>>, ...JSONParsed<U>]
59
- : T extends Array<infer U>
60
- ? Array<JSONParsed<InvalidToNull<U>>>
61
- : T extends ReadonlyArray<infer U>
62
- ? ReadonlyArray<JSONParsed<InvalidToNull<U>>>
63
- : T extends Set<unknown> | Map<unknown, unknown>
64
- ? {}
65
- : T extends object
66
- ? {
67
- [K in keyof OmitSymbolKeys<T> as IsInvalid<T[K]> extends true
68
- ? never
69
- : K]: boolean extends IsInvalid<T[K]> ? JSONParsed<T[K]> | undefined : JSONParsed<T[K]>
70
- }
71
- : never
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> = RequiredKeysOf<BaseType> extends never
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({ length: 20 }, (_, i) =>
175
- `<a href="/link${String(i)}">Link ${String(i)}</a>`
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(/^(function|class|interface|type|const|documentation):\s+(.+)$/);
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
- 'auth',
335
- 'login',
336
- 'jwt',
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 = (args: string, options: { expectError?: boolean; stdin?: string } = {}): {
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;
@@ -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 () => {
@@ -5,7 +5,7 @@ describe('MCP Server', () => {
5
5
  it('should create server with search tool', () => {
6
6
  const server = createMCPServer({
7
7
  dataDir: '/tmp/test',
8
- config: undefined
8
+ config: undefined,
9
9
  });
10
10
 
11
11
  expect(server).toBeDefined();
@@ -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
+ });
@@ -19,8 +19,8 @@ describe('Progressive Context', () => {
19
19
  signature: 'validateToken(token: string): boolean',
20
20
  purpose: 'Validates JWT token',
21
21
  location: 'src/auth.ts:45',
22
- relevanceReason: 'Matches query about token validation'
23
- }
22
+ relevanceReason: 'Matches query about token validation',
23
+ },
24
24
  };
25
25
 
26
26
  expect(result.summary).toBeDefined();