careerclaw-js 1.0.5 → 1.1.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 CHANGED
@@ -1,485 +1,584 @@
1
- # Changelog
2
-
3
- All notable changes to careerclaw-js are documented here.
4
- Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
- Versioning follows [Semantic Versioning](https://semver.org/).
6
-
7
- ---
8
-
9
- ## [Unreleased]
10
-
11
- ---
12
-
13
- ## [1.0.5] - 2026-03-18
14
-
15
- ### Fixed
16
-
17
- - Fixed hex and decimal numeric HTML entities (e.g. `/`, `/`) leaking
18
- undecoded into job titles, descriptions, and outreach drafts
19
- (`src/adapters/remoteok.ts`). Added generic catch-all decoding for both
20
- `&#xHH;` and `&#DD;` forms before the named-entity block in `stripHtml`.
21
- Triggered by the fast-xml-parser v5 upgrade (CVE-2026-26278), which stopped
22
- silently pre-decoding entities inside CDATA content that v4 had handled
23
- transparently.
24
-
25
- ---
26
-
27
- ## [1.0.4] - 2026-03-08
28
-
29
- ### Changed
30
- - Runtime check replaced — no more auto-install, just a non-blocking update nudge
31
- - Write-failure gate added in Step 3
32
- - Resume write failed row added to error table
33
-
34
- ---
35
-
36
- ## [1.0.3] - 2026-03-08
37
-
38
- ### Fixed
39
- - Agent no longer presents a multi-question setup form when profile is missing — redirects to resume upload only
40
- - Agent no longer invents career frameworks, phases, or methodologies not defined in this file
41
- - Agent no longer enters consultant mode during first-time setup
42
-
43
- ### Changed
44
- - Rewrote SKILL.md from persona/behavior narrative format to numbered runbook format
45
- - Added hard Rules section at top with six explicit prohibitions
46
- - Added profile existence check (Step 1) as hard gate before any command runs
47
- - Setup flow now asks one question at a time — work mode first, salary second
48
- - Removed Agent Persona, Behavior 1, and Behavior 2 sections entirely
49
- - Bumped runtime self-healing check expected version to 1.0.3
50
-
51
- ---
52
-
53
- ## [1.0.2] - 2026-03-07
54
-
55
- ### Fixed
56
- - Behavior 2 (Strategic Gap Closing) no longer fires during First-Time Setup.
57
- The agent was entering consultant mode after resume intake and ending setup
58
- with open-ended targeting questions instead of proceeding to the first briefing.
59
-
60
- ### Changed
61
- - Added explicit activation gate to Behavior 2: only applies after setup is
62
- complete and only when the user asks a listed trigger phrase.
63
- - Step 2 of First-Time Setup now specifies a strict exit condition: collect
64
- work mode and salary floor, then proceed directly to Step 3 with no
65
- analysis, strategy suggestions, or targeting options.
66
- - Added three entries to "What Not to Do" to reinforce setup boundaries.
67
-
68
- ---
69
-
70
- ## [1.0.1] - 2026-03-06
71
-
72
- ### Changed
73
- - Hardened the public `SKILL.md` contract for ClawHub/OpenClaw deployment.
74
- - Standardized runtime behavior to a single install/execute approach:
75
- - self-healing install with `npm install -g careerclaw-js@"$EXPECTED"`
76
- - direct execution with `careerclaw-js ...`
77
- - Updated required runtime bins to match the Node/npm execution path.
78
- - Scoped CareerClaw behavior more clearly as an invoked skill instead of a session-wide assistant.
79
- - Simplified first-run onboarding around canonical `.careerclaw/resume.txt` handling.
80
- - Reduced first-run friction by keeping setup resume-first and minimizing follow-up questions.
81
-
82
- ### Improved
83
- - Restored Strategic Gap Closing behavior so CareerClaw can act more like a practical career consultant when the fit is imperfect.
84
- - Improved recommendation tone and guidance for fatal, acceptable, and bridgeable gaps.
85
- - Cleaned up Pro activation flow so premium setup is only introduced when it is actually needed.
86
- - Restored the public Gumroad purchase link for CareerClaw Pro while keeping internal product IDs out of user-facing setup.
87
-
88
- ### Removed
89
- - Removed internal Gumroad product ID exposure from the public skill surface.
90
- - Removed Python compatibility messaging from `SKILL.md` to keep the public skill contract focused on the current Node-based experience.
91
-
92
- ---
93
-
94
- ## [1.0.0] — 2026-03-06
95
-
96
- ### Added
97
-
98
- - `SKILL.md` full rewrite (Phase 12): Agent Persona section; zero-config resume
99
- intake; Daily Stand-up behavior (reads `tracking.json` on session start,
100
- surfaces still-live and undrafted jobs); Strategic Gap Closing (coach tone
101
- for matches with gaps > 0.6 score); Sunday Night Strategy (first weekday of
102
- month = HN priority, Sunday evening prompt, Monday/Friday timing context);
103
- Tier-1 upsell trigger (Google, Meta, Apple, Stripe, Airbnb, Netflix);
104
- Free vs Pro comparison table; `optionalEnv` frontmatter for all 12 env vars
105
- - `RELEASE_CHECKLIST.md` — pre-publish gate, npm publish steps, VirusTotal
106
- scan instructions, OpenClaw test gate, ClawHub submission checklist
107
-
108
- ### Changed
109
-
110
- - `src/llm-enhance.ts` — OpenAI dual-transport support: `gpt-4o` family uses
111
- `/v1/chat/completions` with `max_completion_tokens`; newer models (gpt-5.x+)
112
- use `/v1/responses` with `max_output_tokens`; transport auto-detected via
113
- `defaultOpenAITransport(model)` and injectable per `ChainCandidate`;
114
- `SYSTEM_PROMPT` rewritten with Bridge Sentence method as core doctrine,
115
- 250/300-word hard limits, 22 banned phrases by name; `buildPrompt()` updated
116
- with explicit word limit and Bridge Sentence paragraph-level instruction
117
- - `src/config.ts` `CAREERCLAW_DIR` empty-string guard: `??` replaced with
118
- `.trim().length > 0` check so blank `CAREERCLAW_DIR=` in `.env` correctly
119
- falls back to `.careerclaw/`
120
- - `scripts/smoke_llm.ts` Pro key section now calls `checkLicense()` live
121
- against Gumroad when both keys are set; stale "not yet implemented" message
122
- removed; Polar.sh URL replaced with Gumroad URL
123
- - `README.md` — Polar.sh references replaced with Gumroad; CLI commands
124
- corrected (no `briefing` subcommand); `--resume-pdf` removed (not
125
- implemented); env vars table updated; roadmap updated through Phase 13
126
-
127
- ### Notes
128
-
129
- 270 tests across 16 files, all passing. `tsc --noEmit` clean.
130
- No new production dependencies. First ClawHub-ready release.
131
- OpenAI transport validated against gpt-4o-mini (chat) and gpt-5.2 (responses)
132
- in live smoke testing.
133
-
134
- ---
135
-
136
- ## [0.11.0] — 2026-03-05
137
-
138
- ### Added
139
-
140
- - `src/license.ts` `checkLicense(key, options?)` `Promise<LicenseResult>`:
141
- Gumroad Pro license validation with 7-day offline cache; raw key never
142
- written to disk — only `sha256(key)` cached; `{ valid: false, source: "none" }`
143
- returned immediately when `CAREERCLAW_GUMROAD_PRODUCT_ID` is unset so Free
144
- tier users are unaffected; `fetchFn` and `cachePath` injectable for offline tests
145
- - `src/tests/license.test.ts` 12 offline tests covering the missing product ID
146
- guard, fresh/stale/missing/wrong-key cache branches, all Gumroad API response
147
- variants (success, refunded, chargebacked, 404, 500), never-throws invariant,
148
- and hash safety (raw key must not appear in cache file)
149
-
150
- ### Changed
151
-
152
- - `src/briefing.ts` bare `proKey` presence check replaced with
153
- `await checkLicense(proKey)`; `isProActive` is now driven by
154
- `licenseResult.valid`; `BriefingOptions` gains `licenseFetchFn` and
155
- `licenseCachePath` for test injection
156
- - `src/config.ts` added `GUMROAD_PRODUCT_ID`, `GUMROAD_API_BASE`,
157
- `LICENSE_CACHE_TTL_MS` (7 days in ms); `POLAR_PRODUCT_SLUG` and
158
- `POLAR_API_BASE` retained and marked `@deprecated` with
159
- `TODO: Phase 11-Polar` migration comment
160
- - `.env.example` Pro purchase URL updated to Gumroad;
161
- `CAREERCLAW_GUMROAD_PRODUCT_ID=` added with dashboard instructions
162
-
163
- ### Security
164
-
165
- - Raw license key is never written to disk; only `sha256(key)` is persisted
166
- - Gumroad validation uses `increment_uses_count=false` — validation calls
167
- do not consume customer usage quota
168
-
169
- ### Notes
170
-
171
- 270 tests across 16 files, all passing. `tsc --noEmit` clean.
172
- No new production dependencies. Polar migration path preserved via
173
- `@deprecated` constants and `TODO: Phase 11-Polar` comment in `config.ts`.
174
-
175
- ---
176
-
177
- ## [0.10.0] 2026-03-05
178
-
179
- ### Added
180
-
181
- - `src/llm-enhance.ts` — `enhanceDraft(job, profile, resumeIntel, draft, gapKeywords, options)`:
182
- Pro tier LLM-powered outreach draft generation; parses `LLM_CHAIN` into a left-to-right failover
183
- list; per-candidate retry loop up to `LLM_MAX_RETRIES`; circuit breaker opens after
184
- `LLM_CIRCUIT_BREAKER_FAILS` consecutive failures; `_chainOverride` for offline testing
185
- - `src/tests/llm-enhance.test.ts` 12 offline tests covering Anthropic path, OpenAI path,
186
- HTTP error fallback, unparseable response fallback, never-throws invariant, circuit breaker
187
- call-count bounds, privacy (raw resume text must not appear in outbound request payload),
188
- and empty-chain no-op
189
-
190
- ### Changed
191
-
192
- - `src/briefing.ts` — `BriefingOptions` gains `resumeIntel?: ResumeIntelligence`,
193
- `proKey?: string`, `enhanceFetchFn?`; draft stage converted to `async`; calls `enhanceDraft()`
194
- per match when `proKey` is set — falls back to deterministic baseline on any LLM failure
195
- - `src/cli.ts` — calls `buildResumeIntelligence()` after resume load; passes `resumeIntel` and
196
- `PRO_KEY` to `runBriefing()`; Pro enhancement path is now live from the CLI
197
-
198
- ### Security
199
-
200
- - LLM prompt contains only extracted keyword signals (`impact_signals` 12 tokens,
201
- `gap_keywords` 6 tokens) raw resume text and `resume_summary` are never sent to
202
- any external API
203
-
204
- ### Notes
205
-
206
- 258 tests across 15 files, all passing. `tsc --noEmit` clean. No new production dependencies.
207
- License validation (`CAREERCLAW_PRO_KEY` checked against Gumroad) is deferred to Phase 11 (v0.11.0) —
208
- `proKey` is trusted by presence only in this version.
209
-
210
- ---
211
-
212
- ## [0.8.1] 2026-03-04
213
-
214
- ### Fixed
215
-
216
- - **Dentist problem**: replaced additive composite score with a multiplicative model (`total = sqrt(keyword) × qualityBase`);
217
- zero keyword overlap now always produces a score of 0.0 regardless of metadata alignment irrelevant jobs can no longer float to the
218
- top on neutral dimension scores
219
- - Signal gate added to `rankJobs()`: jobs with keyword score below `minKeywordScore` (default: 0.01) are hard-filtered before ranking
220
- - `matched_keywords` and `gap_keywords` were hardcoded empty in `engine.ts`; now wired from `compositeScore()` output
221
- - HTML entity `&#x2F;` (and all `&#x[hex];` sequences) now decoded in `stripHtml()` — fixes `ML&#x2F;AI` appearing in HN job titles and
222
- gap tokens
223
- - `stripHtml()` now applied to parsed HN job title and company name (previously body text only)
224
- - Contraction tokens (`i'm`, `i've`, `don't`, etc.) added to `STOPWORDS` eliminates noise in gap keyword output
225
-
226
- ### Changed
227
-
228
- - `MatchBreakdown` field names renamed: `keyword_score keyword`, `experience_score → experience`, `salary_score → salary`,
229
- `work_mode_score work_mode`
230
- - `compositeScore()` return type extended: now includes `matched` and `gaps` string arrays alongside `total` and `breakdown`
231
- - Default LLM model updated: `claude-sonnet-4-20250514` → `claude-haiku-4-5-20251001`
232
- - `scripts/smoke_briefing.ts` refactored to multi-profile mode; select profile via `PROFILE=0|1|2` env var
233
-
234
- ### Added
235
-
236
- - `.env.example` — full credential reference (OpenClaw gateway, agent LLM keys, Pro license, draft enhancement keys, failover chain config)
237
- - `SECURITY.md` — local-first security architecture, credential handling policy, external network call inventory, LLM data disclosure
238
- - `src/config.ts`: `LLM_ANTHROPIC_KEY`, `LLM_OPENAI_KEY`, `LLM_CHAIN`, `LLM_MAX_RETRIES`, `LLM_CIRCUIT_BREAKER_FAILS`
239
- - `scripts/smoke_llm.ts` — LLM connectivity and Pro key smoke test; `npm run smoke:llm`
240
-
241
- ### Notes
242
-
243
- 234 tests across 13 files, all passing. No new production dependencies.
244
- `MatchBreakdown` rename is a breaking internal change no public API consumers exist prior to the CLI (Phase 9).
245
-
246
- ---
247
-
248
- ## [0.8.0] — 2026-03-04
249
-
250
- ### Added
251
-
252
- - `src/briefing.ts` — `runBriefing(profile, options)`: end-to-end pipeline orchestrator; four timed stages (fetch, rank, draft,
253
- persist); skill-first design — profile is a parameter, never loaded from disk; `fetchFn` and `repo` are injectable for testing; dry-run
254
- suppresses all writes while keeping counts accurate; catastrophic fetch failure returns empty result rather than throwing;
255
- `run_id` generated via `crypto.randomUUID()` (Node built-in); `version` read from package.json at runtime via `createRequire`
256
- - `BriefingResult` interface added to `src/models.ts` — stable JSON output schema for OpenClaw/ClawHub agent consumption: `run`,
257
- `matches`, `drafts`, `tracking`, `dry_run`
258
- - `src/tests/briefing.test.ts` 17 integration tests; all offline via stubbed `fetchFn` and `tmpdir`-backed `TrackingRepository`
259
-
260
- ### Fixed
261
-
262
- - `draftOutreach()` and `upsertEntries()` were being passed `ScoredJob` where `NormalizedJob` was required; `ScoredJob` wraps `NormalizedJob`
263
- as `.job` and does not extend it — both callsites corrected to unwrap `.job`; corresponding test assertion corrected from
264
- `scored.job_id` to `scored.job.job_id`
265
-
266
- ### Notes
267
-
268
- 243 tests across 13 files, all passing. No new dependencies. The pipeline is now end-to-end complete. Every module from Phase 1 through
269
- Phase 7 is wired into a single callable function. Phase 9 (CLI entry point) will expose `runBriefing()` as an executable with `--dry-run`,
270
- `--top-k`, and `--json` flags, matching the Python careerclaw CLI surface.
271
-
272
- ---
273
-
274
- ## [0.7.0] — 2026-03-04
275
-
276
- ### Added
277
-
278
- - `src/tracking.ts` — `TrackingRepository` class managing two runtime files under `.careerclaw/`:
279
- - `tracking.json` — keyed object of `TrackingEntry` records; new jobs saved with status `"saved"`; re-encountered jobs have
280
- `last_seen_at` and `updated_at` refreshed, all other fields (including user-set status) preserved; one disk write per
281
- `upsertEntries()` call regardless of batch size
282
- - `runs.jsonl` — append-only newline-delimited JSON log; one `BriefingRun` per line via `appendRun()`
283
- - Constructor accepts `trackingPath`, `runsPath`, and `dryRun` overrides; defaults from `config.ts`
284
- - `load()` returns empty store on a missing or corrupt file — no crash
285
- - `ensureDir()` creates parent directory recursively on first writing
286
- - `upsertEntries()` returns accurate `{ created, already_present }` counts even in dry-run mode
287
- - `ScoredJob?` parameter on `upsertEntries()` reserved for Phase 8 score snapshot attachment
288
- - `src/tests/tracking.test.ts` — 23 unit tests; per-test isolated `tmpdir` directories; covers load, upsert (new + re-encounter),
289
- disk writes, JSONL append, and dry-run suppression
290
-
291
- ### Notes
292
-
293
- 226 tests across 12 files, all passing. No new dependencies. The `TrackingRepository` is the last infrastructure module before the
294
- Phase 8 briefing orchestrator wires everything into the full pipeline.
295
- Dry-run mode is a first-class concern throughout: all writing paths are suppressed, all read and count paths remain fully functional, matching
296
- the Python careerclaw `--dry-run` behaviour.
297
-
298
- ---
299
-
300
- ## [0.6.0]2026-03-04
301
-
302
- ### Added
303
-
304
- - `src/drafting.ts` — `draftOutreach(job, profile, matchedKeywords)`:
305
- deterministic outreach email generator; subject line follows `Interest in {title} at {company}` format; body inserts experience
306
- clause (years or "extensive experience" fallback), up to 3 matched keywords formatted as natural language, and 3 fixed
307
- reliability/collaboration/instrumentation bullet highlights; word count 161–168 words depending on a keyword path, inside 150–250 word
308
- spec; `llm_enhanced: false` always; `formatList()` helper for natural-language list formatting
309
- - `src/tests/drafting.test.ts` — 20 unit tests
310
-
311
- ### Fixed
312
-
313
- - Deterministic template body was 127 words on the first pass — below the 150-word MVP spec floor; fixed by expanding opening and closing
314
- paragraphs; both keyword and fallback paths re-verified at 161 and 168 words respectively
315
-
316
- ### Notes
317
-
318
- 203 tests across 11 files, all passing. No new dependencies. `llm_enhanced` is always false in this phase — LLM enhancement
319
- (Phase 7+) will set this flag to true when the Pro key is configured and the call succeeds. The deterministic template remains the permanent
320
- fallback for the Free tier and for LLM failure scenarios.
321
-
322
- ---
323
-
324
- ## [0.5.0] — 2026-03-04
325
-
326
- ### Added
327
-
328
- - `src/requirements.ts` `extractJobRequirements(job)`: tokenizes job title and description into a deduplicated keyword and phrase corpus for
329
- use as the job-side input to gap analysis
330
- - `src/resume-intel.ts` `buildResumeIntelligence(params)`:
331
- section-aware keyword/phrase extraction across skills (weight 1.0), summary + target_roles (weight 0.8), and optional resume_text (weight
332
- 0.6); per-keyword weight is the max across sections; `impact_signals` are keywords with weight >= 0.8; `source` flag indicates which inputs
333
- contributed; PR-E fix (skills injection) baked in from day one
334
- - `src/gap.ts` — `gapAnalysis(intel, job)`: weighted `fit_score` (sum of matched keyword_weights / job keyword count), `fit_score_unweighted`
335
- (Jaccard), `signals` (resume job), `gaps` (job resume), and top-5 `summary` for display
336
- - `JobRequirements`, `ResumeIntelligence`, `GapAnalysisResult` interfaces added to `src/models.ts`; `ResumeIntelligence` schema is
337
- JSON-compatible with Python careerclaw output
338
- - `src/tests/resume-intel.test.ts` — 19 unit tests
339
- - `src/tests/gap.test.ts` — 16 unit tests
340
-
341
- ### Fixed
342
-
343
- - Added `"am"` to `STOPWORDS` in `src/core/text-processing.ts`missed from an initial set alongside `"is"`, `"are"`, `"was"`, `"were"`, `"be"`;
344
- caught by resume-intel stopword filter test
345
-
346
- ### Notes
347
-
348
- 183 tests across 10 files, all passing. No new dependencies. The `fit_score` weighted formula is identical to the Python careerclaw
349
- implementation: skills listed in UserProfile. Skills receive weight 1.0 and will never appear as gaps. The practical fit_score ceiling against
350
- real job postings is ~50% due to company names and location tokens in the denominator.
351
-
352
- ### Future Work
353
-
354
- - CorpusCache: Entropy-based token filtering (IDF) to suppress tokens that appear in >80% of fetched jobs. Gated behind corpus_size >= 50.
355
- Planned for a future release after job tracking accumulates sufficient data.
356
-
357
- ---
358
-
359
- ## [0.4.0] — 2026-03-04
360
-
361
- ### Added
362
-
363
- - `src/matching/scoring.ts` four pure scoring functions:
364
- `scoreKeyword()` (Jaccard token overlap, returns matched and gap keyword lists), `scoreExperience()` (clamped linear user/job years ratio),
365
- `scoreSalary()` (proportional against a user minimum),
366
- `scoreWorkMode()` (exact=1.0, hybrid=0.5 partial, mismatch=0.0);
367
- `compositeScore()` with `WEIGHTS` (keyword=0.50, experience=0.20, salary=0.15, work_mode=0.15)
368
- - `src/matching/engine.ts` `rankJobs(jobs, profile, topK)` scores all jobs, sorts descending by composite score, returns top-K `ScoredJob[]`
369
- with full breakdown and keyword lists; scores rounded to 4 d.p.
370
- - `src/matching/index.ts` — barrel export for matching public API
371
- - `src/tests/matching.scoring.test.ts` — 36 unit tests
372
- - `src/tests/matching.engine.test.ts` — 10 end-to-end tests using real model types
373
-
374
- ### Notes
375
-
376
- 148 tests across 8 files, all passing. No new dependencies. Neutral score (0.5) is used for all null data cases, so missing job fields
377
- neither reward nor penalize the composite consistent with Python careerclaw behavior. Gap keywords from `scoreKeyword()` feed directly
378
- into Phase 5 gap analysis.
379
-
380
- ---
381
-
382
- ## [0.3.0] 2026-03-04
383
-
384
- ### Added
385
-
386
- - `src/sources.ts` source aggregation layer: `fetchAllJobs()` runs both
387
- adapters concurrently with per-source error isolation; `deduplicate()`
388
- removes duplicate `job_id` entries (first-seen wins); returns `FetchResult`
389
- with job list, per-source counts, and error map for run instrumentation
390
- - `src/core/text-processing.ts` — shared text processing library:
391
- `STOPWORDS` (English function words and full PR-E recruitment boilerplate set),
392
- `SECTION_WEIGHTS` (skills=1.0, summary=0.8, experience=0.6, education=0.4),
393
- `tokenize()`, `tokenizeUnique()`, `extractPhrases()`,
394
- `extractPhrasesFromText()`, `tokenOverlap()`, `matchedTokens()`,
395
- `gapTokens()`
396
- - `src/tests/text-processing.test.ts` — 34 unit tests
397
- - `src/tests/sources.test.ts` — 10 unit tests (ESM-safe adapter stubs via
398
- `vi.doMock()` + `vi.resetModules()`; no network)
399
-
400
- ### Notes
401
-
402
- 102 tests across 6 files, all passing. No new production dependencies.
403
- `SECTION_WEIGHTS` is defined here and will be consumed by resume intelligence
404
- in Phase 5. The `FetchResult.errors` map feeds into `BriefingRun.sources`
405
- instrumentation in Phase 8.
406
-
407
- ---
408
-
409
- ## [0.2.0] — 2026-03-03
410
-
411
- ### Added
412
-
413
- - `src/adapters/remoteok.ts` RemoteOK RSS adapter; parses RSS XML into
414
- `NormalizedJob[]`; `parseRss()` exported separately from `fetchRemoteOkJobs()`
415
- so contract tests call pure parsing functions without network mocking
416
- - `src/adapters/hackernews.ts` — HN Firebase adapter; fetches "Who is Hiring?"
417
- thread comments in parallel; `parseComment()` exported for offline testing;
418
- handles deleted/dead items gracefully
419
- - `src/adapters/index.ts` barrel export for all adapter public API
420
- - `src/tests/fixtures/remoteok.xml` — RSS fixture covering full fields, no-salary,
421
- and k-suffix salary variants
422
- - `src/tests/fixtures/hn-thread.json` — HN thread fixture with `kids` array
423
- - `src/tests/fixtures/hn-comment-job.json`HN job comment fixture (pipe-separated
424
- header, HTML body, salary, experience years)
425
- - `src/tests/fixtures/hn-comment-deleted.json` — deleted comment fixture (adapter
426
- must skip)
427
- - `src/tests/adapters.remoteok.test.ts` — 25 offline contract tests (title/company
428
- splitting, salary parsing, work-mode inference, HTML stripping, `stableId`)
429
- - `src/tests/adapters.hackernews.test.ts` — 18 offline contract tests (header
430
- parsing, timestamp conversion, HTML decoding, skip logic for deleted items)
431
- - `scripts/smoke_sources.ts` live smoke test hitting real RemoteOK RSS and HN
432
- Firebase APIs; run manually before releases with `npm run smoke`
433
- - `fast-xml-parser` added as a production dependency (RSS parsing)
434
- - `tsx` added as a dev dependency (runs a smoke script without a compiler step)
435
-
436
- ### Changed
437
-
438
- - `stripHtml()` fixed: opening `<p>` tags now convert to `\n` (was `""`) so HN
439
- comment header and body lines split correctly after HTML stripping
440
- - `README.md` — roadmap updated; Phase 2 marked complete; note updated to
441
- reference v0.2.0
442
- - **Payment processor:** Pro license switched from Gumroad to **Polar.sh**
443
- (`https://polar.sh/orestes-garcia-martinez/careerclaw-pro`); `CAREERCLAW_PRO_KEY`
444
- env var name and SHA-256 cache behavior unchanged
445
-
446
- ### Notes
447
-
448
- 58 tests across 4 test files, all passing. No network calls in CI all adapter
449
- tests use offline fixtures. Run `npm run smoke` manually before each release to
450
- validate live sources.
451
-
452
-
453
- ## [0.1.0] 2026-03-03
454
-
455
- ### Added
456
-
457
- - Initial repository scaffold: `package.json`, `tsconfig.json`, `vitest.config.ts`
458
- - `src/models.ts`canonical data schemas (`NormalizedJob`, `UserProfile`,
459
- `TrackingEntry`, `BriefingRun`, `ScoredJob`, `OutreachDraft`); identical
460
- JSON serialization format to Python careerclaw for cross-implementation
461
- file compatibility
462
- - `src/config.ts` — centralised environment and source configuration
463
- (runtime paths, HTTP defaults, RemoteOK RSS URL, HN thread ID, LLM and
464
- license env vars)
465
- - `SKILL.md` OpenClaw skill definition with Node-native self-healing
466
- install check (`npm install -g careerclaw-js`)
467
- - `CHANGELOG.md`
468
- - Unit tests for `models.ts` and `config.ts` (Vitest)
469
-
470
- ### Notes
471
-
472
- This release establishes the Phase 1 foundation types. No adapters,
473
- matching, or CLI are included yet — those follow in Phases 2–8 per the
474
- Node Migration Decision (ADR, March 2026).
475
-
476
- [Unreleased]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.8.0...HEAD
477
- [0.8.1]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.8.0...v0.8.1
478
- [0.8.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.7.0...v0.8.0
479
- [0.7.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.6.0...v0.7.0
480
- [0.6.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.5.0...v0.6.0
481
- [0.5.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.4.0...v0.5.0
482
- [0.4.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.3.0...v0.4.0
483
- [0.3.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.2.0...v0.3.0
484
- [0.2.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.1.0...v0.2.0
1
+ # Changelog
2
+
3
+ All notable changes to careerclaw-js are documented here.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+ Versioning follows [Semantic Versioning](https://semver.org/).
6
+
7
+ ---
8
+
9
+ ## [Unreleased]
10
+
11
+ ---
12
+
13
+ ## [1.1.0] - 2026-03-26
14
+
15
+ ### Added
16
+
17
+ - `src/execution-context.ts` dual execution-context architecture for CareerClaw
18
+ runtimes; `ClawOsExecutionContext` (trusted platform path, `verified: true`,
19
+ feature list passed from upstream entitlement) and `StandaloneExecutionContext`
20
+ (local CLI / direct package use with Gumroad license validation);
21
+ `CAREERCLAW_FEATURES` constant registry with four feature flags
22
+ (`LLM_OUTREACH_DRAFT`, `TAILORED_COVER_LETTER`, `RESUME_GAP_ANALYSIS`,
23
+ `TOPK_EXTENDED`); `createClawOsExecutionContext()` and
24
+ `createStandaloneExecutionContext()` factory functions;
25
+ `hasCareerClawFeature()` type-safe feature check helper;
26
+ `CareerClawFeatureKey` union type derived from `CAREERCLAW_FEATURES`
27
+ - `src/runtime.ts` programmatic runtime wrappers providing the clean
28
+ import boundary for both ClawOS and standalone consumers;
29
+ `runCareerClawStandalone(input, options)` delegates to `runBriefing()` with
30
+ standalone license flow; `runCareerClawWithContext(input, context, options)`
31
+ delegates to `runBriefingWithContext()` with trusted platform context;
32
+ `resolveResumeIntelligence()` centralises resume-intel construction from
33
+ profile + optional resume text, eliminating duplicate logic across callers;
34
+ `CareerClawRunInput`, `CareerClawRunSupportOptions`, and
35
+ `CareerClawStandaloneRunOptions` interfaces for typed consumer APIs
36
+ - `src/index.ts` — public API barrel export; re-exports `models`, `briefing`,
37
+ `runtime`, `resume-intel`, `execution-context`, `license`, `tracking`, and
38
+ `sources` modules; enables `import { ... } from "careerclaw-js"` for all
39
+ public types and functions
40
+ - `src/tests/runtime.test.ts` 4 integration tests covering the trust
41
+ boundary security matrix: standalone with Pro key (license validated via
42
+ Gumroad → LLM drafts active), standalone without Pro key (no license check →
43
+ template drafts only), ClawOS with `LLM_OUTREACH_DRAFT` feature (no Gumroad
44
+ call LLM drafts active), ClawOS without feature (no Gumroad call → template
45
+ drafts only); mocks `checkLicense` and `enhanceDraft` to verify call isolation
46
+
47
+ ### Changed
48
+
49
+ - `src/briefing.ts` refactored from single `runBriefing()` entry point to
50
+ dual-mode architecture via internal strategy pattern; added
51
+ `runBriefingWithContext(profile, context, options)` as the trusted ClawOS
52
+ entry point; extracted `runBriefingInternal()` as the shared pipeline with
53
+ `InternalExecutionMode` discriminated union (`"standalone"` | `"clawos"`);
54
+ extracted `resolvePremiumDraftAccess(executionMode, resumeIntel)` to resolve
55
+ Pro activation per mode — standalone validates via `checkLicense()`, ClawOS
56
+ checks `tier === "pro"` + `hasCareerClawFeature(LLM_OUTREACH_DRAFT)`;
57
+ fixed unsafe `resumeIntel!` non-null assertion to safe `isProActive && resumeIntel`
58
+ guard; added `ContextBriefingOptions` interface (excludes `proKey`,
59
+ `licenseFetchFn`, `licenseCachePath` — not applicable in trusted mode);
60
+ removed inline JSDoc comments from `BriefingOptions` fields (types are
61
+ self-documenting)
62
+ - `src/cli.ts` added `--version` / `-v` flag using `createRequire` to read
63
+ version from `package.json`; version check exits before any profile or config
64
+ loading; updated `printHelp()` to document the new flag; replaced direct
65
+ `runBriefing()` call with `runCareerClawStandalone()` from `runtime.ts`,
66
+ delegating resume-intel construction and option wiring to the runtime layer
67
+ - `package.json` — description updated to reflect dual-runtime identity
68
+ (ClawOS + standalone); `"clawos"` keyword added; `exports` map added with
69
+ three subpath entries: `"."` (main library, `dist/index.js`), `"./cli"`
70
+ (CLI entry, `dist/cli.js`), `"./package.json"` (self-reference for
71
+ `createRequire` version reads)
72
+ - `README.md` — full rewrite aligned with dual execution-context architecture;
73
+ added [ClawOS](https://clawoshq.com/) links throughout; added
74
+ [ClawHub](https://clawhub.ai/) and [OpenClaw](https://openclaw.org/)
75
+ references for standalone skill distribution; split Pro pricing into two
76
+ channels (ClawOS $9/month recommended, Standalone $39 lifetime via Gumroad);
77
+ added programmatic integration examples for both `runCareerClawWithContext()`
78
+ (with `createClawOsExecutionContext()`) and `runCareerClawStandalone()`;
79
+ added `--version` flag to CLI options; updated project structure tree with
80
+ `execution-context.ts`, `index.ts`, and `runtime.ts`
81
+
82
+ ### Security
83
+
84
+ - ClawOS execution path never touches Gumroad entitlement is resolved
85
+ upstream by the platform; the `ClawOsExecutionContext.verified: true`
86
+ literal type enforces that only pre-verified contexts are accepted
87
+ - Standalone execution path always validates through `checkLicense()` when a
88
+ `proKey` is provided — no bypass flag exists on the public CLI
89
+ - `resumeIntel!` non-null assertion removed from the draft enhancement call;
90
+ replaced with an explicit `isProActive && resumeIntel` guard to prevent
91
+ runtime errors if `resumeIntel` is undefined
92
+
93
+ ### Notes
94
+
95
+ 274 tests across 17 files, all passing. `tsc --noEmit` clean.
96
+ No new production dependencies. The `exports` map in `package.json` makes
97
+ the package a proper ESM citizen with subpath resolution for ClawOS
98
+ direct-import consumption.
99
+
100
+ ---
101
+
102
+ ## [1.0.6] - 2026-03-19
103
+
104
+ ### Fixed
105
+
106
+ - Triggered by the fast-xml-parser 5.5.7, upgrade (CVE-2026-33349),
107
+ which Entity Expansion Limits Bypassed When Set to Zero Due to JavaScript
108
+ Falsy Evaluation in fast-xml-parser
109
+
110
+ ---
111
+
112
+ ## [1.0.5] - 2026-03-18
113
+
114
+ ### Fixed
115
+
116
+ - Fixed hex and decimal numeric HTML entities (e.g. `&#x2F;`, `&#47;`) leaking
117
+ undecoded into job titles, descriptions, and outreach drafts
118
+ (`src/adapters/remoteok.ts`). Added generic catch-all decoding for both
119
+ `&#xHH;` and `&#DD;` forms before the named-entity block in `stripHtml`.
120
+ Triggered by the fast-xml-parser v5 upgrade (CVE-2026-26278), which stopped
121
+ silently pre-decoding entities inside CDATA content that v4 had handled
122
+ transparently.
123
+
124
+ ---
125
+
126
+ ## [1.0.4] - 2026-03-08
127
+
128
+ ### Changed
129
+ - Runtime check replaced — no more auto-install, just a non-blocking update nudge
130
+ - Write-failure gate added in Step 3
131
+ - Resume write failed row added to error table
132
+
133
+ ---
134
+
135
+ ## [1.0.3] - 2026-03-08
136
+
137
+ ### Fixed
138
+ - Agent no longer presents a multi-question setup form when profile is missing — redirects to resume upload only
139
+ - Agent no longer invents career frameworks, phases, or methodologies not defined in this file
140
+ - Agent no longer enters consultant mode during first-time setup
141
+
142
+ ### Changed
143
+ - Rewrote SKILL.md from persona/behavior narrative format to numbered runbook format
144
+ - Added hard Rules section at top with six explicit prohibitions
145
+ - Added profile existence check (Step 1) as hard gate before any command runs
146
+ - Setup flow now asks one question at a time — work mode first, salary second
147
+ - Removed Agent Persona, Behavior 1, and Behavior 2 sections entirely
148
+ - Bumped runtime self-healing check expected version to 1.0.3
149
+
150
+ ---
151
+
152
+ ## [1.0.2] - 2026-03-07
153
+
154
+ ### Fixed
155
+ - Behavior 2 (Strategic Gap Closing) no longer fires during First-Time Setup.
156
+ The agent was entering consultant mode after resume intake and ending setup
157
+ with open-ended targeting questions instead of proceeding to the first briefing.
158
+
159
+ ### Changed
160
+ - Added explicit activation gate to Behavior 2: only applies after setup is
161
+ complete and only when the user asks a listed trigger phrase.
162
+ - Step 2 of First-Time Setup now specifies a strict exit condition: collect
163
+ work mode and salary floor, then proceed directly to Step 3 with no
164
+ analysis, strategy suggestions, or targeting options.
165
+ - Added three entries to "What Not to Do" to reinforce setup boundaries.
166
+
167
+ ---
168
+
169
+ ## [1.0.1] - 2026-03-06
170
+
171
+ ### Changed
172
+ - Hardened the public `SKILL.md` contract for ClawHub/OpenClaw deployment.
173
+ - Standardized runtime behavior to a single install/execute approach:
174
+ - self-healing install with `npm install -g careerclaw-js@"$EXPECTED"`
175
+ - direct execution with `careerclaw-js ...`
176
+ - Updated required runtime bins to match the Node/npm execution path.
177
+ - Scoped CareerClaw behavior more clearly as an invoked skill instead of a session-wide assistant.
178
+ - Simplified first-run onboarding around canonical `.careerclaw/resume.txt` handling.
179
+ - Reduced first-run friction by keeping setup resume-first and minimizing follow-up questions.
180
+
181
+ ### Improved
182
+ - Restored Strategic Gap Closing behavior so CareerClaw can act more like a practical career consultant when the fit is imperfect.
183
+ - Improved recommendation tone and guidance for fatal, acceptable, and bridgeable gaps.
184
+ - Cleaned up Pro activation flow so premium setup is only introduced when it is actually needed.
185
+ - Restored the public Gumroad purchase link for CareerClaw Pro while keeping internal product IDs out of user-facing setup.
186
+
187
+ ### Removed
188
+ - Removed internal Gumroad product ID exposure from the public skill surface.
189
+ - Removed Python compatibility messaging from `SKILL.md` to keep the public skill contract focused on the current Node-based experience.
190
+
191
+ ---
192
+
193
+ ## [1.0.0] 2026-03-06
194
+
195
+ ### Added
196
+
197
+ - `SKILL.md` full rewrite (Phase 12): Agent Persona section; zero-config resume
198
+ intake; Daily Stand-up behavior (reads `tracking.json` on session start,
199
+ surfaces still-live and undrafted jobs); Strategic Gap Closing (coach tone
200
+ for matches with gaps > 0.6 score); Sunday Night Strategy (first weekday of
201
+ month = HN priority, Sunday evening prompt, Monday/Friday timing context);
202
+ Tier-1 upsell trigger (Google, Meta, Apple, Stripe, Airbnb, Netflix);
203
+ Free vs Pro comparison table; `optionalEnv` frontmatter for all 12 env vars
204
+ - `RELEASE_CHECKLIST.md` — pre-publish gate, npm publish steps, VirusTotal
205
+ scan instructions, OpenClaw test gate, ClawHub submission checklist
206
+
207
+ ### Changed
208
+
209
+ - `src/llm-enhance.ts` — OpenAI dual-transport support: `gpt-4o` family uses
210
+ `/v1/chat/completions` with `max_completion_tokens`; newer models (gpt-5.x+)
211
+ use `/v1/responses` with `max_output_tokens`; transport auto-detected via
212
+ `defaultOpenAITransport(model)` and injectable per `ChainCandidate`;
213
+ `SYSTEM_PROMPT` rewritten with Bridge Sentence method as core doctrine,
214
+ 250/300-word hard limits, 22 banned phrases by name; `buildPrompt()` updated
215
+ with explicit word limit and Bridge Sentence paragraph-level instruction
216
+ - `src/config.ts` `CAREERCLAW_DIR` empty-string guard: `??` replaced with
217
+ `.trim().length > 0` check so blank `CAREERCLAW_DIR=` in `.env` correctly
218
+ falls back to `.careerclaw/`
219
+ - `scripts/smoke_llm.ts` Pro key section now calls `checkLicense()` live
220
+ against Gumroad when both keys are set; stale "not yet implemented" message
221
+ removed; Polar.sh URL replaced with Gumroad URL
222
+ - `README.md` — Polar.sh references replaced with Gumroad; CLI commands
223
+ corrected (no `briefing` subcommand); `--resume-pdf` removed (not
224
+ implemented); env vars table updated; roadmap updated through Phase 13
225
+
226
+ ### Notes
227
+
228
+ 270 tests across 16 files, all passing. `tsc --noEmit` clean.
229
+ No new production dependencies. First ClawHub-ready release.
230
+ OpenAI transport validated against gpt-4o-mini (chat) and gpt-5.2 (responses)
231
+ in live smoke testing.
232
+
233
+ ---
234
+
235
+ ## [0.11.0] — 2026-03-05
236
+
237
+ ### Added
238
+
239
+ - `src/license.ts` — `checkLicense(key, options?)` `Promise<LicenseResult>`:
240
+ Gumroad Pro license validation with 7-day offline cache; raw key never
241
+ written to disk — only `sha256(key)` cached; `{ valid: false, source: "none" }`
242
+ returned immediately when `CAREERCLAW_GUMROAD_PRODUCT_ID` is unset so Free
243
+ tier users are unaffected; `fetchFn` and `cachePath` injectable for offline tests
244
+ - `src/tests/license.test.ts` — 12 offline tests covering the missing product ID
245
+ guard, fresh/stale/missing/wrong-key cache branches, all Gumroad API response
246
+ variants (success, refunded, chargebacked, 404, 500), never-throws invariant,
247
+ and hash safety (raw key must not appear in cache file)
248
+
249
+ ### Changed
250
+
251
+ - `src/briefing.ts` — bare `proKey` presence check replaced with
252
+ `await checkLicense(proKey)`; `isProActive` is now driven by
253
+ `licenseResult.valid`; `BriefingOptions` gains `licenseFetchFn` and
254
+ `licenseCachePath` for test injection
255
+ - `src/config.ts` added `GUMROAD_PRODUCT_ID`, `GUMROAD_API_BASE`,
256
+ `LICENSE_CACHE_TTL_MS` (7 days in ms); `POLAR_PRODUCT_SLUG` and
257
+ `POLAR_API_BASE` retained and marked `@deprecated` with
258
+ `TODO: Phase 11-Polar` migration comment
259
+ - `.env.example` — Pro purchase URL updated to Gumroad;
260
+ `CAREERCLAW_GUMROAD_PRODUCT_ID=` added with dashboard instructions
261
+
262
+ ### Security
263
+
264
+ - Raw license key is never written to disk; only `sha256(key)` is persisted
265
+ - Gumroad validation uses `increment_uses_count=false` — validation calls
266
+ do not consume customer usage quota
267
+
268
+ ### Notes
269
+
270
+ 270 tests across 16 files, all passing. `tsc --noEmit` clean.
271
+ No new production dependencies. Polar migration path preserved via
272
+ `@deprecated` constants and `TODO: Phase 11-Polar` comment in `config.ts`.
273
+
274
+ ---
275
+
276
+ ## [0.10.0] — 2026-03-05
277
+
278
+ ### Added
279
+
280
+ - `src/llm-enhance.ts` `enhanceDraft(job, profile, resumeIntel, draft, gapKeywords, options)`:
281
+ Pro tier LLM-powered outreach draft generation; parses `LLM_CHAIN` into a left-to-right failover
282
+ list; per-candidate retry loop up to `LLM_MAX_RETRIES`; circuit breaker opens after
283
+ `LLM_CIRCUIT_BREAKER_FAILS` consecutive failures; `_chainOverride` for offline testing
284
+ - `src/tests/llm-enhance.test.ts` 12 offline tests covering Anthropic path, OpenAI path,
285
+ HTTP error fallback, unparseable response fallback, never-throws invariant, circuit breaker
286
+ call-count bounds, privacy (raw resume text must not appear in outbound request payload),
287
+ and empty-chain no-op
288
+
289
+ ### Changed
290
+
291
+ - `src/briefing.ts` — `BriefingOptions` gains `resumeIntel?: ResumeIntelligence`,
292
+ `proKey?: string`, `enhanceFetchFn?`; draft stage converted to `async`; calls `enhanceDraft()`
293
+ per match when `proKey` is set falls back to deterministic baseline on any LLM failure
294
+ - `src/cli.ts` calls `buildResumeIntelligence()` after resume load; passes `resumeIntel` and
295
+ `PRO_KEY` to `runBriefing()`; Pro enhancement path is now live from the CLI
296
+
297
+ ### Security
298
+
299
+ - LLM prompt contains only extracted keyword signals (`impact_signals` ≤ 12 tokens,
300
+ `gap_keywords` 6 tokens) raw resume text and `resume_summary` are never sent to
301
+ any external API
302
+
303
+ ### Notes
304
+
305
+ 258 tests across 15 files, all passing. `tsc --noEmit` clean. No new production dependencies.
306
+ License validation (`CAREERCLAW_PRO_KEY` checked against Gumroad) is deferred to Phase 11 (v0.11.0)
307
+ `proKey` is trusted by presence only in this version.
308
+
309
+ ---
310
+
311
+ ## [0.8.1] — 2026-03-04
312
+
313
+ ### Fixed
314
+
315
+ - **Dentist problem**: replaced additive composite score with a multiplicative model (`total = sqrt(keyword) × qualityBase`);
316
+ zero keyword overlap now always produces a score of 0.0 regardless of metadata alignment — irrelevant jobs can no longer float to the
317
+ top on neutral dimension scores
318
+ - Signal gate added to `rankJobs()`: jobs with keyword score below `minKeywordScore` (default: 0.01) are hard-filtered before ranking
319
+ - `matched_keywords` and `gap_keywords` were hardcoded empty in `engine.ts`; now wired from `compositeScore()` output
320
+ - HTML entity `&#x2F;` (and all `&#x[hex];` sequences) now decoded in `stripHtml()` — fixes `ML&#x2F;AI` appearing in HN job titles and
321
+ gap tokens
322
+ - `stripHtml()` now applied to parsed HN job title and company name (previously body text only)
323
+ - Contraction tokens (`i'm`, `i've`, `don't`, etc.) added to `STOPWORDS` — eliminates noise in gap keyword output
324
+
325
+ ### Changed
326
+
327
+ - `MatchBreakdown` field names renamed: `keyword_score → keyword`, `experience_score → experience`, `salary_score → salary`,
328
+ `work_mode_score work_mode`
329
+ - `compositeScore()` return type extended: now includes `matched` and `gaps` string arrays alongside `total` and `breakdown`
330
+ - Default LLM model updated: `claude-sonnet-4-20250514` `claude-haiku-4-5-20251001`
331
+ - `scripts/smoke_briefing.ts` refactored to multi-profile mode; select profile via `PROFILE=0|1|2` env var
332
+
333
+ ### Added
334
+
335
+ - `.env.example` full credential reference (OpenClaw gateway, agent LLM keys, Pro license, draft enhancement keys, failover chain config)
336
+ - `SECURITY.md` local-first security architecture, credential handling policy, external network call inventory, LLM data disclosure
337
+ - `src/config.ts`: `LLM_ANTHROPIC_KEY`, `LLM_OPENAI_KEY`, `LLM_CHAIN`, `LLM_MAX_RETRIES`, `LLM_CIRCUIT_BREAKER_FAILS`
338
+ - `scripts/smoke_llm.ts` — LLM connectivity and Pro key smoke test; `npm run smoke:llm`
339
+
340
+ ### Notes
341
+
342
+ 234 tests across 13 files, all passing. No new production dependencies.
343
+ `MatchBreakdown` rename is a breaking internal change no public API consumers exist prior to the CLI (Phase 9).
344
+
345
+ ---
346
+
347
+ ## [0.8.0] — 2026-03-04
348
+
349
+ ### Added
350
+
351
+ - `src/briefing.ts` — `runBriefing(profile, options)`: end-to-end pipeline orchestrator; four timed stages (fetch, rank, draft,
352
+ persist); skill-first design — profile is a parameter, never loaded from disk; `fetchFn` and `repo` are injectable for testing; dry-run
353
+ suppresses all writes while keeping counts accurate; catastrophic fetch failure returns empty result rather than throwing;
354
+ `run_id` generated via `crypto.randomUUID()` (Node built-in); `version` read from package.json at runtime via `createRequire`
355
+ - `BriefingResult` interface added to `src/models.ts` stable JSON output schema for OpenClaw/ClawHub agent consumption: `run`,
356
+ `matches`, `drafts`, `tracking`, `dry_run`
357
+ - `src/tests/briefing.test.ts` — 17 integration tests; all offline via stubbed `fetchFn` and `tmpdir`-backed `TrackingRepository`
358
+
359
+ ### Fixed
360
+
361
+ - `draftOutreach()` and `upsertEntries()` were being passed `ScoredJob` where `NormalizedJob` was required; `ScoredJob` wraps `NormalizedJob`
362
+ as `.job` and does not extend it — both callsites corrected to unwrap `.job`; corresponding test assertion corrected from
363
+ `scored.job_id` to `scored.job.job_id`
364
+
365
+ ### Notes
366
+
367
+ 243 tests across 13 files, all passing. No new dependencies. The pipeline is now end-to-end complete. Every module from Phase 1 through
368
+ Phase 7 is wired into a single callable function. Phase 9 (CLI entry point) will expose `runBriefing()` as an executable with `--dry-run`,
369
+ `--top-k`, and `--json` flags, matching the Python careerclaw CLI surface.
370
+
371
+ ---
372
+
373
+ ## [0.7.0] — 2026-03-04
374
+
375
+ ### Added
376
+
377
+ - `src/tracking.ts` `TrackingRepository` class managing two runtime files under `.careerclaw/`:
378
+ - `tracking.json` keyed object of `TrackingEntry` records; new jobs saved with status `"saved"`; re-encountered jobs have
379
+ `last_seen_at` and `updated_at` refreshed, all other fields (including user-set status) preserved; one disk write per
380
+ `upsertEntries()` call regardless of batch size
381
+ - `runs.jsonl` — append-only newline-delimited JSON log; one `BriefingRun` per line via `appendRun()`
382
+ - Constructor accepts `trackingPath`, `runsPath`, and `dryRun` overrides; defaults from `config.ts`
383
+ - `load()` returns empty store on a missing or corrupt file — no crash
384
+ - `ensureDir()` creates parent directory recursively on first writing
385
+ - `upsertEntries()` returns accurate `{ created, already_present }` counts even in dry-run mode
386
+ - `ScoredJob?` parameter on `upsertEntries()` reserved for Phase 8 score snapshot attachment
387
+ - `src/tests/tracking.test.ts` 23 unit tests; per-test isolated `tmpdir` directories; covers load, upsert (new + re-encounter),
388
+ disk writes, JSONL append, and dry-run suppression
389
+
390
+ ### Notes
391
+
392
+ 226 tests across 12 files, all passing. No new dependencies. The `TrackingRepository` is the last infrastructure module before the
393
+ Phase 8 briefing orchestrator wires everything into the full pipeline.
394
+ Dry-run mode is a first-class concern throughout: all writing paths are suppressed, all read and count paths remain fully functional, matching
395
+ the Python careerclaw `--dry-run` behaviour.
396
+
397
+ ---
398
+
399
+ ## [0.6.0] — 2026-03-04
400
+
401
+ ### Added
402
+
403
+ - `src/drafting.ts` `draftOutreach(job, profile, matchedKeywords)`:
404
+ deterministic outreach email generator; subject line follows `Interest in {title} at {company}` format; body inserts experience
405
+ clause (years or "extensive experience" fallback), up to 3 matched keywords formatted as natural language, and 3 fixed
406
+ reliability/collaboration/instrumentation bullet highlights; word count 161–168 words depending on a keyword path, inside 150–250 word
407
+ spec; `llm_enhanced: false` always; `formatList()` helper for natural-language list formatting
408
+ - `src/tests/drafting.test.ts` — 20 unit tests
409
+
410
+ ### Fixed
411
+
412
+ - Deterministic template body was 127 words on the first pass — below the 150-word MVP spec floor; fixed by expanding opening and closing
413
+ paragraphs; both keyword and fallback paths re-verified at 161 and 168 words respectively
414
+
415
+ ### Notes
416
+
417
+ 203 tests across 11 files, all passing. No new dependencies. `llm_enhanced` is always false in this phase — LLM enhancement
418
+ (Phase 7+) will set this flag to true when the Pro key is configured and the call succeeds. The deterministic template remains the permanent
419
+ fallback for the Free tier and for LLM failure scenarios.
420
+
421
+ ---
422
+
423
+ ## [0.5.0]2026-03-04
424
+
425
+ ### Added
426
+
427
+ - `src/requirements.ts` — `extractJobRequirements(job)`: tokenizes job title and description into a deduplicated keyword and phrase corpus for
428
+ use as the job-side input to gap analysis
429
+ - `src/resume-intel.ts` — `buildResumeIntelligence(params)`:
430
+ section-aware keyword/phrase extraction across skills (weight 1.0), summary + target_roles (weight 0.8), and optional resume_text (weight
431
+ 0.6); per-keyword weight is the max across sections; `impact_signals` are keywords with weight >= 0.8; `source` flag indicates which inputs
432
+ contributed; PR-E fix (skills injection) baked in from day one
433
+ - `src/gap.ts` `gapAnalysis(intel, job)`: weighted `fit_score` (sum of matched keyword_weights / job keyword count), `fit_score_unweighted`
434
+ (Jaccard), `signals` (resume job), `gaps` (job resume), and top-5 `summary` for display
435
+ - `JobRequirements`, `ResumeIntelligence`, `GapAnalysisResult` interfaces added to `src/models.ts`; `ResumeIntelligence` schema is
436
+ JSON-compatible with Python careerclaw output
437
+ - `src/tests/resume-intel.test.ts` — 19 unit tests
438
+ - `src/tests/gap.test.ts` 16 unit tests
439
+
440
+ ### Fixed
441
+
442
+ - Added `"am"` to `STOPWORDS` in `src/core/text-processing.ts` — missed from an initial set alongside `"is"`, `"are"`, `"was"`, `"were"`, `"be"`;
443
+ caught by resume-intel stopword filter test
444
+
445
+ ### Notes
446
+
447
+ 183 tests across 10 files, all passing. No new dependencies. The `fit_score` weighted formula is identical to the Python careerclaw
448
+ implementation: skills listed in UserProfile. Skills receive weight 1.0 and will never appear as gaps. The practical fit_score ceiling against
449
+ real job postings is ~50% due to company names and location tokens in the denominator.
450
+
451
+ ### Future Work
452
+
453
+ - CorpusCache: Entropy-based token filtering (IDF) to suppress tokens that appear in >80% of fetched jobs. Gated behind corpus_size >= 50.
454
+ Planned for a future release after job tracking accumulates sufficient data.
455
+
456
+ ---
457
+
458
+ ## [0.4.0]2026-03-04
459
+
460
+ ### Added
461
+
462
+ - `src/matching/scoring.ts` — four pure scoring functions:
463
+ `scoreKeyword()` (Jaccard token overlap, returns matched and gap keyword lists), `scoreExperience()` (clamped linear user/job years ratio),
464
+ `scoreSalary()` (proportional against a user minimum),
465
+ `scoreWorkMode()` (exact=1.0, hybrid=0.5 partial, mismatch=0.0);
466
+ `compositeScore()` with `WEIGHTS` (keyword=0.50, experience=0.20, salary=0.15, work_mode=0.15)
467
+ - `src/matching/engine.ts` — `rankJobs(jobs, profile, topK)` scores all jobs, sorts descending by composite score, returns top-K `ScoredJob[]`
468
+ with full breakdown and keyword lists; scores rounded to 4 d.p.
469
+ - `src/matching/index.ts` — barrel export for matching public API
470
+ - `src/tests/matching.scoring.test.ts` — 36 unit tests
471
+ - `src/tests/matching.engine.test.ts` — 10 end-to-end tests using real model types
472
+
473
+ ### Notes
474
+
475
+ 148 tests across 8 files, all passing. No new dependencies. Neutral score (0.5) is used for all null data cases, so missing job fields
476
+ neither reward nor penalize the composite — consistent with Python careerclaw behavior. Gap keywords from `scoreKeyword()` feed directly
477
+ into Phase 5 gap analysis.
478
+
479
+ ---
480
+
481
+ ## [0.3.0] — 2026-03-04
482
+
483
+ ### Added
484
+
485
+ - `src/sources.ts` — source aggregation layer: `fetchAllJobs()` runs both
486
+ adapters concurrently with per-source error isolation; `deduplicate()`
487
+ removes duplicate `job_id` entries (first-seen wins); returns `FetchResult`
488
+ with job list, per-source counts, and error map for run instrumentation
489
+ - `src/core/text-processing.ts` — shared text processing library:
490
+ `STOPWORDS` (English function words and full PR-E recruitment boilerplate set),
491
+ `SECTION_WEIGHTS` (skills=1.0, summary=0.8, experience=0.6, education=0.4),
492
+ `tokenize()`, `tokenizeUnique()`, `extractPhrases()`,
493
+ `extractPhrasesFromText()`, `tokenOverlap()`, `matchedTokens()`,
494
+ `gapTokens()`
495
+ - `src/tests/text-processing.test.ts` — 34 unit tests
496
+ - `src/tests/sources.test.ts` — 10 unit tests (ESM-safe adapter stubs via
497
+ `vi.doMock()` + `vi.resetModules()`; no network)
498
+
499
+ ### Notes
500
+
501
+ 102 tests across 6 files, all passing. No new production dependencies.
502
+ `SECTION_WEIGHTS` is defined here and will be consumed by resume intelligence
503
+ in Phase 5. The `FetchResult.errors` map feeds into `BriefingRun.sources`
504
+ instrumentation in Phase 8.
505
+
506
+ ---
507
+
508
+ ## [0.2.0] — 2026-03-03
509
+
510
+ ### Added
511
+
512
+ - `src/adapters/remoteok.ts` — RemoteOK RSS adapter; parses RSS XML into
513
+ `NormalizedJob[]`; `parseRss()` exported separately from `fetchRemoteOkJobs()`
514
+ so contract tests call pure parsing functions without network mocking
515
+ - `src/adapters/hackernews.ts` — HN Firebase adapter; fetches "Who is Hiring?"
516
+ thread comments in parallel; `parseComment()` exported for offline testing;
517
+ handles deleted/dead items gracefully
518
+ - `src/adapters/index.ts` — barrel export for all adapter public API
519
+ - `src/tests/fixtures/remoteok.xml` — RSS fixture covering full fields, no-salary,
520
+ and k-suffix salary variants
521
+ - `src/tests/fixtures/hn-thread.json` — HN thread fixture with `kids` array
522
+ - `src/tests/fixtures/hn-comment-job.json` — HN job comment fixture (pipe-separated
523
+ header, HTML body, salary, experience years)
524
+ - `src/tests/fixtures/hn-comment-deleted.json` — deleted comment fixture (adapter
525
+ must skip)
526
+ - `src/tests/adapters.remoteok.test.ts` — 25 offline contract tests (title/company
527
+ splitting, salary parsing, work-mode inference, HTML stripping, `stableId`)
528
+ - `src/tests/adapters.hackernews.test.ts` — 18 offline contract tests (header
529
+ parsing, timestamp conversion, HTML decoding, skip logic for deleted items)
530
+ - `scripts/smoke_sources.ts` — live smoke test hitting real RemoteOK RSS and HN
531
+ Firebase APIs; run manually before releases with `npm run smoke`
532
+ - `fast-xml-parser` added as a production dependency (RSS parsing)
533
+ - `tsx` added as a dev dependency (runs a smoke script without a compiler step)
534
+
535
+ ### Changed
536
+
537
+ - `stripHtml()` fixed: opening `<p>` tags now convert to `\n` (was `""`) so HN
538
+ comment header and body lines split correctly after HTML stripping
539
+ - `README.md` — roadmap updated; Phase 2 marked complete; note updated to
540
+ reference v0.2.0
541
+ - **Payment processor:** Pro license switched from Gumroad to **Polar.sh**
542
+ (`https://polar.sh/orestes-garcia-martinez/careerclaw-pro`); `CAREERCLAW_PRO_KEY`
543
+ env var name and SHA-256 cache behavior unchanged
544
+
545
+ ### Notes
546
+
547
+ 58 tests across 4 test files, all passing. No network calls in CI — all adapter
548
+ tests use offline fixtures. Run `npm run smoke` manually before each release to
549
+ validate live sources.
550
+
551
+
552
+ ## [0.1.0] — 2026-03-03
553
+
554
+ ### Added
555
+
556
+ - Initial repository scaffold: `package.json`, `tsconfig.json`, `vitest.config.ts`
557
+ - `src/models.ts` — canonical data schemas (`NormalizedJob`, `UserProfile`,
558
+ `TrackingEntry`, `BriefingRun`, `ScoredJob`, `OutreachDraft`); identical
559
+ JSON serialization format to Python careerclaw for cross-implementation
560
+ file compatibility
561
+ - `src/config.ts` — centralised environment and source configuration
562
+ (runtime paths, HTTP defaults, RemoteOK RSS URL, HN thread ID, LLM and
563
+ license env vars)
564
+ - `SKILL.md` — OpenClaw skill definition with Node-native self-healing
565
+ install check (`npm install -g careerclaw-js`)
566
+ - `CHANGELOG.md`
567
+ - Unit tests for `models.ts` and `config.ts` (Vitest)
568
+
569
+ ### Notes
570
+
571
+ This release establishes the Phase 1 foundation types. No adapters,
572
+ matching, or CLI are included yet — those follow in Phases 2–8 per the
573
+ Node Migration Decision (ADR, March 2026).
574
+
575
+ [Unreleased]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.8.0...HEAD
576
+ [0.8.1]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.8.0...v0.8.1
577
+ [0.8.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.7.0...v0.8.0
578
+ [0.7.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.6.0...v0.7.0
579
+ [0.6.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.5.0...v0.6.0
580
+ [0.5.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.4.0...v0.5.0
581
+ [0.4.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.3.0...v0.4.0
582
+ [0.3.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.2.0...v0.3.0
583
+ [0.2.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/compare/v0.1.0...v0.2.0
485
584
  [0.1.0]: https://github.com/orestes-garcia-martinez/careerclaw-js/releases/tag/v0.1.0