job_ops-mcp 0.3.0
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/.env.example +33 -0
- package/LICENSE +21 -0
- package/README.md +400 -0
- package/config/profile.example.yml +67 -0
- package/cv.example.md +53 -0
- package/dist/cli.js +385 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.js +63 -0
- package/dist/config.js.map +1 -0
- package/dist/core/browser.js +27 -0
- package/dist/core/browser.js.map +1 -0
- package/dist/core/content_hash.js +11 -0
- package/dist/core/content_hash.js.map +1 -0
- package/dist/core/csv.js +107 -0
- package/dist/core/csv.js.map +1 -0
- package/dist/core/cv_parse.js +201 -0
- package/dist/core/cv_parse.js.map +1 -0
- package/dist/core/html.js +10 -0
- package/dist/core/html.js.map +1 -0
- package/dist/core/jd_normalize.js +99 -0
- package/dist/core/jd_normalize.js.map +1 -0
- package/dist/core/jobs.js +106 -0
- package/dist/core/jobs.js.map +1 -0
- package/dist/core/llm.js +227 -0
- package/dist/core/llm.js.map +1 -0
- package/dist/core/modes.js +55 -0
- package/dist/core/modes.js.map +1 -0
- package/dist/core/outreach_safety.js +77 -0
- package/dist/core/outreach_safety.js.map +1 -0
- package/dist/core/profile.js +88 -0
- package/dist/core/profile.js.map +1 -0
- package/dist/core/providers/amazon.js +36 -0
- package/dist/core/providers/amazon.js.map +1 -0
- package/dist/core/providers/ashby.js +31 -0
- package/dist/core/providers/ashby.js.map +1 -0
- package/dist/core/providers/google.js +46 -0
- package/dist/core/providers/google.js.map +1 -0
- package/dist/core/providers/greenhouse.js +55 -0
- package/dist/core/providers/greenhouse.js.map +1 -0
- package/dist/core/providers/http.js +36 -0
- package/dist/core/providers/http.js.map +1 -0
- package/dist/core/providers/index.js +53 -0
- package/dist/core/providers/index.js.map +1 -0
- package/dist/core/providers/lever.js +32 -0
- package/dist/core/providers/lever.js.map +1 -0
- package/dist/core/providers/playwright_generic.js +53 -0
- package/dist/core/providers/playwright_generic.js.map +1 -0
- package/dist/core/providers/types.js +2 -0
- package/dist/core/providers/types.js.map +1 -0
- package/dist/core/providers/workday.js +44 -0
- package/dist/core/providers/workday.js.map +1 -0
- package/dist/core/render.js +253 -0
- package/dist/core/render.js.map +1 -0
- package/dist/core/reports.js +257 -0
- package/dist/core/reports.js.map +1 -0
- package/dist/core/resources.js +40 -0
- package/dist/core/resources.js.map +1 -0
- package/dist/core/scan_engine.js +164 -0
- package/dist/core/scan_engine.js.map +1 -0
- package/dist/core/scheduler.js +117 -0
- package/dist/core/scheduler.js.map +1 -0
- package/dist/db.js +60 -0
- package/dist/db.js.map +1 -0
- package/dist/http/app.js +35 -0
- package/dist/http/app.js.map +1 -0
- package/dist/http/dashboard.js +131 -0
- package/dist/http/dashboard.js.map +1 -0
- package/dist/mcp/define.js +35 -0
- package/dist/mcp/define.js.map +1 -0
- package/dist/mcp/server.js +103 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/apply_prefill.js +167 -0
- package/dist/mcp/tools/apply_prefill.js.map +1 -0
- package/dist/mcp/tools/batch_evaluate.js +143 -0
- package/dist/mcp/tools/batch_evaluate.js.map +1 -0
- package/dist/mcp/tools/evaluate_job.js +181 -0
- package/dist/mcp/tools/evaluate_job.js.map +1 -0
- package/dist/mcp/tools/generate_materials.js +126 -0
- package/dist/mcp/tools/generate_materials.js.map +1 -0
- package/dist/mcp/tools/get_report.js +24 -0
- package/dist/mcp/tools/get_report.js.map +1 -0
- package/dist/mcp/tools/ops.js +321 -0
- package/dist/mcp/tools/ops.js.map +1 -0
- package/dist/mcp/tools/outreach.js +481 -0
- package/dist/mcp/tools/outreach.js.map +1 -0
- package/dist/mcp/tools/render_pdf.js +27 -0
- package/dist/mcp/tools/render_pdf.js.map +1 -0
- package/dist/mcp/tools/scan_portals.js +35 -0
- package/dist/mcp/tools/scan_portals.js.map +1 -0
- package/dist/mcp/tools/scheduler.js +32 -0
- package/dist/mcp/tools/scheduler.js.map +1 -0
- package/dist/mcp/tools/stories.js +172 -0
- package/dist/mcp/tools/stories.js.map +1 -0
- package/dist/mcp/tools/tracker.js +183 -0
- package/dist/mcp/tools/tracker.js.map +1 -0
- package/dist/mcp/tools/visa.js +219 -0
- package/dist/mcp/tools/visa.js.map +1 -0
- package/dist/migrations/001_initial.sql +505 -0
- package/dist/migrations/002_llm_and_digest.sql +42 -0
- package/dist/server.js +55 -0
- package/dist/server.js.map +1 -0
- package/fonts/dm-sans-latin-ext.woff2 +0 -0
- package/fonts/dm-sans-latin.woff2 +0 -0
- package/fonts/space-grotesk-latin-ext.woff2 +0 -0
- package/fonts/space-grotesk-latin.woff2 +0 -0
- package/modes/career_packet.md +91 -0
- package/modes/negotiation_playbook.md +64 -0
- package/modes/outreach_tone.md +80 -0
- package/modes/report_format.md +83 -0
- package/modes/rubric.md +119 -0
- package/modes/tailoring_rules.md +102 -0
- package/package.json +67 -0
- package/portals.example.yml +95 -0
- package/templates/cover-template.html +64 -0
- package/templates/cv-template.html +421 -0
- package/templates/cv-template.tex +123 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// batch_evaluate — api path. Rates all unrated (score_total IS NULL) jobs through the
|
|
2
|
+
// configured LLM with the rubric. Strict JSON parse with PARSE_ERROR fallback: failed
|
|
3
|
+
// jobs stay unrated and their score_detail records the parse_error + raw — never silent
|
|
4
|
+
// zeros (study guide §4.3 gotcha).
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { config } from '../../config.js';
|
|
7
|
+
import { getDb, runInWriteLock } from '../../db.js';
|
|
8
|
+
import { defineTool, okResult, errResult } from '../define.js';
|
|
9
|
+
import { chatLogged, llmAvailable } from '../../core/llm.js';
|
|
10
|
+
import { getMode } from '../../core/modes.js';
|
|
11
|
+
import { combineNoVisa } from '../../core/reports.js';
|
|
12
|
+
const ROLE_CATEGORIES = ['pm', 'ml_eng', 'data_eng', 'analytics_eng', 'swe', 'forward_deployed', 'other'];
|
|
13
|
+
export const batchEvaluateTool = defineTool({
|
|
14
|
+
name: 'batch_evaluate',
|
|
15
|
+
title: 'Batch-rate unrated jobs via LLM',
|
|
16
|
+
description: 'api path. Selects unrated jobs (score_total IS NULL) matching the optional filter and rates each via the configured ' +
|
|
17
|
+
'LLM (Gemini default / DeepSeek). Returns an A-F tier distribution and any parse-error count. Never produces silent zeros.',
|
|
18
|
+
inputSchema: {
|
|
19
|
+
role_category: z.enum(ROLE_CATEGORIES).optional(),
|
|
20
|
+
company: z.string().optional().describe('Substring match on company name.'),
|
|
21
|
+
limit: z.number().int().min(1).max(500).default(50),
|
|
22
|
+
concurrency: z.number().int().min(1).max(8).default(2),
|
|
23
|
+
},
|
|
24
|
+
handler: async (args) => {
|
|
25
|
+
if (!llmAvailable()) {
|
|
26
|
+
return errResult('No LLM provider configured. Set MCP_JSA_LLM_PROVIDER=gemini and GEMINI_API_KEY, or use mode=chat on evaluate_job for the manual path.');
|
|
27
|
+
}
|
|
28
|
+
const rubric = getMode('rubric.md');
|
|
29
|
+
if (!rubric || rubric.startsWith('_missing'))
|
|
30
|
+
return errResult('modes/rubric.md missing');
|
|
31
|
+
// Pick jobs.
|
|
32
|
+
const where = ['j.score_total IS NULL', `j.status NOT IN ('rejected','discarded','skip')`];
|
|
33
|
+
const params = [];
|
|
34
|
+
if (args.role_category) {
|
|
35
|
+
where.push('j.role_category = ?');
|
|
36
|
+
params.push(args.role_category);
|
|
37
|
+
}
|
|
38
|
+
if (args.company) {
|
|
39
|
+
where.push('LOWER(COALESCE(c.name, j.company_name_raw)) LIKE ?');
|
|
40
|
+
params.push(`%${args.company.toLowerCase()}%`);
|
|
41
|
+
}
|
|
42
|
+
const rows = getDb().prepare(`
|
|
43
|
+
SELECT j.id, j.title, COALESCE(c.name, j.company_name_raw) AS company_name,
|
|
44
|
+
j.location_raw AS location, j.description
|
|
45
|
+
FROM jobs j LEFT JOIN companies c ON c.id = j.company_id
|
|
46
|
+
WHERE ${where.join(' AND ')}
|
|
47
|
+
ORDER BY datetime(j.discovered_at) DESC LIMIT ?
|
|
48
|
+
`).all(...params, args.limit);
|
|
49
|
+
if (!rows.length)
|
|
50
|
+
return okResult({ rated: 0, distribution: emptyDist(), parse_errors: 0, items: [] });
|
|
51
|
+
const distribution = emptyDist();
|
|
52
|
+
let parseErrors = 0;
|
|
53
|
+
const items = [];
|
|
54
|
+
// Bounded-concurrency loop without an extra dep.
|
|
55
|
+
const queue = rows.slice();
|
|
56
|
+
const workers = Array.from({ length: Math.min(args.concurrency, queue.length) }, async () => {
|
|
57
|
+
while (queue.length) {
|
|
58
|
+
const job = queue.shift();
|
|
59
|
+
try {
|
|
60
|
+
const res = await rateOne(job, rubric);
|
|
61
|
+
tierBump(distribution, res?.score_total);
|
|
62
|
+
if (!res?.parsed)
|
|
63
|
+
parseErrors++;
|
|
64
|
+
items.push({ job_id: job.id, title: job.title, company: job.company_name,
|
|
65
|
+
score_total: res?.score_total ?? null, role_category: res?.role_category ?? null,
|
|
66
|
+
parse_ok: !!res?.parsed });
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
parseErrors++;
|
|
70
|
+
items.push({ job_id: job.id, title: job.title, company: job.company_name, error: e?.message ?? String(e) });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
await Promise.all(workers);
|
|
75
|
+
return okResult({
|
|
76
|
+
rated: items.length - parseErrors,
|
|
77
|
+
parse_errors: parseErrors,
|
|
78
|
+
distribution,
|
|
79
|
+
items: items.slice().sort((a, b) => (b.score_total ?? -1) - (a.score_total ?? -1)),
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
async function rateOne(job, rubric) {
|
|
84
|
+
const system = rubric +
|
|
85
|
+
'\n\n== INSTRUCTIONS ==\nScore the JD below per the rubric. Output ONLY the strict JSON object specified in "Output contract (chat mode)". No prose outside JSON.';
|
|
86
|
+
const user = `JOB title: ${job.title}\nCompany: ${job.company_name}\nLocation: ${job.location ?? ''}\n\nDESCRIPTION:\n${(job.description ?? '').slice(0, 8000)}`;
|
|
87
|
+
const call = await chatLogged('batch_evaluate', [
|
|
88
|
+
{ role: 'system', content: system }, { role: 'user', content: user },
|
|
89
|
+
], { responseFormat: 'json_object', temperature: 0.2, jobId: job.id });
|
|
90
|
+
if (!call.parseOk) {
|
|
91
|
+
// Persist parse error — keep score_total NULL so it stays in unrated bucket for retry.
|
|
92
|
+
await runInWriteLock(() => {
|
|
93
|
+
getDb().prepare(`
|
|
94
|
+
UPDATE jobs SET score_detail = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
|
|
95
|
+
`).run(JSON.stringify({ parse_error: call.parseError, raw: call.text.slice(0, 800), mode: 'api', llm_call_id: call.id }), job.id);
|
|
96
|
+
});
|
|
97
|
+
return { parsed: null, score_total: null };
|
|
98
|
+
}
|
|
99
|
+
const p = call.parsed;
|
|
100
|
+
const resumeFit = clampInt(p.resume_fit);
|
|
101
|
+
const tasteFit = clampInt(p.taste_fit);
|
|
102
|
+
const visaFit = config.visaScoringEnabled ? clampInt(p.visa_fit) : null;
|
|
103
|
+
const score = config.visaScoringEnabled
|
|
104
|
+
? clampInt(p.score_total ?? null)
|
|
105
|
+
: combineNoVisa(resumeFit, tasteFit);
|
|
106
|
+
await runInWriteLock(() => {
|
|
107
|
+
getDb().prepare(`
|
|
108
|
+
UPDATE jobs SET
|
|
109
|
+
score_total = ?, score_resume_fit = ?, score_taste_fit = ?, score_visa_fit = ?,
|
|
110
|
+
role_category = COALESCE(?, role_category),
|
|
111
|
+
seniority = COALESCE(?, seniority),
|
|
112
|
+
score_detail = ?, scored_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
|
|
113
|
+
WHERE id = ?
|
|
114
|
+
`).run(score, resumeFit, tasteFit, visaFit, p.role_category ?? null, p.seniority ?? null, JSON.stringify({ ...p, mode: 'api', llm_call_id: call.id, visa_scoring_enabled: config.visaScoringEnabled }), job.id);
|
|
115
|
+
});
|
|
116
|
+
return { parsed: p, score_total: score, role_category: p.role_category };
|
|
117
|
+
}
|
|
118
|
+
function clampInt(n) {
|
|
119
|
+
if (n === null || n === undefined)
|
|
120
|
+
return null;
|
|
121
|
+
const x = Number(n);
|
|
122
|
+
if (!Number.isFinite(x))
|
|
123
|
+
return null;
|
|
124
|
+
return Math.max(0, Math.min(100, Math.round(x)));
|
|
125
|
+
}
|
|
126
|
+
function emptyDist() { return { A: 0, B: 0, C: 0, D: 0, F: 0, unrated: 0 }; }
|
|
127
|
+
function tierBump(d, score) {
|
|
128
|
+
if (score == null) {
|
|
129
|
+
d.unrated++;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (score >= 85)
|
|
133
|
+
d.A++;
|
|
134
|
+
else if (score >= 75)
|
|
135
|
+
d.B++;
|
|
136
|
+
else if (score >= 60)
|
|
137
|
+
d.C++;
|
|
138
|
+
else if (score >= 40)
|
|
139
|
+
d.D++;
|
|
140
|
+
else
|
|
141
|
+
d.F++;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=batch_evaluate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch_evaluate.js","sourceRoot":"","sources":["../../../src/mcp/tools/batch_evaluate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// evaluate_job — two-step chat-mode evaluator.
|
|
2
|
+
//
|
|
3
|
+
// Step 1: caller passes `input` (URL or pasted JD). Server normalizes, persists a job row,
|
|
4
|
+
// returns { job_id, normalized_jd, rubric, report_format, career_packet } so the
|
|
5
|
+
// chat client can score + draft the 6-block report.
|
|
6
|
+
// Step 2: caller calls evaluate_job again with the same job_id + a `report` payload + scores.
|
|
7
|
+
// Server persists the eval_report, updates job scores, returns the report link.
|
|
8
|
+
//
|
|
9
|
+
// api mode: requires GEMINI_API_KEY or DEEPSEEK_API_KEY. Runs both scoring + the 6-block
|
|
10
|
+
// report through the LLM with the rubric/report_format as system messages. Strict JSON
|
|
11
|
+
// parsing — never silent zeros (see core/llm.ts parseJsonStrict).
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { config } from '../../config.js';
|
|
14
|
+
import { adoptJobFromJD, getJob } from '../../core/jobs.js';
|
|
15
|
+
import { normalizeJD } from '../../core/jd_normalize.js';
|
|
16
|
+
import { saveReport } from '../../core/reports.js';
|
|
17
|
+
import { getActiveCareerPacket } from '../../core/profile.js';
|
|
18
|
+
import { chatLogged } from '../../core/llm.js';
|
|
19
|
+
import { getMode } from '../../core/modes.js';
|
|
20
|
+
import { defineTool, okResult, errResult } from '../define.js';
|
|
21
|
+
const reportBlocks = {
|
|
22
|
+
archetype_detected: z.string().nullish(),
|
|
23
|
+
block_role_summary: z.string().nullish(),
|
|
24
|
+
block_cv_match: z.string().nullish(),
|
|
25
|
+
block_level: z.string().nullish(),
|
|
26
|
+
block_comp: z.string().nullish(),
|
|
27
|
+
block_personalize: z.string().nullish(),
|
|
28
|
+
block_interview: z.string().nullish(),
|
|
29
|
+
block_legitimacy: z.string().nullish(),
|
|
30
|
+
keywords: z.array(z.string()).nullish(),
|
|
31
|
+
};
|
|
32
|
+
const scores = {
|
|
33
|
+
resume_fit: z.number().int().min(0).max(100).optional(),
|
|
34
|
+
taste_fit: z.number().int().min(0).max(100).optional(),
|
|
35
|
+
visa_fit: z.number().int().min(0).max(100).optional(),
|
|
36
|
+
score_total: z.number().int().min(0).max(100).optional(),
|
|
37
|
+
reasoning: z.string().optional(),
|
|
38
|
+
concerns: z.string().nullish(),
|
|
39
|
+
role_category: z.string().optional(),
|
|
40
|
+
seniority: z.string().optional(),
|
|
41
|
+
};
|
|
42
|
+
export const evaluateJobTool = defineTool({
|
|
43
|
+
name: 'evaluate_job',
|
|
44
|
+
title: 'Evaluate a job',
|
|
45
|
+
description: 'Two-step JD evaluator. Step 1: pass `input` (URL or pasted JD) to get rubric + normalized JD. ' +
|
|
46
|
+
'Step 2: pass `job_id` + `report` + `scores` to persist the 6-block report and return its localhost link. ' +
|
|
47
|
+
'mode="api" runs both steps server-side via the configured LLM.',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
input: z.string().min(1).optional()
|
|
50
|
+
.describe('A URL OR pasted JD text. Required on step 1.'),
|
|
51
|
+
job_id: z.string().optional()
|
|
52
|
+
.describe('Reuse an existing job row instead of adopting a new one. Set this on step 2.'),
|
|
53
|
+
mode: z.enum(['chat', 'api']).default('chat'),
|
|
54
|
+
title: z.string().optional().describe('Optional title override.'),
|
|
55
|
+
company: z.string().optional().describe('Optional company override.'),
|
|
56
|
+
location: z.string().optional(),
|
|
57
|
+
report: z.object(reportBlocks).optional()
|
|
58
|
+
.describe('Filled by chat on step 2 — the 6-block A–F (+G) markdown blocks.'),
|
|
59
|
+
scores: z.object(scores).strict().optional()
|
|
60
|
+
.describe('Filled by chat on step 2 — strict JSON scores per modes/rubric.md.'),
|
|
61
|
+
},
|
|
62
|
+
handler: async (args) => {
|
|
63
|
+
const mode = args.mode ?? 'chat';
|
|
64
|
+
const isFinalize = !!(args.report || args.scores);
|
|
65
|
+
// ── Step 2 (chat OR api) — finalize ────────────────────────────────────
|
|
66
|
+
if (isFinalize) {
|
|
67
|
+
if (!args.job_id)
|
|
68
|
+
return errResult('Finalize call requires job_id from the step-1 response.');
|
|
69
|
+
const job = getJob(args.job_id);
|
|
70
|
+
if (!job)
|
|
71
|
+
return errResult(`No job with id ${args.job_id}`);
|
|
72
|
+
const saved = await saveReport({
|
|
73
|
+
job_id: job.id,
|
|
74
|
+
mode,
|
|
75
|
+
raw_input: job.description ?? '',
|
|
76
|
+
blocks: normalizeBlocks(args.report),
|
|
77
|
+
scores: args.scores ?? {},
|
|
78
|
+
});
|
|
79
|
+
return okResult({
|
|
80
|
+
step: 'finalized',
|
|
81
|
+
job_id: job.id,
|
|
82
|
+
report_id: saved.id,
|
|
83
|
+
report_url: saved.url,
|
|
84
|
+
tracker_url: `${config.baseUrl}/`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// ── Step 1 ──────────────────────────────────────────────────────────────
|
|
88
|
+
if (!args.input)
|
|
89
|
+
return errResult('First call requires `input` (URL or pasted JD).');
|
|
90
|
+
const jd = await normalizeJD(args.input);
|
|
91
|
+
const adopted = await adoptJobFromJD({
|
|
92
|
+
jd, title: args.title, company: args.company, location: args.location,
|
|
93
|
+
});
|
|
94
|
+
if (mode === 'chat') {
|
|
95
|
+
return okResult({
|
|
96
|
+
step: 'prepared',
|
|
97
|
+
job_id: adopted.id,
|
|
98
|
+
job_created: adopted.created,
|
|
99
|
+
normalized_jd: {
|
|
100
|
+
source: jd.source,
|
|
101
|
+
source_url: jd.source_url,
|
|
102
|
+
title_guess: jd.title_guess,
|
|
103
|
+
company_guess: jd.company_guess,
|
|
104
|
+
char_count: jd.text.length,
|
|
105
|
+
text: jd.text,
|
|
106
|
+
},
|
|
107
|
+
instructions: 'Read the three resources below, score the JD per modes/rubric.md (STRICT JSON), ' +
|
|
108
|
+
'draft the 6-block A–F (+G) report per modes/report_format.md, then CALL evaluate_job ' +
|
|
109
|
+
'AGAIN with { job_id, report, scores } to persist and get the report link.',
|
|
110
|
+
rubric: getMode('rubric.md'),
|
|
111
|
+
report_format: getMode('report_format.md'),
|
|
112
|
+
career_packet: getActiveCareerPacket()?.content ?? '_no active career packet_',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// ── Step 1 (api mode) — run scoring + report inline ─────────────────────
|
|
116
|
+
try {
|
|
117
|
+
const packet = getActiveCareerPacket()?.content ?? '';
|
|
118
|
+
const userMsg = `== JOB (title=${jd.title_guess ?? ''}, company=${jd.company_guess ?? ''}) ==\n${jd.text}`;
|
|
119
|
+
const scoringSystem = getMode('rubric.md') +
|
|
120
|
+
'\n\n== INSTRUCTIONS ==\n' +
|
|
121
|
+
'Score the JD below per the rubric. Output ONLY the strict JSON object specified in ' +
|
|
122
|
+
'"Output contract (chat mode)". No prose outside JSON.';
|
|
123
|
+
const blocksSystem = getMode('report_format.md') +
|
|
124
|
+
'\n\n== CAREER PACKET ==\n' + packet +
|
|
125
|
+
'\n\n== INSTRUCTIONS ==\n' +
|
|
126
|
+
'Draft the 6-block A–F (+G optional) report. Output STRICT JSON only with keys: ' +
|
|
127
|
+
'archetype_detected, block_role_summary, block_cv_match, block_level, block_comp, ' +
|
|
128
|
+
'block_personalize, block_interview, block_legitimacy, keywords (array of strings).';
|
|
129
|
+
// Scoring + blocks are independent given the same JD — run in parallel.
|
|
130
|
+
const [scoreCall, blocksCall] = await Promise.all([
|
|
131
|
+
chatLogged('evaluate_job.api.scores', [
|
|
132
|
+
{ role: 'system', content: scoringSystem }, { role: 'user', content: userMsg },
|
|
133
|
+
], { responseFormat: 'json_object', temperature: 0.2, jobId: adopted.id }),
|
|
134
|
+
chatLogged('evaluate_job.api.blocks', [
|
|
135
|
+
{ role: 'system', content: blocksSystem }, { role: 'user', content: userMsg },
|
|
136
|
+
], { responseFormat: 'json_object', temperature: 0.3, maxTokens: 6000, jobId: adopted.id }),
|
|
137
|
+
]);
|
|
138
|
+
const scoreData = (scoreCall.parsed ?? null);
|
|
139
|
+
// Save — even on parse failure, persist what we have. NEVER silent zeros.
|
|
140
|
+
const saved = await saveReport({
|
|
141
|
+
job_id: adopted.id, mode: 'api',
|
|
142
|
+
raw_input: jd.text,
|
|
143
|
+
blocks: normalizeBlocks(blocksCall.parsed),
|
|
144
|
+
// Only attach scores if parsing succeeded — else leave NULL.
|
|
145
|
+
scores: scoreCall.parseOk && scoreData ? scoreData : undefined,
|
|
146
|
+
});
|
|
147
|
+
return okResult({
|
|
148
|
+
step: 'finalized',
|
|
149
|
+
mode: 'api',
|
|
150
|
+
job_id: adopted.id,
|
|
151
|
+
report_id: saved.id,
|
|
152
|
+
report_url: saved.url,
|
|
153
|
+
scoring_parse_ok: scoreCall.parseOk,
|
|
154
|
+
blocks_parse_ok: blocksCall.parseOk,
|
|
155
|
+
scoring_parse_error: scoreCall.parseError ?? null,
|
|
156
|
+
blocks_parse_error: blocksCall.parseError ?? null,
|
|
157
|
+
tracker_url: `${config.baseUrl}/`,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
return errResult(`api-mode evaluate_job failed: ${e?.message ?? String(e)}`);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
// Single shape converter — used by both the chat-finalize and api paths so the keys-list
|
|
166
|
+
// stays one source of truth and only ReportBlocks knows what the report shape is.
|
|
167
|
+
function normalizeBlocks(raw) {
|
|
168
|
+
const r = raw ?? {};
|
|
169
|
+
return {
|
|
170
|
+
archetype_detected: r.archetype_detected ?? null,
|
|
171
|
+
block_role_summary: r.block_role_summary ?? null,
|
|
172
|
+
block_cv_match: r.block_cv_match ?? null,
|
|
173
|
+
block_level: r.block_level ?? null,
|
|
174
|
+
block_comp: r.block_comp ?? null,
|
|
175
|
+
block_personalize: r.block_personalize ?? null,
|
|
176
|
+
block_interview: r.block_interview ?? null,
|
|
177
|
+
block_legitimacy: r.block_legitimacy ?? null,
|
|
178
|
+
keywords: Array.isArray(r.keywords) ? r.keywords : null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=evaluate_job.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate_job.js","sourceRoot":"","sources":["../../../src/mcp/tools/evaluate_job.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// generate_materials — picks subsets from career_packet per JD using tailoring_rules.md.
|
|
2
|
+
// Writes tailored_bullets + cover_letter_draft to applications. chat-mode default;
|
|
3
|
+
// api-mode runs the LLM with strict-JSON parse. The brief: "never invent claims not in
|
|
4
|
+
// career_packet" — we enforce this by re-checking the LLM output against the packet for
|
|
5
|
+
// any concrete metrics it cites (best-effort; the chat is the final guard).
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { getDb, runInWriteLock } from '../../db.js';
|
|
9
|
+
import { defineTool, okResult, errResult } from '../define.js';
|
|
10
|
+
import { chatLogged } from '../../core/llm.js';
|
|
11
|
+
import { getActiveCareerPacket } from '../../core/profile.js';
|
|
12
|
+
import { scanForVisaLeakage } from '../../core/outreach_safety.js';
|
|
13
|
+
import { getMode } from '../../core/modes.js';
|
|
14
|
+
// `experience_bullets` is a map keyed by employer/role slug — the slugs come from the
|
|
15
|
+
// user's own career_packet, so the renderer doesn't bake in any specific employer name.
|
|
16
|
+
const materialsShape = z.object({
|
|
17
|
+
tagline: z.string().optional(),
|
|
18
|
+
experience_bullets: z.record(z.string(), z.array(z.string())).optional(),
|
|
19
|
+
projects_section: z.string().optional(),
|
|
20
|
+
skills_section: z.string().optional(),
|
|
21
|
+
cover_letter_body: z.string().optional(),
|
|
22
|
+
tailoring_notes: z.string().optional(),
|
|
23
|
+
});
|
|
24
|
+
export const generateMaterialsTool = defineTool({
|
|
25
|
+
name: 'generate_materials',
|
|
26
|
+
title: 'Generate tailored resume bullets + cover letter',
|
|
27
|
+
description: 'Picks bullets / tagline / cover from the active career_packet per the JD via tailoring_rules. ' +
|
|
28
|
+
'chat-mode default returns context for the chat to draft. api-mode calls the LLM, validates against ' +
|
|
29
|
+
'the visa rail, and writes tailored_bullets + cover_letter_draft to the application row.',
|
|
30
|
+
inputSchema: {
|
|
31
|
+
job_id: z.string().min(1),
|
|
32
|
+
mode: z.enum(['chat', 'api']).default('chat'),
|
|
33
|
+
materials: materialsShape.optional()
|
|
34
|
+
.describe('Provide on a second chat-mode call to persist what the chat drafted.'),
|
|
35
|
+
},
|
|
36
|
+
handler: async (args) => {
|
|
37
|
+
const db = getDb();
|
|
38
|
+
const job = db.prepare(`
|
|
39
|
+
SELECT j.*, COALESCE(c.name, j.company_name_raw) AS company_name
|
|
40
|
+
FROM jobs j LEFT JOIN companies c ON c.id = j.company_id WHERE j.id = ?
|
|
41
|
+
`).get(args.job_id);
|
|
42
|
+
if (!job)
|
|
43
|
+
return errResult(`No job ${args.job_id}`);
|
|
44
|
+
if (args.mode === 'chat' && args.materials) {
|
|
45
|
+
const result = await persistMaterials(args.job_id, args.materials);
|
|
46
|
+
return okResult(result);
|
|
47
|
+
}
|
|
48
|
+
if (args.mode === 'chat') {
|
|
49
|
+
return okResult({
|
|
50
|
+
instructions: 'Read modes/tailoring_rules.md and modes/career_packet.md. Pick subsets per JD. Output STRICT JSON ' +
|
|
51
|
+
'matching the contract in tailoring_rules.md. NEVER invent metrics not in the packet. NEVER mention ' +
|
|
52
|
+
'visa / OPT / sponsorship in any bullet or in the cover letter. Then call generate_materials AGAIN with `materials` to persist.',
|
|
53
|
+
job: { id: job.id, title: job.title, company: job.company_name, location: job.location_raw,
|
|
54
|
+
role_category: job.role_category, seniority: job.seniority,
|
|
55
|
+
description: job.description, requirements: job.requirements },
|
|
56
|
+
tailoring_rules: getMode('tailoring_rules.md'),
|
|
57
|
+
career_packet: getActiveCareerPacket()?.content ?? '',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// api mode
|
|
61
|
+
try {
|
|
62
|
+
const system = getMode('tailoring_rules.md') +
|
|
63
|
+
'\n\n== CAREER PACKET (AUTHORITATIVE — never invent claims outside this) ==\n' +
|
|
64
|
+
(getActiveCareerPacket()?.content ?? '') +
|
|
65
|
+
'\n\n== INSTRUCTIONS ==\nOutput STRICT JSON only. NO LaTeX in cover_letter_body. NO visa / OPT / sponsorship mentions.';
|
|
66
|
+
const user = JSON.stringify({
|
|
67
|
+
title: job.title, company: job.company_name, location: job.location_raw,
|
|
68
|
+
role_category: job.role_category, seniority: job.seniority,
|
|
69
|
+
description: (job.description ?? '').slice(0, 10_000),
|
|
70
|
+
requirements: job.requirements ?? null,
|
|
71
|
+
});
|
|
72
|
+
const call = await chatLogged('generate_materials.api', [
|
|
73
|
+
{ role: 'system', content: system }, { role: 'user', content: user },
|
|
74
|
+
], { responseFormat: 'json_object', temperature: 0.4, maxTokens: 6000, jobId: job.id });
|
|
75
|
+
if (!call.parseOk)
|
|
76
|
+
return errResult(`LLM produced unparseable output: ${call.parseError}`);
|
|
77
|
+
const m = call.parsed;
|
|
78
|
+
const result = await persistMaterials(args.job_id, m);
|
|
79
|
+
return okResult({ ...result, parse_ok: true });
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
return errResult(`api generate_materials failed: ${e?.message ?? String(e)}`);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
async function persistMaterials(job_id, m) {
|
|
87
|
+
// Final visa rail across all generated text.
|
|
88
|
+
const blob = JSON.stringify(m ?? {});
|
|
89
|
+
const leaks = scanForVisaLeakage(blob);
|
|
90
|
+
if (leaks.length) {
|
|
91
|
+
throw new Error(`materials failed visa rail: ${JSON.stringify(leaks)} — not persisted`);
|
|
92
|
+
}
|
|
93
|
+
return runInWriteLock(() => {
|
|
94
|
+
const db = getDb();
|
|
95
|
+
const existing = db.prepare(`SELECT id, materials_v FROM applications WHERE job_id = ?`).get(job_id);
|
|
96
|
+
const tailored = JSON.stringify({
|
|
97
|
+
tagline: m.tagline ?? null,
|
|
98
|
+
experience_bullets: m.experience_bullets ?? null,
|
|
99
|
+
projects_section: m.projects_section ?? null,
|
|
100
|
+
skills_section: m.skills_section ?? null,
|
|
101
|
+
});
|
|
102
|
+
if (existing) {
|
|
103
|
+
const newV = existing.materials_v + 1;
|
|
104
|
+
db.prepare(`
|
|
105
|
+
UPDATE applications SET
|
|
106
|
+
tailored_bullets = ?, cover_letter_draft = ?, tailoring_notes = ?,
|
|
107
|
+
materials_v = ?, status = 'materials_drafted', last_status_change_at = CURRENT_TIMESTAMP,
|
|
108
|
+
updated_at = CURRENT_TIMESTAMP,
|
|
109
|
+
resume_tex = NULL, cover_letter_tex = NULL -- mark for re-render
|
|
110
|
+
WHERE id = ?
|
|
111
|
+
`).run(tailored, m.cover_letter_body ?? null, m.tailoring_notes ?? null, newV, existing.id);
|
|
112
|
+
// Mirror status onto jobs row.
|
|
113
|
+
db.prepare(`UPDATE jobs SET status = 'materials_drafted', materials_generated_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ?`).run(job_id);
|
|
114
|
+
return { application_id: existing.id, job_id, materials_v: newV, status: 'materials_drafted' };
|
|
115
|
+
}
|
|
116
|
+
const appId = randomUUID();
|
|
117
|
+
db.prepare(`
|
|
118
|
+
INSERT INTO applications (
|
|
119
|
+
id, job_id, status, tailored_bullets, cover_letter_draft, tailoring_notes, materials_v
|
|
120
|
+
) VALUES (?, ?, 'materials_drafted', ?, ?, ?, 1)
|
|
121
|
+
`).run(appId, job_id, tailored, m.cover_letter_body ?? null, m.tailoring_notes ?? null);
|
|
122
|
+
db.prepare(`UPDATE jobs SET status = 'materials_drafted', materials_generated_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ?`).run(job_id);
|
|
123
|
+
return { application_id: appId, job_id, materials_v: 1, status: 'materials_drafted' };
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=generate_materials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate_materials.js","sourceRoot":"","sources":["../../../src/mcp/tools/generate_materials.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,mFAAmF;AACnF,uFAAuF;AACvF,wFAAwF;AACxF,4EAA4E;AAE5E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAE9C,sFAAsF;AACtF,wFAAwF;AACxF,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9B,OAAO,EAAa,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC,kBAAkB,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;IACxE,gBAAgB,EAAI,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC,cAAc,EAAM,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC,iBAAiB,EAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC,eAAe,EAAK,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC1C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG,UAAU,CAAC;IAC9C,IAAI,EAAE,oBAAoB;IAC1B,KAAK,EAAE,iDAAiD;IACxD,WAAW,EACT,gGAAgG;QAChG,qGAAqG;QACrG,yFAAyF;IAC3F,WAAW,EAAE;QACX,MAAM,EAAK,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5B,IAAI,EAAO,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;QACjD,SAAS,EAAE,cAAc,CAAC,QAAQ,EAAE;aACvB,QAAQ,CAAC,sEAAsE,CAAC;KAC9F;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACtB,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC;;;KAGtB,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAQ,CAAC;QAC3B,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC,UAAU,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAEpD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YACnE,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;QAED,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACzB,OAAO,QAAQ,CAAC;gBACd,YAAY,EACV,oGAAoG;oBACpG,qGAAqG;oBACrG,gIAAgI;gBAClI,GAAG,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,YAAY,EAAE,QAAQ,EAAE,GAAG,CAAC,YAAY;oBAClF,aAAa,EAAE,GAAG,CAAC,aAAa,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS;oBAC1D,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE;gBACtE,eAAe,EAAE,OAAO,CAAC,oBAAoB,CAAC;gBAC9C,aAAa,EAAI,qBAAqB,EAAE,EAAE,OAAO,IAAI,EAAE;aACxD,CAAC,CAAC;QACL,CAAC;QAED,WAAW;QACX,IAAI,CAAC;YACH,MAAM,MAAM,GACV,OAAO,CAAC,oBAAoB,CAAC;gBAC7B,8EAA8E;gBAC9E,CAAC,qBAAqB,EAAE,EAAE,OAAO,IAAI,EAAE,CAAC;gBACxC,uHAAuH,CAAC;YAC1H,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;gBAC1B,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,YAAY,EAAE,QAAQ,EAAE,GAAG,CAAC,YAAY;gBACvE,aAAa,EAAE,GAAG,CAAC,aAAa,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS;gBAC1D,WAAW,EAAE,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC;gBACrD,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,IAAI;aACvC,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,wBAAwB,EAAE;gBACtD,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;aACrE,EAAE,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;YACxF,IAAI,CAAC,IAAI,CAAC,OAAO;gBAAE,OAAO,SAAS,CAAC,oCAAoC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;YAC3F,MAAM,CAAC,GAAG,IAAI,CAAC,MAAa,CAAC;YAC7B,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YACtD,OAAO,QAAQ,CAAC,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,OAAO,SAAS,CAAC,kCAAkC,CAAC,EAAE,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;CACF,CAAC,CAAC;AAEH,KAAK,UAAU,gBAAgB,CAAC,MAAc,EAAE,CAAM;IACpD,6CAA6C;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,+BAA+B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC1F,CAAC;IACD,OAAO,cAAc,CAAC,GAAG,EAAE;QACzB,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;QACnB,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC,2DAA2D,CAAC,CAAC,GAAG,CAAC,MAAM,CAAoD,CAAC;QACxJ,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;YAC9B,OAAO,EAAa,CAAC,CAAC,OAAO,IAAI,IAAI;YACrC,kBAAkB,EAAE,CAAC,CAAC,kBAAkB,IAAI,IAAI;YAChD,gBAAgB,EAAI,CAAC,CAAC,gBAAgB,IAAI,IAAI;YAC9C,cAAc,EAAM,CAAC,CAAC,cAAc,IAAI,IAAI;SAC7C,CAAC,CAAC;QACH,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,GAAG,CAAC,CAAC;YACtC,EAAE,CAAC,OAAO,CAAC;;;;;;;OAOV,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,iBAAiB,IAAI,IAAI,EAAE,CAAC,CAAC,eAAe,IAAI,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC5F,+BAA+B;YAC/B,EAAE,CAAC,OAAO,CAAC,uIAAuI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAChK,OAAO,EAAE,cAAc,EAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;QACjG,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;QAC3B,EAAE,CAAC,OAAO,CAAC;;;;KAIV,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,iBAAiB,IAAI,IAAI,EAAE,CAAC,CAAC,eAAe,IAAI,IAAI,CAAC,CAAC;QACxF,EAAE,CAAC,OAAO,CAAC,uIAAuI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAChK,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IACxF,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { config } from '../../config.js';
|
|
3
|
+
import { getLatestReport } from '../../core/reports.js';
|
|
4
|
+
import { defineTool, okResult, errResult } from '../define.js';
|
|
5
|
+
export const getReportTool = defineTool({
|
|
6
|
+
name: 'get_report',
|
|
7
|
+
title: 'Get the latest eval report for a job',
|
|
8
|
+
description: 'Returns the localhost HTML report link and metadata for the latest eval_report row for a job_id.',
|
|
9
|
+
inputSchema: { job_id: z.string().min(1) },
|
|
10
|
+
handler: async (args) => {
|
|
11
|
+
const row = getLatestReport(args.job_id);
|
|
12
|
+
if (!row)
|
|
13
|
+
return errResult(`No eval report for job ${args.job_id}. Run evaluate_job first.`);
|
|
14
|
+
return okResult({
|
|
15
|
+
job_id: args.job_id,
|
|
16
|
+
report_id: row.id,
|
|
17
|
+
archetype_detected: row.archetype_detected,
|
|
18
|
+
keywords: row.keywords,
|
|
19
|
+
url: `${config.baseUrl}/files/${row.html_path}`,
|
|
20
|
+
created_at: row.created_at,
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=get_report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get_report.js","sourceRoot":"","sources":["../../../src/mcp/tools/get_report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE/D,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;IACtC,IAAI,EAAE,YAAY;IAClB,KAAK,EAAE,sCAAsC;IAC7C,WAAW,EAAE,kGAAkG;IAC/G,WAAW,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;IAC1C,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACtB,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC,0BAA0B,IAAI,CAAC,MAAM,2BAA2B,CAAC,CAAC;QAC7F,OAAO,QAAQ,CAAC;YACd,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,SAAS,EAAE,GAAG,CAAC,EAAE;YACjB,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;YAC1C,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,GAAG,EAAE,GAAG,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,SAAS,EAAE;YAC/C,UAAU,EAAE,GAAG,CAAC,UAAU;SAC3B,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC"}
|