careerclaw-js 0.11.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/CHANGELOG.md +362 -0
- package/README.md +348 -0
- package/SECURITY.md +156 -0
- package/SKILL.md +463 -0
- package/dist/adapters/hackernews.d.ts +36 -0
- package/dist/adapters/hackernews.d.ts.map +1 -0
- package/dist/adapters/hackernews.js +164 -0
- package/dist/adapters/hackernews.js.map +1 -0
- package/dist/adapters/index.d.ts +10 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +9 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/remoteok.d.ts +35 -0
- package/dist/adapters/remoteok.d.ts.map +1 -0
- package/dist/adapters/remoteok.js +212 -0
- package/dist/adapters/remoteok.js.map +1 -0
- package/dist/briefing.d.ts +81 -0
- package/dist/briefing.d.ts.map +1 -0
- package/dist/briefing.js +152 -0
- package/dist/briefing.js.map +1 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +235 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +91 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +126 -0
- package/dist/config.js.map +1 -0
- package/dist/core/text-processing.d.ts +62 -0
- package/dist/core/text-processing.d.ts.map +1 -0
- package/dist/core/text-processing.js +187 -0
- package/dist/core/text-processing.js.map +1 -0
- package/dist/drafting.d.ts +28 -0
- package/dist/drafting.d.ts.map +1 -0
- package/dist/drafting.js +116 -0
- package/dist/drafting.js.map +1 -0
- package/dist/gap.d.ts +27 -0
- package/dist/gap.d.ts.map +1 -0
- package/dist/gap.js +90 -0
- package/dist/gap.js.map +1 -0
- package/dist/license.d.ts +40 -0
- package/dist/license.d.ts.map +1 -0
- package/dist/license.js +122 -0
- package/dist/license.js.map +1 -0
- package/dist/llm-enhance.d.ts +69 -0
- package/dist/llm-enhance.d.ts.map +1 -0
- package/dist/llm-enhance.js +376 -0
- package/dist/llm-enhance.js.map +1 -0
- package/dist/matching/engine.d.ts +31 -0
- package/dist/matching/engine.d.ts.map +1 -0
- package/dist/matching/engine.js +51 -0
- package/dist/matching/engine.js.map +1 -0
- package/dist/matching/index.d.ts +8 -0
- package/dist/matching/index.d.ts.map +1 -0
- package/dist/matching/index.js +8 -0
- package/dist/matching/index.js.map +1 -0
- package/dist/matching/scoring.d.ts +84 -0
- package/dist/matching/scoring.d.ts.map +1 -0
- package/dist/matching/scoring.js +184 -0
- package/dist/matching/scoring.js.map +1 -0
- package/dist/models.d.ts +221 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +28 -0
- package/dist/models.js.map +1 -0
- package/dist/requirements.d.ts +22 -0
- package/dist/requirements.d.ts.map +1 -0
- package/dist/requirements.js +30 -0
- package/dist/requirements.js.map +1 -0
- package/dist/resume-intel.d.ts +40 -0
- package/dist/resume-intel.d.ts.map +1 -0
- package/dist/resume-intel.js +111 -0
- package/dist/resume-intel.js.map +1 -0
- package/dist/sources.d.ts +32 -0
- package/dist/sources.d.ts.map +1 -0
- package/dist/sources.js +72 -0
- package/dist/sources.js.map +1 -0
- package/dist/tracking.d.ts +68 -0
- package/dist/tracking.d.ts.map +1 -0
- package/dist/tracking.js +140 -0
- package/dist/tracking.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"license.js","sourceRoot":"","sources":["../src/license.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llm-enhance.ts — LLM-powered outreach draft enhancement (Pro tier).
|
|
3
|
+
*
|
|
4
|
+
* `enhanceDraft()` replaces the deterministic baseline draft with a
|
|
5
|
+
* personalised LLM-generated version when a valid Pro key is active.
|
|
6
|
+
*
|
|
7
|
+
* Design principles:
|
|
8
|
+
* - Silent fallback: any failure returns the original draft unchanged.
|
|
9
|
+
* The function never throws.
|
|
10
|
+
* - Privacy-first: only extracted keyword signals are sent to the LLM.
|
|
11
|
+
* Raw resume text is NEVER included in the prompt.
|
|
12
|
+
* - Failover chain: providers are tried left-to-right from LLM_CHAIN.
|
|
13
|
+
* Each candidate is retried up to LLM_MAX_RETRIES times before moving
|
|
14
|
+
* to the next. A circuit breaker opens after LLM_CIRCUIT_BREAKER_FAILS
|
|
15
|
+
* consecutive failures across the whole chain.
|
|
16
|
+
* - Testable: the fetch function is injectable so tests run fully offline.
|
|
17
|
+
*/
|
|
18
|
+
import type { NormalizedJob, UserProfile, OutreachDraft, ResumeIntelligence } from "./models.js";
|
|
19
|
+
type OpenAITransport = "chat" | "responses";
|
|
20
|
+
export interface ChainCandidate {
|
|
21
|
+
provider: "anthropic" | "openai";
|
|
22
|
+
model: string;
|
|
23
|
+
apiKey: string;
|
|
24
|
+
transport?: OpenAITransport;
|
|
25
|
+
}
|
|
26
|
+
export interface EnhanceOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Injectable fetch — defaults to global fetch.
|
|
29
|
+
* Pass a stub in tests to avoid live network calls.
|
|
30
|
+
*/
|
|
31
|
+
fetchFn?: typeof fetch;
|
|
32
|
+
/**
|
|
33
|
+
* Override the resolved provider chain — for testing only.
|
|
34
|
+
* Bypasses env-var key resolution so tests can run without real credentials.
|
|
35
|
+
*/
|
|
36
|
+
_chainOverride?: ChainCandidate[];
|
|
37
|
+
/**
|
|
38
|
+
* Output mode.
|
|
39
|
+
*
|
|
40
|
+
* "draft" — (default, Free tier) Short outreach email, max 250 words.
|
|
41
|
+
* Produced by the deterministic template on Free; LLM-enhanced on Pro.
|
|
42
|
+
* "cover_letter" — (Pro only, future) Tailored cover letter, max 300 words.
|
|
43
|
+
* Every sentence connects the candidate to this specific role.
|
|
44
|
+
* Zero generic filler. Requires active Pro license.
|
|
45
|
+
*
|
|
46
|
+
* TODO: Phase X — implement cover_letter mode. Stub is in buildPrompt().
|
|
47
|
+
*/
|
|
48
|
+
mode?: "draft" | "cover_letter";
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Attempt to replace `draft` with an LLM-enhanced version.
|
|
52
|
+
*
|
|
53
|
+
* @param job - The job being applied to
|
|
54
|
+
* @param profile - User profile (experience_years used in prompt)
|
|
55
|
+
* @param resumeIntel - Section-aware keyword signals (impact_signals used)
|
|
56
|
+
* @param draft - Deterministic baseline to fall back to on failure
|
|
57
|
+
* @param gapKeywords - Keywords from the job not present in the profile
|
|
58
|
+
* @param options - Injectable fetch for testing
|
|
59
|
+
* @returns Enhanced draft with llm_enhanced=true, or original draft on failure
|
|
60
|
+
*/
|
|
61
|
+
export declare function enhanceDraft(job: NormalizedJob, profile: UserProfile, resumeIntel: ResumeIntelligence, draft: OutreachDraft, gapKeywords?: string[], options?: EnhanceOptions): Promise<OutreachDraft>;
|
|
62
|
+
/**
|
|
63
|
+
* System prompt — establishes the LLM as a senior career writer.
|
|
64
|
+
* Sent as the `system` field (Anthropic) or system role message (OpenAI).
|
|
65
|
+
*/
|
|
66
|
+
declare const SYSTEM_PROMPT = "You are a senior career consultant and professional writer with 15 years of experience placing candidates at top-tier companies. You write outreach emails and cover letters that are specific, compelling, and human \u2014 never generic.\n\nYour core method \u2014 the Bridge Sentence:\nFor every strength signal the candidate has, identify the corresponding requirement in the role and connect them with a concrete, specific sentence. Example: \"Because I architected the data pipeline at [Company], I can solve your need for reliable real-time ingestion from day one.\" Every paragraph must contain at least one Bridge Sentence.\n\nYour writing principles:\n- Every sentence earns its place. No filler, no padding, no throat-clearing.\n- Map the candidate's actual experience to this specific role's needs \u2014 not to \"the industry.\"\n- When the candidate has a gap, frame it as genuine motivation to grow, not as an apology.\n- Write like a thoughtful senior professional, not a cover letter generator.\n- Strict word limits: 250 words maximum for outreach emails, 300 words for cover letters.\n- Count words before finishing. If over the limit, cut the weakest sentence, not the strongest.\n- Output your final word count at the very bottom of the response in brackets.\n\nBanned phrases \u2014 never use these under any circumstances:\n\"I am writing to express my interest\", \"I am excited to apply\", \"I would be a great fit\", \"passion for\", \"passionate about\", \"leverage my skills\", \"leverage my experience\", \"dynamic team\", \"fast-paced environment\", \"detail-oriented\", \"results-driven\", \"team player\", \"go-getter\", \"hard worker\", \"strong work ethic\", \"I am a quick learner\", \"Enclosed please find\", \"Please find attached\", \"I am writing to\", \"I look forward to hearing from you\", \"Thank you for your consideration\", \"I believe I would\", \"synergy\", \"proactive\", \"self-starter\".";
|
|
67
|
+
/** Exported for testing — same reference used by both API callers. */
|
|
68
|
+
export { SYSTEM_PROMPT };
|
|
69
|
+
//# sourceMappingURL=llm-enhance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-enhance.d.ts","sourceRoot":"","sources":["../src/llm-enhance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAejG,KAAK,eAAe,GAAG,MAAM,GAAG,WAAW,CAAC;AAE5C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,WAAW,GAAG,QAAQ,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB;;;OAGG;IACH,cAAc,CAAC,EAAE,cAAc,EAAE,CAAC;IAClC;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC;CACjC;AAMD;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,WAAW,EAAE,kBAAkB,EAC/B,KAAK,EAAE,aAAa,EACpB,WAAW,GAAE,MAAM,EAAO,EAC1B,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAkCxB;AA2DD;;;GAGG;AACH,QAAA,MAAM,aAAa,m5DAe2gB,CAAC;AA8F/hB,sEAAsE;AACtE,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llm-enhance.ts — LLM-powered outreach draft enhancement (Pro tier).
|
|
3
|
+
*
|
|
4
|
+
* `enhanceDraft()` replaces the deterministic baseline draft with a
|
|
5
|
+
* personalised LLM-generated version when a valid Pro key is active.
|
|
6
|
+
*
|
|
7
|
+
* Design principles:
|
|
8
|
+
* - Silent fallback: any failure returns the original draft unchanged.
|
|
9
|
+
* The function never throws.
|
|
10
|
+
* - Privacy-first: only extracted keyword signals are sent to the LLM.
|
|
11
|
+
* Raw resume text is NEVER included in the prompt.
|
|
12
|
+
* - Failover chain: providers are tried left-to-right from LLM_CHAIN.
|
|
13
|
+
* Each candidate is retried up to LLM_MAX_RETRIES times before moving
|
|
14
|
+
* to the next. A circuit breaker opens after LLM_CIRCUIT_BREAKER_FAILS
|
|
15
|
+
* consecutive failures across the whole chain.
|
|
16
|
+
* - Testable: the fetch function is injectable so tests run fully offline.
|
|
17
|
+
*/
|
|
18
|
+
import { LLM_ANTHROPIC_KEY, LLM_OPENAI_KEY, LLM_API_KEY, LLM_CHAIN, LLM_MAX_RETRIES, LLM_CIRCUIT_BREAKER_FAILS, HTTP_TIMEOUT_MS, } from "./config.js";
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Public API
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
/**
|
|
23
|
+
* Attempt to replace `draft` with an LLM-enhanced version.
|
|
24
|
+
*
|
|
25
|
+
* @param job - The job being applied to
|
|
26
|
+
* @param profile - User profile (experience_years used in prompt)
|
|
27
|
+
* @param resumeIntel - Section-aware keyword signals (impact_signals used)
|
|
28
|
+
* @param draft - Deterministic baseline to fall back to on failure
|
|
29
|
+
* @param gapKeywords - Keywords from the job not present in the profile
|
|
30
|
+
* @param options - Injectable fetch for testing
|
|
31
|
+
* @returns Enhanced draft with llm_enhanced=true, or original draft on failure
|
|
32
|
+
*/
|
|
33
|
+
export async function enhanceDraft(job, profile, resumeIntel, draft, gapKeywords = [], options = {}) {
|
|
34
|
+
const fetchFn = options.fetchFn ?? fetch;
|
|
35
|
+
const mode = options.mode ?? "draft";
|
|
36
|
+
const chain = options._chainOverride ?? buildChain();
|
|
37
|
+
if (chain.length === 0) {
|
|
38
|
+
return draft; // No configured providers — degrade silently
|
|
39
|
+
}
|
|
40
|
+
let consecutiveFails = 0;
|
|
41
|
+
for (const candidate of chain) {
|
|
42
|
+
if (consecutiveFails >= LLM_CIRCUIT_BREAKER_FAILS) {
|
|
43
|
+
break; // Circuit breaker open
|
|
44
|
+
}
|
|
45
|
+
for (let attempt = 0; attempt < LLM_MAX_RETRIES; attempt++) {
|
|
46
|
+
if (consecutiveFails >= LLM_CIRCUIT_BREAKER_FAILS)
|
|
47
|
+
break;
|
|
48
|
+
try {
|
|
49
|
+
const prompt = buildPrompt(job, profile, resumeIntel, gapKeywords, mode);
|
|
50
|
+
const text = await callProvider(candidate, prompt, fetchFn);
|
|
51
|
+
const parsed = parseResponse(text, job);
|
|
52
|
+
if (parsed) {
|
|
53
|
+
return { ...draft, subject: parsed.subject, body: parsed.body, llm_enhanced: true };
|
|
54
|
+
}
|
|
55
|
+
// Unparseable response counts as a soft failure — try next attempt
|
|
56
|
+
consecutiveFails++;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
consecutiveFails++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return draft; // All candidates exhausted or circuit open — fall back
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Chain construction
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
/**
|
|
69
|
+
* Parse LLM_CHAIN into candidates with resolved API keys.
|
|
70
|
+
* Candidates without a usable key are silently skipped.
|
|
71
|
+
*
|
|
72
|
+
* Supported chain formats:
|
|
73
|
+
* "anthropic/claude-haiku-4-5-20251001,openai/gpt-4o-mini"
|
|
74
|
+
* "openai:chat/gpt-4o-mini,openai:responses/gpt-5.2"
|
|
75
|
+
*/
|
|
76
|
+
function buildChain() {
|
|
77
|
+
return LLM_CHAIN.split(",")
|
|
78
|
+
.map((entry) => entry.trim())
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.flatMap((entry) => {
|
|
81
|
+
const slash = entry.indexOf("/");
|
|
82
|
+
if (slash === -1)
|
|
83
|
+
return [];
|
|
84
|
+
const left = entry.slice(0, slash).toLowerCase();
|
|
85
|
+
const model = entry.slice(slash + 1).trim();
|
|
86
|
+
if (!model)
|
|
87
|
+
return [];
|
|
88
|
+
let provider = null;
|
|
89
|
+
let transport;
|
|
90
|
+
if (left === "anthropic") {
|
|
91
|
+
provider = "anthropic";
|
|
92
|
+
}
|
|
93
|
+
else if (left === "openai") {
|
|
94
|
+
provider = "openai";
|
|
95
|
+
}
|
|
96
|
+
else if (left === "openai:chat") {
|
|
97
|
+
provider = "openai";
|
|
98
|
+
transport = "chat";
|
|
99
|
+
}
|
|
100
|
+
else if (left === "openai:responses") {
|
|
101
|
+
provider = "openai";
|
|
102
|
+
transport = "responses";
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const apiKey = resolveKey(provider);
|
|
108
|
+
if (!apiKey)
|
|
109
|
+
return []; // No key for this provider — skip
|
|
110
|
+
return [{ provider, model, apiKey, ...(transport !== undefined ? { transport } : {}) }];
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function resolveKey(provider) {
|
|
114
|
+
if (provider === "anthropic")
|
|
115
|
+
return LLM_ANTHROPIC_KEY ?? LLM_API_KEY;
|
|
116
|
+
if (provider === "openai")
|
|
117
|
+
return LLM_OPENAI_KEY ?? LLM_API_KEY;
|
|
118
|
+
}
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Prompt construction
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
/**
|
|
123
|
+
* System prompt — establishes the LLM as a senior career writer.
|
|
124
|
+
* Sent as the `system` field (Anthropic) or system role message (OpenAI).
|
|
125
|
+
*/
|
|
126
|
+
const SYSTEM_PROMPT = `You are a senior career consultant and professional writer with 15 years of experience placing candidates at top-tier companies. You write outreach emails and cover letters that are specific, compelling, and human — never generic.
|
|
127
|
+
|
|
128
|
+
Your core method — the Bridge Sentence:
|
|
129
|
+
For every strength signal the candidate has, identify the corresponding requirement in the role and connect them with a concrete, specific sentence. Example: "Because I architected the data pipeline at [Company], I can solve your need for reliable real-time ingestion from day one." Every paragraph must contain at least one Bridge Sentence.
|
|
130
|
+
|
|
131
|
+
Your writing principles:
|
|
132
|
+
- Every sentence earns its place. No filler, no padding, no throat-clearing.
|
|
133
|
+
- Map the candidate's actual experience to this specific role's needs — not to "the industry."
|
|
134
|
+
- When the candidate has a gap, frame it as genuine motivation to grow, not as an apology.
|
|
135
|
+
- Write like a thoughtful senior professional, not a cover letter generator.
|
|
136
|
+
- Strict word limits: 250 words maximum for outreach emails, 300 words for cover letters.
|
|
137
|
+
- Count words before finishing. If over the limit, cut the weakest sentence, not the strongest.
|
|
138
|
+
- Output your final word count at the very bottom of the response in brackets.
|
|
139
|
+
|
|
140
|
+
Banned phrases — never use these under any circumstances:
|
|
141
|
+
"I am writing to express my interest", "I am excited to apply", "I would be a great fit", "passion for", "passionate about", "leverage my skills", "leverage my experience", "dynamic team", "fast-paced environment", "detail-oriented", "results-driven", "team player", "go-getter", "hard worker", "strong work ethic", "I am a quick learner", "Enclosed please find", "Please find attached", "I am writing to", "I look forward to hearing from you", "Thank you for your consideration", "I believe I would", "synergy", "proactive", "self-starter".`;
|
|
142
|
+
/**
|
|
143
|
+
* Build the LLM prompt for the requested output mode.
|
|
144
|
+
*
|
|
145
|
+
* Only sends keyword signals — never raw resume text.
|
|
146
|
+
* Uses impact_signals (skills + summary section, weight >= 0.8) as the
|
|
147
|
+
* candidate's strength summary, and gap_keywords for strategic gap framing.
|
|
148
|
+
*
|
|
149
|
+
* mode "draft" — outreach email, max 250 words (Free tier baseline, LLM-enhanced on Pro)
|
|
150
|
+
* mode "cover_letter" — tailored cover letter, max 300 words, zero filler (Pro only, future)
|
|
151
|
+
*/
|
|
152
|
+
function buildPrompt(job, profile, resumeIntel, gapKeywords = [], mode = "draft") {
|
|
153
|
+
const experienceClause = profile.experience_years != null
|
|
154
|
+
? `${profile.experience_years}+ years`
|
|
155
|
+
: "extensive experience";
|
|
156
|
+
// Cap signal lists to keep token usage low (Haiku: ~0.25¢/1K tokens)
|
|
157
|
+
const strengths = resumeIntel.impact_signals.slice(0, 12).join(", ") || "software engineering";
|
|
158
|
+
const gaps = gapKeywords.slice(0, 6).join(", ");
|
|
159
|
+
const gapsInstruction = gaps
|
|
160
|
+
? `Gap keywords (acknowledge briefly as growth areas — do not apologise, do not skip): ${gaps}`
|
|
161
|
+
: "";
|
|
162
|
+
if (mode === "cover_letter") {
|
|
163
|
+
// TODO: Phase X — cover_letter mode (Pro only).
|
|
164
|
+
// Spec: under 300 words, zero generic filler, every sentence connects
|
|
165
|
+
// the candidate to this specific role. Paragraph structure:
|
|
166
|
+
// 1. Why this company/role specifically (1–2 sentences)
|
|
167
|
+
// 2. Strongest signal mapped to the role's core need (2–3 sentences)
|
|
168
|
+
// 3. Gap acknowledgement as motivation (1 sentence, only if gaps exist)
|
|
169
|
+
// 4. Clear call to action (1 sentence)
|
|
170
|
+
// For now fall through to draft mode until the feature ships.
|
|
171
|
+
}
|
|
172
|
+
// Draft mode — LLM-enhanced outreach email
|
|
173
|
+
return [
|
|
174
|
+
`Write a cold outreach email from a job candidate to a hiring team.`,
|
|
175
|
+
``,
|
|
176
|
+
`Role: ${job.title}`,
|
|
177
|
+
`Company: ${job.company}`,
|
|
178
|
+
`Candidate experience: ${experienceClause}`,
|
|
179
|
+
`Candidate's strongest signals: ${strengths}`,
|
|
180
|
+
gapsInstruction,
|
|
181
|
+
``,
|
|
182
|
+
`Output format (follow exactly):`,
|
|
183
|
+
`- First line: subject line prefixed with "Subject: "`,
|
|
184
|
+
`- One blank line`,
|
|
185
|
+
`- Email body: maximum 250 words — count before finishing and cut if over`,
|
|
186
|
+
``,
|
|
187
|
+
`Writing requirements:`,
|
|
188
|
+
`- Opening: "Hi ${job.company} team,"`,
|
|
189
|
+
`- Bridge Sentence method: for each strength signal, identify the matching requirement`,
|
|
190
|
+
` in this role and write one concrete sentence connecting them.`,
|
|
191
|
+
` Example: "Because I [did X], I can solve your need for [Y] from day one."`,
|
|
192
|
+
` Every paragraph must contain at least one Bridge Sentence.`,
|
|
193
|
+
`- First paragraph: connect the candidate's strongest 1–2 signals directly to what`,
|
|
194
|
+
` this specific role needs — make the match feel specific and inevitable`,
|
|
195
|
+
`- Second paragraph: show how the candidate's background maps to a real challenge`,
|
|
196
|
+
` this company faces — be concrete, not generic`,
|
|
197
|
+
`- If gap keywords are provided, one sentence frames them as active growth, not deficiency`,
|
|
198
|
+
`- Close with a single, low-friction call to action`,
|
|
199
|
+
`- Closing: "Best regards," on its own line, then "[Your Name]" on the next`,
|
|
200
|
+
`- Plain text only — no markdown, no bullet points`,
|
|
201
|
+
`- Do NOT invent specific projects, metrics, or company details`,
|
|
202
|
+
`- Do NOT include any placeholder other than [Your Name]`,
|
|
203
|
+
]
|
|
204
|
+
.filter(Boolean)
|
|
205
|
+
.join("\n");
|
|
206
|
+
}
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Provider API calls
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
async function callProvider(candidate, prompt, fetchFn) {
|
|
211
|
+
if (candidate.provider === "anthropic") {
|
|
212
|
+
return callAnthropic(candidate.apiKey, candidate.model, prompt, fetchFn);
|
|
213
|
+
}
|
|
214
|
+
return callOpenAI(candidate.apiKey, candidate.model, prompt, fetchFn, candidate.transport);
|
|
215
|
+
}
|
|
216
|
+
/** Exported for testing — same reference used by both API callers. */
|
|
217
|
+
export { SYSTEM_PROMPT };
|
|
218
|
+
async function callAnthropic(apiKey, model, prompt, fetchFn) {
|
|
219
|
+
const res = await fetchFn("https://api.anthropic.com/v1/messages", {
|
|
220
|
+
method: "POST",
|
|
221
|
+
headers: {
|
|
222
|
+
"Content-Type": "application/json",
|
|
223
|
+
"x-api-key": apiKey,
|
|
224
|
+
"anthropic-version": "2023-06-01",
|
|
225
|
+
},
|
|
226
|
+
body: JSON.stringify({
|
|
227
|
+
model,
|
|
228
|
+
max_tokens: 512,
|
|
229
|
+
system: SYSTEM_PROMPT,
|
|
230
|
+
messages: [{ role: "user", content: prompt }],
|
|
231
|
+
}),
|
|
232
|
+
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),
|
|
233
|
+
});
|
|
234
|
+
if (!res.ok) {
|
|
235
|
+
const body = await res.text().catch(() => "(no body)");
|
|
236
|
+
throw new Error(`Anthropic HTTP ${res.status}: ${body.slice(0, 200)}`);
|
|
237
|
+
}
|
|
238
|
+
const data = await res.json();
|
|
239
|
+
const text = data.content?.find((b) => b.type === "text")?.text ?? "";
|
|
240
|
+
if (!text)
|
|
241
|
+
throw new Error("Anthropic: empty response content");
|
|
242
|
+
return text;
|
|
243
|
+
}
|
|
244
|
+
function defaultOpenAITransport(model) {
|
|
245
|
+
const normalized = model.toLowerCase();
|
|
246
|
+
if (normalized.startsWith("gpt-4o"))
|
|
247
|
+
return "chat";
|
|
248
|
+
return "responses";
|
|
249
|
+
}
|
|
250
|
+
async function callOpenAI(apiKey, model, prompt, fetchFn, transport) {
|
|
251
|
+
const resolvedTransport = transport ?? defaultOpenAITransport(model);
|
|
252
|
+
if (resolvedTransport === "responses") {
|
|
253
|
+
return callOpenAIResponses(apiKey, model, prompt, fetchFn);
|
|
254
|
+
}
|
|
255
|
+
return callOpenAIChat(apiKey, model, prompt, fetchFn);
|
|
256
|
+
}
|
|
257
|
+
async function callOpenAIChat(apiKey, model, prompt, fetchFn) {
|
|
258
|
+
const res = await fetchFn("https://api.openai.com/v1/chat/completions", {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: {
|
|
261
|
+
"Content-Type": "application/json",
|
|
262
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
263
|
+
},
|
|
264
|
+
body: JSON.stringify({
|
|
265
|
+
model,
|
|
266
|
+
max_tokens: 512,
|
|
267
|
+
temperature: 0.7,
|
|
268
|
+
messages: [
|
|
269
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
270
|
+
{ role: "user", content: prompt },
|
|
271
|
+
],
|
|
272
|
+
}),
|
|
273
|
+
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),
|
|
274
|
+
});
|
|
275
|
+
if (!res.ok) {
|
|
276
|
+
const body = await res.text().catch(() => "(no body)");
|
|
277
|
+
throw new Error(`OpenAI chat HTTP ${res.status}: ${body.slice(0, 200)}`);
|
|
278
|
+
}
|
|
279
|
+
const data = await res.json();
|
|
280
|
+
const content = data.choices?.[0]?.message?.content;
|
|
281
|
+
if (typeof content === "string" && content.trim()) {
|
|
282
|
+
return content;
|
|
283
|
+
}
|
|
284
|
+
if (Array.isArray(content)) {
|
|
285
|
+
const text = content
|
|
286
|
+
.filter((part) => part?.type === "text" && typeof part.text === "string")
|
|
287
|
+
.map((part) => part.text ?? "")
|
|
288
|
+
.join("")
|
|
289
|
+
.trim();
|
|
290
|
+
if (text)
|
|
291
|
+
return text;
|
|
292
|
+
}
|
|
293
|
+
throw new Error("OpenAI chat: empty response content");
|
|
294
|
+
}
|
|
295
|
+
async function callOpenAIResponses(apiKey, model, prompt, fetchFn) {
|
|
296
|
+
const res = await fetchFn("https://api.openai.com/v1/responses", {
|
|
297
|
+
method: "POST",
|
|
298
|
+
headers: {
|
|
299
|
+
"Content-Type": "application/json",
|
|
300
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
301
|
+
},
|
|
302
|
+
body: JSON.stringify({
|
|
303
|
+
model,
|
|
304
|
+
max_output_tokens: 512,
|
|
305
|
+
temperature: 0.7,
|
|
306
|
+
input: [
|
|
307
|
+
{
|
|
308
|
+
role: "system",
|
|
309
|
+
content: [{ type: "input_text", text: SYSTEM_PROMPT }],
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
role: "user",
|
|
313
|
+
content: [{ type: "input_text", text: prompt }],
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
}),
|
|
317
|
+
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),
|
|
318
|
+
});
|
|
319
|
+
if (!res.ok) {
|
|
320
|
+
const body = await res.text().catch(() => "(no body)");
|
|
321
|
+
throw new Error(`OpenAI responses HTTP ${res.status}: ${body.slice(0, 200)}`);
|
|
322
|
+
}
|
|
323
|
+
const data = await res.json();
|
|
324
|
+
if (typeof data.output_text === "string" && data.output_text.trim()) {
|
|
325
|
+
return data.output_text;
|
|
326
|
+
}
|
|
327
|
+
const text = (data.output ?? [])
|
|
328
|
+
.flatMap((item) => item.content ?? [])
|
|
329
|
+
.filter((part) => part?.type === "output_text" && typeof part.text === "string")
|
|
330
|
+
.map((part) => part.text ?? "")
|
|
331
|
+
.join("")
|
|
332
|
+
.trim();
|
|
333
|
+
if (!text) {
|
|
334
|
+
throw new Error("OpenAI responses: empty response content");
|
|
335
|
+
}
|
|
336
|
+
return text;
|
|
337
|
+
}
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
// Response parsing
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
/**
|
|
342
|
+
* Extract subject and body from raw LLM text.
|
|
343
|
+
*
|
|
344
|
+
* Expected format:
|
|
345
|
+
* Subject: <subject line>
|
|
346
|
+
* <blank line>
|
|
347
|
+
* <body...>
|
|
348
|
+
*
|
|
349
|
+
* Returns null if the response cannot be parsed — caller treats as failure.
|
|
350
|
+
*/
|
|
351
|
+
function parseResponse(text, job) {
|
|
352
|
+
const lines = text.trim().split(/\r?\n/);
|
|
353
|
+
// Find subject line (must start with "Subject:")
|
|
354
|
+
const subjectIdx = lines.findIndex((l) => l.trimStart().toLowerCase().startsWith("subject:"));
|
|
355
|
+
if (subjectIdx === -1)
|
|
356
|
+
return null;
|
|
357
|
+
const subject = lines[subjectIdx]
|
|
358
|
+
.replace(/^subject:\s*/i, "")
|
|
359
|
+
.trim();
|
|
360
|
+
if (!subject)
|
|
361
|
+
return null;
|
|
362
|
+
// Body is everything after the subject (skip blank separator line)
|
|
363
|
+
let bodyStart = subjectIdx + 1;
|
|
364
|
+
while (bodyStart < lines.length && lines[bodyStart].trim() === "") {
|
|
365
|
+
bodyStart++;
|
|
366
|
+
}
|
|
367
|
+
const body = lines.slice(bodyStart).join("\n").trim();
|
|
368
|
+
if (!body)
|
|
369
|
+
return null;
|
|
370
|
+
// Sanity check: body must mention the company (guards against hallucination)
|
|
371
|
+
if (!body.toLowerCase().includes(job.company.toLowerCase().slice(0, 6))) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
return { subject, body };
|
|
375
|
+
}
|
|
376
|
+
//# sourceMappingURL=llm-enhance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-enhance.js","sourceRoot":"","sources":["../src/llm-enhance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* matching/engine.ts — Matching engine orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* `rankJobs()` is the single entry point for the briefing pipeline's
|
|
5
|
+
* ranking step. It scores every job against the user profile, filters
|
|
6
|
+
* out irrelevant jobs via the signal gate, sorts descending by composite
|
|
7
|
+
* score, and returns the top-K results as `ScoredJob[]`.
|
|
8
|
+
*
|
|
9
|
+
* Two-stage retrieval pipeline:
|
|
10
|
+
* Stage 1 (Multiplier): compositeScore() — keyword overlap gates the
|
|
11
|
+
* magnitude of the total score. Zero keyword overlap → score of 0.0.
|
|
12
|
+
* Stage 2 (Gate): minKeywordScore filter — hard boundary that removes
|
|
13
|
+
* any job below the minimum technical relevance threshold before
|
|
14
|
+
* ranking. Prevents irrelevant jobs floating on neutral dimension
|
|
15
|
+
* scores (the "dentist problem").
|
|
16
|
+
*
|
|
17
|
+
* Downstream layers (gap analysis, drafting, tracking) only consume
|
|
18
|
+
* `ScoredJob[]` — they are score-agnostic.
|
|
19
|
+
*/
|
|
20
|
+
import type { NormalizedJob, UserProfile, ScoredJob } from "../models.js";
|
|
21
|
+
/**
|
|
22
|
+
* Score, filter, and rank jobs against a user profile.
|
|
23
|
+
*
|
|
24
|
+
* @param jobs - Raw normalised jobs from source adapters
|
|
25
|
+
* @param profile - User profile to rank against
|
|
26
|
+
* @param limit - Maximum results to return (default: DEFAULT_TOP_K)
|
|
27
|
+
* @param minKeywordScore - Signal gate threshold; jobs with keyword score
|
|
28
|
+
* below this are dropped before ranking (default: 0.01)
|
|
29
|
+
*/
|
|
30
|
+
export declare function rankJobs(jobs: NormalizedJob[], profile: UserProfile, limit?: number, minKeywordScore?: number): ScoredJob[];
|
|
31
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/matching/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAQ1E;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,aAAa,EAAE,EACrB,OAAO,EAAE,WAAW,EACpB,KAAK,GAAE,MAAsB,EAC7B,eAAe,GAAE,MAAa,GAC7B,SAAS,EAAE,CAgBb"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* matching/engine.ts — Matching engine orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* `rankJobs()` is the single entry point for the briefing pipeline's
|
|
5
|
+
* ranking step. It scores every job against the user profile, filters
|
|
6
|
+
* out irrelevant jobs via the signal gate, sorts descending by composite
|
|
7
|
+
* score, and returns the top-K results as `ScoredJob[]`.
|
|
8
|
+
*
|
|
9
|
+
* Two-stage retrieval pipeline:
|
|
10
|
+
* Stage 1 (Multiplier): compositeScore() — keyword overlap gates the
|
|
11
|
+
* magnitude of the total score. Zero keyword overlap → score of 0.0.
|
|
12
|
+
* Stage 2 (Gate): minKeywordScore filter — hard boundary that removes
|
|
13
|
+
* any job below the minimum technical relevance threshold before
|
|
14
|
+
* ranking. Prevents irrelevant jobs floating on neutral dimension
|
|
15
|
+
* scores (the "dentist problem").
|
|
16
|
+
*
|
|
17
|
+
* Downstream layers (gap analysis, drafting, tracking) only consume
|
|
18
|
+
* `ScoredJob[]` — they are score-agnostic.
|
|
19
|
+
*/
|
|
20
|
+
import { DEFAULT_TOP_K } from "../config.js";
|
|
21
|
+
import { compositeScore } from "./scoring.js";
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Public API
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/**
|
|
26
|
+
* Score, filter, and rank jobs against a user profile.
|
|
27
|
+
*
|
|
28
|
+
* @param jobs - Raw normalised jobs from source adapters
|
|
29
|
+
* @param profile - User profile to rank against
|
|
30
|
+
* @param limit - Maximum results to return (default: DEFAULT_TOP_K)
|
|
31
|
+
* @param minKeywordScore - Signal gate threshold; jobs with keyword score
|
|
32
|
+
* below this are dropped before ranking (default: 0.01)
|
|
33
|
+
*/
|
|
34
|
+
export function rankJobs(jobs, profile, limit = DEFAULT_TOP_K, minKeywordScore = 0.01) {
|
|
35
|
+
return jobs
|
|
36
|
+
.map((job) => {
|
|
37
|
+
const { total, breakdown, matched, gaps } = compositeScore(profile, job);
|
|
38
|
+
return {
|
|
39
|
+
job,
|
|
40
|
+
score: total,
|
|
41
|
+
breakdown,
|
|
42
|
+
matched_keywords: matched,
|
|
43
|
+
gap_keywords: gaps,
|
|
44
|
+
};
|
|
45
|
+
})
|
|
46
|
+
// Stage 2: drop jobs that fail the technical relevance gate
|
|
47
|
+
.filter((scored) => scored.breakdown.keyword >= minKeywordScore)
|
|
48
|
+
.sort((a, b) => b.score - a.score)
|
|
49
|
+
.slice(0, limit);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/matching/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CACtB,IAAqB,EACrB,OAAoB,EACpB,QAAgB,aAAa,EAC7B,kBAA0B,IAAI;IAE9B,OAAO,IAAI;SACR,GAAG,CAAC,CAAC,GAAG,EAAa,EAAE;QACtB,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACzE,OAAO;YACL,GAAG;YACH,KAAK,EAAE,KAAK;YACZ,SAAS;YACT,gBAAgB,EAAE,OAAO;YACzB,YAAY,EAAE,IAAI;SACnB,CAAC;IACJ,CAAC,CAAC;QACF,4DAA4D;SAC3D,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,IAAI,eAAe,CAAC;SAC/D,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;SACjC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* matching/index.ts — Public matching API.
|
|
3
|
+
*
|
|
4
|
+
* Import from here rather than individual files.
|
|
5
|
+
*/
|
|
6
|
+
export { rankJobs } from "./engine.js";
|
|
7
|
+
export { scoreKeyword, scoreExperience, scoreSalary, scoreWorkMode, compositeScore, } from "./scoring.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/matching/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EACL,YAAY,EACZ,eAAe,EACf,WAAW,EACX,aAAa,EACb,cAAc,GACf,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* matching/index.ts — Public matching API.
|
|
3
|
+
*
|
|
4
|
+
* Import from here rather than individual files.
|
|
5
|
+
*/
|
|
6
|
+
export { rankJobs } from "./engine.js";
|
|
7
|
+
export { scoreKeyword, scoreExperience, scoreSalary, scoreWorkMode, compositeScore, } from "./scoring.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/matching/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EACL,YAAY,EACZ,eAAe,EACf,WAAW,EACX,aAAa,EACb,cAAc,GACf,MAAM,cAAc,CAAC"}
|