···11+# Jane Doe
22+**Full Stack Developer** | New York, NY | jane.doe@email.com
33+44+---
55+66+## Summary
77+Passionate full-stack developer with 6 years of experience in building modern web applications. Strong background in React, Node.js, and cloud technologies. Committed to writing maintainable, well-tested code.
88+99+---
1010+1111+## Experience
1212+1313+### Lead Developer
1414+**Global Tech Solutions** | April 2021 - Present
1515+1616+- Architected and led development of customer portal used by 50K+ enterprises
1717+- Established coding standards and code review processes for team of 8 engineers
1818+- Migrated legacy monolith to event-driven microservices architecture
1919+2020+**Technologies:** React, TypeScript, NestJS, PostgreSQL, RabbitMQ, AWS
2121+2222+### Software Engineer
2323+**StartupXYZ** | August 2018 - March 2021
2424+2525+- Built real-time collaboration features using WebSockets
2626+- Developed mobile-responsive UI components with React Native
2727+- Integrated third-party payment and authentication services
2828+2929+**Technologies:** React, React Native, Node.js, MongoDB, Firebase
3030+3131+### Junior Software Developer
3232+**Digital Creations Ltd** | January 2017 - July 2018
3333+3434+- Created RESTful APIs for content management system
3535+- Wrote unit and integration tests achieving 85% code coverage
3636+- Participated in agile ceremonies and sprint planning
3737+3838+**Technologies:** Express.js, JavaScript, MySQL, Jest
3939+4040+---
4141+4242+## Education
4343+4444+### Master of Science in Software Engineering
4545+**Columbia University** | September 2015 - May 2017
4646+- Focus: Distributed Systems and Cloud Computing
4747+- Thesis: "Scalable Event-Driven Architectures for Real-Time Applications"
4848+4949+### Bachelor of Arts in Computer Science
5050+**NYU** | September 2011 - May 2015
5151+- Minor: Mathematics
5252+- Graduated Magna Cum Laude
5353+5454+---
5555+5656+## Skills
5757+5858+**Languages:** TypeScript, JavaScript, Python, Go
5959+**Frontend:** React, React Native, Next.js, Vue.js
6060+**Backend:** Node.js, NestJS, Express, GraphQL
6161+**Databases:** PostgreSQL, MongoDB, Redis
6262+**Cloud & DevOps:** AWS, Docker, Kubernetes, Terraform, CI/CD
···11+John Smith
22+Software Engineer | San Francisco, CA
33+44+PROFESSIONAL SUMMARY
55+Experienced software engineer with 8+ years of experience building scalable web applications and distributed systems. Passionate about clean code, testing, and mentoring junior developers.
66+77+WORK EXPERIENCE
88+99+Senior Software Engineer
1010+Acme Corporation
1111+January 2020 - Present
1212+- Led development of microservices architecture serving 10M+ users
1313+- Mentored team of 5 junior developers
1414+- Implemented CI/CD pipelines reducing deployment time by 60%
1515+Skills: TypeScript, Node.js, Kubernetes, PostgreSQL, Redis
1616+1717+Software Engineer
1818+TechStart Inc
1919+March 2016 - December 2019
2020+- Built REST APIs and GraphQL endpoints for e-commerce platform
2121+- Optimized database queries improving response times by 40%
2222+- Collaborated with product team on feature specifications
2323+Skills: JavaScript, Python, MySQL, Docker, AWS
2424+2525+Junior Developer
2626+WebDev Agency
2727+June 2014 - February 2016
2828+- Developed responsive websites for various clients
2929+- Maintained legacy PHP applications
3030+Skills: PHP, JavaScript, HTML, CSS
3131+3232+EDUCATION
3333+3434+Bachelor of Science in Computer Science
3535+Stanford University
3636+September 2010 - June 2014
3737+- GPA: 3.8/4.0
3838+- Dean's List
3939+Skills: Algorithms, Data Structures, C++
4040+4141+SKILLS
4242+TypeScript, JavaScript, Python, Node.js, React, GraphQL, PostgreSQL, MongoDB, Redis, Docker, Kubernetes, AWS, Git
···11+// Schemas and types
22+export {
33+ ParsedCVDataSchema,
44+ ParsedJobExperienceSchema,
55+ ParsedEducationSchema,
66+ type ParsedCVData,
77+ type ParsedJobExperience,
88+ type ParsedEducation,
99+} from './schemas';
1010+1111+// Prompts
1212+export { CV_PARSING_PROMPT, getCV_PARSING_PROMPT } from './prompts';
1313+1414+// Service
1515+export { CVParserService, type CVParserConfig } from './ai-parser.service';
1616+1717+// NestJS Module
1818+export { CVParserModule, CV_PARSER_SERVICE } from './cv-parser.module';
+61
packages/ai-parser/src/prompts.ts
···11+/**
22+ * System prompt for CV parsing
33+ * Instructs the LLM to extract structured information from CV text
44+ */
55+export const CV_PARSING_PROMPT = `You are a professional CV parser. Your task is to extract structured information from the provided CV text and return it as a JSON object.
66+77+Extract the following information:
88+1. Personal info: name and introduction/summary
99+2. Work experience: for each job, extract company, role, level, dates, description, and skills mentioned
1010+3. Education: for each entry, extract institution, degree, field of study, dates, description, and skills
1111+4. Skills: list of all mentioned skills
1212+1313+IMPORTANT RULES:
1414+- Return ONLY valid JSON, no other text
1515+- All dates must be in ISO 8601 format (YYYY-MM-DD)
1616+- For current positions, set endDate to null
1717+- Skills should be extracted as an array of strings
1818+- If a field is not found, omit it from the object (except for arrays, which default to [])
1919+- Company/institution names should be exact as written in the CV
2020+- Keep descriptions concise (1-2 sentences)
2121+2222+Example JSON structure:
2323+{
2424+ "personalInfo": {
2525+ "name": "John Doe",
2626+ "introduction": "Software engineer with 10 years of experience"
2727+ },
2828+ "jobExperiences": [
2929+ {
3030+ "companyName": "Tech Corp",
3131+ "roleName": "Senior Software Engineer",
3232+ "levelName": "Senior",
3333+ "startDate": "2020-01-15",
3434+ "endDate": null,
3535+ "description": "Led development of microservices architecture using Kubernetes",
3636+ "skills": ["Kubernetes", "Go", "Docker", "PostgreSQL"]
3737+ }
3838+ ],
3939+ "education": [
4040+ {
4141+ "institutionName": "MIT",
4242+ "degree": "Bachelor of Science",
4343+ "fieldOfStudy": "Computer Science",
4444+ "startDate": "2012-09-01",
4545+ "endDate": "2016-05-31",
4646+ "skills": ["C++", "Algorithms", "Data Structures"]
4747+ }
4848+ ],
4949+ "skills": ["Kubernetes", "Go", "Docker", "PostgreSQL", "C++", "Algorithms"]
5050+}
5151+5252+CV Text to parse:
5353+---
5454+{cvText}
5555+---
5656+5757+Return only the JSON object.`;
5858+5959+export const getCV_PARSING_PROMPT = (cvText: string): string => {
6060+ return CV_PARSING_PROMPT.replace('{cvText}', cvText);
6161+};
+66
packages/ai-parser/src/schemas.ts
···11+import { z } from 'zod';
22+33+/**
44+ * Schema for parsed job experience extracted from CV text
55+ */
66+export const ParsedJobExperienceSchema = z.object({
77+ companyName: z.string().min(1, 'Company name is required'),
88+ roleName: z.string().min(1, 'Role name is required'),
99+ levelName: z
1010+ .string()
1111+ .optional()
1212+ .transform((val) => val?.trim() || undefined),
1313+ startDate: z.string().min(1, 'Start date is required'), // ISO date string YYYY-MM-DD
1414+ endDate: z.string().nullable().optional(), // ISO date string or null for current position
1515+ description: z
1616+ .string()
1717+ .optional()
1818+ .transform((val) => val?.trim() || undefined),
1919+ skills: z.array(z.string()).default([]),
2020+});
2121+2222+export type ParsedJobExperience = z.infer<typeof ParsedJobExperienceSchema>;
2323+2424+/**
2525+ * Schema for parsed education extracted from CV text
2626+ */
2727+export const ParsedEducationSchema = z.object({
2828+ institutionName: z.string().min(1, 'Institution name is required'),
2929+ degree: z.string().min(1, 'Degree is required'),
3030+ fieldOfStudy: z
3131+ .string()
3232+ .optional()
3333+ .transform((val) => val?.trim() || undefined),
3434+ startDate: z.string().min(1, 'Start date is required'), // ISO date string YYYY-MM-DD
3535+ endDate: z.string().nullable().optional(), // ISO date string or null for currently studying
3636+ description: z
3737+ .string()
3838+ .optional()
3939+ .transform((val) => val?.trim() || undefined),
4040+ skills: z.array(z.string()).default([]),
4141+});
4242+4343+export type ParsedEducation = z.infer<typeof ParsedEducationSchema>;
4444+4545+/**
4646+ * Schema for complete CV data parsed from text
4747+ */
4848+export const ParsedCVDataSchema = z.object({
4949+ personalInfo: z
5050+ .object({
5151+ name: z
5252+ .string()
5353+ .optional()
5454+ .transform((val) => val?.trim() || undefined),
5555+ introduction: z
5656+ .string()
5757+ .optional()
5858+ .transform((val) => val?.trim() || undefined),
5959+ })
6060+ .optional(),
6161+ jobExperiences: z.array(ParsedJobExperienceSchema).default([]),
6262+ education: z.array(ParsedEducationSchema).default([]),
6363+ skills: z.array(z.string()).default([]),
6464+});
6565+6666+export type ParsedCVData = z.infer<typeof ParsedCVDataSchema>;
···11+# AI Provider Test Setup
22+33+## Overview
44+55+This package has two types of tests:
66+77+1. **Unit Tests** (`.test.ts`) - Fast, mocked tests that don't require llama.cpp
88+2. **Integration Tests** (`.integration.test.ts`) - Slower tests that hit the actual llama.cpp service
99+1010+## Running Tests
1111+1212+```bash
1313+# Run fast unit tests only (default)
1414+pnpm test
1515+1616+# Run in watch mode during development
1717+pnpm test:watch
1818+1919+# Run integration tests (requires llama.cpp running)
2020+pnpm test:integration
2121+2222+# Run ALL tests (unit + integration)
2323+pnpm test:all
2424+```
2525+2626+## Integration Test Requirements
2727+2828+Integration tests require:
2929+- Docker Compose services running (`docker compose up -d llama`)
3030+- llama.cpp healthy on `http://localhost:8080`
3131+- Model downloaded (~4.4GB Mistral 7B)
3232+3333+**Note**: Integration tests are slow (~30-60 seconds) because they hit the real model.
3434+3535+## Test Structure
3636+3737+### Unit Tests (`ai-parser.service.test.ts`)
3838+- Mock the AI provider
3939+- Test JSON extraction logic
4040+- Test stop sequence configuration (caught the `'\n\n'` bug!)
4141+- Test error handling
4242+- **Fast**: Run in milliseconds
4343+4444+### Integration Tests (`llama-cpp.provider.integration.test.ts`)
4545+- Hit real llama.cpp service
4646+- Test health checks
4747+- Test basic completions
4848+- Test JSON generation
4949+- Test timeout handling
5050+- **Slow**: Run in 30+ seconds
5151+5252+## What These Tests Caught
5353+5454+✅ **Stop Sequence Bug**: Unit tests verify `'\n\n'` is NOT in stop sequences
5555+✅ **Timeout Issues**: Integration tests verify timeouts work correctly
5656+✅ **JSON Parsing**: Both test JSON extraction from various response formats
5757+✅ **Error Handling**: Tests verify clear error messages for invalid responses
5858+5959+## CI/CD Recommendations
6060+6161+In CI, run:
6262+```bash
6363+# Fast feedback (unit tests only)
6464+pnpm test
6565+6666+# Full validation (run integration tests separately, allow failures)
6767+pnpm test:integration || echo "Integration tests skipped or failed"
6868+```
6969+7070+Integration tests can be flaky (model availability, performance), so don't block CI on them.
···77 CannotViewError,
88} from "../errors/authorization.error";
99import type { User } from "../user/user.entity";
1010-import type { PolicyRegistry } from "./policy-registry.service";
1010+import { PolicyRegistry } from "./policy-registry.service";
11111212@Injectable()
1313export class AuthorizationService {
···11import { raise } from "@cv/system";
22import { Injectable, type OnModuleInit, type Type } from "@nestjs/common";
33-import type { DiscoveryService, Reflector } from "@nestjs/core";
33+import { DiscoveryService, Reflector } from "@nestjs/core";
44import { POLICY_RESOURCE_KEY } from "./policy.decorator";
55import type { Policy } from "./policy.interface";
66
+1-1
packages/auth/src/config/jwt.config.ts
···11import { Injectable } from "@nestjs/common";
22-import type { ConfigService } from "@nestjs/config";
22+import { ConfigService } from "@nestjs/config";
3344@Injectable()
55export class JwtConfigService {
···11import { raise } from "@cv/system";
22import { Injectable, type OnModuleInit } from "@nestjs/common";
33-import type { DiscoveryService, Reflector } from "@nestjs/core";
33+import { DiscoveryService, Reflector } from "@nestjs/core";
44import { z } from "zod";
55import {
66 IDENTITY_PROVIDER_KEY,
···44 PASSWORD_RESET_REQUESTED,
55 type PasswordResetRequestedEvent,
66} from "../events/password-reset-requested.event";
77-import type { TemplatedEmailService } from "../templated-email.service";
77+import { TemplatedEmailService } from "../templated-email.service";
8899@Injectable()
1010export class PasswordResetEmailListener {
···44 REGISTRATION_ATTEMPT_ON_EXISTING_EMAIL,
55 type RegistrationAttemptOnExistingEmailEvent,
66} from "../events/registration-attempt-on-existing-email.event";
77-import type { TemplatedEmailService } from "../templated-email.service";
77+import { TemplatedEmailService } from "../templated-email.service";
8899@Injectable()
1010export class RegistrationAttemptEmailListener {
···44 VERIFICATION_EMAIL_REQUESTED,
55 type VerificationEmailRequestedEvent,
66} from "../events/verification-email-requested.event";
77-import type { TemplatedEmailService } from "../templated-email.service";
77+import { TemplatedEmailService } from "../templated-email.service";
8899@Injectable()
1010export class VerificationEmailListener {
···11import { Injectable } from "@nestjs/common";
22-import type { EventEmitter2 } from "@nestjs/event-emitter";
22+import { EventEmitter2 } from "@nestjs/event-emitter";
33import * as bcrypt from "bcryptjs";
44import {
55 CurrentPasswordIncorrectError,
···88 InvalidVerificationTokenError,
99 PasswordIncorrectError,
1010} from "../../errors/authentication.error";
1111-import type { IdentityProviderRegistry } from "../../identity-provider-registry.service";
1111+import { IdentityProviderRegistry } from "../../identity-provider-registry.service";
1212import { JwtScope } from "../../jwt/jwt-scope.enum";
1313import type { RequestMetadata } from "../../request/request-metadata.decorator";
1414-import type { TokenService } from "../../token/token.service";
1515-import type { TokenExpiryConfigService } from "../../token/token-expiry.config";
1616-import type { CredentialsService } from "../../user/credentials.service";
1414+import { TokenService } from "../../token/token.service";
1515+import { TokenExpiryConfigService } from "../../token/token-expiry.config";
1616+import { CredentialsService } from "../../user/credentials.service";
1717import { generateSecureToken } from "../../user/credentials-token.util";
1818import type { User } from "../../user/user.entity";
1919-import type { UserService } from "../../user/user.service";
1919+import { UserService } from "../../user/user.service";
2020import {
2121 PASSWORD_RESET_REQUESTED,
2222 PasswordResetRequestedEvent,
···33import * as bcrypt from "bcryptjs";
44import { InvalidCredentialsError } from "../../errors/authentication.error";
55import type { IdentityProvider as IIdentityProvider } from "../../identity-provider.interface";
66-import type { CredentialsService } from "../../user/credentials.service";
66+import { CredentialsService } from "../../user/credentials.service";
77import type { User } from "../../user/user.entity";
8899export const PASSWORD_PROVIDER_NAME = Symbol("password");
···11-import type { PrismaService } from "@cv/system";
11+import { PrismaService } from "@cv/system";
22import { Injectable } from "@nestjs/common";
33import { InvalidRefreshTokenError, notFound } from "../errors";
44-import type { DeviceIdentificationService } from "../metadata/device-identification.service";
55-import type { LocationService } from "../metadata/location.service";
44+import { DeviceIdentificationService } from "../metadata/device-identification.service";
55+import { LocationService } from "../metadata/location.service";
66import { hashToken } from "../user/credentials-token.util";
77-import type { TokenEncryptionService } from "../user/token-encryption.service";
77+import { TokenEncryptionService } from "../user/token-encryption.service";
88import type { RefreshToken } from "./refresh-token.entity";
99-import type { RefreshTokenMapper } from "./refresh-token.mapper";
99+import { RefreshTokenMapper } from "./refresh-token.mapper";
10101111@Injectable()
1212export class RefreshTokenService {
+1-1
packages/auth/src/token/token-expiry.config.ts
···11import { Injectable } from "@nestjs/common";
22-import type { ConfigService } from "@nestjs/config";
22+import { ConfigService } from "@nestjs/config";
3344@Injectable()
55export class TokenExpiryConfigService {
+5-5
packages/auth/src/token/token.service.ts
···11import { Injectable } from "@nestjs/common";
22-import type { JwtService } from "@nestjs/jwt";
33-import type { JwtConfigService } from "../config/jwt.config";
22+import { JwtService } from "@nestjs/jwt";
33+import { JwtConfigService } from "../config/jwt.config";
44import { InvalidRefreshTokenError } from "../errors/authentication.error";
55import { JwtScope } from "../jwt/jwt-scope.enum";
66import type { RequestMetadata } from "../request/request-metadata.decorator";
77import type { User } from "../user/user.entity";
88-import type { UserService } from "../user/user.service";
99-import type { RefreshTokenService } from "./refresh-token.service";
1010-import type { TokenExpiryConfigService } from "./token-expiry.config";
88+import { UserService } from "../user/user.service";
99+import { RefreshTokenService } from "./refresh-token.service";
1010+import { TokenExpiryConfigService } from "./token-expiry.config";
11111212@Injectable()
1313export class TokenService {
+4-4
packages/auth/src/user/credentials.service.ts
···11-import type { PrismaService } from "@cv/system";
11+import { PrismaService } from "@cv/system";
22import { Injectable } from "@nestjs/common";
33import { notFound } from "../errors";
44import {
···99 VerificationTokenExpiredError,
1010} from "../errors/authentication.error";
1111import type { Credentials } from "./credentials.entity";
1212-import type { CredentialsMapper } from "./credentials.mapper";
1212+import { CredentialsMapper } from "./credentials.mapper";
1313import { hashToken, verifyToken } from "./credentials-token.util";
1414-import type { TokenEncryptionService } from "./token-encryption.service";
1414+import { TokenEncryptionService } from "./token-encryption.service";
1515import type { User } from "./user.entity";
1616-import type { UserMapper } from "./user.mapper";
1616+import { UserMapper } from "./user.mapper";
17171818@Injectable()
1919export class CredentialsService {
···55 scryptSync,
66} from "node:crypto";
77import { Injectable } from "@nestjs/common";
88-import type { ConfigService } from "@nestjs/config";
88+import { ConfigService } from "@nestjs/config";
991010@Injectable()
1111export class TokenEncryptionService {
+1-1
packages/auth/src/user/user.mapper.ts
···11import type { BaseMapper } from "@cv/system";
22import { Injectable } from "@nestjs/common";
33import type { Prisma } from "@prisma/client";
44-import type { CredentialsMapper } from "./credentials.mapper";
44+import { CredentialsMapper } from "./credentials.mapper";
55import { User } from "./user.entity";
6677type PrismaUserWithCredentials = Prisma.UserGetPayload<{
+2-2
packages/auth/src/user/user.service.ts
···11-import type { PrismaService } from "@cv/system";
11+import { PrismaService } from "@cv/system";
22import { Injectable } from "@nestjs/common";
33import { notFound } from "../errors";
44import type { User } from "./user.entity";
55-import type { UserMapper } from "./user.mapper";
55+import { UserMapper } from "./user.mapper";
6677@Injectable()
88export class UserService {
···11-import type { PrismaService } from "../database/prisma.service";
11+import { PrismaService } from "../database/prisma.service";
22import type { EntityService } from "./entity-service.interface";
33import type { BaseMapper } from "./mapper.interface";
44import type { NamedEntity } from "./named-entity";
+1-1
packages/system/src/base/pagination.service.ts
···11import { Injectable } from "@nestjs/common";
22import type { BaseEntity } from "./base.entity";
33-import type { CursorService } from "./cursor.service";
33+import { CursorService } from "./cursor.service";
44import {
55 PageInfo,
66 type PaginationOptions,
···33 type OnModuleDestroy,
44 type OnModuleInit,
55} from "@nestjs/common";
66-import type { ConfigService } from "@nestjs/config";
66+import { ConfigService } from "@nestjs/config";
77import { PrismaPg } from "@prisma/adapter-pg";
88import { PrismaClient } from "@prisma/client";
99import { Pool } from "pg";
+9-2
packages/system/src/mail/resend/resend.module.ts
···11-import { Global, Module } from "@nestjs/common";
11+import { Global, Logger, Module } from "@nestjs/common";
22import { ConfigService } from "@nestjs/config";
33import {
44 MAIL_SERVICE_TOKEN,
55 type MailService,
66} from "../mail.service.interface";
77+import { ConsoleMailService } from "../providers/console-mail.service";
78import { ResendMailService } from "./resend-mail.service";
89910@Global()
···1213 {
1314 provide: MAIL_SERVICE_TOKEN,
1415 useFactory: (configService: ConfigService): MailService => {
1515- const apiKey = configService.getOrThrow<string>("RESEND_API_KEY");
1616+ const apiKey = configService.get<string>("RESEND_API_KEY");
1717+ if (!apiKey) {
1818+ new Logger("ResendModule").warn(
1919+ "RESEND_API_KEY not set - using console mail service",
2020+ );
2121+ return new ConsoleMailService();
2222+ }
1623 return new ResendMailService(apiKey);
1724 },
1825 inject: [ConfigService],
···22import { join } from "node:path";
33import { Injectable } from "@nestjs/common";
44import Handlebars from "handlebars";
55-import type { TemplateRegistryService } from "./template-registry.service";
55+import { TemplateRegistryService } from "./template-registry.service";
6677@Injectable()
88export class HandlebarsTemplateService {