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.
Files changed (81) hide show
  1. package/CHANGELOG.md +362 -0
  2. package/README.md +348 -0
  3. package/SECURITY.md +156 -0
  4. package/SKILL.md +463 -0
  5. package/dist/adapters/hackernews.d.ts +36 -0
  6. package/dist/adapters/hackernews.d.ts.map +1 -0
  7. package/dist/adapters/hackernews.js +164 -0
  8. package/dist/adapters/hackernews.js.map +1 -0
  9. package/dist/adapters/index.d.ts +10 -0
  10. package/dist/adapters/index.d.ts.map +1 -0
  11. package/dist/adapters/index.js +9 -0
  12. package/dist/adapters/index.js.map +1 -0
  13. package/dist/adapters/remoteok.d.ts +35 -0
  14. package/dist/adapters/remoteok.d.ts.map +1 -0
  15. package/dist/adapters/remoteok.js +212 -0
  16. package/dist/adapters/remoteok.js.map +1 -0
  17. package/dist/briefing.d.ts +81 -0
  18. package/dist/briefing.d.ts.map +1 -0
  19. package/dist/briefing.js +152 -0
  20. package/dist/briefing.js.map +1 -0
  21. package/dist/cli.d.ts +22 -0
  22. package/dist/cli.d.ts.map +1 -0
  23. package/dist/cli.js +235 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/config.d.ts +91 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +126 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/core/text-processing.d.ts +62 -0
  30. package/dist/core/text-processing.d.ts.map +1 -0
  31. package/dist/core/text-processing.js +187 -0
  32. package/dist/core/text-processing.js.map +1 -0
  33. package/dist/drafting.d.ts +28 -0
  34. package/dist/drafting.d.ts.map +1 -0
  35. package/dist/drafting.js +116 -0
  36. package/dist/drafting.js.map +1 -0
  37. package/dist/gap.d.ts +27 -0
  38. package/dist/gap.d.ts.map +1 -0
  39. package/dist/gap.js +90 -0
  40. package/dist/gap.js.map +1 -0
  41. package/dist/license.d.ts +40 -0
  42. package/dist/license.d.ts.map +1 -0
  43. package/dist/license.js +122 -0
  44. package/dist/license.js.map +1 -0
  45. package/dist/llm-enhance.d.ts +69 -0
  46. package/dist/llm-enhance.d.ts.map +1 -0
  47. package/dist/llm-enhance.js +376 -0
  48. package/dist/llm-enhance.js.map +1 -0
  49. package/dist/matching/engine.d.ts +31 -0
  50. package/dist/matching/engine.d.ts.map +1 -0
  51. package/dist/matching/engine.js +51 -0
  52. package/dist/matching/engine.js.map +1 -0
  53. package/dist/matching/index.d.ts +8 -0
  54. package/dist/matching/index.d.ts.map +1 -0
  55. package/dist/matching/index.js +8 -0
  56. package/dist/matching/index.js.map +1 -0
  57. package/dist/matching/scoring.d.ts +84 -0
  58. package/dist/matching/scoring.d.ts.map +1 -0
  59. package/dist/matching/scoring.js +184 -0
  60. package/dist/matching/scoring.js.map +1 -0
  61. package/dist/models.d.ts +221 -0
  62. package/dist/models.d.ts.map +1 -0
  63. package/dist/models.js +28 -0
  64. package/dist/models.js.map +1 -0
  65. package/dist/requirements.d.ts +22 -0
  66. package/dist/requirements.d.ts.map +1 -0
  67. package/dist/requirements.js +30 -0
  68. package/dist/requirements.js.map +1 -0
  69. package/dist/resume-intel.d.ts +40 -0
  70. package/dist/resume-intel.d.ts.map +1 -0
  71. package/dist/resume-intel.js +111 -0
  72. package/dist/resume-intel.js.map +1 -0
  73. package/dist/sources.d.ts +32 -0
  74. package/dist/sources.d.ts.map +1 -0
  75. package/dist/sources.js +72 -0
  76. package/dist/sources.js.map +1 -0
  77. package/dist/tracking.d.ts +68 -0
  78. package/dist/tracking.d.ts.map +1 -0
  79. package/dist/tracking.js +140 -0
  80. package/dist/tracking.js.map +1 -0
  81. package/package.json +58 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * matching/scoring.ts — Pure per-dimension scoring functions.
3
+ *
4
+ * Each dimension function returns a value in [0, 1].
5
+ * Neutral (0.5) is used when data is missing so absent fields neither
6
+ * reward nor penalise the composite score.
7
+ *
8
+ * compositeScore() uses a multiplicative model:
9
+ *
10
+ * total = sqrt(keyword_score) × qualityBase
11
+ *
12
+ * where qualityBase = weighted sum of experience + salary + work_mode.
13
+ *
14
+ * This means keyword overlap is a signal multiplier, not just another
15
+ * additive term. A job with zero keyword overlap scores 0.0 regardless
16
+ * of how well it matches on salary or work mode — solving the problem
17
+ * of irrelevant jobs floating to the top on neutral dimension scores.
18
+ *
19
+ * sqrt() softens the penalty for partial keyword matches: a job with
20
+ * 25% keyword overlap gets a signal of 0.5 (not 0.25), so genuine
21
+ * partial matches are still surfaced meaningfully.
22
+ *
23
+ * All functions are pure and stateless — safe to unit test in isolation.
24
+ */
25
+ import type { NormalizedJob, UserProfile, MatchBreakdown } from "../models.js";
26
+ /**
27
+ * Score keyword overlap between the user profile and a job posting.
28
+ *
29
+ * Profile corpus: skills + target_roles + resume_summary tokens (combined).
30
+ * Job corpus: title + description tokens.
31
+ *
32
+ * Returns Jaccard-like intersection/union in [0, 1].
33
+ * Returns 0.0 if either corpus tokenises to empty.
34
+ */
35
+ export declare function scoreKeyword(profile: UserProfile, job: NormalizedJob): {
36
+ score: number;
37
+ matched: string[];
38
+ gaps: string[];
39
+ };
40
+ /**
41
+ * Score alignment between user experience years and job requirements.
42
+ *
43
+ * - Returns neutral 0.5 if either side has no data.
44
+ * - Returns 1.0 if the job requires 0 years.
45
+ * - Clamped linear: user_years / job_years, capped at 1.0.
46
+ * Over-qualified candidates are not penalised.
47
+ */
48
+ export declare function scoreExperience(profile: UserProfile, job: NormalizedJob): number;
49
+ /**
50
+ * Score alignment between user salary expectations and the job's posted range.
51
+ *
52
+ * - Returns neutral 0.5 if either side has no data.
53
+ * - Returns 1.0 if job minimum meets or exceeds user minimum.
54
+ * - Proportional score if job pays less, clamped to [0, 1].
55
+ */
56
+ export declare function scoreSalary(profile: UserProfile, job: NormalizedJob): number;
57
+ /**
58
+ * Score alignment between user work mode preference and job work mode.
59
+ *
60
+ * - Returns 1.0 on exact match.
61
+ * - Returns 0.5 if either side is null (insufficient data).
62
+ * - Returns 0.5 if one side is hybrid (partial compatibility).
63
+ * - Returns 0.0 on hard mismatch (remote vs onsite).
64
+ */
65
+ export declare function scoreWorkMode(profile: UserProfile, job: NormalizedJob): number;
66
+ /**
67
+ * Compute the weighted composite score.
68
+ *
69
+ * Formula:
70
+ * qualityBase = (experience × 0.4) + (salary × 0.3) + (work_mode × 0.3)
71
+ * total = sqrt(keyword) × qualityBase
72
+ *
73
+ * The keyword score acts as a signal multiplier:
74
+ * - keyword = 0.0 → total = 0.0 always (the "dentist fix")
75
+ * - keyword = 1.0 → total = qualityBase
76
+ * - keyword = 0.25 → signal = 0.5 (sqrt softens partial-match penalty)
77
+ */
78
+ export declare function compositeScore(profile: UserProfile, job: NormalizedJob): {
79
+ total: number;
80
+ breakdown: MatchBreakdown;
81
+ matched: string[];
82
+ gaps: string[];
83
+ };
84
+ //# sourceMappingURL=scoring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scoring.d.ts","sourceRoot":"","sources":["../../src/matching/scoring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAqC/E;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,aAAa,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAmBtD;AAMD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,aAAa,GACjB,MAAM,CASR;AAMD;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,aAAa,GACjB,MAAM,CAUR;AAMD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,aAAa,GACjB,MAAM,CAUR;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,aAAa,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAoBjF"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * matching/scoring.ts — Pure per-dimension scoring functions.
3
+ *
4
+ * Each dimension function returns a value in [0, 1].
5
+ * Neutral (0.5) is used when data is missing so absent fields neither
6
+ * reward nor penalise the composite score.
7
+ *
8
+ * compositeScore() uses a multiplicative model:
9
+ *
10
+ * total = sqrt(keyword_score) × qualityBase
11
+ *
12
+ * where qualityBase = weighted sum of experience + salary + work_mode.
13
+ *
14
+ * This means keyword overlap is a signal multiplier, not just another
15
+ * additive term. A job with zero keyword overlap scores 0.0 regardless
16
+ * of how well it matches on salary or work mode — solving the problem
17
+ * of irrelevant jobs floating to the top on neutral dimension scores.
18
+ *
19
+ * sqrt() softens the penalty for partial keyword matches: a job with
20
+ * 25% keyword overlap gets a signal of 0.5 (not 0.25), so genuine
21
+ * partial matches are still surfaced meaningfully.
22
+ *
23
+ * All functions are pure and stateless — safe to unit test in isolation.
24
+ */
25
+ import { tokenizeUnique, tokenOverlap, matchedTokens, gapTokens, } from "../core/text-processing.js";
26
+ // ---------------------------------------------------------------------------
27
+ // Weights for the quality dimensions (must sum to 1.0)
28
+ // ---------------------------------------------------------------------------
29
+ /**
30
+ * Quality dimension weights — applied AFTER the keyword signal multiplier.
31
+ * These normalise the three metadata dimensions to a [0, 1] quality base.
32
+ *
33
+ * Originating weights (additive model):
34
+ * experience 20%, salary 15%, work_mode 15% → sum = 50%
35
+ * Normalised to sum to 1.0 for the quality base:
36
+ * experience 0.4, salary 0.3, work_mode 0.3
37
+ */
38
+ const QUALITY_WEIGHTS = {
39
+ experience: 0.4,
40
+ salary: 0.3,
41
+ work_mode: 0.3,
42
+ };
43
+ // Verify weights sum to 1.0 at module load time
44
+ const _weightSum = Object.values(QUALITY_WEIGHTS).reduce((a, b) => a + b, 0);
45
+ if (Math.abs(_weightSum - 1.0) > 1e-9) {
46
+ throw new Error(`QUALITY_WEIGHTS must sum to 1.0, got ${_weightSum}`);
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // Keyword score
50
+ // ---------------------------------------------------------------------------
51
+ /**
52
+ * Score keyword overlap between the user profile and a job posting.
53
+ *
54
+ * Profile corpus: skills + target_roles + resume_summary tokens (combined).
55
+ * Job corpus: title + description tokens.
56
+ *
57
+ * Returns Jaccard-like intersection/union in [0, 1].
58
+ * Returns 0.0 if either corpus tokenises to empty.
59
+ */
60
+ export function scoreKeyword(profile, job) {
61
+ const profileText = [
62
+ ...profile.skills,
63
+ ...profile.target_roles,
64
+ profile.resume_summary ?? "",
65
+ ].join(" ");
66
+ const profileTokens = tokenizeUnique(profileText);
67
+ const jobTokens = tokenizeUnique(`${job.title} ${job.description}`);
68
+ if (profileTokens.length === 0 || jobTokens.length === 0) {
69
+ return { score: 0.0, matched: [], gaps: [] };
70
+ }
71
+ return {
72
+ score: tokenOverlap(profileTokens, jobTokens),
73
+ matched: matchedTokens(profileTokens, jobTokens),
74
+ gaps: gapTokens(jobTokens, profileTokens),
75
+ };
76
+ }
77
+ // ---------------------------------------------------------------------------
78
+ // Experience score
79
+ // ---------------------------------------------------------------------------
80
+ /**
81
+ * Score alignment between user experience years and job requirements.
82
+ *
83
+ * - Returns neutral 0.5 if either side has no data.
84
+ * - Returns 1.0 if the job requires 0 years.
85
+ * - Clamped linear: user_years / job_years, capped at 1.0.
86
+ * Over-qualified candidates are not penalised.
87
+ */
88
+ export function scoreExperience(profile, job) {
89
+ const userYears = profile.experience_years;
90
+ const jobYears = job.experience_years;
91
+ if (userYears === null || userYears === undefined)
92
+ return 0.5;
93
+ if (jobYears === null || jobYears === undefined)
94
+ return 0.5;
95
+ if (jobYears === 0)
96
+ return 1.0;
97
+ return Math.min(userYears / jobYears, 1.0);
98
+ }
99
+ // ---------------------------------------------------------------------------
100
+ // Salary score
101
+ // ---------------------------------------------------------------------------
102
+ /**
103
+ * Score alignment between user salary expectations and the job's posted range.
104
+ *
105
+ * - Returns neutral 0.5 if either side has no data.
106
+ * - Returns 1.0 if job minimum meets or exceeds user minimum.
107
+ * - Proportional score if job pays less, clamped to [0, 1].
108
+ */
109
+ export function scoreSalary(profile, job) {
110
+ const userMin = profile.salary_min;
111
+ const jobMin = job.salary_min;
112
+ if (userMin === null || userMin === undefined)
113
+ return 0.5;
114
+ if (jobMin === null || jobMin === undefined)
115
+ return 0.5;
116
+ if (userMin === 0)
117
+ return 1.0;
118
+ if (jobMin >= userMin)
119
+ return 1.0;
120
+ return Math.max(jobMin / userMin, 0.0);
121
+ }
122
+ // ---------------------------------------------------------------------------
123
+ // Work mode score
124
+ // ---------------------------------------------------------------------------
125
+ /**
126
+ * Score alignment between user work mode preference and job work mode.
127
+ *
128
+ * - Returns 1.0 on exact match.
129
+ * - Returns 0.5 if either side is null (insufficient data).
130
+ * - Returns 0.5 if one side is hybrid (partial compatibility).
131
+ * - Returns 0.0 on hard mismatch (remote vs onsite).
132
+ */
133
+ export function scoreWorkMode(profile, job) {
134
+ const userMode = profile.work_mode;
135
+ const jobMode = job.work_mode;
136
+ if (userMode === null || userMode === undefined)
137
+ return 0.5;
138
+ if (jobMode === null || jobMode === undefined)
139
+ return 0.5;
140
+ if (userMode === jobMode)
141
+ return 1.0;
142
+ if (userMode === "hybrid" || jobMode === "hybrid")
143
+ return 0.5;
144
+ return 0.0;
145
+ }
146
+ // ---------------------------------------------------------------------------
147
+ // Composite score — multiplicative model
148
+ // ---------------------------------------------------------------------------
149
+ /**
150
+ * Compute the weighted composite score.
151
+ *
152
+ * Formula:
153
+ * qualityBase = (experience × 0.4) + (salary × 0.3) + (work_mode × 0.3)
154
+ * total = sqrt(keyword) × qualityBase
155
+ *
156
+ * The keyword score acts as a signal multiplier:
157
+ * - keyword = 0.0 → total = 0.0 always (the "dentist fix")
158
+ * - keyword = 1.0 → total = qualityBase
159
+ * - keyword = 0.25 → signal = 0.5 (sqrt softens partial-match penalty)
160
+ */
161
+ export function compositeScore(profile, job) {
162
+ const kw = scoreKeyword(profile, job);
163
+ const experience = scoreExperience(profile, job);
164
+ const salary = scoreSalary(profile, job);
165
+ const work_mode = scoreWorkMode(profile, job);
166
+ const qualityBase = experience * QUALITY_WEIGHTS.experience +
167
+ salary * QUALITY_WEIGHTS.salary +
168
+ work_mode * QUALITY_WEIGHTS.work_mode;
169
+ const signal = Math.sqrt(kw.score);
170
+ const total = roundScore(signal * qualityBase);
171
+ return {
172
+ total,
173
+ breakdown: { keyword: kw.score, experience, salary, work_mode },
174
+ matched: kw.matched,
175
+ gaps: kw.gaps,
176
+ };
177
+ }
178
+ // ---------------------------------------------------------------------------
179
+ // Helpers
180
+ // ---------------------------------------------------------------------------
181
+ function roundScore(n) {
182
+ return Math.round(n * 10_000) / 10_000;
183
+ }
184
+ //# sourceMappingURL=scoring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scoring.js","sourceRoot":"","sources":["../../src/matching/scoring.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * models.ts — Canonical data schemas for CareerClaw.
3
+ *
4
+ * These types are the single source of truth for all data flowing through
5
+ * the pipeline. JSON serialisation formats are intentionally kept identical
6
+ * to the Python careerclaw package so that profile.json, tracking.json, and
7
+ * runs.jsonl files remain compatible between the two implementations.
8
+ */
9
+ /** Application lifecycle states, persisted as lowercase strings in JSON. */
10
+ export type ApplicationStatus = "saved" | "applied" | "interviewing" | "rejected" | "offer";
11
+ /** Work-mode preference. Matches the values accepted in profile.json. */
12
+ export type WorkMode = "remote" | "hybrid" | "onsite" | "any";
13
+ /** Canonical source identifiers for job listings. */
14
+ export type JobSource = "remoteok" | "hackernews" | "unknown";
15
+ /**
16
+ * A normalised job record produced by any adapter.
17
+ *
18
+ * `job_id` is a stable SHA-256 hex digest of the canonical URL (or
19
+ * company+title when no URL is available). It is used as the primary key
20
+ * in tracking.json and for deduplication across sources and runs.
21
+ */
22
+ export interface NormalizedJob {
23
+ /** Stable hash-based identifier. */
24
+ job_id: string;
25
+ title: string;
26
+ company: string;
27
+ /** Raw location string from the source ("Remote", "NYC", etc.). */
28
+ location: string;
29
+ description: string;
30
+ /** Canonical URL — empty string when unavailable. */
31
+ url: string;
32
+ source: JobSource;
33
+ /** Minimum salary in annualised USD, or null when not stated. */
34
+ salary_min: number | null;
35
+ /** Maximum salary in annualised USD, or null when not stated. */
36
+ salary_max: number | null;
37
+ /** Normalised work mode inferred from job text, or null when ambiguous. */
38
+ work_mode: WorkMode | null;
39
+ /** Required years of experience inferred from job text, or null. */
40
+ experience_years: number | null;
41
+ /** ISO-8601 UTC timestamp of when the job was posted, or null. */
42
+ posted_at: string | null;
43
+ /** ISO-8601 UTC timestamp of when this record was fetched. */
44
+ fetched_at: string;
45
+ }
46
+ /**
47
+ * User profile loaded from `.careerclaw/profile.json`.
48
+ *
49
+ * All fields are optional so that partial profiles (e.g. profiles created
50
+ * incrementally via the OpenClaw agent wizard) are still valid at runtime.
51
+ * The briefing pipeline degrades gracefully when fields are missing.
52
+ */
53
+ export interface UserProfile {
54
+ skills: string[];
55
+ target_roles: string[];
56
+ /** Total years of professional experience. */
57
+ experience_years: number | null;
58
+ work_mode: WorkMode | null;
59
+ /** Short free-text resume summary used for keyword extraction. */
60
+ resume_summary: string | null;
61
+ /** City / region string used for location scoring. */
62
+ location: string | null;
63
+ /** Minimum acceptable annual salary in USD. */
64
+ salary_min: number | null;
65
+ }
66
+ /**
67
+ * A single tracked application in `.careerclaw/tracking.json`.
68
+ *
69
+ * Keyed by `job_id` in the JSON file. All timestamps are ISO-8601 UTC.
70
+ */
71
+ export interface TrackingEntry {
72
+ job_id: string;
73
+ status: ApplicationStatus;
74
+ /** Snapshot of the job title at time of saving. */
75
+ title: string;
76
+ /** Snapshot of the company name at time of saving. */
77
+ company: string;
78
+ url: string;
79
+ source: JobSource;
80
+ /** ISO-8601 UTC timestamp when the entry was first saved. */
81
+ saved_at: string;
82
+ /** ISO-8601 UTC timestamp when the application was submitted, or null. */
83
+ applied_at: string | null;
84
+ /** ISO-8601 UTC timestamp when the entry was last updated. */
85
+ updated_at: string;
86
+ /** ISO-8601 UTC timestamp when this job was last seen in a briefing run. */
87
+ last_seen_at: string | null;
88
+ notes: string | null;
89
+ }
90
+ /**
91
+ * A single run record appended to `.careerclaw/runs.jsonl`.
92
+ *
93
+ * Each line in the JSONL file is one serialised BriefingRun.
94
+ */
95
+ export interface BriefingRun {
96
+ /** Random UUID v4 identifying this run. */
97
+ run_id: string;
98
+ /** ISO-8601 UTC timestamp when the run started. */
99
+ run_at: string;
100
+ /** Whether this was a dry-run (no writes to disk). */
101
+ dry_run: boolean;
102
+ /** Total jobs fetched across all sources. */
103
+ jobs_fetched: number;
104
+ /** Jobs that passed deduplication and were considered for ranking. */
105
+ jobs_ranked: number;
106
+ /** Top-k jobs returned by the engine. */
107
+ jobs_matched: number;
108
+ /** Source breakdown, e.g. { remoteok: 12, hackernews: 7 }. */
109
+ sources: Partial<Record<JobSource, number>>;
110
+ /** Wall-clock duration in milliseconds for each pipeline stage. */
111
+ timings: {
112
+ fetch_ms: number | null;
113
+ rank_ms: number | null;
114
+ draft_ms: number | null;
115
+ persist_ms: number | null;
116
+ };
117
+ /** careerclaw-js package version that produced this run. */
118
+ version: string;
119
+ }
120
+ /**
121
+ * Per-dimension score breakdown produced by the matching engine.
122
+ * All values are in [0, 1].
123
+ */
124
+ export interface MatchBreakdown {
125
+ /** Raw Jaccard-like keyword overlap score in [0, 1]. */
126
+ keyword: number;
127
+ experience: number;
128
+ salary: number;
129
+ work_mode: number;
130
+ }
131
+ /**
132
+ * A ranked job with its composite score and explanation.
133
+ */
134
+ export interface ScoredJob {
135
+ job: NormalizedJob;
136
+ /** Weighted composite score in [0, 1]. */
137
+ score: number;
138
+ breakdown: MatchBreakdown;
139
+ /** Human-readable explanation tokens (matched keywords etc.). */
140
+ matched_keywords: string[];
141
+ /** Skills/requirements in the job not present in the user profile. */
142
+ gap_keywords: string[];
143
+ }
144
+ /**
145
+ * Structured requirements extracted from a job description.
146
+ * Used as the job corpus for gap analysis.
147
+ */
148
+ export interface JobRequirements {
149
+ keywords: string[];
150
+ phrases: string[];
151
+ }
152
+ /**
153
+ * Section-aware keyword extraction from a resume / profile.
154
+ * Schema is JSON-compatible with the Python careerclaw output.
155
+ */
156
+ export interface ResumeIntelligence {
157
+ extracted_keywords: string[];
158
+ extracted_phrases: string[];
159
+ keyword_stream: string[];
160
+ phrase_stream: string[];
161
+ impact_signals: string[];
162
+ keyword_weights: Record<string, number>;
163
+ phrase_weights: Record<string, number>;
164
+ source: 'summary_only' | 'resume_text' | 'skills_injected';
165
+ }
166
+ /**
167
+ * Result of gapAnalysis() — fit score and matched/gap keyword lists.
168
+ */
169
+ export interface GapAnalysisResult {
170
+ fit_score: number;
171
+ fit_score_unweighted: number;
172
+ signals: {
173
+ keywords: string[];
174
+ phrases: string[];
175
+ };
176
+ gaps: {
177
+ keywords: string[];
178
+ phrases: string[];
179
+ };
180
+ summary: {
181
+ top_signals: {
182
+ keywords: string[];
183
+ phrases: string[];
184
+ };
185
+ top_gaps: {
186
+ keywords: string[];
187
+ phrases: string[];
188
+ };
189
+ };
190
+ }
191
+ export interface OutreachDraft {
192
+ job_id: string;
193
+ subject: string;
194
+ body: string;
195
+ /** True when the draft was produced by LLM enhancement (Pro tier). */
196
+ llm_enhanced: boolean;
197
+ }
198
+ /** Returns an ISO-8601 UTC timestamp string for the current moment. */
199
+ export declare function utcNow(): string;
200
+ /** Returns a default empty UserProfile. */
201
+ export declare function emptyProfile(): UserProfile;
202
+ /**
203
+ * The complete output of a single briefing run.
204
+ * This is the stable JSON schema consumed by OpenClaw/ClawHub agents.
205
+ */
206
+ export interface BriefingResult {
207
+ /** The BriefingRun record that was (or would be) appended to runs.jsonl. */
208
+ run: BriefingRun;
209
+ /** Top-K scored jobs produced by the ranking engine. */
210
+ matches: ScoredJob[];
211
+ /** One OutreachDraft per match, in the same order as matches[]. */
212
+ drafts: OutreachDraft[];
213
+ /** Counts from upsertEntries() — always accurate even in dry-run. */
214
+ tracking: {
215
+ created: number;
216
+ already_present: number;
217
+ };
218
+ /** Whether this was a dry run (no files written). */
219
+ dry_run: boolean;
220
+ }
221
+ //# sourceMappingURL=models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"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"}
package/dist/models.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * models.ts — Canonical data schemas for CareerClaw.
3
+ *
4
+ * These types are the single source of truth for all data flowing through
5
+ * the pipeline. JSON serialisation formats are intentionally kept identical
6
+ * to the Python careerclaw package so that profile.json, tracking.json, and
7
+ * runs.jsonl files remain compatible between the two implementations.
8
+ */
9
+ // ---------------------------------------------------------------------------
10
+ // Helpers
11
+ // ---------------------------------------------------------------------------
12
+ /** Returns an ISO-8601 UTC timestamp string for the current moment. */
13
+ export function utcNow() {
14
+ return new Date().toISOString();
15
+ }
16
+ /** Returns a default empty UserProfile. */
17
+ export function emptyProfile() {
18
+ return {
19
+ skills: [],
20
+ target_roles: [],
21
+ experience_years: null,
22
+ work_mode: null,
23
+ resume_summary: null,
24
+ location: null,
25
+ salary_min: null,
26
+ };
27
+ }
28
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAyNH,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,uEAAuE;AACvE,MAAM,UAAU,MAAM;IACpB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,YAAY;IAC1B,OAAO;QACL,MAAM,EAAE,EAAE;QACV,YAAY,EAAE,EAAE;QAChB,gBAAgB,EAAE,IAAI;QACtB,SAAS,EAAE,IAAI;QACf,cAAc,EAAE,IAAI;QACpB,QAAQ,EAAE,IAAI;QACd,UAAU,EAAE,IAAI;KACjB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * requirements.ts — Job requirements extractor.
3
+ *
4
+ * Extracts a structured keyword + phrase corpus from a job posting's
5
+ * title and description. This corpus is the "job side" input for
6
+ * gap analysis (Phase 5) and is distinct from the full tokenisation
7
+ * used by the matching engine — here we want raw coverage, not scoring.
8
+ *
9
+ * No heuristics beyond tokenisation: STOPWORDS filtering already removes
10
+ * boilerplate (apply, candidate, competitive, etc.) so the resulting
11
+ * keyword list reflects genuine technical and role signals.
12
+ */
13
+ import type { NormalizedJob, JobRequirements } from "./models.js";
14
+ /**
15
+ * Extract requirements corpus from a job posting.
16
+ *
17
+ * Combines title and description into a single text corpus, then
18
+ * tokenises and extracts bigram/trigram phrases. Both lists are
19
+ * deduplicated (insertion order preserved).
20
+ */
21
+ export declare function extractJobRequirements(job: NormalizedJob): JobRequirements;
22
+ //# sourceMappingURL=requirements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requirements.d.ts","sourceRoot":"","sources":["../src/requirements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAOlE;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,aAAa,GAAG,eAAe,CAK1E"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * requirements.ts — Job requirements extractor.
3
+ *
4
+ * Extracts a structured keyword + phrase corpus from a job posting's
5
+ * title and description. This corpus is the "job side" input for
6
+ * gap analysis (Phase 5) and is distinct from the full tokenisation
7
+ * used by the matching engine — here we want raw coverage, not scoring.
8
+ *
9
+ * No heuristics beyond tokenisation: STOPWORDS filtering already removes
10
+ * boilerplate (apply, candidate, competitive, etc.) so the resulting
11
+ * keyword list reflects genuine technical and role signals.
12
+ */
13
+ import { tokenizeUnique, extractPhrasesFromText } from "./core/text-processing.js";
14
+ // ---------------------------------------------------------------------------
15
+ // Public API
16
+ // ---------------------------------------------------------------------------
17
+ /**
18
+ * Extract requirements corpus from a job posting.
19
+ *
20
+ * Combines title and description into a single text corpus, then
21
+ * tokenises and extracts bigram/trigram phrases. Both lists are
22
+ * deduplicated (insertion order preserved).
23
+ */
24
+ export function extractJobRequirements(job) {
25
+ const text = `${job.title} ${job.description}`;
26
+ const keywords = tokenizeUnique(text);
27
+ const phrases = extractPhrasesFromText(text);
28
+ return { keywords, phrases };
29
+ }
30
+ //# sourceMappingURL=requirements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requirements.js","sourceRoot":"","sources":["../src/requirements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAEnF,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAkB;IACvD,MAAM,IAAI,GAAG,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;IAC/C,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAC7C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * resume-intel.ts — Section-aware resume intelligence builder.
3
+ *
4
+ * `buildResumeIntelligence()` extracts a weighted keyword/phrase corpus
5
+ * from multiple resume/profile inputs. Each source has a section weight
6
+ * (from SECTION_WEIGHTS) that determines how important its tokens are
7
+ * for gap analysis.
8
+ *
9
+ * Key design from PR-E: UserProfile.skills are injected as a synthetic
10
+ * "skills" section at weight 1.0 — the highest weight. This prevents
11
+ * skills the user explicitly listed from appearing as gaps.
12
+ *
13
+ * Output schema is JSON-compatible with Python careerclaw so
14
+ * `.careerclaw/resume_intel.json` files are portable across
15
+ * implementations.
16
+ */
17
+ import type { ResumeIntelligence } from "./models.js";
18
+ export interface ResumeIntelligenceParams {
19
+ /** Short free-text summary from UserProfile.resume_summary. */
20
+ resume_summary: string;
21
+ /** Full resume text from resume.txt or PDF extraction (optional). */
22
+ resume_text?: string;
23
+ /** UserProfile.skills — injected as a synthetic skills section (PR-E fix). */
24
+ skills?: string[];
25
+ /** UserProfile.target_roles — injected at summary weight. */
26
+ target_roles?: string[];
27
+ }
28
+ /**
29
+ * Build section-aware resume intelligence from available inputs.
30
+ *
31
+ * Sections processed (highest weight first):
32
+ * 1. skills (weight 1.0) — UserProfile.skills list
33
+ * 2. summary (weight 0.8) — resume_summary + target_roles
34
+ * 3. experience (weight 0.6) — resume_text (if provided)
35
+ *
36
+ * For each keyword, the final keyword_weight is the maximum weight
37
+ * across all sections in which it appeared.
38
+ */
39
+ export declare function buildResumeIntelligence(params: ResumeIntelligenceParams): ResumeIntelligence;
40
+ //# sourceMappingURL=resume-intel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-intel.d.ts","sourceRoot":"","sources":["../src/resume-intel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAWtD,MAAM,WAAW,wBAAwB;IACvC,+DAA+D;IAC/D,cAAc,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,wBAAwB,GAC/B,kBAAkB,CA8FpB"}