careermate 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +12 -8
- package/README.md +14 -8
- package/dist/career-os/knowledge/interview-behavioral.md +9 -1
- package/dist/career-os/knowledge/offer-evaluation-decision.md +2 -2
- package/dist/career-os/knowledge/onboarding-first-90-days.md +1 -1
- package/dist/career-os/knowledge/verifiers/human-voice.md +20 -0
- package/dist/doctor.mjs +421 -11
- package/dist/mcp.mjs +421 -11
- package/dist/migrate.mjs +3 -1
- package/dist/public/index.html +1 -0
- package/dist/public/pages/settings.js +45 -0
- package/dist/public/styles.css +2 -0
- package/dist/site/index.html +19 -12
- package/dist/site/llms-install.txt +11 -8
- package/dist/site/start.html +16 -9
- package/dist/site/style.css +9 -0
- package/dist/web.mjs +351 -6
- package/docs/SUPPORTED_AI_APPS.md +6 -6
- package/docs/career-os/knowledge/interview-behavioral.md +9 -1
- package/docs/career-os/knowledge/offer-evaluation-decision.md +2 -2
- package/docs/career-os/knowledge/onboarding-first-90-days.md +1 -1
- package/docs/career-os/knowledge/verifiers/human-voice.md +20 -0
- package/package.json +1 -1
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/services.ts +61 -2
- package/packages/core/src/verify/index.ts +11 -0
- package/packages/core/src/verify/lexicons.ts +149 -0
- package/packages/core/src/verify/lint.ts +248 -0
- package/packages/core/src/verify/normalize.ts +112 -0
- package/packages/db/src/index.ts +1 -0
- package/packages/db/src/settings.ts +41 -0
- package/packages/mcp-tools/src/tools.ts +80 -6
- package/packages/shared/src/schemas.ts +8 -0
package/dist/mcp.mjs
CHANGED
|
@@ -67603,8 +67603,8 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
|
|
|
67603
67603
|
return [g, g, g];
|
|
67604
67604
|
}
|
|
67605
67605
|
static G_HTML([g]) {
|
|
67606
|
-
const
|
|
67607
|
-
return `#${
|
|
67606
|
+
const G2 = makeColorComp(g);
|
|
67607
|
+
return `#${G2}${G2}${G2}`;
|
|
67608
67608
|
}
|
|
67609
67609
|
static RGB_G([r, g, b]) {
|
|
67610
67610
|
return ["G", 0.3 * r + 0.59 * g + 0.11 * b];
|
|
@@ -92496,6 +92496,26 @@ function fromJson(text, fallback) {
|
|
|
92496
92496
|
var toBit = (b) => b ? 1 : 0;
|
|
92497
92497
|
var fromBit = (n) => Number(n) === 1;
|
|
92498
92498
|
|
|
92499
|
+
// packages/db/src/settings.ts
|
|
92500
|
+
var SETTING_PREFIX = "setting.";
|
|
92501
|
+
function getSetting(key) {
|
|
92502
|
+
const row = getDb().prepare(`SELECT value FROM _meta WHERE key = ?`).get(SETTING_PREFIX + key);
|
|
92503
|
+
return row ? row.value : null;
|
|
92504
|
+
}
|
|
92505
|
+
function setSetting(key, value) {
|
|
92506
|
+
getDb().prepare(
|
|
92507
|
+
`INSERT INTO _meta (key, value) VALUES (?, ?)
|
|
92508
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
92509
|
+
).run(SETTING_PREFIX + key, value);
|
|
92510
|
+
}
|
|
92511
|
+
function getVerifyStrict() {
|
|
92512
|
+
return getSetting("verify_strict") === "1";
|
|
92513
|
+
}
|
|
92514
|
+
function setVerifyStrict(on) {
|
|
92515
|
+
setSetting("verify_strict", on ? "1" : "0");
|
|
92516
|
+
return on;
|
|
92517
|
+
}
|
|
92518
|
+
|
|
92499
92519
|
// packages/shared/src/enums.ts
|
|
92500
92520
|
var APPLICATION_STATUSES = [
|
|
92501
92521
|
"draft",
|
|
@@ -92731,7 +92751,9 @@ var CoverLetterVersionInputSchema = external_exports.object({
|
|
|
92731
92751
|
content: reqBody,
|
|
92732
92752
|
note: optNote.describe("\uC774 \uBC84\uC804\uC758 \uBCC0\uACBD \uC694\uC57D (\uC608: \uC9C0\uC6D0\uB3D9\uAE30 \uBCF4\uAC15)"),
|
|
92733
92753
|
source: external_exports.enum(CONTENT_SOURCES).optional(),
|
|
92734
|
-
set_current: external_exports.boolean().optional().describe("\uC774 \uBC84\uC804\uC744 \uD604\uC7AC \uBC84\uC804\uC73C\uB85C \uC9C0\uC815 (\uAE30\uBCF8 true)")
|
|
92754
|
+
set_current: external_exports.boolean().optional().describe("\uC774 \uBC84\uC804\uC744 \uD604\uC7AC \uBC84\uC804\uC73C\uB85C \uC9C0\uC815 (\uAE30\uBCF8 true)"),
|
|
92755
|
+
force: external_exports.boolean().optional().describe("\uC800\uC7A5 \uC804 \uC790\uB3D9 \uC810\uAC80(\uADFC\uAC70 \uC5C6\uB294 \uC218\uCE58)\uC5D0\uC11C \uB9C9\uD614\uC744 \uB54C, \uC758\uB3C4\uD55C \uAC12\uC784\uC744 \uD655\uC778\uD558\uACE0 \uADF8\uB300\uB85C \uC800\uC7A5"),
|
|
92756
|
+
strict: external_exports.boolean().optional().describe("\uC774\uBC88 \uC800\uC7A5\uB9CC \uC5C4\uACA9\uD558\uAC8C \uC810\uAC80(\uC774\uB825\uC11C \uBCF8\uBB38\uC5D0 \uADFC\uAC70 \uC5C6\uB294 \uC218\uCE58\uB3C4 \uCC28\uB2E8). \uC0DD\uB7B5\uD558\uBA74 \uC800\uC7A5\uB41C \uBAA8\uB4DC \uC124\uC815\uC744 \uB530\uB984")
|
|
92735
92757
|
});
|
|
92736
92758
|
var CoverLetterVersionRecordSchema = external_exports.object({
|
|
92737
92759
|
id,
|
|
@@ -92893,7 +92915,7 @@ var ApplicationContextSchema = external_exports.object({
|
|
|
92893
92915
|
});
|
|
92894
92916
|
|
|
92895
92917
|
// packages/shared/src/build-info.ts
|
|
92896
|
-
var APP_VERSION = true ? "0.0.
|
|
92918
|
+
var APP_VERSION = true ? "0.0.2" : "0.0.0-dev";
|
|
92897
92919
|
var BUNDLED = true;
|
|
92898
92920
|
|
|
92899
92921
|
// packages/shared/src/index.ts
|
|
@@ -93709,7 +93731,333 @@ function getApplicationContext(opts = {}) {
|
|
|
93709
93731
|
};
|
|
93710
93732
|
}
|
|
93711
93733
|
|
|
93734
|
+
// packages/core/src/verify/normalize.ts
|
|
93735
|
+
var ZERO_WIDTH = /[-]/g;
|
|
93736
|
+
function normalizeText(s) {
|
|
93737
|
+
return (s ?? "").normalize("NFKC").replace(ZERO_WIDTH, "");
|
|
93738
|
+
}
|
|
93739
|
+
var MAGNITUDE = { "\uC5B5": 1e8, "\uB9CC": 1e4, "\uCC9C": 1e3 };
|
|
93740
|
+
var RISK_UNITS = /* @__PURE__ */ new Set([
|
|
93741
|
+
"%",
|
|
93742
|
+
"\uBC30",
|
|
93743
|
+
"\uC6D0",
|
|
93744
|
+
"\uAC74",
|
|
93745
|
+
"\uBA85",
|
|
93746
|
+
"\uC810",
|
|
93747
|
+
"\uC704",
|
|
93748
|
+
"\uB144\uCC28",
|
|
93749
|
+
"\uB144",
|
|
93750
|
+
"\uAC1C\uC6D4",
|
|
93751
|
+
"\uC8FC",
|
|
93752
|
+
"\uC2DC\uAC04",
|
|
93753
|
+
"\uD68C",
|
|
93754
|
+
"\uAC1C"
|
|
93755
|
+
]);
|
|
93756
|
+
var NUM_RE = /(\d{1,3}(?:,\d{3})+|\d+)(?:\.(\d+))?\s*(억|만|천)?\s*(원|%p|%|%|퍼센트|배|건|명|점|위|년차|개월|년|시간|주|회|개)?/g;
|
|
93757
|
+
var YEAR_RE = /^(?:19|20)\d{2}$/;
|
|
93758
|
+
function normUnit(u) {
|
|
93759
|
+
if (u === "\uFF05" || u === "\uD37C\uC13C\uD2B8" || u === "%p") return "%";
|
|
93760
|
+
return u;
|
|
93761
|
+
}
|
|
93762
|
+
function extractNumbers(text) {
|
|
93763
|
+
const t = normalizeText(text);
|
|
93764
|
+
const out = [];
|
|
93765
|
+
for (const m of t.matchAll(NUM_RE)) {
|
|
93766
|
+
const intPart = m[1].replace(/,/g, "");
|
|
93767
|
+
const frac = m[2] ? "." + m[2] : "";
|
|
93768
|
+
const base = Number(intPart + frac);
|
|
93769
|
+
if (!Number.isFinite(base)) continue;
|
|
93770
|
+
const mag = m[3] ?? "";
|
|
93771
|
+
let unit = normUnit(m[4] ?? "");
|
|
93772
|
+
if (!mag && (!unit || unit === "\uB144") && YEAR_RE.test(intPart) && !frac) continue;
|
|
93773
|
+
const scale = MAGNITUDE[mag] ?? 1;
|
|
93774
|
+
const value = base * scale;
|
|
93775
|
+
if (mag && !unit) unit = "\uC6D0";
|
|
93776
|
+
const surface = m[0].trim();
|
|
93777
|
+
if (!surface) continue;
|
|
93778
|
+
const risk = RISK_UNITS.has(unit) || !unit && value >= 1e3 || !!mag && !unit;
|
|
93779
|
+
out.push({ raw: surface, value, unit, risk });
|
|
93780
|
+
}
|
|
93781
|
+
return out;
|
|
93782
|
+
}
|
|
93783
|
+
function splitSegments(text) {
|
|
93784
|
+
return normalizeText(text).split(/[\n.。!?!?·••]|(?:\s-\s)/).map((s) => s.trim()).filter(Boolean);
|
|
93785
|
+
}
|
|
93786
|
+
function addToMap(map2, unit, value) {
|
|
93787
|
+
const arr = map2.get(unit);
|
|
93788
|
+
if (arr) arr.push(value);
|
|
93789
|
+
else map2.set(unit, [value]);
|
|
93790
|
+
}
|
|
93791
|
+
function buildCorpus(text) {
|
|
93792
|
+
const byUnit = /* @__PURE__ */ new Map();
|
|
93793
|
+
const segments = [];
|
|
93794
|
+
for (const seg of splitSegments(text)) {
|
|
93795
|
+
const segMap = /* @__PURE__ */ new Map();
|
|
93796
|
+
for (const n of extractNumbers(seg)) {
|
|
93797
|
+
addToMap(byUnit, n.unit, n.value);
|
|
93798
|
+
addToMap(segMap, n.unit, n.value);
|
|
93799
|
+
}
|
|
93800
|
+
if (segMap.size) segments.push(segMap);
|
|
93801
|
+
}
|
|
93802
|
+
return { byUnit, segments };
|
|
93803
|
+
}
|
|
93804
|
+
|
|
93805
|
+
// packages/core/src/verify/lexicons.ts
|
|
93806
|
+
var G = (src, flags = "g") => new RegExp(src, flags);
|
|
93807
|
+
var GENERIC_OPENERS = {
|
|
93808
|
+
id: "generic_opener",
|
|
93809
|
+
label: "\uC81C\uB108\uB9AD\xB7\uC0C1\uD22C\uC801 \uB3C4\uC785(\uD68C\uC0AC\uBA85 \uBC14\uAFD4\uB3C4 \uB9D0\uC774 \uB418\uB294 \uC2DC\uC791)",
|
|
93810
|
+
regex: G(
|
|
93811
|
+
[
|
|
93812
|
+
"i am writing to apply",
|
|
93813
|
+
"i have always been passionate",
|
|
93814
|
+
"as a recent graduate",
|
|
93815
|
+
"to whom it may concern",
|
|
93816
|
+
"\uC9C0\uC6D0\uD558\uAC8C \uB418\uC5C8\uC2B5\uB2C8\uB2E4",
|
|
93817
|
+
"\uC9C0\uC6D0\uD569\uB2C8\uB2E4",
|
|
93818
|
+
"\uC5B4\uB9B4 ?\uC801\uBD80\uD130",
|
|
93819
|
+
"\uBCF4\uBA70 \uC790\uB790\uC2B5\uB2C8\uB2E4",
|
|
93820
|
+
"\uC800\uB294 .{0,10}\uD55C \uC0AC\uB78C\uC785\uB2C8\uB2E4",
|
|
93821
|
+
"\uD3C9\uC18C .{0,14}\uAD00\uC2EC\uC774 \uB9CE\uC544",
|
|
93822
|
+
"\uD56D\uC0C1 .{0,12}\uBBFF\uC5B4",
|
|
93823
|
+
"\uB370\uC774\uD130\uC758 \uD798\uC744 \uBBFF"
|
|
93824
|
+
].join("|"),
|
|
93825
|
+
"gi"
|
|
93826
|
+
)
|
|
93827
|
+
};
|
|
93828
|
+
var EMPTY_ADJECTIVES = {
|
|
93829
|
+
id: "empty_adjective",
|
|
93830
|
+
label: "\uC99D\uAC70 \uC5C6\uB294 \uD3C9\uAC00\uD615 \uD615\uC6A9\uC0AC\xB7\uC790\uAE30\uC11C\uC220",
|
|
93831
|
+
regex: G(
|
|
93832
|
+
[
|
|
93833
|
+
"\uC131\uC2E4(\uD558|\uD55C|\uD568)",
|
|
93834
|
+
"\uCC45\uC784\uAC10\uC774 \uAC15",
|
|
93835
|
+
"\uC5F4\uC815(\uC744 \uAC00\uC9C0|\uC801)",
|
|
93836
|
+
"\uC18C\uD1B5\uC744 \uC798",
|
|
93837
|
+
"\uCEE4\uBBA4\uB2C8\uCF00\uC774\uC158\uC774 \uC6D0\uD65C",
|
|
93838
|
+
"\uAF3C\uAF3C(\uD558|\uD55C|\uD568)",
|
|
93839
|
+
"\uB048\uAE30(\uC788|\uAC00)",
|
|
93840
|
+
"\uD5CC\uC2E0\uC801",
|
|
93841
|
+
"\uC8FC\uB3C4\uC801\uC778",
|
|
93842
|
+
"\uC131\uC7A5\uD558\uB294 \uC778\uC7AC",
|
|
93843
|
+
"\uB77C\uACE0 \uC0DD\uAC01\uD569\uB2C8\uB2E4",
|
|
93844
|
+
"\uB9CE\uC740 \uAC83\uC744 \uBC30\uC6E0",
|
|
93845
|
+
"\uD1B5\uD574 \uC131\uC7A5",
|
|
93846
|
+
"\uAE30\uC5EC\uD558\uACE0 \uC2F6",
|
|
93847
|
+
"\uCD5C\uC120\uC744 \uB2E4",
|
|
93848
|
+
"\uBCF4\uD0EC\uC774 \uB418\uACA0",
|
|
93849
|
+
"team player",
|
|
93850
|
+
"hard-?working",
|
|
93851
|
+
"detail-?oriented",
|
|
93852
|
+
"motivated",
|
|
93853
|
+
"proactive",
|
|
93854
|
+
"dedicated",
|
|
93855
|
+
"diligent"
|
|
93856
|
+
].join("|"),
|
|
93857
|
+
"gi"
|
|
93858
|
+
)
|
|
93859
|
+
};
|
|
93860
|
+
var CLICHES = {
|
|
93861
|
+
id: "cliche",
|
|
93862
|
+
label: "AI \uD074\uB9AC\uC170\xB7\uACF5\uD5C8\uD55C \uC0C1\uD22C\uC5B4",
|
|
93863
|
+
regex: G(
|
|
93864
|
+
[
|
|
93865
|
+
"\uAE09\uBCC0\uD558\uB294 \uC2DC\uB300",
|
|
93866
|
+
"4\uCC28 \uC0B0\uC5C5\uD601\uBA85",
|
|
93867
|
+
"\uB04A\uC784\uC5C6\uB294 \uB178\uB825",
|
|
93868
|
+
"\uADC0\uC0AC\uC758 \uBC1C\uC804\uC5D0 \uAE30\uC5EC",
|
|
93869
|
+
"\uADC0\uC0AC",
|
|
93870
|
+
"\uC18C\uD1B5\uACFC \uD611\uC5C5",
|
|
93871
|
+
"\uAE00\uB85C\uBC8C \uC778\uC7AC",
|
|
93872
|
+
"\uBB34\uD55C\uD55C \uAC00\uB2A5\uC131",
|
|
93873
|
+
"\uD55C \uB2E8\uACC4 \uB354 \uC131\uC7A5"
|
|
93874
|
+
].join("|")
|
|
93875
|
+
)
|
|
93876
|
+
};
|
|
93877
|
+
var TRANSLATIONESE = {
|
|
93878
|
+
id: "translationese",
|
|
93879
|
+
label: "\uBC88\uC5ED\uD22C \uBB38\uBC95(\uC601\uC5B4 \uC9C1\uC5ED\uCCB4)",
|
|
93880
|
+
regex: G(
|
|
93881
|
+
[
|
|
93882
|
+
"\uC5D0 \uB300\uD55C",
|
|
93883
|
+
"\uC744 \uD1B5\uD574",
|
|
93884
|
+
"\uB97C \uD1B5\uD574",
|
|
93885
|
+
"\uC5D0 \uC788\uC5B4\uC11C",
|
|
93886
|
+
"\uB77C\uB294 \uC810\uC5D0\uC11C",
|
|
93887
|
+
"\uAC00\uC9C0\uACE0 \uC788\uC2B5\uB2C8\uB2E4",
|
|
93888
|
+
"\uB418\uC5B4\uC84C",
|
|
93889
|
+
"\uB418\uC5B4\uC9D1\uB2C8\uB2E4",
|
|
93890
|
+
"\uD568\uC73C\uB85C\uC368",
|
|
93891
|
+
"\uC5D0 \uB2E4\uB984 \uC544\uB2C8",
|
|
93892
|
+
"\uC9C4\uD589\uD558\uC600\uC2B5\uB2C8\uB2E4"
|
|
93893
|
+
].join("|")
|
|
93894
|
+
)
|
|
93895
|
+
};
|
|
93896
|
+
var HYPE = {
|
|
93897
|
+
id: "hype",
|
|
93898
|
+
label: "\uACFC\uC7A5 \uC218\uC2DD\uC5B4",
|
|
93899
|
+
regex: G(["\uB9E4\uC6B0", "\uC815\uB9D0\uB85C", "\uD0C1\uC6D4(\uD55C|\uD558\uAC8C)", "\uC131\uACF5\uC801\uC73C\uB85C", "\uC644\uBCBD(\uD55C|\uD558\uAC8C)", "\uC5C4\uCCAD\uB09C", "\uD68D\uAE30\uC801"].join("|"))
|
|
93900
|
+
};
|
|
93901
|
+
var BLIND_PII = {
|
|
93902
|
+
id: "blind_pii",
|
|
93903
|
+
label: "\uBE14\uB77C\uC778\uB4DC \uCC44\uC6A9 \uC704\uBC18 \uC18C\uC9C0(\uC9C1\uBB34\uBB34\uAD00 \uAC1C\uC778\uC815\uBCF4)",
|
|
93904
|
+
regex: G(["\\d{2,3}\\s?\uC138", "\uC0AC\uC9C4\\s?\uCCA8\uBD80", "\uAC00\uC871\uAD00\uACC4", "\uBCF8\uC801", "\uCD9C\uC2E0\uC9C0", "\uC0DD\uB144\uC6D4\uC77C"].join("|"))
|
|
93905
|
+
};
|
|
93906
|
+
var STYLE_LEXICONS = [
|
|
93907
|
+
GENERIC_OPENERS,
|
|
93908
|
+
EMPTY_ADJECTIVES,
|
|
93909
|
+
CLICHES,
|
|
93910
|
+
TRANSLATIONESE,
|
|
93911
|
+
HYPE,
|
|
93912
|
+
BLIND_PII
|
|
93913
|
+
];
|
|
93914
|
+
function countHits(lex, text, maxHits = 8) {
|
|
93915
|
+
const hits = [];
|
|
93916
|
+
let count = 0;
|
|
93917
|
+
for (const m of text.matchAll(lex.regex)) {
|
|
93918
|
+
count++;
|
|
93919
|
+
if (hits.length < maxHits) hits.push(m[0].trim());
|
|
93920
|
+
}
|
|
93921
|
+
return { count, hits };
|
|
93922
|
+
}
|
|
93923
|
+
|
|
93924
|
+
// packages/core/src/verify/lint.ts
|
|
93925
|
+
var within = (a, b) => Math.abs(a - b) <= Math.max(0.5, 0.02 * Math.max(Math.abs(a), Math.abs(b)));
|
|
93926
|
+
var approx = (a, b) => Math.abs(a - b) <= Math.max(0.5, 0.01 * Math.max(Math.abs(a), Math.abs(b)));
|
|
93927
|
+
function isExplained(token, corpus) {
|
|
93928
|
+
const v = token.value;
|
|
93929
|
+
for (const s of corpus.byUnit.get(token.unit) ?? []) if (within(v, s)) return true;
|
|
93930
|
+
for (const seg of corpus.segments) {
|
|
93931
|
+
const vals = seg.get(token.unit);
|
|
93932
|
+
if (vals && vals.length >= 2) {
|
|
93933
|
+
for (let i2 = 0; i2 < vals.length; i2++)
|
|
93934
|
+
for (let j = 0; j < vals.length; j++) {
|
|
93935
|
+
if (i2 === j) continue;
|
|
93936
|
+
if (approx(v, vals[i2] - vals[j]) || approx(v, vals[i2] + vals[j])) return true;
|
|
93937
|
+
}
|
|
93938
|
+
}
|
|
93939
|
+
}
|
|
93940
|
+
if (token.unit === "%") {
|
|
93941
|
+
for (const seg of corpus.segments) {
|
|
93942
|
+
for (const [u, vals] of seg) {
|
|
93943
|
+
if (u === "%" || vals.length < 2) continue;
|
|
93944
|
+
for (let i2 = 0; i2 < vals.length; i2++)
|
|
93945
|
+
for (let j = 0; j < vals.length; j++) {
|
|
93946
|
+
if (i2 === j) continue;
|
|
93947
|
+
const a = vals[i2];
|
|
93948
|
+
const b = vals[j];
|
|
93949
|
+
if (b !== 0 && (approx(v, a / b * 100) || approx(v, (a - b) / b * 100) || approx(v, (a - b) / a * 100)))
|
|
93950
|
+
return true;
|
|
93951
|
+
if (a + b !== 0 && approx(v, a / (a + b) * 100)) return true;
|
|
93952
|
+
}
|
|
93953
|
+
}
|
|
93954
|
+
}
|
|
93955
|
+
}
|
|
93956
|
+
return false;
|
|
93957
|
+
}
|
|
93958
|
+
function analyzeProvenance(outputText, corpus) {
|
|
93959
|
+
const documents = buildCorpus(corpus.documents);
|
|
93960
|
+
const structured = buildCorpus(corpus.structured);
|
|
93961
|
+
const job = buildCorpus(corpus.job);
|
|
93962
|
+
const nums = extractNumbers(outputText).filter((n) => n.risk);
|
|
93963
|
+
const res = { checked: nums.length, supported: 0, fabricated: [], unverified: [], jobSourced: [] };
|
|
93964
|
+
for (const n of nums) {
|
|
93965
|
+
const tok = { raw: n.raw, value: n.value, unit: n.unit };
|
|
93966
|
+
if (isExplained(n, documents)) res.supported++;
|
|
93967
|
+
else if (isExplained(n, structured)) res.unverified.push({ ...tok, reason: "structured_only" });
|
|
93968
|
+
else if (isExplained(n, job)) res.jobSourced.push({ ...tok, reason: "job_only" });
|
|
93969
|
+
else res.fabricated.push({ ...tok, reason: "fabricated" });
|
|
93970
|
+
}
|
|
93971
|
+
return res;
|
|
93972
|
+
}
|
|
93973
|
+
function styleSignals(text) {
|
|
93974
|
+
const t = normalizeText(text);
|
|
93975
|
+
const signals = [];
|
|
93976
|
+
for (const lex of STYLE_LEXICONS) {
|
|
93977
|
+
const { count, hits } = countHits(lex, t);
|
|
93978
|
+
if (count > 0) {
|
|
93979
|
+
const severity = lex.id === "blind_pii" || lex.id === "generic_opener" ? "warn" : "info";
|
|
93980
|
+
signals.push({ id: lex.id, label: lex.label, severity, count, hits });
|
|
93981
|
+
}
|
|
93982
|
+
}
|
|
93983
|
+
return signals;
|
|
93984
|
+
}
|
|
93985
|
+
var DISCLAIMER = "\uC774 \uC810\uAC80\uC740 LLM \uC5C6\uC774 \uC140 \uC218 \uC788\uB294 \uAC83\uB9CC \uBD05\uB2C8\uB2E4(\uC22B\uC790 \uCD9C\uCC98\xB7\uBB38\uCCB4 \uC2E0\uD638). \uD1B5\uACFC=\uD569\uACA9\uC774 \uC544\uB2C8\uBA70, \uC0AC\uC2E4\xB7\uB3D9\uBB38\uC11C\uB2F5\xB7\uC758\uBBF8 \uD310\uB2E8\uC740 \uB2F9\uC2E0(AI)\uC774 \uC9C1\uC811 \uD574\uC57C \uD569\uB2C8\uB2E4.";
|
|
93986
|
+
function lintArtifact(kind, text, corpus, opts = {}) {
|
|
93987
|
+
const norm = normalizeText(text);
|
|
93988
|
+
const corpusAvailable = (corpus.documents + corpus.structured).trim().length > 0;
|
|
93989
|
+
const provenance = analyzeProvenance(text, corpus);
|
|
93990
|
+
const signals = styleSignals(text);
|
|
93991
|
+
const strict = !!opts.strict;
|
|
93992
|
+
const blocking = [];
|
|
93993
|
+
if (corpusAvailable && kind === "cover_letter") {
|
|
93994
|
+
if (provenance.fabricated.length > 0) {
|
|
93995
|
+
blocking.push({
|
|
93996
|
+
id: "fabricated_numbers",
|
|
93997
|
+
label: "\uADFC\uAC70 \uC5C6\uB294 \uC218\uCE58(\uC800\uC7A5\uB41C \uACBD\uB825\xB7\uC774\uB825\uC11C\uC5D0 \uC5C6\uC74C)",
|
|
93998
|
+
detail: provenance.fabricated.map((f) => f.raw).join(", ")
|
|
93999
|
+
});
|
|
94000
|
+
}
|
|
94001
|
+
if (strict && provenance.unverified.length > 0) {
|
|
94002
|
+
blocking.push({
|
|
94003
|
+
id: "unverified_numbers_strict",
|
|
94004
|
+
label: "\uC5C4\uACA9 \uBAA8\uB4DC: \uC774\uB825\uC11C \uBCF8\uBB38\uC5D0 \uADFC\uAC70 \uC5C6\uB294 \uC218\uCE58(\uAD6C\uC870\uD654 \uACBD\uB825/\uD504\uB85C\uC81D\uD2B8 \uD56D\uBAA9\uC5D0\uB9CC \uC788\uC74C)",
|
|
94005
|
+
detail: provenance.unverified.map((u) => u.raw).join(", ")
|
|
94006
|
+
});
|
|
94007
|
+
}
|
|
94008
|
+
}
|
|
94009
|
+
return {
|
|
94010
|
+
kind,
|
|
94011
|
+
charCount: [...norm].length,
|
|
94012
|
+
signals,
|
|
94013
|
+
provenance,
|
|
94014
|
+
blocking,
|
|
94015
|
+
corpusAvailable,
|
|
94016
|
+
strict,
|
|
94017
|
+
disclaimer: DISCLAIMER
|
|
94018
|
+
};
|
|
94019
|
+
}
|
|
94020
|
+
function summarizeReport(r) {
|
|
94021
|
+
const parts = [];
|
|
94022
|
+
if (r.blocking.length)
|
|
94023
|
+
parts.push(`\u26D4 \uC800\uC7A5 \uCC28\uB2E8: ${r.blocking.map((b) => `${b.label}(${b.detail})`).join("; ")}`);
|
|
94024
|
+
if (r.provenance.unverified.length)
|
|
94025
|
+
parts.push(
|
|
94026
|
+
`\uBBF8\uD655\uC778 \uC218\uCE58 ${r.provenance.unverified.length}\uAC1C(${r.provenance.unverified.map((u) => u.raw).join(", ")}) \u2014 \uC774\uB825\uC11C \uBCF8\uBB38\uC5D4 \uC5C6\uACE0 \uAD6C\uC870\uD654 \uACBD\uB825/\uD504\uB85C\uC81D\uD2B8 \uD56D\uBAA9\uC5D0\uB9CC \uC788\uC74C, \uC0AC\uC2E4\uC778\uC9C0 \uD655\uC778`
|
|
94027
|
+
);
|
|
94028
|
+
if (r.provenance.jobSourced.length)
|
|
94029
|
+
parts.push(`\uACF5\uACE0 \uCD9C\uCC98 \uC218\uCE58 ${r.provenance.jobSourced.length}\uAC1C(\uBCF8\uC778 \uC131\uACFC\uB85C \uC4F0\uC9C0 \uC54A\uC558\uB294\uC9C0 \uD655\uC778)`);
|
|
94030
|
+
const adv = r.signals.filter((s) => s.count > 0);
|
|
94031
|
+
if (adv.length) parts.push("\uBB38\uCCB4 \uC2E0\uD638: " + adv.map((s) => `${s.label.split("(")[0]}\xD7${s.count}`).join(", "));
|
|
94032
|
+
if (!parts.length) parts.push("\uC790\uB3D9 \uC810\uAC80 \uD1B5\uACFC(\uC140 \uC218 \uC788\uB294 \uD56D\uBAA9 \uAE30\uC900). \uC758\uBBF8 \uD310\uB2E8\uC740 \uC9C1\uC811 \uD655\uC778\uD558\uC138\uC694.");
|
|
94033
|
+
return parts.join(" \xB7 ");
|
|
94034
|
+
}
|
|
94035
|
+
|
|
93712
94036
|
// packages/core/src/services.ts
|
|
94037
|
+
function collectVerifyCorpus(jobId) {
|
|
94038
|
+
const documents = [];
|
|
94039
|
+
for (const d of documentRepo.list()) {
|
|
94040
|
+
if (d.source !== "ai") documents.push(d.content);
|
|
94041
|
+
}
|
|
94042
|
+
const structured = [];
|
|
94043
|
+
for (const e of experienceRepo.list()) {
|
|
94044
|
+
structured.push([e.company, e.role, e.description, ...e.achievements ?? [], ...e.tech ?? []].filter(Boolean).join("\n"));
|
|
94045
|
+
}
|
|
94046
|
+
for (const p of projectRepo.list()) {
|
|
94047
|
+
structured.push([p.name, p.description, ...p.highlights ?? [], ...p.tech ?? []].filter(Boolean).join("\n"));
|
|
94048
|
+
}
|
|
94049
|
+
for (const s of skillRepo.list()) {
|
|
94050
|
+
structured.push([s.name, s.level, s.years != null ? `${s.years}\uB144` : ""].filter(Boolean).join(" "));
|
|
94051
|
+
}
|
|
94052
|
+
const job = jobId ? jobRepo.get(jobId) : null;
|
|
94053
|
+
const jobText = job ? [job.description, ...job.requirements ?? [], ...job.keywords ?? []].filter(Boolean).join("\n") : "";
|
|
94054
|
+
return { documents: documents.join("\n"), structured: structured.join("\n"), job: jobText };
|
|
94055
|
+
}
|
|
94056
|
+
function previewCoverLetter(text, jobId, strict) {
|
|
94057
|
+
return lintArtifact("cover_letter", text, collectVerifyCorpus(jobId), {
|
|
94058
|
+
strict: strict ?? getVerifyStrict()
|
|
94059
|
+
});
|
|
94060
|
+
}
|
|
93713
94061
|
function saveProfile(input) {
|
|
93714
94062
|
const p = profileRepo.save(input);
|
|
93715
94063
|
activityRepo.log("profile_updated", `\uD504\uB85C\uD544\uC744 ${p.name ? `(${p.name}) ` : ""}\uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4.`, "profile", p.id);
|
|
@@ -93751,6 +94099,15 @@ function saveFitAnalysis(input) {
|
|
|
93751
94099
|
return fit;
|
|
93752
94100
|
}
|
|
93753
94101
|
function saveCoverLetterVersion(input) {
|
|
94102
|
+
const strict = input.strict ?? getVerifyStrict();
|
|
94103
|
+
const verification = lintArtifact("cover_letter", input.content, collectVerifyCorpus(input.job_id), { strict });
|
|
94104
|
+
const forced = verification.blocking.length > 0 && input.force === true;
|
|
94105
|
+
if (verification.blocking.length > 0 && !forced) {
|
|
94106
|
+
const detail = verification.blocking.map((b) => `${b.label}: ${b.detail}`).join("; ");
|
|
94107
|
+
const strictHit = verification.blocking.some((b) => b.id === "unverified_numbers_strict");
|
|
94108
|
+
const tail = strictHit ? `\uC5C4\uACA9 \uBAA8\uB4DC\uAC00 \uCF1C\uC838 \uC788\uC5B4, \uC62C\uB9B0 \uC774\uB825\uC11C \uBCF8\uBB38\uC5D0 \uC5C6\uB294 \uC218\uCE58\uB294 \uB9C9\uD799\uB2C8\uB2E4. \uC774\uB825\uC11C\uB97C \uC62C\uB9AC\uAC70\uB098 \uC5C4\uACA9 \uBAA8\uB4DC\uB97C \uB044\uAC70\uB098, \uC758\uB3C4\uD55C \uAC12\uC774\uBA74 force: true\uB85C \uC800\uC7A5\uD558\uC138\uC694.` : `\uC774 \uC218\uCE58\uB294 \uC800\uC7A5\uB41C \uACBD\uB825\xB7\uC774\uB825\uC11C\xB7\uD504\uB85C\uC81D\uD2B8\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4. \uC6D0\uBCF8 \uB370\uC774\uD130\uC758 \uC2E4\uC81C \uC218\uCE58\uB85C \uACE0\uCE58\uAC70\uB098, \uC758\uB3C4\uD55C \uAC12\uC774 \uB9DE\uB2E4\uBA74 \uBA3C\uC800 \uACBD\uB825/\uD504\uB85C\uC81D\uD2B8\uC5D0 \uADF8 \uC218\uCE58\uB97C \uCD94\uAC00\uD55C \uB4A4 \uB2E4\uC2DC \uC800\uC7A5\uD558\uC138\uC694(\uADF8\uB300\uB85C \uC800\uC7A5\uD558\uB824\uBA74 force: true).`;
|
|
94109
|
+
throw new Error(`\uC800\uC7A5 \uC804 \uC790\uB3D9 \uC810\uAC80\uC5D0\uC11C \uB9C9\uD614\uC2B5\uB2C8\uB2E4 \u2014 ${detail}. ${tail}`);
|
|
94110
|
+
}
|
|
93754
94111
|
const { coverLetter, version: version3 } = coverLetterRepo.addVersion({
|
|
93755
94112
|
cover_letter_id: input.cover_letter_id,
|
|
93756
94113
|
title: input.title,
|
|
@@ -93772,7 +94129,7 @@ function saveCoverLetterVersion(input) {
|
|
|
93772
94129
|
"cover_letter",
|
|
93773
94130
|
coverLetter.id
|
|
93774
94131
|
);
|
|
93775
|
-
return { coverLetter, version: version3 };
|
|
94132
|
+
return { coverLetter, version: version3, verification, forced };
|
|
93776
94133
|
}
|
|
93777
94134
|
function updateApplicationStatus(jobId, status, note) {
|
|
93778
94135
|
const job = jobRepo.get(jobId);
|
|
@@ -95625,16 +95982,22 @@ ${summary}
|
|
|
95625
95982
|
const s = getOnboardingStatus();
|
|
95626
95983
|
const lines = s.next_steps.map((t, i2) => `${i2 + 1}. ${t}`).join("\n");
|
|
95627
95984
|
const upd = getUpdateStatus();
|
|
95985
|
+
const strict = getVerifyStrict();
|
|
95628
95986
|
let text = `\uC628\uBCF4\uB529 ${s.completed ? "\uC644\uB8CC\uB428" : "\uC9C4\uD589 \uD544\uC694"} \xB7 \uD504\uB85C\uD544 \uC644\uC131\uB3C4 ${s.profile_completeness}%
|
|
95629
95987
|
|
|
95630
95988
|
\uB2E4\uC74C \uB2E8\uACC4:
|
|
95631
95989
|
${lines}`;
|
|
95990
|
+
if (!s.completed) {
|
|
95991
|
+
text += `
|
|
95992
|
+
|
|
95993
|
+
\u{1F4A1} \uCC38\uACE0: \uC790\uC18C\uC11C\uB97C \uC800\uC7A5\uD560 \uB54C CareerMate\uAC00 \uBCF8\uBB38\uC758 \uC218\uCE58\uB97C \uC800\uC7A5\uB41C \uACBD\uB825\xB7\uC774\uB825\uC11C\uC640 \uC790\uB3D9 \uB300\uC870\uD574, \uADFC\uAC70 \uC5C6\uB294 \uC218\uCE58\uB294 \uB9C9\uC2B5\uB2C8\uB2E4. \uB354 \uAE50\uAE50\uD558\uAC8C \uBCF4\uAE38 \uC6D0\uD558\uBA74 "\uC5C4\uACA9\uD558\uAC8C \uBD10\uC918"\uB77C\uACE0 \uD558\uAC70\uB098 \uB300\uC2DC\uBCF4\uB4DC \uC124\uC815\uC5D0\uC11C \uC5C4\uACA9 \uBAA8\uB4DC\uB97C \uCF24 \uC218 \uC788\uC5B4\uC694(\uC774\uB825\uC11C \uBCF8\uBB38\uC5D0 \uC5C6\uB294 \uC218\uCE58\uAE4C\uC9C0 \uCC28\uB2E8).`;
|
|
95994
|
+
}
|
|
95632
95995
|
if (upd.update_available) {
|
|
95633
95996
|
text += `
|
|
95634
95997
|
|
|
95635
95998
|
\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.`;
|
|
95636
95999
|
}
|
|
95637
|
-
return ok(text, { ...s, update: upd });
|
|
96000
|
+
return ok(text, { ...s, verify_strict: strict, update: upd });
|
|
95638
96001
|
}
|
|
95639
96002
|
},
|
|
95640
96003
|
/* --------------------------------------------------------------- profile */
|
|
@@ -95768,11 +96131,20 @@ ${lines}`;
|
|
|
95768
96131
|
{
|
|
95769
96132
|
name: "save_cover_letter_version",
|
|
95770
96133
|
title: "\uC790\uAE30\uC18C\uAC1C\uC11C \uBC84\uC804 \uC800\uC7A5",
|
|
95771
|
-
description: '\uC790\uAE30\uC18C\uAC1C\uC11C\uC758 \uC0C8 \uBC84\uC804\uC744 \uC800\uC7A5\uD569\uB2C8\uB2E4. CareerMate \uC790\uC18C\uC11C \uC791\uC131 \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC758 \uD575\uC2EC \uC800\uC7A5 \uB2E8\uACC4\uC785\uB2C8\uB2E4. cover_letter_id\uB97C \uC8FC\uBA74 \uAE30\uC874 \uC790\uC18C\uC11C\uC5D0 \uC0C8 \uBC84\uC804\uC744 \uCD94\uAC00\uD558\uACE0, \uC5C6\uC73C\uBA74 \uC0C8 \uC790\uC18C\uC11C\uB97C \uB9CC\uB4E4\uC5B4 v1\uB85C \uC800\uC7A5\uD569\uB2C8\uB2E4(\uC774\uB54C title \uAD8C\uC7A5). note\uC5D0 \uC774 \uBC84\uC804\uC758 \uBCC0\uACBD \uC694\uC57D(\uC608: "\uC9C0\uC6D0\uB3D9\uAE30 \uBCF4\uAC15")\uC744 \uB0A8\uAE30\uBA74 \uC0AC\uC6A9\uC790\uAC00 \uB300\uC2DC\uBCF4\uB4DC\uC5D0\uC11C \uBC84\uC804 \uD788\uC2A4\uD1A0\uB9AC\uB97C \uC774\uD574\uD558\uAE30 \uC27D\uC2B5\uB2C8\uB2E4. job_id\uB85C \uACF5\uACE0\uC5D0 \uC5F0\uACB0\uD558\uBA74 \uC9C0\uC6D0 \uD56D\uBAA9\uACFC \uC790\uB3D9 \uC5F0\uACB0\uB429\uB2C8\uB2E4.',
|
|
96134
|
+
description: '\uC790\uAE30\uC18C\uAC1C\uC11C\uC758 \uC0C8 \uBC84\uC804\uC744 \uC800\uC7A5\uD569\uB2C8\uB2E4. CareerMate \uC790\uC18C\uC11C \uC791\uC131 \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC758 \uD575\uC2EC \uC800\uC7A5 \uB2E8\uACC4\uC785\uB2C8\uB2E4. cover_letter_id\uB97C \uC8FC\uBA74 \uAE30\uC874 \uC790\uC18C\uC11C\uC5D0 \uC0C8 \uBC84\uC804\uC744 \uCD94\uAC00\uD558\uACE0, \uC5C6\uC73C\uBA74 \uC0C8 \uC790\uC18C\uC11C\uB97C \uB9CC\uB4E4\uC5B4 v1\uB85C \uC800\uC7A5\uD569\uB2C8\uB2E4(\uC774\uB54C title \uAD8C\uC7A5). note\uC5D0 \uC774 \uBC84\uC804\uC758 \uBCC0\uACBD \uC694\uC57D(\uC608: "\uC9C0\uC6D0\uB3D9\uAE30 \uBCF4\uAC15")\uC744 \uB0A8\uAE30\uBA74 \uC0AC\uC6A9\uC790\uAC00 \uB300\uC2DC\uBCF4\uB4DC\uC5D0\uC11C \uBC84\uC804 \uD788\uC2A4\uD1A0\uB9AC\uB97C \uC774\uD574\uD558\uAE30 \uC27D\uC2B5\uB2C8\uB2E4. job_id\uB85C \uACF5\uACE0\uC5D0 \uC5F0\uACB0\uD558\uBA74 \uC9C0\uC6D0 \uD56D\uBAA9\uACFC \uC790\uB3D9 \uC5F0\uACB0\uB429\uB2C8\uB2E4. \uC800\uC7A5 \uC9C1\uC804 \uC790\uB3D9\uC73C\uB85C "\uADFC\uAC70 \uC5C6\uB294 \uC218\uCE58" \uC810\uAC80\uC744 \uB3CC\uB9BD\uB2C8\uB2E4 \u2014 \uBCF8\uBB38\uC758 \uC815\uB7C9 \uC218\uCE58\uAC00 \uC800\uC7A5\uB41C \uACBD\uB825\xB7\uC774\uB825\uC11C\xB7\uD504\uB85C\uC81D\uD2B8\uC5D0 \uADFC\uAC70\uAC00 \uC5C6\uC73C\uBA74(\uD658\uAC01 \uC758\uC2EC) \uC800\uC7A5\uC774 \uB9C9\uD799\uB2C8\uB2E4. \uADF8\uB54C\uB294 \uC6D0\uBCF8\uC758 \uC2E4\uC81C \uC218\uCE58\uB85C \uACE0\uCE58\uAC70\uB098, \uC758\uB3C4\uD55C \uAC12\uC774\uBA74 force:true\uB85C \uB2E4\uC2DC \uC800\uC7A5\uD558\uC138\uC694.',
|
|
95772
96135
|
inputSchema: CoverLetterVersionInputSchema.shape,
|
|
95773
96136
|
handler: (args) => {
|
|
95774
|
-
|
|
95775
|
-
|
|
96137
|
+
try {
|
|
96138
|
+
const { coverLetter, version: version3, verification, forced } = saveCoverLetterVersion(args);
|
|
96139
|
+
const advisory = summarizeReport(verification);
|
|
96140
|
+
const head = `'${coverLetter.title}' v${version3.version_no}\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4.`;
|
|
96141
|
+
const hasAdvisory = verification.signals.length || verification.provenance.jobSourced.length || verification.provenance.unverified.length;
|
|
96142
|
+
const note = forced ? "\n\u26A0\uFE0F \uADFC\uAC70 \uC5C6\uB294 \uC218\uCE58\uAC00 \uC788\uB294\uB370\uB3C4 force\uB85C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4(\uAC80\uC99D \uBBF8\uD1B5\uACFC\uB85C \uAE30\uB85D). \uBA74\uC811 \uC804 \uC0AC\uC2E4 \uD655\uC778\uC744 \uAD8C\uD569\uB2C8\uB2E4." : hasAdvisory ? `
|
|
96143
|
+
\uC810\uAC80: ${advisory}` : "";
|
|
96144
|
+
return ok(head + note, { coverLetter, version: version3, verification });
|
|
96145
|
+
} catch (e) {
|
|
96146
|
+
return fail(e instanceof Error ? e.message : "\uC800\uC7A5 \uC2E4\uD328");
|
|
96147
|
+
}
|
|
95776
96148
|
}
|
|
95777
96149
|
},
|
|
95778
96150
|
/* ------------------------------------------------------------------ jobs */
|
|
@@ -96002,7 +96374,7 @@ ${lines}`;
|
|
|
96002
96374
|
handler: (args) => {
|
|
96003
96375
|
try {
|
|
96004
96376
|
const content = getPlaybook(args.domain);
|
|
96005
|
-
return ok(content, { domain: args.domain
|
|
96377
|
+
return ok(content, { domain: args.domain });
|
|
96006
96378
|
} catch (e) {
|
|
96007
96379
|
return fail(e instanceof Error ? e.message : "\uD50C\uB808\uC774\uBD81\uC744 \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
|
|
96008
96380
|
}
|
|
@@ -96019,12 +96391,50 @@ ${lines}`;
|
|
|
96019
96391
|
handler: (args) => {
|
|
96020
96392
|
try {
|
|
96021
96393
|
const content = getVerifier(args.id);
|
|
96022
|
-
return ok(content, { id: args.id
|
|
96394
|
+
return ok(content, { id: args.id });
|
|
96023
96395
|
} catch (e) {
|
|
96024
96396
|
return fail(e instanceof Error ? e.message : "\uAC80\uC99D \uB8E8\uBE0C\uB9AD\uC744 \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
|
|
96025
96397
|
}
|
|
96026
96398
|
}
|
|
96027
96399
|
},
|
|
96400
|
+
/* ----------------------------------------- career-os: deterministic lint */
|
|
96401
|
+
{
|
|
96402
|
+
name: "validate_cover_letter",
|
|
96403
|
+
title: "\uC790\uAE30\uC18C\uAC1C\uC11C \uC790\uB3D9 \uC810\uAC80 (\uC800\uC7A5 \uC804 \uBBF8\uB9AC\uBCF4\uAE30)",
|
|
96404
|
+
description: "\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C save_cover_letter_version\uC73C\uB85C \uC800\uC7A5\uD558\uAE30 \uC9C1\uC804\uC5D0 \uD638\uCD9C\uD558\uBA74, \uC800\uC7A5\uD558\uC9C0 \uC54A\uACE0 \uBBF8\uB9AC \uC810\uAC80\uB9CC \uD569\uB2C8\uB2E4. CareerMate\uAC00 LLM \uC5C6\uC774 \uACB0\uC815\uB860\uC73C\uB85C (1) \uBCF8\uBB38\uC758 \uC815\uB7C9 \uC218\uCE58\uAC00 \uC800\uC7A5\uB41C \uACBD\uB825\xB7\uC774\uB825\uC11C\xB7\uD504\uB85C\uC81D\uD2B8\uC5D0 \uADFC\uAC70\uAC00 \uC788\uB294\uC9C0(\uC5C6\uC73C\uBA74 \uD658\uAC01 \uC758\uC2EC \u2192 \uC800\uC7A5 \uC2DC \uCC28\uB2E8\uB428), (2) \uACF5\uACE0\uC5D0\uC11C \uAC00\uC838\uC628 \uC218\uCE58\uB97C \uBCF8\uC778 \uC131\uACFC\uCC98\uB7FC \uC4F0\uC9C0 \uC54A\uC558\uB294\uC9C0, (3) \uBC88\uC5ED\uD22C\xB7\uC81C\uB108\uB9AD \uB3C4\uC785\xB7\uBE48 \uD615\uC6A9\uC0AC\xB7\uBE14\uB77C\uC778\uB4DC \uC704\uBC18 \uAC19\uC740 \uBB38\uCCB4 \uC2E0\uD638\uB97C \uC138\uC5B4 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC810\uAC80\uC740 \uC140 \uC218 \uC788\uB294 \uAC83\uB9CC \uBD05\uB2C8\uB2E4 \u2014 \uC0AC\uC2E4\uC131\xB7\uB3D9\uBB38\uC11C\uB2F5 \uAC19\uC740 \uC758\uBBF8 \uD310\uB2E8\uC740 \uB2F9\uC2E0(AI)\uC774 \uC9C1\uC811 \uD558\uC138\uC694. job_id\uB97C \uC8FC\uBA74 \uACF5\uACE0 \uC218\uCE58\uAE4C\uC9C0 \uBE44\uAD50\uD569\uB2C8\uB2E4.",
|
|
96405
|
+
inputSchema: {
|
|
96406
|
+
text: external_exports.string().max(2e5).describe("\uC810\uAC80\uD560 \uC790\uAE30\uC18C\uAC1C\uC11C \uBCF8\uBB38"),
|
|
96407
|
+
job_id: external_exports.string().optional().describe("\uC5F0\uACB0\uD560 \uACF5\uACE0(\uC788\uC73C\uBA74 \uACF5\uACE0 \uC218\uCE58\uAE4C\uC9C0 \uBE44\uAD50)"),
|
|
96408
|
+
strict: external_exports.boolean().optional().describe('\uC774\uBC88 \uC810\uAC80\uB9CC \uC5C4\uACA9\uD558\uAC8C(\uC774\uB825\uC11C \uBCF8\uBB38\uC5D0 \uADFC\uAC70 \uC5C6\uB294 \uC218\uCE58\uB3C4 \uCC28\uB2E8). \uC0AC\uC6A9\uC790\uAC00 "\uC5C4\uACA9\uD558\uAC8C \uBD10\uC918"\uB77C\uACE0 \uD560 \uB54C true. \uC0DD\uB7B5\uD558\uBA74 \uC800\uC7A5\uB41C \uBAA8\uB4DC\uB97C \uB530\uB984')
|
|
96409
|
+
},
|
|
96410
|
+
readOnly: true,
|
|
96411
|
+
handler: (args) => {
|
|
96412
|
+
const text = typeof args?.text === "string" ? args.text : "";
|
|
96413
|
+
if (!text.trim()) return fail("\uC810\uAC80\uD560 \uBCF8\uBB38\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
96414
|
+
const report = previewCoverLetter(text, args?.job_id ?? null, args?.strict);
|
|
96415
|
+
const verdict = report.blocking.length ? `\u26D4 \uC774\uB300\uB85C \uC800\uC7A5\uD558\uBA74 \uB9C9\uD799\uB2C8\uB2E4 \u2014 ${report.blocking.map((b) => `${b.label}: ${b.detail}`).join("; ")}` : "\u2705 \uC790\uB3D9 \uCC28\uB2E8 \uD56D\uBAA9 \uC5C6\uC74C(\uC140 \uC218 \uC788\uB294 \uD56D\uBAA9 \uAE30\uC900).";
|
|
96416
|
+
const modeLine = report.strict ? "\n(\uC5C4\uACA9 \uBAA8\uB4DC \uC801\uC6A9 \uC911)" : "";
|
|
96417
|
+
return ok(`${verdict}${modeLine}
|
|
96418
|
+
${summarizeReport(report)}
|
|
96419
|
+
|
|
96420
|
+
${report.disclaimer}`, report);
|
|
96421
|
+
}
|
|
96422
|
+
},
|
|
96423
|
+
{
|
|
96424
|
+
name: "set_verify_mode",
|
|
96425
|
+
title: "\uC810\uAC80 \uBAA8\uB4DC \uC124\uC815 (\uAE30\uBCF8/\uC5C4\uACA9)",
|
|
96426
|
+
description: '\uC800\uC7A5 \uC804 \uC790\uB3D9 \uC810\uAC80\uC758 \uC5C4\uACA9\uB3C4\uB97C \uBC14\uAFC9\uB2C8\uB2E4. \uC5C4\uACA9 \uBAA8\uB4DC(strict=true)\uB97C \uCF1C\uBA74, \uC774\uD6C4 \uBAA8\uB4E0 \uC790\uC18C\uC11C \uC800\uC7A5\uC5D0\uC11C "\uC774\uB825\uC11C \uBCF8\uBB38\uC5D0 \uADFC\uAC70 \uC5C6\uB294 \uC218\uCE58"(\uAD6C\uC870\uD654 \uACBD\uB825 \uD56D\uBAA9\uC5D0\uB9CC \uC788\uB294 \uAC12)\uAE4C\uC9C0 \uCC28\uB2E8\uD569\uB2C8\uB2E4. \uAE30\uBCF8 \uBAA8\uB4DC\uB294 \uCD9C\uCC98\uAC00 \uC544\uC608 \uC5C6\uB294 \uC218\uCE58\uB9CC \uCC28\uB2E8\uD569\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 "\uC5C4\uACA9\uD558\uAC8C \uBD10\uC918"\uB77C\uACE0 \uD558\uBA74 \uBC14\uB85C \uCF1C\uC9C0 \uB9D0\uACE0 "\uC55E\uC73C\uB85C \uD56D\uC0C1 \uC5C4\uACA9\uD558\uAC8C \uBCFC\uAE4C\uC694?"\uB77C\uACE0 \uBA3C\uC800 \uD655\uC778\uD55C \uB4A4 \uB3D9\uC758\uD558\uBA74 strict=true\uB85C \uD638\uCD9C\uD558\uC138\uC694. \uB044\uAE30(strict=false)\uB294 \uC548\uC804\uC7A5\uCE58\uB97C \uC57D\uD558\uAC8C \uB9CC\uB4DC\uB294 \uAC83\uC774\uBBC0\uB85C, \uC0AC\uC6A9\uC790\uAC00 \uBA85\uD655\uD788 \uB044\uACA0\uB2E4\uACE0 \uD560 \uB54C\uB9CC \uD638\uCD9C\uD558\uC138\uC694. \uC774\uBC88 \uD55C \uAC74\uB9CC \uC5C4\uACA9\uD558\uAC8C \uBCF4\uB824\uBA74 \uBAA8\uB4DC\uB97C \uBC14\uAFB8\uC9C0 \uB9D0\uACE0 validate_cover_letter / save_cover_letter_version\uC5D0 strict:true\uB97C \uD55C \uBC88\uB9CC \uB118\uAE30\uC138\uC694.',
|
|
96427
|
+
inputSchema: {
|
|
96428
|
+
strict: external_exports.boolean().describe("true=\uC5C4\uACA9 \uCF1C\uAE30, false=\uB044\uAE30(\uBA85\uD655\uD55C \uC0AC\uC6A9\uC790 \uC758\uC0AC\uC77C \uB54C\uB9CC)")
|
|
96429
|
+
},
|
|
96430
|
+
handler: (args) => {
|
|
96431
|
+
const on = setVerifyStrict(!!args?.strict);
|
|
96432
|
+
return ok(
|
|
96433
|
+
on ? '\uC5C4\uACA9 \uC810\uAC80 \uBAA8\uB4DC\uB97C \uCF30\uC2B5\uB2C8\uB2E4. \uC774\uC81C \uC790\uC18C\uC11C\uB97C \uC800\uC7A5\uD560 \uB54C, \uC62C\uB9B0 \uC774\uB825\uC11C \uBCF8\uBB38\uC5D0 \uADFC\uAC70\uAC00 \uC5C6\uB294 \uC218\uCE58\uB294 \uB9C9\uD799\uB2C8\uB2E4(\uAD6C\uC870\uD654 \uACBD\uB825 \uD56D\uBAA9\uC5D0\uB9CC \uC788\uB294 \uAC12 \uD3EC\uD568). \uB044\uB824\uBA74 "\uC5C4\uACA9 \uBAA8\uB4DC \uAEBC\uC918"\uB77C\uACE0 \uD558\uC138\uC694.' : "\uC5C4\uACA9 \uC810\uAC80 \uBAA8\uB4DC\uB97C \uAED0\uC2B5\uB2C8\uB2E4. \uC774\uC81C \uCD9C\uCC98\uAC00 \uC544\uC608 \uC5C6\uB294 \uC218\uCE58\uB9CC \uCC28\uB2E8\uD569\uB2C8\uB2E4(\uAD6C\uC870\uD654 \uD56D\uBAA9\uC5D0\uB9CC \uC788\uB294 \uAC12\uC740 \uACBD\uACE0\uB9CC).",
|
|
96434
|
+
{ strict: on }
|
|
96435
|
+
);
|
|
96436
|
+
}
|
|
96437
|
+
},
|
|
96028
96438
|
/* --------------------------------------------------- writing style guide */
|
|
96029
96439
|
{
|
|
96030
96440
|
name: "get_writing_style_guide",
|
package/dist/migrate.mjs
CHANGED
|
@@ -4568,7 +4568,9 @@ var CoverLetterVersionInputSchema = external_exports.object({
|
|
|
4568
4568
|
content: reqBody,
|
|
4569
4569
|
note: optNote.describe("\uC774 \uBC84\uC804\uC758 \uBCC0\uACBD \uC694\uC57D (\uC608: \uC9C0\uC6D0\uB3D9\uAE30 \uBCF4\uAC15)"),
|
|
4570
4570
|
source: external_exports.enum(CONTENT_SOURCES).optional(),
|
|
4571
|
-
set_current: external_exports.boolean().optional().describe("\uC774 \uBC84\uC804\uC744 \uD604\uC7AC \uBC84\uC804\uC73C\uB85C \uC9C0\uC815 (\uAE30\uBCF8 true)")
|
|
4571
|
+
set_current: external_exports.boolean().optional().describe("\uC774 \uBC84\uC804\uC744 \uD604\uC7AC \uBC84\uC804\uC73C\uB85C \uC9C0\uC815 (\uAE30\uBCF8 true)"),
|
|
4572
|
+
force: external_exports.boolean().optional().describe("\uC800\uC7A5 \uC804 \uC790\uB3D9 \uC810\uAC80(\uADFC\uAC70 \uC5C6\uB294 \uC218\uCE58)\uC5D0\uC11C \uB9C9\uD614\uC744 \uB54C, \uC758\uB3C4\uD55C \uAC12\uC784\uC744 \uD655\uC778\uD558\uACE0 \uADF8\uB300\uB85C \uC800\uC7A5"),
|
|
4573
|
+
strict: external_exports.boolean().optional().describe("\uC774\uBC88 \uC800\uC7A5\uB9CC \uC5C4\uACA9\uD558\uAC8C \uC810\uAC80(\uC774\uB825\uC11C \uBCF8\uBB38\uC5D0 \uADFC\uAC70 \uC5C6\uB294 \uC218\uCE58\uB3C4 \uCC28\uB2E8). \uC0DD\uB7B5\uD558\uBA74 \uC800\uC7A5\uB41C \uBAA8\uB4DC \uC124\uC815\uC744 \uB530\uB984")
|
|
4572
4574
|
});
|
|
4573
4575
|
var CoverLetterVersionRecordSchema = external_exports.object({
|
|
4574
4576
|
id,
|
package/dist/public/index.html
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
<div class="sidebar__foot">
|
|
29
29
|
<div><span class="dot"></span><span id="foot-status">로컬에서 실행 중</span></div>
|
|
30
30
|
<div id="foot-path" class="truncate" style="margin-top:4px"></div>
|
|
31
|
+
<a class="sidebar__foot-link" href="https://github.com/osntak/careermate" target="_blank" rel="noopener" title="GitHub에서 소스 보기"><svg viewBox="0 0 16 16" width="13" height="13" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg><span>GitHub</span></a>
|
|
31
32
|
</div>
|
|
32
33
|
</aside>
|
|
33
34
|
|
|
@@ -19,6 +19,7 @@ export async function render(ctx) {
|
|
|
19
19
|
DataLocation(info),
|
|
20
20
|
Connection(info),
|
|
21
21
|
Theme(),
|
|
22
|
+
VerifyMode(info),
|
|
22
23
|
MyData(info.counts),
|
|
23
24
|
Backup(backups, () => render(ctx)),
|
|
24
25
|
DangerZone(ctx),
|
|
@@ -135,6 +136,50 @@ function Theme() {
|
|
|
135
136
|
});
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
/* ------------------------------------------------- 자소서 수치 점검 모드 */
|
|
140
|
+
function VerifyMode(info) {
|
|
141
|
+
const options = [
|
|
142
|
+
{ value: false, label: '기본', desc: '저장된 경력·이력서·프로젝트에 아예 근거가 없는 수치만 차단' },
|
|
143
|
+
{ value: true, label: '엄격', desc: '이력서 본문에 없고 구조화 경력/프로젝트 항목에만 있는 수치까지 차단' },
|
|
144
|
+
];
|
|
145
|
+
let current = !!info.verify_strict;
|
|
146
|
+
const row = el('div', { class: 'flex gap-2 wrap' });
|
|
147
|
+
|
|
148
|
+
async function set(value) {
|
|
149
|
+
if (value === current) return;
|
|
150
|
+
try {
|
|
151
|
+
const res = await post('/api/settings/verify-mode', { strict: value });
|
|
152
|
+
current = !!res.verify_strict;
|
|
153
|
+
toastOk(current ? '엄격 모드를 켰어요.' : '엄격 모드를 껐어요.');
|
|
154
|
+
paint();
|
|
155
|
+
} catch (e) {
|
|
156
|
+
toastError(e);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function paint() {
|
|
161
|
+
mount(row, ...options.map((o) => {
|
|
162
|
+
const b = Btn(o.label, { variant: 'ghost', onClick: () => set(o.value) });
|
|
163
|
+
b.setAttribute('aria-pressed', String(o.value === current));
|
|
164
|
+
if (o.value === current) b.classList.add('is-selected');
|
|
165
|
+
return b;
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
paint();
|
|
169
|
+
|
|
170
|
+
const note = el('div', { class: 'muted text-sm', style: { lineHeight: '1.7', marginTop: '10px' } },
|
|
171
|
+
el('div', {}, el('span', { class: 'strong' }, '기본'), ' — ', options[0].desc),
|
|
172
|
+
el('div', {}, el('span', { class: 'strong' }, '엄격'), ' — ', options[1].desc),
|
|
173
|
+
el('div', { style: { marginTop: '6px' } },
|
|
174
|
+
'엄격 모드에선 올린 이력서에 없는 수치가 막힐 수 있어요. 막히면 이력서를 올리거나 모드를 끄세요. AI에게 "엄격하게 봐줘"라고 해도 켤 수 있습니다.'));
|
|
175
|
+
|
|
176
|
+
return Card({
|
|
177
|
+
title: '자소서 수치 점검',
|
|
178
|
+
sub: 'AI가 쓴 수치가 내 실제 데이터에 근거가 있는지 저장 전에 자동으로 확인합니다',
|
|
179
|
+
body: el('div', { class: 'stack-2' }, row, note),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
138
183
|
/* ---------------------------------------------------------- 내 데이터 */
|
|
139
184
|
function MyData(counts = {}) {
|
|
140
185
|
const items = Object.entries(COUNT_LABELS).map(([key, label]) =>
|
package/dist/public/styles.css
CHANGED
|
@@ -145,6 +145,8 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
|
145
145
|
.nav__item.is-active .nav__badge { background: var(--accent); color: var(--text-on-accent); }
|
|
146
146
|
.sidebar__foot { padding: 12px 16px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-tertiary); }
|
|
147
147
|
.sidebar__foot .dot { display:inline-block; width:7px; height:7px; border-radius:999px; background: var(--green); margin-right:6px; }
|
|
148
|
+
.sidebar__foot-link { display:inline-flex; align-items:center; gap:6px; margin-top:8px; color: var(--text-tertiary); text-decoration:none; }
|
|
149
|
+
.sidebar__foot-link:hover { color: var(--text-secondary); }
|
|
148
150
|
|
|
149
151
|
.main { display: flex; flex-direction: column; min-width: 0; }
|
|
150
152
|
.topbar {
|