careermate 0.0.1 → 0.0.2

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 (33) hide show
  1. package/INSTALL.md +12 -8
  2. package/README.md +14 -8
  3. package/dist/career-os/knowledge/interview-behavioral.md +9 -1
  4. package/dist/career-os/knowledge/offer-evaluation-decision.md +2 -2
  5. package/dist/career-os/knowledge/onboarding-first-90-days.md +1 -1
  6. package/dist/career-os/knowledge/verifiers/human-voice.md +20 -0
  7. package/dist/doctor.mjs +421 -11
  8. package/dist/mcp.mjs +421 -11
  9. package/dist/migrate.mjs +3 -1
  10. package/dist/public/index.html +1 -0
  11. package/dist/public/pages/settings.js +45 -0
  12. package/dist/public/styles.css +2 -0
  13. package/dist/site/index.html +19 -12
  14. package/dist/site/llms-install.txt +11 -8
  15. package/dist/site/start.html +16 -9
  16. package/dist/site/style.css +9 -0
  17. package/dist/web.mjs +351 -6
  18. package/docs/SUPPORTED_AI_APPS.md +6 -6
  19. package/docs/career-os/knowledge/interview-behavioral.md +9 -1
  20. package/docs/career-os/knowledge/offer-evaluation-decision.md +2 -2
  21. package/docs/career-os/knowledge/onboarding-first-90-days.md +1 -1
  22. package/docs/career-os/knowledge/verifiers/human-voice.md +20 -0
  23. package/package.json +1 -1
  24. package/packages/core/src/index.ts +1 -0
  25. package/packages/core/src/services.ts +61 -2
  26. package/packages/core/src/verify/index.ts +11 -0
  27. package/packages/core/src/verify/lexicons.ts +149 -0
  28. package/packages/core/src/verify/lint.ts +248 -0
  29. package/packages/core/src/verify/normalize.ts +112 -0
  30. package/packages/db/src/index.ts +1 -0
  31. package/packages/db/src/settings.ts +41 -0
  32. package/packages/mcp-tools/src/tools.ts +80 -6
  33. package/packages/shared/src/schemas.ts +8 -0
@@ -42,6 +42,8 @@ import {
42
42
  getExportsDir,
43
43
  getDataDir,
44
44
  getInboxDir,
45
+ getVerifyStrict,
46
+ setVerifyStrict,
45
47
  } from '@careermate/db';
46
48
  import {
47
49
  getOnboardingStatus,
@@ -58,6 +60,8 @@ import {
58
60
  saveInterviewPrep,
59
61
  listRecentActivity,
60
62
  jobWithMeta,
63
+ previewCoverLetter,
64
+ summarizeReport,
61
65
  } from '@careermate/core';
62
66
  import { HUMANIZE_WRITING_GUIDE } from '@careermate/prompts';
63
67
  import { getWorkflow, renderWorkflowMarkdown, WORKFLOWS } from '@careermate/workflows';
@@ -264,11 +268,19 @@ export const TOOLS: ToolDef[] = [
264
268
  const s = getOnboardingStatus();
265
269
  const lines = s.next_steps.map((t, i) => `${i + 1}. ${t}`).join('\n');
266
270
  const upd = getUpdateStatus();
271
+ const strict = getVerifyStrict();
267
272
  let text = `온보딩 ${s.completed ? '완료됨' : '진행 필요'} · 프로필 완성도 ${s.profile_completeness}%\n\n다음 단계:\n${lines}`;
273
+ // One-time guidance: CareerMate machine-checks that the numbers in a cover
274
+ // letter trace to the user's real data. Mention the optional strict mode.
275
+ if (!s.completed) {
276
+ text +=
277
+ `\n\n💡 참고: 자소서를 저장할 때 CareerMate가 본문의 수치를 저장된 경력·이력서와 자동 대조해, 근거 없는 수치는 막습니다. ` +
278
+ `더 깐깐하게 보길 원하면 "엄격하게 봐줘"라고 하거나 대시보드 설정에서 엄격 모드를 켤 수 있어요(이력서 본문에 없는 수치까지 차단).`;
279
+ }
268
280
  if (upd.update_available) {
269
281
  text += `\n\n🆕 새 버전 v${upd.latest} 사용 가능 (현재 v${upd.current}). 사용자가 원하면 update_careermate 도구로 업데이트하세요.`;
270
282
  }
271
- return ok(text, { ...s, update: upd });
283
+ return ok(text, { ...s, verify_strict: strict, update: upd });
272
284
  },
273
285
  },
274
286
 
@@ -414,11 +426,26 @@ export const TOOLS: ToolDef[] = [
414
426
  name: 'save_cover_letter_version',
415
427
  title: '자기소개서 버전 저장',
416
428
  description:
417
- '자기소개서의 새 버전을 저장합니다. CareerMate 자소서 작성 워크플로우의 핵심 저장 단계입니다. cover_letter_id를 주면 기존 자소서에 새 버전을 추가하고, 없으면 새 자소서를 만들어 v1로 저장합니다(이때 title 권장). note에 이 버전의 변경 요약(예: "지원동기 보강")을 남기면 사용자가 대시보드에서 버전 히스토리를 이해하기 쉽습니다. job_id로 공고에 연결하면 지원 항목과 자동 연결됩니다.',
429
+ '자기소개서의 새 버전을 저장합니다. CareerMate 자소서 작성 워크플로우의 핵심 저장 단계입니다. cover_letter_id를 주면 기존 자소서에 새 버전을 추가하고, 없으면 새 자소서를 만들어 v1로 저장합니다(이때 title 권장). note에 이 버전의 변경 요약(예: "지원동기 보강")을 남기면 사용자가 대시보드에서 버전 히스토리를 이해하기 쉽습니다. job_id로 공고에 연결하면 지원 항목과 자동 연결됩니다. 저장 직전 자동으로 "근거 없는 수치" 점검을 돌립니다 — 본문의 정량 수치가 저장된 경력·이력서·프로젝트에 근거가 없으면(환각 의심) 저장이 막힙니다. 그때는 원본의 실제 수치로 고치거나, 의도한 값이면 force:true로 다시 저장하세요.',
418
430
  inputSchema: CoverLetterVersionInputSchema.shape,
419
431
  handler: (args) => {
420
- const { coverLetter, version } = saveCoverLetterVersion(args);
421
- return ok(`'${coverLetter.title}' v${version.version_no}을 저장했습니다.`, { coverLetter, version });
432
+ try {
433
+ const { coverLetter, version, verification, forced } = saveCoverLetterVersion(args);
434
+ const advisory = summarizeReport(verification);
435
+ const head = `'${coverLetter.title}' v${version.version_no}을 저장했습니다.`;
436
+ const hasAdvisory =
437
+ verification.signals.length ||
438
+ verification.provenance.jobSourced.length ||
439
+ verification.provenance.unverified.length;
440
+ const note = forced
441
+ ? '\n⚠️ 근거 없는 수치가 있는데도 force로 저장했습니다(검증 미통과로 기록). 면접 전 사실 확인을 권합니다.'
442
+ : hasAdvisory
443
+ ? `\n점검: ${advisory}`
444
+ : '';
445
+ return ok(head + note, { coverLetter, version, verification });
446
+ } catch (e) {
447
+ return fail(e instanceof Error ? e.message : '저장 실패');
448
+ }
422
449
  },
423
450
  },
424
451
 
@@ -672,7 +699,9 @@ export const TOOLS: ToolDef[] = [
672
699
  handler: (args) => {
673
700
  try {
674
701
  const content = getPlaybook(args.domain as ExpertDomain);
675
- return ok(content, { domain: args.domain, content });
702
+ // Content is already in the text block; don't duplicate it in the
703
+ // structured payload (it was being shipped ~2x → token bloat).
704
+ return ok(content, { domain: args.domain });
676
705
  } catch (e) {
677
706
  return fail(e instanceof Error ? e.message : '플레이북을 불러오지 못했습니다.');
678
707
  }
@@ -692,13 +721,58 @@ export const TOOLS: ToolDef[] = [
692
721
  handler: (args) => {
693
722
  try {
694
723
  const content = getVerifier(args.id as VerifierId);
695
- return ok(content, { id: args.id, content });
724
+ return ok(content, { id: args.id });
696
725
  } catch (e) {
697
726
  return fail(e instanceof Error ? e.message : '검증 루브릭을 불러오지 못했습니다.');
698
727
  }
699
728
  },
700
729
  },
701
730
 
731
+ /* ----------------------------------------- career-os: deterministic lint */
732
+ {
733
+ name: 'validate_cover_letter',
734
+ title: '자기소개서 자동 점검 (저장 전 미리보기)',
735
+ description:
736
+ '자기소개서를 save_cover_letter_version으로 저장하기 직전에 호출하면, 저장하지 않고 미리 점검만 합니다. CareerMate가 LLM 없이 결정론으로 (1) 본문의 정량 수치가 저장된 경력·이력서·프로젝트에 근거가 있는지(없으면 환각 의심 → 저장 시 차단됨), (2) 공고에서 가져온 수치를 본인 성과처럼 쓰지 않았는지, (3) 번역투·제너릭 도입·빈 형용사·블라인드 위반 같은 문체 신호를 세어 돌려줍니다. 점검은 셀 수 있는 것만 봅니다 — 사실성·동문서답 같은 의미 판단은 당신(AI)이 직접 하세요. job_id를 주면 공고 수치까지 비교합니다.',
737
+ inputSchema: {
738
+ text: z.string().max(200_000).describe('점검할 자기소개서 본문'),
739
+ job_id: z.string().optional().describe('연결할 공고(있으면 공고 수치까지 비교)'),
740
+ strict: z
741
+ .boolean()
742
+ .optional()
743
+ .describe('이번 점검만 엄격하게(이력서 본문에 근거 없는 수치도 차단). 사용자가 "엄격하게 봐줘"라고 할 때 true. 생략하면 저장된 모드를 따름'),
744
+ },
745
+ readOnly: true,
746
+ handler: (args) => {
747
+ const text = typeof args?.text === 'string' ? args.text : '';
748
+ if (!text.trim()) return fail('점검할 본문이 비어 있습니다.');
749
+ const report = previewCoverLetter(text, args?.job_id ?? null, args?.strict);
750
+ const verdict = report.blocking.length
751
+ ? `⛔ 이대로 저장하면 막힙니다 — ${report.blocking.map((b) => `${b.label}: ${b.detail}`).join('; ')}`
752
+ : '✅ 자동 차단 항목 없음(셀 수 있는 항목 기준).';
753
+ const modeLine = report.strict ? '\n(엄격 모드 적용 중)' : '';
754
+ return ok(`${verdict}${modeLine}\n${summarizeReport(report)}\n\n${report.disclaimer}`, report);
755
+ },
756
+ },
757
+ {
758
+ name: 'set_verify_mode',
759
+ title: '점검 모드 설정 (기본/엄격)',
760
+ description:
761
+ '저장 전 자동 점검의 엄격도를 바꿉니다. 엄격 모드(strict=true)를 켜면, 이후 모든 자소서 저장에서 "이력서 본문에 근거 없는 수치"(구조화 경력 항목에만 있는 값)까지 차단합니다. 기본 모드는 출처가 아예 없는 수치만 차단합니다. 사용자가 "엄격하게 봐줘"라고 하면 바로 켜지 말고 "앞으로 항상 엄격하게 볼까요?"라고 먼저 확인한 뒤 동의하면 strict=true로 호출하세요. 끄기(strict=false)는 안전장치를 약하게 만드는 것이므로, 사용자가 명확히 끄겠다고 할 때만 호출하세요. 이번 한 건만 엄격하게 보려면 모드를 바꾸지 말고 validate_cover_letter / save_cover_letter_version에 strict:true를 한 번만 넘기세요.',
762
+ inputSchema: {
763
+ strict: z.boolean().describe('true=엄격 켜기, false=끄기(명확한 사용자 의사일 때만)'),
764
+ },
765
+ handler: (args) => {
766
+ const on = setVerifyStrict(!!args?.strict);
767
+ return ok(
768
+ on
769
+ ? '엄격 점검 모드를 켰습니다. 이제 자소서를 저장할 때, 올린 이력서 본문에 근거가 없는 수치는 막힙니다(구조화 경력 항목에만 있는 값 포함). 끄려면 "엄격 모드 꺼줘"라고 하세요.'
770
+ : '엄격 점검 모드를 껐습니다. 이제 출처가 아예 없는 수치만 차단합니다(구조화 항목에만 있는 값은 경고만).',
771
+ { strict: on },
772
+ );
773
+ },
774
+ },
775
+
702
776
  /* --------------------------------------------------- writing style guide */
703
777
  {
704
778
  name: 'get_writing_style_guide',
@@ -244,6 +244,14 @@ export const CoverLetterVersionInputSchema = z.object({
244
244
  note: optNote.describe('이 버전의 변경 요약 (예: 지원동기 보강)'),
245
245
  source: z.enum(CONTENT_SOURCES).optional(),
246
246
  set_current: z.boolean().optional().describe('이 버전을 현재 버전으로 지정 (기본 true)'),
247
+ force: z
248
+ .boolean()
249
+ .optional()
250
+ .describe('저장 전 자동 점검(근거 없는 수치)에서 막혔을 때, 의도한 값임을 확인하고 그대로 저장'),
251
+ strict: z
252
+ .boolean()
253
+ .optional()
254
+ .describe('이번 저장만 엄격하게 점검(이력서 본문에 근거 없는 수치도 차단). 생략하면 저장된 모드 설정을 따름'),
247
255
  });
248
256
  export type CoverLetterVersionInput = z.infer<typeof CoverLetterVersionInputSchema>;
249
257