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/mcp.mjs CHANGED
@@ -6891,12 +6891,12 @@ var require_dist = __commonJS({
6891
6891
  throw new Error(`Unknown format "${name}"`);
6892
6892
  return f;
6893
6893
  };
6894
- function addFormats(ajv, list, fs7, exportName) {
6894
+ function addFormats(ajv, list, fs8, exportName) {
6895
6895
  var _a2;
6896
6896
  var _b2;
6897
6897
  (_a2 = (_b2 = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b2.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
6898
6898
  for (const f of list)
6899
- ajv.addFormat(f, fs7[f]);
6899
+ ajv.addFormat(f, fs8[f]);
6900
6900
  }
6901
6901
  module2.exports = exports2 = formatsPlugin;
6902
6902
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -13333,9 +13333,9 @@ var require_timers = __commonJS({
13333
13333
  clearTimeout(this.handle);
13334
13334
  };
13335
13335
  var afterValue = function(value) {
13336
- return delay(+this).thenReturn(value);
13336
+ return delay2(+this).thenReturn(value);
13337
13337
  };
13338
- var delay = Promise2.delay = function(ms, value) {
13338
+ var delay2 = Promise2.delay = function(ms, value) {
13339
13339
  var ret2;
13340
13340
  var handle;
13341
13341
  if (value !== void 0) {
@@ -13357,7 +13357,7 @@ var require_timers = __commonJS({
13357
13357
  return ret2;
13358
13358
  };
13359
13359
  Promise2.prototype.delay = function(ms) {
13360
- return delay(ms, this);
13360
+ return delay2(ms, this);
13361
13361
  };
13362
13362
  var afterTimeout = function(promise, message, parent) {
13363
13363
  var err2;
@@ -35153,7 +35153,7 @@ var require_path_is_absolute = __commonJS({
35153
35153
  var require_files = __commonJS({
35154
35154
  "node_modules/mammoth/lib/docx/files.js"(exports2) {
35155
35155
  "use strict";
35156
- var fs7 = __require("fs");
35156
+ var fs8 = __require("fs");
35157
35157
  var url = __require("url");
35158
35158
  var os3 = __require("os");
35159
35159
  var dirname = __require("path").dirname;
@@ -35194,7 +35194,7 @@ var require_files = __commonJS({
35194
35194
  read
35195
35195
  };
35196
35196
  }
35197
- var readFile = promises.promisify(fs7.readFile.bind(fs7));
35197
+ var readFile = promises.promisify(fs8.readFile.bind(fs8));
35198
35198
  function uriToPath(uriString, platform) {
35199
35199
  if (!platform) {
35200
35200
  platform = os3.platform();
@@ -37936,11 +37936,11 @@ var require_options_reader = __commonJS({
37936
37936
  var require_unzip = __commonJS({
37937
37937
  "node_modules/mammoth/lib/unzip.js"(exports2) {
37938
37938
  "use strict";
37939
- var fs7 = __require("fs");
37939
+ var fs8 = __require("fs");
37940
37940
  var promises = require_promises();
37941
37941
  var zipfile = require_zipfile();
37942
37942
  exports2.openZip = openZip;
37943
- var readFile = promises.promisify(fs7.readFile);
37943
+ var readFile = promises.promisify(fs8.readFile);
37944
37944
  function openZip(options) {
37945
37945
  if (options.path) {
37946
37946
  return readFile(options.path).then(zipfile.openArrayBuffer);
@@ -38665,9 +38665,9 @@ var require_cjs = __commonJS({
38665
38665
  }
38666
38666
  return o;
38667
38667
  }
38668
- var fs7;
38668
+ var fs8;
38669
38669
  function get_fs() {
38670
- return fs7 || (fs7 = require$$0__default["default"]);
38670
+ return fs8 || (fs8 = require$$0__default["default"]);
38671
38671
  }
38672
38672
  function parse4(file, options) {
38673
38673
  if (file[0] == 80 && file[1] == 75) return parse_zip(file, options);
@@ -38952,7 +38952,7 @@ var require_cjs = __commonJS({
38952
38952
  }
38953
38953
  function read_file(filename2, options) {
38954
38954
  get_fs();
38955
- return parse4(fs7.readFileSync(filename2), options);
38955
+ return parse4(fs8.readFileSync(filename2), options);
38956
38956
  }
38957
38957
  function read(blob, options) {
38958
38958
  switch (options && options.type || "base64") {
@@ -39267,7 +39267,7 @@ var require_cjs = __commonJS({
39267
39267
  function write_file(cfb2, filename2, options) {
39268
39268
  get_fs();
39269
39269
  var o = _write(cfb2, options);
39270
- fs7.writeFileSync(filename2, o);
39270
+ fs8.writeFileSync(filename2, o);
39271
39271
  }
39272
39272
  function a2s(o) {
39273
39273
  var out = new Array(o.length);
@@ -39279,7 +39279,7 @@ var require_cjs = __commonJS({
39279
39279
  switch (options && options.type || "buffer") {
39280
39280
  case "file":
39281
39281
  get_fs();
39282
- fs7.writeFileSync(options.filename, o);
39282
+ fs8.writeFileSync(options.filename, o);
39283
39283
  return o;
39284
39284
  case "binary":
39285
39285
  return typeof o == "string" ? o : a2s(o);
@@ -46972,8 +46972,8 @@ function blend(fg, bg, alpha) {
46972
46972
  return Math.round(alpha * fg + (1 - alpha) * bg);
46973
46973
  }
46974
46974
  async function node_utils_fetchData(url) {
46975
- const fs7 = process.getBuiltinModule("fs/promises");
46976
- const data = await fs7.readFile(url);
46975
+ const fs8 = process.getBuiltinModule("fs/promises");
46976
+ const data = await fs8.readFile(url);
46977
46977
  return new Uint8Array(data);
46978
46978
  }
46979
46979
  function initGPU() {
@@ -47602,11 +47602,11 @@ function network_getArrayBuffer(val) {
47602
47602
  return typeof val !== "string" ? val : stringToBytes(val).buffer;
47603
47603
  }
47604
47604
  function getReadableStream(url, opts = null) {
47605
- const fs7 = process.getBuiltinModule("fs");
47605
+ const fs8 = process.getBuiltinModule("fs");
47606
47606
  const {
47607
47607
  Readable
47608
47608
  } = process.getBuiltinModule("stream");
47609
- const readStream = fs7.createReadStream(url, opts);
47609
+ const readStream = fs8.createReadStream(url, opts);
47610
47610
  return Readable.toWeb(readStream);
47611
47611
  }
47612
47612
  function getNetworkStream(url) {
@@ -64603,8 +64603,8 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
64603
64603
  url
64604
64604
  } = stream._source;
64605
64605
  this._isStreamingSupported = !disableStream;
64606
- const fs7 = process.getBuiltinModule("fs/promises");
64607
- fs7.lstat(url).then((stat) => {
64606
+ const fs8 = process.getBuiltinModule("fs/promises");
64607
+ fs8.lstat(url).then((stat) => {
64608
64608
  const readableStream = getReadableStream(url);
64609
64609
  this._reader = readableStream.getReader();
64610
64610
  const {
@@ -66250,9 +66250,9 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
66250
66250
  return;
66251
66251
  }
66252
66252
  if (reason instanceof RenderingCancelledException) {
66253
- let delay = RENDERING_CANCELLED_TIMEOUT;
66253
+ let delay2 = RENDERING_CANCELLED_TIMEOUT;
66254
66254
  if (reason.extraDelay > 0 && reason.extraDelay < 1e3) {
66255
- delay += reason.extraDelay;
66255
+ delay2 += reason.extraDelay;
66256
66256
  }
66257
66257
  intentState.streamReaderCancelTimeout = setTimeout(() => {
66258
66258
  intentState.streamReaderCancelTimeout = null;
@@ -66261,7 +66261,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
66261
66261
  reason,
66262
66262
  force: true
66263
66263
  });
66264
- }, delay);
66264
+ }, delay2);
66265
66265
  return;
66266
66266
  }
66267
66267
  }
@@ -92431,6 +92431,22 @@ var MIGRATIONS = [
92431
92431
 
92432
92432
  COMMIT;
92433
92433
  PRAGMA foreign_keys=ON;
92434
+ `,
92435
+ // v3 — per-job timeline entries. Document/cover-letter refs live in payload
92436
+ // snapshots instead of FKs so deleting a document does not erase the history.
92437
+ `
92438
+ CREATE TABLE IF NOT EXISTS application_timeline_events (
92439
+ id TEXT PRIMARY KEY,
92440
+ job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
92441
+ type TEXT NOT NULL,
92442
+ title TEXT NOT NULL,
92443
+ summary TEXT,
92444
+ payload TEXT NOT NULL DEFAULT '{}',
92445
+ occurred_at TEXT NOT NULL,
92446
+ created_at TEXT NOT NULL
92447
+ );
92448
+
92449
+ CREATE INDEX IF NOT EXISTS idx_timeline_job ON application_timeline_events(job_id, occurred_at ASC);
92434
92450
  `
92435
92451
  ];
92436
92452
  function migrate(db) {
@@ -92462,6 +92478,12 @@ function getDb() {
92462
92478
  _db = db;
92463
92479
  return db;
92464
92480
  }
92481
+ function closeDb() {
92482
+ if (_db) {
92483
+ _db.close();
92484
+ _db = null;
92485
+ }
92486
+ }
92465
92487
  var _inTx = false;
92466
92488
  function tx(fn) {
92467
92489
  if (_inTx) return fn();
@@ -92591,6 +92613,12 @@ function assertStatusTransition(from, to) {
92591
92613
  }
92592
92614
  }
92593
92615
  var DOCUMENT_KINDS = ["resume", "career_description", "portfolio", "other"];
92616
+ var DOCUMENT_KIND_LABELS = {
92617
+ resume: "\uC774\uB825\uC11C",
92618
+ career_description: "\uACBD\uB825\uAE30\uC220\uC11C",
92619
+ portfolio: "\uD3EC\uD2B8\uD3F4\uB9AC\uC624",
92620
+ other: "\uAE30\uD0C0 \uBB38\uC11C"
92621
+ };
92594
92622
  var CONTENT_SOURCES = ["manual", "upload", "ai", "edit"];
92595
92623
  var ACTIVITY_TYPES = [
92596
92624
  "profile_updated",
@@ -92603,6 +92631,13 @@ var ACTIVITY_TYPES = [
92603
92631
  "interview_prep_saved",
92604
92632
  "document_exported"
92605
92633
  ];
92634
+ var APPLICATION_TIMELINE_TYPES = [
92635
+ "job_saved",
92636
+ "fit_analysis_saved",
92637
+ "cover_letter_version_saved",
92638
+ "application_status_changed",
92639
+ "interview_prep_saved"
92640
+ ];
92606
92641
  var ENTITY_TYPES = [
92607
92642
  "profile",
92608
92643
  "experience",
@@ -92869,11 +92904,19 @@ var ApplicationInputSchema = external_exports.object({
92869
92904
  applied_at: optLine,
92870
92905
  notes: optNote
92871
92906
  });
92907
+ var ApplicationSubmissionSchema = external_exports.object({
92908
+ submitted_at: optLine.describe("\uC2E4\uC81C \uC81C\uCD9C\uC77C YYYY-MM-DD"),
92909
+ channel: optLine.describe("\uC81C\uCD9C \uCC44\uB110(\uC6D0\uD2F0\uB4DC/\uC0AC\uB78C\uC778/\uD68C\uC0AC \uCC44\uC6A9\uD398\uC774\uC9C0 \uB4F1)"),
92910
+ cover_letter_id: optLine,
92911
+ cover_letter_version_id: optLine,
92912
+ 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")
92913
+ });
92872
92914
  var ApplicationStatusUpdateSchema = external_exports.object({
92873
92915
  job_id: optLine,
92874
92916
  application_id: optLine,
92875
92917
  status: external_exports.enum(APPLICATION_STATUSES),
92876
- note: optNote.describe("\uC0C1\uD0DC \uBCC0\uACBD \uC0AC\uC720/\uBA54\uBAA8")
92918
+ note: optNote.describe("\uC0C1\uD0DC \uBCC0\uACBD \uC0AC\uC720/\uBA54\uBAA8"),
92919
+ submission: ApplicationSubmissionSchema.optional().describe("\uC9C0\uC6D0 \uC644\uB8CC \uAE30\uB85D\uC5D0 \uB0A8\uAE38 \uC81C\uCD9C \uC790\uB8CC \uC2A4\uB0C5\uC0F7")
92877
92920
  });
92878
92921
  var ApplicationRecordSchema = external_exports.object({
92879
92922
  ...baseRecord,
@@ -92891,8 +92934,10 @@ var StarGuideSchema = external_exports.object({
92891
92934
  action: optNote,
92892
92935
  result: optNote
92893
92936
  });
92937
+ var INTERVIEW_CATEGORIES = ["technical", "behavioral"];
92894
92938
  var InterviewQuestionSchema = external_exports.object({
92895
92939
  question: external_exports.string().max(MAX_NOTE),
92940
+ 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."),
92896
92941
  intent: external_exports.string().max(MAX_NOTE).optional().describe("\uC9C8\uBB38 \uC758\uB3C4"),
92897
92942
  followups: strList.optional().describe("\uC608\uC0C1 \uAF2C\uB9AC \uC9C8\uBB38"),
92898
92943
  answer_outline: optBody.describe("\uB2F5\uBCC0 \uAC00\uC774\uB4DC/\uD575\uC2EC \uD3EC\uC778\uD2B8")
@@ -92920,6 +92965,24 @@ var ActivityRecordSchema = external_exports.object({
92920
92965
  summary: external_exports.string(),
92921
92966
  created_at: isoDateTime
92922
92967
  });
92968
+ var ApplicationTimelineInputSchema = external_exports.object({
92969
+ job_id: reqLine,
92970
+ type: external_exports.enum(APPLICATION_TIMELINE_TYPES),
92971
+ title: reqLine,
92972
+ summary: optNote,
92973
+ payload: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
92974
+ occurred_at: optLine
92975
+ });
92976
+ var ApplicationTimelineRecordSchema = external_exports.object({
92977
+ id,
92978
+ job_id: external_exports.string(),
92979
+ type: external_exports.enum(APPLICATION_TIMELINE_TYPES),
92980
+ title: external_exports.string(),
92981
+ summary: external_exports.string().nullable(),
92982
+ payload: external_exports.record(external_exports.string(), external_exports.unknown()),
92983
+ occurred_at: isoDateTime,
92984
+ created_at: isoDateTime
92985
+ });
92923
92986
  var OnboardingStatusSchema = external_exports.object({
92924
92987
  completed: external_exports.boolean(),
92925
92988
  has_profile: external_exports.boolean(),
@@ -92956,7 +93019,7 @@ var ApplicationContextSchema = external_exports.object({
92956
93019
  });
92957
93020
 
92958
93021
  // packages/shared/src/build-info.ts
92959
- var APP_VERSION = true ? "0.0.4" : "0.0.0-dev";
93022
+ var APP_VERSION = true ? "0.0.6" : "0.0.0-dev";
92960
93023
  var BUNDLED = true;
92961
93024
 
92962
93025
  // packages/shared/src/index.ts
@@ -93050,7 +93113,7 @@ function mapExperience(r) {
93050
93113
  };
93051
93114
  }
93052
93115
  function expKey(company, role, start) {
93053
- return [norm(company), norm(role), norm(start)].join("\0");
93116
+ return [norm(company), norm(role), norm(start)].join("");
93054
93117
  }
93055
93118
  var experienceRepo = {
93056
93119
  list() {
@@ -93468,6 +93531,7 @@ var coverLetterRepo = {
93468
93531
  remove(id2) {
93469
93532
  const db = getDb();
93470
93533
  return tx(() => {
93534
+ db.prepare(`UPDATE applications SET cover_letter_id=NULL, updated_at=? WHERE cover_letter_id=?`).run(now(), id2);
93471
93535
  db.prepare(`DELETE FROM cover_letter_versions WHERE cover_letter_id=?`).run(id2);
93472
93536
  return db.prepare(`DELETE FROM cover_letters WHERE id=?`).run(id2).changes > 0;
93473
93537
  });
@@ -93556,9 +93620,11 @@ var jobRepo = {
93556
93620
  remove(id2) {
93557
93621
  const db = getDb();
93558
93622
  return tx(() => {
93623
+ db.prepare(`UPDATE cover_letters SET job_id=NULL, updated_at=? WHERE job_id=?`).run(now(), id2);
93559
93624
  db.prepare(`DELETE FROM fit_analyses WHERE job_id=?`).run(id2);
93560
93625
  db.prepare(`DELETE FROM applications WHERE job_id=?`).run(id2);
93561
93626
  db.prepare(`DELETE FROM interview_preps WHERE job_id=?`).run(id2);
93627
+ db.prepare(`DELETE FROM application_timeline_events WHERE job_id=?`).run(id2);
93562
93628
  return db.prepare(`DELETE FROM jobs WHERE id=?`).run(id2).changes > 0;
93563
93629
  });
93564
93630
  }
@@ -93671,7 +93737,7 @@ var applicationRepo = {
93671
93737
  const m = { ...app, ...input };
93672
93738
  getDb().prepare(
93673
93739
  `UPDATE applications SET status=?,resume_id=?,cover_letter_id=?,applied_at=?,notes=?,updated_at=? WHERE id=?`
93674
- ).run(m.status, m.resume_id, m.cover_letter_id, m.applied_at, m.notes, now(), app.id);
93740
+ ).run(m.status, m.resume_id ?? null, m.cover_letter_id ?? null, m.applied_at ?? null, m.notes ?? null, now(), app.id);
93675
93741
  return this.get(app.id);
93676
93742
  },
93677
93743
  setStatus(jobId, status, note) {
@@ -93752,9 +93818,51 @@ var activityRepo = {
93752
93818
  return getDb().prepare(`SELECT * FROM activities ORDER BY created_at DESC LIMIT ?`).all(limit);
93753
93819
  }
93754
93820
  };
93821
+ function mapTimelineEvent(r) {
93822
+ return {
93823
+ id: r.id,
93824
+ job_id: r.job_id,
93825
+ type: r.type,
93826
+ title: r.title,
93827
+ summary: r.summary,
93828
+ payload: fromJson(r.payload, {}),
93829
+ occurred_at: r.occurred_at,
93830
+ created_at: r.created_at
93831
+ };
93832
+ }
93833
+ var timelineRepo = {
93834
+ add(input) {
93835
+ const ts = now();
93836
+ const id2 = newId("tl_");
93837
+ getDb().prepare(
93838
+ `INSERT INTO application_timeline_events (id,job_id,type,title,summary,payload,occurred_at,created_at)
93839
+ VALUES (?,?,?,?,?,?,?,?)`
93840
+ ).run(
93841
+ id2,
93842
+ input.job_id,
93843
+ input.type,
93844
+ input.title,
93845
+ input.summary ?? null,
93846
+ toJson(input.payload ?? {}),
93847
+ input.occurred_at ?? ts,
93848
+ ts
93849
+ );
93850
+ return this.get(id2);
93851
+ },
93852
+ get(id2) {
93853
+ const r = getDb().prepare(`SELECT * FROM application_timeline_events WHERE id=?`).get(id2);
93854
+ return r ? mapTimelineEvent(r) : null;
93855
+ },
93856
+ listByJob(jobId) {
93857
+ return getDb().prepare(`SELECT * FROM application_timeline_events WHERE job_id=? ORDER BY occurred_at ASC, created_at ASC`).all(jobId).map(mapTimelineEvent);
93858
+ },
93859
+ remove(id2) {
93860
+ return getDb().prepare(`DELETE FROM application_timeline_events WHERE id=?`).run(id2).changes > 0;
93861
+ }
93862
+ };
93755
93863
 
93756
93864
  // packages/mcp-tools/src/tools.ts
93757
- import fs6 from "node:fs";
93865
+ import fs7 from "node:fs";
93758
93866
  import path7 from "node:path";
93759
93867
 
93760
93868
  // packages/core/src/onboarding.ts
@@ -93778,12 +93886,13 @@ function profileCompleteness(p) {
93778
93886
  function getOnboardingStatus() {
93779
93887
  const profile = profileRepo.get();
93780
93888
  const resumes = documentRepo.list("resume");
93889
+ const careerDescriptions = documentRepo.list("career_description");
93781
93890
  const coverLetters = coverLetterRepo.list();
93782
93891
  const experiences = experienceRepo.list();
93783
93892
  const skills = skillRepo.list();
93784
93893
  const jobs = jobRepo.list();
93785
93894
  const has_profile = !!(profile && profile.name);
93786
- const has_resume = resumes.length > 0;
93895
+ const has_resume = resumes.length > 0 || careerDescriptions.length > 0;
93787
93896
  const has_cover_letter = coverLetters.length > 0;
93788
93897
  const has_experience = experiences.length > 0;
93789
93898
  const has_skills = skills.length > 0;
@@ -94196,6 +94305,29 @@ function batchNames(names) {
94196
94305
  if (!shown) return "";
94197
94306
  return `: ${shown}${names.length > 8 ? " \uC678" : ""}`;
94198
94307
  }
94308
+ function coverLetterTimelineRef(coverLetter, version3, requestedVersionId) {
94309
+ const resolvedVersion = version3 ?? coverLetter.versions?.find((v) => v.id === requestedVersionId || v.id === coverLetter.current_version_id) ?? null;
94310
+ return {
94311
+ kind: "cover_letter",
94312
+ id: coverLetter.id,
94313
+ title: coverLetter.title,
94314
+ version_id: resolvedVersion?.id ?? requestedVersionId ?? coverLetter.current_version_id,
94315
+ version_no: resolvedVersion?.version_no ?? coverLetter.version_count
94316
+ };
94317
+ }
94318
+ function documentTimelineRef(doc) {
94319
+ return {
94320
+ kind: "document",
94321
+ id: doc.id,
94322
+ title: doc.title,
94323
+ document_kind: doc.kind
94324
+ };
94325
+ }
94326
+ function submittedAtToOccurrence(value) {
94327
+ if (!value) return void 0;
94328
+ if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return `${value}T00:00:00.000Z`;
94329
+ return void 0;
94330
+ }
94199
94331
  function addExperiences(inputs) {
94200
94332
  const res = experienceRepo.addMany(inputs);
94201
94333
  activityRepo.log(
@@ -94234,11 +94366,22 @@ function saveJobPosting(input, id2) {
94234
94366
  }
94235
94367
  function saveFitAnalysis(input) {
94236
94368
  const job = jobRepo.get(input.job_id);
94237
- 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.`);
94369
+ 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.");
94238
94370
  applicationRepo.ensure(job.id, "draft");
94239
94371
  const fit = fitRepo.save(input);
94240
94372
  const scoreText = fit.score != null ? ` (\uC801\uD569\uB3C4 ${fit.score}\uC810)` : "";
94241
94373
  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);
94374
+ timelineRepo.add({
94375
+ job_id: job.id,
94376
+ type: "fit_analysis_saved",
94377
+ title: "\uC801\uD569\uB3C4 \uBD84\uC11D",
94378
+ summary: fit.score != null ? `\uC885\uD569 ${fit.score}\uC810` : fit.summary ?? void 0,
94379
+ payload: {
94380
+ fit_id: fit.id,
94381
+ score: fit.score
94382
+ },
94383
+ occurred_at: fit.updated_at
94384
+ });
94242
94385
  return fit;
94243
94386
  }
94244
94387
  function saveCoverLetterVersion(input) {
@@ -94272,35 +94415,88 @@ function saveCoverLetterVersion(input) {
94272
94415
  "cover_letter",
94273
94416
  coverLetter.id
94274
94417
  );
94418
+ if (coverLetter.job_id) {
94419
+ timelineRepo.add({
94420
+ job_id: coverLetter.job_id,
94421
+ type: "cover_letter_version_saved",
94422
+ title: "\uC790\uAE30\uC18C\uAC1C\uC11C \uC791\uC131",
94423
+ summary: `${coverLetter.title} v${version3.version_no}`,
94424
+ payload: {
94425
+ cover_letter: coverLetterTimelineRef(coverLetter, version3)
94426
+ },
94427
+ occurred_at: version3.created_at
94428
+ });
94429
+ }
94275
94430
  return { coverLetter, version: version3, verification, forced };
94276
94431
  }
94277
- function updateApplicationStatus(jobId, status, note) {
94432
+ function updateApplicationStatus(jobId, status, note, submission) {
94278
94433
  const job = jobRepo.get(jobId);
94279
- if (!job) throw notFound(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${jobId}`);
94280
- const application = applicationRepo.setStatus(jobId, status, note);
94281
- activityRepo.log(
94282
- "application_status_changed",
94283
- `${job.company} \xB7 ${job.position} \uC0C1\uD0DC\uB97C '${APPLICATION_STATUS_LABELS[status]}'(\uC73C)\uB85C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.`,
94284
- "application",
94285
- application.id
94286
- );
94434
+ if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
94435
+ const before2 = applicationRepo.getByJob(jobId);
94436
+ const application = tx(() => {
94437
+ let application2 = applicationRepo.setStatus(jobId, status, note);
94438
+ const submittedDocs = (submission?.document_ids ?? []).map((docId) => documentRepo.get(docId)).filter((doc) => !!doc);
94439
+ const submittedCoverLetter = submission?.cover_letter_id ? coverLetterRepo.get(submission.cover_letter_id, true) : null;
94440
+ if (status === "applied" && submission) {
94441
+ const resumeLike = submittedDocs.find((doc) => doc.kind === "resume" || doc.kind === "career_description") ?? submittedDocs[0] ?? null;
94442
+ application2 = applicationRepo.upsert({
94443
+ job_id: jobId,
94444
+ resume_id: resumeLike?.id ?? application2.resume_id ?? void 0,
94445
+ cover_letter_id: submittedCoverLetter?.id ?? application2.cover_letter_id ?? void 0,
94446
+ applied_at: submission.submitted_at ?? application2.applied_at ?? void 0
94447
+ });
94448
+ }
94449
+ activityRepo.log(
94450
+ "application_status_changed",
94451
+ `${job.company} \xB7 ${job.position} \uC0C1\uD0DC\uB97C '${APPLICATION_STATUS_LABELS[status]}'(\uC73C)\uB85C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.`,
94452
+ "application",
94453
+ application2.id
94454
+ );
94455
+ const submittedAt = submission?.submitted_at?.trim();
94456
+ const channel = submission?.channel?.trim();
94457
+ const submissionPayload = submission && (submittedAt || channel || submittedCoverLetter || submittedDocs.length > 0) ? {
94458
+ submitted_at: submittedAt || null,
94459
+ channel: channel || null,
94460
+ cover_letter: submittedCoverLetter ? coverLetterTimelineRef(submittedCoverLetter, null, submission.cover_letter_version_id) : null,
94461
+ documents: submittedDocs.map(documentTimelineRef)
94462
+ } : null;
94463
+ timelineRepo.add({
94464
+ job_id: job.id,
94465
+ type: "application_status_changed",
94466
+ title: APPLICATION_STATUS_LABELS[status],
94467
+ summary: channel && status === "applied" ? `${channel}\uB85C \uC81C\uCD9C` : note ?? void 0,
94468
+ payload: {
94469
+ from_status: before2?.status ?? null,
94470
+ status,
94471
+ status_label: APPLICATION_STATUS_LABELS[status],
94472
+ note: note ?? null,
94473
+ submission: submissionPayload
94474
+ },
94475
+ occurred_at: submittedAtToOccurrence(submittedAt)
94476
+ });
94477
+ return application2;
94478
+ });
94287
94479
  const interview_unlocked = INTERVIEW_UNLOCK_STATUSES.includes(status);
94288
94480
  const hasPrep = !!interviewRepo.getByJob(jobId);
94289
- 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;
94481
+ 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;
94290
94482
  return { application, job, interview_unlocked, hint };
94291
94483
  }
94292
94484
  function saveInterviewPrep(input) {
94293
94485
  const job = jobRepo.get(input.job_id);
94294
- if (!job) throw notFound(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.job_id}`);
94295
- const app = applicationRepo.getByJob(job.id);
94296
- if (!app || !INTERVIEW_UNLOCK_STATUSES.includes(app.status)) {
94297
- const current = app ? APPLICATION_STATUS_LABELS[app.status] : "\uBBF8\uC9C0\uC6D0";
94298
- throw conflict(
94299
- `\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.`
94300
- );
94301
- }
94486
+ if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
94302
94487
  const prep = interviewRepo.save(input);
94303
94488
  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);
94489
+ timelineRepo.add({
94490
+ job_id: job.id,
94491
+ type: "interview_prep_saved",
94492
+ title: "\uBA74\uC811 \uC900\uBE44",
94493
+ summary: prep.questions.length > 0 ? `\uC608\uC0C1 \uC9C8\uBB38 ${prep.questions.length}\uAC1C` : prep.notes ?? void 0,
94494
+ payload: {
94495
+ interview_prep_id: prep.id,
94496
+ question_count: prep.questions.length
94497
+ },
94498
+ occurred_at: prep.updated_at
94499
+ });
94304
94500
  return prep;
94305
94501
  }
94306
94502
 
@@ -94321,6 +94517,8 @@ var HUMANIZE_WRITING_GUIDE = `# \uC0AC\uB78C\uC774 \uC4F4 \uAC83\uCC98\uB7FC: \u
94321
94517
  \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.
94322
94518
  \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.
94323
94519
 
94520
+ > **\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.
94521
+
94324
94522
  ## \uC808\uB300 \uC6D0\uCE59
94325
94523
  - \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.
94326
94524
  - \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.
@@ -94362,6 +94560,33 @@ var HUMANIZE_WRITING_GUIDE = `# \uC0AC\uB78C\uC774 \uC4F4 \uAC83\uCC98\uB7FC: \u
94362
94560
  - \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.
94363
94561
  - \uC0AC\uC2E4\xB7\uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uAC00 \uC6D0\uBCF8\uACFC 100% \uC77C\uCE58\uD558\uB294\uAC00?
94364
94562
  - \uC0AC\uC6A9\uC790\uC758 \uD1A4\uACFC \uAC15\uC870 \uD3EC\uC778\uD2B8\uAC00 \uC0B4\uC544 \uC788\uB294\uAC00?`;
94563
+ 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
94564
+
94565
+ \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.
94566
+
94567
+ ## \uC704 8\uAC1C AI-tell \uC911 \uAD6C\uC870\uD615\uC5D0\uC11C \uB2E4\uB974\uAC8C \uC801\uC6A9\uD558\uB294 \uAC83
94568
+ - 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.
94569
+ - \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.
94570
+
94571
+ ## \uAD6C\uC870\uB294 \uC0B4\uB9AC\uACE0, \uBD88\uB9BF '\uC548\uC758 \uC5B8\uC5B4'\uB9CC \uC0AC\uB78C\uB2F5\uAC8C
94572
+ - \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.
94573
+ - \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)
94574
+ - 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.
94575
+
94576
+ ## Do / Don't
94577
+ - 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.
94578
+ - 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.
94579
+
94580
+ \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.`;
94581
+ var STRUCTURED_DOC_TYPES = /* @__PURE__ */ new Set(["career_description", "resume", "portfolio"]);
94582
+ function getWritingStyleGuide(docType) {
94583
+ if (docType && STRUCTURED_DOC_TYPES.has(docType)) {
94584
+ return `${HUMANIZE_WRITING_GUIDE}
94585
+
94586
+ ${STRUCTURED_DOC_WRITING_GUIDE}`;
94587
+ }
94588
+ return HUMANIZE_WRITING_GUIDE;
94589
+ }
94365
94590
 
94366
94591
  // packages/workflows/src/definitions.ts
94367
94592
  var WORKFLOWS = [
@@ -94372,7 +94597,7 @@ var WORKFLOWS = [
94372
94597
  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.',
94373
94598
  steps: [
94374
94599
  "`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.",
94375
- '\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).',
94600
+ '\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).',
94376
94601
  '\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.)',
94377
94602
  "\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.",
94378
94603
  "\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).",
@@ -94393,9 +94618,9 @@ var WORKFLOWS = [
94393
94618
  "\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.",
94394
94619
  "\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.",
94395
94620
  "\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).",
94396
- "\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.",
94397
- '"\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.',
94398
- '\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.',
94621
+ "\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.",
94622
+ '\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".',
94623
+ '\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.',
94399
94624
  "\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).",
94400
94625
  "`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."
94401
94626
  ]
@@ -94412,7 +94637,24 @@ var WORKFLOWS = [
94412
94637
  "\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.",
94413
94638
  "\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).",
94414
94639
  "\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.",
94415
- "\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."
94640
+ "\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."
94641
+ ]
94642
+ },
94643
+ {
94644
+ id: "write_career_description",
94645
+ title: "\uACBD\uB825\uAE30\uC220\uC11C \uC791\uC131",
94646
+ 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.",
94647
+ 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.',
94648
+ steps: [
94649
+ "`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.",
94650
+ "\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.",
94651
+ '`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.',
94652
+ "\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.",
94653
+ "\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.",
94654
+ '\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.',
94655
+ '\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.',
94656
+ "\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.",
94657
+ "\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."
94416
94658
  ]
94417
94659
  },
94418
94660
  {
@@ -94423,7 +94665,7 @@ var WORKFLOWS = [
94423
94665
  steps: [
94424
94666
  "\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.",
94425
94667
  "\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.",
94426
- "\uBCC0\uACBD \uACB0\uACFC\uB97C \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC54C\uB9B0\uB2E4.",
94668
+ "\uBCC0\uACBD \uACB0\uACFC\uB97C \uC0AC\uC6A9\uC790\uC758 \uC5B8\uC5B4\uB85C \uC27D\uAC8C \uC54C\uB9B0\uB2E4.",
94427
94669
  "\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.",
94428
94670
  "\uD544\uC694\uD558\uBA74 `open_application`\uC73C\uB85C \uD574\uB2F9 \uC9C0\uC6D0 \uAC74\uC744 \uC5F4\uC5B4 \uBCF4\uC5EC\uC900\uB2E4."
94429
94671
  ]
@@ -94431,14 +94673,29 @@ var WORKFLOWS = [
94431
94673
  {
94432
94674
  id: "prepare_interview",
94433
94675
  title: "\uBA74\uC811 \uC900\uBE44",
94434
- 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.",
94435
- 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.',
94676
+ 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.",
94677
+ 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.',
94436
94678
  steps: [
94437
94679
  "`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.",
94438
94680
  "\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.",
94439
94681
  "\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.",
94440
94682
  "\uC900\uBE44 \uB0B4\uC6A9\uC744 `save_interview_prep`\uB85C \uC800\uC7A5\uD55C\uB2E4(job_id, questions, star_guides, self_introduction, notes).",
94441
- "\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."
94683
+ "\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."
94684
+ ]
94685
+ },
94686
+ {
94687
+ id: "build_personal_brand",
94688
+ title: "\uD37C\uC2A4\uB110 \uBE0C\uB79C\uB4DC (LinkedIn\xB7\uD3EC\uD2B8\uD3F4\uB9AC\uC624)",
94689
+ 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.",
94690
+ 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.',
94691
+ steps: [
94692
+ "`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.",
94693
+ '`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.',
94694
+ "\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.",
94695
+ "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.",
94696
+ '\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.',
94697
+ "\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.",
94698
+ "\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."
94442
94699
  ]
94443
94700
  }
94444
94701
  ];
@@ -94522,7 +94779,7 @@ var CAREER_ROUTES = {
94522
94779
  verifierSequence: ["truthfulness", "consistency"]
94523
94780
  },
94524
94781
  analyze_job: {
94525
- eop: "job-analysis",
94782
+ eop: ["job-analysis", "fit-analysis"],
94526
94783
  expertSequence: ["fit-matching", "company-research"],
94527
94784
  verifierSequence: ["truthfulness", "consistency", "responsiveness-on-target"],
94528
94785
  loop: "draft_verify_revise"
@@ -94533,11 +94790,46 @@ var CAREER_ROUTES = {
94533
94790
  verifierSequence: ["human-voice", "truthfulness", "ats-compat", "consistency"],
94534
94791
  loop: "draft_verify_revise"
94535
94792
  },
94793
+ write_career_description: {
94794
+ eop: "career-description",
94795
+ expertSequence: ["resume", "ats"],
94796
+ verifierSequence: ["truthfulness", "consistency", "ats-compat"],
94797
+ loop: "draft_verify_revise"
94798
+ },
94536
94799
  prepare_interview: {
94537
94800
  eop: "interview-prep",
94538
- expertSequence: ["interview-behavioral", "interview-technical", "company-research"],
94801
+ expertSequence: ["recruiter-screen", "interview-behavioral", "interview-technical", "company-research"],
94539
94802
  verifierSequence: ["truthfulness", "consistency"],
94540
94803
  loop: "draft_verify_revise"
94804
+ },
94805
+ // R7 A2: manage_application_status had no route → rejection-triage / offer-evaluation /
94806
+ // salary-negotiation were orphaned (served only on-demand via get_playbook). Route the
94807
+ // decision-stage playbooks so they are pushed when the user manages application outcomes.
94808
+ // R7 A4: A2 routed the decision-stage playbooks, but the EOP-free lifecycle domains
94809
+ // onboarding-first-90-days and networking-referrals were still on-demand-only. Anchor them
94810
+ // here (the only EOP-free lifecycle route). NOTE: networking's strongest lever is *before*
94811
+ // applied (pre-referral, networking-referrals §2) — this route is the surfacing point, not
94812
+ // the recommended timing, so surface networking early when the user is pre-application.
94813
+ // linkedin-profile/portfolio stay on-demand pending a dedicated personal-brand workflow id
94814
+ // (FIXES.md / STATUS §7.9 follow-up — onboarding EOP is résumé-import-only, so brand-
94815
+ // creation playbooks would blur its stage meaning). Additive wiring (A1/A2 precedent).
94816
+ manage_application_status: {
94817
+ expertSequence: [
94818
+ "rejection-triage-iteration",
94819
+ "offer-evaluation-decision",
94820
+ "salary-negotiation",
94821
+ "onboarding-first-90-days",
94822
+ "networking-referrals"
94823
+ ],
94824
+ verifierSequence: ["truthfulness", "consistency"]
94825
+ },
94826
+ // R7 후속: linkedin-profile·portfolio는 onboarding(이력서 임포트)과 단계 의미가
94827
+ // 달라(개인 브랜드 자산 *생성*) 전용 워크플로우로 분리(consensus 권고). networking은
94828
+ // manage_application_status에 이미 정박. 저장은 add_resume(kind=portfolio); linkedin
94829
+ // 전용 저장/검증은 Phase B.
94830
+ build_personal_brand: {
94831
+ expertSequence: ["linkedin-profile", "portfolio"],
94832
+ verifierSequence: ["truthfulness", "human-voice", "consistency"]
94541
94833
  }
94542
94834
  };
94543
94835
  function getRoute(id2) {
@@ -94547,12 +94839,15 @@ function renderRouteGuide(id2) {
94547
94839
  const route = CAREER_ROUTES[id2];
94548
94840
  if (!route) return void 0;
94549
94841
  const lines = [];
94550
- if (route.eop) {
94842
+ const eops = route.eop ? Array.isArray(route.eop) ? route.eop : [route.eop] : [];
94843
+ if (eops.length) {
94551
94844
  lines.push("---", "");
94552
94845
  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");
94553
94846
  lines.push("");
94554
- lines.push(getEop(route.eop));
94555
- lines.push("");
94847
+ for (const e of eops) {
94848
+ lines.push(getEop(e));
94849
+ lines.push("");
94850
+ }
94556
94851
  }
94557
94852
  lines.push("## \uC774 \uC791\uC5C5\uC5D0 \uC801\uC6A9\uD560 \uC804\uBB38\uAC00 \uD50C\uB808\uC774\uBD81");
94558
94853
  lines.push("\uC791\uC131 \uC9C1\uC804, \uC544\uB798 \uB3C4\uBA54\uC778\uC758 \uAE4A\uC740 \uC9C0\uC2DD\uC744 \uBC1B\uC544 \uC801\uC6A9\uD558\uC138\uC694:");
@@ -94666,10 +94961,22 @@ function slugify(input, fallback = "document") {
94666
94961
  function joinBlocks(blocks) {
94667
94962
  return blocks.filter((b) => typeof b === "string" && b.trim().length > 0).join("\n\n");
94668
94963
  }
94964
+ function bulletList(items) {
94965
+ const lines = items.filter((i2) => typeof i2 === "string" && i2.trim().length > 0).map((i2) => `- ${i2.trim()}`);
94966
+ return lines.join("\n");
94967
+ }
94669
94968
  function kv(label, value) {
94670
94969
  if (!value || !value.trim()) return "";
94671
94970
  return `**${label}**: ${value.trim()}`;
94672
94971
  }
94972
+ function dateRange(start, end, current = false) {
94973
+ const s = (start ?? "").trim();
94974
+ const e = current ? "\uD604\uC7AC" : (end ?? "").trim();
94975
+ if (!s && !e) return "";
94976
+ if (!s) return e;
94977
+ if (!e) return s;
94978
+ return `${s} ~ ${e}`;
94979
+ }
94673
94980
  function renderInline(escaped) {
94674
94981
  let out = escaped;
94675
94982
  out = out.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
@@ -94778,6 +95085,123 @@ function coverLetterToHtml(cl, opts) {
94778
95085
  content: toPrintableHtml(cl.title, coverLetterBody(cl, opts))
94779
95086
  };
94780
95087
  }
95088
+ function resumeToMarkdown(doc, profile) {
95089
+ const kindLabel = DOCUMENT_KIND_LABELS[doc.kind] ?? doc.kind;
95090
+ const header = [];
95091
+ if (profile?.name) header.push(kv("\uC774\uB984", profile.name));
95092
+ if (profile?.email) header.push(kv("\uC774\uBA54\uC77C", profile.email));
95093
+ if (profile?.phone) header.push(kv("\uC5F0\uB77D\uCC98", profile.phone));
95094
+ const body = joinBlocks([
95095
+ `# ${doc.title}`,
95096
+ `_${kindLabel}_`,
95097
+ header.length ? header.filter(Boolean).join("\n\n") : "",
95098
+ header.length ? "---" : "",
95099
+ (doc.content ?? "").trim() || "_(\uB0B4\uC6A9 \uC5C6\uC74C)_"
95100
+ ]);
95101
+ return {
95102
+ filename: `${slugify(doc.title, doc.kind)}.md`,
95103
+ mimeType: MD_MIME,
95104
+ content: body
95105
+ };
95106
+ }
95107
+ function resumeToHtml(doc, profile) {
95108
+ const md = resumeToMarkdown(doc, profile);
95109
+ return {
95110
+ filename: `${slugify(doc.title, doc.kind)}.html`,
95111
+ mimeType: HTML_MIME,
95112
+ content: toPrintableHtml(doc.title, md.content)
95113
+ };
95114
+ }
95115
+ function profileToMarkdown(profile, experiences = [], projects = [], skills = []) {
95116
+ const name = profile.name?.trim() || "\uC774\uB825\uC11C";
95117
+ const contact = [];
95118
+ if (profile.email) contact.push(profile.email);
95119
+ if (profile.phone) contact.push(profile.phone);
95120
+ if (profile.location) contact.push(profile.location);
95121
+ const links = (profile.links ?? []).filter((l2) => l2.url).map((l2) => `[${l2.label || l2.url}](${l2.url})`);
95122
+ const headerBlocks = joinBlocks([
95123
+ `# ${name}`,
95124
+ profile.headline ? `## ${profile.headline}` : "",
95125
+ contact.length ? contact.join(" \xB7 ") : "",
95126
+ links.length ? links.join(" \xB7 ") : ""
95127
+ ]);
95128
+ const summaryBlock = profile.summary?.trim() ? joinBlocks(["## \uC18C\uAC1C", profile.summary.trim()]) : "";
95129
+ const desiredLines = [];
95130
+ if (profile.desired_roles?.length) {
95131
+ desiredLines.push(kv("\uD76C\uB9DD \uC9C1\uBB34", profile.desired_roles.join(", ")));
95132
+ }
95133
+ if (profile.desired_conditions?.trim()) {
95134
+ desiredLines.push(kv("\uD76C\uB9DD \uC870\uAC74", profile.desired_conditions));
95135
+ }
95136
+ const desiredBlock = desiredLines.length ? joinBlocks(["## \uD76C\uB9DD \uC0AC\uD56D", desiredLines.filter(Boolean).join("\n\n")]) : "";
95137
+ const expBlocks = experiences.length ? joinBlocks([
95138
+ "## \uACBD\uB825",
95139
+ ...experiences.map((e) => {
95140
+ const range2 = dateRange(e.start_date, e.end_date, e.is_current);
95141
+ const titleParts = [e.company, e.role].filter(Boolean).join(" \u2014 ");
95142
+ const subParts = [e.employment_type, range2].filter(Boolean).join(" \xB7 ");
95143
+ return joinBlocks([
95144
+ `### ${titleParts}`,
95145
+ subParts ? `_${subParts}_` : "",
95146
+ e.description?.trim() ? e.description.trim() : "",
95147
+ e.achievements?.length ? bulletList(e.achievements) : "",
95148
+ e.tech?.length ? kv("\uAE30\uC220", e.tech.join(", ")) : ""
95149
+ ]);
95150
+ })
95151
+ ]) : "";
95152
+ const projBlocks = projects.length ? joinBlocks([
95153
+ "## \uD504\uB85C\uC81D\uD2B8",
95154
+ ...projects.map((p) => {
95155
+ const range2 = dateRange(p.start_date, p.end_date);
95156
+ const subParts = [p.role, range2].filter(Boolean).join(" \xB7 ");
95157
+ return joinBlocks([
95158
+ `### ${p.name}`,
95159
+ subParts ? `_${subParts}_` : "",
95160
+ p.description?.trim() ? p.description.trim() : "",
95161
+ p.highlights?.length ? bulletList(p.highlights) : "",
95162
+ p.tech?.length ? kv("\uAE30\uC220", p.tech.join(", ")) : "",
95163
+ p.url ? kv("\uB9C1\uD06C", p.url) : ""
95164
+ ]);
95165
+ })
95166
+ ]) : "";
95167
+ let skillsBlock = "";
95168
+ if (skills.length) {
95169
+ const groups = /* @__PURE__ */ new Map();
95170
+ for (const s of skills) {
95171
+ const cat = s.category?.trim() || "\uAE30\uD0C0";
95172
+ const label = s.level?.trim() ? `${s.name} (${s.level.trim()})` : s.name;
95173
+ if (!groups.has(cat)) groups.set(cat, []);
95174
+ groups.get(cat).push(label);
95175
+ }
95176
+ const lines = [...groups.entries()].map(
95177
+ ([cat, items]) => kv(cat, items.join(", "))
95178
+ );
95179
+ skillsBlock = joinBlocks(["## \uBCF4\uC720 \uAE30\uC220", lines.filter(Boolean).join("\n\n")]);
95180
+ }
95181
+ const content = joinBlocks([
95182
+ headerBlocks,
95183
+ "---",
95184
+ summaryBlock,
95185
+ desiredBlock,
95186
+ expBlocks,
95187
+ projBlocks,
95188
+ skillsBlock
95189
+ ]);
95190
+ return {
95191
+ filename: `${slugify(name, "resume")}-resume.md`,
95192
+ mimeType: MD_MIME,
95193
+ content
95194
+ };
95195
+ }
95196
+ function profileToHtml(profile, experiences = [], projects = [], skills = []) {
95197
+ const md = profileToMarkdown(profile, experiences, projects, skills);
95198
+ const title = profile.name?.trim() || "\uC774\uB825\uC11C";
95199
+ return {
95200
+ filename: `${slugify(title, "resume")}-resume.html`,
95201
+ mimeType: HTML_MIME,
95202
+ content: toPrintableHtml(title, md.content)
95203
+ };
95204
+ }
94781
95205
 
94782
95206
  // packages/parsers/src/html.ts
94783
95207
  var NAMED_ENTITIES = {
@@ -95338,7 +95762,12 @@ async function extractDocument(input, baseDir) {
95338
95762
  path: filePath,
95339
95763
  ...over
95340
95764
  });
95341
- if (SENSITIVE_PATH_RE.test(filePath)) {
95765
+ let realPath = filePath;
95766
+ try {
95767
+ realPath = fs4.realpathSync.native(filePath);
95768
+ } catch {
95769
+ }
95770
+ if (SENSITIVE_PATH_RE.test(filePath) || SENSITIVE_PATH_RE.test(realPath)) {
95342
95771
  return base("unsupported", {
95343
95772
  warnings: [
95344
95773
  "\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."
@@ -95675,14 +96104,94 @@ function toCallToolResult(r) {
95675
96104
  }
95676
96105
 
95677
96106
  // packages/mcp-tools/src/bridge.ts
95678
- import { spawn } from "node:child_process";
96107
+ import { spawn, spawnSync } from "node:child_process";
96108
+ import fs5 from "node:fs";
95679
96109
  import path5 from "node:path";
95680
96110
  import { fileURLToPath as fileURLToPath2 } from "node:url";
95681
96111
  var __dirname = path5.dirname(fileURLToPath2(import.meta.url));
95682
96112
  var REPO_ROOT = path5.resolve(__dirname, "..", "..", "..");
95683
- function liveServer() {
96113
+ var HEALTH_TIMEOUT_MS = 800;
96114
+ var pendingStart = null;
96115
+ function dashboardLogPath() {
96116
+ return path5.join(getDataDir(), "server.log");
96117
+ }
96118
+ function launchdPlistPath() {
96119
+ return path5.join(getDataDir(), "dashboard.launchd.plist");
96120
+ }
96121
+ function launchdLabel() {
96122
+ let hash = 5381;
96123
+ for (const ch of getDataDir()) hash = (hash * 33 ^ ch.charCodeAt(0)) >>> 0;
96124
+ return `life.careermate.dashboard.${hash.toString(16)}`;
96125
+ }
96126
+ function xmlEscape(value) {
96127
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
96128
+ }
96129
+ function plistString(key, value) {
96130
+ return ` <key>${xmlEscape(key)}</key>
96131
+ <string>${xmlEscape(value)}</string>`;
96132
+ }
96133
+ function renderLaunchdPlist(bin, args, cwd, log) {
96134
+ const env2 = {
96135
+ CAREERMATE_DATA_DIR: getDataDir(),
96136
+ CAREERMATE_NO_OPEN: "1"
96137
+ };
96138
+ if (process.env.CAREERMATE_PORT) env2.CAREERMATE_PORT = process.env.CAREERMATE_PORT;
96139
+ if (process.env.PATH) env2.PATH = process.env.PATH;
96140
+ const programArgs = [bin, ...args].map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n");
96141
+ const envEntries = Object.entries(env2).map(([key, value]) => `${plistString(key, value)}`).join("\n");
96142
+ return `<?xml version="1.0" encoding="UTF-8"?>
96143
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
96144
+ <plist version="1.0">
96145
+ <dict>
96146
+ <key>Label</key>
96147
+ <string>${xmlEscape(launchdLabel())}</string>
96148
+ <key>ProgramArguments</key>
96149
+ <array>
96150
+ ${programArgs}
96151
+ </array>
96152
+ <key>WorkingDirectory</key>
96153
+ <string>${xmlEscape(cwd)}</string>
96154
+ <key>EnvironmentVariables</key>
96155
+ <dict>
96156
+ ${envEntries}
96157
+ </dict>
96158
+ <key>RunAtLoad</key>
96159
+ <true/>
96160
+ <key>KeepAlive</key>
96161
+ <false/>
96162
+ <key>StandardOutPath</key>
96163
+ <string>${xmlEscape(log)}</string>
96164
+ <key>StandardErrorPath</key>
96165
+ <string>${xmlEscape(log)}</string>
96166
+ </dict>
96167
+ </plist>
96168
+ `;
96169
+ }
96170
+ function delay(ms) {
96171
+ return new Promise((resolve) => setTimeout(resolve, ms));
96172
+ }
96173
+ async function pingDashboard(url, timeoutMs = HEALTH_TIMEOUT_MS) {
96174
+ const controller = new AbortController();
96175
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
96176
+ timer.unref?.();
96177
+ try {
96178
+ const healthUrl = new URL("/api/health", url);
96179
+ const res = await fetch(healthUrl, {
96180
+ signal: controller.signal,
96181
+ headers: { accept: "application/json" }
96182
+ });
96183
+ if (!res.ok) return false;
96184
+ const body = await res.json().catch(() => null);
96185
+ return body?.ok === true;
96186
+ } catch {
96187
+ return false;
96188
+ } finally {
96189
+ clearTimeout(timer);
96190
+ }
96191
+ }
96192
+ async function liveServer() {
95684
96193
  const info2 = readRuntimeInfo();
95685
- if (info2 && isProcessAlive(info2.pid)) return info2;
96194
+ if (info2 && isProcessAlive(info2.pid) && await pingDashboard(info2.url)) return info2;
95686
96195
  return null;
95687
96196
  }
95688
96197
  function spawnDetached(cmd, args, opts = {}) {
@@ -95691,19 +96200,101 @@ function spawnDetached(cmd, args, opts = {}) {
95691
96200
  child.on("error", () => {
95692
96201
  });
95693
96202
  child.unref();
96203
+ return child.pid ?? null;
95694
96204
  } catch {
96205
+ return null;
95695
96206
  }
95696
96207
  }
96208
+ function spawnViaLaunchd(bin, args, cwd, log) {
96209
+ if (process.platform !== "darwin" || process.env.CAREERMATE_LAUNCHER === "detached") return false;
96210
+ if (typeof process.getuid !== "function") return false;
96211
+ const domain = `gui/${process.getuid()}`;
96212
+ const label = launchdLabel();
96213
+ const plist = launchdPlistPath();
96214
+ try {
96215
+ fs5.writeFileSync(plist, renderLaunchdPlist(bin, args, cwd, log), "utf8");
96216
+ spawnSync("launchctl", ["bootout", `${domain}/${label}`], { stdio: "ignore" });
96217
+ const res = spawnSync("launchctl", ["bootstrap", domain, plist], {
96218
+ encoding: "utf8",
96219
+ timeout: 5e3
96220
+ });
96221
+ if (res.status === 0) return true;
96222
+ fs5.appendFileSync(log, `[${(/* @__PURE__ */ new Date()).toISOString()}] launchctl bootstrap failed: ${res.stderr || res.stdout || res.status}
96223
+ `);
96224
+ } catch (e) {
96225
+ try {
96226
+ fs5.appendFileSync(log, `[${(/* @__PURE__ */ new Date()).toISOString()}] launchctl launch failed: ${e instanceof Error ? e.message : String(e)}
96227
+ `);
96228
+ } catch {
96229
+ }
96230
+ }
96231
+ return false;
96232
+ }
96233
+ function spawnDashboardDetached(bin, args, cwd, logFd) {
96234
+ spawnDetached(bin, args, {
96235
+ cwd,
96236
+ env: { ...process.env, CAREERMATE_NO_OPEN: "1" },
96237
+ stdio: logFd === null ? "ignore" : ["ignore", logFd, logFd]
96238
+ });
96239
+ }
95697
96240
  async function ensureServer(timeoutMs = 12e3) {
95698
- const existing = liveServer();
96241
+ const existing = await liveServer();
95699
96242
  if (existing) return { url: existing.url };
95700
- 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];
95701
- spawnDetached(bin, args, { cwd, env: { ...process.env, CAREERMATE_NO_OPEN: "1" } });
96243
+ if (pendingStart) return pendingStart;
96244
+ pendingStart = startServerBackground(timeoutMs);
96245
+ try {
96246
+ return await pendingStart;
96247
+ } finally {
96248
+ pendingStart = null;
96249
+ }
96250
+ }
96251
+ async function startServerBackground(timeoutMs) {
96252
+ const [bin, args, cwd] = BUNDLED ? [process.execPath, ["--experimental-sqlite", path5.join(__dirname, "web.mjs")], __dirname] : [
96253
+ process.execPath,
96254
+ ["--no-warnings", "--experimental-sqlite", "--import", "tsx", path5.join(REPO_ROOT, "apps/web/src/index.ts")],
96255
+ REPO_ROOT
96256
+ ];
96257
+ const log = dashboardLogPath();
96258
+ let logFd = null;
96259
+ try {
96260
+ logFd = fs5.openSync(log, "a");
96261
+ fs5.writeSync(logFd, `
96262
+ [${(/* @__PURE__ */ new Date()).toISOString()}] CareerMate dashboard background launch
96263
+ `);
96264
+ } catch {
96265
+ logFd = null;
96266
+ }
96267
+ let launchedViaLaunchd = false;
96268
+ try {
96269
+ launchedViaLaunchd = spawnViaLaunchd(bin, args, cwd, log);
96270
+ if (!launchedViaLaunchd) spawnDashboardDetached(bin, args, cwd, logFd);
96271
+ } finally {
96272
+ if (logFd !== null) {
96273
+ try {
96274
+ fs5.closeSync(logFd);
96275
+ } catch {
96276
+ }
96277
+ }
96278
+ }
95702
96279
  const start = Date.now();
95703
96280
  while (Date.now() - start < timeoutMs) {
95704
- const info2 = liveServer();
96281
+ const info2 = await liveServer();
95705
96282
  if (info2) return { url: info2.url };
95706
- await new Promise((r) => setTimeout(r, 300));
96283
+ await delay(300);
96284
+ }
96285
+ if (launchedViaLaunchd) {
96286
+ try {
96287
+ fs5.appendFileSync(log, `[${(/* @__PURE__ */ new Date()).toISOString()}] launchd did not produce a healthy server; falling back to detached spawn
96288
+ `);
96289
+ } catch {
96290
+ }
96291
+ spawnDashboardDetached(bin, args, cwd, null);
96292
+ const fallbackStart = Date.now();
96293
+ while (Date.now() - fallbackStart < Math.min(timeoutMs, 8e3)) {
96294
+ const info2 = await liveServer();
96295
+ if (info2) return { url: info2.url };
96296
+ await delay(300);
96297
+ }
95707
96298
  }
95708
96299
  return null;
95709
96300
  }
@@ -95727,7 +96318,7 @@ function openInFileManager(dir) {
95727
96318
  else openOsTarget(dir);
95728
96319
  }
95729
96320
  async function resolveDashboardUrl(hash = "") {
95730
- const live = liveServer();
96321
+ const live = await liveServer();
95731
96322
  if (live) return { url: live.url + hash, running: true };
95732
96323
  const started = await ensureServer();
95733
96324
  if (started) return { url: started.url + hash, running: true };
@@ -95735,7 +96326,7 @@ async function resolveDashboardUrl(hash = "") {
95735
96326
  }
95736
96327
 
95737
96328
  // packages/mcp-tools/src/update.ts
95738
- import fs5 from "node:fs";
96329
+ import fs6 from "node:fs";
95739
96330
  import path6 from "node:path";
95740
96331
  import { fileURLToPath as fileURLToPath3 } from "node:url";
95741
96332
  import { spawn as spawn2 } from "node:child_process";
@@ -95792,14 +96383,14 @@ function comparePrerelease(a, b) {
95792
96383
  }
95793
96384
  function readCache() {
95794
96385
  try {
95795
- return JSON.parse(fs5.readFileSync(cacheFile(), "utf8"));
96386
+ return JSON.parse(fs6.readFileSync(cacheFile(), "utf8"));
95796
96387
  } catch {
95797
96388
  return null;
95798
96389
  }
95799
96390
  }
95800
96391
  function writeCache(c) {
95801
96392
  try {
95802
- fs5.writeFileSync(cacheFile(), JSON.stringify(c), "utf8");
96393
+ fs6.writeFileSync(cacheFile(), JSON.stringify(c), "utf8");
95803
96394
  } catch {
95804
96395
  }
95805
96396
  }
@@ -95858,14 +96449,20 @@ function getUpdateStatus() {
95858
96449
  if (cacheExpired(cache2)) refreshInBackground();
95859
96450
  return buildStatus(cache2);
95860
96451
  }
95861
- async function getUpdateStatusAsync() {
96452
+ async function getUpdateStatusAsync(force = false) {
95862
96453
  if (checkDisabled()) return buildStatus(null);
95863
- let cache2 = readCache();
95864
- if (cacheExpired(cache2)) {
95865
- cache2 = { checkedAt: Date.now(), latest: await fetchLatest() };
95866
- writeCache(cache2);
95867
- }
95868
- return buildStatus(cache2);
96454
+ const cache2 = readCache();
96455
+ if (!force && !cacheExpired(cache2)) return buildStatus(cache2);
96456
+ const latest = await fetchLatest();
96457
+ if (latest !== null) {
96458
+ const fresh = { checkedAt: Date.now(), latest };
96459
+ writeCache(fresh);
96460
+ return buildStatus(fresh);
96461
+ }
96462
+ if (force) return buildStatus(null);
96463
+ const failed = { checkedAt: Date.now(), latest: null };
96464
+ writeCache(failed);
96465
+ return buildStatus(failed);
95869
96466
  }
95870
96467
  function killTree(child) {
95871
96468
  if (process.platform === "win32" && child.pid) {
@@ -95923,12 +96520,8 @@ async function runSelfUpdate() {
95923
96520
  manual: manualCommand()
95924
96521
  };
95925
96522
  }
95926
- let cache2 = readCache();
95927
- if (cacheExpired(cache2)) {
95928
- cache2 = { checkedAt: Date.now(), latest: await fetchLatest() };
95929
- writeCache(cache2);
95930
- }
95931
- const latest = cache2?.latest ?? null;
96523
+ const latest = await fetchLatest();
96524
+ if (latest !== null) writeCache({ checkedAt: Date.now(), latest });
95932
96525
  if (latest === null) {
95933
96526
  return {
95934
96527
  ok: false,
@@ -95956,26 +96549,38 @@ async function runSelfUpdate() {
95956
96549
  ok: true,
95957
96550
  from,
95958
96551
  to: latest,
95959
- 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.`
96552
+ 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.`
95960
96553
  };
95961
96554
  }
95962
96555
 
95963
96556
  // packages/mcp-tools/src/tools.ts
95964
96557
  var STATUS_LIST = APPLICATION_STATUSES.map((s) => `${s}(${APPLICATION_STATUS_LABELS[s]})`).join(", ");
96558
+ var DELETE_CONFIRMATION = "DELETE";
96559
+ var DASHBOARD_URL = "http://127.0.0.1:4319";
95965
96560
  function listInboxFiles(dir) {
95966
96561
  let names;
95967
96562
  try {
95968
- names = fs6.readdirSync(dir);
96563
+ names = fs7.readdirSync(dir);
95969
96564
  } catch {
95970
96565
  return [];
95971
96566
  }
96567
+ let realDir;
96568
+ try {
96569
+ realDir = fs7.realpathSync.native(dir);
96570
+ } catch {
96571
+ realDir = dir;
96572
+ }
95972
96573
  const SKIP = /^[.~]|^(thumbs\.db|desktop\.ini)$/i;
95973
96574
  const out = [];
95974
96575
  for (const name of names) {
95975
96576
  if (SKIP.test(name)) continue;
95976
96577
  const p = path7.join(dir, name);
95977
96578
  try {
95978
- if (fs6.statSync(p).isFile()) out.push({ name, path: p });
96579
+ if (!fs7.statSync(p).isFile()) continue;
96580
+ const real = fs7.realpathSync.native(p);
96581
+ const rel = path7.relative(realDir, real);
96582
+ if (rel.startsWith("..") || path7.isAbsolute(rel)) continue;
96583
+ out.push({ name, path: p });
95979
96584
  } catch {
95980
96585
  }
95981
96586
  }
@@ -96045,8 +96650,8 @@ ${r.warnings.join("\n")}`, {
96045
96650
  /* --------------------------------------------------------- inbox (파일 인입) */
96046
96651
  {
96047
96652
  name: "open_inbox",
96048
- title: "\uC774\uB825\uC11C \uC778\uC785 \uD3F4\uB354 \uC5F4\uAE30",
96049
- 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.',
96653
+ title: "\uBB38\uC11C \uC778\uC785 \uD3F4\uB354 \uC5F4\uAE30",
96654
+ 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.',
96050
96655
  inputSchema: {},
96051
96656
  handler: () => {
96052
96657
  const dir = getInboxDir();
@@ -96054,10 +96659,10 @@ ${r.warnings.join("\n")}`, {
96054
96659
  const files = listInboxFiles(dir);
96055
96660
  const list = files.length ? files.map((f) => `- ${f.name}`).join("\n") : "(\uC544\uC9C1 \uBE44\uC5B4 \uC788\uC74C)";
96056
96661
  return ok(
96057
- `\uC774\uB825\uC11C \uC778\uC785 \uD3F4\uB354\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4:
96662
+ `\uBB38\uC11C \uC778\uC785 \uD3F4\uB354\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4:
96058
96663
  ${dir}
96059
96664
 
96060
- \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.)
96665
+ \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.)
96061
96666
 
96062
96667
  \uD604\uC7AC \uD30C\uC77C:
96063
96668
  ${list}`,
@@ -96068,7 +96673,7 @@ ${list}`,
96068
96673
  {
96069
96674
  name: "read_inbox",
96070
96675
  title: "\uC778\uC785 \uD3F4\uB354 \uD30C\uC77C \uC77D\uAE30",
96071
- 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.',
96676
+ 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.',
96072
96677
  inputSchema: {
96073
96678
  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."),
96074
96679
  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")
@@ -96109,7 +96714,7 @@ ${dir}
96109
96714
  `\uC778\uC785 \uD3F4\uB354 \uD30C\uC77C ${files.length}\uAC74\uC744 \uC77D\uC5C8\uC2B5\uB2C8\uB2E4:
96110
96715
  ${summary}
96111
96716
 
96112
- \uCD94\uCD9C\uB41C \uB0B4\uC6A9\uC744 \uAD6C\uC870\uD654\uD574 save_profile\xB7add_resume \uB4F1\uC73C\uB85C \uC800\uC7A5\uD558\uC138\uC694.`,
96717
+ \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.`,
96113
96718
  { dir, files }
96114
96719
  );
96115
96720
  }
@@ -96138,9 +96743,17 @@ ${lines}`;
96138
96743
  if (upd.update_available) {
96139
96744
  text += `
96140
96745
 
96141
- \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.`;
96746
+ \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.`;
96142
96747
  }
96143
- return ok(text, { ...s, verify_strict: strict, update: upd });
96748
+ return ok(text, {
96749
+ ...s,
96750
+ verify_strict: strict,
96751
+ update: upd,
96752
+ suggested_next_action: upd.update_available ? {
96753
+ prompt: `\uC0C8 \uBC84\uC804 v${upd.latest}\uC774 \uB098\uC654\uC2B5\uB2C8\uB2E4. \uC9C0\uAE08 \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694?`,
96754
+ next_tool: "update_careermate"
96755
+ } : null
96756
+ });
96144
96757
  }
96145
96758
  },
96146
96759
  /* --------------------------------------------------------------- profile */
@@ -96169,7 +96782,7 @@ ${lines}`;
96169
96782
  {
96170
96783
  name: "add_resume",
96171
96784
  title: "\uC774\uB825\uC11C/\uACBD\uB825\uAE30\uC220\uC11C \uCD94\uAC00",
96172
- 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.",
96785
+ 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.",
96173
96786
  // Reuse the shared (length-bounded, whitespace-rejecting) Document schema so
96174
96787
  // the MCP ingestion path — the primary route for résumé text — inherits the
96175
96788
  // same caps as the web API. kind stays optional here (defaults to resume).
@@ -96193,6 +96806,23 @@ ${lines}`;
96193
96806
  return ok(`\uBB38\uC11C ${docs.length}\uAC74`, docs);
96194
96807
  }
96195
96808
  },
96809
+ {
96810
+ name: "delete_resume",
96811
+ title: "\uC774\uB825\uC11C/\uBB38\uC11C \uC0AD\uC81C",
96812
+ 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.",
96813
+ inputSchema: {
96814
+ document_id: external_exports.string(),
96815
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
96816
+ },
96817
+ handler: (args) => {
96818
+ 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.");
96819
+ const doc = documentRepo.get(args.document_id);
96820
+ if (!doc) return fail("\uBB38\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
96821
+ if (!documentRepo.remove(doc.id)) return fail("\uBB38\uC11C\uB97C \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
96822
+ const user_message = `'${doc.title}' \uBB38\uC11C\uB97C \uC0AD\uC81C\uD588\uC5B4\uC694.`;
96823
+ return ok(user_message, { deleted: true, document_id: doc.id, user_message });
96824
+ }
96825
+ },
96196
96826
  /* ----------------------------------------------------------- experiences */
96197
96827
  {
96198
96828
  name: "add_experience",
@@ -96262,6 +96892,57 @@ ${lines}`;
96262
96892
  return ok(`\uAE30\uC220 ${list.length}\uAC74`, list);
96263
96893
  }
96264
96894
  },
96895
+ {
96896
+ name: "delete_experience",
96897
+ title: "\uACBD\uB825 \uD56D\uBAA9 \uC0AD\uC81C",
96898
+ 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.",
96899
+ inputSchema: {
96900
+ experience_id: external_exports.string(),
96901
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
96902
+ },
96903
+ handler: (args) => {
96904
+ 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.");
96905
+ const exp = experienceRepo.get(args.experience_id);
96906
+ if (!exp) return fail("\uD574\uB2F9 \uACBD\uB825 \uD56D\uBAA9\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
96907
+ if (!experienceRepo.remove(exp.id)) return fail("\uACBD\uB825 \uD56D\uBAA9\uC744 \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
96908
+ const user_message = `'${exp.company}' \uACBD\uB825\uC744 \uC0AD\uC81C\uD588\uC5B4\uC694.`;
96909
+ return ok(user_message, { deleted: true, experience_id: exp.id, user_message });
96910
+ }
96911
+ },
96912
+ {
96913
+ name: "delete_project",
96914
+ title: "\uD504\uB85C\uC81D\uD2B8 \uD56D\uBAA9 \uC0AD\uC81C",
96915
+ 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.",
96916
+ inputSchema: {
96917
+ project_id: external_exports.string(),
96918
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
96919
+ },
96920
+ handler: (args) => {
96921
+ 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.");
96922
+ const proj = projectRepo.get(args.project_id);
96923
+ if (!proj) return fail("\uD574\uB2F9 \uD504\uB85C\uC81D\uD2B8 \uD56D\uBAA9\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
96924
+ if (!projectRepo.remove(proj.id)) return fail("\uD504\uB85C\uC81D\uD2B8 \uD56D\uBAA9\uC744 \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
96925
+ const user_message = `'${proj.name}' \uD504\uB85C\uC81D\uD2B8\uB97C \uC0AD\uC81C\uD588\uC5B4\uC694.`;
96926
+ return ok(user_message, { deleted: true, project_id: proj.id, user_message });
96927
+ }
96928
+ },
96929
+ {
96930
+ name: "delete_skill",
96931
+ title: "\uAE30\uC220\uC2A4\uD0DD \uD56D\uBAA9 \uC0AD\uC81C",
96932
+ 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.",
96933
+ inputSchema: {
96934
+ skill_id: external_exports.string(),
96935
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
96936
+ },
96937
+ handler: (args) => {
96938
+ 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.");
96939
+ const skill = skillRepo.get(args.skill_id);
96940
+ if (!skill) return fail("\uD574\uB2F9 \uAE30\uC220 \uD56D\uBAA9\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
96941
+ if (!skillRepo.remove(skill.id)) return fail("\uAE30\uC220 \uD56D\uBAA9\uC744 \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
96942
+ const user_message = `'${skill.name}' \uAE30\uC220\uC744 \uC0AD\uC81C\uD588\uC5B4\uC694.`;
96943
+ return ok(user_message, { deleted: true, skill_id: skill.id, user_message });
96944
+ }
96945
+ },
96265
96946
  /* ---------------------------------------------------------- cover letters */
96266
96947
  {
96267
96948
  name: "get_cover_letters",
@@ -96293,6 +96974,31 @@ ${lines}`;
96293
96974
  }
96294
96975
  }
96295
96976
  },
96977
+ {
96978
+ name: "delete_cover_letter",
96979
+ title: "\uC790\uAE30\uC18C\uAC1C\uC11C \uC0AD\uC81C",
96980
+ 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.",
96981
+ inputSchema: {
96982
+ cover_letter_id: external_exports.string(),
96983
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
96984
+ },
96985
+ handler: (args) => {
96986
+ if (args?.confirm !== DELETE_CONFIRMATION) {
96987
+ return fail("\uC0AD\uC81C\uD558\uB824\uBA74 confirm\uC5D0 DELETE\uB97C \uB123\uC5B4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694.");
96988
+ }
96989
+ const cl = coverLetterRepo.get(args.cover_letter_id, false);
96990
+ if (!cl) return fail("\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
96991
+ const removed = coverLetterRepo.remove(cl.id);
96992
+ if (!removed) return fail("\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
96993
+ 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.`;
96994
+ return ok(user_message, {
96995
+ deleted: true,
96996
+ cover_letter_id: cl.id,
96997
+ title: cl.title,
96998
+ user_message
96999
+ });
97000
+ }
97001
+ },
96296
97002
  /* ------------------------------------------------------------------ jobs */
96297
97003
  {
96298
97004
  name: "save_job_posting",
@@ -96301,18 +97007,26 @@ ${lines}`;
96301
97007
  inputSchema: JobInputSchema.shape,
96302
97008
  handler: (args) => {
96303
97009
  const { job } = saveJobPosting(args);
96304
- return ok(`'${job.company} \xB7 ${job.position}' \uACF5\uACE0\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4. (job_id: ${job.id})`, job);
97010
+ const user_message = `'${job.company} \xB7 ${job.position}' \uACF5\uACE0\uB97C \uC800\uC7A5\uD588\uC5B4\uC694.`;
97011
+ return ok(user_message, {
97012
+ ...job,
97013
+ user_message,
97014
+ suggested_next_action: {
97015
+ prompt: "\uC774 \uACF5\uACE0\uC640 \uB0B4 \uD504\uB85C\uD544\uC744 \uBE44\uAD50\uD574 \uC801\uD569\uB3C4\uB97C \uBD84\uC11D\uD560\uAE4C\uC694?",
97016
+ next_workflow: "analyze_job"
97017
+ }
97018
+ });
96305
97019
  }
96306
97020
  },
96307
97021
  {
96308
97022
  name: "get_job_posting",
96309
97023
  title: "\uCC44\uC6A9\uACF5\uACE0 \uC0C1\uC138 \uC870\uD68C",
96310
- 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.",
97024
+ 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.",
96311
97025
  inputSchema: { job_id: external_exports.string() },
96312
97026
  readOnly: true,
96313
97027
  handler: (args) => {
96314
97028
  const job = jobRepo.get(args.job_id);
96315
- if (!job) return fail(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${args.job_id}`);
97029
+ if (!job) return fail("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
96316
97030
  return ok(`${job.company} \xB7 ${job.position}`, {
96317
97031
  job: jobWithMeta(job),
96318
97032
  fit: fitRepo.getByJob(job.id),
@@ -96333,6 +97047,35 @@ ${lines}`;
96333
97047
  return ok(`\uACF5\uACE0 ${jobs.length}\uAC74`, jobs);
96334
97048
  }
96335
97049
  },
97050
+ {
97051
+ name: "delete_job_posting",
97052
+ title: "\uCC44\uC6A9\uACF5\uACE0 \uC0AD\uC81C",
97053
+ 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.",
97054
+ inputSchema: {
97055
+ job_id: external_exports.string(),
97056
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
97057
+ },
97058
+ handler: (args) => {
97059
+ if (args?.confirm !== DELETE_CONFIRMATION) {
97060
+ return fail("\uC0AD\uC81C\uD558\uB824\uBA74 confirm\uC5D0 DELETE\uB97C \uB123\uC5B4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694.");
97061
+ }
97062
+ const job = jobRepo.get(args.job_id);
97063
+ if (!job) return fail("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
97064
+ const detachedCoverLetters = coverLetterRepo.listByJob(job.id).length;
97065
+ const removed = jobRepo.remove(job.id);
97066
+ if (!removed) return fail("\uACF5\uACE0\uB97C \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
97067
+ 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.` : "";
97068
+ 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}`;
97069
+ return ok(user_message, {
97070
+ deleted: true,
97071
+ job_id: job.id,
97072
+ company: job.company,
97073
+ position: job.position,
97074
+ detached_cover_letters: detachedCoverLetters,
97075
+ user_message
97076
+ });
97077
+ }
97078
+ },
96336
97079
  /* ----------------------------------------------------- application context */
96337
97080
  {
96338
97081
  name: "get_application_context",
@@ -96344,18 +97087,46 @@ ${lines}`;
96344
97087
  const ctx = getApplicationContext({ job_id: args?.job_id });
96345
97088
  const recommended_route = pickRoute(ctx);
96346
97089
  const route = recommended_route ? getRoute(recommended_route) : void 0;
97090
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
97091
+ const ym = (s) => {
97092
+ const m = s ? /^(\d{4})-(\d{2})/.exec(s) : null;
97093
+ return m ? Number(m[1]) * 12 + Number(m[2]) : null;
97094
+ };
97095
+ const monthsBetween = (a, b) => {
97096
+ const x2 = ym(a), y = ym(b);
97097
+ return x2 != null && y != null ? y - x2 : null;
97098
+ };
97099
+ const tenure = ctx.experiences.map((e) => {
97100
+ if (!e.start_date) return null;
97101
+ const asOf = e.is_current ? e.updated_at ?? today : e.end_date;
97102
+ const months = monthsBetween(e.start_date, asOf);
97103
+ if (months == null || months < 0) return null;
97104
+ return {
97105
+ company: e.company,
97106
+ is_current: e.is_current,
97107
+ start_date: e.start_date,
97108
+ as_of: (asOf ?? "").slice(0, 7),
97109
+ // 재직중=updated_at(정보 확인 시점) / 종료=end_date
97110
+ months,
97111
+ years: Math.round(months / 12 * 10) / 10,
97112
+ data_age_months: e.is_current ? monthsBetween(e.updated_at, today) : null
97113
+ };
97114
+ }).filter((t) => t != null);
96347
97115
  const enriched = {
96348
97116
  ...ctx,
97117
+ today,
97118
+ tenure,
96349
97119
  recommended_route,
96350
97120
  verifier_sequence: route?.verifierSequence ?? [],
96351
97121
  next_tool: recommended_route ? "get_workflow_guide" : null
96352
97122
  };
96353
97123
  const summary = [
97124
+ `\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.`,
96354
97125
  `\uD504\uB85C\uD544: ${ctx.profile?.name ?? "\uBBF8\uC785\uB825"}`,
96355
97126
  `\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}`,
96356
97127
  ctx.job ? `\uB300\uC0C1 \uACF5\uACE0: ${ctx.job.company} \xB7 ${ctx.job.position}` : "\uB300\uC0C1 \uACF5\uACE0 \uC5C6\uC74C",
96357
97128
  ctx.writing_preferences.preferred_tone ? `\uC120\uD638 \uBB38\uCCB4: ${ctx.writing_preferences.preferred_tone}` : "",
96358
- 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."
97129
+ 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."
96359
97130
  ].filter(Boolean).join("\n");
96360
97131
  return ok(summary, enriched);
96361
97132
  }
@@ -96368,7 +97139,34 @@ ${lines}`;
96368
97139
  handler: (args) => {
96369
97140
  try {
96370
97141
  const fit = saveFitAnalysis(args);
96371
- return ok(`\uC801\uD569\uB3C4 \uBD84\uC11D\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4${fit.score != null ? ` (${fit.score}\uC810)` : ""}.`, fit);
97142
+ const suggested_next_action = {
97143
+ prompt: "\uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uB4DC\uB9B4\uAE4C\uC694?",
97144
+ options: [
97145
+ {
97146
+ label: "\uB124, \uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uC918",
97147
+ next_workflow: "write_cover_letter"
97148
+ },
97149
+ {
97150
+ label: "\uC544\uB2C8\uC694, \uBD84\uC11D\uB9CC \uC800\uC7A5\uD574\uC918",
97151
+ next_workflow: null
97152
+ }
97153
+ ]
97154
+ };
97155
+ 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}`;
97156
+ return ok(
97157
+ [
97158
+ user_message,
97159
+ "",
97160
+ "\uC0AC\uC6A9\uC790\uC5D0\uAC8C \uB2E4\uC74C \uC120\uD0DD\uC9C0\uB97C \uBCF4\uC5EC\uC8FC\uC138\uC694:",
97161
+ "1. \uB124, \uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uC918",
97162
+ "2. \uC544\uB2C8\uC694, \uBD84\uC11D\uB9CC \uC800\uC7A5\uD574\uC918"
97163
+ ].join("\n"),
97164
+ {
97165
+ ...fit,
97166
+ user_message,
97167
+ suggested_next_action
97168
+ }
97169
+ );
96372
97170
  } catch (e) {
96373
97171
  return fail(e instanceof Error ? e.message : "\uC800\uC7A5 \uC2E4\uD328");
96374
97172
  }
@@ -96382,16 +97180,25 @@ ${lines}`;
96382
97180
  inputSchema: {
96383
97181
  job_id: external_exports.string(),
96384
97182
  status: external_exports.enum(APPLICATION_STATUSES),
96385
- note: external_exports.string().optional()
97183
+ note: external_exports.string().optional(),
97184
+ submission: ApplicationSubmissionSchema.optional()
96386
97185
  },
96387
97186
  handler: (args) => {
96388
97187
  try {
96389
- const res = updateApplicationStatus(args.job_id, args.status, args.note);
96390
- return ok(
96391
- `\uC0C1\uD0DC\uB97C '${APPLICATION_STATUS_LABELS[args.status]}'(\uC73C)\uB85C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.${res.hint ? `
97188
+ const res = updateApplicationStatus(args.job_id, args.status, args.note, args.submission);
97189
+ const user_message = `\uC9C0\uC6D0 \uC0C1\uD0DC\uB97C '${APPLICATION_STATUS_LABELS[args.status]}'(\uC73C)\uB85C \uBC14\uAFE8\uC5B4\uC694.` + (res.hint ? `
96392
97190
 
96393
- \u{1F4A1} ${res.hint}` : ""}`,
96394
- res
97191
+ ${res.hint}` : "");
97192
+ return ok(
97193
+ user_message,
97194
+ {
97195
+ ...res,
97196
+ user_message,
97197
+ suggested_next_action: res.hint ? {
97198
+ prompt: res.hint,
97199
+ next_workflow: "prepare_interview"
97200
+ } : null
97201
+ }
96395
97202
  );
96396
97203
  } catch (e) {
96397
97204
  return fail(e instanceof Error ? e.message : "\uBCC0\uACBD \uC2E4\uD328");
@@ -96402,12 +97209,15 @@ ${lines}`;
96402
97209
  {
96403
97210
  name: "save_interview_prep",
96404
97211
  title: "\uBA74\uC811 \uC900\uBE44 \uC790\uB8CC \uC800\uC7A5",
96405
- 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.",
97212
+ 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.",
96406
97213
  inputSchema: InterviewPrepInputSchema.shape,
96407
97214
  handler: (args) => {
96408
97215
  try {
96409
97216
  const prep = saveInterviewPrep(args);
96410
- return ok(`\uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4 (\uC9C8\uBB38 ${prep.questions.length}\uAC1C).`, prep);
97217
+ const tech = prep.questions.filter((q) => (q.category ?? "technical") === "technical").length;
97218
+ const behavioral = prep.questions.filter((q) => q.category === "behavioral").length;
97219
+ 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.";
97220
+ return ok(user_message, { ...prep, user_message });
96411
97221
  } catch (e) {
96412
97222
  return fail(e instanceof Error ? e.message : "\uC800\uC7A5 \uC2E4\uD328");
96413
97223
  }
@@ -96432,11 +97242,70 @@ ${lines}`;
96432
97242
  const dir = getExportsDir();
96433
97243
  const filePath = path7.join(dir, result2.filename);
96434
97244
  try {
96435
- fs6.writeFileSync(filePath, result2.content, "utf8");
97245
+ fs7.writeFileSync(filePath, result2.content, "utf8");
96436
97246
  } catch (e) {
96437
97247
  return fail(`\uD30C\uC77C \uC800\uC7A5 \uC2E4\uD328: ${e instanceof Error ? e.message : e}`);
96438
97248
  }
96439
97249
  return ok(`'${cl.title}'\uB97C ${format.toUpperCase()}\uB85C \uB0B4\uBCF4\uB0C8\uC2B5\uB2C8\uB2E4.
97250
+ \uC800\uC7A5 \uC704\uCE58: ${filePath}`, {
97251
+ path: filePath,
97252
+ filename: result2.filename,
97253
+ content: result2.content
97254
+ });
97255
+ }
97256
+ },
97257
+ {
97258
+ name: "export_resume",
97259
+ title: "\uC774\uB825\uC11C/\uACBD\uB825\uAE30\uC220\uC11C \uB0B4\uBCF4\uB0B4\uAE30",
97260
+ 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.',
97261
+ inputSchema: {
97262
+ document_id: external_exports.string(),
97263
+ format: external_exports.enum(["md", "html"]).optional().describe("\uAE30\uBCF8 md")
97264
+ },
97265
+ handler: (args) => {
97266
+ const doc = documentRepo.get(args.document_id);
97267
+ if (!doc) return fail("\uBB38\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
97268
+ const profile = profileRepo.get();
97269
+ const format = args.format ?? "md";
97270
+ const result2 = format === "html" ? resumeToHtml(doc, profile) : resumeToMarkdown(doc, profile);
97271
+ const dir = getExportsDir();
97272
+ const filePath = path7.join(dir, result2.filename);
97273
+ try {
97274
+ fs7.writeFileSync(filePath, result2.content, "utf8");
97275
+ } catch (e) {
97276
+ return fail(`\uD30C\uC77C \uC800\uC7A5 \uC2E4\uD328: ${e instanceof Error ? e.message : e}`);
97277
+ }
97278
+ return ok(`'${doc.title}'\uB97C ${format.toUpperCase()}\uB85C \uB0B4\uBCF4\uB0C8\uC2B5\uB2C8\uB2E4.
97279
+ \uC800\uC7A5 \uC704\uCE58: ${filePath}`, {
97280
+ path: filePath,
97281
+ filename: result2.filename,
97282
+ content: result2.content
97283
+ });
97284
+ }
97285
+ },
97286
+ {
97287
+ name: "export_profile",
97288
+ title: "\uD504\uB85C\uD544\uC744 \uC774\uB825\uC11C\uB85C \uB0B4\uBCF4\uB0B4\uAE30",
97289
+ 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.',
97290
+ inputSchema: {
97291
+ format: external_exports.enum(["md", "html"]).optional().describe("\uAE30\uBCF8 md")
97292
+ },
97293
+ handler: (args) => {
97294
+ const profile = profileRepo.get();
97295
+ if (!profile) return fail("\uD504\uB85C\uD544\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uD504\uB85C\uD544\uC744 \uC800\uC7A5\uD558\uC138\uC694.");
97296
+ const format = args.format ?? "md";
97297
+ const experiences = experienceRepo.list();
97298
+ const projects = projectRepo.list();
97299
+ const skills = skillRepo.list();
97300
+ const result2 = format === "html" ? profileToHtml(profile, experiences, projects, skills) : profileToMarkdown(profile, experiences, projects, skills);
97301
+ const dir = getExportsDir();
97302
+ const filePath = path7.join(dir, result2.filename);
97303
+ try {
97304
+ fs7.writeFileSync(filePath, result2.content, "utf8");
97305
+ } catch (e) {
97306
+ return fail(`\uD30C\uC77C \uC800\uC7A5 \uC2E4\uD328: ${e instanceof Error ? e.message : e}`);
97307
+ }
97308
+ return ok(`\uD504\uB85C\uD544\uC744 \uC774\uB825\uC11C(${format.toUpperCase()})\uB85C \uB0B4\uBCF4\uB0C8\uC2B5\uB2C8\uB2E4.
96440
97309
  \uC800\uC7A5 \uC704\uCE58: ${filePath}`, {
96441
97310
  path: filePath,
96442
97311
  filename: result2.filename,
@@ -96488,9 +97357,17 @@ ${lines}`;
96488
97357
  {
96489
97358
  name: "get_workflow_guide",
96490
97359
  title: "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uAC00\uC774\uB4DC",
96491
- 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.',
97360
+ 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.',
96492
97361
  inputSchema: {
96493
- workflow_id: external_exports.enum(["onboarding", "analyze_job", "write_cover_letter", "manage_application_status", "prepare_interview"]).optional()
97362
+ workflow_id: external_exports.enum([
97363
+ "onboarding",
97364
+ "analyze_job",
97365
+ "write_cover_letter",
97366
+ "write_career_description",
97367
+ "manage_application_status",
97368
+ "prepare_interview",
97369
+ "build_personal_brand"
97370
+ ]).optional()
96494
97371
  },
96495
97372
  readOnly: true,
96496
97373
  handler: (args) => {
@@ -96585,38 +97462,59 @@ ${report.disclaimer}`, report);
96585
97462
  {
96586
97463
  name: "get_writing_style_guide",
96587
97464
  title: "AI \uD2F0 \uC548 \uB098\uB294 \uAE00\uC4F0\uAE30 \uAC00\uC774\uB4DC",
96588
- 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.',
96589
- inputSchema: {},
97465
+ 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.',
97466
+ inputSchema: {
97467
+ doc_type: external_exports.enum([
97468
+ "cover_letter",
97469
+ "self_pr",
97470
+ "application_email",
97471
+ "career_description",
97472
+ "resume",
97473
+ "portfolio"
97474
+ ]).optional().describe(
97475
+ "\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."
97476
+ )
97477
+ },
96590
97478
  readOnly: true,
96591
- 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 })
97479
+ handler: (args) => ok("AI \uD2F0 \uC548 \uB098\uB294 \uAE00\uC4F0\uAE30 \uAC00\uC774\uB4DC\uB97C \uC801\uC6A9\uD574 \uC791\uC131\uD558\uC138\uC694.", {
97480
+ guide: getWritingStyleGuide(args?.doc_type)
97481
+ })
96592
97482
  },
96593
97483
  /* ----------------------------------------------------------------- update */
96594
97484
  {
96595
97485
  name: "check_for_update",
96596
97486
  title: "\uC5C5\uB370\uC774\uD2B8 \uD655\uC778",
96597
- 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.',
97487
+ 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.',
96598
97488
  inputSchema: {},
96599
97489
  readOnly: true,
96600
97490
  handler: async () => {
96601
- const u = await getUpdateStatusAsync();
97491
+ const u = await getUpdateStatusAsync(true);
96602
97492
  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);
97493
+ 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}).`;
96603
97494
  return ok(
96604
- 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}).`,
96605
- u
97495
+ user_message,
97496
+ {
97497
+ ...u,
97498
+ user_message,
97499
+ suggested_next_action: u.update_available ? {
97500
+ prompt: "\uC9C0\uAE08 CareerMate\uB97C \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694?",
97501
+ next_tool: "update_careermate"
97502
+ } : null
97503
+ }
96606
97504
  );
96607
97505
  }
96608
97506
  },
96609
97507
  {
96610
97508
  name: "update_careermate",
96611
97509
  title: "CareerMate \uC5C5\uB370\uC774\uD2B8",
96612
- 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.',
97510
+ 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.',
96613
97511
  inputSchema: {},
96614
97512
  handler: async () => {
96615
97513
  const r = await runSelfUpdate();
96616
97514
  if (!r.ok) return fail(`${r.message}${r.manual ? `
96617
97515
 
96618
97516
  \uC9C1\uC811 \uC2E4\uD589: ${r.manual}` : ""}`);
96619
- return ok(r.message, r);
97517
+ return ok(r.message, { ...r, user_message: r.message });
96620
97518
  }
96621
97519
  }
96622
97520
  ];
@@ -96627,7 +97525,7 @@ async function main() {
96627
97525
  const server = new McpServer(
96628
97526
  { name: "careermate", version: APP_VERSION },
96629
97527
  {
96630
- instructions: "CareerMate\uB294 \uC0AC\uC6A9\uC790\uC758 \uB85C\uCEEC \uCEE4\uB9AC\uC5B4 \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uC785\uB2C8\uB2E4. \uACF5\uACE0 \uBD84\uC11D\uC774\uB098 \uC790\uAE30\uC18C\uAC1C\uC11C \uC791\uC131 \uC804\uC5D0\uB294 \uD56D\uC0C1 get_application_context\uB97C \uBA3C\uC800 \uD638\uCD9C\uD574 \uC0AC\uC6A9\uC790 \uC815\uBCF4\uB97C \uAC00\uC838\uC624\uACE0(\uC751\uB2F5\uC758 recommended_route\xB7next_tool\uAC00 \uB2E4\uC74C \uD638\uCD9C\uC744 \uC548\uB0B4\uD569\uB2C8\uB2E4), \uC774\uC5B4 get_workflow_guide\uB85C \uADF8 \uC791\uC5C5\uC758 \uC804\uBB38\uAC00 \uC2E4\uD589 \uC808\uCC28\uC640 \uAC80\uC99D \uC21C\uC11C\uB97C \uBC1B\uC544 \uC801\uC6A9\uD558\uC138\uC694. \uACB0\uACFC\uB294 save_fit_analysis / save_cover_letter_version \uB4F1\uC73C\uB85C \uB2E4\uC2DC \uC800\uC7A5\uD558\uB418, \uC800\uC7A5 \uC9C1\uC804 \uC548\uB0B4\uB41C get_verifier \uB8E8\uBE0C\uB9AD\uC73C\uB85C \uB2F9\uC2E0\uC774 \uC2A4\uC2A4\uB85C \uC810\uAC80\uD558\uC138\uC694 \u2014 CareerMate\uB294 \uC140 \uC218 \uC788\uB294 \uD56D\uBAA9\uB9CC \uB3CC\uB824\uC8FC\uACE0 \uBD84\uC11D\xB7\uC791\uC131\xB7\uD310\uB2E8\uC740 \uB2F9\uC2E0(AI)\uC774 \uD569\uB2C8\uB2E4. \uBAA8\uB4E0 \uB370\uC774\uD130\uB294 \uC0AC\uC6A9\uC790 \uB85C\uCEEC\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4."
97528
+ instructions: "CareerMate\uB294 \uC0AC\uC6A9\uC790\uC758 \uB85C\uCEEC \uCEE4\uB9AC\uC5B4 \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uC785\uB2C8\uB2E4. \uACF5\uACE0 \uBD84\uC11D, \uC790\uAE30\uC18C\uAC1C\uC11C \uC791\uC131, \uACBD\uB825\uAE30\uC220\uC11C \uC791\uC131 \uC804\uC5D0\uB294 \uD56D\uC0C1 \uC800\uC7A5\uB41C \uC0AC\uC6A9\uC790 \uC815\uBCF4\uB97C \uBA3C\uC800 \uAC00\uC838\uC624\uACE0(\uACF5\uACE0 \uC911\uC2EC \uC791\uC5C5\uC740 get_application_context \uC751\uB2F5\uC758 recommended_route\xB7next_tool\uAC00 \uB2E4\uC74C \uD638\uCD9C\uC744 \uC548\uB0B4\uD569\uB2C8\uB2E4), \uC774\uC5B4 get_workflow_guide\uB85C \uADF8 \uC791\uC5C5\uC758 \uC804\uBB38\uAC00 \uC2E4\uD589 \uC808\uCC28\uC640 \uAC80\uC99D \uC21C\uC11C\uB97C \uBC1B\uC544 \uC801\uC6A9\uD558\uC138\uC694. \uACB0\uACFC\uB294 save_fit_analysis / save_cover_letter_version / add_resume(kind=career_description) \uB4F1\uC73C\uB85C \uB2E4\uC2DC \uC800\uC7A5\uD558\uB418, \uC800\uC7A5 \uC9C1\uC804 \uC548\uB0B4\uB41C get_verifier \uB8E8\uBE0C\uB9AD\uC73C\uB85C \uB2F9\uC2E0\uC774 \uC2A4\uC2A4\uB85C \uC810\uAC80\uD558\uC138\uC694 \u2014 CareerMate\uB294 \uC140 \uC218 \uC788\uB294 \uD56D\uBAA9\uB9CC \uB3CC\uB824\uC8FC\uACE0 \uBD84\uC11D\xB7\uC791\uC131\xB7\uD310\uB2E8\uC740 \uB2F9\uC2E0(AI)\uC774 \uD569\uB2C8\uB2E4. \uBAA8\uB4E0 \uB370\uC774\uD130\uB294 \uC0AC\uC6A9\uC790 \uB85C\uCEEC\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uB2F5\uD560 \uB54C\uB294 \uD56D\uC0C1 \uC0AC\uC6A9\uC790\uAC00 \uC4F4 \uC5B8\uC5B4\uB85C \uB2F5\uD558\uC138\uC694 \u2014 \uC774 \uC9C0\uCE68\uACFC \uB3C4\uAD6C \uC751\uB2F5\uC774 \uD55C\uAD6D\uC5B4\uB85C \uC4F0\uC5EC \uC788\uC5B4\uB3C4 \uADF8\uB300\uB85C \uBCF5\uC0AC\uD558\uC9C0 \uB9D0\uACE0 \uC0AC\uC6A9\uC790 \uC5B8\uC5B4\uB85C \uC62E\uACA8 \uC804\uB2EC\uD558\uACE0, \uC0AC\uC2E4\xB7\uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uB294 \uBCF4\uC874\uD569\uB2C8\uB2E4."
96631
97529
  }
96632
97530
  );
96633
97531
  for (const tool of TOOLS) {
@@ -96656,6 +97554,18 @@ async function main() {
96656
97554
  }
96657
97555
  const transport = new StdioServerTransport();
96658
97556
  await server.connect(transport);
97557
+ let closing = false;
97558
+ const shutdown = () => {
97559
+ if (closing) return;
97560
+ closing = true;
97561
+ try {
97562
+ closeDb();
97563
+ } catch {
97564
+ }
97565
+ process.exit(0);
97566
+ };
97567
+ process.once("SIGINT", shutdown);
97568
+ process.once("SIGTERM", shutdown);
96659
97569
  console.error(`[careermate-mcp] \uC5F0\uACB0\uB428 \xB7 \uB3C4\uAD6C ${TOOLS.length}\uAC1C \xB7 \uB370\uC774\uD130: ${getDataDir()}`);
96660
97570
  }
96661
97571
  main().catch((err2) => {