@dolusoft/hirebase-mcp 1.3.4 → 1.4.0

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 (238) hide show
  1. package/README.md +28 -2
  2. package/dist/__tests__/integration/hybrid-search.test.d.ts +1 -0
  3. package/dist/__tests__/integration/hybrid-search.test.js +70 -0
  4. package/dist/__tests__/integration/hybrid-search.test.js.map +1 -0
  5. package/dist/__tests__/unit/chunking.test.d.ts +1 -0
  6. package/dist/__tests__/unit/chunking.test.js +70 -0
  7. package/dist/__tests__/unit/chunking.test.js.map +1 -0
  8. package/dist/__tests__/unit/composite-score.test.d.ts +1 -0
  9. package/dist/__tests__/unit/composite-score.test.js +28 -0
  10. package/dist/__tests__/unit/composite-score.test.js.map +1 -0
  11. package/dist/__tests__/unit/find-similar-candidates.test.d.ts +1 -0
  12. package/dist/__tests__/unit/find-similar-candidates.test.js +33 -0
  13. package/dist/__tests__/unit/find-similar-candidates.test.js.map +1 -0
  14. package/dist/__tests__/unit/query-enhancer.test.d.ts +1 -0
  15. package/dist/__tests__/unit/query-enhancer.test.js +20 -0
  16. package/dist/__tests__/unit/query-enhancer.test.js.map +1 -0
  17. package/dist/__tests__/unit/skill-graph.test.d.ts +1 -0
  18. package/dist/__tests__/unit/skill-graph.test.js +38 -0
  19. package/dist/__tests__/unit/skill-graph.test.js.map +1 -0
  20. package/dist/__tests__/unit/skill-normalizer.test.d.ts +1 -0
  21. package/dist/__tests__/unit/skill-normalizer.test.js +41 -0
  22. package/dist/__tests__/unit/skill-normalizer.test.js.map +1 -0
  23. package/dist/__tests__/unit/smoke.test.d.ts +1 -0
  24. package/dist/__tests__/unit/smoke.test.js +7 -0
  25. package/dist/__tests__/unit/smoke.test.js.map +1 -0
  26. package/dist/application/dto/candidate-summary.d.ts +2 -0
  27. package/dist/application/dto/filter-input.d.ts +2 -0
  28. package/dist/application/dto/search-results.d.ts +3 -0
  29. package/dist/application/dto/stats.d.ts +6 -0
  30. package/dist/application/services/query-enhancer.d.ts +10 -0
  31. package/dist/application/services/query-enhancer.js +34 -0
  32. package/dist/application/services/query-enhancer.js.map +1 -0
  33. package/dist/application/use-cases/add-cv.d.ts +5 -1
  34. package/dist/application/use-cases/add-cv.js +25 -4
  35. package/dist/application/use-cases/add-cv.js.map +1 -1
  36. package/dist/application/use-cases/delete-cv.d.ts +5 -1
  37. package/dist/application/use-cases/delete-cv.js +13 -1
  38. package/dist/application/use-cases/delete-cv.js.map +1 -1
  39. package/dist/application/use-cases/delete-job-posting.d.ts +10 -0
  40. package/dist/application/use-cases/delete-job-posting.js +23 -0
  41. package/dist/application/use-cases/delete-job-posting.js.map +1 -0
  42. package/dist/application/use-cases/filter-candidates.js +12 -1
  43. package/dist/application/use-cases/filter-candidates.js.map +1 -1
  44. package/dist/application/use-cases/find-similar-candidates.d.ts +21 -0
  45. package/dist/application/use-cases/find-similar-candidates.js +85 -0
  46. package/dist/application/use-cases/find-similar-candidates.js.map +1 -0
  47. package/dist/application/use-cases/get-stats.js +2 -0
  48. package/dist/application/use-cases/get-stats.js.map +1 -1
  49. package/dist/application/use-cases/list-cvs.d.ts +2 -1
  50. package/dist/application/use-cases/list-cvs.js +18 -2
  51. package/dist/application/use-cases/list-cvs.js.map +1 -1
  52. package/dist/application/use-cases/match-candidates.d.ts +5 -1
  53. package/dist/application/use-cases/match-candidates.js +61 -19
  54. package/dist/application/use-cases/match-candidates.js.map +1 -1
  55. package/dist/application/use-cases/reindex-all.d.ts +22 -0
  56. package/dist/application/use-cases/reindex-all.js +124 -0
  57. package/dist/application/use-cases/reindex-all.js.map +1 -0
  58. package/dist/application/use-cases/semantic-search.d.ts +3 -1
  59. package/dist/application/use-cases/semantic-search.js +13 -3
  60. package/dist/application/use-cases/semantic-search.js.map +1 -1
  61. package/dist/application/use-cases/update-cv.d.ts +2 -1
  62. package/dist/application/use-cases/update-cv.js +18 -3
  63. package/dist/application/use-cases/update-cv.js.map +1 -1
  64. package/dist/domain/entities/candidate.d.ts +3 -0
  65. package/dist/domain/repositories/candidate-repository.d.ts +1 -0
  66. package/dist/domain/repositories/chunk-repository.d.ts +8 -0
  67. package/dist/infrastructure/config/app-config.d.ts +5 -0
  68. package/dist/infrastructure/config/app-config.js +4 -0
  69. package/dist/infrastructure/config/app-config.js.map +1 -1
  70. package/dist/infrastructure/persistence/database.d.ts +1 -0
  71. package/dist/infrastructure/persistence/database.js +35 -1
  72. package/dist/infrastructure/persistence/database.js.map +1 -1
  73. package/dist/infrastructure/persistence/lancedb-candidate-repository.d.ts +1 -0
  74. package/dist/infrastructure/persistence/lancedb-candidate-repository.js +20 -0
  75. package/dist/infrastructure/persistence/lancedb-candidate-repository.js.map +1 -1
  76. package/dist/infrastructure/persistence/lancedb-chunk-repository.d.ts +4 -1
  77. package/dist/infrastructure/persistence/lancedb-chunk-repository.js +64 -2
  78. package/dist/infrastructure/persistence/lancedb-chunk-repository.js.map +1 -1
  79. package/dist/interface/cli/index.js +19 -2
  80. package/dist/interface/cli/index.js.map +1 -1
  81. package/dist/interface/dashboard/public/200.html +1 -1
  82. package/dist/interface/dashboard/public/404.html +1 -1
  83. package/dist/interface/dashboard/public/_nuxt/builds/latest.json +1 -1
  84. package/dist/interface/dashboard/public/_nuxt/builds/meta/05a92851-5bed-4eaf-bd75-e0aff9ec5876.json +1 -0
  85. package/dist/interface/dashboard/public/_nuxt/builds/meta/1d36013b-8fb8-4cea-a448-d5f224b731ee.json +1 -0
  86. package/dist/interface/dashboard/public/_nuxt/builds/meta/2bb695c5-c739-46a0-a3e6-4b76aa321c29.json +1 -0
  87. package/dist/interface/dashboard/public/_nuxt/builds/meta/3ee0a1ee-8e79-4443-a6e7-b0b6412b0a38.json +1 -0
  88. package/dist/interface/dashboard/public/_nuxt/builds/meta/a4cd5ce8-79f3-42cd-99a6-ddc9cd1696d0.json +1 -0
  89. package/dist/interface/dashboard/public/_nuxt/builds/meta/ba31dbc0-8fe2-44bc-8214-6b3d717566a0.json +1 -0
  90. package/dist/interface/dashboard/public/_nuxt/builds/meta/c24c5825-f907-4b91-805a-a6f061aae9ee.json +1 -0
  91. package/dist/interface/dashboard/public/_nuxt/builds/meta/c344ef44-e491-4e51-ae81-c21dfe2317bb.json +1 -0
  92. package/dist/interface/dashboard/public/_nuxt/builds/meta/fc914bdb-64a6-4798-a08f-64eb8ea9a307.json +1 -0
  93. package/dist/interface/dashboard/public/index.html +1 -1
  94. package/dist/interface/mcp/server.js +119 -42
  95. package/dist/interface/mcp/server.js.map +1 -1
  96. package/dist/interface/mcp/tools/add-cv.d.ts +2 -2
  97. package/dist/interface/mcp/tools/add-cv.js +31 -3
  98. package/dist/interface/mcp/tools/add-cv.js.map +1 -1
  99. package/dist/interface/mcp/tools/add-job-posting.d.ts +2 -2
  100. package/dist/interface/mcp/tools/add-job-posting.js +20 -12
  101. package/dist/interface/mcp/tools/add-job-posting.js.map +1 -1
  102. package/dist/interface/mcp/tools/add-pipeline-note.d.ts +2 -2
  103. package/dist/interface/mcp/tools/add-pipeline-note.js +10 -6
  104. package/dist/interface/mcp/tools/add-pipeline-note.js.map +1 -1
  105. package/dist/interface/mcp/tools/add-reference-cv.d.ts +2 -2
  106. package/dist/interface/mcp/tools/add-reference-cv.js +9 -5
  107. package/dist/interface/mcp/tools/add-reference-cv.js.map +1 -1
  108. package/dist/interface/mcp/tools/add-to-pipeline.d.ts +2 -2
  109. package/dist/interface/mcp/tools/add-to-pipeline.js +9 -5
  110. package/dist/interface/mcp/tools/add-to-pipeline.js.map +1 -1
  111. package/dist/interface/mcp/tools/check-duplicate-candidate.d.ts +2 -2
  112. package/dist/interface/mcp/tools/check-duplicate-candidate.js +8 -4
  113. package/dist/interface/mcp/tools/check-duplicate-candidate.js.map +1 -1
  114. package/dist/interface/mcp/tools/compare-candidates.d.ts +2 -2
  115. package/dist/interface/mcp/tools/compare-candidates.js +8 -4
  116. package/dist/interface/mcp/tools/compare-candidates.js.map +1 -1
  117. package/dist/interface/mcp/tools/create-call-batch.d.ts +2 -2
  118. package/dist/interface/mcp/tools/create-call-batch.js +10 -6
  119. package/dist/interface/mcp/tools/create-call-batch.js.map +1 -1
  120. package/dist/interface/mcp/tools/delete-cv.d.ts +2 -2
  121. package/dist/interface/mcp/tools/delete-cv.js +29 -2
  122. package/dist/interface/mcp/tools/delete-cv.js.map +1 -1
  123. package/dist/interface/mcp/tools/delete-job-posting.d.ts +2 -2
  124. package/dist/interface/mcp/tools/delete-job-posting.js +32 -3
  125. package/dist/interface/mcp/tools/delete-job-posting.js.map +1 -1
  126. package/dist/interface/mcp/tools/export-results.d.ts +2 -2
  127. package/dist/interface/mcp/tools/export-results.js +7 -3
  128. package/dist/interface/mcp/tools/export-results.js.map +1 -1
  129. package/dist/interface/mcp/tools/filter-candidates.d.ts +2 -2
  130. package/dist/interface/mcp/tools/filter-candidates.js +17 -9
  131. package/dist/interface/mcp/tools/filter-candidates.js.map +1 -1
  132. package/dist/interface/mcp/tools/find-similar-candidates.d.ts +3 -0
  133. package/dist/interface/mcp/tools/find-similar-candidates.js +39 -0
  134. package/dist/interface/mcp/tools/find-similar-candidates.js.map +1 -0
  135. package/dist/interface/mcp/tools/generate-screening-report.d.ts +2 -2
  136. package/dist/interface/mcp/tools/generate-screening-report.js +8 -4
  137. package/dist/interface/mcp/tools/generate-screening-report.js.map +1 -1
  138. package/dist/interface/mcp/tools/get-call-batch-status.d.ts +2 -2
  139. package/dist/interface/mcp/tools/get-call-batch-status.js +7 -3
  140. package/dist/interface/mcp/tools/get-call-batch-status.js.map +1 -1
  141. package/dist/interface/mcp/tools/get-candidate-history.d.ts +2 -2
  142. package/dist/interface/mcp/tools/get-candidate-history.js +7 -3
  143. package/dist/interface/mcp/tools/get-candidate-history.js.map +1 -1
  144. package/dist/interface/mcp/tools/get-cv-chunks.d.ts +2 -2
  145. package/dist/interface/mcp/tools/get-cv-chunks.js +13 -9
  146. package/dist/interface/mcp/tools/get-cv-chunks.js.map +1 -1
  147. package/dist/interface/mcp/tools/get-cv-detail.d.ts +2 -2
  148. package/dist/interface/mcp/tools/get-cv-detail.js +6 -2
  149. package/dist/interface/mcp/tools/get-cv-detail.js.map +1 -1
  150. package/dist/interface/mcp/tools/get-cv-versions.d.ts +2 -2
  151. package/dist/interface/mcp/tools/get-cv-versions.js +6 -2
  152. package/dist/interface/mcp/tools/get-cv-versions.js.map +1 -1
  153. package/dist/interface/mcp/tools/get-job-posting.d.ts +2 -2
  154. package/dist/interface/mcp/tools/get-job-posting.js +7 -3
  155. package/dist/interface/mcp/tools/get-job-posting.js.map +1 -1
  156. package/dist/interface/mcp/tools/get-pending-actions.d.ts +2 -2
  157. package/dist/interface/mcp/tools/get-pending-actions.js +6 -2
  158. package/dist/interface/mcp/tools/get-pending-actions.js.map +1 -1
  159. package/dist/interface/mcp/tools/get-pipeline-candidates.d.ts +2 -2
  160. package/dist/interface/mcp/tools/get-pipeline-candidates.js +7 -3
  161. package/dist/interface/mcp/tools/get-pipeline-candidates.js.map +1 -1
  162. package/dist/interface/mcp/tools/get-pipeline.d.ts +2 -2
  163. package/dist/interface/mcp/tools/get-pipeline.js +7 -3
  164. package/dist/interface/mcp/tools/get-pipeline.js.map +1 -1
  165. package/dist/interface/mcp/tools/get-screening-history.d.ts +2 -2
  166. package/dist/interface/mcp/tools/get-screening-history.js +7 -3
  167. package/dist/interface/mcp/tools/get-screening-history.js.map +1 -1
  168. package/dist/interface/mcp/tools/get-server-status.d.ts +2 -2
  169. package/dist/interface/mcp/tools/get-server-status.js +6 -2
  170. package/dist/interface/mcp/tools/get-server-status.js.map +1 -1
  171. package/dist/interface/mcp/tools/get-stats.d.ts +2 -2
  172. package/dist/interface/mcp/tools/get-stats.js +6 -2
  173. package/dist/interface/mcp/tools/get-stats.js.map +1 -1
  174. package/dist/interface/mcp/tools/list-cvs.d.ts +2 -2
  175. package/dist/interface/mcp/tools/list-cvs.js +32 -11
  176. package/dist/interface/mcp/tools/list-cvs.js.map +1 -1
  177. package/dist/interface/mcp/tools/list-job-postings.d.ts +2 -2
  178. package/dist/interface/mcp/tools/list-job-postings.js +6 -2
  179. package/dist/interface/mcp/tools/list-job-postings.js.map +1 -1
  180. package/dist/interface/mcp/tools/list-reference-cvs.d.ts +2 -2
  181. package/dist/interface/mcp/tools/list-reference-cvs.js +7 -3
  182. package/dist/interface/mcp/tools/list-reference-cvs.js.map +1 -1
  183. package/dist/interface/mcp/tools/manage-tags.d.ts +2 -2
  184. package/dist/interface/mcp/tools/manage-tags.js +8 -4
  185. package/dist/interface/mcp/tools/manage-tags.js.map +1 -1
  186. package/dist/interface/mcp/tools/match-candidates.d.ts +2 -2
  187. package/dist/interface/mcp/tools/match-candidates.js +26 -12
  188. package/dist/interface/mcp/tools/match-candidates.js.map +1 -1
  189. package/dist/interface/mcp/tools/reindex-all.d.ts +3 -0
  190. package/dist/interface/mcp/tools/reindex-all.js +31 -0
  191. package/dist/interface/mcp/tools/reindex-all.js.map +1 -0
  192. package/dist/interface/mcp/tools/remove-reference-cv.d.ts +2 -2
  193. package/dist/interface/mcp/tools/remove-reference-cv.js +29 -2
  194. package/dist/interface/mcp/tools/remove-reference-cv.js.map +1 -1
  195. package/dist/interface/mcp/tools/reset-database.d.ts +2 -2
  196. package/dist/interface/mcp/tools/reset-database.js +29 -2
  197. package/dist/interface/mcp/tools/reset-database.js.map +1 -1
  198. package/dist/interface/mcp/tools/semantic-search.d.ts +2 -2
  199. package/dist/interface/mcp/tools/semantic-search.js +24 -11
  200. package/dist/interface/mcp/tools/semantic-search.js.map +1 -1
  201. package/dist/interface/mcp/tools/set-pending-action.d.ts +2 -2
  202. package/dist/interface/mcp/tools/set-pending-action.js +16 -12
  203. package/dist/interface/mcp/tools/set-pending-action.js.map +1 -1
  204. package/dist/interface/mcp/tools/update-call-outcome.d.ts +2 -2
  205. package/dist/interface/mcp/tools/update-call-outcome.js +10 -6
  206. package/dist/interface/mcp/tools/update-call-outcome.js.map +1 -1
  207. package/dist/interface/mcp/tools/update-cv.d.ts +2 -2
  208. package/dist/interface/mcp/tools/update-cv.js +16 -6
  209. package/dist/interface/mcp/tools/update-cv.js.map +1 -1
  210. package/dist/interface/mcp/tools/update-job-posting.d.ts +2 -2
  211. package/dist/interface/mcp/tools/update-job-posting.js +16 -12
  212. package/dist/interface/mcp/tools/update-job-posting.js.map +1 -1
  213. package/dist/interface/mcp/tools/update-pipeline-status.d.ts +2 -2
  214. package/dist/interface/mcp/tools/update-pipeline-status.js +9 -5
  215. package/dist/interface/mcp/tools/update-pipeline-status.js.map +1 -1
  216. package/dist/interface/mcp/types.d.ts +2 -0
  217. package/dist/shared/data/skill-graph.json +115 -0
  218. package/dist/shared/data/skill-map.json +104 -0
  219. package/dist/shared/mcp/logger.d.ts +7 -0
  220. package/dist/shared/mcp/logger.js +20 -0
  221. package/dist/shared/mcp/logger.js.map +1 -0
  222. package/dist/shared/mcp/progress.d.ts +6 -0
  223. package/dist/shared/mcp/progress.js +33 -0
  224. package/dist/shared/mcp/progress.js.map +1 -0
  225. package/dist/shared/services/skill-graph.d.ts +11 -0
  226. package/dist/shared/services/skill-graph.js +84 -0
  227. package/dist/shared/services/skill-graph.js.map +1 -0
  228. package/dist/shared/services/skill-normalizer.d.ts +6 -0
  229. package/dist/shared/services/skill-normalizer.js +32 -0
  230. package/dist/shared/services/skill-normalizer.js.map +1 -0
  231. package/dist/shared/types/index.d.ts +3 -1
  232. package/dist/shared/utils/chunking.d.ts +9 -1
  233. package/dist/shared/utils/chunking.js +46 -36
  234. package/dist/shared/utils/chunking.js.map +1 -1
  235. package/dist/shared/utils/skill-migration.d.ts +6 -0
  236. package/dist/shared/utils/skill-migration.js +35 -0
  237. package/dist/shared/utils/skill-migration.js.map +1 -0
  238. package/package.json +12 -6
package/README.md CHANGED
@@ -31,7 +31,7 @@ src/
31
31
  | mammoth | DOCX text extraction |
32
32
  | Tesseract.js | OCR for image-based PDFs |
33
33
 
34
- ## MCP Tools (27)
34
+ ## MCP Tools (38)
35
35
 
36
36
  ### CV Management
37
37
 
@@ -45,6 +45,7 @@ src/
45
45
  | `get_cv_versions` | Version history |
46
46
  | `list_cvs` | Paginated candidate list with sorting |
47
47
  | `manage_tags` | Add/remove/list tags |
48
+ | `check_duplicate_candidate` | Check if a candidate already exists by name or email before importing |
48
49
 
49
50
  ### Search & Matching
50
51
 
@@ -53,6 +54,7 @@ src/
53
54
  | `semantic_search` | Vector similarity search with optional section filter |
54
55
  | `filter_candidates` | Structured filtering (skills, location, experience, languages, tags) |
55
56
  | `match_candidates` | Composite scoring: semantic similarity + skill overlap + loyalty factor |
57
+ | `compare_candidates` | Compare 2–5 candidates side by side, optionally with job skill match scores |
56
58
  | `export_results` | Export as JSON or CSV |
57
59
 
58
60
  ### Job Posting Management
@@ -65,6 +67,14 @@ src/
65
67
  | `update_job_posting` | Update existing job posting |
66
68
  | `delete_job_posting` | Delete a job posting |
67
69
 
70
+ ### Reference CVs
71
+
72
+ | Tool | Description |
73
+ |---|---|
74
+ | `add_reference_cv` | Mark a candidate as a reference/benchmark CV for a job posting |
75
+ | `list_reference_cvs` | List all reference CVs for a job posting |
76
+ | `remove_reference_cv` | Remove a reference CV from a job posting |
77
+
68
78
  ### Recruitment Pipeline
69
79
 
70
80
  | Tool | Description |
@@ -74,17 +84,33 @@ src/
74
84
  | `add_pipeline_note` | Add notes, call attempts, or interview records |
75
85
  | `set_pending_action` | Track pending actions with due dates and owner (us/candidate) |
76
86
  | `get_pipeline` | Full pipeline view for a job posting |
87
+ | `get_pipeline_candidates` | Lightweight list of candidates in a pipeline (ID, name, status only) |
77
88
  | `get_candidate_history` | Candidate's recruitment history across all postings |
78
89
  | `get_pending_actions` | All overdue and upcoming pending actions |
79
90
 
80
91
  **Pipeline statuses:** `new` → `contacted` → `unreachable` · `not_interested` · `interview_scheduled` → `no_show` · `interviewed` → `rejected` · `offer_sent` → `hired`
81
92
 
93
+ ### Screening
94
+
95
+ | Tool | Description |
96
+ |---|---|
97
+ | `get_screening_history` | Shortlisted, screened out, and in-pipeline candidates for a job posting |
98
+ | `generate_screening_report` | Summary report with statistics, shortlisted, screened out, and pending candidates |
99
+
100
+ ### Call Batches
101
+
102
+ | Tool | Description |
103
+ |---|---|
104
+ | `create_call_batch` | Create a call batch: assign candidates to a person for calling |
105
+ | `get_call_batch_status` | Status of a call batch: candidates, phone numbers, and call outcomes |
106
+ | `update_call_outcome` | Update call outcome: `pending`, `reached`, or `unreachable` |
107
+
82
108
  ### System
83
109
 
84
110
  | Tool | Description |
85
111
  |---|---|
86
112
  | `get_stats` | Database statistics (candidates, skills, locations, etc.) |
87
- | `get_server_status` | Server info, dashboard URL, connected clients |
113
+ | `get_server_status` | Server version, dashboard URL, connected clients, config |
88
114
  | `reset_database` | Drop and recreate all tables |
89
115
 
90
116
  ## How It Works
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import * as lancedb from '@lancedb/lancedb';
3
+ import { LanceDbChunkRepository } from '../../infrastructure/persistence/lancedb-chunk-repository.js';
4
+ import { getOrCreateTable, getChunkSeedRow, TABLE_NAMES } from '../../infrastructure/persistence/database.js';
5
+ import { nanoid } from 'nanoid';
6
+ const TEST_DB_PATH = './data/test-lancedb-' + nanoid(6);
7
+ describe('hybridSearch', () => {
8
+ let db;
9
+ let repo;
10
+ const mockChunks = [
11
+ {
12
+ id: 'chunk-1',
13
+ candidateId: 'cand-1',
14
+ sectionType: 'experience',
15
+ content: 'Senior React Developer at Acme Corp with 5 years TypeScript experience',
16
+ vector: new Array(1536).fill(0).map(() => Math.random()),
17
+ metadata: {},
18
+ createdAt: new Date().toISOString(),
19
+ },
20
+ {
21
+ id: 'chunk-2',
22
+ candidateId: 'cand-2',
23
+ sectionType: 'experience',
24
+ content: 'Java Backend Engineer at BigBank with Spring Boot and Kubernetes',
25
+ vector: new Array(1536).fill(0).map(() => Math.random()),
26
+ metadata: {},
27
+ createdAt: new Date().toISOString(),
28
+ },
29
+ {
30
+ id: 'chunk-3',
31
+ candidateId: 'cand-3',
32
+ sectionType: 'skills',
33
+ content: 'React, TypeScript, Node.js, Next.js, GraphQL, Docker',
34
+ vector: new Array(1536).fill(0).map(() => Math.random()),
35
+ metadata: {},
36
+ createdAt: new Date().toISOString(),
37
+ },
38
+ ];
39
+ beforeAll(async () => {
40
+ db = await lancedb.connect(TEST_DB_PATH);
41
+ await getOrCreateTable(db, TABLE_NAMES.chunks, getChunkSeedRow());
42
+ repo = new LanceDbChunkRepository(db);
43
+ await repo.saveMany(mockChunks);
44
+ await repo.optimizeIndex();
45
+ });
46
+ afterAll(async () => {
47
+ const { rm } = await import('node:fs/promises');
48
+ await rm(TEST_DB_PATH, { recursive: true, force: true });
49
+ });
50
+ it('returns results for FTS text query', async () => {
51
+ const dummyVector = new Array(1536).fill(0);
52
+ const results = await repo.hybridSearch(dummyVector, 'React TypeScript', 10);
53
+ expect(results.length).toBeGreaterThan(0);
54
+ const contents = results.map(r => r.chunk.content);
55
+ expect(contents.some(c => c.includes('React'))).toBe(true);
56
+ });
57
+ it('falls back to pure vector search when textQuery is empty', async () => {
58
+ const dummyVector = mockChunks[0].vector;
59
+ const results = await repo.hybridSearch(dummyVector, '', 10);
60
+ expect(results.length).toBeGreaterThan(0);
61
+ });
62
+ it('filters with WHERE clause', async () => {
63
+ const dummyVector = new Array(1536).fill(0);
64
+ const results = await repo.hybridSearch(dummyVector, 'React', 10, "section_type = 'skills'");
65
+ for (const r of results) {
66
+ expect(r.chunk.sectionType).toBe('skills');
67
+ }
68
+ });
69
+ });
70
+ //# sourceMappingURL=hybrid-search.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hybrid-search.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/hybrid-search.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createChunks, buildContextHeader, splitByTokens } from '../../shared/utils/chunking.js';
3
+ describe('buildContextHeader', () => {
4
+ it('builds header from candidate info', () => {
5
+ const header = buildContextHeader({
6
+ name: 'Ali Yılmaz',
7
+ experienceYears: 8,
8
+ location: 'Istanbul',
9
+ topSkills: ['React', 'TypeScript', 'Node.js'],
10
+ });
11
+ expect(header).toContain('Ali Yılmaz');
12
+ expect(header).toContain('8');
13
+ expect(header).toContain('Istanbul');
14
+ expect(header).toContain('React');
15
+ });
16
+ });
17
+ describe('splitByTokens', () => {
18
+ it('does not split short text', () => {
19
+ const parts = splitByTokens('Hello world', 512, 64);
20
+ expect(parts).toHaveLength(1);
21
+ expect(parts[0]).toBe('Hello world');
22
+ });
23
+ it('splits long text with overlap', () => {
24
+ const longText = 'word '.repeat(600);
25
+ const parts = splitByTokens(longText, 512, 64);
26
+ expect(parts.length).toBeGreaterThan(1);
27
+ });
28
+ });
29
+ describe('createChunks', () => {
30
+ it('does NOT produce a full chunk', () => {
31
+ const extracted = {
32
+ name: 'Test User',
33
+ experience: [],
34
+ education: [],
35
+ skills: ['React'],
36
+ languages: [],
37
+ certifications: [],
38
+ projects: [],
39
+ };
40
+ const chunks = createChunks(extracted, 'some raw text', {
41
+ name: 'Test User',
42
+ experienceYears: 1,
43
+ location: '',
44
+ topSkills: ['React'],
45
+ });
46
+ const sectionTypes = chunks.map(c => c.sectionType);
47
+ expect(sectionTypes).not.toContain('full');
48
+ });
49
+ it('prepends contextual header to each chunk', () => {
50
+ const extracted = {
51
+ name: 'Ali Yılmaz',
52
+ experience: [{ company: 'Acme', position: 'Dev', description: 'Built stuff' }],
53
+ education: [],
54
+ skills: ['React'],
55
+ languages: [],
56
+ certifications: [],
57
+ projects: [],
58
+ };
59
+ const chunks = createChunks(extracted, 'raw', {
60
+ name: 'Ali Yılmaz',
61
+ experienceYears: 5,
62
+ location: 'Istanbul',
63
+ topSkills: ['React'],
64
+ });
65
+ for (const chunk of chunks) {
66
+ expect(chunk.content).toContain('[Ali Yılmaz');
67
+ }
68
+ });
69
+ });
70
+ //# sourceMappingURL=chunking.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunking.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/chunking.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ describe('composite score calculation', () => {
3
+ it('without references: 50% vector + 30% skill + 20% loyalty', () => {
4
+ const vectorScore = 0.8;
5
+ const skillOverlap = 0.6;
6
+ const loyaltyFactor = 1.0;
7
+ const expected = 0.5 * vectorScore + 0.3 * skillOverlap + 0.2 * loyaltyFactor;
8
+ expect(expected).toBeCloseTo(0.78);
9
+ });
10
+ it('with references: 40% vector + 25% skill + 15% loyalty + 20% reference', () => {
11
+ const vectorScore = 0.8;
12
+ const skillOverlap = 0.6;
13
+ const loyaltyFactor = 1.0;
14
+ const referenceBoost = 0.7;
15
+ const expected = 0.4 * vectorScore + 0.25 * skillOverlap + 0.15 * loyaltyFactor + 0.2 * referenceBoost;
16
+ expect(expected).toBeCloseTo(0.76);
17
+ });
18
+ it('skill overlap defaults to 1.0 when no required skills', () => {
19
+ // When requiredSkills is empty, computeSkillOverlap returns { skillOverlap: 1 }
20
+ // This means the skill component contributes its full weight to the score
21
+ const vectorScore = 0.8;
22
+ const skillOverlap = 1.0; // default when no requirements
23
+ const loyaltyFactor = 1.0;
24
+ const expected = 0.5 * vectorScore + 0.3 * skillOverlap + 0.2 * loyaltyFactor;
25
+ expect(expected).toBeCloseTo(0.9);
26
+ });
27
+ });
28
+ //# sourceMappingURL=composite-score.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composite-score.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/composite-score.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,WAAW,GAAG,GAAG,CAAC;QACxB,MAAM,YAAY,GAAG,GAAG,CAAC;QACzB,MAAM,aAAa,GAAG,GAAG,CAAC;QAC1B,MAAM,QAAQ,GAAG,GAAG,GAAG,WAAW,GAAG,GAAG,GAAG,YAAY,GAAG,GAAG,GAAG,aAAa,CAAC;QAC9E,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,WAAW,GAAG,GAAG,CAAC;QACxB,MAAM,YAAY,GAAG,GAAG,CAAC;QACzB,MAAM,aAAa,GAAG,GAAG,CAAC;QAC1B,MAAM,cAAc,GAAG,GAAG,CAAC;QAC3B,MAAM,QAAQ,GAAG,GAAG,GAAG,WAAW,GAAG,IAAI,GAAG,YAAY,GAAG,IAAI,GAAG,aAAa,GAAG,GAAG,GAAG,cAAc,CAAC;QACvG,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,gFAAgF;QAChF,0EAA0E;QAC1E,MAAM,WAAW,GAAG,GAAG,CAAC;QACxB,MAAM,YAAY,GAAG,GAAG,CAAC,CAAC,+BAA+B;QACzD,MAAM,aAAa,GAAG,GAAG,CAAC;QAC1B,MAAM,QAAQ,GAAG,GAAG,GAAG,WAAW,GAAG,GAAG,GAAG,YAAY,GAAG,GAAG,GAAG,aAAa,CAAC;QAC9E,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { computeCentroidVector, findCommonSkills } from '../../application/use-cases/find-similar-candidates.js';
3
+ describe('computeCentroidVector', () => {
4
+ it('computes average of vectors', () => {
5
+ const vectors = [
6
+ [1, 0, 0],
7
+ [0, 1, 0],
8
+ [0, 0, 1],
9
+ ];
10
+ const centroid = computeCentroidVector(vectors);
11
+ expect(centroid[0]).toBeCloseTo(1 / 3);
12
+ expect(centroid[1]).toBeCloseTo(1 / 3);
13
+ expect(centroid[2]).toBeCloseTo(1 / 3);
14
+ });
15
+ it('returns zero vector for empty input', () => {
16
+ const centroid = computeCentroidVector([]);
17
+ expect(centroid).toEqual([]);
18
+ });
19
+ });
20
+ describe('findCommonSkills', () => {
21
+ it('finds intersection of skill arrays', () => {
22
+ const common = findCommonSkills(['react', 'typescript', 'nodejs'], ['typescript', 'python', 'nodejs']);
23
+ expect(common).toEqual(['typescript', 'nodejs']);
24
+ });
25
+ it('includes graph-based partial matches', () => {
26
+ // nextjs is a child of react in the skill graph
27
+ // findCommonSkills checks: for each skill in a, if bSkill is a child of skill → partial match
28
+ const common = findCommonSkills(['react'], ['nextjs']);
29
+ // getRelationship('nextjs', 'react') → 'child' because nextjs is in react's children
30
+ expect(common).toEqual(['react (via nextjs)']);
31
+ });
32
+ });
33
+ //# sourceMappingURL=find-similar-candidates.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-similar-candidates.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/find-similar-candidates.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,wDAAwD,CAAC;AAEjH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,OAAO,GAAG;YACd,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACT,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACT,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;SACV,CAAC;QACF,MAAM,QAAQ,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;QAChD,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACvC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACvC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,QAAQ,GAAG,qBAAqB,CAAC,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,MAAM,GAAG,gBAAgB,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;QACvG,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,gDAAgD;QAChD,8FAA8F;QAC9F,MAAM,MAAM,GAAG,gBAAgB,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACvD,qFAAqF;QACrF,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { QueryEnhancerService } from '../../application/services/query-enhancer.js';
3
+ describe('QueryEnhancerService', () => {
4
+ it('returns hypotheticalCV and keywords', async () => {
5
+ const mockOpenAI = {
6
+ responses: {
7
+ create: vi.fn().mockResolvedValue({
8
+ output_text: 'Senior React developer with 5 years experience in TypeScript and Node.js. Built scalable web applications.\n\nKEYWORDS:\nReact, TypeScript, Node.js, frontend, senior developer',
9
+ }),
10
+ },
11
+ };
12
+ const service = new QueryEnhancerService(mockOpenAI);
13
+ const result = await service.enhanceQuery('React frontend developer Istanbul');
14
+ expect(result.hypotheticalCV).toBeTruthy();
15
+ expect(result.hypotheticalCV.length).toBeGreaterThan(10);
16
+ expect(result.keywords).toBeInstanceOf(Array);
17
+ expect(result.keywords.length).toBeGreaterThan(0);
18
+ });
19
+ });
20
+ //# sourceMappingURL=query-enhancer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-enhancer.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/query-enhancer.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AAEpF,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,UAAU,GAAG;YACjB,SAAS,EAAE;gBACT,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC;oBAChC,WAAW,EAAE,iLAAiL;iBAC/L,CAAC;aACH;SACF,CAAC;QAEF,MAAM,OAAO,GAAG,IAAI,oBAAoB,CAAC,UAAiB,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,mCAAmC,CAAC,CAAC;QAE/E,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,UAAU,EAAE,CAAC;QAC3C,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;QACzD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAC9C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { SkillGraphService } from '../../shared/services/skill-graph.js';
3
+ describe('SkillGraphService', () => {
4
+ const service = new SkillGraphService();
5
+ describe('expandSkill', () => {
6
+ it('returns skill + children at depth 1', () => {
7
+ const expanded = service.expandSkill('react', 1);
8
+ expect(expanded).toContain('react');
9
+ expect(expanded).toContain('nextjs');
10
+ });
11
+ it('returns just the skill for unknown skills', () => {
12
+ expect(service.expandSkill('obscure-skill')).toEqual(['obscure-skill']);
13
+ });
14
+ it('does not expand beyond specified depth', () => {
15
+ const depth1 = service.expandSkill('javascript', 1);
16
+ expect(depth1).toContain('react');
17
+ expect(depth1).not.toContain('nextjs'); // nextjs is child of react (depth 2)
18
+ });
19
+ });
20
+ describe('getAncestors', () => {
21
+ it('returns ancestor chain', () => {
22
+ const ancestors = service.getAncestors('nextjs');
23
+ expect(ancestors).toEqual(['react', 'javascript']);
24
+ });
25
+ it('returns empty for root skills', () => {
26
+ expect(service.getAncestors('javascript')).toEqual([]);
27
+ });
28
+ });
29
+ describe('findGaps', () => {
30
+ it('identifies missing and partial matches', () => {
31
+ const result = service.findGaps(['nextjs', 'typescript', 'docker'], ['react', 'nodejs', 'kubernetes']);
32
+ expect(result.partialMatch).toContain('react');
33
+ expect(result.partialMatch).toContain('kubernetes');
34
+ expect(result.missing).toContain('nodejs');
35
+ });
36
+ });
37
+ });
38
+ //# sourceMappingURL=skill-graph.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-graph.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/skill-graph.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAEzE,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAE,CAAC;IAExC,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YACjD,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YACpC,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;YACpD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAClC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,qCAAqC;QAC/E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;YAChC,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;YACjD,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;QACxB,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAC7B,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,CAAC,EAClC,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,CAAC,CAClC,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAC/C,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;YACpD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { SkillNormalizerService } from '../../shared/services/skill-normalizer.js';
3
+ describe('SkillNormalizerService', () => {
4
+ const service = new SkillNormalizerService();
5
+ describe('normalize', () => {
6
+ it('normalizes known alias to canonical name', () => {
7
+ expect(service.normalize('Node.js')).toBe('nodejs');
8
+ expect(service.normalize('node')).toBe('nodejs');
9
+ expect(service.normalize('node js')).toBe('nodejs');
10
+ });
11
+ it('normalizes canonical name to itself', () => {
12
+ expect(service.normalize('react')).toBe('react');
13
+ expect(service.normalize('React')).toBe('react');
14
+ });
15
+ it('returns lowercase original for unknown skills', () => {
16
+ expect(service.normalize('SomeObscureFramework')).toBe('someobscureframework');
17
+ });
18
+ it('handles C# edge case', () => {
19
+ expect(service.normalize('C#')).toBe('csharp');
20
+ expect(service.normalize('c sharp')).toBe('csharp');
21
+ });
22
+ it('handles .NET edge case', () => {
23
+ expect(service.normalize('.NET')).toBe('dotnet');
24
+ expect(service.normalize('ASP.NET')).toBe('dotnet');
25
+ });
26
+ });
27
+ describe('normalizeAll', () => {
28
+ it('normalizes and deduplicates', () => {
29
+ const result = service.normalizeAll(['Node.js', 'node', 'React', 'react.js', 'Python']);
30
+ expect(result).toEqual(['nodejs', 'react', 'python']);
31
+ });
32
+ it('preserves order of first occurrence', () => {
33
+ const result = service.normalizeAll(['React', 'Node.js', 'react.js']);
34
+ expect(result).toEqual(['react', 'nodejs']);
35
+ });
36
+ it('returns empty for empty input', () => {
37
+ expect(service.normalizeAll([])).toEqual([]);
38
+ });
39
+ });
40
+ });
41
+ //# sourceMappingURL=skill-normalizer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-normalizer.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/skill-normalizer.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ describe('smoke test', () => {
3
+ it('vitest is configured correctly', () => {
4
+ expect(1 + 1).toBe(2);
5
+ });
6
+ });
7
+ //# sourceMappingURL=smoke.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smoke.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/smoke.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -7,4 +7,6 @@ export interface CandidateSummaryDto {
7
7
  experienceYears?: number;
8
8
  tags: string[];
9
9
  createdAt: string;
10
+ source: string;
11
+ pool?: string;
10
12
  }
@@ -5,4 +5,6 @@ export interface FilterInput {
5
5
  minExperienceYears?: number;
6
6
  languages?: string[];
7
7
  tags?: string[];
8
+ source?: string;
9
+ pool?: string;
8
10
  }
@@ -8,6 +8,9 @@ export interface SearchResultDto {
8
8
  }
9
9
  export interface SearchInput {
10
10
  query: string;
11
+ textQuery?: string;
12
+ enhanceQuery?: boolean;
11
13
  limit?: number;
12
14
  sectionType?: string;
15
+ location?: string;
13
16
  }
@@ -1,3 +1,8 @@
1
+ export interface FtsIndexStatusDto {
2
+ exists: boolean;
3
+ numIndexedRows?: number;
4
+ numUnindexedRows?: number;
5
+ }
1
6
  export interface StatsDto {
2
7
  totalCandidates: number;
3
8
  totalChunks: number;
@@ -7,4 +12,5 @@ export interface StatsDto {
7
12
  tagCounts: Record<string, number>;
8
13
  totalApplications: number;
9
14
  totalApplicationEvents: number;
15
+ ftsIndex: FtsIndexStatusDto;
10
16
  }
@@ -0,0 +1,10 @@
1
+ import type OpenAI from 'openai';
2
+ export interface EnhancedQuery {
3
+ hypotheticalCV: string;
4
+ keywords: string[];
5
+ }
6
+ export declare class QueryEnhancerService {
7
+ private openai;
8
+ constructor(openai: OpenAI);
9
+ enhanceQuery(originalQuery: string, location?: string): Promise<EnhancedQuery>;
10
+ }
@@ -0,0 +1,34 @@
1
+ export class QueryEnhancerService {
2
+ openai;
3
+ constructor(openai) {
4
+ this.openai = openai;
5
+ }
6
+ async enhanceQuery(originalQuery, location) {
7
+ const queryWithLocation = location
8
+ ? `${originalQuery}\nPreferred location: ${location}`
9
+ : originalQuery;
10
+ const response = await this.openai.responses.create({
11
+ model: 'gpt-5-mini',
12
+ input: [
13
+ {
14
+ role: 'system',
15
+ content: 'You are a recruitment assistant. Given a job search query, write a hypothetical ideal CV summary that would perfectly match this query. If a location is specified, prominently include it in the CV summary (e.g., "based in {location}"). Max 150 words. Also extract 5-10 key search keywords from the query. Respond in this exact format:\n\nCV_SUMMARY:\n<summary>\n\nKEYWORDS:\n<comma-separated keywords>',
16
+ },
17
+ {
18
+ role: 'user',
19
+ content: queryWithLocation,
20
+ },
21
+ ],
22
+ });
23
+ const text = response.output_text ?? '';
24
+ const cvMatch = text.match(/CV_SUMMARY:\s*\n([\s\S]*?)(?=\nKEYWORDS:)/);
25
+ const kwMatch = text.match(/KEYWORDS:\s*\n(.+)/);
26
+ const hypotheticalCV = cvMatch?.[1]?.trim() ?? originalQuery;
27
+ const keywords = kwMatch?.[1]
28
+ ?.split(',')
29
+ .map((k) => k.trim())
30
+ .filter(Boolean) ?? originalQuery.split(/\s+/);
31
+ return { hypotheticalCV, keywords };
32
+ }
33
+ }
34
+ //# sourceMappingURL=query-enhancer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-enhancer.js","sourceRoot":"","sources":["../../../src/application/services/query-enhancer.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,oBAAoB;IACX;IAApB,YAAoB,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;IAAG,CAAC;IAEtC,KAAK,CAAC,YAAY,CAAC,aAAqB,EAAE,QAAiB;QACzD,MAAM,iBAAiB,GAAG,QAAQ;YAChC,CAAC,CAAC,GAAG,aAAa,yBAAyB,QAAQ,EAAE;YACrD,CAAC,CAAC,aAAa,CAAC;QAElB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;YAClD,KAAK,EAAE,YAAY;YACnB,KAAK,EAAE;gBACL;oBACE,IAAI,EAAE,QAAQ;oBACd,OAAO,EACL,mZAAmZ;iBACtZ;gBACD;oBACE,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,iBAAiB;iBAC3B;aACF;SACF,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,IAAI,EAAE,CAAC;QAExC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;QAEjD,MAAM,cAAc,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,aAAa,CAAC;QAC7D,MAAM,QAAQ,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;YAC3B,EAAE,KAAK,CAAC,GAAG,CAAC;aACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,OAAO,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAEjD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC;IACtC,CAAC;CACF"}
@@ -5,6 +5,10 @@ import type { IDocumentParser } from '../ports/document-parser.js';
5
5
  import type { IEmbeddingService } from '../ports/embedding-service.js';
6
6
  import type { IStructuredExtractor } from '../ports/structured-extractor.js';
7
7
  export type ProgressCallback = (step: string, message: string) => void;
8
+ export interface AddCvOptions {
9
+ source?: string;
10
+ pool?: string;
11
+ }
8
12
  export declare class AddCvUseCase {
9
13
  private candidateRepo;
10
14
  private chunkRepo;
@@ -12,5 +16,5 @@ export declare class AddCvUseCase {
12
16
  private embeddingService;
13
17
  private extractor;
14
18
  constructor(candidateRepo: ICandidateRepository, chunkRepo: IChunkRepository, parsers: IDocumentParser[], embeddingService: IEmbeddingService, extractor: IStructuredExtractor);
15
- execute(filePath: string, onProgress?: ProgressCallback): Promise<Candidate>;
19
+ execute(filePath: string, options?: AddCvOptions, onProgress?: ProgressCallback): Promise<Candidate>;
16
20
  }