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