careermate 0.0.4 → 0.0.6

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 (124) hide show
  1. package/AGENTS.md +11 -7
  2. package/INSTALL.md +18 -16
  3. package/README.md +28 -26
  4. package/THIRD_PARTY_NOTICES.md +2 -3
  5. package/bin/careermate.mjs +8 -1
  6. package/dist/career-os/README.md +8 -3
  7. package/dist/career-os/eop/career-description.md +136 -0
  8. package/dist/career-os/eop/cover-letter.md +7 -5
  9. package/dist/career-os/eop/fit-analysis.md +15 -5
  10. package/dist/career-os/eop/interview-prep.md +209 -21
  11. package/dist/career-os/eop/job-analysis.md +5 -3
  12. package/dist/career-os/eop/profile-extraction.md +5 -4
  13. package/dist/career-os/knowledge/cover-letter.md +4 -4
  14. package/dist/career-os/knowledge/fit-matching.md +8 -8
  15. package/dist/career-os/knowledge/interview-behavioral.md +76 -8
  16. package/dist/career-os/knowledge/interview-technical.md +106 -4
  17. package/dist/career-os/knowledge/linkedin-profile.md +1 -1
  18. package/dist/career-os/knowledge/networking-referrals.md +1 -1
  19. package/dist/career-os/knowledge/offer-evaluation-decision.md +10 -10
  20. package/dist/career-os/knowledge/portfolio.md +3 -3
  21. package/dist/career-os/knowledge/verifiers/ats-compat.md +8 -4
  22. package/dist/career-os/knowledge/verifiers/human-voice.md +2 -2
  23. package/dist/career-os/knowledge/verifiers/recency-staleness.md +1 -1
  24. package/dist/career-os/knowledge/verifiers/responsiveness-on-target.md +2 -2
  25. package/dist/career-os/knowledge/verifiers/truthfulness.md +1 -1
  26. package/dist/doctor.mjs +1019 -127
  27. package/dist/mcp.mjs +1036 -126
  28. package/dist/migrate.mjs +52 -1
  29. package/dist/public/app.js +42 -16
  30. package/dist/public/i18n/en/applications.js +23 -0
  31. package/dist/public/i18n/en/common.js +22 -0
  32. package/dist/public/i18n/en/documents.js +133 -0
  33. package/dist/public/i18n/en/home.js +75 -0
  34. package/dist/public/i18n/en/interview.js +69 -0
  35. package/dist/public/i18n/en/jobs.js +152 -0
  36. package/dist/public/i18n/en/nav.js +18 -0
  37. package/dist/public/i18n/en/profile.js +201 -0
  38. package/dist/public/i18n/en/settings.js +127 -0
  39. package/dist/public/i18n/en/status.js +22 -0
  40. package/dist/public/i18n/en.js +17 -0
  41. package/dist/public/i18n/ko/applications.js +23 -0
  42. package/dist/public/i18n/ko/common.js +22 -0
  43. package/dist/public/i18n/ko/documents.js +133 -0
  44. package/dist/public/i18n/ko/home.js +63 -0
  45. package/dist/public/i18n/ko/interview.js +69 -0
  46. package/dist/public/i18n/ko/jobs.js +152 -0
  47. package/dist/public/i18n/ko/nav.js +18 -0
  48. package/dist/public/i18n/ko/profile.js +201 -0
  49. package/dist/public/i18n/ko/settings.js +127 -0
  50. package/dist/public/i18n/ko/status.js +23 -0
  51. package/dist/public/i18n/ko.js +18 -0
  52. package/dist/public/i18n.js +55 -0
  53. package/dist/public/index.html +1 -0
  54. package/dist/public/lib.js +25 -17
  55. package/dist/public/pages/applications.js +17 -21
  56. package/dist/public/pages/documents.js +153 -107
  57. package/dist/public/pages/home.js +88 -53
  58. package/dist/public/pages/interview.js +105 -51
  59. package/dist/public/pages/jobs.js +330 -106
  60. package/dist/public/pages/profile.js +157 -152
  61. package/dist/public/pages/settings.js +270 -72
  62. package/dist/public/styles.css +38 -3
  63. package/dist/web.mjs +859 -103
  64. package/docs/FAQ.md +26 -5
  65. package/docs/MCP_TOOLS.md +26 -12
  66. package/docs/START_WORKFLOW.md +2 -2
  67. package/docs/SUPPORTED_AI_APPS.md +13 -0
  68. package/docs/WORKFLOWS.md +80 -23
  69. package/docs/career-os/README.md +8 -3
  70. package/docs/career-os/eop/career-description.md +136 -0
  71. package/docs/career-os/eop/cover-letter.md +7 -5
  72. package/docs/career-os/eop/fit-analysis.md +15 -5
  73. package/docs/career-os/eop/interview-prep.md +209 -21
  74. package/docs/career-os/eop/job-analysis.md +5 -3
  75. package/docs/career-os/eop/profile-extraction.md +5 -4
  76. package/docs/career-os/knowledge/cover-letter.md +4 -4
  77. package/docs/career-os/knowledge/fit-matching.md +8 -8
  78. package/docs/career-os/knowledge/interview-behavioral.md +76 -8
  79. package/docs/career-os/knowledge/interview-technical.md +106 -4
  80. package/docs/career-os/knowledge/linkedin-profile.md +1 -1
  81. package/docs/career-os/knowledge/networking-referrals.md +1 -1
  82. package/docs/career-os/knowledge/offer-evaluation-decision.md +10 -10
  83. package/docs/career-os/knowledge/portfolio.md +3 -3
  84. package/docs/career-os/knowledge/verifiers/ats-compat.md +8 -4
  85. package/docs/career-os/knowledge/verifiers/human-voice.md +2 -2
  86. package/docs/career-os/knowledge/verifiers/recency-staleness.md +1 -1
  87. package/docs/career-os/knowledge/verifiers/responsiveness-on-target.md +2 -2
  88. package/docs/career-os/knowledge/verifiers/truthfulness.md +1 -1
  89. package/package.json +2 -2
  90. package/packages/core/src/onboarding.ts +2 -1
  91. package/packages/core/src/services.ts +138 -21
  92. package/packages/core/src/summary.ts +59 -1
  93. package/packages/core/src/verify/lint.ts +4 -0
  94. package/packages/db/src/index.ts +1 -0
  95. package/packages/db/src/repositories.ts +0 -0
  96. package/packages/db/src/schema.ts +17 -0
  97. package/packages/exporters/src/index.ts +28 -17
  98. package/packages/knowledge/src/index.ts +46 -7
  99. package/packages/mcp-tools/src/bridge.ts +194 -13
  100. package/packages/mcp-tools/src/tools.ts +390 -38
  101. package/packages/mcp-tools/src/update.ts +31 -16
  102. package/packages/parsers/src/documents.ts +12 -1
  103. package/packages/prompts/src/humanize.ts +46 -0
  104. package/packages/prompts/src/index.ts +5 -1
  105. package/packages/prompts/src/install.ts +4 -4
  106. package/packages/prompts/src/onboarding.ts +4 -4
  107. package/packages/prompts/src/system.ts +17 -8
  108. package/packages/shared/src/enums.ts +14 -4
  109. package/packages/shared/src/schemas.ts +41 -0
  110. package/packages/workflows/src/definitions.ts +47 -10
  111. package/scripts/init.ts +284 -13
  112. package/dist/site/analytics.js +0 -97
  113. package/dist/site/favicon.svg +0 -1
  114. package/dist/site/fonts/PretendardVariable.woff2 +0 -0
  115. package/dist/site/icon-180.png +0 -0
  116. package/dist/site/icon-512.png +0 -0
  117. package/dist/site/index.html +0 -565
  118. package/dist/site/landing.js +0 -54
  119. package/dist/site/llms-install.txt +0 -218
  120. package/dist/site/robots.txt +0 -4
  121. package/dist/site/sitemap.xml +0 -21
  122. package/dist/site/start.html +0 -353
  123. package/dist/site/style.css +0 -559
  124. package/dist/site/vercel.json +0 -20
package/dist/doctor.mjs CHANGED
@@ -6477,9 +6477,9 @@ var require_timers = __commonJS({
6477
6477
  clearTimeout(this.handle);
6478
6478
  };
6479
6479
  var afterValue = function(value) {
6480
- return delay(+this).thenReturn(value);
6480
+ return delay2(+this).thenReturn(value);
6481
6481
  };
6482
- var delay = Promise2.delay = function(ms, value) {
6482
+ var delay2 = Promise2.delay = function(ms, value) {
6483
6483
  var ret2;
6484
6484
  var handle;
6485
6485
  if (value !== void 0) {
@@ -6501,7 +6501,7 @@ var require_timers = __commonJS({
6501
6501
  return ret2;
6502
6502
  };
6503
6503
  Promise2.prototype.delay = function(ms) {
6504
- return delay(ms, this);
6504
+ return delay2(ms, this);
6505
6505
  };
6506
6506
  var afterTimeout = function(promise, message, parent) {
6507
6507
  var err2;
@@ -28297,7 +28297,7 @@ var require_path_is_absolute = __commonJS({
28297
28297
  var require_files = __commonJS({
28298
28298
  "node_modules/mammoth/lib/docx/files.js"(exports2) {
28299
28299
  "use strict";
28300
- var fs8 = __require("fs");
28300
+ var fs9 = __require("fs");
28301
28301
  var url = __require("url");
28302
28302
  var os3 = __require("os");
28303
28303
  var dirname = __require("path").dirname;
@@ -28338,7 +28338,7 @@ var require_files = __commonJS({
28338
28338
  read
28339
28339
  };
28340
28340
  }
28341
- var readFile = promises.promisify(fs8.readFile.bind(fs8));
28341
+ var readFile = promises.promisify(fs9.readFile.bind(fs9));
28342
28342
  function uriToPath(uriString, platform) {
28343
28343
  if (!platform) {
28344
28344
  platform = os3.platform();
@@ -31080,11 +31080,11 @@ var require_options_reader = __commonJS({
31080
31080
  var require_unzip = __commonJS({
31081
31081
  "node_modules/mammoth/lib/unzip.js"(exports2) {
31082
31082
  "use strict";
31083
- var fs8 = __require("fs");
31083
+ var fs9 = __require("fs");
31084
31084
  var promises = require_promises();
31085
31085
  var zipfile = require_zipfile();
31086
31086
  exports2.openZip = openZip;
31087
- var readFile = promises.promisify(fs8.readFile);
31087
+ var readFile = promises.promisify(fs9.readFile);
31088
31088
  function openZip(options) {
31089
31089
  if (options.path) {
31090
31090
  return readFile(options.path).then(zipfile.openArrayBuffer);
@@ -31809,9 +31809,9 @@ var require_cjs = __commonJS({
31809
31809
  }
31810
31810
  return o;
31811
31811
  }
31812
- var fs8;
31812
+ var fs9;
31813
31813
  function get_fs() {
31814
- return fs8 || (fs8 = require$$0__default["default"]);
31814
+ return fs9 || (fs9 = require$$0__default["default"]);
31815
31815
  }
31816
31816
  function parse2(file, options) {
31817
31817
  if (file[0] == 80 && file[1] == 75) return parse_zip(file, options);
@@ -32096,7 +32096,7 @@ var require_cjs = __commonJS({
32096
32096
  }
32097
32097
  function read_file(filename2, options) {
32098
32098
  get_fs();
32099
- return parse2(fs8.readFileSync(filename2), options);
32099
+ return parse2(fs9.readFileSync(filename2), options);
32100
32100
  }
32101
32101
  function read(blob, options) {
32102
32102
  switch (options && options.type || "base64") {
@@ -32411,7 +32411,7 @@ var require_cjs = __commonJS({
32411
32411
  function write_file(cfb2, filename2, options) {
32412
32412
  get_fs();
32413
32413
  var o = _write(cfb2, options);
32414
- fs8.writeFileSync(filename2, o);
32414
+ fs9.writeFileSync(filename2, o);
32415
32415
  }
32416
32416
  function a2s(o) {
32417
32417
  var out = new Array(o.length);
@@ -32423,7 +32423,7 @@ var require_cjs = __commonJS({
32423
32423
  switch (options && options.type || "buffer") {
32424
32424
  case "file":
32425
32425
  get_fs();
32426
- fs8.writeFileSync(options.filename, o);
32426
+ fs9.writeFileSync(options.filename, o);
32427
32427
  return o;
32428
32428
  case "binary":
32429
32429
  return typeof o == "string" ? o : a2s(o);
@@ -40116,8 +40116,8 @@ function blend(fg, bg, alpha) {
40116
40116
  return Math.round(alpha * fg + (1 - alpha) * bg);
40117
40117
  }
40118
40118
  async function node_utils_fetchData(url) {
40119
- const fs8 = process.getBuiltinModule("fs/promises");
40120
- const data = await fs8.readFile(url);
40119
+ const fs9 = process.getBuiltinModule("fs/promises");
40120
+ const data = await fs9.readFile(url);
40121
40121
  return new Uint8Array(data);
40122
40122
  }
40123
40123
  function initGPU() {
@@ -40746,11 +40746,11 @@ function network_getArrayBuffer(val) {
40746
40746
  return typeof val !== "string" ? val : stringToBytes(val).buffer;
40747
40747
  }
40748
40748
  function getReadableStream(url, opts = null) {
40749
- const fs8 = process.getBuiltinModule("fs");
40749
+ const fs9 = process.getBuiltinModule("fs");
40750
40750
  const {
40751
40751
  Readable
40752
40752
  } = process.getBuiltinModule("stream");
40753
- const readStream = fs8.createReadStream(url, opts);
40753
+ const readStream = fs9.createReadStream(url, opts);
40754
40754
  return Readable.toWeb(readStream);
40755
40755
  }
40756
40756
  function getNetworkStream(url) {
@@ -57747,8 +57747,8 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
57747
57747
  url
57748
57748
  } = stream._source;
57749
57749
  this._isStreamingSupported = !disableStream;
57750
- const fs8 = process.getBuiltinModule("fs/promises");
57751
- fs8.lstat(url).then((stat) => {
57750
+ const fs9 = process.getBuiltinModule("fs/promises");
57751
+ fs9.lstat(url).then((stat) => {
57752
57752
  const readableStream = getReadableStream(url);
57753
57753
  this._reader = readableStream.getReader();
57754
57754
  const {
@@ -59394,9 +59394,9 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
59394
59394
  return;
59395
59395
  }
59396
59396
  if (reason instanceof RenderingCancelledException) {
59397
- let delay = RENDERING_CANCELLED_TIMEOUT;
59397
+ let delay2 = RENDERING_CANCELLED_TIMEOUT;
59398
59398
  if (reason.extraDelay > 0 && reason.extraDelay < 1e3) {
59399
- delay += reason.extraDelay;
59399
+ delay2 += reason.extraDelay;
59400
59400
  }
59401
59401
  intentState.streamReaderCancelTimeout = setTimeout(() => {
59402
59402
  intentState.streamReaderCancelTimeout = null;
@@ -59405,7 +59405,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
59405
59405
  reason,
59406
59406
  force: true
59407
59407
  });
59408
- }, delay);
59408
+ }, delay2);
59409
59409
  return;
59410
59410
  }
59411
59411
  }
@@ -71040,7 +71040,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
71040
71040
  });
71041
71041
 
71042
71042
  // scripts/doctor.ts
71043
- import fs7 from "node:fs";
71043
+ import fs8 from "node:fs";
71044
71044
 
71045
71045
  // packages/db/src/paths.ts
71046
71046
  import os from "node:os";
@@ -71366,6 +71366,22 @@ var MIGRATIONS = [
71366
71366
 
71367
71367
  COMMIT;
71368
71368
  PRAGMA foreign_keys=ON;
71369
+ `,
71370
+ // v3 — per-job timeline entries. Document/cover-letter refs live in payload
71371
+ // snapshots instead of FKs so deleting a document does not erase the history.
71372
+ `
71373
+ CREATE TABLE IF NOT EXISTS application_timeline_events (
71374
+ id TEXT PRIMARY KEY,
71375
+ job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
71376
+ type TEXT NOT NULL,
71377
+ title TEXT NOT NULL,
71378
+ summary TEXT,
71379
+ payload TEXT NOT NULL DEFAULT '{}',
71380
+ occurred_at TEXT NOT NULL,
71381
+ created_at TEXT NOT NULL
71382
+ );
71383
+
71384
+ CREATE INDEX IF NOT EXISTS idx_timeline_job ON application_timeline_events(job_id, occurred_at ASC);
71369
71385
  `
71370
71386
  ];
71371
71387
  function migrate(db) {
@@ -71526,6 +71542,12 @@ function assertStatusTransition(from, to) {
71526
71542
  }
71527
71543
  }
71528
71544
  var DOCUMENT_KINDS = ["resume", "career_description", "portfolio", "other"];
71545
+ var DOCUMENT_KIND_LABELS = {
71546
+ resume: "\uC774\uB825\uC11C",
71547
+ career_description: "\uACBD\uB825\uAE30\uC220\uC11C",
71548
+ portfolio: "\uD3EC\uD2B8\uD3F4\uB9AC\uC624",
71549
+ other: "\uAE30\uD0C0 \uBB38\uC11C"
71550
+ };
71529
71551
  var CONTENT_SOURCES = ["manual", "upload", "ai", "edit"];
71530
71552
  var ACTIVITY_TYPES = [
71531
71553
  "profile_updated",
@@ -71538,6 +71560,13 @@ var ACTIVITY_TYPES = [
71538
71560
  "interview_prep_saved",
71539
71561
  "document_exported"
71540
71562
  ];
71563
+ var APPLICATION_TIMELINE_TYPES = [
71564
+ "job_saved",
71565
+ "fit_analysis_saved",
71566
+ "cover_letter_version_saved",
71567
+ "application_status_changed",
71568
+ "interview_prep_saved"
71569
+ ];
71541
71570
  var ENTITY_TYPES = [
71542
71571
  "profile",
71543
71572
  "experience",
@@ -75845,11 +75874,19 @@ var ApplicationInputSchema = external_exports.object({
75845
75874
  applied_at: optLine,
75846
75875
  notes: optNote
75847
75876
  });
75877
+ var ApplicationSubmissionSchema = external_exports.object({
75878
+ submitted_at: optLine.describe("\uC2E4\uC81C \uC81C\uCD9C\uC77C YYYY-MM-DD"),
75879
+ channel: optLine.describe("\uC81C\uCD9C \uCC44\uB110(\uC6D0\uD2F0\uB4DC/\uC0AC\uB78C\uC778/\uD68C\uC0AC \uCC44\uC6A9\uD398\uC774\uC9C0 \uB4F1)"),
75880
+ cover_letter_id: optLine,
75881
+ cover_letter_version_id: optLine,
75882
+ document_ids: strList.optional().describe("\uC81C\uCD9C\uD55C \uC774\uB825\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C\xB7\uD3EC\uD2B8\uD3F4\uB9AC\uC624 \uBB38\uC11C ID \uBAA9\uB85D")
75883
+ });
75848
75884
  var ApplicationStatusUpdateSchema = external_exports.object({
75849
75885
  job_id: optLine,
75850
75886
  application_id: optLine,
75851
75887
  status: external_exports.enum(APPLICATION_STATUSES),
75852
- note: optNote.describe("\uC0C1\uD0DC \uBCC0\uACBD \uC0AC\uC720/\uBA54\uBAA8")
75888
+ note: optNote.describe("\uC0C1\uD0DC \uBCC0\uACBD \uC0AC\uC720/\uBA54\uBAA8"),
75889
+ submission: ApplicationSubmissionSchema.optional().describe("\uC9C0\uC6D0 \uC644\uB8CC \uAE30\uB85D\uC5D0 \uB0A8\uAE38 \uC81C\uCD9C \uC790\uB8CC \uC2A4\uB0C5\uC0F7")
75853
75890
  });
75854
75891
  var ApplicationRecordSchema = external_exports.object({
75855
75892
  ...baseRecord,
@@ -75867,8 +75904,10 @@ var StarGuideSchema = external_exports.object({
75867
75904
  action: optNote,
75868
75905
  result: optNote
75869
75906
  });
75907
+ var INTERVIEW_CATEGORIES = ["technical", "behavioral"];
75870
75908
  var InterviewQuestionSchema = external_exports.object({
75871
75909
  question: external_exports.string().max(MAX_NOTE),
75910
+ category: external_exports.enum(INTERVIEW_CATEGORIES).optional().describe("\uC9C8\uBB38 \uAC08\uB798: 'technical'(\uAE30\uC220\xB7\uC9C1\uBB34 1\uCC28) | 'behavioral'(\uC778\uC131\xB7\uCEEC\uCC98\uD54F\xB7\uC784\uC6D0 2\uCC28, \uC790\uAE30\uC18C\uAC1C\uC11C \uAE30\uBC18). \uC0DD\uB7B5 \uC2DC \uAE30\uC220\uB85C \uAC04\uC8FC."),
75872
75911
  intent: external_exports.string().max(MAX_NOTE).optional().describe("\uC9C8\uBB38 \uC758\uB3C4"),
75873
75912
  followups: strList.optional().describe("\uC608\uC0C1 \uAF2C\uB9AC \uC9C8\uBB38"),
75874
75913
  answer_outline: optBody.describe("\uB2F5\uBCC0 \uAC00\uC774\uB4DC/\uD575\uC2EC \uD3EC\uC778\uD2B8")
@@ -75896,6 +75935,24 @@ var ActivityRecordSchema = external_exports.object({
75896
75935
  summary: external_exports.string(),
75897
75936
  created_at: isoDateTime
75898
75937
  });
75938
+ var ApplicationTimelineInputSchema = external_exports.object({
75939
+ job_id: reqLine,
75940
+ type: external_exports.enum(APPLICATION_TIMELINE_TYPES),
75941
+ title: reqLine,
75942
+ summary: optNote,
75943
+ payload: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
75944
+ occurred_at: optLine
75945
+ });
75946
+ var ApplicationTimelineRecordSchema = external_exports.object({
75947
+ id,
75948
+ job_id: external_exports.string(),
75949
+ type: external_exports.enum(APPLICATION_TIMELINE_TYPES),
75950
+ title: external_exports.string(),
75951
+ summary: external_exports.string().nullable(),
75952
+ payload: external_exports.record(external_exports.string(), external_exports.unknown()),
75953
+ occurred_at: isoDateTime,
75954
+ created_at: isoDateTime
75955
+ });
75899
75956
  var OnboardingStatusSchema = external_exports.object({
75900
75957
  completed: external_exports.boolean(),
75901
75958
  has_profile: external_exports.boolean(),
@@ -75932,7 +75989,7 @@ var ApplicationContextSchema = external_exports.object({
75932
75989
  });
75933
75990
 
75934
75991
  // packages/shared/src/build-info.ts
75935
- var APP_VERSION = true ? "0.0.4" : "0.0.0-dev";
75992
+ var APP_VERSION = true ? "0.0.6" : "0.0.0-dev";
75936
75993
  var BUNDLED = true;
75937
75994
 
75938
75995
  // packages/shared/src/index.ts
@@ -76026,7 +76083,7 @@ function mapExperience(r) {
76026
76083
  };
76027
76084
  }
76028
76085
  function expKey(company, role, start) {
76029
- return [norm(company), norm(role), norm(start)].join("\0");
76086
+ return [norm(company), norm(role), norm(start)].join("");
76030
76087
  }
76031
76088
  var experienceRepo = {
76032
76089
  list() {
@@ -76444,6 +76501,7 @@ var coverLetterRepo = {
76444
76501
  remove(id2) {
76445
76502
  const db = getDb();
76446
76503
  return tx(() => {
76504
+ db.prepare(`UPDATE applications SET cover_letter_id=NULL, updated_at=? WHERE cover_letter_id=?`).run(now(), id2);
76447
76505
  db.prepare(`DELETE FROM cover_letter_versions WHERE cover_letter_id=?`).run(id2);
76448
76506
  return db.prepare(`DELETE FROM cover_letters WHERE id=?`).run(id2).changes > 0;
76449
76507
  });
@@ -76532,9 +76590,11 @@ var jobRepo = {
76532
76590
  remove(id2) {
76533
76591
  const db = getDb();
76534
76592
  return tx(() => {
76593
+ db.prepare(`UPDATE cover_letters SET job_id=NULL, updated_at=? WHERE job_id=?`).run(now(), id2);
76535
76594
  db.prepare(`DELETE FROM fit_analyses WHERE job_id=?`).run(id2);
76536
76595
  db.prepare(`DELETE FROM applications WHERE job_id=?`).run(id2);
76537
76596
  db.prepare(`DELETE FROM interview_preps WHERE job_id=?`).run(id2);
76597
+ db.prepare(`DELETE FROM application_timeline_events WHERE job_id=?`).run(id2);
76538
76598
  return db.prepare(`DELETE FROM jobs WHERE id=?`).run(id2).changes > 0;
76539
76599
  });
76540
76600
  }
@@ -76647,7 +76707,7 @@ var applicationRepo = {
76647
76707
  const m = { ...app, ...input };
76648
76708
  getDb().prepare(
76649
76709
  `UPDATE applications SET status=?,resume_id=?,cover_letter_id=?,applied_at=?,notes=?,updated_at=? WHERE id=?`
76650
- ).run(m.status, m.resume_id, m.cover_letter_id, m.applied_at, m.notes, now(), app.id);
76710
+ ).run(m.status, m.resume_id ?? null, m.cover_letter_id ?? null, m.applied_at ?? null, m.notes ?? null, now(), app.id);
76651
76711
  return this.get(app.id);
76652
76712
  },
76653
76713
  setStatus(jobId, status, note) {
@@ -76728,9 +76788,51 @@ var activityRepo = {
76728
76788
  return getDb().prepare(`SELECT * FROM activities ORDER BY created_at DESC LIMIT ?`).all(limit);
76729
76789
  }
76730
76790
  };
76791
+ function mapTimelineEvent(r) {
76792
+ return {
76793
+ id: r.id,
76794
+ job_id: r.job_id,
76795
+ type: r.type,
76796
+ title: r.title,
76797
+ summary: r.summary,
76798
+ payload: fromJson(r.payload, {}),
76799
+ occurred_at: r.occurred_at,
76800
+ created_at: r.created_at
76801
+ };
76802
+ }
76803
+ var timelineRepo = {
76804
+ add(input) {
76805
+ const ts = now();
76806
+ const id2 = newId("tl_");
76807
+ getDb().prepare(
76808
+ `INSERT INTO application_timeline_events (id,job_id,type,title,summary,payload,occurred_at,created_at)
76809
+ VALUES (?,?,?,?,?,?,?,?)`
76810
+ ).run(
76811
+ id2,
76812
+ input.job_id,
76813
+ input.type,
76814
+ input.title,
76815
+ input.summary ?? null,
76816
+ toJson(input.payload ?? {}),
76817
+ input.occurred_at ?? ts,
76818
+ ts
76819
+ );
76820
+ return this.get(id2);
76821
+ },
76822
+ get(id2) {
76823
+ const r = getDb().prepare(`SELECT * FROM application_timeline_events WHERE id=?`).get(id2);
76824
+ return r ? mapTimelineEvent(r) : null;
76825
+ },
76826
+ listByJob(jobId) {
76827
+ return getDb().prepare(`SELECT * FROM application_timeline_events WHERE job_id=? ORDER BY occurred_at ASC, created_at ASC`).all(jobId).map(mapTimelineEvent);
76828
+ },
76829
+ remove(id2) {
76830
+ return getDb().prepare(`DELETE FROM application_timeline_events WHERE id=?`).run(id2).changes > 0;
76831
+ }
76832
+ };
76731
76833
 
76732
76834
  // packages/mcp-tools/src/tools.ts
76733
- import fs6 from "node:fs";
76835
+ import fs7 from "node:fs";
76734
76836
  import path7 from "node:path";
76735
76837
 
76736
76838
  // packages/core/src/onboarding.ts
@@ -76754,12 +76856,13 @@ function profileCompleteness(p) {
76754
76856
  function getOnboardingStatus() {
76755
76857
  const profile = profileRepo.get();
76756
76858
  const resumes = documentRepo.list("resume");
76859
+ const careerDescriptions = documentRepo.list("career_description");
76757
76860
  const coverLetters = coverLetterRepo.list();
76758
76861
  const experiences = experienceRepo.list();
76759
76862
  const skills = skillRepo.list();
76760
76863
  const jobs = jobRepo.list();
76761
76864
  const has_profile = !!(profile && profile.name);
76762
- const has_resume = resumes.length > 0;
76865
+ const has_resume = resumes.length > 0 || careerDescriptions.length > 0;
76763
76866
  const has_cover_letter = coverLetters.length > 0;
76764
76867
  const has_experience = experiences.length > 0;
76765
76868
  const has_skills = skills.length > 0;
@@ -77172,6 +77275,29 @@ function batchNames(names) {
77172
77275
  if (!shown) return "";
77173
77276
  return `: ${shown}${names.length > 8 ? " \uC678" : ""}`;
77174
77277
  }
77278
+ function coverLetterTimelineRef(coverLetter, version2, requestedVersionId) {
77279
+ const resolvedVersion = version2 ?? coverLetter.versions?.find((v) => v.id === requestedVersionId || v.id === coverLetter.current_version_id) ?? null;
77280
+ return {
77281
+ kind: "cover_letter",
77282
+ id: coverLetter.id,
77283
+ title: coverLetter.title,
77284
+ version_id: resolvedVersion?.id ?? requestedVersionId ?? coverLetter.current_version_id,
77285
+ version_no: resolvedVersion?.version_no ?? coverLetter.version_count
77286
+ };
77287
+ }
77288
+ function documentTimelineRef(doc) {
77289
+ return {
77290
+ kind: "document",
77291
+ id: doc.id,
77292
+ title: doc.title,
77293
+ document_kind: doc.kind
77294
+ };
77295
+ }
77296
+ function submittedAtToOccurrence(value) {
77297
+ if (!value) return void 0;
77298
+ if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return `${value}T00:00:00.000Z`;
77299
+ return void 0;
77300
+ }
77175
77301
  function addExperiences(inputs) {
77176
77302
  const res = experienceRepo.addMany(inputs);
77177
77303
  activityRepo.log(
@@ -77210,11 +77336,22 @@ function saveJobPosting(input, id2) {
77210
77336
  }
77211
77337
  function saveFitAnalysis(input) {
77212
77338
  const job = jobRepo.get(input.job_id);
77213
- if (!job) throw notFound(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.job_id}. \uBA3C\uC800 save_job_posting\uC73C\uB85C \uACF5\uACE0\uB97C \uC800\uC7A5\uD558\uC138\uC694.`);
77339
+ if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uACF5\uACE0\uB97C \uC800\uC7A5\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.");
77214
77340
  applicationRepo.ensure(job.id, "draft");
77215
77341
  const fit = fitRepo.save(input);
77216
77342
  const scoreText = fit.score != null ? ` (\uC801\uD569\uB3C4 ${fit.score}\uC810)` : "";
77217
77343
  activityRepo.log("fit_analysis_saved", `${job.company} \xB7 ${job.position} \uC801\uD569\uB3C4 \uBD84\uC11D\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4${scoreText}.`, "fit_analysis", fit.id);
77344
+ timelineRepo.add({
77345
+ job_id: job.id,
77346
+ type: "fit_analysis_saved",
77347
+ title: "\uC801\uD569\uB3C4 \uBD84\uC11D",
77348
+ summary: fit.score != null ? `\uC885\uD569 ${fit.score}\uC810` : fit.summary ?? void 0,
77349
+ payload: {
77350
+ fit_id: fit.id,
77351
+ score: fit.score
77352
+ },
77353
+ occurred_at: fit.updated_at
77354
+ });
77218
77355
  return fit;
77219
77356
  }
77220
77357
  function saveCoverLetterVersion(input) {
@@ -77248,35 +77385,88 @@ function saveCoverLetterVersion(input) {
77248
77385
  "cover_letter",
77249
77386
  coverLetter.id
77250
77387
  );
77388
+ if (coverLetter.job_id) {
77389
+ timelineRepo.add({
77390
+ job_id: coverLetter.job_id,
77391
+ type: "cover_letter_version_saved",
77392
+ title: "\uC790\uAE30\uC18C\uAC1C\uC11C \uC791\uC131",
77393
+ summary: `${coverLetter.title} v${version2.version_no}`,
77394
+ payload: {
77395
+ cover_letter: coverLetterTimelineRef(coverLetter, version2)
77396
+ },
77397
+ occurred_at: version2.created_at
77398
+ });
77399
+ }
77251
77400
  return { coverLetter, version: version2, verification, forced };
77252
77401
  }
77253
- function updateApplicationStatus(jobId, status, note) {
77402
+ function updateApplicationStatus(jobId, status, note, submission) {
77254
77403
  const job = jobRepo.get(jobId);
77255
- if (!job) throw notFound(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${jobId}`);
77256
- const application = applicationRepo.setStatus(jobId, status, note);
77257
- activityRepo.log(
77258
- "application_status_changed",
77259
- `${job.company} \xB7 ${job.position} \uC0C1\uD0DC\uB97C '${APPLICATION_STATUS_LABELS[status]}'(\uC73C)\uB85C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.`,
77260
- "application",
77261
- application.id
77262
- );
77404
+ if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
77405
+ const before2 = applicationRepo.getByJob(jobId);
77406
+ const application = tx(() => {
77407
+ let application2 = applicationRepo.setStatus(jobId, status, note);
77408
+ const submittedDocs = (submission?.document_ids ?? []).map((docId) => documentRepo.get(docId)).filter((doc) => !!doc);
77409
+ const submittedCoverLetter = submission?.cover_letter_id ? coverLetterRepo.get(submission.cover_letter_id, true) : null;
77410
+ if (status === "applied" && submission) {
77411
+ const resumeLike = submittedDocs.find((doc) => doc.kind === "resume" || doc.kind === "career_description") ?? submittedDocs[0] ?? null;
77412
+ application2 = applicationRepo.upsert({
77413
+ job_id: jobId,
77414
+ resume_id: resumeLike?.id ?? application2.resume_id ?? void 0,
77415
+ cover_letter_id: submittedCoverLetter?.id ?? application2.cover_letter_id ?? void 0,
77416
+ applied_at: submission.submitted_at ?? application2.applied_at ?? void 0
77417
+ });
77418
+ }
77419
+ activityRepo.log(
77420
+ "application_status_changed",
77421
+ `${job.company} \xB7 ${job.position} \uC0C1\uD0DC\uB97C '${APPLICATION_STATUS_LABELS[status]}'(\uC73C)\uB85C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.`,
77422
+ "application",
77423
+ application2.id
77424
+ );
77425
+ const submittedAt = submission?.submitted_at?.trim();
77426
+ const channel = submission?.channel?.trim();
77427
+ const submissionPayload = submission && (submittedAt || channel || submittedCoverLetter || submittedDocs.length > 0) ? {
77428
+ submitted_at: submittedAt || null,
77429
+ channel: channel || null,
77430
+ cover_letter: submittedCoverLetter ? coverLetterTimelineRef(submittedCoverLetter, null, submission.cover_letter_version_id) : null,
77431
+ documents: submittedDocs.map(documentTimelineRef)
77432
+ } : null;
77433
+ timelineRepo.add({
77434
+ job_id: job.id,
77435
+ type: "application_status_changed",
77436
+ title: APPLICATION_STATUS_LABELS[status],
77437
+ summary: channel && status === "applied" ? `${channel}\uB85C \uC81C\uCD9C` : note ?? void 0,
77438
+ payload: {
77439
+ from_status: before2?.status ?? null,
77440
+ status,
77441
+ status_label: APPLICATION_STATUS_LABELS[status],
77442
+ note: note ?? null,
77443
+ submission: submissionPayload
77444
+ },
77445
+ occurred_at: submittedAtToOccurrence(submittedAt)
77446
+ });
77447
+ return application2;
77448
+ });
77263
77449
  const interview_unlocked = INTERVIEW_UNLOCK_STATUSES.includes(status);
77264
77450
  const hasPrep = !!interviewRepo.getByJob(jobId);
77265
- const hint = interview_unlocked && !hasPrep ? "\uC11C\uB958 \uB2E8\uACC4\uB97C \uD1B5\uACFC\uD588\uC5B4\uC694. \uC774 \uACF5\uACE0 \uAE30\uC900\uC73C\uB85C \uC608\uC0C1 \uBA74\uC811 \uC9C8\uBB38\uACFC 1\uBD84 \uC790\uAE30\uC18C\uAC1C\uB97C \uC900\uBE44\uD560\uAE4C\uC694? (save_interview_prep)" : null;
77451
+ const hint = interview_unlocked && !hasPrep ? "\uC11C\uB958 \uB2E8\uACC4\uB97C \uD1B5\uACFC\uD588\uC5B4\uC694. \uC774 \uACF5\uACE0 \uAE30\uC900\uC73C\uB85C \uC608\uC0C1 \uBA74\uC811 \uC9C8\uBB38\uACFC 1\uBD84 \uC790\uAE30\uC18C\uAC1C\uB97C \uC900\uBE44\uD560\uAE4C\uC694?" : null;
77266
77452
  return { application, job, interview_unlocked, hint };
77267
77453
  }
77268
77454
  function saveInterviewPrep(input) {
77269
77455
  const job = jobRepo.get(input.job_id);
77270
- if (!job) throw notFound(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.job_id}`);
77271
- const app = applicationRepo.getByJob(job.id);
77272
- if (!app || !INTERVIEW_UNLOCK_STATUSES.includes(app.status)) {
77273
- const current = app ? APPLICATION_STATUS_LABELS[app.status] : "\uBBF8\uC9C0\uC6D0";
77274
- throw conflict(
77275
- `\uBA74\uC811 \uC900\uBE44\uB294 '\uC11C\uB958 \uD569\uACA9' \uC774\uC0C1 \uC0C1\uD0DC\uC5D0\uC11C\uB9CC \uC800\uC7A5\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uD604\uC7AC \uC0C1\uD0DC: '${current}'. \uBA3C\uC800 update_application_status\uB85C \uC0C1\uD0DC\uB97C \uC62C\uB9B0 \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.`
77276
- );
77277
- }
77456
+ if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
77278
77457
  const prep = interviewRepo.save(input);
77279
77458
  activityRepo.log("interview_prep_saved", `${job.company} \xB7 ${job.position} \uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4.`, "interview_prep", prep.id);
77459
+ timelineRepo.add({
77460
+ job_id: job.id,
77461
+ type: "interview_prep_saved",
77462
+ title: "\uBA74\uC811 \uC900\uBE44",
77463
+ summary: prep.questions.length > 0 ? `\uC608\uC0C1 \uC9C8\uBB38 ${prep.questions.length}\uAC1C` : prep.notes ?? void 0,
77464
+ payload: {
77465
+ interview_prep_id: prep.id,
77466
+ question_count: prep.questions.length
77467
+ },
77468
+ occurred_at: prep.updated_at
77469
+ });
77280
77470
  return prep;
77281
77471
  }
77282
77472
 
@@ -77297,6 +77487,8 @@ var HUMANIZE_WRITING_GUIDE = `# \uC0AC\uB78C\uC774 \uC4F4 \uAC83\uCC98\uB7FC: \u
77297
77487
  \uBAA9\uD45C: \uC0AC\uC2E4\uACFC \uACBD\uD5D8\uC740 100% \uADF8\uB300\uB85C \uB450\uBA74\uC11C, "AI\uAC00 \uC4F4 \uD2F0"\uAC00 \uB098\uB294 \uBB38\uC7A5 \uC2B5\uAD00\uB9CC \uC81C\uAC70\uD55C\uB2E4.
77298
77488
  \uAC80\uC5F4\uAE30\uB97C \uC18D\uC774\uB294 \uAC8C \uC544\uB2C8\uB77C, \uCC44\uC6A9 \uB2F4\uB2F9\uC790\uAC00 \uC77D\uAE30\uC5D0 \uC790\uC5F0\uC2A4\uB7FD\uACE0 \uC0AC\uB78C \uB0C4\uC0C8 \uB098\uB294 \uAE00\uC744 \uC4F0\uB294 \uAC83\uC774 \uBAA9\uC801\uC774\uB2E4.
77299
77489
 
77490
+ > **\uC5B8\uC5B4 \uBC94\uC704:** \uC544\uB798 AI-tell \uBAA9\uB85D\uC740 \uD55C\uAD6D\uC5B4 \uAE00 \uAE30\uC900\uC774\uB2E4. \uC601\uC5B4 \uB4F1 \uB2E4\uB978 \uC5B8\uC5B4\uB85C \uC4F8 \uB54C\uB294 \uC774 \uBAA9\uB85D\uC744 \uADF8\uB300\uB85C \uC801\uC6A9\uD558\uC9C0 \uB9D0\uACE0, **\uAC19\uC740 \uC6D0\uB9AC**(\uD074\uB9AC\uC170\xB7\uAE30\uACC4\uC801 \uBCD1\uB82C\xB7\uACFC\uC7A5 \uC218\uC2DD\xB7\uACF5\uD5C8\uD55C \uC77C\uBC18\uB860 \uC81C\uAC70, \uAD6C\uCCB4\uC131, \uC0AC\uC2E4\xB7\uC218\uCE58 \uBCF4\uC874)\uB97C \uADF8 \uC5B8\uC5B4\uC758 \uC790\uC5F0\uC2A4\uB7EC\uC6B4 \uAD00\uC2B5\uC73C\uB85C \uC801\uC6A9\uD55C\uB2E4. \uACB0\uACFC\uBB3C\uC758 \uC5B8\uC5B4\uB294 \uB300\uC0C1 \uACF5\uACE0\xB7\uC2DC\uC7A5\uC744 \uB530\uB978\uB2E4.
77491
+
77300
77492
  ## \uC808\uB300 \uC6D0\uCE59
77301
77493
  - \uC800\uC7A5\uB41C \uC2E4\uC81C \uACBD\uD5D8\xB7\uC218\uCE58\xB7\uD68C\uC0AC\uBA85\xB7\uACE0\uC720\uBA85\uC0AC\xB7\uC9C1\uC811 \uC778\uC6A9\uC740 \uC808\uB300 \uBC14\uAFB8\uAC70\uB098 \uC9C0\uC5B4\uB0B4\uC9C0 \uC54A\uB294\uB2E4. \uBB38\uC7A5 \uACB0\uB9CC \uB2E4\uB4EC\uB294\uB2E4.
77302
77494
  - \uBE48\uCE78\uC774\uB098 \uD655\uC778\uC774 \uD544\uC694\uD55C \uBD80\uBD84\uC740 \uB9E4\uB044\uB7FD\uAC8C \uCC44\uC6CC \uB123\uC9C0 \uB9D0\uACE0 \uADF8\uB300\uB85C \uD45C\uC2DC\uD574 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBB3B\uB294\uB2E4.
@@ -77338,6 +77530,33 @@ var HUMANIZE_WRITING_GUIDE = `# \uC0AC\uB78C\uC774 \uC4F4 \uAC83\uCC98\uB7FC: \u
77338
77530
  - \uC18C\uB9AC \uB0B4\uC5B4 \uC77D\uC5C8\uC744 \uB54C \uC5B4\uC0C9\uD558\uAC70\uB098 "\uC0AC\uB78C\uC774 \uC774\uB807\uAC8C \uB9D0 \uC548 \uD558\uB294\uB370" \uC2F6\uC740 \uACF3\uC744 \uB2E4\uB4EC\uB294\uB2E4.
77339
77531
  - \uC0AC\uC2E4\xB7\uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uAC00 \uC6D0\uBCF8\uACFC 100% \uC77C\uCE58\uD558\uB294\uAC00?
77340
77532
  - \uC0AC\uC6A9\uC790\uC758 \uD1A4\uACFC \uAC15\uC870 \uD3EC\uC778\uD2B8\uAC00 \uC0B4\uC544 \uC788\uB294\uAC00?`;
77533
+ var STRUCTURED_DOC_WRITING_GUIDE = `# \uAD6C\uC870\uD615 \uBB38\uC11C(\uACBD\uB825\uAE30\uC220\uC11C\xB7\uC774\uB825\uC11C\xB7\uD3EC\uD2B8\uD3F4\uB9AC\uC624) \uBCF4\uC815
77534
+
77535
+ \uACBD\uB825\uAE30\uC220\uC11C\xB7\uC774\uB825\uC11C\xB7\uD3EC\uD2B8\uD3F4\uB9AC\uC624\uB294 \uCC44\uC6A9 \uB2F4\uB2F9\uC790\uAC00 \uCC98\uC74C\uBD80\uD130 \uB05D\uAE4C\uC9C0 \uC77D\uB294 \uAE00\uC774 \uC544\uB2C8\uB77C \uBA87 \uCD08 \uC548\uC5D0 "\uC2A4\uCE94"\uD558\uB294 \uBB38\uC11C\uB2E4. \uBD88\uB9BF, \uBC88\uD638 \uB9E4\uAE34 \uC131\uACFC, \uC9C1\uBB34\xB7\uAE30\uAC04\xB7\uC5ED\uD560\uC744 \uAD6C\uBD84\uD55C \uC139\uC158 \uAD6C\uC870\uB294 AI\uAC00 \uC4F4 \uD2F0\uAC00 \uC544\uB2C8\uB77C \uC774 \uBB38\uC11C\uC758 \uC815\uC11D\uC774\uB2E4. \uC790\uAE30\uC18C\uAC1C\uC11C\uCC98\uB7FC \uC904\uAE00(\uC0B0\uBB38)\uB85C \uD480\uC9C0 \uB9D0\uACE0 \uAD6C\uC870\uB97C \uBCF4\uC874\uD55C\uB2E4.
77536
+
77537
+ ## \uC704 8\uAC1C AI-tell \uC911 \uAD6C\uC870\uD615\uC5D0\uC11C \uB2E4\uB974\uAC8C \uC801\uC6A9\uD558\uB294 \uAC83
77538
+ - 3\uBC88(\uAE30\uACC4\uC801 \uBCD1\uB82C)\xB78\uBC88(\uD615\uC2DD \uACFC\uC6A9)\uC740 \uC0B0\uBB38 \uBB38\uC7A5\uC5D0\uB9CC \uC801\uC6A9\uD55C\uB2E4. \uC131\uACFC\uB97C \uB098\uC5F4\uD558\uB294 \uBD88\uB9BF\xB7\uBC88\uD638 \uBAA9\uB85D \uAD6C\uC870 \uC790\uCCB4\uC5D0\uB294 \uC801\uC6A9\uD558\uC9C0 \uC54A\uB294\uB2E4 \u2014 \uBD88\uB9BF\uB85C \uB04A\uC5B4 \uC4F0\uB294 \uAC83\uC740 \uAC00\uB3C5\uC131\uC744 \uC704\uD55C \uC815\uC0C1 \uAD6C\uC870\uB2E4.
77539
+ - \uB2E8 \uC758\uBBF8 \uC5C6\uB294 "\uCCAB\uC9F8\xB7\uB458\uC9F8\xB7\uC14B\uC9F8" \uC11C\uC218 \uB098\uC5F4, \uBAA8\uB4E0 \uBD88\uB9BF\uC744 \uB611\uAC19\uC740 \uACE8\uACA9("~\uC744 \uD1B5\uD574 ~\uC744 \uD568")\uC73C\uB85C \uCC0D\uC5B4\uB0B4\uB294 \uAC83, \uACFC\uD55C \uAD75\uAC8C\xB7\uC774\uBAA8\uC9C0\xB7\uAC00\uC6B4\uB383\uC810 \uB5A1\uCE60\uC740 \uAD6C\uC870\uD615\uC5D0\uC11C\uB3C4 \uC81C\uAC70\uD55C\uB2E4.
77540
+
77541
+ ## \uAD6C\uC870\uB294 \uC0B4\uB9AC\uACE0, \uBD88\uB9BF '\uC548\uC758 \uC5B8\uC5B4'\uB9CC \uC0AC\uB78C\uB2F5\uAC8C
77542
+ - \uAC01 \uBD88\uB9BF\uC740 \uB3D9\uC0AC\uB098 \uC218\uCE58\uB85C \uC2DC\uC791\uD558\uACE0, \uAC80\uC99D \uAC00\uB2A5\uD55C \uC0AC\uC2E4(\uC22B\uC790\xB7\uAE30\uAC04\xB7\uB3C4\uAD6C\xB7\uC5ED\uD560\xB7\uACB0\uACFC)\uC744 \uD55C \uC904\uC5D0 \uB2F4\uB294\uB2E4.
77543
+ - \uBD88\uB9BF \uC548\uC758 \uBC88\uC5ED\uD22C\xB7\uD074\uB9AC\uC170\xB7\uACF5\uD5C8\uD55C \uC77C\uBC18\uB860\xB7\uC218\uB3D9\uD0DC\uB294 \uC790\uAE30\uC18C\uAC1C\uC11C\uC640 \uB611\uAC19\uC774 \uC81C\uAC70\uD55C\uB2E4. (\uC608: "\uBD84\uC11D\uC744 \uC9C4\uD589\uD558\uC600\uC2B5\uB2C8\uB2E4" \u2192 "\uBD84\uC11D\uD588\uB2E4", "\uB9CE\uC740 \uC131\uACFC\uB97C \uB0C8\uC2B5\uB2C8\uB2E4" \u2192 \uC2E4\uC81C \uC218\uCE58)
77544
+ - STAR\uC744 \uD55C\uB450 \uC904\uB85C \uC555\uCD95\uD55C\uB2E4: \uBB34\uC5C7\uC744 \uC65C \uD588\uACE0 \uACB0\uACFC \uC218\uCE58\uAC00 \uC5B4\uB5BB\uAC8C \uB410\uB294\uC9C0.
77545
+
77546
+ ## Do / Don't
77547
+ - Do: \uB450\uAD04\uC2DD \uBD88\uB9BF(\uACB0\uACFC\xB7\uC218\uCE58\uB97C \uC55E\uC138\uC6C0), \uC9C1\uBB34\xB7\uAE30\uAC04\xB7\uC5ED\uD560 \uAD6C\uBD84, \uC131\uACFC\uB97C \uC22B\uC790\uB85C.
77548
+ - Don't: \uACBD\uB825\uAE30\uC220\uC11C\uB97C \uC790\uAE30\uC18C\uAC1C\uC11C\uCC98\uB7FC \uC904\uAE00\uB85C \uD3C9\uD0C4\uD654\uD558\uC9C0 \uC54A\uB294\uB2E4. \uBD88\uB9BF\uC744 \uC5C6\uC560 \uC2A4\uCE94\uD558\uAE30 \uC5B4\uB835\uAC8C \uB9CC\uB4E4\uC9C0 \uC54A\uB294\uB2E4. \uB77C\uBCA8\xB7\uAD6C\uC870\uB97C \uB2E4 \uAC77\uC5B4\uB0B4 \uAC00\uB3C5\uC131\uC744 \uB5A8\uC5B4\uB728\uB9AC\uC9C0 \uC54A\uB294\uB2E4.
77549
+
77550
+ \uD575\uC2EC: \uC790\uAE30\uC18C\uAC1C\uC11C\uB294 \uC0AC\uB78C\uC758 \uBAA9\uC18C\uB9AC(\uC0B0\uBB38)\uB85C, \uACBD\uB825\uAE30\uC220\uC11C\xB7\uC774\uB825\uC11C\uB294 \uC0AC\uB78C\uC758 \uC5B8\uC5B4\uB85C \uC4F4 \uAE54\uB054\uD55C \uAD6C\uC870\uB85C. \uB458 \uB2E4 \uC0AC\uC2E4\xB7\uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uB294 100% \uBCF4\uC874\uD55C\uB2E4.`;
77551
+ var STRUCTURED_DOC_TYPES = /* @__PURE__ */ new Set(["career_description", "resume", "portfolio"]);
77552
+ function getWritingStyleGuide(docType) {
77553
+ if (docType && STRUCTURED_DOC_TYPES.has(docType)) {
77554
+ return `${HUMANIZE_WRITING_GUIDE}
77555
+
77556
+ ${STRUCTURED_DOC_WRITING_GUIDE}`;
77557
+ }
77558
+ return HUMANIZE_WRITING_GUIDE;
77559
+ }
77341
77560
 
77342
77561
  // packages/workflows/src/definitions.ts
77343
77562
  var WORKFLOWS = [
@@ -77348,7 +77567,7 @@ var WORKFLOWS = [
77348
77567
  trigger: '\uC0AC\uC6A9\uC790\uAC00 \uCC98\uC74C CareerMate\uC5D0 \uC5F0\uACB0\uD588\uAC70\uB098, "\uC2DC\uC791\uD558\uC790 / \uC14B\uC5C5\uD574\uC918 / \uD504\uB85C\uD544 \uB4F1\uB85D"\uC774\uB77C\uACE0 \uC694\uCCAD\uD560 \uB54C.',
77349
77568
  steps: [
77350
77569
  "`get_onboarding_status`\uB97C \uD638\uCD9C\uD574 \uD604\uC7AC \uB4F1\uB85D \uC0C1\uD0DC(has_profile, has_resume, has_cover_letter, has_experience, has_skills, profile_completeness, next_steps)\uB97C \uD655\uC778\uD55C\uB2E4.",
77351
- '\uBE44\uC5B4 \uC788\uB294 \uD56D\uBAA9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC548\uB0B4\uD558\uACE0, \uBA3C\uC800 "\uAE30\uC874\uC5D0 \uC791\uC131\uD574 \uB454 \uC774\uB825\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C \uD30C\uC77C\uC774 \uC788\uB294\uC9C0" \uBB3B\uB294\uB2E4(hwp/docx/pdf \uBAA8\uB450 \uAC00\uB2A5).',
77570
+ '\uBE44\uC5B4 \uC788\uB294 \uD56D\uBAA9\uC744 \uC0AC\uC6A9\uC790\uC758 \uC5B8\uC5B4\uB85C \uC27D\uAC8C \uC548\uB0B4\uD558\uACE0, \uBA3C\uC800 "\uAE30\uC874\uC5D0 \uC791\uC131\uD574 \uB454 \uC774\uB825\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C\xB7\uC790\uAE30\uC18C\uAC1C\uC11C \uD30C\uC77C\uC774 \uC788\uB294\uC9C0" \uBB3B\uB294\uB2E4(hwp/docx/pdf \uBAA8\uB450 \uAC00\uB2A5).',
77352
77571
  '\uD30C\uC77C \uC785\uB825 \uBD84\uAE30: \u2460 \uC0AC\uC6A9\uC790\uAC00 "\uD30C\uC77C\uC774 \uC788\uB2E4"\uACE0 \uD558\uBA74 `open_inbox`\uB85C \uC778\uC785 \uD3F4\uB354\uB97C \uC5F4\uC5B4 \uD30C\uC77C\uC744 \uB123\uAC8C \uD558\uACE0, "\uB2E4 \uB123\uC5C8\uB2E4"\uACE0 \uD558\uBA74 `read_inbox`\uB85C \uBCF8\uBB38\uC744 \uC77D\uB294\uB2E4(pdf\xB7\uC774\uBBF8\uC9C0\uB294 read_inbox\uAC00 \uACBD\uB85C\uB9CC \uC8FC\uBBC0\uB85C \uD074\uB77C\uC774\uC5B8\uD2B8\uC758 \uD30C\uC77C \uC77D\uAE30\uB85C \uC9C1\uC811 \uC77D\uB294\uB2E4). \u2461 \uC0AC\uC6A9\uC790\uAC00 \uD30C\uC77C \uACBD\uB85C\uB97C \uC9C1\uC811 \uC54C\uB824\uC8FC\uBA74 `read_document`\uB85C \uC77D\uB294\uB2E4. \u2462 "\uC5C6\uB2E4/\uC9C1\uC811 \uC785\uB825/\uD14D\uC2A4\uD2B8\uB85C \uBD99\uC5EC\uB123\uAE30"\uBA74 \uD3F4\uB354\uB97C \uC5F4\uC9C0 \uB9D0\uACE0 \uBC1B\uC740 \uD14D\uC2A4\uD2B8\uB97C \uADF8\uB300\uB85C \uC4F4\uB2E4. (\uBB3B\uC9C0\uB3C4 \uC54A\uACE0 open_inbox\uB85C \uD3F4\uB354\uB97C \uC790\uB3D9\uC73C\uB85C \uC5F4\uC9C0 \uC54A\uB294\uB2E4.)',
77353
77572
  "\uC218\uC9D1\uD55C \uC815\uBCF4\uB97C \uAD6C\uC870\uD654\uD574 `save_profile`\uB85C \uC800\uC7A5\uD55C\uB2E4(\uC774\uB984\xB7\uC5F0\uB77D\uCC98\xB7headline\xB7summary\xB7desired_roles\xB7desired_conditions, \uADF8\uB9AC\uACE0 \uAE00\uC4F0\uAE30 \uC120\uD638\uC778 preferred_tone\uACFC emphasis_points \uD3EC\uD568). \uD30C\uC77C \uCD9C\uCC98\uB294 source=upload, \uC9C1\uC811 \uC785\uB825\uC740 manual.",
77354
77573
  "\uC5C5\uB85C\uB4DC\uD55C \uC774\uB825\uC11C/\uACBD\uB825\uAE30\uC220\uC11C/\uD3EC\uD2B8\uD3F4\uB9AC\uC624 \uBCF8\uBB38\uC744 `add_resume`\uB85C \uC800\uC7A5\uD55C\uB2E4(kind\xB7title \uC9C0\uC815, \uB300\uD45C \uBB38\uC11C\uB294 is_primary=true).",
@@ -77369,9 +77588,9 @@ var WORKFLOWS = [
77369
77588
  "\uACF5\uACE0 \uC694\uAD6C\uC0AC\uD56D\uACFC \uC0AC\uC6A9\uC790 \uACBD\uB825/\uC2A4\uD0AC/\uD504\uB85C\uC81D\uD2B8\uB97C \uBE44\uAD50\uD55C\uB2E4: \uC77C\uCE58\uD558\uB294 \uAC15\uC810(strengths)\uACFC \uBD80\uC871\uD55C \uBD80\uBD84(gaps), \uB9E4\uCE6D \uD0A4\uC6CC\uB4DC(matched_keywords)\uC640 \uB204\uB77D \uD0A4\uC6CC\uB4DC(missing_keywords)\uB97C \uB3C4\uCD9C\uD558\uACE0, \uC885\uD569 \uC801\uD569\uB3C4 \uC810\uC218(score 0~100)\uC640 \uC694\uC57D(summary), \uC9C0\uC6D0 \uC804\uB7B5 \uC81C\uC548(recommendations)\uC744 \uC815\uB9AC\uD55C\uB2E4. \uC5C6\uB294 \uACBD\uD5D8\uC744 \uC9C0\uC5B4\uB0B4\uC9C0 \uC54A\uB294\uB2E4.",
77370
77589
  "\uC544\uC9C1 \uC800\uC7A5\uB418\uC9C0 \uC54A\uC740 \uACF5\uACE0\uB77C\uBA74 `save_job_posting`\uC73C\uB85C \uACF5\uACE0\uB97C \uC800\uC7A5\uD558\uACE0 job_id\uB97C \uD655\uBCF4\uD55C\uB2E4.",
77371
77590
  "\uB3C4\uCD9C\uD55C \uBD84\uC11D \uACB0\uACFC\uB97C `save_fit_analysis`\uB85C \uC800\uC7A5\uD55C\uB2E4(job_id, score, summary, strengths, gaps, matched_keywords, missing_keywords, recommendations).",
77372
- "\uBD84\uC11D \uACB0\uACFC\uB97C \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC124\uBA85\uD55C\uB2E4(\uC798 \uB9DE\uB294 \uC810, \uBCF4\uC644\uD560 \uC810, \uCD94\uCC9C \uC804\uB7B5). \uAE30\uC220 \uC6A9\uC5B4\uB294 \uBE7C\uACE0 \uC804\uB2EC\uD55C\uB2E4.",
77373
- '"\uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uB4DC\uB9B4\uAE4C\uC694?"\uB77C\uACE0 \uC790\uAE30\uC18C\uAC1C\uC11C \uC791\uC131\uC744 \uC81C\uC548\uD55C\uB2E4.',
77374
- '\uC0AC\uC6A9\uC790\uAC00 \uB3D9\uC758\uD558\uBA74 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC791\uC131\uD558\uAE30 \uC9C1\uC804\uC5D0 `get_writing_style_guide`\uB97C \uD638\uCD9C\uD574 "AI \uD2F0 \uC548 \uB098\uB294 \uAE00\uC4F0\uAE30 \uADDC\uCE59"\uC744 \uAC00\uC838\uC628\uB2E4.',
77591
+ "\uBD84\uC11D \uACB0\uACFC\uB97C \uC0AC\uC6A9\uC790\uC758 \uC5B8\uC5B4\uB85C \uC27D\uAC8C \uC124\uBA85\uD55C\uB2E4(\uC798 \uB9DE\uB294 \uC810, \uBCF4\uC644\uD560 \uC810, \uCD94\uCC9C \uC804\uB7B5). \uAE30\uC220 \uC6A9\uC5B4\uB294 \uBE7C\uACE0 \uC804\uB2EC\uD55C\uB2E4.",
77592
+ '\uBD84\uC11D \uB4A4 \uACE7\uC7A5 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC4F0\uC9C0 \uB9D0\uACE0, "\uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uB4DC\uB9B4\uAE4C\uC694?"\uB77C\uACE0 \uBB3B\uACE0 \uC120\uD0DD\uC9C0\uB97C \uC81C\uC2DC\uD55C\uB2E4: \u2460 "\uB124, \uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uC918" \u2461 "\uC544\uB2C8\uC694, \uBD84\uC11D\uB9CC \uC800\uC7A5\uD574\uC918".',
77593
+ '\uC0AC\uC6A9\uC790\uAC00 \u2460\uC744 \uC120\uD0DD\uD558\uBA74 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC791\uC131\uD558\uAE30 \uC9C1\uC804\uC5D0 `get_writing_style_guide`\uB97C \uD638\uCD9C\uD574 "AI \uD2F0 \uC548 \uB098\uB294 \uAE00\uC4F0\uAE30 \uADDC\uCE59"\uC744 \uAC00\uC838\uC628\uB2E4. \u2461\uB97C \uC120\uD0DD\uD558\uBA74 \uC790\uC18C\uC11C \uC791\uC131 \uC5C6\uC774 \uBA48\uCD94\uACE0 \uC800\uC7A5 \uACB0\uACFC\uC640 \uB300\uC2DC\uBCF4\uB4DC \uD655\uC778\uB9CC \uC548\uB0B4\uD55C\uB2E4.',
77375
77594
  "\uC801\uD569\uB3C4 \uBD84\uC11D\xB7\uAE00\uC4F0\uAE30 \uC120\uD638\uC640 \uAE00\uC4F0\uAE30 \uADDC\uCE59\uC744 \uD568\uAED8 \uBC18\uC601\uD574 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC791\uC131\uD558\uACE0(\uBC88\uC5ED\uD22C\xB7\uD074\uB9AC\uC170\xB7\uAE30\uACC4\uC801 \uBCD1\uB82C \uC81C\uAC70, \uC0AC\uC2E4\xB7\uC218\uCE58\uB294 \uADF8\uB300\uB85C), `save_cover_letter_version`\uC73C\uB85C \uC800\uC7A5\uD55C\uB2E4(job_id \uC5F0\uACB0, note\uC5D0 \uC791\uC131 \uC758\uB3C4 \uC694\uC57D).",
77376
77595
  "`open_application`\uC73C\uB85C \uD574\uB2F9 \uC9C0\uC6D0 \uAC74\uC744 \uC5F4\uC5B4 \uC0AC\uC6A9\uC790\uAC00 \uACB0\uACFC\uB97C \uD655\uC778\uD558\uAC8C \uD558\uACE0, \uC9C0\uC6D0 \uC0C1\uD0DC \uBCC0\uACBD(\uC608: \uC9C0\uC6D0 \uC608\uC815/\uC9C0\uC6D0 \uC644\uB8CC)\uC744 \uC81C\uC548\uD55C\uB2E4."
77377
77596
  ]
@@ -77388,7 +77607,24 @@ var WORKFLOWS = [
77388
77607
  "\uC800\uC7A5\uB41C \uC2E4\uC81C \uACBD\uD5D8\xB7\uC131\uACFC\uB9CC\uC73C\uB85C \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC791\uC131\uD558\uB418, \uC704 \uAE00\uC4F0\uAE30 \uADDC\uCE59\uC744 \uC801\uC6A9\uD574 \uC0AC\uB78C\uC774 \uC4F4 \uB4EF \uC790\uC5F0\uC2A4\uB7FD\uAC8C \uC4F4\uB2E4(\uC0AC\uC2E4\xB7\uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uB294 \uADF8\uB300\uB85C). \uBE48\uCE78\uC774\uB098 \uD655\uC778\uC774 \uD544\uC694\uD55C \uBD80\uBD84\uC740 \uCD94\uCE21\uD558\uC9C0 \uB9D0\uACE0 \uD45C\uC2DC\uD574 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBB3B\uB294\uB2E4.",
77389
77608
  "\uC644\uC131\uBCF8\uC744 `save_cover_letter_version`\uC73C\uB85C \uC800\uC7A5\uD55C\uB2E4(\uAE30\uC874 \uC790\uAE30\uC18C\uAC1C\uC11C\uBA74 cover_letter_id, \uC0C8\uB85C \uB9CC\uB4E4\uBA74 title, \uACF5\uACE0\uC6A9\uC774\uBA74 job_id, \uBCC0\uACBD \uC694\uC57D\uC740 note, source=ai).",
77390
77609
  "\uC0AC\uC6A9\uC790\uAC00 \uC6D0\uD558\uBA74 `export_cover_letter`\uB85C \uD30C\uC77C\uB85C \uB0B4\uBCF4\uB0B4\uACE0, \uCD94\uAC00 \uC218\uC815 \uC694\uCCAD\uC774 \uC788\uC73C\uBA74 \uC0C8 \uBC84\uC804\uC73C\uB85C \uB2E4\uC2DC \uC800\uC7A5\uD55C\uB2E4.",
77391
- "\uC800\uC7A5 \uACB0\uACFC\uB97C \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC54C\uB9AC\uACE0, \uC9C0\uC6D0 \uC0C1\uD0DC \uC5C5\uB370\uC774\uD2B8(`update_application_status`)\uB098 \uBA74\uC811 \uC900\uBE44 \uB4F1 \uB2E4\uC74C \uB2E8\uACC4\uB97C \uC81C\uC548\uD55C\uB2E4."
77610
+ "\uC800\uC7A5 \uACB0\uACFC\uB97C \uC0AC\uC6A9\uC790\uC758 \uC5B8\uC5B4\uB85C \uC54C\uB9AC\uACE0, \uC9C0\uC6D0 \uC0C1\uD0DC \uC5C5\uB370\uC774\uD2B8(`update_application_status`)\uB098 \uBA74\uC811 \uC900\uBE44 \uB4F1 \uB2E4\uC74C \uB2E8\uACC4\uB97C \uC81C\uC548\uD55C\uB2E4."
77611
+ ]
77612
+ },
77613
+ {
77614
+ id: "write_career_description",
77615
+ title: "\uACBD\uB825\uAE30\uC220\uC11C \uC791\uC131",
77616
+ description: "\uD504\uB85C\uD544\xB7\uC774\uB825\uC11C\xB7\uACBD\uB825\xB7\uD504\uB85C\uC81D\uD2B8\xB7\uC2A4\uD0AC\uC744 \uADFC\uAC70\uB85C \uC2E4\uC81C \uC81C\uCD9C \uAC00\uB2A5\uD55C \uACBD\uB825\uAE30\uC220\uC11C\uB97C \uC791\uC131\uD558\uACE0 \uBB38\uC11C\uD568\uC5D0 \uC800\uC7A5\uD558\uB294 \uD750\uB984.",
77617
+ trigger: '\uC0AC\uC6A9\uC790\uAC00 "\uACBD\uB825\uAE30\uC220\uC11C \uC368\uC918 / \uACBD\uB825\uAE30\uC220\uC11C \uB9CC\uB4E4\uC5B4\uC918 / \uB0B4 \uACBD\uB825 \uC815\uB9AC\uD574\uC918"\uB77C\uACE0 \uC694\uCCAD\uD560 \uB54C.',
77618
+ steps: [
77619
+ "`get_onboarding_status`\uB85C \uC900\uBE44 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uACE0, `get_profile`\xB7`get_resumes`\xB7`get_experiences`\xB7`get_projects`\xB7`get_skills`\uB97C \uD638\uCD9C\uD574 \uAE30\uC874 \uD504\uB85C\uD544, \uC774\uB825\uC11C, \uACBD\uB825\uAE30\uC220\uC11C(kind=career_description), \uACBD\uB825, \uD504\uB85C\uC81D\uD2B8, \uAE30\uC220\uC2A4\uD0DD\uC744 \uBAA8\uB450 \uD655\uC778\uD55C\uB2E4.",
77620
+ "\uC0AC\uC6A9\uC790\uAC00 \uD30C\uC77C \uACBD\uB85C\uB97C \uC8FC\uBA74 `read_document`\uB85C \uC77D\uACE0, \uC778\uC785 \uD3F4\uB354\uB97C \uC0AC\uC6A9\uD588\uB2E4\uBA74 `read_inbox`\uB85C \uBCF8\uBB38\uC744 \uC77D\uB294\uB2E4. \uC774\uBBF8 \uC800\uC7A5\uB41C \uC790\uB8CC\uC640 \uC0C8 \uD30C\uC77C\uC744 \uBE44\uAD50\uD574 \uC911\uBCF5\xB7\uC0C1\uCDA9\uB418\uB294 \uC0AC\uC2E4\uC740 \uD45C\uC2DC\uD558\uACE0 \uCD94\uCE21\uD558\uC9C0 \uC54A\uB294\uB2E4.",
77621
+ '`get_workflow_guide({ workflow_id: "write_career_description" })`\uAC00 \uC548\uB0B4\uD558\uB294 \uC804\uBB38\uAC00 \uC808\uCC28\uB97C \uB530\uB978\uB2E4. \uC791\uC131 \uC9C1\uC804 `get_playbook({ domain: "resume" })`\uC640 `get_playbook({ domain: "ats" })`\uB97C \uBC1B\uC544 \uC2E4\uC81C \uACBD\uB825\uAE30\uC220\uC11C \uD615\uC2DD\uACFC ATS \uCE5C\uD654\uC131\uC744 \uC801\uC6A9\uD55C\uB2E4.',
77622
+ "\uC791\uC131 \uC804\uC5D0 \uBAA9\uC801\uC744 \uD655\uC778\uD55C\uB2E4: \uBC94\uC6A9 \uB9C8\uC2A4\uD130\uBCF8\uC778\uC9C0, \uD2B9\uC815 \uC9C1\uBB34/\uACF5\uACE0\uC6A9\uC778\uC9C0, \uC2E0\uC785/\uBB34\uACBD\uB825\uC774\uB77C \uD504\uB85C\uC81D\uD2B8\xB7\uAD50\uC721\xB7\uD65C\uB3D9 \uC911\uC2EC \uC5ED\uB7C9\uAE30\uC220\uC11C\uB85C \uC4F8\uC9C0, \uBD84\uB7C9\uACFC \uD1A4\uC740 \uC5B4\uB5BB\uAC8C \uD560\uC9C0 \uBB3B\uB294\uB2E4.",
77623
+ "\uACBD\uB825\uAE30\uC220\uC11C\uB294 \uC790\uAE30\uC18C\uAC1C\uC11C\uCC98\uB7FC \uC11C\uC0AC\uB85C \uC4F0\uC9C0 \uC54A\uB294\uB2E4. \uC81C\uBAA9, \uD575\uC2EC \uC694\uC57D, \uD575\uC2EC \uC5ED\uB7C9, \uACBD\uB825/\uD504\uB85C\uC81D\uD2B8\uBCC4 \uC5ED\uD560\xB7\uAE30\uAC04\xB7\uB3C4\uAD6C\xB7\uBB38\uC81C\xB7\uD589\uB3D9\xB7\uC131\uACFC\uB97C \uD45C\uB098 \uBD88\uB9BF \uC911\uC2EC\uC73C\uB85C \uC815\uB9AC\uD55C\uB2E4. \uC218\uCE58\xB7\uD68C\uC0AC\uBA85\xB7\uAE30\uAC04\xB7\uC9C1\uCC45\uC740 \uC800\uC7A5\uB41C \uC0AC\uC2E4\uB9CC \uC4F0\uACE0, \uC5C6\uB294 \uC131\uACFC \uC218\uCE58\uB294 \uB9CC\uB4E4\uC9C0 \uC54A\uB294\uB2E4.",
77624
+ '\uBB34\uACBD\uB825/\uBE44\uAC1C\uBC1C\uC790\uB77C\uBA74 \uACBD\uB825\uC744 \uB0A0\uC870\uD558\uC9C0 \uB9D0\uACE0 \uAD50\uC721\xB7\uD504\uB85C\uC81D\uD2B8\xB7\uC6B4\uC601 \uACBD\uD5D8\xB7\uC5C5\uBB34 \uB3C4\uAD6C\xB7\uD611\uC5C5 \uACBD\uD5D8\xB7\uC131\uACFC \uC99D\uAC70 \uC911\uC2EC\uC758 "\uC5ED\uB7C9\uAE30\uC220\uC11C"\uB85C \uAD6C\uC131\uD55C\uB2E4.',
77625
+ '\uC800\uC7A5 \uC9C1\uC804 `get_verifier({ id: "truthfulness" })`, `get_verifier({ id: "consistency" })`, `get_verifier({ id: "ats-compat" })`\uB97C \uBC1B\uC544 \uB2F9\uC2E0\uC774 \uC9C1\uC811 \uC810\uAC80\uD558\uACE0, \uD5C8\uC704\xB7\uC0C1\uCDA9\xB7\uD0A4\uC6CC\uB4DC \uB204\uB77D\uC744 \uACE0\uCE5C\uB2E4. \uD655\uC778\uC774 \uD544\uC694\uD55C \uC0AC\uC2E4\uC740 \uBCF8\uBB38\uC5D0 \uD45C\uC2DC\uD558\uACE0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBB3B\uB294\uB2E4.',
77626
+ "\uC644\uC131\uBCF8\uC744 `add_resume`\uC73C\uB85C \uC800\uC7A5\uD55C\uB2E4(kind=career_description, source=ai, title\uC740 \uC0AC\uC6A9 \uBAA9\uC801\uC774 \uB4DC\uB7EC\uB098\uAC8C \uC791\uC131, tags\uC5D0 \uC9C1\uBB34/\uC6A9\uB3C4 \uD0A4\uC6CC\uB4DC \uD3EC\uD568). \uB300\uD45C \uBB38\uC11C\uB85C \uC0BC\uC544\uB3C4 \uB418\uB294\uC9C0 \uD655\uC778\uB41C \uACBD\uC6B0\uC5D0\uB9CC is_primary=true\uB97C \uC4F4\uB2E4.",
77627
+ "\uC800\uC7A5 \uACB0\uACFC\uB97C \uC0AC\uC6A9\uC790\uC758 \uC5B8\uC5B4\uB85C \uC54C\uB9AC\uACE0, \uBB38\uC11C \uD0ED\uC5D0\uC11C \uD655\uC778\uD560 \uC218 \uC788\uC74C\uC744 \uC548\uB0B4\uD55C\uB2E4. \uD2B9\uC815 \uACF5\uACE0\uC6A9\uC73C\uB85C \uC791\uC131\uD588\uB2E4\uBA74 \uC774\uC5B4\uC11C \uC790\uAE30\uC18C\uAC1C\uC11C\uB098 \uBA74\uC811 \uC900\uBE44\uB97C \uC81C\uC548\uD55C\uB2E4."
77392
77628
  ]
77393
77629
  },
77394
77630
  {
@@ -77399,7 +77635,7 @@ var WORKFLOWS = [
77399
77635
  steps: [
77400
77636
  "\uD544\uC694\uD558\uBA74 `get_application_context` \uB610\uB294 `get_job_posting`\uC73C\uB85C \uB300\uC0C1 \uC9C0\uC6D0 \uAC74\uACFC \uACF5\uACE0\uB97C \uD655\uC778\uD55C\uB2E4.",
77401
77637
  "\uC801\uC808\uD55C \uC0C1\uD0DC\uB85C `update_application_status`\uB97C \uD638\uCD9C\uD55C\uB2E4. 8\uB2E8\uACC4: draft(\uC791\uC131 \uC911), planned(\uC9C0\uC6D0 \uC608\uC815), applied(\uC9C0\uC6D0 \uC644\uB8CC), document_passed(\uC11C\uB958 \uD569\uACA9), interview(\uBA74\uC811 \uC9C4\uD589), final_passed(\uCD5C\uC885 \uD569\uACA9), rejected(\uBD88\uD569\uACA9), on_hold(\uBCF4\uB958). \uBCC0\uACBD \uC0AC\uC720\uB294 note\uC5D0 \uB0A8\uAE34\uB2E4.",
77402
- "\uBCC0\uACBD \uACB0\uACFC\uB97C \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC54C\uB9B0\uB2E4.",
77638
+ "\uBCC0\uACBD \uACB0\uACFC\uB97C \uC0AC\uC6A9\uC790\uC758 \uC5B8\uC5B4\uB85C \uC27D\uAC8C \uC54C\uB9B0\uB2E4.",
77403
77639
  "\uC0C1\uD0DC\uC5D0 \uB9DE\uB294 \uB2E4\uC74C \uB2E8\uACC4\uB97C \uC81C\uC548\uD55C\uB2E4: applied \u2192 \uACB0\uACFC \uAE30\uB2E4\uB9AC\uAE30 \uC548\uB0B4, document_passed/interview \u2192 \uBA74\uC811 \uC900\uBE44(prepare_interview) \uC81C\uC548, final_passed \u2192 \uCD95\uD558 \uBC0F \uB9C8\uBB34\uB9AC, rejected/on_hold \u2192 \uD68C\uACE0 \uB610\uB294 \uB2E4\uB978 \uACF5\uACE0 \uD0D0\uC0C9 \uC81C\uC548.",
77404
77640
  "\uD544\uC694\uD558\uBA74 `open_application`\uC73C\uB85C \uD574\uB2F9 \uC9C0\uC6D0 \uAC74\uC744 \uC5F4\uC5B4 \uBCF4\uC5EC\uC900\uB2E4."
77405
77641
  ]
@@ -77407,14 +77643,29 @@ var WORKFLOWS = [
77407
77643
  {
77408
77644
  id: "prepare_interview",
77409
77645
  title: "\uBA74\uC811 \uC900\uBE44",
77410
- description: "\uC11C\uB958 \uD569\uACA9 \uC774\uD6C4 \uC608\uC0C1 \uC9C8\uBB38\xB7STAR \uB2F5\uBCC0\xB71\uBD84 \uC790\uAE30\uC18C\uAC1C\uB97C \uC900\uBE44\uD574 \uC800\uC7A5\uD558\uB294 \uD750\uB984.",
77411
- trigger: '\uC9C0\uC6D0 \uC0C1\uD0DC\uAC00 \uC11C\uB958 \uD569\uACA9(document_passed) \uB610\uB294 \uBA74\uC811 \uC9C4\uD589(interview)\uC73C\uB85C \uBC14\uB00C\uC5C8\uC744 \uB54C, \uB610\uB294 \uC0AC\uC6A9\uC790\uAC00 "\uBA74\uC811 \uC900\uBE44 \uB3C4\uC640\uC918"\uB77C\uACE0 \uC694\uCCAD\uD560 \uB54C.',
77646
+ description: "\uACF5\uACE0 \uAE30\uC900 \uC608\uC0C1 \uC9C8\uBB38\xB7STAR \uB2F5\uBCC0\xB71\uBD84 \uC790\uAE30\uC18C\uAC1C\uB97C \uC900\uBE44\uD574 \uC800\uC7A5\uD558\uB294 \uD750\uB984.",
77647
+ trigger: '\uC0AC\uC6A9\uC790\uAC00 "\uBA74\uC811 \uC900\uBE44 \uB3C4\uC640\uC918"\uB77C\uACE0 \uC694\uCCAD\uD558\uAC70\uB098, \uC9C0\uC6D0 \uC0C1\uD0DC\uAC00 \uC11C\uB958 \uD569\uACA9(document_passed) \uB610\uB294 \uBA74\uC811 \uC9C4\uD589(interview)\uC73C\uB85C \uBC14\uB00C\uC5C8\uC744 \uB54C.',
77412
77648
  steps: [
77413
77649
  "`get_application_context`(job_id \uD3EC\uD568)\uB97C \uD638\uCD9C\uD574 \uACF5\uACE0, \uD504\uB85C\uD544, \uACBD\uB825/\uD504\uB85C\uC81D\uD2B8, \uC801\uD569\uB3C4 \uBD84\uC11D(\uAC15\uC810\xB7\uAC2D), \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uAC00\uC838\uC628\uB2E4.",
77414
77650
  "\uACF5\uACE0\uC640 \uC0AC\uC6A9\uC790 \uACBD\uD5D8\uC744 \uBC14\uD0D5\uC73C\uB85C \uC608\uC0C1 \uBA74\uC811 \uC9C8\uBB38(question, intent, followups, answer_outline)\uC744 \uB3C4\uCD9C\uD55C\uB2E4.",
77415
77651
  "\uD575\uC2EC \uACBD\uD5D8\uC5D0 \uB300\uD55C STAR \uAC00\uC774\uB4DC(question, situation, task, action, result)\uC640 1\uBD84 \uC790\uAE30\uC18C\uAC1C(self_introduction) \uCD08\uC548\uC744 \uC791\uC131\uD55C\uB2E4. \uC2E4\uC81C \uACBD\uD5D8\uB9CC \uC0AC\uC6A9\uD55C\uB2E4.",
77416
77652
  "\uC900\uBE44 \uB0B4\uC6A9\uC744 `save_interview_prep`\uB85C \uC800\uC7A5\uD55C\uB2E4(job_id, questions, star_guides, self_introduction, notes).",
77417
- "\uC900\uBE44 \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC694\uC57D\uD574 \uC804\uB2EC\uD558\uACE0, \uBAA8\uC758 \uBA74\uC811/\uCD94\uAC00 \uC9C8\uBB38 \uC5F0\uC2B5\uC774\uB098 \uC0C1\uD0DC \uC5C5\uB370\uC774\uD2B8(`update_application_status`\uB85C interview/final_passed)\uB97C \uC81C\uC548\uD55C\uB2E4."
77653
+ "\uC900\uBE44 \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC758 \uC5B8\uC5B4\uB85C \uC27D\uAC8C \uC694\uC57D\uD574 \uC804\uB2EC\uD558\uACE0, \uBAA8\uC758 \uBA74\uC811/\uCD94\uAC00 \uC9C8\uBB38 \uC5F0\uC2B5\uC774\uB098 \uC0C1\uD0DC \uC5C5\uB370\uC774\uD2B8(`update_application_status`\uB85C interview/final_passed)\uB97C \uC81C\uC548\uD55C\uB2E4."
77654
+ ]
77655
+ },
77656
+ {
77657
+ id: "build_personal_brand",
77658
+ title: "\uD37C\uC2A4\uB110 \uBE0C\uB79C\uB4DC (LinkedIn\xB7\uD3EC\uD2B8\uD3F4\uB9AC\uC624)",
77659
+ description: "LinkedIn \uD504\uB85C\uD544\uACFC \uD3EC\uD2B8\uD3F4\uB9AC\uC624 \uAC19\uC740 \uC790\uAE30\uD45C\uD604 \uC790\uC0B0\uC744 \uC9C1\uBB34\uC5D0 \uB9DE\uCDB0 \uC791\uC131\xB7\uCD5C\uC801\uD654\uD558\uB294 \uD750\uB984. \uC774\uB825\uC11C \uC784\uD3EC\uD2B8(\uC628\uBCF4\uB529)\uC640 \uBD84\uB9AC\uB41C \uBCC4\uB3C4 \uC791\uC5C5.",
77660
+ trigger: '\uC0AC\uC6A9\uC790\uAC00 "\uB9C1\uD06C\uB4DC\uC778 \uD504\uB85C\uD544 \uBD10\uC918 / \uD3EC\uD2B8\uD3F4\uB9AC\uC624 \uC815\uB9AC\uD574\uC918 / \uD37C\uC2A4\uB110 \uBE0C\uB79C\uB529 \uB3C4\uC640\uC918"\uB77C\uACE0 \uC694\uCCAD\uD560 \uB54C.',
77661
+ steps: [
77662
+ "`get_application_context`(\uAC00\uB2A5\uD558\uBA74 \uB300\uC0C1 job_id \uD3EC\uD568)\uB97C \uD638\uCD9C\uD574 \uD504\uB85C\uD544\xB7\uACBD\uB825\xB7\uD504\uB85C\uC81D\uD2B8\xB7\uC2A4\uD0AC\xB7\uB300\uD45C \uC774\uB825\uC11C\xB7\uAE00\uC4F0\uAE30 \uC120\uD638\uB97C \uAC00\uC838\uC628\uB2E4. \uD0C0\uAE43 \uACF5\uACE0\uAC00 \uC788\uC73C\uBA74 \uADF8 \uD0A4\uC6CC\uB4DC\uB97C \uC0AC\uC804 \uC785\uB825\uC73C\uB85C \uC4F4\uB2E4.",
77663
+ '`get_workflow_guide({ workflow_id: "build_personal_brand" })`\uAC00 \uC548\uB0B4\uD558\uB294 \uC804\uBB38\uAC00 \uC808\uCC28\uB97C \uB530\uB978\uB2E4. \uC791\uC131 \uC9C1\uC804 `get_playbook({ domain: "linkedin-profile" })`\xB7`get_playbook({ domain: "portfolio" })`\uB97C \uBC1B\uC544 \uD5E4\uB4DC\uB77C\uC778\xB7About\xB7Featured\xB73\uB9C9(problem\u2192approach\u2192impact) \uADDC\uBC94\uC744 \uC801\uC6A9\uD55C\uB2E4.',
77664
+ "\uBB34\uC5C7\uC744 \uB9CC\uB4E4\uC9C0 \uD655\uC778\uD55C\uB2E4(LinkedIn \uD504\uB85C\uD544\uB9CC/\uD3EC\uD2B8\uD3F4\uB9AC\uC624\uB9CC/\uB458 \uB2E4, \uD0C0\uAE43 \uC9C1\uBB34, \uAC15\uC870 \uD3EC\uC778\uD2B8). \uC800\uC7A5\uB41C \uC2E4\uC81C \uACBD\uD5D8\xB7\uC131\uACFC\uB9CC \uC4F0\uACE0, \uC5C6\uB294 \uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uB294 \uCD94\uCE21\uD558\uC9C0 \uB9D0\uACE0 `[\uD655\uC778 \uD544\uC694]`\uB85C \uD45C\uC2DC\uD574 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBB3B\uB294\uB2E4.",
77665
+ "LinkedIn: \uD5E4\uB4DC\uB77C\uC778(\uD575\uC2EC \uD0A4\uC6CC\uB4DC\uB97C \uC55E 120\uC790\uC5D0 front-load, \uD558\uB4DC \uC0C1\uD55C 220\uC790)\xB7About \uCCAB \uBB38\uC7A5 \uD6C5\xB7\uC815\uB7C9 \uC131\uACFC\xB7\uC9C1\uBB34 \uD0A4\uC6CC\uB4DC\uB97C \uC791\uC131. \uD3EC\uD2B8\uD3F4\uB9AC\uC624: \uC9C1\uBB34\uC5D0 \uB9DE\uB294 3~6\uAC1C \uD56D\uBAA9\uC744 problem\u2192approach\u2192impact 3\uB9C9\uC73C\uB85C \uAD6C\uC131\uD558\uACE0 \uBCF8\uC778 \uAE30\uC5EC(my_role)\uC640 \uC815\uB7C9 \uC784\uD329\uD2B8\uB97C \uBA85\uC2DC\uD55C\uB2E4.",
77666
+ '\uC800\uC7A5 \uC9C1\uC804 `get_verifier({ id: "human-voice" })`\xB7`get_verifier({ id: "truthfulness" })`\xB7`get_verifier({ id: "consistency" })`\uB97C \uBC1B\uC544 \uB2F9\uC2E0\uC774 \uC9C1\uC811 \uC810\uAC80\uD55C\uB2E4(AI \uD2F0\xB7\uD658\uAC01\xB7\uC0AC\uC2E4 \uBCF4\uC874). \uD0A4\uC6CC\uB4DC \uC0AC\uC804\uC774 \uBE44\uBA74 \uD0A4\uC6CC\uB4DC \uCCB4\uD06C\uB294 \uBCF4\uB958\uD55C\uB2E4.',
77667
+ "\uD3EC\uD2B8\uD3F4\uB9AC\uC624 \uBCF8\uBB38\uC740 `add_resume`(kind=portfolio)\uB85C \uC800\uC7A5\uD55C\uB2E4. LinkedIn \uD504\uB85C\uD544 \uCD08\uC548\uC740 \uC0AC\uC6A9\uC790\uAC00 \uBCF5\uC0AC\uD574 \uC4F0\uB3C4\uB85D \uC81C\uC2DC\uD558\uACE0(\uC804\uC6A9 \uC800\uC7A5\xB7\uAC80\uC99D \uB3C4\uAD6C validate_linkedin_profile\uC740 Phase B \uBBF8\uAD6C\uD604), \uC6D0\uD558\uBA74 \uD30C\uC77C\uB85C \uB0B4\uBCF4\uB0B8\uB2E4.",
77668
+ "\uC800\uC7A5 \uACB0\uACFC\uB97C \uC0AC\uC6A9\uC790\uC758 \uC5B8\uC5B4\uB85C \uC54C\uB9AC\uACE0 \uB300\uC2DC\uBCF4\uB4DC \uB9C1\uD06C\uB97C \uC548\uB0B4\uD558\uBA70, \uB124\uD2B8\uC6CC\uD0B9(\uCD94\uCC9C \uACBD\uB85C)\uC774\uB098 \uACF5\uACE0 \uC9C0\uC6D0\uC73C\uB85C \uC774\uC5B4\uAC08\uC9C0 \uC81C\uC548\uD55C\uB2E4."
77418
77669
  ]
77419
77670
  }
77420
77671
  ];
@@ -77498,7 +77749,7 @@ var CAREER_ROUTES = {
77498
77749
  verifierSequence: ["truthfulness", "consistency"]
77499
77750
  },
77500
77751
  analyze_job: {
77501
- eop: "job-analysis",
77752
+ eop: ["job-analysis", "fit-analysis"],
77502
77753
  expertSequence: ["fit-matching", "company-research"],
77503
77754
  verifierSequence: ["truthfulness", "consistency", "responsiveness-on-target"],
77504
77755
  loop: "draft_verify_revise"
@@ -77509,11 +77760,46 @@ var CAREER_ROUTES = {
77509
77760
  verifierSequence: ["human-voice", "truthfulness", "ats-compat", "consistency"],
77510
77761
  loop: "draft_verify_revise"
77511
77762
  },
77763
+ write_career_description: {
77764
+ eop: "career-description",
77765
+ expertSequence: ["resume", "ats"],
77766
+ verifierSequence: ["truthfulness", "consistency", "ats-compat"],
77767
+ loop: "draft_verify_revise"
77768
+ },
77512
77769
  prepare_interview: {
77513
77770
  eop: "interview-prep",
77514
- expertSequence: ["interview-behavioral", "interview-technical", "company-research"],
77771
+ expertSequence: ["recruiter-screen", "interview-behavioral", "interview-technical", "company-research"],
77515
77772
  verifierSequence: ["truthfulness", "consistency"],
77516
77773
  loop: "draft_verify_revise"
77774
+ },
77775
+ // R7 A2: manage_application_status had no route → rejection-triage / offer-evaluation /
77776
+ // salary-negotiation were orphaned (served only on-demand via get_playbook). Route the
77777
+ // decision-stage playbooks so they are pushed when the user manages application outcomes.
77778
+ // R7 A4: A2 routed the decision-stage playbooks, but the EOP-free lifecycle domains
77779
+ // onboarding-first-90-days and networking-referrals were still on-demand-only. Anchor them
77780
+ // here (the only EOP-free lifecycle route). NOTE: networking's strongest lever is *before*
77781
+ // applied (pre-referral, networking-referrals §2) — this route is the surfacing point, not
77782
+ // the recommended timing, so surface networking early when the user is pre-application.
77783
+ // linkedin-profile/portfolio stay on-demand pending a dedicated personal-brand workflow id
77784
+ // (FIXES.md / STATUS §7.9 follow-up — onboarding EOP is résumé-import-only, so brand-
77785
+ // creation playbooks would blur its stage meaning). Additive wiring (A1/A2 precedent).
77786
+ manage_application_status: {
77787
+ expertSequence: [
77788
+ "rejection-triage-iteration",
77789
+ "offer-evaluation-decision",
77790
+ "salary-negotiation",
77791
+ "onboarding-first-90-days",
77792
+ "networking-referrals"
77793
+ ],
77794
+ verifierSequence: ["truthfulness", "consistency"]
77795
+ },
77796
+ // R7 후속: linkedin-profile·portfolio는 onboarding(이력서 임포트)과 단계 의미가
77797
+ // 달라(개인 브랜드 자산 *생성*) 전용 워크플로우로 분리(consensus 권고). networking은
77798
+ // manage_application_status에 이미 정박. 저장은 add_resume(kind=portfolio); linkedin
77799
+ // 전용 저장/검증은 Phase B.
77800
+ build_personal_brand: {
77801
+ expertSequence: ["linkedin-profile", "portfolio"],
77802
+ verifierSequence: ["truthfulness", "human-voice", "consistency"]
77517
77803
  }
77518
77804
  };
77519
77805
  function getRoute(id2) {
@@ -77523,12 +77809,15 @@ function renderRouteGuide(id2) {
77523
77809
  const route = CAREER_ROUTES[id2];
77524
77810
  if (!route) return void 0;
77525
77811
  const lines = [];
77526
- if (route.eop) {
77812
+ const eops = route.eop ? Array.isArray(route.eop) ? route.eop : [route.eop] : [];
77813
+ if (eops.length) {
77527
77814
  lines.push("---", "");
77528
77815
  lines.push("## \uC804\uBB38\uAC00 \uC2E4\uD589 \uC808\uCC28 (EOP) \u2014 \uC989\uB2F5\uD558\uC9C0 \uB9D0\uACE0 \uC774 \uC808\uCC28\uB97C \uB530\uB974\uC138\uC694");
77529
77816
  lines.push("");
77530
- lines.push(getEop(route.eop));
77531
- lines.push("");
77817
+ for (const e of eops) {
77818
+ lines.push(getEop(e));
77819
+ lines.push("");
77820
+ }
77532
77821
  }
77533
77822
  lines.push("## \uC774 \uC791\uC5C5\uC5D0 \uC801\uC6A9\uD560 \uC804\uBB38\uAC00 \uD50C\uB808\uC774\uBD81");
77534
77823
  lines.push("\uC791\uC131 \uC9C1\uC804, \uC544\uB798 \uB3C4\uBA54\uC778\uC758 \uAE4A\uC740 \uC9C0\uC2DD\uC744 \uBC1B\uC544 \uC801\uC6A9\uD558\uC138\uC694:");
@@ -77642,10 +77931,22 @@ function slugify(input, fallback = "document") {
77642
77931
  function joinBlocks(blocks) {
77643
77932
  return blocks.filter((b) => typeof b === "string" && b.trim().length > 0).join("\n\n");
77644
77933
  }
77934
+ function bulletList(items) {
77935
+ const lines = items.filter((i2) => typeof i2 === "string" && i2.trim().length > 0).map((i2) => `- ${i2.trim()}`);
77936
+ return lines.join("\n");
77937
+ }
77645
77938
  function kv(label, value) {
77646
77939
  if (!value || !value.trim()) return "";
77647
77940
  return `**${label}**: ${value.trim()}`;
77648
77941
  }
77942
+ function dateRange(start, end, current = false) {
77943
+ const s = (start ?? "").trim();
77944
+ const e = current ? "\uD604\uC7AC" : (end ?? "").trim();
77945
+ if (!s && !e) return "";
77946
+ if (!s) return e;
77947
+ if (!e) return s;
77948
+ return `${s} ~ ${e}`;
77949
+ }
77649
77950
  function renderInline(escaped) {
77650
77951
  let out = escaped;
77651
77952
  out = out.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
@@ -77754,6 +78055,123 @@ function coverLetterToHtml(cl, opts) {
77754
78055
  content: toPrintableHtml(cl.title, coverLetterBody(cl, opts))
77755
78056
  };
77756
78057
  }
78058
+ function resumeToMarkdown(doc, profile) {
78059
+ const kindLabel = DOCUMENT_KIND_LABELS[doc.kind] ?? doc.kind;
78060
+ const header = [];
78061
+ if (profile?.name) header.push(kv("\uC774\uB984", profile.name));
78062
+ if (profile?.email) header.push(kv("\uC774\uBA54\uC77C", profile.email));
78063
+ if (profile?.phone) header.push(kv("\uC5F0\uB77D\uCC98", profile.phone));
78064
+ const body = joinBlocks([
78065
+ `# ${doc.title}`,
78066
+ `_${kindLabel}_`,
78067
+ header.length ? header.filter(Boolean).join("\n\n") : "",
78068
+ header.length ? "---" : "",
78069
+ (doc.content ?? "").trim() || "_(\uB0B4\uC6A9 \uC5C6\uC74C)_"
78070
+ ]);
78071
+ return {
78072
+ filename: `${slugify(doc.title, doc.kind)}.md`,
78073
+ mimeType: MD_MIME,
78074
+ content: body
78075
+ };
78076
+ }
78077
+ function resumeToHtml(doc, profile) {
78078
+ const md = resumeToMarkdown(doc, profile);
78079
+ return {
78080
+ filename: `${slugify(doc.title, doc.kind)}.html`,
78081
+ mimeType: HTML_MIME,
78082
+ content: toPrintableHtml(doc.title, md.content)
78083
+ };
78084
+ }
78085
+ function profileToMarkdown(profile, experiences = [], projects = [], skills = []) {
78086
+ const name = profile.name?.trim() || "\uC774\uB825\uC11C";
78087
+ const contact = [];
78088
+ if (profile.email) contact.push(profile.email);
78089
+ if (profile.phone) contact.push(profile.phone);
78090
+ if (profile.location) contact.push(profile.location);
78091
+ const links = (profile.links ?? []).filter((l2) => l2.url).map((l2) => `[${l2.label || l2.url}](${l2.url})`);
78092
+ const headerBlocks = joinBlocks([
78093
+ `# ${name}`,
78094
+ profile.headline ? `## ${profile.headline}` : "",
78095
+ contact.length ? contact.join(" \xB7 ") : "",
78096
+ links.length ? links.join(" \xB7 ") : ""
78097
+ ]);
78098
+ const summaryBlock = profile.summary?.trim() ? joinBlocks(["## \uC18C\uAC1C", profile.summary.trim()]) : "";
78099
+ const desiredLines = [];
78100
+ if (profile.desired_roles?.length) {
78101
+ desiredLines.push(kv("\uD76C\uB9DD \uC9C1\uBB34", profile.desired_roles.join(", ")));
78102
+ }
78103
+ if (profile.desired_conditions?.trim()) {
78104
+ desiredLines.push(kv("\uD76C\uB9DD \uC870\uAC74", profile.desired_conditions));
78105
+ }
78106
+ const desiredBlock = desiredLines.length ? joinBlocks(["## \uD76C\uB9DD \uC0AC\uD56D", desiredLines.filter(Boolean).join("\n\n")]) : "";
78107
+ const expBlocks = experiences.length ? joinBlocks([
78108
+ "## \uACBD\uB825",
78109
+ ...experiences.map((e) => {
78110
+ const range2 = dateRange(e.start_date, e.end_date, e.is_current);
78111
+ const titleParts = [e.company, e.role].filter(Boolean).join(" \u2014 ");
78112
+ const subParts = [e.employment_type, range2].filter(Boolean).join(" \xB7 ");
78113
+ return joinBlocks([
78114
+ `### ${titleParts}`,
78115
+ subParts ? `_${subParts}_` : "",
78116
+ e.description?.trim() ? e.description.trim() : "",
78117
+ e.achievements?.length ? bulletList(e.achievements) : "",
78118
+ e.tech?.length ? kv("\uAE30\uC220", e.tech.join(", ")) : ""
78119
+ ]);
78120
+ })
78121
+ ]) : "";
78122
+ const projBlocks = projects.length ? joinBlocks([
78123
+ "## \uD504\uB85C\uC81D\uD2B8",
78124
+ ...projects.map((p) => {
78125
+ const range2 = dateRange(p.start_date, p.end_date);
78126
+ const subParts = [p.role, range2].filter(Boolean).join(" \xB7 ");
78127
+ return joinBlocks([
78128
+ `### ${p.name}`,
78129
+ subParts ? `_${subParts}_` : "",
78130
+ p.description?.trim() ? p.description.trim() : "",
78131
+ p.highlights?.length ? bulletList(p.highlights) : "",
78132
+ p.tech?.length ? kv("\uAE30\uC220", p.tech.join(", ")) : "",
78133
+ p.url ? kv("\uB9C1\uD06C", p.url) : ""
78134
+ ]);
78135
+ })
78136
+ ]) : "";
78137
+ let skillsBlock = "";
78138
+ if (skills.length) {
78139
+ const groups = /* @__PURE__ */ new Map();
78140
+ for (const s of skills) {
78141
+ const cat = s.category?.trim() || "\uAE30\uD0C0";
78142
+ const label = s.level?.trim() ? `${s.name} (${s.level.trim()})` : s.name;
78143
+ if (!groups.has(cat)) groups.set(cat, []);
78144
+ groups.get(cat).push(label);
78145
+ }
78146
+ const lines = [...groups.entries()].map(
78147
+ ([cat, items]) => kv(cat, items.join(", "))
78148
+ );
78149
+ skillsBlock = joinBlocks(["## \uBCF4\uC720 \uAE30\uC220", lines.filter(Boolean).join("\n\n")]);
78150
+ }
78151
+ const content = joinBlocks([
78152
+ headerBlocks,
78153
+ "---",
78154
+ summaryBlock,
78155
+ desiredBlock,
78156
+ expBlocks,
78157
+ projBlocks,
78158
+ skillsBlock
78159
+ ]);
78160
+ return {
78161
+ filename: `${slugify(name, "resume")}-resume.md`,
78162
+ mimeType: MD_MIME,
78163
+ content
78164
+ };
78165
+ }
78166
+ function profileToHtml(profile, experiences = [], projects = [], skills = []) {
78167
+ const md = profileToMarkdown(profile, experiences, projects, skills);
78168
+ const title = profile.name?.trim() || "\uC774\uB825\uC11C";
78169
+ return {
78170
+ filename: `${slugify(title, "resume")}-resume.html`,
78171
+ mimeType: HTML_MIME,
78172
+ content: toPrintableHtml(title, md.content)
78173
+ };
78174
+ }
77757
78175
 
77758
78176
  // packages/parsers/src/html.ts
77759
78177
  var NAMED_ENTITIES = {
@@ -78314,7 +78732,12 @@ async function extractDocument(input, baseDir) {
78314
78732
  path: filePath,
78315
78733
  ...over
78316
78734
  });
78317
- if (SENSITIVE_PATH_RE.test(filePath)) {
78735
+ let realPath = filePath;
78736
+ try {
78737
+ realPath = fs4.realpathSync.native(filePath);
78738
+ } catch {
78739
+ }
78740
+ if (SENSITIVE_PATH_RE.test(filePath) || SENSITIVE_PATH_RE.test(realPath)) {
78318
78741
  return base("unsupported", {
78319
78742
  warnings: [
78320
78743
  "\uBCF4\uC548\uC0C1 \uBBFC\uAC10\uD560 \uC218 \uC788\uB294 \uD30C\uC77C(\uAC1C\uC778 \uD0A4\xB7\uC790\uACA9\uC99D\uBA85 \uB4F1)\uC740 \uC77D\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC774\uB825\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C\xB7\uC790\uAE30\uC18C\uAC1C\uC11C \uAC19\uC740 \uBB38\uC11C \uD30C\uC77C\uC744 \uC9C0\uC815\uD574 \uC8FC\uC138\uC694."
@@ -78639,14 +79062,94 @@ function fail(text) {
78639
79062
  }
78640
79063
 
78641
79064
  // packages/mcp-tools/src/bridge.ts
78642
- import { spawn } from "node:child_process";
79065
+ import { spawn, spawnSync } from "node:child_process";
79066
+ import fs5 from "node:fs";
78643
79067
  import path5 from "node:path";
78644
79068
  import { fileURLToPath as fileURLToPath2 } from "node:url";
78645
79069
  var __dirname = path5.dirname(fileURLToPath2(import.meta.url));
78646
79070
  var REPO_ROOT = path5.resolve(__dirname, "..", "..", "..");
78647
- function liveServer() {
79071
+ var HEALTH_TIMEOUT_MS = 800;
79072
+ var pendingStart = null;
79073
+ function dashboardLogPath() {
79074
+ return path5.join(getDataDir(), "server.log");
79075
+ }
79076
+ function launchdPlistPath() {
79077
+ return path5.join(getDataDir(), "dashboard.launchd.plist");
79078
+ }
79079
+ function launchdLabel() {
79080
+ let hash = 5381;
79081
+ for (const ch of getDataDir()) hash = (hash * 33 ^ ch.charCodeAt(0)) >>> 0;
79082
+ return `life.careermate.dashboard.${hash.toString(16)}`;
79083
+ }
79084
+ function xmlEscape(value) {
79085
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
79086
+ }
79087
+ function plistString(key, value) {
79088
+ return ` <key>${xmlEscape(key)}</key>
79089
+ <string>${xmlEscape(value)}</string>`;
79090
+ }
79091
+ function renderLaunchdPlist(bin, args, cwd, log) {
79092
+ const env2 = {
79093
+ CAREERMATE_DATA_DIR: getDataDir(),
79094
+ CAREERMATE_NO_OPEN: "1"
79095
+ };
79096
+ if (process.env.CAREERMATE_PORT) env2.CAREERMATE_PORT = process.env.CAREERMATE_PORT;
79097
+ if (process.env.PATH) env2.PATH = process.env.PATH;
79098
+ const programArgs = [bin, ...args].map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n");
79099
+ const envEntries = Object.entries(env2).map(([key, value]) => `${plistString(key, value)}`).join("\n");
79100
+ return `<?xml version="1.0" encoding="UTF-8"?>
79101
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
79102
+ <plist version="1.0">
79103
+ <dict>
79104
+ <key>Label</key>
79105
+ <string>${xmlEscape(launchdLabel())}</string>
79106
+ <key>ProgramArguments</key>
79107
+ <array>
79108
+ ${programArgs}
79109
+ </array>
79110
+ <key>WorkingDirectory</key>
79111
+ <string>${xmlEscape(cwd)}</string>
79112
+ <key>EnvironmentVariables</key>
79113
+ <dict>
79114
+ ${envEntries}
79115
+ </dict>
79116
+ <key>RunAtLoad</key>
79117
+ <true/>
79118
+ <key>KeepAlive</key>
79119
+ <false/>
79120
+ <key>StandardOutPath</key>
79121
+ <string>${xmlEscape(log)}</string>
79122
+ <key>StandardErrorPath</key>
79123
+ <string>${xmlEscape(log)}</string>
79124
+ </dict>
79125
+ </plist>
79126
+ `;
79127
+ }
79128
+ function delay(ms) {
79129
+ return new Promise((resolve) => setTimeout(resolve, ms));
79130
+ }
79131
+ async function pingDashboard(url, timeoutMs = HEALTH_TIMEOUT_MS) {
79132
+ const controller = new AbortController();
79133
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
79134
+ timer.unref?.();
79135
+ try {
79136
+ const healthUrl = new URL("/api/health", url);
79137
+ const res = await fetch(healthUrl, {
79138
+ signal: controller.signal,
79139
+ headers: { accept: "application/json" }
79140
+ });
79141
+ if (!res.ok) return false;
79142
+ const body = await res.json().catch(() => null);
79143
+ return body?.ok === true;
79144
+ } catch {
79145
+ return false;
79146
+ } finally {
79147
+ clearTimeout(timer);
79148
+ }
79149
+ }
79150
+ async function liveServer() {
78648
79151
  const info3 = readRuntimeInfo();
78649
- if (info3 && isProcessAlive(info3.pid)) return info3;
79152
+ if (info3 && isProcessAlive(info3.pid) && await pingDashboard(info3.url)) return info3;
78650
79153
  return null;
78651
79154
  }
78652
79155
  function spawnDetached(cmd, args, opts = {}) {
@@ -78655,19 +79158,101 @@ function spawnDetached(cmd, args, opts = {}) {
78655
79158
  child.on("error", () => {
78656
79159
  });
78657
79160
  child.unref();
79161
+ return child.pid ?? null;
78658
79162
  } catch {
79163
+ return null;
79164
+ }
79165
+ }
79166
+ function spawnViaLaunchd(bin, args, cwd, log) {
79167
+ if (process.platform !== "darwin" || process.env.CAREERMATE_LAUNCHER === "detached") return false;
79168
+ if (typeof process.getuid !== "function") return false;
79169
+ const domain = `gui/${process.getuid()}`;
79170
+ const label = launchdLabel();
79171
+ const plist = launchdPlistPath();
79172
+ try {
79173
+ fs5.writeFileSync(plist, renderLaunchdPlist(bin, args, cwd, log), "utf8");
79174
+ spawnSync("launchctl", ["bootout", `${domain}/${label}`], { stdio: "ignore" });
79175
+ const res = spawnSync("launchctl", ["bootstrap", domain, plist], {
79176
+ encoding: "utf8",
79177
+ timeout: 5e3
79178
+ });
79179
+ if (res.status === 0) return true;
79180
+ fs5.appendFileSync(log, `[${(/* @__PURE__ */ new Date()).toISOString()}] launchctl bootstrap failed: ${res.stderr || res.stdout || res.status}
79181
+ `);
79182
+ } catch (e) {
79183
+ try {
79184
+ fs5.appendFileSync(log, `[${(/* @__PURE__ */ new Date()).toISOString()}] launchctl launch failed: ${e instanceof Error ? e.message : String(e)}
79185
+ `);
79186
+ } catch {
79187
+ }
78659
79188
  }
79189
+ return false;
79190
+ }
79191
+ function spawnDashboardDetached(bin, args, cwd, logFd) {
79192
+ spawnDetached(bin, args, {
79193
+ cwd,
79194
+ env: { ...process.env, CAREERMATE_NO_OPEN: "1" },
79195
+ stdio: logFd === null ? "ignore" : ["ignore", logFd, logFd]
79196
+ });
78660
79197
  }
78661
79198
  async function ensureServer(timeoutMs = 12e3) {
78662
- const existing = liveServer();
79199
+ const existing = await liveServer();
78663
79200
  if (existing) return { url: existing.url };
78664
- const [bin, args, cwd] = BUNDLED ? [process.execPath, ["--experimental-sqlite", path5.join(__dirname, "web.mjs")], __dirname] : [process.execPath, ["--no-warnings", "--experimental-sqlite", "--import", "tsx", "apps/web/src/index.ts"], REPO_ROOT];
78665
- spawnDetached(bin, args, { cwd, env: { ...process.env, CAREERMATE_NO_OPEN: "1" } });
79201
+ if (pendingStart) return pendingStart;
79202
+ pendingStart = startServerBackground(timeoutMs);
79203
+ try {
79204
+ return await pendingStart;
79205
+ } finally {
79206
+ pendingStart = null;
79207
+ }
79208
+ }
79209
+ async function startServerBackground(timeoutMs) {
79210
+ const [bin, args, cwd] = BUNDLED ? [process.execPath, ["--experimental-sqlite", path5.join(__dirname, "web.mjs")], __dirname] : [
79211
+ process.execPath,
79212
+ ["--no-warnings", "--experimental-sqlite", "--import", "tsx", path5.join(REPO_ROOT, "apps/web/src/index.ts")],
79213
+ REPO_ROOT
79214
+ ];
79215
+ const log = dashboardLogPath();
79216
+ let logFd = null;
79217
+ try {
79218
+ logFd = fs5.openSync(log, "a");
79219
+ fs5.writeSync(logFd, `
79220
+ [${(/* @__PURE__ */ new Date()).toISOString()}] CareerMate dashboard background launch
79221
+ `);
79222
+ } catch {
79223
+ logFd = null;
79224
+ }
79225
+ let launchedViaLaunchd = false;
79226
+ try {
79227
+ launchedViaLaunchd = spawnViaLaunchd(bin, args, cwd, log);
79228
+ if (!launchedViaLaunchd) spawnDashboardDetached(bin, args, cwd, logFd);
79229
+ } finally {
79230
+ if (logFd !== null) {
79231
+ try {
79232
+ fs5.closeSync(logFd);
79233
+ } catch {
79234
+ }
79235
+ }
79236
+ }
78666
79237
  const start = Date.now();
78667
79238
  while (Date.now() - start < timeoutMs) {
78668
- const info3 = liveServer();
79239
+ const info3 = await liveServer();
78669
79240
  if (info3) return { url: info3.url };
78670
- await new Promise((r) => setTimeout(r, 300));
79241
+ await delay(300);
79242
+ }
79243
+ if (launchedViaLaunchd) {
79244
+ try {
79245
+ fs5.appendFileSync(log, `[${(/* @__PURE__ */ new Date()).toISOString()}] launchd did not produce a healthy server; falling back to detached spawn
79246
+ `);
79247
+ } catch {
79248
+ }
79249
+ spawnDashboardDetached(bin, args, cwd, null);
79250
+ const fallbackStart = Date.now();
79251
+ while (Date.now() - fallbackStart < Math.min(timeoutMs, 8e3)) {
79252
+ const info3 = await liveServer();
79253
+ if (info3) return { url: info3.url };
79254
+ await delay(300);
79255
+ }
78671
79256
  }
78672
79257
  return null;
78673
79258
  }
@@ -78691,7 +79276,7 @@ function openInFileManager(dir) {
78691
79276
  else openOsTarget(dir);
78692
79277
  }
78693
79278
  async function resolveDashboardUrl(hash = "") {
78694
- const live = liveServer();
79279
+ const live = await liveServer();
78695
79280
  if (live) return { url: live.url + hash, running: true };
78696
79281
  const started = await ensureServer();
78697
79282
  if (started) return { url: started.url + hash, running: true };
@@ -78699,7 +79284,7 @@ async function resolveDashboardUrl(hash = "") {
78699
79284
  }
78700
79285
 
78701
79286
  // packages/mcp-tools/src/update.ts
78702
- import fs5 from "node:fs";
79287
+ import fs6 from "node:fs";
78703
79288
  import path6 from "node:path";
78704
79289
  import { fileURLToPath as fileURLToPath3 } from "node:url";
78705
79290
  import { spawn as spawn2 } from "node:child_process";
@@ -78756,14 +79341,14 @@ function comparePrerelease(a, b) {
78756
79341
  }
78757
79342
  function readCache() {
78758
79343
  try {
78759
- return JSON.parse(fs5.readFileSync(cacheFile(), "utf8"));
79344
+ return JSON.parse(fs6.readFileSync(cacheFile(), "utf8"));
78760
79345
  } catch {
78761
79346
  return null;
78762
79347
  }
78763
79348
  }
78764
79349
  function writeCache(c) {
78765
79350
  try {
78766
- fs5.writeFileSync(cacheFile(), JSON.stringify(c), "utf8");
79351
+ fs6.writeFileSync(cacheFile(), JSON.stringify(c), "utf8");
78767
79352
  } catch {
78768
79353
  }
78769
79354
  }
@@ -78822,14 +79407,20 @@ function getUpdateStatus() {
78822
79407
  if (cacheExpired(cache2)) refreshInBackground();
78823
79408
  return buildStatus(cache2);
78824
79409
  }
78825
- async function getUpdateStatusAsync() {
79410
+ async function getUpdateStatusAsync(force = false) {
78826
79411
  if (checkDisabled()) return buildStatus(null);
78827
- let cache2 = readCache();
78828
- if (cacheExpired(cache2)) {
78829
- cache2 = { checkedAt: Date.now(), latest: await fetchLatest() };
78830
- writeCache(cache2);
78831
- }
78832
- return buildStatus(cache2);
79412
+ const cache2 = readCache();
79413
+ if (!force && !cacheExpired(cache2)) return buildStatus(cache2);
79414
+ const latest = await fetchLatest();
79415
+ if (latest !== null) {
79416
+ const fresh = { checkedAt: Date.now(), latest };
79417
+ writeCache(fresh);
79418
+ return buildStatus(fresh);
79419
+ }
79420
+ if (force) return buildStatus(null);
79421
+ const failed = { checkedAt: Date.now(), latest: null };
79422
+ writeCache(failed);
79423
+ return buildStatus(failed);
78833
79424
  }
78834
79425
  function killTree(child) {
78835
79426
  if (process.platform === "win32" && child.pid) {
@@ -78887,12 +79478,8 @@ async function runSelfUpdate() {
78887
79478
  manual: manualCommand()
78888
79479
  };
78889
79480
  }
78890
- let cache2 = readCache();
78891
- if (cacheExpired(cache2)) {
78892
- cache2 = { checkedAt: Date.now(), latest: await fetchLatest() };
78893
- writeCache(cache2);
78894
- }
78895
- const latest = cache2?.latest ?? null;
79481
+ const latest = await fetchLatest();
79482
+ if (latest !== null) writeCache({ checkedAt: Date.now(), latest });
78896
79483
  if (latest === null) {
78897
79484
  return {
78898
79485
  ok: false,
@@ -78920,26 +79507,38 @@ async function runSelfUpdate() {
78920
79507
  ok: true,
78921
79508
  from,
78922
79509
  to: latest,
78923
- message: `careermate\uB97C ${latest ? `v${latest}` : "\uCD5C\uC2E0 \uBC84\uC804"}(\uC73C)\uB85C \uC5C5\uB370\uC774\uD2B8\uD588\uC2B5\uB2C8\uB2E4. \uC801\uC6A9\uD558\uB824\uBA74 AI \uC571(\uB610\uB294 MCP \uC5F0\uACB0)\uC744 \uC7AC\uC2DC\uC791\uD558\uC138\uC694. \uC7AC\uC2DC\uC791 \uC2DC \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uB294 \uC790\uB3D9\uC73C\uB85C \uB9C8\uC774\uADF8\uB808\uC774\uC158\uB418\uBA70 \uAE30\uC874 \uB370\uC774\uD130\uB294 \uBCF4\uC874\uB429\uB2C8\uB2E4.`
79510
+ message: `CareerMate\uB97C ${latest ? `v${latest}` : "\uCD5C\uC2E0 \uBC84\uC804"}(\uC73C)\uB85C \uC5C5\uB370\uC774\uD2B8\uD588\uC2B5\uB2C8\uB2E4. \uC801\uC6A9\uD558\uB824\uBA74 AI \uC571(\uB610\uB294 MCP \uC5F0\uACB0)\uC744 \uC7AC\uC2DC\uC791\uD558\uC138\uC694. \uC7AC\uC2DC\uC791 \uC2DC \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uB294 \uC790\uB3D9\uC73C\uB85C \uB9C8\uC774\uADF8\uB808\uC774\uC158\uB418\uBA70 \uAE30\uC874 \uB370\uC774\uD130\uB294 \uBCF4\uC874\uB429\uB2C8\uB2E4.`
78924
79511
  };
78925
79512
  }
78926
79513
 
78927
79514
  // packages/mcp-tools/src/tools.ts
78928
79515
  var STATUS_LIST = APPLICATION_STATUSES.map((s) => `${s}(${APPLICATION_STATUS_LABELS[s]})`).join(", ");
79516
+ var DELETE_CONFIRMATION = "DELETE";
79517
+ var DASHBOARD_URL = "http://127.0.0.1:4319";
78929
79518
  function listInboxFiles(dir) {
78930
79519
  let names;
78931
79520
  try {
78932
- names = fs6.readdirSync(dir);
79521
+ names = fs7.readdirSync(dir);
78933
79522
  } catch {
78934
79523
  return [];
78935
79524
  }
79525
+ let realDir;
79526
+ try {
79527
+ realDir = fs7.realpathSync.native(dir);
79528
+ } catch {
79529
+ realDir = dir;
79530
+ }
78936
79531
  const SKIP = /^[.~]|^(thumbs\.db|desktop\.ini)$/i;
78937
79532
  const out = [];
78938
79533
  for (const name of names) {
78939
79534
  if (SKIP.test(name)) continue;
78940
79535
  const p = path7.join(dir, name);
78941
79536
  try {
78942
- if (fs6.statSync(p).isFile()) out.push({ name, path: p });
79537
+ if (!fs7.statSync(p).isFile()) continue;
79538
+ const real = fs7.realpathSync.native(p);
79539
+ const rel = path7.relative(realDir, real);
79540
+ if (rel.startsWith("..") || path7.isAbsolute(rel)) continue;
79541
+ out.push({ name, path: p });
78943
79542
  } catch {
78944
79543
  }
78945
79544
  }
@@ -79009,8 +79608,8 @@ ${r.warnings.join("\n")}`, {
79009
79608
  /* --------------------------------------------------------- inbox (파일 인입) */
79010
79609
  {
79011
79610
  name: "open_inbox",
79012
- title: "\uC774\uB825\uC11C \uC778\uC785 \uD3F4\uB354 \uC5F4\uAE30",
79013
- description: 'CLI \uD074\uB77C\uC774\uC5B8\uD2B8\uB294 \uD30C\uC77C \uCCA8\uBD80\uAC00 \uC548 \uB418\uBBC0\uB85C, \uC0AC\uC6A9\uC790\uAC00 \uAE30\uC874 \uC774\uB825\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C \uD30C\uC77C(hwp/hwpx/docx/pdf \uB4F1)\uC744 \uB04C\uC5B4\uB2E4 \uB123\uC744 \uC218 \uC788\uB294 \uB85C\uCEEC \uC778\uC785 \uD3F4\uB354(~/.careermate/inbox)\uB97C \uBCF4\uC7A5\uD558\uACE0 \uD30C\uC77C \uD0D0\uC0C9\uAE30\uB85C \uC5FD\uB2C8\uB2E4. \uC628\uBCF4\uB529\uC5D0\uC11C "\uAE30\uC874\uC5D0 \uC791\uC131\uD574 \uB454 \uC774\uB825\uC11C \uD30C\uC77C\uC774 \uC788\uB2E4"\uACE0 \uC0AC\uC6A9\uC790\uAC00 \uB2F5\uD588\uC744 \uB54C\uB9CC \uD638\uCD9C\uD558\uC138\uC694 \u2014 \uBB3B\uC9C0\uB3C4 \uC54A\uACE0 \uD3F4\uB354\uB97C \uC790\uB3D9\uC73C\uB85C \uC5F4\uC9C0 \uB9C8\uC138\uC694. \uD30C\uC77C \uACBD\uB85C\uB97C \uC9C1\uC811 \uC54C\uB824\uC8FC\uB294 \uC0AC\uC6A9\uC790\uC5D0\uAC8C\uB294 \uC774 \uB3C4\uAD6C \uB300\uC2E0 read_document\uB97C \uC4F0\uC138\uC694. \uD638\uCD9C\uD558\uBA74 \uD3F4\uB354 \uACBD\uB85C\uC640 \uD604\uC7AC \uD3F4\uB354\uC5D0 \uB4E4\uC5B4 \uC788\uB294 \uD30C\uC77C \uBAA9\uB85D\uC744 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 \uD30C\uC77C\uC744 \uB2E4 \uB123\uC5C8\uB2E4\uACE0 \uD558\uBA74 read_inbox\uB85C \uBCF8\uBB38\uC744 \uC77D\uC73C\uC138\uC694.',
79611
+ title: "\uBB38\uC11C \uC778\uC785 \uD3F4\uB354 \uC5F4\uAE30",
79612
+ description: 'CLI \uD074\uB77C\uC774\uC5B8\uD2B8\uB294 \uD30C\uC77C \uCCA8\uBD80\uAC00 \uC548 \uB418\uBBC0\uB85C, \uC0AC\uC6A9\uC790\uAC00 \uAE30\uC874 \uC774\uB825\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C\xB7\uD3EC\uD2B8\uD3F4\uB9AC\uC624\xB7\uC790\uAE30\uC18C\uAC1C\uC11C \uD30C\uC77C(hwp/hwpx/docx/pdf \uB4F1)\uC744 \uB04C\uC5B4\uB2E4 \uB123\uC744 \uC218 \uC788\uB294 \uB85C\uCEEC \uC778\uC785 \uD3F4\uB354(~/.careermate/inbox)\uB97C \uBCF4\uC7A5\uD558\uACE0 \uD30C\uC77C \uD0D0\uC0C9\uAE30\uB85C \uC5FD\uB2C8\uB2E4. \uC628\uBCF4\uB529\uC5D0\uC11C "\uAE30\uC874\uC5D0 \uC791\uC131\uD574 \uB454 \uBB38\uC11C \uD30C\uC77C\uC774 \uC788\uB2E4"\uACE0 \uC0AC\uC6A9\uC790\uAC00 \uB2F5\uD588\uC744 \uB54C\uB9CC \uD638\uCD9C\uD558\uC138\uC694 \u2014 \uBB3B\uC9C0\uB3C4 \uC54A\uACE0 \uD3F4\uB354\uB97C \uC790\uB3D9\uC73C\uB85C \uC5F4\uC9C0 \uB9C8\uC138\uC694. \uD30C\uC77C \uACBD\uB85C\uB97C \uC9C1\uC811 \uC54C\uB824\uC8FC\uB294 \uC0AC\uC6A9\uC790\uC5D0\uAC8C\uB294 \uC774 \uB3C4\uAD6C \uB300\uC2E0 read_document\uB97C \uC4F0\uC138\uC694. \uD638\uCD9C\uD558\uBA74 \uD3F4\uB354 \uACBD\uB85C\uC640 \uD604\uC7AC \uD3F4\uB354\uC5D0 \uB4E4\uC5B4 \uC788\uB294 \uD30C\uC77C \uBAA9\uB85D\uC744 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 \uD30C\uC77C\uC744 \uB2E4 \uB123\uC5C8\uB2E4\uACE0 \uD558\uBA74 read_inbox\uB85C \uBCF8\uBB38\uC744 \uC77D\uC73C\uC138\uC694.',
79014
79613
  inputSchema: {},
79015
79614
  handler: () => {
79016
79615
  const dir = getInboxDir();
@@ -79018,10 +79617,10 @@ ${r.warnings.join("\n")}`, {
79018
79617
  const files = listInboxFiles(dir);
79019
79618
  const list = files.length ? files.map((f) => `- ${f.name}`).join("\n") : "(\uC544\uC9C1 \uBE44\uC5B4 \uC788\uC74C)";
79020
79619
  return ok(
79021
- `\uC774\uB825\uC11C \uC778\uC785 \uD3F4\uB354\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4:
79620
+ `\uBB38\uC11C \uC778\uC785 \uD3F4\uB354\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4:
79022
79621
  ${dir}
79023
79622
 
79024
- \uC5EC\uAE30\uC5D0 \uC774\uB825\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C \uD30C\uC77C\uC744 \uB123\uACE0 "\uB2E4 \uB123\uC5C8\uC5B4"\uB77C\uACE0 \uC54C\uB824\uC8FC\uC138\uC694. (hwp/hwpx/docx\uB294 \uC790\uB3D9\uC73C\uB85C \uD14D\uC2A4\uD2B8\uB97C \uCD94\uCD9C\uD558\uACE0, pdf\xB7\uC774\uBBF8\uC9C0\uB294 \uC81C\uAC00 \uC9C1\uC811 \uC77D\uC2B5\uB2C8\uB2E4.)
79623
+ \uC5EC\uAE30\uC5D0 \uC774\uB825\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C\xB7\uD3EC\uD2B8\uD3F4\uB9AC\uC624\xB7\uAE30\uC874 \uC790\uAE30\uC18C\uAC1C\uC11C \uD30C\uC77C\uC744 \uB123\uACE0 "\uB2E4 \uB123\uC5C8\uC5B4"\uB77C\uACE0 \uC54C\uB824\uC8FC\uC138\uC694. (hwp/hwpx/docx\uB294 \uC790\uB3D9\uC73C\uB85C \uD14D\uC2A4\uD2B8\uB97C \uCD94\uCD9C\uD558\uACE0, pdf\xB7\uC774\uBBF8\uC9C0\uB294 \uC81C\uAC00 \uC9C1\uC811 \uC77D\uC2B5\uB2C8\uB2E4.)
79025
79624
 
79026
79625
  \uD604\uC7AC \uD30C\uC77C:
79027
79626
  ${list}`,
@@ -79032,7 +79631,7 @@ ${list}`,
79032
79631
  {
79033
79632
  name: "read_inbox",
79034
79633
  title: "\uC778\uC785 \uD3F4\uB354 \uD30C\uC77C \uC77D\uAE30",
79035
- description: '\uC778\uC785 \uD3F4\uB354(~/.careermate/inbox)\uC5D0 \uC0AC\uC6A9\uC790\uAC00 \uB123\uC5B4 \uB454 \uBB38\uC11C\uB4E4\uC758 \uBCF8\uBB38\uC744 \uD55C \uBC88\uC5D0 \uC77D\uC5B4 \uB3CC\uB824\uC90D\uB2C8\uB2E4. open_inbox\uB85C \uD3F4\uB354\uB97C \uC548\uB0B4\uD55C \uB4A4 \uC0AC\uC6A9\uC790\uAC00 "\uB2E4 \uB123\uC5C8\uC5B4"\uB77C\uACE0 \uD558\uBA74 \uD638\uCD9C\uD558\uC138\uC694. docx/hwp/hwpx/\uD14D\uC2A4\uD2B8\uB294 \uD14D\uC2A4\uD2B8\uB97C \uCD94\uCD9C\uD558\uACE0, pdf\xB7\uC774\uBBF8\uC9C0\uB294 \uCD94\uCD9C\uD558\uC9C0 \uC54A\uACE0 \uD30C\uC77C \uACBD\uB85C\uB97C \uB3CC\uB824\uC8FC\uB2C8 AI \uD074\uB77C\uC774\uC5B8\uD2B8\uC758 \uD30C\uC77C \uC77D\uAE30 \uAE30\uB2A5\uC73C\uB85C \uADF8 \uACBD\uB85C\uB97C \uC9C1\uC811 \uC5F4\uC5B4 \uC77D\uC73C\uC138\uC694. \uC77D\uC740 \uB0B4\uC6A9\uC744 \uAD6C\uC870\uD654\uD574 save_profile\xB7add_resume \uB4F1 \uC54C\uB9DE\uC740 \uB3C4\uAD6C\uB85C \uC800\uC7A5\uD558\uC138\uC694(\uD30C\uC77C \uB0B4\uC6A9\uC744 \uC0C8 \uD30C\uC77C\uB85C \uB2E4\uC2DC \uC4F0\uC9C0 \uB9C8\uC138\uC694). filename\uC744 \uC8FC\uBA74 \uD574\uB2F9 \uD30C\uC77C\uB9CC \uC77D\uC2B5\uB2C8\uB2E4.',
79634
+ description: '\uC778\uC785 \uD3F4\uB354(~/.careermate/inbox)\uC5D0 \uC0AC\uC6A9\uC790\uAC00 \uB123\uC5B4 \uB454 \uBB38\uC11C\uB4E4\uC758 \uBCF8\uBB38\uC744 \uD55C \uBC88\uC5D0 \uC77D\uC5B4 \uB3CC\uB824\uC90D\uB2C8\uB2E4. open_inbox\uB85C \uD3F4\uB354\uB97C \uC548\uB0B4\uD55C \uB4A4 \uC0AC\uC6A9\uC790\uAC00 "\uB2E4 \uB123\uC5C8\uC5B4"\uB77C\uACE0 \uD558\uBA74 \uD638\uCD9C\uD558\uC138\uC694. docx/hwp/hwpx/\uD14D\uC2A4\uD2B8\uB294 \uD14D\uC2A4\uD2B8\uB97C \uCD94\uCD9C\uD558\uACE0, pdf\xB7\uC774\uBBF8\uC9C0\uB294 \uCD94\uCD9C\uD558\uC9C0 \uC54A\uACE0 \uD30C\uC77C \uACBD\uB85C\uB97C \uB3CC\uB824\uC8FC\uB2C8 AI \uD074\uB77C\uC774\uC5B8\uD2B8\uC758 \uD30C\uC77C \uC77D\uAE30 \uAE30\uB2A5\uC73C\uB85C \uADF8 \uACBD\uB85C\uB97C \uC9C1\uC811 \uC5F4\uC5B4 \uC77D\uC73C\uC138\uC694. \uC77D\uC740 \uB0B4\uC6A9\uC744 \uAD6C\uC870\uD654\uD574 \uD504\uB85C\uD544\uC740 save_profile, \uC774\uB825\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C\xB7\uD3EC\uD2B8\uD3F4\uB9AC\uC624\uB294 add_resume, \uAE30\uC874 \uC790\uAE30\uC18C\uAC1C\uC11C\uB294 save_cover_letter_version\uC73C\uB85C \uC800\uC7A5\uD558\uC138\uC694(\uD30C\uC77C \uB0B4\uC6A9\uC744 \uC0C8 \uD30C\uC77C\uB85C \uB2E4\uC2DC \uC4F0\uC9C0 \uB9C8\uC138\uC694). filename\uC744 \uC8FC\uBA74 \uD574\uB2F9 \uD30C\uC77C\uB9CC \uC77D\uC2B5\uB2C8\uB2E4.',
79036
79635
  inputSchema: {
79037
79636
  filename: external_exports.string().optional().describe("\uD2B9\uC815 \uD30C\uC77C\uB9CC \uC77D\uC744 \uB54C \uD30C\uC77C\uBA85(\uB300\uC18C\uBB38\uC790 \uBB34\uC2DC). \uC0DD\uB7B5\uD558\uBA74 \uD3F4\uB354 \uC548 \uBAA8\uB4E0 \uD30C\uC77C\uC744 \uC77D\uC2B5\uB2C8\uB2E4."),
79038
79637
  max_chars: external_exports.number().optional().describe("\uD30C\uC77C\uBCC4 \uBC18\uD658 \uD14D\uC2A4\uD2B8 \uCD5C\uB300 \uAE38\uC774(\uCD08\uACFC \uC2DC \uC798\uB77C\uB0C4). \uAE30\uBCF8 20000\uC790")
@@ -79073,7 +79672,7 @@ ${dir}
79073
79672
  `\uC778\uC785 \uD3F4\uB354 \uD30C\uC77C ${files.length}\uAC74\uC744 \uC77D\uC5C8\uC2B5\uB2C8\uB2E4:
79074
79673
  ${summary}
79075
79674
 
79076
- \uCD94\uCD9C\uB41C \uB0B4\uC6A9\uC744 \uAD6C\uC870\uD654\uD574 save_profile\xB7add_resume \uB4F1\uC73C\uB85C \uC800\uC7A5\uD558\uC138\uC694.`,
79675
+ \uCD94\uCD9C\uB41C \uB0B4\uC6A9\uC744 \uAD6C\uC870\uD654\uD574 \uD504\uB85C\uD544\uC740 save_profile, \uC774\uB825\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C\xB7\uD3EC\uD2B8\uD3F4\uB9AC\uC624\uB294 add_resume, \uAE30\uC874 \uC790\uAE30\uC18C\uAC1C\uC11C\uB294 save_cover_letter_version\uC73C\uB85C \uC800\uC7A5\uD558\uC138\uC694.`,
79077
79676
  { dir, files }
79078
79677
  );
79079
79678
  }
@@ -79102,9 +79701,17 @@ ${lines}`;
79102
79701
  if (upd.update_available) {
79103
79702
  text += `
79104
79703
 
79105
- \u{1F195} \uC0C8 \uBC84\uC804 v${upd.latest} \uC0AC\uC6A9 \uAC00\uB2A5 (\uD604\uC7AC v${upd.current}). \uC0AC\uC6A9\uC790\uAC00 \uC6D0\uD558\uBA74 update_careermate \uB3C4\uAD6C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.`;
79106
- }
79107
- return ok(text, { ...s, verify_strict: strict, update: upd });
79704
+ \uC0C8 \uBC84\uC804 v${upd.latest}\uC774 \uB098\uC654\uC2B5\uB2C8\uB2E4(\uD604\uC7AC v${upd.current}). \uC0AC\uC6A9\uC790\uAC00 \uC6D0\uD558\uBA74 \uBC14\uB85C \uC5C5\uB370\uC774\uD2B8\uB97C \uC9C4\uD589\uD558\uC138\uC694. \uC5C5\uB370\uC774\uD2B8 \uD6C4\uC5D0\uB294 AI \uC571 \uB610\uB294 MCP \uC5F0\uACB0\uC744 \uC7AC\uC2DC\uC791\uD574\uC57C \uC801\uC6A9\uB429\uB2C8\uB2E4.`;
79705
+ }
79706
+ return ok(text, {
79707
+ ...s,
79708
+ verify_strict: strict,
79709
+ update: upd,
79710
+ suggested_next_action: upd.update_available ? {
79711
+ prompt: `\uC0C8 \uBC84\uC804 v${upd.latest}\uC774 \uB098\uC654\uC2B5\uB2C8\uB2E4. \uC9C0\uAE08 \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694?`,
79712
+ next_tool: "update_careermate"
79713
+ } : null
79714
+ });
79108
79715
  }
79109
79716
  },
79110
79717
  /* --------------------------------------------------------------- profile */
@@ -79133,7 +79740,7 @@ ${lines}`;
79133
79740
  {
79134
79741
  name: "add_resume",
79135
79742
  title: "\uC774\uB825\uC11C/\uACBD\uB825\uAE30\uC220\uC11C \uCD94\uAC00",
79136
- description: "\uC774\uB825\uC11C, \uACBD\uB825\uAE30\uC220\uC11C, \uD3EC\uD2B8\uD3F4\uB9AC\uC624 \uB4F1 \uBB38\uC11C\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4. content\uC5D0\uB294 \uC0AC\uC6A9\uC790\uAC00 \uC5C5\uB85C\uB4DC/\uBD99\uC5EC\uB123\uC740 \uD14D\uC2A4\uD2B8(\uB610\uB294 \uC815\uB9AC\uD55C Markdown)\uB97C \uB123\uC73C\uC138\uC694. kind \uAE30\uBCF8\uAC12\uC740 resume\uC785\uB2C8\uB2E4. is_primary=true\uB85C \uB300\uD45C \uBB38\uC11C\uB97C \uC9C0\uC815\uD558\uBA74 get_application_context\uC758 primary_resume\uB85C \uB178\uCD9C\uB429\uB2C8\uB2E4.",
79743
+ description: "\uC774\uB825\uC11C, \uACBD\uB825\uAE30\uC220\uC11C, \uD3EC\uD2B8\uD3F4\uB9AC\uC624 \uB4F1 \uBB38\uC11C\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4. content\uC5D0\uB294 \uC0AC\uC6A9\uC790\uAC00 \uC5C5\uB85C\uB4DC/\uBD99\uC5EC\uB123\uC740 \uD14D\uC2A4\uD2B8 \uB610\uB294 AI\uAC00 \uC804\uBB38\uAC00 \uC808\uCC28\uB85C \uC815\uB9AC\uD55C Markdown\uC744 \uB123\uC73C\uC138\uC694. \uACBD\uB825\uAE30\uC220\uC11C\uB97C \uC791\uC131\uD574 \uC800\uC7A5\uD560 \uB54C\uB294 kind=career_description, source=ai\uB97C \uC0AC\uC6A9\uD569\uB2C8\uB2E4. kind \uAE30\uBCF8\uAC12\uC740 resume\uC785\uB2C8\uB2E4. is_primary=true\uB85C \uB300\uD45C \uBB38\uC11C\uB97C \uC9C0\uC815\uD558\uBA74 get_application_context\uC758 primary_resume\uB85C \uB178\uCD9C\uB429\uB2C8\uB2E4.",
79137
79744
  // Reuse the shared (length-bounded, whitespace-rejecting) Document schema so
79138
79745
  // the MCP ingestion path — the primary route for résumé text — inherits the
79139
79746
  // same caps as the web API. kind stays optional here (defaults to resume).
@@ -79157,6 +79764,23 @@ ${lines}`;
79157
79764
  return ok(`\uBB38\uC11C ${docs.length}\uAC74`, docs);
79158
79765
  }
79159
79766
  },
79767
+ {
79768
+ name: "delete_resume",
79769
+ title: "\uC774\uB825\uC11C/\uBB38\uC11C \uC0AD\uC81C",
79770
+ description: "\uC800\uC7A5\uB41C \uC774\uB825\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C\xB7\uD3EC\uD2B8\uD3F4\uB9AC\uC624 \uB4F1 \uBB38\uC11C\uB97C \uC0AD\uC81C\uD569\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 \uBA85\uD655\uD788 \uC0AD\uC81C\uB97C \uC694\uCCAD\uD588\uC744 \uB54C\uB9CC \uC0AC\uC6A9\uD558\uC138\uC694. \uBA3C\uC800 get_resumes\uB85C \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uACE0 \uC5B4\uB5A4 \uBB38\uC11C\uC778\uC9C0 \uC0AC\uC6A9\uC790\uC640 \uD655\uC778\uD55C \uB4A4 \uD638\uCD9C\uD569\uB2C8\uB2E4. confirm\uC5D0\uB294 DELETE\uB97C \uB123\uC73C\uC138\uC694.",
79771
+ inputSchema: {
79772
+ document_id: external_exports.string(),
79773
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
79774
+ },
79775
+ handler: (args) => {
79776
+ if (args?.confirm !== DELETE_CONFIRMATION) return fail("\uC0AD\uC81C\uD558\uB824\uBA74 confirm\uC5D0 DELETE\uB97C \uB123\uC5B4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694.");
79777
+ const doc = documentRepo.get(args.document_id);
79778
+ if (!doc) return fail("\uBB38\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
79779
+ if (!documentRepo.remove(doc.id)) return fail("\uBB38\uC11C\uB97C \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
79780
+ const user_message = `'${doc.title}' \uBB38\uC11C\uB97C \uC0AD\uC81C\uD588\uC5B4\uC694.`;
79781
+ return ok(user_message, { deleted: true, document_id: doc.id, user_message });
79782
+ }
79783
+ },
79160
79784
  /* ----------------------------------------------------------- experiences */
79161
79785
  {
79162
79786
  name: "add_experience",
@@ -79226,6 +79850,57 @@ ${lines}`;
79226
79850
  return ok(`\uAE30\uC220 ${list.length}\uAC74`, list);
79227
79851
  }
79228
79852
  },
79853
+ {
79854
+ name: "delete_experience",
79855
+ title: "\uACBD\uB825 \uD56D\uBAA9 \uC0AD\uC81C",
79856
+ description: "\uAD6C\uC870\uD654\uB41C \uACBD\uB825(\uC9C1\uC7A5) \uD56D\uBAA9 \uD558\uB098\uB97C \uC0AD\uC81C\uD569\uB2C8\uB2E4. \uC798\uBABB \uCD94\uCD9C/\uC785\uB825\uB41C \uACBD\uB825\uC744 \uC9C0\uC6B8 \uB54C \uC0AC\uC6A9\uD558\uC138\uC694. \uBA3C\uC800 get_experiences\uB85C \uBAA9\uB85D\uACFC id\uB97C \uD655\uC778\uD558\uACE0 \uC0AC\uC6A9\uC790\uC640 \uD655\uC778\uD55C \uB4A4 \uD638\uCD9C\uD569\uB2C8\uB2E4. (\uB0B4\uC6A9 \uC218\uC815\uC740 add_experience\uB85C \uAC19\uC740 \uD56D\uBAA9\uC744 \uB2E4\uC2DC \uC800\uC7A5\uD558\uBA74 \uAC31\uC2E0\uB429\uB2C8\uB2E4.) confirm\uC5D0\uB294 DELETE\uB97C \uB123\uC73C\uC138\uC694.",
79857
+ inputSchema: {
79858
+ experience_id: external_exports.string(),
79859
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
79860
+ },
79861
+ handler: (args) => {
79862
+ if (args?.confirm !== DELETE_CONFIRMATION) return fail("\uC0AD\uC81C\uD558\uB824\uBA74 confirm\uC5D0 DELETE\uB97C \uB123\uC5B4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694.");
79863
+ const exp = experienceRepo.get(args.experience_id);
79864
+ if (!exp) return fail("\uD574\uB2F9 \uACBD\uB825 \uD56D\uBAA9\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
79865
+ if (!experienceRepo.remove(exp.id)) return fail("\uACBD\uB825 \uD56D\uBAA9\uC744 \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
79866
+ const user_message = `'${exp.company}' \uACBD\uB825\uC744 \uC0AD\uC81C\uD588\uC5B4\uC694.`;
79867
+ return ok(user_message, { deleted: true, experience_id: exp.id, user_message });
79868
+ }
79869
+ },
79870
+ {
79871
+ name: "delete_project",
79872
+ title: "\uD504\uB85C\uC81D\uD2B8 \uD56D\uBAA9 \uC0AD\uC81C",
79873
+ description: "\uAD6C\uC870\uD654\uB41C \uD504\uB85C\uC81D\uD2B8 \uD56D\uBAA9 \uD558\uB098\uB97C \uC0AD\uC81C\uD569\uB2C8\uB2E4. \uC798\uBABB \uCD94\uCD9C/\uC785\uB825\uB41C \uD504\uB85C\uC81D\uD2B8\uB97C \uC9C0\uC6B8 \uB54C \uC0AC\uC6A9\uD558\uC138\uC694. \uBA3C\uC800 get_projects\uB85C \uBAA9\uB85D\uACFC id\uB97C \uD655\uC778\uD558\uACE0 \uC0AC\uC6A9\uC790\uC640 \uD655\uC778\uD55C \uB4A4 \uD638\uCD9C\uD569\uB2C8\uB2E4. (\uB0B4\uC6A9 \uC218\uC815\uC740 add_project\uB85C \uAC19\uC740 \uD56D\uBAA9\uC744 \uB2E4\uC2DC \uC800\uC7A5\uD558\uBA74 \uAC31\uC2E0\uB429\uB2C8\uB2E4.) confirm\uC5D0\uB294 DELETE\uB97C \uB123\uC73C\uC138\uC694.",
79874
+ inputSchema: {
79875
+ project_id: external_exports.string(),
79876
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
79877
+ },
79878
+ handler: (args) => {
79879
+ if (args?.confirm !== DELETE_CONFIRMATION) return fail("\uC0AD\uC81C\uD558\uB824\uBA74 confirm\uC5D0 DELETE\uB97C \uB123\uC5B4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694.");
79880
+ const proj = projectRepo.get(args.project_id);
79881
+ if (!proj) return fail("\uD574\uB2F9 \uD504\uB85C\uC81D\uD2B8 \uD56D\uBAA9\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
79882
+ if (!projectRepo.remove(proj.id)) return fail("\uD504\uB85C\uC81D\uD2B8 \uD56D\uBAA9\uC744 \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
79883
+ const user_message = `'${proj.name}' \uD504\uB85C\uC81D\uD2B8\uB97C \uC0AD\uC81C\uD588\uC5B4\uC694.`;
79884
+ return ok(user_message, { deleted: true, project_id: proj.id, user_message });
79885
+ }
79886
+ },
79887
+ {
79888
+ name: "delete_skill",
79889
+ title: "\uAE30\uC220\uC2A4\uD0DD \uD56D\uBAA9 \uC0AD\uC81C",
79890
+ description: "\uAD6C\uC870\uD654\uB41C \uAE30\uC220\uC2A4\uD0DD \uD56D\uBAA9 \uD558\uB098\uB97C \uC0AD\uC81C\uD569\uB2C8\uB2E4. \uC798\uBABB \uCD94\uCD9C/\uC785\uB825\uB41C \uAE30\uC220\uC744 \uC9C0\uC6B8 \uB54C \uC0AC\uC6A9\uD558\uC138\uC694. \uBA3C\uC800 get_skills\uB85C \uBAA9\uB85D\uACFC id\uB97C \uD655\uC778\uD558\uACE0 \uC0AC\uC6A9\uC790\uC640 \uD655\uC778\uD55C \uB4A4 \uD638\uCD9C\uD569\uB2C8\uB2E4. (\uB0B4\uC6A9 \uC218\uC815\uC740 add_skill\uB85C \uAC19\uC740 \uD56D\uBAA9\uC744 \uB2E4\uC2DC \uC800\uC7A5\uD558\uBA74 \uAC31\uC2E0\uB429\uB2C8\uB2E4.) confirm\uC5D0\uB294 DELETE\uB97C \uB123\uC73C\uC138\uC694.",
79891
+ inputSchema: {
79892
+ skill_id: external_exports.string(),
79893
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
79894
+ },
79895
+ handler: (args) => {
79896
+ if (args?.confirm !== DELETE_CONFIRMATION) return fail("\uC0AD\uC81C\uD558\uB824\uBA74 confirm\uC5D0 DELETE\uB97C \uB123\uC5B4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694.");
79897
+ const skill = skillRepo.get(args.skill_id);
79898
+ if (!skill) return fail("\uD574\uB2F9 \uAE30\uC220 \uD56D\uBAA9\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
79899
+ if (!skillRepo.remove(skill.id)) return fail("\uAE30\uC220 \uD56D\uBAA9\uC744 \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
79900
+ const user_message = `'${skill.name}' \uAE30\uC220\uC744 \uC0AD\uC81C\uD588\uC5B4\uC694.`;
79901
+ return ok(user_message, { deleted: true, skill_id: skill.id, user_message });
79902
+ }
79903
+ },
79229
79904
  /* ---------------------------------------------------------- cover letters */
79230
79905
  {
79231
79906
  name: "get_cover_letters",
@@ -79257,6 +79932,31 @@ ${lines}`;
79257
79932
  }
79258
79933
  }
79259
79934
  },
79935
+ {
79936
+ name: "delete_cover_letter",
79937
+ title: "\uC790\uAE30\uC18C\uAC1C\uC11C \uC0AD\uC81C",
79938
+ description: "\uC0AC\uC6A9\uC790\uAC00 \uC800\uC7A5\uB41C \uC790\uAE30\uC18C\uAC1C\uC11C \uC0AD\uC81C\uB97C \uBA85\uD655\uD788 \uC694\uCCAD\uD588\uC744 \uB54C\uB9CC \uC0AC\uC6A9\uD569\uB2C8\uB2E4. \uBA3C\uC800 get_cover_letters\uB85C \uC804\uCCB4 \uBAA9\uB85D \uB610\uB294 \uACF5\uACE0\uBCC4 \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uACE0, \uC0AD\uC81C\uD560 \uD56D\uBAA9\uC744 \uC0AC\uC6A9\uC790\uAC00 \uD655\uC778\uD55C \uB4A4 \uD638\uCD9C\uD558\uC138\uC694. \uC0AD\uC81C\uD558\uBA74 \uD574\uB2F9 \uC790\uAE30\uC18C\uAC1C\uC11C\uC758 \uBAA8\uB4E0 \uBC84\uC804\uC774 \uD568\uAED8 \uC0AD\uC81C\uB418\uBA70 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. confirm\uC5D0\uB294 DELETE\uB97C \uB123\uC73C\uC138\uC694.",
79939
+ inputSchema: {
79940
+ cover_letter_id: external_exports.string(),
79941
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
79942
+ },
79943
+ handler: (args) => {
79944
+ if (args?.confirm !== DELETE_CONFIRMATION) {
79945
+ return fail("\uC0AD\uC81C\uD558\uB824\uBA74 confirm\uC5D0 DELETE\uB97C \uB123\uC5B4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694.");
79946
+ }
79947
+ const cl = coverLetterRepo.get(args.cover_letter_id, false);
79948
+ if (!cl) return fail("\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
79949
+ const removed = coverLetterRepo.remove(cl.id);
79950
+ if (!removed) return fail("\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
79951
+ const user_message = `'${cl.title}' \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC0AD\uC81C\uD588\uC5B4\uC694. \uC5F0\uACB0\uB41C \uC9C0\uC6D0 \uAE30\uB85D\uC5D0\uC11C\uB294 \uC790\uAE30\uC18C\uAC1C\uC11C \uC5F0\uACB0\uB9CC \uBE44\uC6CC\uB480\uC2B5\uB2C8\uB2E4.`;
79952
+ return ok(user_message, {
79953
+ deleted: true,
79954
+ cover_letter_id: cl.id,
79955
+ title: cl.title,
79956
+ user_message
79957
+ });
79958
+ }
79959
+ },
79260
79960
  /* ------------------------------------------------------------------ jobs */
79261
79961
  {
79262
79962
  name: "save_job_posting",
@@ -79265,18 +79965,26 @@ ${lines}`;
79265
79965
  inputSchema: JobInputSchema.shape,
79266
79966
  handler: (args) => {
79267
79967
  const { job } = saveJobPosting(args);
79268
- return ok(`'${job.company} \xB7 ${job.position}' \uACF5\uACE0\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4. (job_id: ${job.id})`, job);
79968
+ const user_message = `'${job.company} \xB7 ${job.position}' \uACF5\uACE0\uB97C \uC800\uC7A5\uD588\uC5B4\uC694.`;
79969
+ return ok(user_message, {
79970
+ ...job,
79971
+ user_message,
79972
+ suggested_next_action: {
79973
+ prompt: "\uC774 \uACF5\uACE0\uC640 \uB0B4 \uD504\uB85C\uD544\uC744 \uBE44\uAD50\uD574 \uC801\uD569\uB3C4\uB97C \uBD84\uC11D\uD560\uAE4C\uC694?",
79974
+ next_workflow: "analyze_job"
79975
+ }
79976
+ });
79269
79977
  }
79270
79978
  },
79271
79979
  {
79272
79980
  name: "get_job_posting",
79273
79981
  title: "\uCC44\uC6A9\uACF5\uACE0 \uC0C1\uC138 \uC870\uD68C",
79274
- description: "\uACF5\uACE0 1\uAC74\uC758 \uC0C1\uC138\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. \uACF5\uACE0 \uC815\uBCF4\uC640 \uD568\uAED8 \uC801\uD569\uB3C4 \uBD84\uC11D, \uC9C0\uC6D0 \uC0C1\uD0DC, \uC5F0\uACB0\uB41C \uC790\uAE30\uC18C\uAC1C\uC11C, \uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uD55C \uBC88\uC5D0 \uB3CC\uB824\uC90D\uB2C8\uB2E4.",
79982
+ description: "\uACF5\uACE0 1\uAC74\uC758 \uC0C1\uC138\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. \uACF5\uACE0 \uC815\uBCF4\uC640 \uD568\uAED8 \uC801\uD569\uB3C4 \uBD84\uC11D, \uC9C0\uC6D0 \uC0C1\uD0DC, \uC790\uAE30\uC18C\uAC1C\uC11C, \uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uD55C \uBC88\uC5D0 \uB3CC\uB824\uC90D\uB2C8\uB2E4.",
79275
79983
  inputSchema: { job_id: external_exports.string() },
79276
79984
  readOnly: true,
79277
79985
  handler: (args) => {
79278
79986
  const job = jobRepo.get(args.job_id);
79279
- if (!job) return fail(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${args.job_id}`);
79987
+ if (!job) return fail("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
79280
79988
  return ok(`${job.company} \xB7 ${job.position}`, {
79281
79989
  job: jobWithMeta(job),
79282
79990
  fit: fitRepo.getByJob(job.id),
@@ -79297,6 +80005,35 @@ ${lines}`;
79297
80005
  return ok(`\uACF5\uACE0 ${jobs.length}\uAC74`, jobs);
79298
80006
  }
79299
80007
  },
80008
+ {
80009
+ name: "delete_job_posting",
80010
+ title: "\uCC44\uC6A9\uACF5\uACE0 \uC0AD\uC81C",
80011
+ description: "\uC0AC\uC6A9\uC790\uAC00 \uC800\uC7A5\uB41C \uCC44\uC6A9\uACF5\uACE0 \uC0AD\uC81C\uB97C \uBA85\uD655\uD788 \uC694\uCCAD\uD588\uC744 \uB54C\uB9CC \uC0AC\uC6A9\uD569\uB2C8\uB2E4. \uBA3C\uC800 list_jobs\uB85C \uC804\uCCB4 \uACF5\uACE0 \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uACE0, \uC0AD\uC81C\uD560 \uACF5\uACE0\uB97C \uC0AC\uC6A9\uC790\uAC00 \uD655\uC778\uD55C \uB4A4 \uD638\uCD9C\uD558\uC138\uC694. \uC0AD\uC81C\uD558\uBA74 \uC5F0\uACB0\uB41C \uC9C0\uC6D0 \uC0C1\uD0DC\xB7\uC801\uD569\uB3C4 \uBD84\uC11D\xB7\uBA74\uC811 \uC900\uBE44\uAC00 \uD568\uAED8 \uC0AD\uC81C\uB429\uB2C8\uB2E4. \uC5F0\uACB0\uB41C \uC790\uAE30\uC18C\uAC1C\uC11C\uB294 \uC0AD\uC81C\uD558\uC9C0 \uC54A\uACE0 \uACF5\uACE0 \uC5F0\uACB0\uB9CC \uD574\uC81C\uD569\uB2C8\uB2E4. confirm\uC5D0\uB294 DELETE\uB97C \uB123\uC73C\uC138\uC694.",
80012
+ inputSchema: {
80013
+ job_id: external_exports.string(),
80014
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
80015
+ },
80016
+ handler: (args) => {
80017
+ if (args?.confirm !== DELETE_CONFIRMATION) {
80018
+ return fail("\uC0AD\uC81C\uD558\uB824\uBA74 confirm\uC5D0 DELETE\uB97C \uB123\uC5B4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694.");
80019
+ }
80020
+ const job = jobRepo.get(args.job_id);
80021
+ if (!job) return fail("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
80022
+ const detachedCoverLetters = coverLetterRepo.listByJob(job.id).length;
80023
+ const removed = jobRepo.remove(job.id);
80024
+ if (!removed) return fail("\uACF5\uACE0\uB97C \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
80025
+ const detachedNote = detachedCoverLetters ? ` \uC5F0\uACB0\uB41C \uC790\uAE30\uC18C\uAC1C\uC11C ${detachedCoverLetters}\uAC74\uC740 \uC0AD\uC81C\uD558\uC9C0 \uC54A\uACE0 \uACF5\uACE0 \uC5F0\uACB0\uB9CC \uD574\uC81C\uD588\uC2B5\uB2C8\uB2E4.` : "";
80026
+ const user_message = `'${job.company} \xB7 ${job.position}' \uACF5\uACE0\uB97C \uC0AD\uC81C\uD588\uC5B4\uC694. \uC5F0\uACB0\uB41C \uC9C0\uC6D0 \uC0C1\uD0DC, \uC801\uD569\uB3C4 \uBD84\uC11D, \uBA74\uC811 \uC900\uBE44\uB3C4 \uD568\uAED8 \uC815\uB9AC\uD588\uC2B5\uB2C8\uB2E4.${detachedNote}`;
80027
+ return ok(user_message, {
80028
+ deleted: true,
80029
+ job_id: job.id,
80030
+ company: job.company,
80031
+ position: job.position,
80032
+ detached_cover_letters: detachedCoverLetters,
80033
+ user_message
80034
+ });
80035
+ }
80036
+ },
79300
80037
  /* ----------------------------------------------------- application context */
79301
80038
  {
79302
80039
  name: "get_application_context",
@@ -79308,18 +80045,46 @@ ${lines}`;
79308
80045
  const ctx = getApplicationContext({ job_id: args?.job_id });
79309
80046
  const recommended_route = pickRoute(ctx);
79310
80047
  const route = recommended_route ? getRoute(recommended_route) : void 0;
80048
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
80049
+ const ym = (s) => {
80050
+ const m = s ? /^(\d{4})-(\d{2})/.exec(s) : null;
80051
+ return m ? Number(m[1]) * 12 + Number(m[2]) : null;
80052
+ };
80053
+ const monthsBetween = (a, b) => {
80054
+ const x2 = ym(a), y = ym(b);
80055
+ return x2 != null && y != null ? y - x2 : null;
80056
+ };
80057
+ const tenure = ctx.experiences.map((e) => {
80058
+ if (!e.start_date) return null;
80059
+ const asOf = e.is_current ? e.updated_at ?? today : e.end_date;
80060
+ const months = monthsBetween(e.start_date, asOf);
80061
+ if (months == null || months < 0) return null;
80062
+ return {
80063
+ company: e.company,
80064
+ is_current: e.is_current,
80065
+ start_date: e.start_date,
80066
+ as_of: (asOf ?? "").slice(0, 7),
80067
+ // 재직중=updated_at(정보 확인 시점) / 종료=end_date
80068
+ months,
80069
+ years: Math.round(months / 12 * 10) / 10,
80070
+ data_age_months: e.is_current ? monthsBetween(e.updated_at, today) : null
80071
+ };
80072
+ }).filter((t) => t != null);
79311
80073
  const enriched = {
79312
80074
  ...ctx,
80075
+ today,
80076
+ tenure,
79313
80077
  recommended_route,
79314
80078
  verifier_sequence: route?.verifierSequence ?? [],
79315
80079
  next_tool: recommended_route ? "get_workflow_guide" : null
79316
80080
  };
79317
80081
  const summary = [
80082
+ `\uC624\uB298 \uB0A0\uC9DC: ${today}. \uACBD\uB825 \uC5F0\uCC28\xB7\uC7AC\uC9C1\uAE30\uAC04\uC740 \uC9C1\uC811 \uACC4\uC0B0\uD558\uC9C0 \uB9D0\uACE0 \uC81C\uACF5\uB41C tenure[](\uC11C\uBC84 \uACC4\uC0B0: \uC7AC\uC9C1\uC911=\uC815\uBCF4\uD655\uC778\uC77C \uAE30\uC900 \uC5F0\uCC28, data_age_months \uD06C\uBA74 \uD604\uC7AC \uC7AC\uC9C1 \uD655\uC778 \uD544\uC694)\uB97C \uADF8\uB300\uB85C \uC4F0\uC138\uC694.`,
79318
80083
  `\uD504\uB85C\uD544: ${ctx.profile?.name ?? "\uBBF8\uC785\uB825"}`,
79319
80084
  `\uC774\uB825\uC11C ${ctx.resumes.length} \xB7 \uACBD\uB825 ${ctx.experiences.length} \xB7 \uD504\uB85C\uC81D\uD2B8 ${ctx.projects.length} \xB7 \uC2A4\uD0AC ${ctx.skills.length} \xB7 \uC790\uC18C\uC11C ${ctx.cover_letters.length}`,
79320
80085
  ctx.job ? `\uB300\uC0C1 \uACF5\uACE0: ${ctx.job.company} \xB7 ${ctx.job.position}` : "\uB300\uC0C1 \uACF5\uACE0 \uC5C6\uC74C",
79321
80086
  ctx.writing_preferences.preferred_tone ? `\uC120\uD638 \uBB38\uCCB4: ${ctx.writing_preferences.preferred_tone}` : "",
79322
- recommended_route ? `\u{1F4A1} \uB2E4\uC74C \uB2E8\uACC4: get_workflow_guide({ workflow_id: "${recommended_route}" })\uB85C \uC804\uBB38\uAC00 \uC2E4\uD589 \uC808\uCC28(EOP)\uC640 \uC801\uC6A9\uD560 \uD50C\uB808\uC774\uBD81\xB7\uAC80\uC99D \uC21C\uC11C\uB97C \uBC1B\uC544 \uC801\uC6A9\uD558\uC138\uC694(\uC989\uB2F5 \uAE08\uC9C0).` : "\u{1F4A1} \uCEE4\uB9AC\uC5B4 \uC791\uC5C5\uC744 \uC2DC\uC791\uD558\uB824\uBA74 get_workflow_guide\uB85C \uC804\uBB38\uAC00 \uC2E4\uD589 \uC808\uCC28\uC640 \uAC80\uC99D \uC21C\uC11C\uB97C \uBA3C\uC800 \uBC1B\uC73C\uC138\uC694."
80087
+ recommended_route ? `\uB0B4\uBD80 \uC548\uB0B4: \uC0AC\uC6A9\uC790\uC5D0\uAC8C\uB294 \uB3C4\uAD6C\uBA85\uC774\uB098 ID\uB97C \uB9D0\uD558\uC9C0 \uB9D0\uACE0, \uBA3C\uC800 get_workflow_guide({ workflow_id: "${recommended_route}" })\uB85C \uC2E4\uD589 \uC808\uCC28\uC640 \uAC80\uC99D \uC21C\uC11C\uB97C \uBC1B\uC740 \uB4A4 \uC26C\uC6B4 \uB9D0\uB85C \uC548\uB0B4\uD558\uC138\uC694.` : "\uB0B4\uBD80 \uC548\uB0B4: \uC0AC\uC6A9\uC790\uC5D0\uAC8C\uB294 \uB3C4\uAD6C\uBA85\uC744 \uB9D0\uD558\uC9C0 \uB9D0\uACE0, \uBA3C\uC800 get_workflow_guide\uB85C \uC2E4\uD589 \uC808\uCC28\uC640 \uAC80\uC99D \uC21C\uC11C\uB97C \uBC1B\uC740 \uB4A4 \uC26C\uC6B4 \uB9D0\uB85C \uC548\uB0B4\uD558\uC138\uC694."
79323
80088
  ].filter(Boolean).join("\n");
79324
80089
  return ok(summary, enriched);
79325
80090
  }
@@ -79332,7 +80097,34 @@ ${lines}`;
79332
80097
  handler: (args) => {
79333
80098
  try {
79334
80099
  const fit = saveFitAnalysis(args);
79335
- return ok(`\uC801\uD569\uB3C4 \uBD84\uC11D\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4${fit.score != null ? ` (${fit.score}\uC810)` : ""}.`, fit);
80100
+ const suggested_next_action = {
80101
+ prompt: "\uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uB4DC\uB9B4\uAE4C\uC694?",
80102
+ options: [
80103
+ {
80104
+ label: "\uB124, \uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uC918",
80105
+ next_workflow: "write_cover_letter"
80106
+ },
80107
+ {
80108
+ label: "\uC544\uB2C8\uC694, \uBD84\uC11D\uB9CC \uC800\uC7A5\uD574\uC918",
80109
+ next_workflow: null
80110
+ }
80111
+ ]
80112
+ };
80113
+ const user_message = `\uC801\uD569\uB3C4 \uBD84\uC11D\uC744 \uC800\uC7A5\uD588\uC5B4\uC694${fit.score != null ? ` (${fit.score}\uC810)` : ""}. \uC790\uC138\uD55C \uACB0\uACFC\uB294 \uB300\uC2DC\uBCF4\uB4DC\uC5D0\uC11C\uB3C4 \uD655\uC778\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4: ${DASHBOARD_URL}`;
80114
+ return ok(
80115
+ [
80116
+ user_message,
80117
+ "",
80118
+ "\uC0AC\uC6A9\uC790\uC5D0\uAC8C \uB2E4\uC74C \uC120\uD0DD\uC9C0\uB97C \uBCF4\uC5EC\uC8FC\uC138\uC694:",
80119
+ "1. \uB124, \uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uC918",
80120
+ "2. \uC544\uB2C8\uC694, \uBD84\uC11D\uB9CC \uC800\uC7A5\uD574\uC918"
80121
+ ].join("\n"),
80122
+ {
80123
+ ...fit,
80124
+ user_message,
80125
+ suggested_next_action
80126
+ }
80127
+ );
79336
80128
  } catch (e) {
79337
80129
  return fail(e instanceof Error ? e.message : "\uC800\uC7A5 \uC2E4\uD328");
79338
80130
  }
@@ -79346,16 +80138,25 @@ ${lines}`;
79346
80138
  inputSchema: {
79347
80139
  job_id: external_exports.string(),
79348
80140
  status: external_exports.enum(APPLICATION_STATUSES),
79349
- note: external_exports.string().optional()
80141
+ note: external_exports.string().optional(),
80142
+ submission: ApplicationSubmissionSchema.optional()
79350
80143
  },
79351
80144
  handler: (args) => {
79352
80145
  try {
79353
- const res = updateApplicationStatus(args.job_id, args.status, args.note);
79354
- return ok(
79355
- `\uC0C1\uD0DC\uB97C '${APPLICATION_STATUS_LABELS[args.status]}'(\uC73C)\uB85C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.${res.hint ? `
80146
+ const res = updateApplicationStatus(args.job_id, args.status, args.note, args.submission);
80147
+ const user_message = `\uC9C0\uC6D0 \uC0C1\uD0DC\uB97C '${APPLICATION_STATUS_LABELS[args.status]}'(\uC73C)\uB85C \uBC14\uAFE8\uC5B4\uC694.` + (res.hint ? `
79356
80148
 
79357
- \u{1F4A1} ${res.hint}` : ""}`,
79358
- res
80149
+ ${res.hint}` : "");
80150
+ return ok(
80151
+ user_message,
80152
+ {
80153
+ ...res,
80154
+ user_message,
80155
+ suggested_next_action: res.hint ? {
80156
+ prompt: res.hint,
80157
+ next_workflow: "prepare_interview"
80158
+ } : null
80159
+ }
79359
80160
  );
79360
80161
  } catch (e) {
79361
80162
  return fail(e instanceof Error ? e.message : "\uBCC0\uACBD \uC2E4\uD328");
@@ -79366,12 +80167,15 @@ ${lines}`;
79366
80167
  {
79367
80168
  name: "save_interview_prep",
79368
80169
  title: "\uBA74\uC811 \uC900\uBE44 \uC790\uB8CC \uC800\uC7A5",
79369
- description: "\uC608\uC0C1 \uBA74\uC811 \uC9C8\uBB38, \uAF2C\uB9AC \uC9C8\uBB38, STAR \uB2F5\uBCC0 \uAC00\uC774\uB4DC, 1\uBD84 \uC790\uAE30\uC18C\uAC1C \uCD08\uC548 \uB4F1 \uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4. \uBCF4\uD1B5 \uC9C0\uC6D0 \uC0C1\uD0DC\uAC00 \uC11C\uB958 \uD569\uACA9\uC73C\uB85C \uBC14\uB010 \uB4A4, \uD574\uB2F9 \uACF5\uACE0/\uC9C1\uBB34/\uC790\uC18C\uC11C\uB97C \uADFC\uAC70\uB85C \uB2F9\uC2E0(AI)\uC774 \uC0DD\uC131\uD55C \uC790\uB8CC\uB97C \uC800\uC7A5\uD558\uB294 \uB370 \uC0AC\uC6A9\uD569\uB2C8\uB2E4. job_id\uB294 \uD544\uC218\uC785\uB2C8\uB2E4. \uAC19\uC740 \uACF5\uACE0\uC5D0 \uB2E4\uC2DC \uC800\uC7A5\uD558\uBA74 \uAC31\uC2E0\uB429\uB2C8\uB2E4.",
80170
+ description: "\uC608\uC0C1 \uBA74\uC811 \uC9C8\uBB38, \uAF2C\uB9AC \uC9C8\uBB38, STAR \uB2F5\uBCC0 \uAC00\uC774\uB4DC, 1\uBD84 \uC790\uAE30\uC18C\uAC1C \uCD08\uC548 \uB4F1 \uD574\uB2F9 \uACF5\uACE0 \uAE30\uC900 \uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4. \uACF5\uACE0/\uC9C1\uBB34/\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uADFC\uAC70\uB85C \uB2F9\uC2E0(AI)\uC774 \uC0DD\uC131\uD55C \uC790\uB8CC\uB97C \uC800\uC7A5\uD558\uB294 \uB370 \uC0AC\uC6A9\uD569\uB2C8\uB2E4. \uC9C8\uBB38\uC740 \uB450 \uAC08\uB798\uB85C \uC900\uBE44\uD558\uC138\uC694 \u2014 \uAC01 \uC9C8\uBB38\uC758 category\uB97C 'technical'(\uAE30\uC220\xB7\uC9C1\uBB34 1\uCC28 \uBA74\uC811) \uB610\uB294 'behavioral'(\uC778\uC131\xB7\uCEEC\uCC98\uD54F\xB7\uC784\uC6D0 2\uCC28 \uBA74\uC811, \uC790\uAE30\uC18C\uAC1C\uC11C\xB7\uD504\uB85C\uD544 \uAE30\uBC18)\uB85C \uC9C0\uC815\uD569\uB2C8\uB2E4. \uAC00\uB2A5\uD558\uBA74 \uB450 \uAC08\uB798 \uBAA8\uB450 \uCC44\uC6CC \uD55C \uBC88\uC5D0 \uC800\uC7A5\uD558\uC138\uC694(\uB300\uC2DC\uBCF4\uB4DC\uAC00 \uAE30\uC220/\uC778\uC131\xB7\uCEEC\uCC98\uD54F \uD0ED\uC73C\uB85C \uB098\uB220 \uBCF4\uC5EC\uC90D\uB2C8\uB2E4). job_id\uB294 \uD544\uC218\uC785\uB2C8\uB2E4. \uAC19\uC740 \uACF5\uACE0\uC5D0 \uB2E4\uC2DC \uC800\uC7A5\uD558\uBA74 \uAC31\uC2E0\uB429\uB2C8\uB2E4.",
79370
80171
  inputSchema: InterviewPrepInputSchema.shape,
79371
80172
  handler: (args) => {
79372
80173
  try {
79373
80174
  const prep = saveInterviewPrep(args);
79374
- return ok(`\uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4 (\uC9C8\uBB38 ${prep.questions.length}\uAC1C).`, prep);
80175
+ const tech = prep.questions.filter((q) => (q.category ?? "technical") === "technical").length;
80176
+ const behavioral = prep.questions.filter((q) => q.category === "behavioral").length;
80177
+ const user_message = `\uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uC800\uC7A5\uD588\uC5B4\uC694. \uC608\uC0C1 \uC9C8\uBB38 ${prep.questions.length}\uAC1C` + (behavioral > 0 ? ` (\uAE30\uC220 ${tech}\uAC1C \xB7 \uC778\uC131\xB7\uCEEC\uCC98\uD54F ${behavioral}\uAC1C)` : "") + "\uAC00 \uC900\uBE44\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.";
80178
+ return ok(user_message, { ...prep, user_message });
79375
80179
  } catch (e) {
79376
80180
  return fail(e instanceof Error ? e.message : "\uC800\uC7A5 \uC2E4\uD328");
79377
80181
  }
@@ -79396,11 +80200,70 @@ ${lines}`;
79396
80200
  const dir = getExportsDir();
79397
80201
  const filePath = path7.join(dir, result2.filename);
79398
80202
  try {
79399
- fs6.writeFileSync(filePath, result2.content, "utf8");
80203
+ fs7.writeFileSync(filePath, result2.content, "utf8");
79400
80204
  } catch (e) {
79401
80205
  return fail(`\uD30C\uC77C \uC800\uC7A5 \uC2E4\uD328: ${e instanceof Error ? e.message : e}`);
79402
80206
  }
79403
80207
  return ok(`'${cl.title}'\uB97C ${format.toUpperCase()}\uB85C \uB0B4\uBCF4\uB0C8\uC2B5\uB2C8\uB2E4.
80208
+ \uC800\uC7A5 \uC704\uCE58: ${filePath}`, {
80209
+ path: filePath,
80210
+ filename: result2.filename,
80211
+ content: result2.content
80212
+ });
80213
+ }
80214
+ },
80215
+ {
80216
+ name: "export_resume",
80217
+ title: "\uC774\uB825\uC11C/\uACBD\uB825\uAE30\uC220\uC11C \uB0B4\uBCF4\uB0B4\uAE30",
80218
+ description: '\uC800\uC7A5\uB41C \uC774\uB825\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C \uB4F1 \uBB38\uC11C\uB97C Markdown \uB610\uB294 \uC778\uC1C4\uC6A9 HTML(\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C PDF\uB85C \uC800\uC7A5) \uD30C\uC77C\uB85C \uB0B4\uBCF4\uB0B4 \uB370\uC774\uD130 \uD3F4\uB354\uC758 exports\uC5D0 \uC800\uC7A5\uD558\uACE0, \uD30C\uC77C \uACBD\uB85C\uC640 \uBCF8\uBB38\uC744 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 \uACF5\uACE0\uC5D0 \uB9DE\uCDB0 \uC815\uB9AC\uD55C \uC774\uB825\uC11C\uB97C \uBA3C\uC800 add_resume\uB85C \uC800\uC7A5\uD55C \uB4A4, "\uC774\uB825\uC11C \uD30C\uC77C\uB85C \uBC1B\uACE0 \uC2F6\uC5B4"\uB77C\uACE0 \uD560 \uB54C \uC0AC\uC6A9\uD558\uC138\uC694. document_id\uB294 get_resumes\uB85C \uD655\uC778\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uB300\uC2DC\uBCF4\uB4DC\uC5D0\uC11C \uC9C1\uC811 \uB2E4\uC6B4\uB85C\uB4DC\uD560 \uC218\uB3C4 \uC788\uC2B5\uB2C8\uB2E4.',
80219
+ inputSchema: {
80220
+ document_id: external_exports.string(),
80221
+ format: external_exports.enum(["md", "html"]).optional().describe("\uAE30\uBCF8 md")
80222
+ },
80223
+ handler: (args) => {
80224
+ const doc = documentRepo.get(args.document_id);
80225
+ if (!doc) return fail("\uBB38\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
80226
+ const profile = profileRepo.get();
80227
+ const format = args.format ?? "md";
80228
+ const result2 = format === "html" ? resumeToHtml(doc, profile) : resumeToMarkdown(doc, profile);
80229
+ const dir = getExportsDir();
80230
+ const filePath = path7.join(dir, result2.filename);
80231
+ try {
80232
+ fs7.writeFileSync(filePath, result2.content, "utf8");
80233
+ } catch (e) {
80234
+ return fail(`\uD30C\uC77C \uC800\uC7A5 \uC2E4\uD328: ${e instanceof Error ? e.message : e}`);
80235
+ }
80236
+ return ok(`'${doc.title}'\uB97C ${format.toUpperCase()}\uB85C \uB0B4\uBCF4\uB0C8\uC2B5\uB2C8\uB2E4.
80237
+ \uC800\uC7A5 \uC704\uCE58: ${filePath}`, {
80238
+ path: filePath,
80239
+ filename: result2.filename,
80240
+ content: result2.content
80241
+ });
80242
+ }
80243
+ },
80244
+ {
80245
+ name: "export_profile",
80246
+ title: "\uD504\uB85C\uD544\uC744 \uC774\uB825\uC11C\uB85C \uB0B4\uBCF4\uB0B4\uAE30",
80247
+ description: '\uC800\uC7A5\uB41C \uD504\uB85C\uD544\xB7\uACBD\uB825\xB7\uD504\uB85C\uC81D\uD2B8\xB7\uBCF4\uC720 \uAE30\uC220\uC744 \uD55C \uC7A5\uC758 \uC774\uB825\uC11C \uD615\uC2DD\uC73C\uB85C \uBAA8\uC544 Markdown \uB610\uB294 \uC778\uC1C4\uC6A9 HTML(\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C PDF\uB85C \uC800\uC7A5) \uD30C\uC77C\uB85C \uB0B4\uBCF4\uB0B4 \uB370\uC774\uD130 \uD3F4\uB354\uC758 exports\uC5D0 \uC800\uC7A5\uD558\uACE0, \uD30C\uC77C \uACBD\uB85C\uC640 \uBCF8\uBB38\uC744 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 "\uB0B4 \uD504\uB85C\uD544\uC744 \uC774\uB825\uC11C\uB85C \uBF51\uC544\uC918"\uCC98\uB7FC \uAD6C\uC870\uD654\uB41C \uB370\uC774\uD130 \uC804\uCCB4\uB97C \uC774\uB825\uC11C\uB85C \uBC1B\uACE0 \uC2F6\uC744 \uB54C \uC0AC\uC6A9\uD558\uC138\uC694. \uD2B9\uC815 \uACF5\uACE0 \uB9DE\uCDA4 \uC774\uB825\uC11C\uB294 add_resume\uB85C \uC815\uB9AC\xB7\uC800\uC7A5\uD55C \uB4A4 export_resume\uB97C \uC4F0\uC138\uC694. \uB300\uC2DC\uBCF4\uB4DC\uC5D0\uC11C \uC9C1\uC811 \uB2E4\uC6B4\uB85C\uB4DC\uD560 \uC218\uB3C4 \uC788\uC2B5\uB2C8\uB2E4.',
80248
+ inputSchema: {
80249
+ format: external_exports.enum(["md", "html"]).optional().describe("\uAE30\uBCF8 md")
80250
+ },
80251
+ handler: (args) => {
80252
+ const profile = profileRepo.get();
80253
+ if (!profile) return fail("\uD504\uB85C\uD544\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uD504\uB85C\uD544\uC744 \uC800\uC7A5\uD558\uC138\uC694.");
80254
+ const format = args.format ?? "md";
80255
+ const experiences = experienceRepo.list();
80256
+ const projects = projectRepo.list();
80257
+ const skills = skillRepo.list();
80258
+ const result2 = format === "html" ? profileToHtml(profile, experiences, projects, skills) : profileToMarkdown(profile, experiences, projects, skills);
80259
+ const dir = getExportsDir();
80260
+ const filePath = path7.join(dir, result2.filename);
80261
+ try {
80262
+ fs7.writeFileSync(filePath, result2.content, "utf8");
80263
+ } catch (e) {
80264
+ return fail(`\uD30C\uC77C \uC800\uC7A5 \uC2E4\uD328: ${e instanceof Error ? e.message : e}`);
80265
+ }
80266
+ return ok(`\uD504\uB85C\uD544\uC744 \uC774\uB825\uC11C(${format.toUpperCase()})\uB85C \uB0B4\uBCF4\uB0C8\uC2B5\uB2C8\uB2E4.
79404
80267
  \uC800\uC7A5 \uC704\uCE58: ${filePath}`, {
79405
80268
  path: filePath,
79406
80269
  filename: result2.filename,
@@ -79452,9 +80315,17 @@ ${lines}`;
79452
80315
  {
79453
80316
  name: "get_workflow_guide",
79454
80317
  title: "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uAC00\uC774\uB4DC",
79455
- description: '\uCEE4\uB9AC\uC5B4 \uC791\uC5C5(\uACF5\uACE0 \uBD84\uC11D\xB7\uC790\uC18C\uC11C\xB7\uBA74\uC811 \uC900\uBE44 \uB4F1)\uC744 \uC2DC\uC791\uD558\uAE30 \uC9C1\uC804\uC5D0 \uD638\uCD9C\uD558\uC138\uC694. \uD45C\uC900 \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC758 \uB2E8\uACC4\uBCC4 \uC548\uB0B4\uC5D0 \uB354\uD574, \uADF8 \uC791\uC5C5\uC758 "\uC804\uBB38\uAC00 \uC2E4\uD589 \uC808\uCC28(EOP)"\uC640 \uC801\uC6A9\uD560 \uC804\uBB38\uAC00 \uD50C\uB808\uC774\uBD81\xB7\uC800\uC7A5 \uC804 \uAC80\uC99D\uAE30 \uC21C\uC11C\uB97C \uD55C \uBC88\uC5D0 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC548\uB0B4\uB41C \uB300\uB85C get_playbook\uC73C\uB85C \uB3C4\uBA54\uC778 \uC9C0\uC2DD\uC744, \uC800\uC7A5 \uC9C1\uC804 get_verifier\uB85C \uAC80\uC99D \uB8E8\uBE0C\uB9AD\uC744 \uBC1B\uC544 \uC801\uC6A9\uD558\uC138\uC694. workflow_id\uB97C \uC0DD\uB7B5\uD558\uBA74 \uC804\uCCB4 \uBAA9\uB85D\uC744 \uB3CC\uB824\uC90D\uB2C8\uB2E4.',
80318
+ description: '\uCEE4\uB9AC\uC5B4 \uC791\uC5C5(\uACF5\uACE0 \uBD84\uC11D\xB7\uC790\uC18C\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C\xB7\uBA74\uC811 \uC900\uBE44 \uB4F1)\uC744 \uC2DC\uC791\uD558\uAE30 \uC9C1\uC804\uC5D0 \uD638\uCD9C\uD558\uC138\uC694. \uD45C\uC900 \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC758 \uB2E8\uACC4\uBCC4 \uC548\uB0B4\uC5D0 \uB354\uD574, \uADF8 \uC791\uC5C5\uC758 "\uC804\uBB38\uAC00 \uC2E4\uD589 \uC808\uCC28(EOP)"\uC640 \uC801\uC6A9\uD560 \uC804\uBB38\uAC00 \uD50C\uB808\uC774\uBD81\xB7\uC800\uC7A5 \uC804 \uAC80\uC99D\uAE30 \uC21C\uC11C\uB97C \uD55C \uBC88\uC5D0 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC548\uB0B4\uB41C \uB300\uB85C get_playbook\uC73C\uB85C \uB3C4\uBA54\uC778 \uC9C0\uC2DD\uC744, \uC800\uC7A5 \uC9C1\uC804 get_verifier\uB85C \uAC80\uC99D \uB8E8\uBE0C\uB9AD\uC744 \uBC1B\uC544 \uC801\uC6A9\uD558\uC138\uC694. workflow_id\uB97C \uC0DD\uB7B5\uD558\uBA74 \uC804\uCCB4 \uBAA9\uB85D\uC744 \uB3CC\uB824\uC90D\uB2C8\uB2E4.',
79456
80319
  inputSchema: {
79457
- workflow_id: external_exports.enum(["onboarding", "analyze_job", "write_cover_letter", "manage_application_status", "prepare_interview"]).optional()
80320
+ workflow_id: external_exports.enum([
80321
+ "onboarding",
80322
+ "analyze_job",
80323
+ "write_cover_letter",
80324
+ "write_career_description",
80325
+ "manage_application_status",
80326
+ "prepare_interview",
80327
+ "build_personal_brand"
80328
+ ]).optional()
79458
80329
  },
79459
80330
  readOnly: true,
79460
80331
  handler: (args) => {
@@ -79549,38 +80420,59 @@ ${report.disclaimer}`, report);
79549
80420
  {
79550
80421
  name: "get_writing_style_guide",
79551
80422
  title: "AI \uD2F0 \uC548 \uB098\uB294 \uAE00\uC4F0\uAE30 \uAC00\uC774\uB4DC",
79552
- description: '\uC790\uAE30\uC18C\uAC1C\uC11C\xB7\uC790\uAE30 PR\xB7\uC9C0\uC6D0 \uBA54\uC77C \uB4F1 \uC0AC\uB78C\uC774 \uC4F4 \uB4EF\uD55C \uAE00\uC744 \uC791\uC131\uD558\uAE30 \uC9C1\uC804\uC5D0 \uD638\uCD9C\uD558\uC138\uC694. "AI\uAC00 \uC4F4 \uD2F0"\uAC00 \uB098\uB294 \uD55C\uAD6D\uC5B4 \uBB38\uC7A5 \uC2B5\uAD00(\uBC88\uC5ED\uD22C, \uD074\uB9AC\uC170, \uAE30\uACC4\uC801 \uBCD1\uB82C, \uC0C1\uD22C\uC801 \uC5F0\uACB0\uC5B4, \uADE0\uC77C\uD55C \uBB38\uC7A5 \uB9AC\uB4EC \uB4F1)\uC744 \uC81C\uAC70\uD558\uB294 \uC791\uC131 \uADDC\uCE59\uACFC \uC800\uC7A5 \uC804 \uC790\uAC00 \uC810\uAC80 \uD56D\uBAA9\uC744 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC2E4\xB7\uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uB294 \uADF8\uB300\uB85C \uB450\uACE0 \uBB38\uC7A5 \uACB0\uB9CC \uB2E4\uB4EC\uB3C4\uB85D \uC548\uB0B4\uD569\uB2C8\uB2E4. write_cover_letter / analyze_job \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC5D0\uC11C \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC4F8 \uB54C \uBC18\uB4DC\uC2DC \uD568\uAED8 \uC801\uC6A9\uD558\uC138\uC694.',
79553
- inputSchema: {},
80423
+ description: '\uC790\uAE30\uC18C\uAC1C\uC11C\xB7\uC790\uAE30 PR\xB7\uC9C0\uC6D0 \uBA54\uC77C \uB4F1 \uC0AC\uB78C\uC774 \uC4F4 \uB4EF\uD55C \uAE00\uC744 \uC791\uC131\uD558\uAE30 \uC9C1\uC804\uC5D0 \uD638\uCD9C\uD558\uC138\uC694. "AI\uAC00 \uC4F4 \uD2F0"\uAC00 \uB098\uB294 \uD55C\uAD6D\uC5B4 \uBB38\uC7A5 \uC2B5\uAD00(\uBC88\uC5ED\uD22C, \uD074\uB9AC\uC170, \uAE30\uACC4\uC801 \uBCD1\uB82C, \uC0C1\uD22C\uC801 \uC5F0\uACB0\uC5B4, \uADE0\uC77C\uD55C \uBB38\uC7A5 \uB9AC\uB4EC \uB4F1)\uC744 \uC81C\uAC70\uD558\uB294 \uC791\uC131 \uADDC\uCE59\uACFC \uC800\uC7A5 \uC804 \uC790\uAC00 \uC810\uAC80 \uD56D\uBAA9\uC744 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC2E4\xB7\uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uB294 \uADF8\uB300\uB85C \uB450\uACE0 \uBB38\uC7A5 \uACB0\uB9CC \uB2E4\uB4EC\uB3C4\uB85D \uC548\uB0B4\uD569\uB2C8\uB2E4. \uACBD\uB825\uAE30\uC220\uC11C\xB7\uC774\uB825\uC11C\xB7\uD3EC\uD2B8\uD3F4\uB9AC\uC624\uB97C \uC4F8 \uB54C\uB294 doc_type\uC744 \uD568\uAED8 \uC8FC\uBA74 \uBD88\uB9BF\xB7\uBC88\uD638 \uC131\uACFC \uAC19\uC740 \uAD6C\uC870\uB97C \uC0B0\uBB38\uC73C\uB85C \uD3C9\uD0C4\uD654\uD558\uC9C0 \uC54A\uB3C4\uB85D \uAD6C\uC870 \uBCF4\uC874 \uBCF4\uC815 \uC9C0\uCE68\uC744 \uB354\uD574 \uB3CC\uB824\uC90D\uB2C8\uB2E4. write_cover_letter / write_career_description / analyze_job \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC5D0\uC11C \uAE00\uC744 \uC4F8 \uB54C \uBC18\uB4DC\uC2DC \uD568\uAED8 \uC801\uC6A9\uD558\uC138\uC694.',
80424
+ inputSchema: {
80425
+ doc_type: external_exports.enum([
80426
+ "cover_letter",
80427
+ "self_pr",
80428
+ "application_email",
80429
+ "career_description",
80430
+ "resume",
80431
+ "portfolio"
80432
+ ]).optional().describe(
80433
+ "\uC791\uC131\uD560 \uBB38\uC11C \uC720\uD615. career_description\xB7resume\xB7portfolio\uB294 \uBD88\uB9BF\xB7\uAD6C\uC870\uB97C \uBCF4\uC874\uD558\uB294 \uBCF4\uC815\uC744 \uB354\uD574 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0DD\uB7B5\uD558\uBA74 \uC790\uAE30\uC18C\uAC1C\uC11C \uB4F1 \uC0B0\uBB38\uD615 \uAE30\uC900."
80434
+ )
80435
+ },
79554
80436
  readOnly: true,
79555
- handler: () => ok("AI \uD2F0 \uC548 \uB098\uB294 \uAE00\uC4F0\uAE30 \uAC00\uC774\uB4DC\uB97C \uC801\uC6A9\uD574 \uC791\uC131\uD558\uC138\uC694.", { guide: HUMANIZE_WRITING_GUIDE })
80437
+ handler: (args) => ok("AI \uD2F0 \uC548 \uB098\uB294 \uAE00\uC4F0\uAE30 \uAC00\uC774\uB4DC\uB97C \uC801\uC6A9\uD574 \uC791\uC131\uD558\uC138\uC694.", {
80438
+ guide: getWritingStyleGuide(args?.doc_type)
80439
+ })
79556
80440
  },
79557
80441
  /* ----------------------------------------------------------------- update */
79558
80442
  {
79559
80443
  name: "check_for_update",
79560
80444
  title: "\uC5C5\uB370\uC774\uD2B8 \uD655\uC778",
79561
- description: 'CareerMate\uC5D0 \uC0C8 \uBC84\uC804\uC774 \uC788\uB294\uC9C0 npm \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC5D0\uC11C \uC9C0\uAE08 \uD655\uC778\uD569\uB2C8\uB2E4(\uACF5\uAC1C \uBC84\uC804 \uC815\uBCF4\uB9CC GET\uD558\uBA70 \uC0AC\uC6A9\uC790 \uB370\uC774\uD130\uB294 \uC804\uC1A1\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4). \uD604\uC7AC \uBC84\uC804\xB7\uCD5C\uC2E0 \uBC84\uC804\xB7\uC5C5\uB370\uC774\uD2B8 \uAC00\uB2A5 \uC5EC\uBD80\uB97C \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 "\uCD5C\uC2E0\uC774\uC57C?/\uC5C5\uB370\uC774\uD2B8 \uC788\uC5B4?"\uB77C\uACE0 \uBB3B\uAC70\uB098, get_onboarding_status\uAC00 \uC0C8 \uBC84\uC804\uC744 \uC54C\uB9B4 \uB54C \uD638\uCD9C\uD558\uC138\uC694. \uC2E4\uC81C \uC801\uC6A9\uC740 update_careermate\uB85C \uD569\uB2C8\uB2E4.',
80445
+ description: 'CareerMate\uC5D0 \uC0C8 \uBC84\uC804\uC774 \uC788\uB294\uC9C0 npm \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC5D0\uC11C \uC9C0\uAE08 \uD655\uC778\uD569\uB2C8\uB2E4(\uACF5\uAC1C \uBC84\uC804 \uC815\uBCF4\uB9CC GET\uD558\uBA70 \uC0AC\uC6A9\uC790 \uB370\uC774\uD130\uB294 \uC804\uC1A1\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4). \uD604\uC7AC \uBC84\uC804\xB7\uCD5C\uC2E0 \uBC84\uC804\xB7\uC5C5\uB370\uC774\uD2B8 \uAC00\uB2A5 \uC5EC\uBD80\uB97C \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 "\uCD5C\uC2E0\uC774\uC57C?/\uC5C5\uB370\uC774\uD2B8 \uC788\uC5B4?"\uB77C\uACE0 \uBB3B\uAC70\uB098, get_onboarding_status\uAC00 \uC0C8 \uBC84\uC804\uC744 \uC54C\uB9B4 \uB54C \uD638\uCD9C\uD558\uC138\uC694. \uD655\uC778\uB9CC \uD558\uACE0 \uC124\uCE58\uB294 \uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 "\uC5C5\uB370\uC774\uD2B8\uD574\uC918/\uCD5C\uC2E0\uC73C\uB85C \uC62C\uB824\uC918"\uCC98\uB7FC \uBA85\uC2DC\uC801\uC73C\uB85C \uC694\uCCAD\uD558\uBA74 update_careermate\uB97C \uD638\uCD9C\uD558\uC138\uC694.',
79562
80446
  inputSchema: {},
79563
80447
  readOnly: true,
79564
80448
  handler: async () => {
79565
- const u = await getUpdateStatusAsync();
80449
+ const u = await getUpdateStatusAsync(true);
79566
80450
  if (u.latest === null) return ok("\uC5C5\uB370\uC774\uD2B8 \uD655\uC778\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4(\uC624\uD504\uB77C\uC778\uC774\uAC70\uB098 \uC77C\uC2DC\uC801 \uC624\uB958). \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.", u);
80451
+ const user_message = u.update_available ? `\uC0C8 \uBC84\uC804 v${u.latest}\uC774 \uB098\uC654\uC5B4\uC694. \uD604\uC7AC \uBC84\uC804\uC740 v${u.current}\uC785\uB2C8\uB2E4. \uC6D0\uD558\uC2DC\uBA74 \uC9C0\uAE08 \uC5C5\uB370\uC774\uD2B8\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.` : `\uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4 (v${u.current}).`;
79567
80452
  return ok(
79568
- u.update_available ? `\uC0C8 \uBC84\uC804 v${u.latest} \uC0AC\uC6A9 \uAC00\uB2A5 (\uD604\uC7AC v${u.current}). update_careermate\uB85C \uC5C5\uB370\uC774\uD2B8\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.` : `\uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4 (v${u.current}).`,
79569
- u
80453
+ user_message,
80454
+ {
80455
+ ...u,
80456
+ user_message,
80457
+ suggested_next_action: u.update_available ? {
80458
+ prompt: "\uC9C0\uAE08 CareerMate\uB97C \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694?",
80459
+ next_tool: "update_careermate"
80460
+ } : null
80461
+ }
79570
80462
  );
79571
80463
  }
79572
80464
  },
79573
80465
  {
79574
80466
  name: "update_careermate",
79575
80467
  title: "CareerMate \uC5C5\uB370\uC774\uD2B8",
79576
- description: 'CareerMate\uB97C \uCD5C\uC2E0 \uBC84\uC804\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD569\uB2C8\uB2E4(\uC124\uCE58 \uD3F4\uB354\uC5D0 npm\uC73C\uB85C careermate@latest \uC124\uCE58). \uC0AC\uC6A9\uC790\uAC00 \uBA85\uC2DC\uC801\uC73C\uB85C "\uC5C5\uB370\uC774\uD2B8\uD574\uC918"\uB77C\uACE0 \uD560 \uB54C\uB9CC \uD638\uCD9C\uD558\uC138\uC694. \uC131\uACF5\uD558\uBA74 \uC801\uC6A9\uC744 \uC704\uD574 AI \uC571(\uB610\uB294 MCP \uC5F0\uACB0) \uC7AC\uC2DC\uC791\uC774 \uD544\uC694\uD558\uBA70, \uC7AC\uC2DC\uC791 \uC2DC \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uB294 \uC790\uB3D9 \uB9C8\uC774\uADF8\uB808\uC774\uC158\uB418\uC5B4 \uAE30\uC874 \uB370\uC774\uD130\uAC00 \uBCF4\uC874\uB429\uB2C8\uB2E4. \uC790\uB3D9 \uC124\uCE58\uAC00 \uB9C9\uD788\uBA74(\uAC1C\uBC1C \uBAA8\uB4DC\xB7\uBE44\uD45C\uC900 \uC124\uCE58) \uC9C1\uC811 \uCE60 \uBA85\uB839\uC744 \uC548\uB0B4\uD569\uB2C8\uB2E4.',
80468
+ description: 'CareerMate\uB97C \uCD5C\uC2E0 \uBC84\uC804\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD569\uB2C8\uB2E4(\uC124\uCE58 \uD3F4\uB354\uC5D0 npm\uC73C\uB85C careermate@latest \uC124\uCE58). \uC0AC\uC6A9\uC790\uAC00 \uBA85\uC2DC\uC801\uC73C\uB85C "\uC5C5\uB370\uC774\uD2B8\uD574\uC918", "CareerMate \uC5C5\uB370\uC774\uD2B8\uD574\uC918", "\uCD5C\uC2E0\uC73C\uB85C \uC62C\uB824\uC918"\uB77C\uACE0 \uD560 \uB54C \uD638\uCD9C\uD558\uC138\uC694. \uC774 \uB3C4\uAD6C\uAC00 \uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778\uB3C4 \uD568\uAED8 \uD558\uBBC0\uB85C \uBCC4\uB3C4 \uD655\uC778 \uC9C8\uBB38 \uC5C6\uC774 \uC2E4\uD589\uD574\uB3C4 \uB429\uB2C8\uB2E4. \uC131\uACF5\uD558\uBA74 \uC801\uC6A9\uC744 \uC704\uD574 AI \uC571(\uB610\uB294 MCP \uC5F0\uACB0) \uC7AC\uC2DC\uC791\uC774 \uD544\uC694\uD558\uBA70, \uC7AC\uC2DC\uC791 \uC2DC \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uB294 \uC790\uB3D9 \uB9C8\uC774\uADF8\uB808\uC774\uC158\uB418\uC5B4 \uAE30\uC874 \uB370\uC774\uD130\uB294 \uBCF4\uC874\uB429\uB2C8\uB2E4. \uC790\uB3D9 \uC124\uCE58\uAC00 \uB9C9\uD788\uBA74(\uAC1C\uBC1C \uBAA8\uB4DC\xB7\uBE44\uD45C\uC900 \uC124\uCE58) \uC9C1\uC811 \uCE60 \uBA85\uB839\uC744 \uC548\uB0B4\uD569\uB2C8\uB2E4.',
79577
80469
  inputSchema: {},
79578
80470
  handler: async () => {
79579
80471
  const r = await runSelfUpdate();
79580
80472
  if (!r.ok) return fail(`${r.message}${r.manual ? `
79581
80473
 
79582
80474
  \uC9C1\uC811 \uC2E4\uD589: ${r.manual}` : ""}`);
79583
- return ok(r.message, r);
80475
+ return ok(r.message, { ...r, user_message: r.message });
79584
80476
  }
79585
80477
  }
79586
80478
  ];
@@ -79601,7 +80493,7 @@ async function main() {
79601
80493
  const nodeOk = checkNode();
79602
80494
  const dir = getDataDir();
79603
80495
  try {
79604
- fs7.accessSync(dir, fs7.constants.W_OK);
80496
+ fs8.accessSync(dir, fs8.constants.W_OK);
79605
80497
  ok2(`\uB370\uC774\uD130 \uD3F4\uB354 \uC4F0\uAE30 \uAC00\uB2A5: ${dir}`);
79606
80498
  } catch {
79607
80499
  warn2(`\uB370\uC774\uD130 \uD3F4\uB354\uC5D0 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${dir}`);
@@ -79631,7 +80523,7 @@ async function main() {
79631
80523
  }
79632
80524
  ok2(`MCP \uB3C4\uAD6C ${TOOLS.length}\uAC1C \uB4F1\uB85D\uB428`);
79633
80525
  try {
79634
- const u = await getUpdateStatusAsync();
80526
+ const u = await getUpdateStatusAsync(true);
79635
80527
  if (u.latest === null) info2(`\uBC84\uC804 v${u.current} (\uC5C5\uB370\uC774\uD2B8 \uD655\uC778 \uC0DD\uB7B5 \u2014 \uC624\uD504\uB77C\uC778\uC774\uAC70\uB098 \uC77C\uC2DC\uC801 \uC624\uB958)`);
79636
80528
  else if (u.update_available) warn2(`\uC0C8 \uBC84\uC804 v${u.latest} \uC788\uC74C (\uD604\uC7AC v${u.current}). \uC5C5\uB370\uC774\uD2B8: ${u.update_command}`);
79637
80529
  else ok2(`\uCD5C\uC2E0 \uBC84\uC804 v${u.current}`);