careermate 0.0.8 → 0.0.10

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 (68) hide show
  1. package/AGENTS.md +2 -2
  2. package/LICENSE +1 -1
  3. package/README.md +78 -81
  4. package/bin/careermate.mjs +11 -2
  5. package/dist/career-os/eop/cover-letter.md +10 -4
  6. package/dist/career-os/eop/interview-prep.md +1 -0
  7. package/dist/career-os/knowledge/cover-letter.md +4 -0
  8. package/dist/career-os/knowledge/verifiers/human-voice.md +7 -2
  9. package/dist/doctor.mjs +18138 -6866
  10. package/dist/mcp.mjs +14820 -4124
  11. package/dist/migrate.mjs +14436 -3712
  12. package/dist/pdf.worker.min.mjs +4 -4
  13. package/dist/public/app.js +10 -19
  14. package/dist/public/i18n/en/applications.js +19 -0
  15. package/dist/public/i18n/en/common.js +6 -0
  16. package/dist/public/i18n/en/documents.js +1 -0
  17. package/dist/public/i18n/en/home.js +8 -1
  18. package/dist/public/i18n/en/jobs.js +19 -0
  19. package/dist/public/i18n/en/nav.js +1 -0
  20. package/dist/public/i18n/en/settings.js +5 -2
  21. package/dist/public/i18n/ko/applications.js +19 -0
  22. package/dist/public/i18n/ko/common.js +5 -0
  23. package/dist/public/i18n/ko/documents.js +1 -0
  24. package/dist/public/i18n/ko/home.js +8 -1
  25. package/dist/public/i18n/ko/jobs.js +19 -0
  26. package/dist/public/i18n/ko/nav.js +1 -0
  27. package/dist/public/i18n/ko/settings.js +5 -2
  28. package/dist/public/lib.js +29 -0
  29. package/dist/public/pages/applications.js +86 -10
  30. package/dist/public/pages/documents.js +25 -12
  31. package/dist/public/pages/home.js +24 -4
  32. package/dist/public/pages/insights.js +156 -0
  33. package/dist/public/pages/interview.js +11 -4
  34. package/dist/public/pages/jobs.js +92 -4
  35. package/dist/public/pages/settings.js +2 -2
  36. package/dist/public/styles.css +16 -0
  37. package/dist/web.mjs +17079 -5651
  38. package/docs/FAQ.md +13 -3
  39. package/docs/LOCAL_FIRST.md +32 -0
  40. package/docs/MCP_TOOLS.md +22 -9
  41. package/docs/REVIEW_IMPLEMENTATION_PLAN.md +22 -0
  42. package/docs/career-os/eop/cover-letter.md +10 -4
  43. package/docs/career-os/eop/interview-prep.md +1 -0
  44. package/docs/career-os/knowledge/cover-letter.md +4 -0
  45. package/docs/career-os/knowledge/verifiers/human-voice.md +7 -2
  46. package/package.json +9 -5
  47. package/packages/core/src/data-health.ts +161 -0
  48. package/packages/core/src/fit-explain.ts +63 -0
  49. package/packages/core/src/index.ts +4 -0
  50. package/packages/core/src/jobsearch.ts +99 -54
  51. package/packages/core/src/offers.ts +41 -0
  52. package/packages/core/src/reuse-insights.ts +126 -0
  53. package/packages/core/src/services.ts +5 -1
  54. package/packages/core/src/summary.ts +245 -19
  55. package/packages/db/src/connection.ts +102 -14
  56. package/packages/db/src/index.ts +3 -0
  57. package/packages/db/src/interview-retrospectives.ts +149 -0
  58. package/packages/db/src/provenance.ts +125 -0
  59. package/packages/db/src/repositories.ts +140 -8
  60. package/packages/db/src/schema.ts +140 -8
  61. package/packages/mcp-tools/src/result.ts +9 -0
  62. package/packages/mcp-tools/src/tools.ts +143 -20
  63. package/packages/parsers/src/documents.ts +7 -1
  64. package/packages/prompts/src/humanize.ts +4 -0
  65. package/packages/prompts/src/system.ts +21 -64
  66. package/packages/shared/src/schemas.ts +134 -0
  67. package/scripts/init.ts +2 -0
  68. package/tsconfig.json +9 -10
@@ -380,6 +380,49 @@ export type CoverLetterRecord = z.infer<typeof CoverLetterRecordSchema>;
380
380
 
381
381
  /* --------------------------------------------------------------------- Job */
382
382
 
383
+ /**
384
+ * One company-specified cover-letter question (한국 문항형 자소서). Stored on the job so
385
+ * the AI writes each answer to its own prompt and checks length against that question's
386
+ * limit. CareerMate counts chars deterministically (checkCharLimit) — it never generates.
387
+ */
388
+ export const JobQuestionSchema = z.object({
389
+ no: z.number().int().positive().optional().describe('문항 번호(있으면)'),
390
+ question: z.string().max(MAX_BODY).describe('문항 텍스트(회사가 제시한 자소서 항목)'),
391
+ min_chars: z.number().int().positive().optional().describe('글자수 하한(있으면)'),
392
+ max_chars: z.number().int().positive().optional().describe('글자수 상한(있으면)'),
393
+ count_mode: z
394
+ .enum(['with_space', 'no_space', 'byte'])
395
+ .optional()
396
+ .describe('글자수 기준: with_space=공백포함(기본)·no_space=공백제외·byte=UTF-8 바이트'),
397
+ note: optLine.describe('부가 메모(예: "필수", "경험 중심")'),
398
+ });
399
+ export type JobQuestion = z.infer<typeof JobQuestionSchema>;
400
+
401
+ /**
402
+ * One recruitment-schedule entry (전형 일정 — 면접·인적성·발표 등). Kept on the job so
403
+ * save_job_posting carries it without a new tool, and the server computes D-day/conflicts
404
+ * deterministically (getActionDigest). This is NOT the document deadline — that stays in
405
+ * `deadline`; these are the downstream stage dates the AI extracts from an interview invite
406
+ * or announcement the user pastes in. Dates are strict YYYY-MM-DD so the server can compute
407
+ * D-day and same-day collisions without guessing formats.
408
+ */
409
+ const SCHEDULE_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
410
+ const SCHEDULE_TIME_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
411
+ export const JobScheduleItemSchema = z.object({
412
+ label: reqLine.describe('일정 이름 (예: 1차 면접·AI역검·서류 발표)'),
413
+ date: z
414
+ .string()
415
+ .regex(SCHEDULE_DATE_RE, 'YYYY-MM-DD 형식이어야 합니다')
416
+ .describe('일정 날짜 YYYY-MM-DD'),
417
+ time: z
418
+ .string()
419
+ .regex(SCHEDULE_TIME_RE, 'HH:MM(24시간) 형식이어야 합니다')
420
+ .optional()
421
+ .describe('시각 HH:MM(24시간, 선택)'),
422
+ note: optLine.describe('부가 메모(예: 장소·준비물, 선택)'),
423
+ });
424
+ export type JobScheduleItem = z.infer<typeof JobScheduleItemSchema>;
425
+
383
426
  export const JobInputSchema = z.object({
384
427
  company: reqLine,
385
428
  position: reqLine.describe('직무/포지션명'),
@@ -394,6 +437,17 @@ export const JobInputSchema = z.object({
394
437
  company_overview: optBody.describe('회사 사업개요·미션·주요 제품·최근 활동 (AI가 리서치해 채움)'),
395
438
  talent_profile: optBody.describe('회사가 강조하는 인재상 (한국 자소서·면접에서 정렬 기준)'),
396
439
  core_values: strList.optional().describe('회사 핵심가치 (키워드 배열)'),
440
+ reputation: optBody.describe('회사 평판 요약 — 평점·워라밸·문화 리스크 등 (AI가 company-research 플레이북으로 리서치해 채움; 출처·최근성 표기)'),
441
+ questions: z
442
+ .array(JobQuestionSchema)
443
+ .max(MAX_ITEMS)
444
+ .optional()
445
+ .describe('회사 지정 자소서 문항·글자수 — 사용자가 문항을 주면 구조화 저장'),
446
+ schedule: z
447
+ .array(JobScheduleItemSchema)
448
+ .max(MAX_ITEMS)
449
+ .optional()
450
+ .describe('전형 일정 — 면접초대 메일·발표 공지를 받으면 구조화 저장(label·date 필수, time 선택). 서류 마감은 deadline 필드를 쓰세요.'),
397
451
  });
398
452
  export type JobInput = z.infer<typeof JobInputSchema>;
399
453
 
@@ -412,9 +466,65 @@ export const JobRecordSchema = z.object({
412
466
  company_overview: z.string().nullable(),
413
467
  talent_profile: z.string().nullable(),
414
468
  core_values: z.array(z.string()),
469
+ reputation: z.string().nullable(),
470
+ questions: z.array(JobQuestionSchema),
471
+ schedule: z.array(JobScheduleItemSchema),
415
472
  });
416
473
  export type JobRecord = z.infer<typeof JobRecordSchema>;
417
474
 
475
+ /* ------------------------------------------------------------------- Offer */
476
+
477
+ /** Offer lifecycle — MY response to the company's proposal. Orthogonal to the
478
+ * application's 8-stage status (which tracks the COMPANY's process): together
479
+ * they can express e.g. "최종 합격했지만 내가 거절" (final_passed + declined). */
480
+ export const OFFER_STATUSES = ['open', 'accepted', 'declined'] as const;
481
+ export type OfferStatus = (typeof OFFER_STATUSES)[number];
482
+
483
+ /** Job offer for comparison. Cash pieces are 만원(numeric) so the server can sum an
484
+ * annual estimate deterministically; equity/other stay free-text. ai_score/verdict
485
+ * are produced by the connected AI at save time (CareerMate has no LLM). */
486
+ export const OfferInputSchema = z.object({
487
+ job_id: reqLine,
488
+ offer_status: z
489
+ .enum(OFFER_STATUSES)
490
+ .optional()
491
+ .describe('오퍼 상태 — open(진행 중)/accepted(수락)/declined(거절). 생략 시 기존 값 유지(신규 저장은 open)'),
492
+ base_salary: z.number().nonnegative().optional().describe('기본급/연봉 (만원)'),
493
+ bonus_amount: z.number().nonnegative().optional().describe('연 성과급·상여 기대액 (만원)'),
494
+ welfare_amount: z.number().nonnegative().optional().describe('복지포인트 등 연환산 (만원)'),
495
+ signing_amount: z.number().nonnegative().optional().describe('사이닝보너스 일시금 (만원)'),
496
+ equity_note: optNote.describe('주식/스톡옵션 (정성 서술 — 가치평가가 모호하므로 텍스트)'),
497
+ comp_note: optBody.describe('기타 보상 자유 서술'),
498
+ work_arrangement: optLine.describe('근무형태 (재택/하이브리드/출근)'),
499
+ contract_type: optLine.describe('계약형태 (정규/계약 등)'),
500
+ accept_deadline: optLine.describe('수락 마감일 YYYY-MM-DD'),
501
+ ai_score: z.number().min(0).max(100).optional().describe('AI가 매긴 종합 점수 0~100 (저장 시 AI가 채움)'),
502
+ verdict: optBody.describe('최종평 — 어떤 상황이면 어디가 좋은지 등 (AI가 저장 시 작성)'),
503
+ notes: optNote,
504
+ });
505
+ export type OfferInput = z.infer<typeof OfferInputSchema>;
506
+
507
+ export const OfferRecordSchema = z.object({
508
+ ...baseRecord,
509
+ job_id: z.string(),
510
+ offer_status: z.enum(OFFER_STATUSES),
511
+ base_salary: z.number().nullable(),
512
+ bonus_amount: z.number().nullable(),
513
+ welfare_amount: z.number().nullable(),
514
+ signing_amount: z.number().nullable(),
515
+ equity_note: z.string().nullable(),
516
+ comp_note: z.string().nullable(),
517
+ work_arrangement: z.string().nullable(),
518
+ contract_type: z.string().nullable(),
519
+ accept_deadline: z.string().nullable(),
520
+ ai_score: z.number().nullable(),
521
+ verdict: z.string().nullable(),
522
+ notes: z.string().nullable(),
523
+ /** Server-computed annual cash estimate = base + bonus + welfare (signing is one-time, excluded). Null if no numeric pieces. */
524
+ total_comp_annual_est: z.number().nullable(),
525
+ });
526
+ export type OfferRecord = z.infer<typeof OfferRecordSchema>;
527
+
418
528
  /* ------------------------------------------------------------- FitAnalysis */
419
529
 
420
530
  export const FitAnalysisInputSchema = z.object({
@@ -444,6 +554,23 @@ export type FitAnalysisRecord = z.infer<typeof FitAnalysisRecordSchema>;
444
554
 
445
555
  /* ------------------------------------------------------------- Application */
446
556
 
557
+ /**
558
+ * 탈락 회고 — 어느 전형 단계에서 떨어졌는지. 탈락 사유를 알려주는 기업은 6%뿐이고 지원자
559
+ * 83%가 알고 싶어하므로, 이 자가 기록이 "서류는 붙는데 면접에서 떨어진다" 같은 패턴을 로컬
560
+ * 데이터로 드러내는 유일한 신호다. status='rejected'일 때만 의미가 있다.
561
+ */
562
+ export const REJECTED_STAGES = ['document', 'assessment', 'interview', 'final', 'other'] as const;
563
+ export type RejectedStage = (typeof REJECTED_STAGES)[number];
564
+
565
+ /** Korean labels for the rejection stage (dashboard localizes separately via i18n). */
566
+ export const REJECTED_STAGE_LABELS: Record<RejectedStage, string> = {
567
+ document: '서류',
568
+ assessment: '인적성·AI역검',
569
+ interview: '면접',
570
+ final: '최종·처우',
571
+ other: '기타',
572
+ };
573
+
447
574
  export const ApplicationInputSchema = z.object({
448
575
  job_id: reqLine,
449
576
  status: z.enum(APPLICATION_STATUSES).optional(),
@@ -469,6 +596,12 @@ export const ApplicationStatusUpdateSchema = z.object({
469
596
  status: z.enum(APPLICATION_STATUSES),
470
597
  note: optNote.describe('상태 변경 사유/메모'),
471
598
  submission: ApplicationSubmissionSchema.optional().describe('지원 완료 기록에 남길 제출 자료 스냅샷'),
599
+ rejected_stage: z
600
+ .enum(REJECTED_STAGES)
601
+ .optional()
602
+ .describe(
603
+ "탈락 회고 — 'rejected'로 바꿀 때 어느 단계에서 떨어졌는지: 서류=document, 인적성·AI역검=assessment, 면접=interview, 최종·처우=final, 기타=other. rejected가 아닌 상태에는 저장되지 않습니다.",
604
+ ),
472
605
  });
473
606
  export type ApplicationStatusUpdate = z.infer<typeof ApplicationStatusUpdateSchema>;
474
607
 
@@ -480,6 +613,7 @@ export const ApplicationRecordSchema = z.object({
480
613
  cover_letter_id: z.string().nullable(),
481
614
  applied_at: z.string().nullable(),
482
615
  notes: z.string().nullable(),
616
+ rejected_stage: z.enum(REJECTED_STAGES).nullable(),
483
617
  });
484
618
  export type ApplicationRecord = z.infer<typeof ApplicationRecordSchema>;
485
619
 
package/scripts/init.ts CHANGED
@@ -399,6 +399,8 @@ const SAFE_TOOLS: readonly string[] = [
399
399
  // search_jobs: 공개 잡보드에서 공고를 가져오기만(보내는 건 검색어뿐, 사용자 데이터 미전송).
400
400
  // check_for_update와 같은 "네트워크 read" 부류라 사전허용 안전(우회·쓰기 없음).
401
401
  'search_jobs',
402
+ // 오퍼(제안) — 로컬 저장/조회(save_fit_analysis 등과 동급), 외부 전송 없음.
403
+ 'save_offer', 'compare_offers', 'get_offer',
402
404
  // careermate 자신의 인박스 폴더·대시보드(127.0.0.1)만 여는 로컬 동작 + 인박스 내부 문서 읽기 → 사전허용 안전.
403
405
  'read_inbox', 'open_inbox', 'open_dashboard', 'open_application',
404
406
  ];
package/tsconfig.json CHANGED
@@ -13,17 +13,16 @@
13
13
  "forceConsistentCasingInFileNames": true,
14
14
  "allowImportingTsExtensions": true,
15
15
  "noEmit": true,
16
- "baseUrl": ".",
17
16
  "paths": {
18
- "@careermate/shared": ["packages/shared/src/index.ts"],
19
- "@careermate/db": ["packages/db/src/index.ts"],
20
- "@careermate/core": ["packages/core/src/index.ts"],
21
- "@careermate/mcp-tools": ["packages/mcp-tools/src/index.ts"],
22
- "@careermate/exporters": ["packages/exporters/src/index.ts"],
23
- "@careermate/parsers": ["packages/parsers/src/index.ts"],
24
- "@careermate/prompts": ["packages/prompts/src/index.ts"],
25
- "@careermate/workflows": ["packages/workflows/src/index.ts"],
26
- "@careermate/knowledge": ["packages/knowledge/src/index.ts"]
17
+ "@careermate/shared": ["./packages/shared/src/index.ts"],
18
+ "@careermate/db": ["./packages/db/src/index.ts"],
19
+ "@careermate/core": ["./packages/core/src/index.ts"],
20
+ "@careermate/mcp-tools": ["./packages/mcp-tools/src/index.ts"],
21
+ "@careermate/exporters": ["./packages/exporters/src/index.ts"],
22
+ "@careermate/parsers": ["./packages/parsers/src/index.ts"],
23
+ "@careermate/prompts": ["./packages/prompts/src/index.ts"],
24
+ "@careermate/workflows": ["./packages/workflows/src/index.ts"],
25
+ "@careermate/knowledge": ["./packages/knowledge/src/index.ts"]
27
26
  }
28
27
  },
29
28
  "include": ["packages/**/*.ts", "apps/**/*.ts", "scripts/**/*.ts"],