diff --git a/packages/ai/src/prompts/analyze-resume-system.md b/packages/ai/src/prompts/analyze-resume-system.md index 6aefedf93..6fcdbd795 100644 --- a/packages/ai/src/prompts/analyze-resume-system.md +++ b/packages/ai/src/prompts/analyze-resume-system.md @@ -47,6 +47,7 @@ Do not include markdown, comments, or additional keys. 4. Never invent candidate achievements or facts. 5. If data is missing, call it out explicitly in rationale/suggestions. 6. Keep scorecard dimensions practical and common for resume review. +7. Use the current date from the analysis context for chronology checks. Do not flag a date on or before that date as future-dated. ## Suggestions Requirements diff --git a/packages/api/src/features/ai/service.test.ts b/packages/api/src/features/ai/service.test.ts index 57f3ef703..5b6f580f0 100644 --- a/packages/api/src/features/ai/service.test.ts +++ b/packages/api/src/features/ai/service.test.ts @@ -1,4 +1,5 @@ import type { UIMessage } from "ai"; +import type { ResumeData } from "@reactive-resume/schema/resume/data"; import { afterEach, describe, expect, it, vi } from "vitest"; import { convertToModelMessages, modelMessageSchema } from "ai"; @@ -10,6 +11,7 @@ vi.mock("@reactive-resume/env/server", () => ({ env: envMock })); afterEach(() => { vi.unstubAllGlobals(); + vi.useRealTimers(); }); function stubOpenAICompatibleResponse(response?: { content?: string; finishReason?: string }) { @@ -67,7 +69,7 @@ function stubRejectedFetch(error: unknown) { return fetchMock; } -const { testConnection } = await import("./service"); +const { analyzeResume, testConnection } = await import("./service"); describe("AI provider connection test", () => { it("names the rejected key instead of reporting a transport failure", async () => { @@ -168,6 +170,26 @@ describe("AI provider connection test", () => { }); }); +describe("AI resume analysis", () => { + it("gives the model the actual current date for chronology checks", async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-08-18T12:00:00Z")); + + const response = stubOpenAICompatibleResponse({ + content: JSON.stringify({ + overallScore: 80, + scorecard: [{ dimension: "Clarity", score: 80, rationale: "Clear." }], + suggestions: [], + strengths: ["Clear experience."], + }), + }); + + await analyzeResume({ ...testInput(), resumeData: {} as ResumeData }); + + expect(JSON.stringify(response.getRequestBody())).toContain("Current date: 2026-08-18"); + }); +}); + describe("AI chat service", () => { it("tests OpenAI-compatible providers without requiring structured output", async () => { const openAiCompatible = stubOpenAICompatibleResponse(); diff --git a/packages/api/src/features/ai/service.ts b/packages/api/src/features/ai/service.ts index 755e04532..803629b4c 100644 --- a/packages/api/src/features/ai/service.ts +++ b/packages/api/src/features/ai/service.ts @@ -509,8 +509,9 @@ type AnalyzeResumeInput = z.infer & { resumeData: ResumeData; }; -function buildAnalyzeResumeSystemPrompt(resumeData: ResumeData): string { - return `${analyzeResumeSystemPromptTemplate}\n\n## Resume Data\n\n${JSON.stringify(resumeData, null, 2)}`; +function buildAnalyzeResumeSystemPrompt(resumeData: ResumeData, now = new Date()): string { + const currentDate = now.toISOString().slice(0, 10); + return `${analyzeResumeSystemPromptTemplate}\n\n## Analysis Context\n\nCurrent date: ${currentDate} (UTC). Treat dates on or before this date as not future-dated. Flag a date as future-dated only when it is after the current date.\n\n## Resume Data\n\n${JSON.stringify(resumeData, null, 2)}`; } /** Sends resume data to the AI provider and returns a structured analysis, parsing raw JSON from the response text. */