careermate 0.0.1

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 (137) hide show
  1. package/AGENTS.md +33 -0
  2. package/INSTALL.md +230 -0
  3. package/LICENSE +21 -0
  4. package/README.md +532 -0
  5. package/THIRD_PARTY_NOTICES.md +57 -0
  6. package/bin/careermate.mjs +77 -0
  7. package/dist/career-os/README.md +78 -0
  8. package/dist/career-os/eop/cover-letter.md +46 -0
  9. package/dist/career-os/eop/fit-analysis.md +48 -0
  10. package/dist/career-os/eop/interview-prep.md +39 -0
  11. package/dist/career-os/eop/job-analysis.md +42 -0
  12. package/dist/career-os/eop/profile-extraction.md +37 -0
  13. package/dist/career-os/knowledge/ats.md +131 -0
  14. package/dist/career-os/knowledge/company-research.md +137 -0
  15. package/dist/career-os/knowledge/cover-letter.md +135 -0
  16. package/dist/career-os/knowledge/fit-matching.md +121 -0
  17. package/dist/career-os/knowledge/human-writing.md +118 -0
  18. package/dist/career-os/knowledge/interview-behavioral.md +132 -0
  19. package/dist/career-os/knowledge/interview-technical.md +126 -0
  20. package/dist/career-os/knowledge/linkedin-profile.md +152 -0
  21. package/dist/career-os/knowledge/networking-referrals.md +166 -0
  22. package/dist/career-os/knowledge/offer-evaluation-decision.md +121 -0
  23. package/dist/career-os/knowledge/onboarding-first-90-days.md +126 -0
  24. package/dist/career-os/knowledge/portfolio.md +121 -0
  25. package/dist/career-os/knowledge/recruiter-screen.md +105 -0
  26. package/dist/career-os/knowledge/rejection-triage-iteration.md +115 -0
  27. package/dist/career-os/knowledge/resume.md +123 -0
  28. package/dist/career-os/knowledge/salary-negotiation.md +123 -0
  29. package/dist/career-os/knowledge/verifiers/ats-compat.md +64 -0
  30. package/dist/career-os/knowledge/verifiers/consistency.md +61 -0
  31. package/dist/career-os/knowledge/verifiers/human-voice.md +82 -0
  32. package/dist/career-os/knowledge/verifiers/recency-staleness.md +72 -0
  33. package/dist/career-os/knowledge/verifiers/responsiveness-on-target.md +82 -0
  34. package/dist/career-os/knowledge/verifiers/truthfulness.md +67 -0
  35. package/dist/doctor.mjs +79094 -0
  36. package/dist/mcp.mjs +96108 -0
  37. package/dist/migrate.mjs +4735 -0
  38. package/dist/pdf.worker.min.mjs +28 -0
  39. package/dist/public/app.js +150 -0
  40. package/dist/public/fonts/PretendardVariable.woff2 +0 -0
  41. package/dist/public/icon-180.png +0 -0
  42. package/dist/public/index.html +52 -0
  43. package/dist/public/lib.js +394 -0
  44. package/dist/public/pages/applications.js +107 -0
  45. package/dist/public/pages/documents.js +467 -0
  46. package/dist/public/pages/home.js +261 -0
  47. package/dist/public/pages/interview.js +227 -0
  48. package/dist/public/pages/jobs.js +493 -0
  49. package/dist/public/pages/profile.js +556 -0
  50. package/dist/public/pages/settings.js +240 -0
  51. package/dist/public/styles.css +498 -0
  52. package/dist/site/analytics.js +97 -0
  53. package/dist/site/favicon.svg +1 -0
  54. package/dist/site/fonts/PretendardVariable.woff2 +0 -0
  55. package/dist/site/icon-180.png +0 -0
  56. package/dist/site/icon-512.png +0 -0
  57. package/dist/site/index.html +598 -0
  58. package/dist/site/landing.js +54 -0
  59. package/dist/site/llms-install.txt +217 -0
  60. package/dist/site/robots.txt +4 -0
  61. package/dist/site/sitemap.xml +21 -0
  62. package/dist/site/start.html +326 -0
  63. package/dist/site/style.css +550 -0
  64. package/dist/site/vercel.json +20 -0
  65. package/dist/web.mjs +46852 -0
  66. package/docs/FAQ.md +101 -0
  67. package/docs/MCP_TOOLS.md +311 -0
  68. package/docs/README.md +28 -0
  69. package/docs/START_WORKFLOW.md +137 -0
  70. package/docs/SUPPORTED_AI_APPS.md +112 -0
  71. package/docs/WORKFLOWS.md +202 -0
  72. package/docs/career-os/README.md +78 -0
  73. package/docs/career-os/eop/cover-letter.md +46 -0
  74. package/docs/career-os/eop/fit-analysis.md +48 -0
  75. package/docs/career-os/eop/interview-prep.md +39 -0
  76. package/docs/career-os/eop/job-analysis.md +42 -0
  77. package/docs/career-os/eop/profile-extraction.md +37 -0
  78. package/docs/career-os/knowledge/ats.md +131 -0
  79. package/docs/career-os/knowledge/company-research.md +137 -0
  80. package/docs/career-os/knowledge/cover-letter.md +135 -0
  81. package/docs/career-os/knowledge/fit-matching.md +121 -0
  82. package/docs/career-os/knowledge/human-writing.md +118 -0
  83. package/docs/career-os/knowledge/interview-behavioral.md +132 -0
  84. package/docs/career-os/knowledge/interview-technical.md +126 -0
  85. package/docs/career-os/knowledge/linkedin-profile.md +152 -0
  86. package/docs/career-os/knowledge/networking-referrals.md +166 -0
  87. package/docs/career-os/knowledge/offer-evaluation-decision.md +121 -0
  88. package/docs/career-os/knowledge/onboarding-first-90-days.md +126 -0
  89. package/docs/career-os/knowledge/portfolio.md +121 -0
  90. package/docs/career-os/knowledge/recruiter-screen.md +105 -0
  91. package/docs/career-os/knowledge/rejection-triage-iteration.md +115 -0
  92. package/docs/career-os/knowledge/resume.md +123 -0
  93. package/docs/career-os/knowledge/salary-negotiation.md +123 -0
  94. package/docs/career-os/knowledge/verifiers/ats-compat.md +64 -0
  95. package/docs/career-os/knowledge/verifiers/consistency.md +61 -0
  96. package/docs/career-os/knowledge/verifiers/human-voice.md +82 -0
  97. package/docs/career-os/knowledge/verifiers/recency-staleness.md +72 -0
  98. package/docs/career-os/knowledge/verifiers/responsiveness-on-target.md +82 -0
  99. package/docs/career-os/knowledge/verifiers/truthfulness.md +67 -0
  100. package/package.json +89 -0
  101. package/packages/core/src/context.ts +74 -0
  102. package/packages/core/src/index.ts +8 -0
  103. package/packages/core/src/onboarding.ts +81 -0
  104. package/packages/core/src/services.ts +187 -0
  105. package/packages/core/src/summary.ts +104 -0
  106. package/packages/db/src/connection.ts +76 -0
  107. package/packages/db/src/index.ts +23 -0
  108. package/packages/db/src/paths.ts +52 -0
  109. package/packages/db/src/repositories.ts +891 -0
  110. package/packages/db/src/runtime.ts +58 -0
  111. package/packages/db/src/schema.ts +307 -0
  112. package/packages/exporters/src/html.ts +110 -0
  113. package/packages/exporters/src/index.ts +364 -0
  114. package/packages/exporters/src/markdown.ts +178 -0
  115. package/packages/knowledge/src/index.ts +168 -0
  116. package/packages/mcp-tools/src/bridge.ts +106 -0
  117. package/packages/mcp-tools/src/index.ts +17 -0
  118. package/packages/mcp-tools/src/result.ts +49 -0
  119. package/packages/mcp-tools/src/tools.ts +747 -0
  120. package/packages/mcp-tools/src/update.ts +330 -0
  121. package/packages/parsers/src/documents.ts +533 -0
  122. package/packages/parsers/src/html.ts +86 -0
  123. package/packages/parsers/src/index.ts +231 -0
  124. package/packages/parsers/src/keywords.ts +151 -0
  125. package/packages/prompts/src/humanize.ts +59 -0
  126. package/packages/prompts/src/index.ts +82 -0
  127. package/packages/prompts/src/install.ts +43 -0
  128. package/packages/prompts/src/onboarding.ts +35 -0
  129. package/packages/prompts/src/system.ts +57 -0
  130. package/packages/shared/src/build-info.ts +22 -0
  131. package/packages/shared/src/enums.ts +142 -0
  132. package/packages/shared/src/index.ts +19 -0
  133. package/packages/shared/src/schemas.ts +453 -0
  134. package/packages/workflows/src/definitions.ts +109 -0
  135. package/packages/workflows/src/index.ts +39 -0
  136. package/scripts/init.ts +371 -0
  137. package/tsconfig.json +32 -0
@@ -0,0 +1,82 @@
1
+ # 사람 목소리 (Human-voice) — CareerMate 교차검증 루브릭 (Phase A)
2
+
3
+ > 이 검증기는 연결된 AI가 커리어 산출물(이력서·자소서·면접 답변·LinkedIn 등)을 점검할 때 적용하는 **크리틱 루브릭**이다. CareerMate는 루브릭을 제공하고, 실제 검사는 연결된 AI가 수행한다(내부 LLM 없음).
4
+
5
+ ## 1. 목적
6
+
7
+ 이 검증기는 커리어 산출물이 **"AI가 쓴 티"가 나지 않고 사람의 목소리로 읽히는지**를 보장한다. 채용 담당자는 천편일률적인 자동 생성 글을 즉시 알아채고 신뢰를 거둔다. 구체적으로 다음 네 가지를 본다.
8
+
9
+ 1. **슬롭 구문 밀도** — `delve`, `leverage`, `tapestry`, "오늘날과 같은", "~에 대한 열정" 같은 LLM 상투어·필러가 과하게 깔려 있지 않은가.
10
+ 2. **문장 길이 다양성 (burstiness)** — 짧은 문장과 긴 문장이 섞여 사람처럼 들숨날숨이 있는가, 아니면 비슷한 길이로 단조롭게 흐르는가.
11
+ 3. **목소리 일관성** — 어조·인칭·격식 수준이 글 전체에서 한 사람의 것으로 유지되는가, 짜깁기처럼 튀는 구간이 있는가.
12
+ 4. **사실·숫자·고유명사 보존** — 사람 목소리로 다듬는 과정에서 원본의 수치·날짜·회사명·기술명이 누락·왜곡되지 않았는가(절대 손상 금지 항목).
13
+
14
+ 핵심: 이 루브릭의 검사 항목은 **LLM 없이도 세거나(개수)·존재 여부 확인·비율 계산·패턴 매칭으로 판정**할 수 있게 설계되었다. 연결된 AI는 직접 세고, CareerMate는 원본/대상 텍스트와 이 기준표를 제공한다.
15
+
16
+ ## 2. 검사 항목
17
+
18
+ > 적용 단위: 한 산출물(자소서 1편, 이력서 1부, 면접 답변 1세트 등). "문장"은 종결부호(`.`, `!`, `?`, `다.`, `요.`) 기준으로 분할한다. 한국어/영어 혼용 글도 동일 규칙을 적용한다.
19
+
20
+ | ID | 검사 | 합격 기준 | 측정 방법(셀 수 있게) |
21
+ |----|------|-----------|------------------------|
22
+ | C1 | 슬롭 단어 밀도 | 슬롭 사전 단어가 **100단어당 1.5개 이하**, 그리고 동일 슬롭 단어가 **3회 초과 반복 없음** | 아래 §슬롭 사전과 대조해 일치 토큰 수를 센다 → (일치 수 ÷ 전체 단어 수) × 100. 단어별 등장 횟수도 집계 |
23
+ | C2 | 슬롭 상투 구문 존재 | 금지 상투 구문 **0건** | 아래 §상투 구문 목록을 부분 문자열로 탐색, 매칭 건수 카운트 |
24
+ | C3 | 문장 길이 다양성 (burstiness) | 문장 길이의 **표준편차 ≥ 5단어**, 그리고 **최단 문장 ≤ 8단어**인 문장이 1개 이상 존재 | 각 문장 단어 수 배열 → 표준편차 계산, 최솟값 확인 |
25
+ | C4 | 문장 시작어 반복 | 동일 단어로 시작하는 문장이 **전체의 30% 미만**, 같은 시작어 **연속 3문장 금지** | 각 문장 첫 단어(조사 제외)를 모아 빈도 집계, 연속 동일 여부 확인 |
26
+ | C5 | 단락 구조 단조성 (rule-of-three 남용) | "A, B, and/그리고 C" 형태 3단 병렬이 **단락당 1회 이하**, 글 전체 **3회 이하** | 3항 병렬 패턴(쉼표 2개 + 접속사) 매칭 수를 센다 |
27
+ | C6 | 과장 형용사·부사 밀도 | "혁신적/seamless/robust/cutting-edge/획기적" 류 과장어가 **100단어당 1개 이하** | §과장어 사전과 대조해 일치 수 카운트 → 비율 계산 |
28
+ | C7 | 인칭·어조 일관성 | 주 인칭(1인칭/3인칭)·문체(평서 "~다" vs 경어 "~습니다") 혼용이 **전체 문장의 10% 미만**, 격식 급변 구간 0 | 종결어미 유형을 문장별 태깅해 소수 유형 비율 계산 |
29
+ | C8 | 사실·숫자 보존 | 원본의 모든 숫자(수치·퍼센트·연도·금액)가 대상 텍스트에 **100% 존재** | 원본에서 숫자 토큰 집합 추출 → 대상에 모두 포함되는지 집합 비교(누락 0) |
30
+ | C9 | 고유명사 보존 | 원본의 회사명·제품명·기술명·자격증명 등 고유명사가 **100% 보존**, 철자 변형 0 | 원본 고유명사 목록 추출 → 대상에서 정확 일치 확인(누락·오타 0) |
31
+ | C10 | 빈말 채움(filler) 문장 | 정보 없는 채움 문장("~에 대한 저의 열정은 남다릅니다" 류)이 **전체 문장의 5% 미만** | §filler 패턴과 매칭되거나 숫자·고유명사·동사 목적어가 전혀 없는 문장 수를 센다 |
32
+ | C11 | 구체 증거(수치·사례) 존재 | 본문에 **정량 근거(숫자/기간/규모/성과 지표)가 최소 2개** 이상 | 숫자+단위/퍼센트/"N배"/"N명"/"N개월" 패턴 매칭 수가 2 이상인지 확인 |
33
+
34
+ ### 슬롭 사전 (C1 — 부분 일치로 카운트)
35
+
36
+ 영어: `delve, leverage, utilize, harness, streamline, underscore, robust, seamless(ly), pivotal, comprehensive, multifaceted, holistic, tapestry, landscape, realm, synergy, testament, beacon, paradigm, embark, navigate, unlock, unleash, empower, bespoke, foster, showcase, intricate, meticulous, vibrant, furthermore, moreover, additionally`
37
+
38
+ 한국어: `오늘날과 같은, 급변하는, 빠르게 변화하는, ~의 세계, 핵심적인 역할, 다양한 측면, 총체적, ~을(를) 통해 ~을(를) 도모, ~에 대한 깊은 이해, 가교 역할, 등불, 여정, 발돋움, 시너지, ~을 도모하고자`
39
+
40
+ ### 상투 구문 목록 (C2 — 0건이어야 함)
41
+
42
+ `In today's [digital] age / In the fast-paced world of / It's important to note / It's worth noting / I hope this message finds you well / Let's dive in / embark on a journey / game changer / 오늘날과 같이 급변하는 / 본 지원자는 / 귀사의 무궁한 발전을 / 지원하게 되어 영광입니다 / 위에서 언급한 바와 같이 / 결론적으로 말씀드리자면`
43
+
44
+ ### 과장어 사전 (C6)
45
+
46
+ `혁신적, 획기적, 탁월한, 압도적, 독보적, seamless, robust, cutting-edge, innovative, transformative, groundbreaking, world-class, game-changing, 최고의, 완벽한`
47
+
48
+ ### filler 패턴 (C10)
49
+
50
+ 문장 안에 (a) 숫자, (b) 고유명사, (c) 구체적 행위 동사+목적어 가운데 **하나도 없으면** 빈말 후보로 카운트. 예: "저는 이 일에 대한 열정이 누구보다 큽니다.", "최선을 다하겠습니다.", "많은 것을 배웠습니다."
51
+
52
+ ## 3. 불합격 시 처리(루프 연결)
53
+
54
+ 연결된 AI는 위 항목을 채점해 **불합격 항목 ID + 근거(센 수치/매칭된 문자열/위치)** 를 모은다. 그다음 항목별로 아래 수정 지시를 적용해 **재작성 → 동일 루브릭 재검증**한다. 재검증 통과까지 반복하되, **C8·C9(사실·고유명사 보존)는 단 1건이라도 위반 시 즉시 하드 실패**로 처리하고 다른 어떤 개선보다 우선해 복구한다.
55
+
56
+ - **C1·C2·C6 위반(슬롭/상투/과장)**: 매칭된 단어·구문을 나열하고, 각각을 그 사람의 실제 경험·맥락에서 나온 **구체 표현으로 1:1 치환**한다. 일반론을 사실로 바꾼다. 치환 후 밀도를 다시 센다.
57
+ - **C3·C4·C5 위반(단조로움)**: 긴 문장 1~2개를 쪼개 **짧은 강조 문장(≤8단어)** 을 삽입하고, 연속으로 같은 시작어를 쓴 문장의 도입부를 바꾼다. 3단 병렬을 1개로 줄이거나 문장 형태를 비대칭으로 푼다. 표준편차·시작어 분포를 재측정한다.
58
+ - **C7 위반(목소리 불일치)**: 다수 종결어미·인칭으로 **전체를 통일**한다. 격식이 튀는 구간을 주변 톤에 맞춰 다시 쓴다.
59
+ - **C10·C11 위반(빈말/근거 부족)**: 빈말 문장을 삭제하거나, 원본 데이터(`get_application_context`로 받은 경력·성과·스킬)에서 **수치·사례를 끌어와** 구체화한다. 없으면 지어내지 말고 사용자에게 해당 수치를 묻는다.
60
+ - **C8·C9 위반(사실/고유명사 손상)**: 원본에서 누락·왜곡된 숫자·고유명사를 **원문 그대로 복원**한다. 추정·대체 금지. 복원 후 집합 비교를 재수행해 100%를 확인한다.
61
+
62
+ 재검증 종료 조건: **C1~C7·C10·C11 전부 합격 + C8·C9 위반 0건**. 한 항목이라도 불합격이면 루프를 멈추지 않는다.
63
+
64
+ ## 4. 출처 (Provenance)
65
+
66
+ - **Perplexity & Burstiness (AI 텍스트 탐지 프레임워크)** — 사람 글은 문장 길이·구조의 변동성(burstiness)이 크고 AI 글은 균일하다는 1차적 구분 신호. C3·C4의 근거. · https://quillbot.com/blog/ai-writing-tools/burstiness-and-perplexity/ , https://www.tryleap.ai/learn/perplexity-vs-burstiness
67
+ - **Wikipedia: Signs of AI writing** — 셀 수 있는 AI 작성 신호 목록(과용 단어, rule of three, "not only X but also Y" 병렬, title-case, em-dash·곡선 따옴표). C1·C2·C5의 근거. · https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing
68
+ - **ChatGPT 과용 단어 목록 (Walter Writes AI / Alston Antony)** — `delve, leverage, tapestry, robust, seamless` 등 슬롭 사전과 상투 구문의 1차 근거. C1·C2·C6. · https://walterwrites.ai/most-common-chatgpt-words-to-avoid/ , https://alstonantony.com/seo-strategy/chatgpt-overused/
69
+ - **Harvard FAS Mignone Center for Career Success — Resume & Cover Letter Guide** — 의무가 아닌 성과, 측정 가능한 수치·구체 사례로 뒷받침하라는 권고. C10·C11의 근거. · https://careerservices.fas.harvard.edu/resources/harvard-college-guide-to-resumes-cover-letters/
70
+ - **STAR 방법 + 정량 성과 (Resume.io / Teal 클리셰 가이드)** — "excellent communication skills" 같은 빈말을 "20% 향상" 같은 수치로 대체. C10·C11·C2. · https://resume.io/blog/star-method-resume , https://www.tealhq.com/post/resume-buzzwords-and-cliches
71
+
72
+ ## 5. Phase B 힌트
73
+
74
+ - **MCP 도구 아이디어:**
75
+ - `get_verifier({ id: "human-voice" })` — 이 루브릭(검사 항목·사전·기준)을 구조화 JSON으로 연결 AI에 제공.
76
+ - `get_verifier_dictionaries({ lang })` — 슬롭/과장/상투 사전을 언어별로 분리 제공(한·영). 사전은 데이터로 두고 버전 관리해 신어 추가가 쉽게.
77
+ - (선택) `precount_human_voice({ text, original })` — LLM 없이 결정적으로 셀 수 있는 항목(C1, C3, C8, C9, C11)을 CareerMate가 로컬에서 미리 카운트해 후보 위반을 표시(연결 AI는 판단만). 단, 두뇌는 외부 AI라는 원칙상 "측정 보조"로만 한정.
78
+ - **데이터 형태 힌트:**
79
+ - 검사 항목: `{ id, label, pass_rule, measure, severity: "soft"|"hard" }`. C8·C9는 `severity:"hard"`.
80
+ - 사전: `{ lang, category: "slop"|"hype"|"cliche"|"filler", terms: string[], match: "substring"|"regex" }`.
81
+ - 결과 리포트: `{ outcome: "pass"|"fail", failed_checks: [{ id, observed, threshold, evidence: string[] }], loop: "rewrite"|"done" }` — 루프 오케스트레이션이 `failed_checks`만 보고 재작성 지시를 생성.
82
+ - 임계값(1.5/100단어, σ≥5 등)은 코드 상수가 아니라 설정값으로 두어 산출물 유형(이력서 vs 자소서)별 프로파일을 허용.
@@ -0,0 +1,72 @@
1
+ # 신선도·시의성 / Recency & Staleness — CareerMate 교차검증 루브릭 (Phase A)
2
+
3
+ > 이 검증기는 연결된 AI가 커리어 산출물(이력서·자소서·면접 답변·LinkedIn·분석 등)을 점검할 때 적용하는 **크리틱 루브릭**이다. CareerMate는 루브릭을 제공하고, 실제 검사는 연결된 AI가 수행한다(내부 LLM 없음).
4
+
5
+ ## 1. 목적
6
+
7
+ 이 검증기는 **모든 커리어 산출물이 "충분히 최신인 입력 위에서, 그 입력의 수집·기준 시점을 정직하게 드러내며" 만들어졌음**을 보장한다. 즉 적합도 분석은 *현재 버전*의 이력서·JD에 근거했고, 연봉 협상은 *아직 stale하지 않은* 시장 데이터에 근거했으며, 회사 리서치의 "최근 뉴스"가 *실제로 최근*이고, 모든 시점 민감 주장에 **as-of 날짜(기준일)** 가 붙어 있음을 검사한다. 이것은 데이터 품질의 표준 차원인 **timeliness(필요 시점 가용성)·currency(현실 반영 최신성)** 를 커리어 산출물에 적용한 게이트다(DAMA DDQ).
8
+
9
+ 이 검증기가 막으려는 5가지 실패 모드:
10
+
11
+ - **stale 시장데이터로 협상(stale benchmark):** 6개월·12개월 지난 연봉 벤치마크를 "현재 시세"처럼 인용해 협상 앵커를 잘못 잡음.
12
+ - **낡은 뉴스를 "최근"으로 표기(false recency):** 12~18개월 지난 사건을 "최근 출시/투자/조직개편"으로 면접에서 인용 → 준비 부족 노출.
13
+ - **버전 드리프트(version drift):** 이력서·JD가 바뀐 뒤에도 옛 버전에 근거한 적합도 분석·자소서를 그대로 사용(재분석 트리거 없음).
14
+ - **날짜 없는 시점주장(undated temporal claim):** "최근", "현재", "요즘"이라 하면서 기준일이 없어 검증·갱신 불가능.
15
+ - **만료 자격·기간 미갱신(expired credential / open-ended tenure):** 만료된 자격증·인증, "현재 재직 중"인데 종료된 포지션, 미래 졸업/이수 예정을 완료로 표기.
16
+
17
+ > 경계 원칙: **이 검증기는 "사실이 참인가"(진실성)가 아니라 "그 사실이 아직 유효·최신이고, 시점이 드러나 있는가"(시의성)만 본다.** 사실은 참이어도 6개월 전 데이터면 협상에서는 위험하고, "최근"이라는 라벨은 시점이 붙어야만 검증된다. 이는 진실성(`truthfulness.md`)·일관성(`consistency.md`)과 **독립 축**이다 — 그 둘은 "값이 저장 데이터와 일치/무모순한가"를 보지만, 이 검증기는 "그 값·입력이 *언제* 것이고 *지금도 유효한가*"라는 시간 축을 단독으로 본다.
18
+
19
+ 핵심 제약상 "6개월"의 적정성·뉴스의 실제 최신성 같은 의미 판단은 결정론으로 100% 셀 수 없다. 따라서 항목을 **(가) LLM 없이 날짜 비교·존재여부로 셀 수 있는 결정론 게이트**와 **(나) 연결된 AI가 라벨·맥락을 판정하는 항목**으로 나눠 설계하고, 각 항목에 어느 쪽인지 표시한다.
20
+
21
+ ## 2. 검사 항목
22
+
23
+ 표기: **[det]** = CareerMate가 LLM 없이 셀 수 있는 결정론 체크(날짜 차이·존재여부·정규식·비율). **[ai]** = 연결된 AI가 라벨·의미를 판정(개수로 환원해 보고). 기준일 `now`는 검증 실행 시각(예: 2026-06-17).
24
+
25
+ | ID | 검사 | 합격 기준 | 측정 방법(셀 수 있게; det/ai 구분) |
26
+ |----|------|-----------|------------------------|
27
+ | C1 **[det]** | **시장데이터 신선도(benchmark staleness)** — 연봉·보상 벤치마크의 `captured_at`이 임계(기본 6개월) 이내인가 | stale 벤치마크 사용 = **0** (또는 stale 표기 후에만 참고치로 사용) | 협상/오퍼평가에 쓰인 각 benchmark의 `captured_at`과 `now`의 개월차 계산 → `months_diff > 6` 건수 보고. `> 6`인데 산출물에 `stale` 라벨이 없으면 FAIL. `> 12`는 critical |
28
+ | C2 **[ai]** | **"최근 뉴스" 스테일 FAIL** — '최근'으로 라벨된 회사 리서치 fact의 출처 날짜가 6개월 이내인가 | 6개월 초과를 "최근"으로 표기 = **0** | `label=='recent'`인 fact의 `dated`(YYYY-MM)와 `now`의 개월차 계산 → `months_diff > 6` 건수 보고. AI는 산문 속 "최근/요즘/현재"가 어느 fact를 가리키는지 매핑. 12~18개월 초과는 `stale`로 강제 라벨 |
29
+ | C3 **[det]** | **분석 입력 버전 추적(version provenance)** — 적합도 분석·자소서가 근거한 이력서 버전 ID·JD 식별자가 기록되어 있는가 | `resumeVersionId`·`jobId`(또는 JD 해시) 둘 다 존재 | 산출물 메타에 `resumeVersionId != null && jobId != null` 여부. 하나라도 null이면 FAIL(재분석 트리거 불능) |
30
+ | C4 **[det]** | **버전 드리프트 감지(stale analysis)** — 분석이 근거한 이력서·JD 버전이 *현재* 저장 버전과 동일한가 | 드리프트 = **0** | 산출물의 `resumeVersionId`·JD 해시를 저장된 *최신* 버전 ID·해시와 비교 → 불일치 건수 보고. 불일치면 "근거 데이터가 바뀜 → 재분석 필요" 플래그 |
31
+ | C5 **[det]** | **시점주장 날짜 부착(dated temporal claims)** — 시점 민감 표현("최근/현재/요즘/올해/지난 분기/now")에 기준일(as-of)이 붙어 있는가 | 날짜 없는 시점주장 = **0** | 정규식 `(최근\|요즘\|현재\|올해\|작년\|지난\s?(분기\|해\|달)\|recent\|currently\|as of\|latest)`로 시점표현 토큰 수 `N_temporal`를 센다 → 같은 절(clause) 내 또는 출처 메타에 `YYYY`/`YYYY-MM` 동반 비율 계산 → 날짜 미동반 건수 보고 |
32
+ | C6 **[ai]** | **자격·인증 유효기간(credential expiry)** — 만료일이 있는 자격증·인증(예: AWS/PMP/어학성적 2년 유효)이 `now` 기준 유효한가 | 만료된 자격을 유효처럼 표기 = **0** | 자격 항목의 `expires_at`(또는 취득일+유효기간 규칙)을 `now`와 비교 → 만료 건수 보고. 만료된 항목은 "취득(만료)"로 표기하거나 갱신 안내. 만료일 미상은 `[유효기간 확인 필요]` 1건 |
33
+ | C7 **[det]** | **재직·진행 종료시점 정합(open-ended tenure)** — "현재 재직/진행 중(present/현재)"으로 표기된 항목이 실제로 진행 중인가, 미래 완료를 완료로 적지 않았는가 | 모순 = **0** | `현재\|present\|재직 중\|진행 중` 표기 항목 중 저장 타임라인에 `end_date`가 이미 있는 건수, 그리고 `start_date`나 졸업/이수 예정일이 `now`보다 미래인데 "완료/수료"로 표기된 건수 보고 |
34
+ | C8 **[ai]** | **시한성 정보 만료(time-bound facts)** — 직무·시장·기술 언급이 더는 유효하지 않은 옛 사실이 아닌가(폐기된 제품/지난 펀딩라운드를 "최신"으로, 단종 기술스택을 "현재 트렌드"로) | 만료된 시한 사실 = **0** | AI가 "최신/현재/지금 뜨는/state-of-the-art" 류 시한성 주장 토큰을 센 뒤, 각 주장에 출처 날짜가 있고 그 날짜가 임계 이내인지 판정 → 날짜 없거나 임계 초과 건수 보고 |
35
+ | C9 **[det]** | **신선도 커버리지/비율(freshness ratio)** — 시점 민감 주장 전체 중 (a)날짜가 붙고 (b)임계 이내인 비율 | freshness_ratio = **1.0** | `freshness_ratio = (N_temporal − N_undated − N_stale) / N_temporal`. C1·C2·C5·C8의 분자/분모를 합산해 단일 수치로 보고. 1.0 미만이면 불합격 |
36
+
37
+ > critical 등급: **C1(>12개월 벤치마크)·C2(12~18개월+ 뉴스를 "최근")·C4(버전 드리프트)** 는 발견 시 해당 산출물을 즉시 불합격 처리(낡은 입력으로 한 협상·리서치·분석 차단). C3은 게이트(없으면 추적 불능 → 불합격). C5·C6·C7·C8은 건수 누적으로 판단하되 0을 목표로 하고, C9는 게이트(1.0이어야 합격). 임계 개월수(6/12/18)는 직무·시장에 따라 조정 가능한 파라미터로 둔다(빠르게 변하는 시장은 더 짧게).
38
+
39
+ ## 3. 불합격 시 처리(루프 연결)
40
+
41
+ 연결된 AI는 아래 순서로 **수정 → 재검증**한다. 재검증은 동일 루브릭 전 항목을 다시 돌려 **C1·C2·C4 critical = 0 이고 C3 존재 = true 이며 C9 freshness_ratio = 1.0** 일 때만 통과로 본다.
42
+
43
+ 1. **분류 먼저.** 각 불합격 항목을 (a) stale 입력 (b) false recency 라벨 (c) 버전 드리프트 (d) 날짜 누락 (e) 만료 자격/기간 중 하나로 라벨링한다.
44
+ 2. **(a) stale 입력 → 재수집 또는 강등.** stale한 연봉 벤치마크(C1)는 사용자가 **새로 수집**해 `save_compensation_benchmark`로 갱신한 뒤 재분석한다. 즉시 재수집이 불가하면 그 수치를 협상 앵커에서 빼고 "기준일 YYYY-MM, 신선도 주의(참고치)"로 강등 표기한다 — 낡은 수치를 현재 시세로 단정하지 않는다.
45
+ 3. **(b) false recency → 라벨 정정 또는 교체.** 6개월 초과 뉴스(C2)는 "최근" 라벨을 떼고 날짜를 명시(`2025-08 발표`)하거나, 라이브로 더 최신 뉴스를 찾아 교체한다. 12~18개월 초과는 `stale`로 표기해 면접 인용에서 제외한다.
46
+ 4. **(c) 버전 드리프트 → 재분석 트리거.** 분석의 `resumeVersionId`·JD 해시가 최신과 다르면(C4), 최신 버전으로 적합도 분석·자소서를 **다시 생성**한다. 사용자에게 "이력서/공고가 바뀌어 이전 분석이 낡았다 → 다시 분석한다"를 쉬운 한국어로 안내한다.
47
+ 5. **(d) 날짜 누락 → as-of 부착.** 날짜 없는 "최근/현재"(C5)에는 출처 기준일(`YYYY-MM`)을 붙인다. 기준일을 모르면 그 시점 주장을 정성 표현으로 일반화하거나 `[기준일 확인 필요]`로 표시한다(추측 날짜 금지 — 진실성 검증기와 연동).
48
+ 6. **(e) 만료 자격/기간 → 갱신 또는 표기.** 만료된 자격증(C6)은 "취득(만료, 갱신 필요)"로 표기하거나 사용자에게 갱신 여부를 묻는다. 종료된 포지션의 "현재 재직 중"(C7)은 실제 `end_date`로 정정하고, 미래 졸업/이수 예정은 "YYYY-MM 졸업예정"으로 표기한다.
49
+ 7. **재검증 게이트.** 수정본을 다시 C1~C9에 통과시킨다. critical(C1>12mo·C2 stale·C4 드리프트) 중 하나라도 남으면 루프를 반복한다. 시장 데이터 재수집이 사용자 액션을 요구하면(2회 시도 후에도 미해결), 해당 보상 수치를 산출물에서 보류하고 사용자에게 수집을 요청한다(낡은 값으로 무한 진행 금지).
50
+ 8. **저장.** 통과한 산출물만 저장 도구(`save_fit_analysis`·`save_cover_letter_version`·`save_interview_prep`·`save_compensation_benchmark` 등)로 보관한다. 검증 결과 요약(`freshness_ratio=1.0`, `stale=0`, `as_of=YYYY-MM`, 근거 버전 ID)을 산출물 메타에 함께 남겨 다음 재분석 트리거의 기준으로 삼는다.
51
+
52
+ ## 4. 출처 (Provenance)
53
+
54
+ - **DAMA DDQ — Dimensions of Data Quality (timeliness / currency)** (1차/표준, 신뢰 높음) — 데이터 품질의 표준 차원으로 **timeliness**(필요 시점에 가용)와 **currency**(현실을 얼마나 최신으로 반영) 정의. 이 검증기를 "커리어 데이터의 timeliness·currency 게이트"로 정초하는 1차 근거. · DAMA-NL, "Dimensions of Data Quality (DDQ) Research Paper v1.2" https://www.dama-nl.org/wp-content/uploads/2020/09/DDQ-Dimensions-of-Data-Quality-Research-Paper-version-1.2-d.d.-3-Sept-2020.pdf
55
+ - **timeliness vs freshness vs currency 구분** (2차/벤더, 보강) — "timeliness=필요 시점 가용, freshness=사용 순간의 최신성, currency=현실과의 최신 일치(6개월 전 이사면 currency 결여)"라는 실무 정의. C5(as-of)·C1·C2 설계의 보강 근거. · Soda, "Data Quality Dimensions: The No-BS Guide" https://soda.io/blog/guide-to-data-quality-dimensions
56
+ - **연봉 시장데이터의 stale 위험·갱신 주기** (2차/벤더, 정성적 방향성) — 보상 밴드가 통상 1년 이상 묵은 데이터로 구축되고, 시장이 빠르게 움직여 stale data가 임금 격차·salary inversion을 낳음. 제공자는 monthly/quarterly, 최신은 weekly/daily 갱신 — **데이터 recency를 반드시 검증하라**는 실무 권고. C1(6개월 임계·stale 플래그)의 직접 근거. · JobsPikr, "Pay Equity Analytics: Stop Using Outdated Salary Data" https://www.jobspikr.com/blog/pay-equity-analytics-compensation-benchmarking/ · Ravio, "How to use market pricing for compensation" https://ravio.com/blog/market-pricing-compensation
57
+ - **연봉 협상 데이터 시의성** (2차/업계, 보조) — 의사 등 빠르게 변하는 직무 시장은 연 1회 서베이로는 부족, 협상엔 더 current한 자료가 필요. C1의 시장별 임계 조정(빠른 시장=짧은 임계) 근거. · American Medical Association, "Must-have dataset for physician salary negotiations" https://www.ama-assn.org/medical-residents/transition-resident-attending/check-out-must-have-dataset-physician-salary
58
+ - **회사 리서치의 "최근 뉴스" 시의성** (대학 커리어센터/2차, 신뢰 높음) — 구체적·최근 동향 언급은 면접 최강 신뢰 신호이며, 낡은 "최근"은 준비 부족을 드러냄. C2의 직접 근거. · UF Career Connections Center, "Four Steps for Researching an Employer" https://career.ufl.edu/four-steps-for-researching-an-employer/ · Ohio State ECS, "How to Research a Company for an Interview" https://www.ecs.osu.edu/news/2025/02/how-research-company-interview
59
+ - **CareerMate 내부 앵커(1차)** — 신선도 우려를 흩어진 채로 두지 않고 횡단 검증기로 승격한 근거: `salary-negotiation.md`(`evaluate_offer`의 `captured_at` 6개월 초과 시 **stale 플래그**·통화/지역 mismatch 규칙) → C1; `company-research.md` **R3**(최근뉴스 `dated` 6개월 이내, 12~18개월 초과 stale, 날짜 날조 금지) → C2·C5; `fit-matching.md` **R10**(분석 근거 `resumeVersionId`·`jobId` 기록으로 재분석 트리거) → C3·C4. · CareerMate `docs/career-os/knowledge/{salary-negotiation,company-research,fit-matching}.md`; `AGENTS.md`(추측 금지·확인 우선)
60
+
61
+ > 로케일 분기: **글로벌(테크)** 은 levels.fyi류 데이터에 `갱신 YYYY-Qn`이 붙어 신선도 추적이 쉽고 시장이 빠르므로 임계를 더 짧게(예: 3~6개월) 둘 수 있다. **한국**은 공채·자사양식 중심에 LinkedIn 침투가 낮아 외부 실시간 신호가 희소하고, 보상 참고치는 잡플래닛 연봉 제보·DART 사업보고서가 주된데 **DART는 연 1회 공시(전사 평균)** 라 본질적으로 최대 ~12개월 묵을 수 있다 → 한국 보상 데이터는 "수집일+공시 회계연도"를 함께 표기하고 6개월 임계를 *경고*로, 12개월을 *critical*로 적용한다(공시 주기 한계를 인정하되 stale를 숨기지 않음).
62
+
63
+ ## 5. Phase B 힌트
64
+
65
+ - **MCP 도구 아이디어:**
66
+ - `get_verifier({ id: "recency-staleness" })` — 이 루브릭(JSON: 항목·합격기준·det/ai 플래그·임계 개월수 파라미터·정규식 패턴·critical 등급)을 연결된 AI에게 그대로 serve.
67
+ - `check_staleness({ artifact_id, now })` — det 항목을 CareerMate가 LLM 없이 계산: 저장된 benchmark `captured_at`, fact `dated`, `resumeVersionId`/JD 해시 vs 최신 버전을 `now`와 비교해 `{ stale_benchmarks[], stale_news[], version_drift: bool, undated_temporal_count, freshness_ratio }` 반환. 시점표현 정규식 카운트(C5)도 여기서 수행.
68
+ - `get_freshness_anchors({ scope })` — 신선도 비교용 기준 집합 제공: `{ latest_resume_version_id, latest_jd_hashes[], benchmarks: [{..., captured_at}], credentials: [{name, expires_at}], timeline: [{role, start, end|null}], now }`. C1·C3·C4·C6·C7의 비교 대상으로 직접 사용.
69
+ - **데이터 형태 힌트:**
70
+ - 모든 시점 민감 값에 `as_of`(YYYY-MM 또는 YYYY-Qn)와 `source_dated`를 1급 필드로 강제. benchmark는 `captured_at`(필수)+`region`+`currency`로 평탄화해 신선도/통화/지역을 한 번에 규칙 검증.
71
+ - 산출물 메타에 `freshness: { ratio: 1.0, stale_count: 0, version: { resumeVersionId, jobId|jd_hash }, checked_at, as_of }`를 저장해 (a) `applied`/협상 단계 전환 전 통과 필수 게이팅, (b) 대시보드의 "근거 데이터가 낡음 → 재분석" 배지 표시, (c) 저장된 이력서·JD가 갱신되면 의존 산출물의 `version` 불일치를 감지해 **자동 재분석 큐**에 올리는 트리거로 활용.
72
+ - 임계 개월수(news 6/12·benchmark 6/12)는 시장·로케일별 파라미터 테이블(`thresholds: { news_recent: 6, news_stale: 18, benchmark_warn: 6, benchmark_critical: 12 }`)로 외부화해 빠른 시장은 더 짧게 조정.
@@ -0,0 +1,82 @@
1
+ # 질문-응답 적합성 / Responsiveness & On-target — CareerMate 교차검증 루브릭 (Phase A)
2
+
3
+ > 이 검증기는 연결된 AI가 커리어 산출물(이력서·자소서·면접 답변·LinkedIn·분석 등)을 점검할 때 적용하는 **크리틱 루브릭**이다. CareerMate는 루브릭을 제공하고, 실제 검사는 연결된 AI가 수행한다(내부 LLM 없음).
4
+
5
+ ## 1. 목적
6
+
7
+ 이 검증기는 산출물이 **던져진 질문·요건에 실제로 응답했는가**를 보장한다. 자소서 문항·면접 질문·JD(job description) 요건은 명시적인 "질문"이다. 글이 아무리 매끄럽고(human-voice) 사실에 충실하며(truthfulness) 산출물 간 모순이 없어도(consistency), **묻지 않은 것을 답하면(동문서답) 즉시 탈락**한다. `human-writing` 지침이 경고한 "슬롭 0이어도 동문서답이면 탈락"을 잡는 **마지막 방어선**이다.
8
+
9
+ 구체적으로 다음 네 가지 실패 모드를 막는다.
10
+
11
+ - **동문서답(off-target):** 문항이 "지원동기"를 물었는데 성장과정을 쓰거나, 면접관이 "갈등 해결 경험"을 물었는데 일반론으로 답함.
12
+ - **부분 응답(partial coverage):** 한 문항에 여러 하위 질문(예: "경험과 그로부터 배운 점, 그리고 입사 후 계획")이 있는데 일부만 답함.
13
+ - **형식 미충족(format miss):** 문항이 요구한 응답 형식(경험 서술 / 이유 / 향후 계획 / 글자 수)을 충족하지 못함.
14
+ - **요건 미커버(JD gap):** JD가 명시한 필수 요건·키워드가 이력서·자소서에서 한 번도 다뤄지지 않음.
15
+
16
+ > **독립 축 선언:** 이 검증기는 truthfulness(사실이 데이터로 추적되는가)·consistency(산출물 간 모순이 없는가)·ats-compat(기계가 파싱하는가)·human-voice(사람 목소리인가)와 **겹치지 않는 별개의 축**이다. 그 넷은 모두 "답이 좋은가/참인가/일관되는가"를 보지만, 이 검증기만이 **"그게 던져진 질문에 대한 답이긴 한가"(응답성)** 를 본다. 좋은 글이 잘못된 질문에 답하는 경우를 다른 어떤 검증기도 잡지 못한다.
17
+
18
+ 핵심: 검사 항목은 가능한 한 **LLM 없이 셀 수 있게**(키워드 교집합 개수·요구 행동 커버리지 비율·형식 토큰 존재 여부·글자 수 비교) 설계했고, 의미 판단이 필요한 항목은 `ai`로 정직하게 표시한다.
19
+
20
+ ## 2. 검사 항목
21
+
22
+ > 적용 단위: **(질문, 응답) 쌍** 1개. 질문 = 자소서 문항 1개 / 면접 질문 1개 / JD 요건 블록 1개. 응답 = 그에 대응하는 답변 텍스트.
23
+ > 측정 보조 개념:
24
+ > - **질문 키워드(Q-keyword):** 질문에서 추출한 핵심어(불용어 제거 후 명사·동사·고유명사). 한국어는 조사 분리.
25
+ > - **요구 행동 동사(directive verb):** 질문이 요구하는 응답 행위. 예: "설명하라/서술하라"(경험), "이유는"(근거), "어떻게 하겠는가/계획은"(계획), "배운 점"(교훈).
26
+ > - **det** = LLM 없이 결정론으로 셀 수 있음 · **ai** = 연결된 AI의 의미 판정 필요. 등급 **D**(결정론 게이트) / **H**(휴먼/AI 판정).
27
+
28
+ | ID | 검사 | 합격 기준 | 측정 방법(셀 수 있게; det/ai 구분) |
29
+ |----|------|-----------|------------------------|
30
+ | C1 | 질문↔응답 매핑 존재 | 모든 질문에 **대응 응답 블록이 1:1 존재**. 미응답 문항 0개 | det/D · 입력된 질문 개수 = 응답 블록 개수인지 비교. 매핑 안 된 질문 ID 개수 = 0 |
31
+ | C2 | 요구 행동 커버리지 | 질문이 요구한 directive 행동을 **모두** 충족(예: "경험+배운점+계획" 3요소면 3개 모두). 충족률 = 100% | det/D · 질문에서 directive 동사 목록 추출 → 응답에서 각 요소 대응 문장 존재 여부 태깅 → (충족 요소 수 ÷ 요구 요소 수). 1.0 미만이면 미충족 요소 나열 |
32
+ | C3 | 질문 키워드 교집합 | 응답이 Q-keyword 상위 N개(기본 5) 중 **≥ 60%** 를 본문에서 다룸(동의어 허용) | det/D(정확 일치) + ai/H(동의어·환언 판정) · 교집합 토큰 수 ÷ N. 미달 시 누락 키워드 목록 출력 |
33
+ | C4 | 첫 문장 온타깃 | 응답 **첫 1~2문장**이 질문 주제어를 직접 언급(두괄식·결론 우선). 서론만 길고 본론 없는 "워밍업" 금지 | det/D · 첫 1~2문장에 Q-keyword가 1개 이상 등장하는가(존재 여부) |
34
+ | C5 | 형식 토큰 충족 | 문항이 명시한 형식 요소가 모두 존재. 예: STAR 면접이면 Situation·Task·Action·Result 4요소, "구체적 사례" 요구면 사례 1건+ | det/D · 형식 체크리스트별 대응 토큰/구간 존재 카운트 → 누락 요소 수 = 0 |
35
+ | C6 | 글자 수/분량 준수 | 자소서 문항의 **글자 수 상·하한**을 지킴(한국 공채 자사양식의 핵심 탈락 사유). 범위 내 | det/D · 응답 길이(공백 포함/제외 둘 다) vs 문항 제한. 초과·미달 글자 수 출력 |
36
+ | C7 | 질문 직접 응답성 | 응답이 질문에 **직접** 답하고 우회·일반론으로 빠지지 않음(off-target/회피 0건) | ai/H · 연결 AI가 "이 응답이 이 질문에 답하는가"를 Y/N 판정. N이면 빗나간 지점 명시 |
37
+ | C8 | JD 필수 요건 커버리지 | JD의 **필수(required) 요건** 항목 중 산출물(이력서+자소서)에서 다뤄진 비율 **≥ 80%**, 필수 요건 미커버 **0개** | det/D · JD에서 필수/우대 요건 분리 추출 → 각 요건 키워드가 산출물에 존재하는지 매칭 → (커버 요건 ÷ 전체 필수). 미커버 필수 요건 나열 |
38
+ | C9 | JD 우대 요건 커버리지 | **우대(preferred)** 요건 커버 비율 보고(차단 아님, 경고). 권장 ≥ 50% | det/D · C8과 동일 로직을 우대 요건에 적용. 비율만 리포트, 미달은 warning |
39
+ | C10 | 과잉/딴소리 비율 | 응답 중 질문과 무관한 문장(Q-keyword·directive 어느 것과도 무관) 비율 **< 20%** | det/D + ai/H · 각 문장을 질문 관련/무관으로 태깅(키워드 매칭=det, 환언 무관 판정=ai) → 무관 문장 비율 |
40
+ | C11 | 질문 전제·제약 준수 | 질문이 건 제약(예: "**팀** 경험", "**실패** 사례", "최근 3년 내")을 응답이 위반하지 않음 | ai/H · 질문의 한정어(팀/개인, 성공/실패, 기간) 추출 → 응답 사례가 그 한정과 일치하는지 판정. 위반 사례 개수 = 0 |
41
+
42
+ ### Locale 분기 (한국 vs 글로벌)
43
+
44
+ - **한국(공채·자사양식):** C5·C6이 1차 게이트. 자사양식 문항은 **글자 수 제한이 엄격**하고(C6), "성장과정/지원동기/직무역량/입사 후 포부" 같은 **정형 문항 유형**이 directive를 규정한다(C2). 자소서에 별도 문항이 없고 "자유 기술"이면 C1·C2는 **인재상·직무기술서 키워드**를 질문 대용으로 사용. 포괄임금·공채 특성상 JD가 느슨할 수 있어 C8은 **채용공고+직무기술서+인재상**을 합쳐 요건 풀로 만든다.
45
+ - **글로벌(JD 중심·LinkedIn):** C8·C9가 1차 게이트. JD의 required/preferred 분리와 키워드 커버리지(ATS 정합)가 핵심. 면접은 STAR(C5) 중심. cover letter는 보통 단일 자유 양식이라 C6 대신 **JD 요건 응답성(C8)** 이 주 축.
46
+
47
+ ## 3. 불합격 시 처리(루프 연결)
48
+
49
+ 연결된 AI는 위 항목을 채점해 **불합격 항목 ID + 근거(미응답 문항 ID·미충족 directive·누락 키워드·초과 글자 수·미커버 요건 목록)** 를 모은다. 그다음 항목별로 아래 수정 지시를 적용해 **보강 재작성 → 동일 루브릭 재검증**한다. 통과까지 반복.
50
+
51
+ - **C1 미응답 문항(critical):** 빠진 문항에 대응하는 답변 블록을 **새로 작성**한다. 데이터가 없으면 지어내지 말고 `get_application_context`/사용자 확인으로 재료를 모은 뒤 작성. 재검증.
52
+ - **C2 directive 미충족(critical):** 누락된 요구 요소(예: "배운 점", "입사 후 계획")를 응답에 **명시적으로 추가**한다. 한국 정형 문항은 해당 문항 유형의 필수 요소표를 적용. 재측정.
53
+ - **C3·C4 온타깃 부족(critical):** 두괄식으로 **첫 문장을 질문 핵심어로 다시 쓰고**, 누락 Q-keyword를 본문에 자연스럽게 반영(키워드 스터핑 금지, 실제 경험 맥락으로). human-voice·truthfulness와 충돌하지 않게 사실 보존. 재측정.
54
+ - **C5 형식 미충족(critical):** 누락 형식 요소를 채운다. STAR면 빠진 S/T/A/R 단계를 보강, "구체적 사례" 요구면 실제 사례 1건을 데이터에서 끌어와 삽입. 없으면 사용자에게 사례를 묻는다. 재검증.
55
+ - **C6 글자 수 위반(critical, 한국):** 초과면 빈말·중복부터 **삭제 우선**(human-voice의 filler 제거와 연계), 미달이면 구체 수치·사례로 **증거 보강**. truthfulness를 깨지 않는 선에서. 범위 안에 들 때까지 루프.
56
+ - **C7·C10·C11 위반(critical):** 질문과 무관한 구간을 **삭제하거나 질문에 맞게 재초점화**한다. C11 전제 위반(예: 성공 사례를 물었는데 실패담)은 **올바른 전제의 사례로 교체**. 재판정.
57
+ - **C8 필수 요건 미커버(critical):** 미커버 필수 요건 각각에 대해 (a) 사용자에게 해당 경험이 있는지 확인 → 있으면 산출물에 반영, (b) 없으면 인접 역량으로 보완하고 갭을 사용자에게 정직히 고지(없는 경험 날조 금지 — truthfulness 게이트 준수). 재검증.
58
+ - **C9 우대 요건(warning):** 차단하지 않고 커버율을 사용자에게 안내. 보유 우대 요건이 있는데 누락됐으면 추가 권유.
59
+ - **종료 조건:** **C1·C2·C5·C7·C8·C11 = 합격(critical 0건)** + C3·C4·C6 합격 + C9·C10은 사용자 확인 완료. 한 critical이라도 불합격이면 루프를 멈추지 않는다. 통과 시 `save_cover_letter_version`/`save_fit_analysis`/`save_interview_prep` 등 다음 단계로 진행하고 대시보드(`http://127.0.0.1:4319`) 링크를 안내한다.
60
+
61
+ ## 4. 출처 (Provenance)
62
+
63
+ - **STAR 프레임워크(Situation·Task·Action·Result) — 면접 응답 형식 요건(C5)** — (1차/대학 커리어센터) UVA Career Center · https://career.virginia.edu/Students/Launch/Interviews/STAR ; Northwestern Career Advancement · https://www.northwestern.edu/careers/jobs-internships/interviewing/the-star-approach.html
64
+ - **면접 비응답(off-target)·우회·횡설수설이 부정적 인상과 탈락으로 직결 — 직접 응답성(C7·C10)** — (1차/정부·표준) U.S. DOL/ILAB Enumerator Training: Interviewing Techniques(질문에 직접·간결 응답, 드리프트 시 probing으로 복귀) · https://www.dol.gov/sites/dolgov/files/ILAB/Section%202%20-%20ILAB%20Enumerator%20Training%20Manual%20-%20Interviewing%20Techniques.pdf
65
+ - **응답이 질문과 불일치하는 현상의 측정 가능성(2~4% 반복 비응답) — 응답성을 독립 축으로 계량 가능 근거** — (1차/연구기관) Pew Research Center, "Answers that did not match the question" · https://www.pewresearch.org/methods/2020/02/18/answers-that-did-not-match-the-question-were-concentrated-in-opt-in-polls/
66
+ - **JD 키워드 커버리지·매치율(필수/우대 분리, 섹션 가중, 60~85% 타깃) — JD 요건 응답성(C8·C9)** — (2차/벤더) Jobscan Resume Scanner · https://www.jobscan.co/resume-scanner ; Scale.jobs, "Match Resume Keywords to Job Descriptions" · https://scale.jobs/blog/how-to-match-resume-keywords-to-job-descriptions
67
+ - **한국 자소서 정형 문항 유형(지원동기·성장과정·직무역량·입사 후 포부)과 문항별 요구 요소·평가표 — directive 커버리지(C2)·locale 분기** — (2차/공공·취업지원) 광주광역시 서구 일자리센터, "자기소개서의 이해 및 문항유형" · https://www.seogu.gwangju.kr/menu.es?mid=b70502010200 ; HAIJOB, "자소서 항목별 작성법" · https://www.haijob.co.kr/blog/how-to-prepare-for-each-item-of-cover-letter-motivation-for-application-growth-process-job-competency-aspiration-after-joining-the-company-etc/
68
+ - **한국 자사양식 자소서 글자 수 제한 준수 — 분량 게이트(C6)** — (2차/취업지원) 인크루트, "자소서 직무역량 전략적 작성 TIP" · https://news.incruit.com/news/newsview.asp?newsno=436828
69
+
70
+ ## 5. Phase B 힌트
71
+
72
+ - **MCP 도구 아이디어:**
73
+ - `get_verifier({ id: "responsiveness-on-target" })` — 이 루브릭(검사 항목·directive 맵·locale 분기)을 구조화 JSON으로 연결 AI에 제공.
74
+ - `extract_questions({ source })` — 자소서 문항/면접 질문/JD 텍스트에서 질문 단위와 directive·형식·글자 수 제약·전제를 구조화해 추출(추출은 연결 AI, 스키마는 CareerMate가 제공).
75
+ - `precount_responsiveness({ qa_pairs, jd })` — LLM 없이 결정론으로 셀 수 있는 항목(C1 매핑, C2 directive 토큰, C3 키워드 교집합, C4 첫 문장, C5 형식 토큰, C6 글자 수, C8/C9 요건 커버리지)을 로컬에서 미리 카운트해 후보 위반 표시. 의미 판정(C7·C10·C11)은 연결 AI 몫. 두뇌는 외부 AI라는 원칙상 "측정 보조"로만 한정.
76
+ - `get_kr_question_schema({ question_type })` — "지원동기/성장과정/직무역량/입사 후 포부" 등 한국 정형 문항별 필수 요소표(directive 체크리스트)를 데이터로 제공·버전 관리.
77
+ - **데이터 형태 힌트:**
78
+ - 질문: `{ id, source: "cover_letter"|"interview"|"jd", text, directives: ["experience"|"reason"|"plan"|"lesson"], format: ["star"|"example"|...], char_limit: { min, max } | null, constraints: ["team"|"failure"|"recent_3y"], keywords: string[] }`.
79
+ - JD 요건: `{ requirements: [{ text, level: "required"|"preferred", keywords: string[] }] }`.
80
+ - 응답: `{ question_id, text }` (질문과 1:1 매핑).
81
+ - 결과 리포트: `{ outcome: "pass"|"fail", failed_checks: [{ id, observed, threshold, evidence: { unanswered_qids?, missing_directives?, missing_keywords?, char_overflow?, uncovered_requirements? } }], loop: "rewrite"|"gather_data"|"done" }` — 루프 오케스트레이션이 `failed_checks`만 보고 보강 지시를 생성.
82
+ - 임계값(키워드 60%, JD 필수 80%·우대 50%, 딴소리 <20%, 글자 수 등)은 코드 상수가 아니라 설정값으로 두어 산출물 유형·locale(KR/global)별 프로파일을 허용.
@@ -0,0 +1,67 @@
1
+ # 진실성 / 날조 방지 (Anti-fabrication) — CareerMate 교차검증 루브릭 (Phase A)
2
+
3
+ > 이 검증기는 연결된 AI가 커리어 산출물(이력서·자소서·면접 답변·LinkedIn 등)을 점검할 때 적용하는 **크리틱 루브릭**이다. CareerMate는 루브릭을 제공하고, 실제 검사는 연결된 AI가 수행한다(내부 LLM 없음).
4
+
5
+ ## 1. 목적
6
+
7
+ 이 검증기는 **모든 커리어 산출물의 모든 사실 주장이 사용자의 저장된 데이터로 추적 가능함**을 보장한다. 즉, 산출물 안의 숫자·직함·회사·기간·학력·자격증·기술 숙련도·고유명사가 (a) 저장된 프로필/경력/이력서/자소서 데이터에 직접 존재하거나, (b) 그 저장 데이터에서 **명시적 산술 파생**(차·합·비율 환산)으로 나온 것이어야 한다는 뜻이다. 그 밖의 모든 사실 주장은 날조(fabrication) 또는 미검증(unverified)으로 분류해 차단하거나 `[확인 필요]`로 표시한다.
8
+
9
+ 이 검증기가 막으려는 4가지 실패 모드:
10
+ - **날조(fabrication):** 없는 사실을 만들어냄(존재하지 않은 회사·직함·자격증, 지어낸 수치).
11
+ - **과장(inflation):** 있는 사실을 부풀림(직함 격상, 팀 성과를 개인 성과로 귀속, "참여"를 "주도"로).
12
+ - **미검증 정밀도(false precision):** 저장 데이터에 근거 없는 구체 수치("매출 37% 증가")를 생성.
13
+ - **갭 은폐(gap concealment):** 모르는 것을 모른다고 하지 않고 빈칸을 그럴듯하게 메움.
14
+
15
+ > 경계 원칙: **표현을 강하게 다듬는 것(action verb·두괄식·정량화)은 허용, 사실을 바꾸는 것은 금지.** 강한 동사로 쓰되 동사가 가리키는 행위·결과는 저장 데이터와 일치해야 한다. 이 검증기는 사람처럼 쓰기(`human-writing.md`)의 "Fact lock(R7)" 신호를 **전 산출물·전 사실유형으로 확장한 전용 게이트**다.
16
+
17
+ 핵심 제약상 한국어 NER·실세계 사실확인은 결정론으로 100% 셀 수 없다. 따라서 항목을 **(가) LLM 없이 셀 수 있는 결정론 게이트**와 **(나) 연결된 AI가 수행하는 대조 판정**으로 나눠 설계하고, 각 항목에 어느 쪽인지 표시한다.
18
+
19
+ ## 2. 검사 항목
20
+
21
+ 표기: **[det]** = CareerMate가 LLM 없이 셀 수 있는 결정론 체크(개수·존재·정규식·비율). **[ai]** = 연결된 AI가 저장 데이터와 대조해 판정(개수로 환원해 보고).
22
+
23
+ | ID | 검사 | 합격 기준 | 측정 방법(셀 수 있게) |
24
+ |----|------|-----------|------------------------|
25
+ | C1 **[ai]** | **수치 앵커링** — 산출물의 모든 정량 주장(숫자+단위/%, 금액, 인원, 기간)이 저장 데이터에 존재하거나 거기서 산술 파생되는가 | 미앵커 수치 = **0** | 정규식 `\d[\d,.]*\s*(%|억|만|원|명|건|배|개월|년|시간|ms|x|배)` 등으로 산출물의 수치 토큰 수 `N_total`를 센다 → AI가 각 토큰을 저장값 또는 파생식에 매핑 → 매핑 실패 수 `N_unanchored` 보고. 합격 = `N_unanchored == 0` |
26
+ | C2 **[ai]** | **고유명사 화이트리스트** — 회사명·기관·제품·기술스택·인명이 저장 데이터(프로필/경력/이력서/스킬)에 존재하는가 | 화이트리스트 밖 고유명사 = **0** | 저장 데이터에서 고유명사 집합 `W`를 추출(set) → 산출물의 고유명사 후보를 `W`와 대조 → `W`에 없는 항목 수 보고. 일반어·직무 일반명사는 제외 |
27
+ | C3 **[ai]** | **직함·고용형태 일치** — 진술된 직함/고용형태(정규직·인턴·계약·프리랜서·동아리)가 저장 직함과 동일하거나 더 낮은 책임 수준인가 | 격상된 직함 = **0** | 저장 직함 문자열과 산출물 직함을 1:1 대조 → 불일치 건수, 그중 "격상"(예: 인턴→주니어, 팀원→팀장, 계약→정규) 건수 보고. 합격 = 격상 0 |
28
+ | C4 **[ai]** | **귀속(attribution) 정확성** — 팀 성과를 1인칭 단독 성과로 바꾸지 않았는가 | 부당 단독귀속 = **0** | "주도/총괄/혼자/단독/led/owned" 류 1인칭 단독 동사 출현 수를 센 뒤, 각각에 대해 저장 데이터가 단독 역할을 뒷받침하는지 AI가 판정 → 미뒷받침 건수 보고 |
29
+ | C5 **[ai]** | **학력·자격·점수 정확성** — 학위/전공/졸업여부/자격증/어학·시험 점수가 저장값과 정확히 일치하는가 | 불일치 = **0** | 저장된 학력·자격·점수 필드와 산출물 토큰을 정확 일치(점수는 값까지) 대조 → 불일치 건수 보고. "더 유명한 학교/상위 학위/미취득을 취득"으로의 변경은 critical |
30
+ | C6 **[ai]** | **숙련도 정직성** — 기술 숙련 표현(전문가/마스터/능숙/숙련/expert)이 저장 근거(연차·프로젝트·산출물)와 모순되지 않는가 | 근거 없는 최상급 = **0** | "전문가/마스터/완벽/모든/항상/expert/mastery" 류 최상급·절대표현 출현 수를 센 뒤, 저장 근거 유무를 AI가 판정 → 근거 없는 건수 보고 |
31
+ | C7 **[det]** | **갭 표식 보존** — AI가 만든 `[확인 필요]`/플레이스홀더가 최종본에 임의의 그럴듯한 값으로 치환되지 않았는가 | 빈칸을 지어낸 값으로 채운 흔적 = **0** | 직전 초안의 `[확인 필요]`·`{{ }}`·`___` 개수 `P_before`와 최종본 개수 `P_after` 비교. `P_after < P_before`이면 그 차이만큼 AI가 "저장 근거로 채움 vs 지어냄"을 보고. 미설명 감소 = 불합격 |
32
+ | C8 **[ai]** | **파생연산 검증** — 산출물의 비율/증감 환산(예: "월12건→월2건 = 83% 감소")이 저장 입력값으로 실제 계산되는가 | 계산 오류·근거 없는 파생 = **0** | 파생 표현마다 `(입력값, 식, 결과)` 3요소를 추출 → 산술 재계산이 일치하는 건수/불일치 건수 보고 |
33
+ | C9 **[ai]** | **시간선 정합성** — 날짜·기간·근속이 저장 경력 타임라인과 모순되지 않는가(미래일자·중복재직·음수기간 없음) | 시간선 모순 = **0** | 산출물의 날짜·기간 토큰을 저장 타임라인과 대조 → 중복재직/미래시작/기간>실재직 같은 모순 건수 보고 |
34
+ | C10 **[det]** | **추적성 비율(traceability)** — 검증 대상이 된 전체 사실 주장 중 저장 데이터로 매핑된 비율 | 추적성 = **100%** (= 미앵커 0) | `traceability = (N_total_claims − N_unanchored − N_unverified) / N_total_claims`. C1·C2·C5의 합산 분모로 계산해 단일 수치로 보고. 1.0 미만이면 불합격 |
35
+
36
+ > critical 등급: C1·C2·C3·C5(없는 사실/격상/학력 위조)는 **단 1건이라도** 발견 시 전체 산출물을 불합격 처리한다. C4·C6·C8·C9는 건수 누적으로 판단하되 0을 목표로 한다. C7·C10은 게이트(전부 통과해야 합격).
37
+
38
+ ## 3. 불합격 시 처리(루프 연결)
39
+
40
+ 연결된 AI는 아래 순서로 **수정 → 재검증**한다. 재검증은 동일 루브릭 전 항목을 다시 돌려 **C1·C2·C3·C5 = 0 이고 C10 = 1.0** 일 때만 통과로 본다.
41
+
42
+ 1. **분류 먼저, 삭제·치환은 다음.** 각 불합격 토큰을 (a) 날조 (b) 과장 (c) 미검증 정밀도 (d) 갭은폐 중 하나로 라벨링한다.
43
+ 2. **(a) 날조 → 삭제.** 저장 근거가 0인 회사·직함·수치·자격증은 문장째 삭제하거나, 저장된 실제 사실로 교체한다. 지어내서 채우지 않는다.
44
+ 3. **(b) 과장 → 하향 정렬.** 직함·귀속·숙련도를 저장 데이터 수준으로 되돌린다(팀장→팀원, 주도→참여, 전문가→"3년 사용"). 표현은 강하게 두되 **사실 레벨만** 내린다.
45
+ 4. **(c) 미검증 정밀도 → 일반화 또는 표식.** 근거 없는 "37%"는 (저장값이 있으면) 정확값으로 바꾸고, 없으면 정성 표현("크게 단축")으로 일반화하거나 `[확인 필요: 수치 출처]`로 표시한다. 추측 수치를 남기지 않는다.
46
+ 5. **(d) 갭 은폐 → 정직한 표식 복원.** 지어낸 빈칸 채움을 `[확인 필요]`로 되돌리고, 사용자에게 해당 사실을 **쉬운 한국어로** 질문한다(직함·기술 용어 노출 금지). 사용자가 값을 주면 저장 도구로 보관 후 반영한다.
47
+ 6. **재검증 게이트.** 수정본을 다시 C1~C10에 통과시킨다. critical 4종 중 하나라도 남으면 루프를 반복한다. 2회 반복 후에도 미앵커가 남으면, 해당 문장을 산출물에서 보류하고 사용자에게 확인을 요청한다(무한정 생성 금지).
48
+ 7. **저장.** 통과한 산출물만 저장 도구(`save_cover_letter_version`·`save_fit_analysis`·`save_interview_prep` 등)로 보관한다. 검증 결과 요약(미앵커 0, traceability=1.0)을 산출물 메타에 함께 남긴다.
49
+
50
+ ## 4. 출처 (Provenance)
51
+
52
+ - **STAR (Situation-Task-Action-Result)** — 행동을 검증 가능한 결과로 환원하는 표준 프레임워크. "성과를 과장하면 STAR로 풀어 쓰기 어려워 거짓이 드러난다"는 점에서 진실성 게이트와 직결. · Monster Career Advice, "How to Create a STAR Method Resume" https://www.monster.com/career-advice/resume/star-method-resume · Resume Genius, "STAR Method Resume" https://resumegenius.com/blog/resume-help/star-method-resume
53
+ - **정량화·임팩트 중심·"YOUR contribution(팀 아닌 본인)"** — 귀속 정확성(C4)과 정량 앵커링(C1)의 1차 근거. · Yale Office of Career Strategy, "Writing Impactful Resume Bullets" https://ocs.yale.edu/resources/writing-impactful-resume-bullets/ · Harvard FAS Mignone Center, "Create a Strong Resume" https://careerservices.fas.harvard.edu/resources/create-a-strong-resume/ · UC Berkeley Career Engagement, "Resumes" https://career.berkeley.edu/prepare-for-success/resumes/
54
+ - **윤리적 강화 vs 비윤리적 날조의 경계** — 직함 격상·경력 날조·성과 인플레·자격 위조를 비윤리로 명시. C2·C3·C5·C6의 근거. · MyResumee, "The Ethics of Resume Enhancements and Where to Draw the Line" https://www.myresumee.com/blog/ree-the-ethics-of-resume-enhancements-and-where-to-draw-the-line
55
+ - **이력서 거짓의 실태·적발·결과(통계)** — 검증 게이트의 필요성 근거. 2024 StandOutCV 설문 64.2%가 이력서에 거짓 기재 / 적발 시 41% 채용 취소·18% 해고. 거짓 유형: 미보유 스킬 숙련 주장(60%), 직함 변경(41%), 더 유명한 대학 주장(39%) → C5·C6 설계의 직접 근거. Gartner: 2028년까지 글로벌 지원자 프로필 4건 중 1건이 완전 가짜 전망. · Shortlister, "40+ Background Check Statistics in 2025" https://www.myshortlister.com/insights/background-check-statistics · Avvanz, "Resume Fraud in 2026" https://www.avvanz.com/blog/resume-fraud-2026-employer-prevention
56
+ - **CareerMate 프라임 디렉티브 / Fact-lock** — "두뇌는 연결된 AI, 저장은 CareerMate. 숫자·고유명사·직함은 입력 그대로 보존, 명시적 산술 파생만 허용, 모르면 `[확인 필요]`." C1·C7·C8의 내부 근거. · CareerMate `AGENTS.md`; `docs/career-os/knowledge/human-writing.md`(R7 Fact lock)
57
+
58
+ ## 5. Phase B 힌트
59
+
60
+ - **MCP 도구 아이디어:**
61
+ - `get_verifier({ id: "truthfulness" })` — 이 루브릭(JSON: 항목·합격기준·det/ai 플래그·정규식 패턴·critical 등급)을 연결된 AI에게 그대로 serve.
62
+ - `get_fact_anchors({ scope })` — 저장 데이터에서 **앵커 집합**을 미리 추출해 제공: `{ numbers[], proper_nouns[](회사·기관·기술·인명), titles[], credentials[], timeline[] }`. C1·C2·C3·C5·C9의 화이트리스트로 직접 사용.
63
+ - `check_traceability({ artifact_id })` — det 항목(C7 갭표식 카운트, C10 추적성 비율, 정규식 수치/날짜 토큰 카운트)을 CareerMate가 LLM 없이 계산해 `{ N_total, N_unanchored, traceability, placeholder_delta }` 반환.
64
+ - **데이터 형태 힌트:**
65
+ - 앵커 집합은 `set`(정확매칭용)과 정규화 함수(콤마/단위/한글수사: `1,200ms==1200ms`, `12만==120000`)를 함께 제공해 거짓양성 감소.
66
+ - 산출물 메타에 `verification: { rubric: "truthfulness", critical_failures: 0, traceability: 1.0, checked_at }`를 저장해 상태 게이팅(`applied` 이상 전환 전 통과 필수) 및 대시보드 표시에 활용.
67
+ - 초안 버전 간 `placeholder_delta`(C7)를 추적하려면 `save_cover_letter_version`이 직전 버전 참조를 보관해야 한다(빈칸 치환 감지용).
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "careermate",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "description": "CareerMate — an MCP-first, local-first career workspace. Connect it to a local AI agent (Claude Desktop, Claude Code, Codex); CareerMate stores your career data locally on your machine and exposes it over MCP.",
6
+ "license": "MIT",
7
+ "author": "CareerMate",
8
+ "homepage": "https://careermate.life",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/osntak/careermate.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/osntak/careermate/issues"
15
+ },
16
+ "keywords": [
17
+ "career",
18
+ "resume",
19
+ "cover-letter",
20
+ "job-application",
21
+ "korean",
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "local-first",
25
+ "claude-code",
26
+ "codex"
27
+ ],
28
+ "bin": {
29
+ "careermate": "bin/careermate.mjs"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "files": [
35
+ "bin",
36
+ "dist",
37
+ "packages",
38
+ "scripts/init.ts",
39
+ "tsconfig.json",
40
+ "README.md",
41
+ "INSTALL.md",
42
+ "THIRD_PARTY_NOTICES.md",
43
+ "AGENTS.md",
44
+ "docs/career-os/README.md",
45
+ "docs/career-os/eop",
46
+ "docs/career-os/knowledge",
47
+ "docs/START_WORKFLOW.md",
48
+ "docs/WORKFLOWS.md",
49
+ "docs/MCP_TOOLS.md",
50
+ "docs/FAQ.md",
51
+ "docs/SUPPORTED_AI_APPS.md"
52
+ ],
53
+ "engines": {
54
+ "node": ">=22.5.0"
55
+ },
56
+ "scripts": {
57
+ "build": "node scripts/build-dist.mjs",
58
+ "build:mcpb": "node scripts/build-mcpb.mjs",
59
+ "prepack": "node scripts/build-dist.mjs",
60
+ "init": "node --no-warnings --import tsx scripts/init.ts",
61
+ "start": "node --no-warnings --experimental-sqlite --import tsx apps/web/src/index.ts",
62
+ "dev": "node --no-warnings --experimental-sqlite --watch --import tsx apps/web/src/index.ts",
63
+ "mcp": "node --no-warnings --experimental-sqlite --import tsx apps/mcp/src/index.ts",
64
+ "migrate": "node --no-warnings --experimental-sqlite --import tsx scripts/migrate.ts",
65
+ "seed": "node --no-warnings --experimental-sqlite --import tsx scripts/seed.ts",
66
+ "shots:seed": "node --no-warnings --experimental-sqlite --import tsx scripts/shots-seed.ts",
67
+ "shots": "node --no-warnings --experimental-sqlite --import tsx scripts/shots.ts",
68
+ "doctor": "node --no-warnings --experimental-sqlite --import tsx scripts/doctor.ts",
69
+ "test": "node --no-warnings scripts/run.mjs scripts/test.ts \"TEST_VERDICT PASS\"",
70
+ "test:ui": "node --no-warnings scripts/run.mjs scripts/ui-smoke.ts \"UI_VERDICT PASS\"",
71
+ "typecheck": "tsc --noEmit"
72
+ },
73
+ "dependencies": {
74
+ "@modelcontextprotocol/sdk": "^1.12.0",
75
+ "fflate": "^0.8.3",
76
+ "hwp.js": "^0.0.3",
77
+ "mammoth": "^1.12.0",
78
+ "pdfjs-dist": "^6.0.0",
79
+ "tsx": "^4.19.2",
80
+ "zod": "^3.24.1"
81
+ },
82
+ "devDependencies": {
83
+ "@playwright/test": "^1.60.0",
84
+ "@types/node": "^22.10.0",
85
+ "esbuild": "^0.28.1",
86
+ "playwright": "^1.60.0",
87
+ "typescript": "^5.7.2"
88
+ }
89
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * get_application_context — the single most important read path.
3
+ *
4
+ * In ONE call the AI gets everything it needs to analyze a posting or write a
5
+ * cover letter: the user's profile, primary resume, experiences, projects,
6
+ * skills, existing cover letters, recent applications, and — when a job_id is
7
+ * given — the target job, its prior fit analysis, related history for the same
8
+ * company/role, and the user's writing preferences.
9
+ *
10
+ * Designed so the AI never has to make five round-trips before doing real work.
11
+ */
12
+ import { type ApplicationContext } from '@careermate/shared';
13
+ import {
14
+ profileRepo,
15
+ documentRepo,
16
+ experienceRepo,
17
+ projectRepo,
18
+ skillRepo,
19
+ coverLetterRepo,
20
+ applicationRepo,
21
+ jobRepo,
22
+ fitRepo,
23
+ } from '@careermate/db';
24
+
25
+ export interface ApplicationContextOptions {
26
+ /** Target job to focus the bundle around. */
27
+ job_id?: string;
28
+ /** Cap on recent applications returned (default 10). */
29
+ recent_limit?: number;
30
+ }
31
+
32
+ export function getApplicationContext(opts: ApplicationContextOptions = {}): ApplicationContext {
33
+ const profile = profileRepo.get();
34
+ const resumes = documentRepo.list('resume');
35
+ const primary_resume = documentRepo.primary('resume');
36
+ const experiences = experienceRepo.list();
37
+ const projects = projectRepo.list();
38
+ const skills = skillRepo.list();
39
+ const cover_letters = coverLetterRepo.list();
40
+ const recent_applications = applicationRepo.recent(opts.recent_limit ?? 10);
41
+
42
+ const job = opts.job_id ? jobRepo.get(opts.job_id) : null;
43
+ const fit_analysis = opts.job_id ? fitRepo.getByJob(opts.job_id) : null;
44
+
45
+ // Related history: prior postings at the same company or with a similar role,
46
+ // with their application + fit so the AI can reuse angles and avoid repeats.
47
+ let related_history: ApplicationContext['related_history'] = [];
48
+ if (job) {
49
+ const related = jobRepo.relatedTo(job.company, job.position, job.id);
50
+ related_history = related.map((rj) => ({
51
+ job: rj,
52
+ application: applicationRepo.getByJob(rj.id),
53
+ fit_analysis: fitRepo.getByJob(rj.id),
54
+ }));
55
+ }
56
+
57
+ return {
58
+ profile,
59
+ primary_resume,
60
+ resumes,
61
+ experiences,
62
+ projects,
63
+ skills,
64
+ cover_letters,
65
+ recent_applications,
66
+ job,
67
+ fit_analysis,
68
+ related_history,
69
+ writing_preferences: {
70
+ preferred_tone: profile?.preferred_tone ?? null,
71
+ emphasis_points: profile?.emphasis_points ?? [],
72
+ },
73
+ };
74
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @careermate/core — transport-agnostic business use-cases. The HTTP API and the
3
+ * MCP server both call into these so the dashboard and the AI always agree.
4
+ */
5
+ export * from './onboarding.ts';
6
+ export * from './context.ts';
7
+ export * from './services.ts';
8
+ export * from './summary.ts';
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Onboarding status + profile completeness.
3
+ *
4
+ * "Completed" is intentionally generous: the moment the user has a profile and
5
+ * at least one document, the AI should stop nagging and move to real work.
6
+ * `next_steps` is what powers the "지금 해야 할 일" guidance everywhere.
7
+ */
8
+ import {
9
+ type OnboardingStatus,
10
+ type ProfileRecord,
11
+ } from '@careermate/shared';
12
+ import {
13
+ profileRepo,
14
+ documentRepo,
15
+ coverLetterRepo,
16
+ experienceRepo,
17
+ skillRepo,
18
+ jobRepo,
19
+ } from '@careermate/db';
20
+
21
+ /** 0–100 score over the fields that make profiles useful for the AI. */
22
+ export function profileCompleteness(p: ProfileRecord | null): number {
23
+ if (!p) return 0;
24
+ const checks: boolean[] = [
25
+ !!p.name,
26
+ !!(p.headline || p.summary),
27
+ !!p.location,
28
+ p.desired_roles.length > 0,
29
+ !!p.desired_conditions,
30
+ !!p.preferred_tone,
31
+ p.emphasis_points.length > 0,
32
+ p.links.length > 0,
33
+ experienceRepo.list().length > 0,
34
+ skillRepo.list().length > 0,
35
+ ];
36
+ const score = Math.round((checks.filter(Boolean).length / checks.length) * 100);
37
+ return score;
38
+ }
39
+
40
+ export function getOnboardingStatus(): OnboardingStatus {
41
+ const profile = profileRepo.get();
42
+ const resumes = documentRepo.list('resume');
43
+ const coverLetters = coverLetterRepo.list();
44
+ const experiences = experienceRepo.list();
45
+ const skills = skillRepo.list();
46
+ const jobs = jobRepo.list();
47
+
48
+ const has_profile = !!(profile && profile.name);
49
+ const has_resume = resumes.length > 0;
50
+ const has_cover_letter = coverLetters.length > 0;
51
+ const has_experience = experiences.length > 0;
52
+ const has_skills = skills.length > 0;
53
+ const has_job = jobs.length > 0;
54
+
55
+ const completeness = profileCompleteness(profile);
56
+ const completed = has_profile && (has_resume || has_experience);
57
+
58
+ const next_steps: string[] = [];
59
+ if (!has_profile) next_steps.push('기본 프로필을 입력하세요 (이름, 한 줄 소개, 희망 직무).');
60
+ if (!has_resume) next_steps.push('이력서나 경력기술서를 추가하세요. 파일 내용을 붙여넣어도 됩니다.');
61
+ if (!has_experience) next_steps.push('주요 경력을 1개 이상 추가하면 적합도 분석 품질이 올라갑니다.');
62
+ if (!has_skills) next_steps.push('보유 기술스택을 정리해 두면 공고 매칭이 정확해집니다.');
63
+ if (has_profile && !has_cover_letter)
64
+ next_steps.push('기존 자기소개서가 있다면 추가해 두세요. AI가 문체를 학습합니다.');
65
+ if (completed && !has_job)
66
+ next_steps.push('관심 있는 채용공고 URL이나 내용을 AI에게 전달해 적합도 분석을 받아보세요.');
67
+ if (next_steps.length === 0)
68
+ next_steps.push('준비가 잘 되어 있어요. 새 공고를 분석하거나 자기소개서를 작성해 보세요.');
69
+
70
+ return {
71
+ completed,
72
+ has_profile,
73
+ has_resume,
74
+ has_cover_letter,
75
+ has_experience,
76
+ has_skills,
77
+ has_job,
78
+ profile_completeness: completeness,
79
+ next_steps,
80
+ };
81
+ }