@forwardimpact/basecamp 2.4.2 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/config/scheduler.json +10 -5
  2. package/package.json +1 -1
  3. package/src/basecamp.js +101 -729
  4. package/template/.claude/agents/chief-of-staff.md +14 -3
  5. package/template/.claude/agents/head-hunter.md +436 -0
  6. package/template/.claude/agents/librarian.md +1 -1
  7. package/template/.claude/settings.json +4 -1
  8. package/template/.claude/skills/analyze-cv/SKILL.md +39 -7
  9. package/template/.claude/skills/draft-emails/SKILL.md +29 -9
  10. package/template/.claude/skills/draft-emails/scripts/scan-emails.mjs +4 -4
  11. package/template/.claude/skills/draft-emails/scripts/send-email.mjs +41 -6
  12. package/template/.claude/skills/meeting-prep/SKILL.md +7 -4
  13. package/template/.claude/skills/process-hyprnote/SKILL.md +17 -8
  14. package/template/.claude/skills/process-hyprnote/scripts/scan.mjs +246 -0
  15. package/template/.claude/skills/scan-open-candidates/SKILL.md +476 -0
  16. package/template/.claude/skills/scan-open-candidates/scripts/state.mjs +396 -0
  17. package/template/.claude/skills/sync-apple-calendar/SKILL.md +41 -0
  18. package/template/.claude/skills/sync-apple-calendar/scripts/query.mjs +301 -0
  19. package/template/.claude/skills/synthesize-deck/SKILL.md +296 -0
  20. package/template/.claude/skills/synthesize-deck/scripts/extract-pptx.mjs +210 -0
  21. package/template/.claude/skills/track-candidates/SKILL.md +45 -0
  22. package/template/.claude/skills/workday-requisition/SKILL.md +86 -53
  23. package/template/.claude/skills/workday-requisition/scripts/parse-workday.mjs +103 -37
  24. package/template/CLAUDE.md +13 -3
@@ -0,0 +1,476 @@
1
+ ---
2
+ name: scan-open-candidates
3
+ description: >
4
+ Scan publicly available sources for candidates who indicate they are open for
5
+ hire. Uses WebFetch to read public APIs (HN Algolia, GitHub, dev.to).
6
+ Writes prospect notes to knowledge/Prospects/. Maintains
7
+ cursor/dedup state in ~/.cache/fit/basecamp/head-hunter/. Use when the
8
+ head-hunter agent is woken or when the user asks to scan for open candidates.
9
+ ---
10
+
11
+ # Scan Open Candidates
12
+
13
+ Fetch and filter publicly available candidate posts from platforms where people
14
+ explicitly indicate they are open for hire. This skill handles the web fetching,
15
+ filtering, and deduplication logic.
16
+
17
+ ## Trigger
18
+
19
+ Run this skill:
20
+
21
+ - When the head-hunter agent is woken by the scheduler
22
+ - When the user asks to scan for open candidates or prospects
23
+
24
+ ## Prerequisites
25
+
26
+ - `WebFetch` tool available (Claude Code built-in — no curl/wget needed)
27
+ - `fit-pathway` CLI available (`npx fit-pathway`)
28
+ - Memory directory at `~/.cache/fit/basecamp/head-hunter/`
29
+
30
+ ## Inputs
31
+
32
+ - `~/.cache/fit/basecamp/head-hunter/cursor.tsv` — source rotation state
33
+ - `~/.cache/fit/basecamp/head-hunter/seen.tsv` — deduplication index
34
+ - Framework data via `npx fit-pathway skill --list` and `npx fit-pathway job`
35
+
36
+ ## Outputs
37
+
38
+ - `knowledge/Prospects/{Name}.md` — prospect notes
39
+ - `~/.cache/fit/basecamp/head-hunter/cursor.tsv` — updated cursors
40
+ - `~/.cache/fit/basecamp/head-hunter/seen.tsv` — updated seen index
41
+ - `~/.cache/fit/basecamp/head-hunter/prospects.tsv` — updated prospect index
42
+ - `~/.cache/fit/basecamp/head-hunter/log.md` — appended activity log
43
+ - `~/.cache/fit/basecamp/state/head_hunter_triage.md` — triage report
44
+
45
+ ---
46
+
47
+ ## Source Definitions
48
+
49
+ ### 1. Hacker News "Who Wants to Be Hired?"
50
+
51
+ A monthly thread where candidates self-post their availability. Posted on the
52
+ 1st of each month on Hacker News.
53
+
54
+ **Find the thread:**
55
+
56
+ ```
57
+ WebFetch URL: https://hn.algolia.com/api/v1/search?query=%22Who+wants+to+be+hired%22&tags=ask_hn&hitsPerPage=5
58
+ ```
59
+
60
+ Parse the JSON response. The first hit with a title matching "Who wants to be
61
+ hired?" and `created_at` in the current or previous month is the target thread.
62
+
63
+ **Fetch candidate posts:**
64
+
65
+ ```
66
+ WebFetch URL: https://hn.algolia.com/api/v1/items/{objectID}
67
+ ```
68
+
69
+ The `children` array contains top-level comments — each is one candidate. Parse
70
+ the `text` field (HTML) for:
71
+
72
+ - **Location** — often first line: "Location: New York" or "NYC / Remote"
73
+ - **Remote** — "Remote: Yes" or "Open to remote"
74
+ - **Skills** — tech stack listings, language/framework mentions
75
+ - **Experience** — years, role titles, past companies
76
+ - **Contact** — email (often obfuscated: "name [at] domain [dot] com")
77
+ - **Resume/CV** — links to personal sites, GitHub, LinkedIn
78
+
79
+ **Cursor:** Store the `objectID` of the thread and the ID of the last processed
80
+ child comment.
81
+
82
+ **Rate limit:** HN Algolia API has no strict rate limit but be respectful — one
83
+ fetch per source per wake cycle.
84
+
85
+ ### 2. GitHub Open to Work
86
+
87
+ Search for GitHub users whose bio signals availability. The GitHub user search
88
+ API returns candidates with bios, locations, and repository metadata.
89
+
90
+ **Search by location (rotate one query per wake):**
91
+
92
+ ```
93
+ WebFetch URL: https://api.github.com/search/users?q=%22open+to+work%22+location:UK&per_page=30&sort=joined&order=desc
94
+ WebFetch URL: https://api.github.com/search/users?q=%22open+to+work%22+location:Europe&per_page=30&sort=joined&order=desc
95
+ WebFetch URL: https://api.github.com/search/users?q=%22looking+for+work%22+location:remote&per_page=30&sort=joined&order=desc
96
+ ```
97
+
98
+ Alternative bio phrases to search (rotate across wakes):
99
+
100
+ - `"available for hire"`
101
+ - `"seeking opportunities"`
102
+ - `"seeking new role"`
103
+ - `"open to new opportunities"`
104
+ - `"currently exploring"`
105
+ - `"freelance" "available"`
106
+ - `"between roles"`
107
+ - `"on the market"`
108
+ - `"open to opportunities"`
109
+
110
+ Response has `total_count` and `items` array. Each item has `login`, `html_url`,
111
+ `score`.
112
+
113
+ **Fetch full profile for promising candidates:**
114
+
115
+ ```
116
+ WebFetch URL: https://api.github.com/users/{login}
117
+ ```
118
+
119
+ Profile fields:
120
+
121
+ - `name` — display name
122
+ - `bio` — bio text (contains open-to-work signals)
123
+ - `location` — geographic location
124
+ - `hireable` — boolean, explicit hire signal
125
+ - `blog` — personal site URL
126
+ - `company` — current employer (null = likely available)
127
+ - `public_repos` — repository count (technical depth indicator)
128
+ - `created_at` — account age (experience proxy)
129
+
130
+ **Cursor:** Store the location query last used and page number. Rotate: UK →
131
+ Europe → Remote → repeat.
132
+
133
+ **Rate limit:** 10 requests/minute unauthenticated. Fetch at most 5 full
134
+ profiles per wake cycle (1 search + 5 profile fetches = 6 requests).
135
+
136
+ ### 3. dev.to
137
+
138
+ Developer articles where candidates signal availability via tags.
139
+
140
+ **Fetch articles tagged with job-seeking:**
141
+
142
+ ```
143
+ WebFetch URL: https://dev.to/api/articles?tag=opentowork&per_page=25
144
+ WebFetch URL: https://dev.to/api/articles?tag=lookingforwork&per_page=25
145
+ ```
146
+
147
+ For articles, parse `title`, `description`, `user.name`, `user.username`, `url`,
148
+ `tag_list`, `published_at`.
149
+
150
+ Skip articles older than 90 days — the candidate may no longer be looking.
151
+
152
+ Additional tags to try when primary tags yield no results:
153
+
154
+ - `jobsearch`
155
+ - `career`
156
+ - `hiring`
157
+ - `job`
158
+ - `remotework`
159
+
160
+ **Cursor:** Store the `id` of the most recent article processed.
161
+
162
+ **Rate limit:** dev.to API allows 30 requests per 30 seconds. One fetch per wake
163
+ is fine.
164
+
165
+ ---
166
+
167
+ ## Creative Fallback Strategies
168
+
169
+ When the primary query for a source yields zero new prospects after filtering,
170
+ try these alternative approaches before reporting an empty scan.
171
+
172
+ ### Strategy 1: Alternative Search Terms
173
+
174
+ Each source has multiple query variations. If the first query returns nothing
175
+ new, try the next variation:
176
+
177
+ **HN:**
178
+
179
+ - Check the previous month's "Who Wants to Be Hired?" thread (candidates post
180
+ late or threads stay active)
181
+ - Search for `"Who is hiring"` threads — candidates sometimes post in the wrong
182
+ thread, and comments may link to candidate profiles
183
+ - Try:
184
+ `https://hn.algolia.com/api/v1/search?query=%22freelancer+available%22&tags=comment`
185
+
186
+ **GitHub:**
187
+
188
+ - Search by skill + availability instead of just bio phrases:
189
+ ```
190
+ WebFetch URL: https://api.github.com/search/users?q=%22data+engineering%22+%22open+to+work%22&per_page=30&sort=joined&order=desc
191
+ WebFetch URL: https://api.github.com/search/users?q=%22full+stack%22+%22available+for+hire%22&per_page=30&sort=joined&order=desc
192
+ WebFetch URL: https://api.github.com/search/users?q=%22devops%22+%22looking+for%22&per_page=30&sort=joined&order=desc
193
+ ```
194
+ - Search repos with README availability signals:
195
+ ```
196
+ WebFetch URL: https://api.github.com/search/repositories?q=%22hire+me%22+in:readme&sort=updated&order=desc&per_page=10
197
+ ```
198
+ - Try different location terms: `Greece`, `Athens`, `Warsaw`, `Bucharest`,
199
+ `Sofia`, `Manchester`, `Edinburgh`
200
+
201
+ **dev.to:**
202
+
203
+ - Try broader tags: `jobsearch`, `career`, `remotework`
204
+ - Search articles directly:
205
+ ```
206
+ WebFetch URL: https://dev.to/api/articles?tag=career&per_page=25
207
+ ```
208
+ Then filter article titles/descriptions for availability signals.
209
+
210
+ ### Strategy 2: Relax Filters
211
+
212
+ If geographic filtering eliminated all candidates:
213
+
214
+ - Re-scan the same results without the location filter
215
+ - Candidates without stated locations may still be open to target regions
216
+ - Mark these as "location unconfirmed" in the prospect note
217
+
218
+ If skill alignment filtered everyone out:
219
+
220
+ - Lower the minimum bar from 2 framework skills to 1
221
+ - Look for transferable skills (e.g., strong Python → likely data integration
222
+ capability)
223
+ - Consider adjacent skill indicators (e.g., "machine learning" implies data
224
+ skills)
225
+
226
+ ### Strategy 3: Cross-Reference
227
+
228
+ When a source yields very few results, cross-reference what you do find:
229
+
230
+ - If a GitHub profile links to a blog or portfolio, check it for more detail
231
+ (via WebFetch) before deciding on skill fit
232
+ - If an HN post mentions a GitHub username, fetch their GitHub profile for
233
+ richer signal
234
+
235
+ ### Logging Alternatives
236
+
237
+ Log every alternative approach in `log.md`:
238
+
239
+ ```markdown
240
+ ## 2026-03-05 14:00
241
+
242
+ Source: github_open_to_work
243
+ Primary query: "open to work" location:UK — 30 results, 0 new after dedup
244
+ Alternative 1: "data engineering" "open to work" — 12 results, 1 new prospect
245
+ Alternative 2: "full stack" "available for hire" — 8 results, 0 new
246
+ Stopped after 2 alternatives (1 prospect found)
247
+ ```
248
+
249
+ ---
250
+
251
+ ## Failure Handling
252
+
253
+ When a WebFetch fails (HTTP 4xx, 5xx, timeout, empty response, or redirect to a
254
+ block page), handle it gracefully:
255
+
256
+ 1. **Record the failure** in `failures.tsv`:
257
+
258
+ ```bash
259
+ sed -i '' "s/^{source}\t.*/&/" ~/.cache/fit/basecamp/head-hunter/failures.tsv
260
+ # Or increment the count and update last_error
261
+ ```
262
+
263
+ 2. **Do not retry** the same source in this wake cycle. Move on.
264
+
265
+ 3. **Suspend after 3 consecutive failures.** During source selection, skip any
266
+ source with count ≥ 3 in `failures.tsv`. The agent should still attempt
267
+ suspended sources once every 7 days to detect recovery.
268
+
269
+ 4. **Common failure patterns:**
270
+ - **503 with HTML redirect** — Corporate proxy blocking the domain
271
+ (social-networking category). Source will not recover without network
272
+ change.
273
+ - **403 Forbidden** — API requires authentication or blocks automated
274
+ requests. Source may not recover.
275
+ - **429 Too Many Requests** — Rate limited. Will recover. Don't suspend
276
+ permanently.
277
+ - **Empty response / timeout** — Transient. Will likely recover.
278
+
279
+ 5. **Log all failures** in `log.md` with the HTTP status and error details.
280
+
281
+ 6. **Reset on success.** When a previously-failing source succeeds, reset its
282
+ count to 0 in `failures.tsv`.
283
+
284
+ ## Filtering Pipeline
285
+
286
+ Apply these filters to each candidate post, in order:
287
+
288
+ ### Filter 1: Open-for-Hire Signal
289
+
290
+ - HN "Who Wants to Be Hired?" — **auto-pass** (thread is explicitly opt-in)
291
+ - GitHub — must have `hireable: true`, or bio containing open-to-work phrases
292
+ - dev.to — must be tagged `opentowork` or `lookingforwork`
293
+
294
+ ### Filter 2: Deduplication
295
+
296
+ ```bash
297
+ grep -q "^{source}\t{post_id}\t" ~/.cache/fit/basecamp/head-hunter/seen.tsv
298
+ ```
299
+
300
+ If found, skip. Otherwise continue.
301
+
302
+ ### Filter 3: Geographic Fit
303
+
304
+ Look for location mentions. Prefer candidates in or open to:
305
+
306
+ - **US East Coast** (NYC, Boston, DC, Philadelphia, etc.)
307
+ - **UK** (London, Manchester, Edinburgh, etc.)
308
+ - **EU** — especially Greece, Poland, Romania, Bulgaria
309
+ - **Remote / Anywhere / Global**
310
+
311
+ Skip candidates explicitly locked to incompatible locations (e.g., "San
312
+ Francisco only", "APAC only"). When location is ambiguous or unstated, include
313
+ the candidate — let the user decide.
314
+
315
+ ### Filter 4: Skill Alignment
316
+
317
+ Run `npx fit-pathway skill --list` to get the framework skill inventory. Check
318
+ whether the candidate mentions skills that map to framework capabilities:
319
+
320
+ **Strong signals (forward-deployed track):**
321
+
322
+ - Multiple industries or domains in background
323
+ - Customer-facing project experience
324
+ - Data integration, analytics, visualization
325
+ - Full-stack development with business context
326
+ - Non-traditional path (law, policy, academia → tech)
327
+ - AI/ML tool proficiency (Claude, GPT, Cursor, "vibe coding")
328
+
329
+ **Strong signals (platform track):**
330
+
331
+ - Infrastructure, cloud platforms, DevOps
332
+ - Architecture and system design
333
+ - API design, shared services
334
+ - Performance, scalability, reliability
335
+
336
+ **Minimum bar:** At least 2 framework-relevant skills must be identifiable from
337
+ the post. Skip candidates with purely non-technical profiles.
338
+
339
+ ### Filter 5: Experience Level
340
+
341
+ Estimate career level from signals:
342
+
343
+ | Signal | Likely Level |
344
+ | ------------------------------------------- | ------------ |
345
+ | "junior", "entry-level", 0-2 years | J040 |
346
+ | "mid-level", 3-5 years | J060 |
347
+ | "senior", 5-8 years, "lead" | J070 |
348
+ | "staff", "principal", 8+ years, "architect" | J090+ |
349
+
350
+ When uncertain, default to J060 with low confidence.
351
+
352
+ ## Prospect Note Format
353
+
354
+ Write to `knowledge/Prospects/{Name}.md`:
355
+
356
+ ```markdown
357
+ # {Name}
358
+
359
+ ## Info
360
+ **Source:** [{platform}]({permalink})
361
+ **Date found:** {YYYY-MM-DD}
362
+ **Location:** {location or "Not specified"}
363
+ **Estimated level:** {J040–J110} (confidence: {high/medium/low})
364
+ **Best track fit:** {forward_deployed / platform / either}
365
+ **Match strength:** {strong / moderate}
366
+
367
+ ## Profile
368
+ {2-4 sentences: who they are, what they do, why they match. Reference specific
369
+ framework skill IDs in parentheses where possible.}
370
+
371
+ ## Framework Alignment
372
+ **Matching skills:** {comma-separated skill IDs}
373
+ **Key strengths:** {what stands out relative to the framework}
374
+ **Gaps:** {notable missing skills for the estimated role}
375
+
376
+ ## Source Post
377
+ > {Brief excerpt or summary of the original post — 2-3 lines max}
378
+
379
+ ## Notes
380
+ {Additional observations: polymath signals, AI tool usage, unusual background
381
+ combinations, anything noteworthy for the user}
382
+ ```
383
+
384
+ **Naming:** Use the candidate's display name as given. If only a username is
385
+ available, use the username. Never fabricate real names.
386
+
387
+ ## State Management Script
388
+
389
+ **Use the state script for ALL state file operations.** Do NOT write bespoke
390
+ scripts to update cursor, seen, prospects, failures, or log files.
391
+
392
+ node .claude/skills/scan-open-candidates/scripts/state.mjs <command> [args]
393
+
394
+ ### Commands
395
+
396
+ **Cursor** (source rotation):
397
+
398
+ ```bash
399
+ # Check which source to scan next
400
+ node scripts/state.mjs cursor list
401
+
402
+ # Get cursor for a specific source
403
+ node scripts/state.mjs cursor get github_open_to_work
404
+
405
+ # Update cursor after scanning
406
+ node scripts/state.mjs cursor set github_open_to_work "2026-03-09T22:00:00Z" "UK-done_next:Europe"
407
+ ```
408
+
409
+ **Seen** (deduplication):
410
+
411
+ ```bash
412
+ # Check if a candidate was already seen (exit 0=seen, 1=new)
413
+ node scripts/state.mjs seen check github_open_to_work mxmxmx333
414
+
415
+ # Mark one ID as seen
416
+ node scripts/state.mjs seen add github_open_to_work mxmxmx333
417
+
418
+ # Mark multiple IDs as seen in one call
419
+ node scripts/state.mjs seen batch github_open_to_work id1 id2 id3 id4
420
+ ```
421
+
422
+ **Prospects**:
423
+
424
+ ```bash
425
+ # Add a new prospect
426
+ node scripts/state.mjs prospect add "Hasan Cam" github_open_to_work strong "J060-J070 platform"
427
+
428
+ # List recent prospects
429
+ node scripts/state.mjs prospect list --limit 10
430
+
431
+ # Count total prospects
432
+ node scripts/state.mjs prospect count
433
+ ```
434
+
435
+ **Failures**:
436
+
437
+ ```bash
438
+ # Check failure count (for source selection — skip if ≥3)
439
+ node scripts/state.mjs failure get mastodon_hachyderm
440
+
441
+ # Record a fetch failure
442
+ node scripts/state.mjs failure increment mastodon_hachyderm
443
+
444
+ # Reset after successful fetch
445
+ node scripts/state.mjs failure reset github_open_to_work
446
+ ```
447
+
448
+ **Logging**:
449
+
450
+ ```bash
451
+ # Append a formatted wake cycle entry
452
+ node scripts/state.mjs log-wake github_open_to_work "Primary query: 'open to work' location:UK — 30 results, 2 new prospects"
453
+
454
+ # Append raw text
455
+ node scripts/state.mjs log "Manual note about source rotation"
456
+ ```
457
+
458
+ **Summary** (state overview):
459
+
460
+ ```bash
461
+ node scripts/state.mjs summary
462
+ ```
463
+
464
+ ## Quality Checklist
465
+
466
+ - [ ] Selected the least-recently-checked source from cursor.tsv
467
+ - [ ] Fetched data using WebFetch (not curl/wget)
468
+ - [ ] Applied all 5 filters in order
469
+ - [ ] Checked seen.tsv before processing each post
470
+ - [ ] Used fit-pathway to benchmark each prospect against a real job
471
+ - [ ] Prospect notes follow the standard format
472
+ - [ ] Updated cursor.tsv with new position
473
+ - [ ] Appended all processed post IDs to seen.tsv
474
+ - [ ] Appended new prospects to prospects.tsv
475
+ - [ ] Appended wake summary to log.md
476
+ - [ ] Wrote triage report to state directory