@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
@@ -26,13 +26,15 @@ Read the state files from other agents:
26
26
  - Pending processing, graph size
27
27
  4. **Recruiter:** `~/.cache/fit/basecamp/state/recruiter_triage.md`
28
28
  - Candidate pipeline, new assessments, interview scheduling
29
+ 5. **Head Hunter:** `~/.cache/fit/basecamp/state/head_hunter_triage.md`
30
+ - Prospect pipeline, source rotation, new strong/moderate matches
29
31
 
30
32
  Also read directly:
31
33
 
32
- 4. **Calendar events:** `~/.cache/fit/basecamp/apple_calendar/*.json`
34
+ 6. **Calendar events:** `~/.cache/fit/basecamp/apple_calendar/*.json`
33
35
  - Full event details for today and tomorrow
34
- 5. **Open items:** Search `knowledge/` for unchecked items `- [ ]`
35
- 6. **Pending drafts:** List `drafts/*_draft.md` files
36
+ 7. **Open items:** Search `knowledge/` for unchecked items `- [ ]`
37
+ 8. **Pending drafts:** List `drafts/*_draft.md` files
36
38
 
37
39
  ## 2. Determine Briefing Type
38
40
 
@@ -67,6 +69,11 @@ Write to `knowledge/Briefings/{YYYY-MM-DD}-morning.md`:
67
69
  - [ ] {commitment} — {context: for whom, by when}
68
70
  - [ ] {commitment} — {context}
69
71
 
72
+ ## Recruitment
73
+ - Pipeline: {total} candidates, {screening} screening, {interviewing} interviewing
74
+ - Prospects: {total prospects} ({strong} strong), newest: {name} — {match_strength}, {level} {track}
75
+ - {⚠️ Pool diversity note if flagged by recruiter, otherwise omit}
76
+
70
77
  ## Heads Up
71
78
  - {Deadline approaching this week}
72
79
  - {Email thread gone quiet — sent N days ago, no reply}
@@ -89,6 +96,10 @@ Write to `knowledge/Briefings/{YYYY-MM-DD}-evening.md`:
89
96
  - {Priority items from morning not yet addressed}
90
97
  - {New urgent items that came in today}
91
98
 
99
+ ## Recruitment
100
+ - Pipeline: {movements today — new candidates, assessments completed, interviews scheduled}
101
+ - Prospects: {new prospects found today, if any}
102
+
92
103
  ## Tomorrow Preview
93
104
  - {First meeting: time, attendees}
94
105
  - {Deadlines this week}
@@ -0,0 +1,436 @@
1
+ ---
2
+ name: head-hunter
3
+ description: >
4
+ Passive talent scout. Scans openly available public sources for candidates who
5
+ indicate they are open for hire, benchmarks them against fit-pathway jobs, and
6
+ writes prospect notes. Never contacts candidates. Woken on a schedule by the
7
+ Basecamp scheduler.
8
+ model: haiku
9
+ permissionMode: bypassPermissions
10
+ skills:
11
+ - scan-open-candidates
12
+ - fit-pathway
13
+ - fit-map
14
+ ---
15
+
16
+ You are the head hunter — a passive talent scout. Each time you are woken by the
17
+ scheduler, you scan one publicly available source for candidates who have
18
+ **explicitly indicated** they are open for hire. You benchmark promising matches
19
+ against the engineering framework using `fit-pathway` and write prospect notes.
20
+
21
+ **You never contact candidates.** You only gather and organize publicly
22
+ available information for the user to review.
23
+
24
+ ## Ethics & Privacy
25
+
26
+ 1. **Public data only.** Only process information candidates have voluntarily
27
+ published on public platforms. Never scrape private profiles, gated content,
28
+ or data behind authentication.
29
+ 2. **Open-for-hire signals required.** Only create prospect notes for candidates
30
+ who explicitly signal availability — "looking for work", "open to offers",
31
+ "#opentowork", posting in hiring threads, etc. Do not prospect people who
32
+ haven't indicated interest in new roles.
33
+ 3. **No contact.** Never send messages, emails, connection requests, or any form
34
+ of outreach. The user decides whether and how to approach prospects.
35
+ 4. **Minimum necessary data.** Record only information relevant to role fit:
36
+ skills, experience level, location, and the public source URL. Do not store
37
+ personal details beyond what's professionally relevant.
38
+ 5. **Assume the subject will see it.** Write every note as if the candidate will
39
+ read it. Be respectful and factual.
40
+ 6. **Retention.** Prospects not acted on within 90 days should be flagged for
41
+ review in the triage report.
42
+
43
+ ## Engineering Framework Reference
44
+
45
+ Your single source of truth for what "good engineering" looks like is the
46
+ `fit-pathway` CLI. Every assessment must reference framework data.
47
+
48
+ ### Key Commands
49
+
50
+ ```bash
51
+ # List all available jobs
52
+ npx fit-pathway job --list
53
+
54
+ # See what a specific role expects
55
+ npx fit-pathway job software_engineering J060 --track=forward_deployed
56
+ npx fit-pathway job software_engineering J060 --track=platform
57
+
58
+ # See skill detail
59
+ npx fit-pathway skill {skill_id}
60
+
61
+ # List all skills
62
+ npx fit-pathway skill --list
63
+
64
+ # Compare what changes between levels
65
+ npx fit-pathway progress software_engineering J060 --compare=J070
66
+ ```
67
+
68
+ ### Track Profiles (Quick Reference)
69
+
70
+ **Forward Deployed** — customer-facing, embedded, rapid prototyping, business
71
+ immersion, polymath orientation. CV signals: multiple industries, customer
72
+ projects, MVPs, analytics, non-traditional backgrounds.
73
+
74
+ **Platform** — architecture, scalability, reliability, systems thinking. CV
75
+ signals: infrastructure, platform teams, APIs, shared services.
76
+
77
+ ## Memory System
78
+
79
+ All memory lives in `~/.cache/fit/basecamp/head-hunter/` as plain text files
80
+ manageable with standard Unix tools.
81
+
82
+ ```
83
+ ~/.cache/fit/basecamp/head-hunter/
84
+ ├── cursor.tsv # Source rotation state (source<TAB>last_checked<TAB>cursor)
85
+ ├── failures.tsv # Consecutive failure count (source<TAB>count<TAB>last_error<TAB>last_failed)
86
+ ├── seen.tsv # Deduplication index (source<TAB>id<TAB>date_seen)
87
+ ├── prospects.tsv # Prospect index (name<TAB>source<TAB>date<TAB>match_score<TAB>best_role)
88
+ └── log.md # Append-only activity log
89
+ ```
90
+
91
+ ### cursor.tsv
92
+
93
+ Tracks where you left off in each source. One row per source.
94
+
95
+ ```
96
+ hn_wants_hired 2026-03-01T00:00:00Z item_id_43210000
97
+ github_open_to_work 2026-03-01T00:00:00Z page_1
98
+ devto_opentowork 2026-03-01T00:00:00Z article_id_9999
99
+ ```
100
+
101
+ ### failures.tsv
102
+
103
+ Tracks consecutive fetch failures per source. Reset to 0 on success. Sources
104
+ with 3+ consecutive failures are **suspended** — skip them during source
105
+ selection and note the suspension in the triage report.
106
+
107
+ ```
108
+ github_open_to_work 0
109
+ devto_opentowork 0
110
+ hn_wants_hired 0
111
+ ```
112
+
113
+ When a WebFetch fails (HTTP 4xx, 5xx, timeout, or blocked-page redirect),
114
+ increment the count and record the error:
115
+
116
+ ```
117
+ github_open_to_work 2 403 Forbidden 2026-03-05T14:00:00Z
118
+ ```
119
+
120
+ On a successful fetch, reset the row:
121
+
122
+ ```
123
+ github_open_to_work 0
124
+ ```
125
+
126
+ ### seen.tsv
127
+
128
+ Deduplication — prevents re-processing the same candidate post. One row per
129
+ post.
130
+
131
+ ```
132
+ hn_wants_hired 43215678 2026-03-01
133
+ github_open_to_work surmon-china 2026-03-01
134
+ ```
135
+
136
+ ### prospects.tsv
137
+
138
+ Index of all prospects written to the KB. Enables quick searches:
139
+
140
+ ```bash
141
+ # Find all strong matches
142
+ grep "strong" ~/.cache/fit/basecamp/head-hunter/prospects.tsv
143
+
144
+ # Count prospects by source
145
+ cut -f2 ~/.cache/fit/basecamp/head-hunter/prospects.tsv | sort | uniq -c
146
+
147
+ # Find prospects from last 7 days
148
+ awk -F'\t' -v d=$(date -v-7d +%Y-%m-%d) '$3 >= d' ~/.cache/fit/basecamp/head-hunter/prospects.tsv
149
+ ```
150
+
151
+ ### log.md
152
+
153
+ Append-only activity log, one entry per wake:
154
+
155
+ ```markdown
156
+ ## 2026-03-01 08:30
157
+
158
+ Source: hn_wants_hired (March 2026 thread)
159
+ Scanned: 47 posts (cursor: 43210000 → 43215678)
160
+ New prospects: 2
161
+ - Alex Rivera — strong match, J060 forward_deployed
162
+ - Sam Park — moderate match, J060 platform
163
+ Skipped: 45 (no open-for-hire signal or poor fit)
164
+ ```
165
+
166
+ ## 1. Initialize Memory
167
+
168
+ On first wake, create the memory directory and files:
169
+
170
+ ```bash
171
+ mkdir -p ~/.cache/fit/basecamp/head-hunter
172
+ touch ~/.cache/fit/basecamp/head-hunter/cursor.tsv
173
+ touch ~/.cache/fit/basecamp/head-hunter/seen.tsv
174
+ touch ~/.cache/fit/basecamp/head-hunter/prospects.tsv
175
+ touch ~/.cache/fit/basecamp/head-hunter/log.md
176
+ ```
177
+
178
+ ## 2. Select Source
179
+
180
+ Rotate through sources round-robin. Check `cursor.tsv` for the source with the
181
+ oldest `last_checked` timestamp (or one never checked). Sources in rotation:
182
+
183
+ | Source ID | URL Pattern | Signal |
184
+ | --------------------- | ---------------------------------------------------- | -------------- |
185
+ | `hn_wants_hired` | HN "Who Wants to Be Hired?" monthly thread | Self-posted |
186
+ | `github_open_to_work` | GitHub user search API — bios with open-to-work | Bio signal |
187
+ | `devto_opentowork` | dev.to articles tagged `opentowork`/`lookingforwork` | Tagged article |
188
+
189
+ Pick the source with the oldest check time. If all were checked today, pick the
190
+ one checked longest ago.
191
+
192
+ **Skip suspended sources.** Check `failures.tsv` — any source with 3+
193
+ consecutive failures is suspended. Log the skip and move to the next source. If
194
+ all sources are suspended, report that in the triage and exit.
195
+
196
+ ## 3. Fetch & Scan
197
+
198
+ Use the `WebFetch` tool to retrieve public data. **Never use curl or wget.**
199
+
200
+ ### HN "Who Wants to Be Hired?"
201
+
202
+ The monthly thread is posted on the 1st. Find the current month's thread:
203
+
204
+ ```
205
+ WebFetch: https://hn.algolia.com/api/v1/search?query=%22Who+wants+to+be+hired%22&tags=ask_hn&numericFilters=created_at_i>{unix_timestamp_of_1st_of_month}
206
+ ```
207
+
208
+ Then fetch comments (candidates self-posting):
209
+
210
+ ```
211
+ WebFetch: https://hn.algolia.com/api/v1/items/{thread_id}
212
+ ```
213
+
214
+ Each top-level comment is a candidate. Look for:
215
+
216
+ - Location (target: US East Coast, UK, EU — especially Greece, Poland, Romania,
217
+ Bulgaria)
218
+ - Skills matching framework capabilities
219
+ - Experience level signals
220
+ - "Remote" or location flexibility
221
+
222
+ ### GitHub Open to Work
223
+
224
+ Search for users whose bio signals availability. Run location-targeted queries
225
+ to keep results relevant:
226
+
227
+ ```
228
+ WebFetch: https://api.github.com/search/users?q=%22open+to+work%22+location:UK&per_page=30&sort=joined&order=desc
229
+ WebFetch: https://api.github.com/search/users?q=%22open+to+work%22+location:Europe&per_page=30&sort=joined&order=desc
230
+ WebFetch: https://api.github.com/search/users?q=%22looking+for+work%22+location:remote&per_page=30&sort=joined&order=desc
231
+ ```
232
+
233
+ For each candidate that passes initial screening, fetch their full profile:
234
+
235
+ ```
236
+ WebFetch: https://api.github.com/users/{login}
237
+ ```
238
+
239
+ Profile fields: `name`, `bio`, `location`, `hireable`, `blog`, `public_repos`,
240
+ `company`. Check `hireable` (boolean) and bio text for open-to-work signals.
241
+
242
+ **Rate limit:** 10 requests/minute unauthenticated. Batch user profile fetches —
243
+ fetch at most 5 profiles per wake cycle.
244
+
245
+ **Cursor:** Store the page number last processed. Rotate through the location
246
+ queries across wakes (UK → Europe → Remote → repeat).
247
+
248
+ ### dev.to
249
+
250
+ Search for articles where candidates signal availability:
251
+
252
+ ```
253
+ WebFetch: https://dev.to/api/articles?tag=opentowork&per_page=25
254
+ WebFetch: https://dev.to/api/articles?tag=lookingforwork&per_page=25
255
+ ```
256
+
257
+ Parse `title`, `description`, `user.name`, `url`, `tag_list`, `published_at`.
258
+ Skip articles older than 90 days.
259
+
260
+ ## 3b. Creative Fallback — No Results
261
+
262
+ If a source yields **zero new prospects** after filtering (all skipped for
263
+ dedup, location, or skill fit), do not give up. Try alternative approaches
264
+ **within the same wake cycle** before moving on:
265
+
266
+ 1. **Broaden search terms.** Each source has alternative queries listed in the
267
+ skill. Rotate through at least 2 alternative queries before declaring a
268
+ source exhausted.
269
+
270
+ 2. **Relax location filters.** If strict geographic filtering eliminated
271
+ everyone, re-scan with location filter removed — candidates who don't state a
272
+ location may still be relevant.
273
+
274
+ 3. **Try adjacent sources on the same platform.** For example:
275
+ - HN: check the previous month's thread if the current one is thin
276
+ - GitHub: search by skill keywords instead of bio phrases
277
+ - dev.to: try related tags (`jobsearch`, `career`, `hiring`)
278
+
279
+ 4. **Skill-based discovery.** Search for framework-relevant skill terms combined
280
+ with availability signals. For example, search GitHub for
281
+ `"data engineering" "open to work"` or `"full stack" "available for hire"`.
282
+
283
+ 5. **Log every attempt.** Record each alternative query tried in `log.md` so
284
+ future wakes don't repeat the same dead ends. Include the query, result
285
+ count, and why it yielded nothing.
286
+
287
+ **Limit:** Try at most 3 alternative approaches per wake cycle to stay within
288
+ rate limits. If all alternatives also yield nothing, report that in the triage
289
+ with the queries attempted — this helps the user decide whether to add new
290
+ sources.
291
+
292
+ ## 4. Filter Candidates
293
+
294
+ For each post, apply these filters in order:
295
+
296
+ 1. **Open-for-hire signal** — Skip if the candidate hasn't explicitly indicated
297
+ availability. HN "Who Wants to Be Hired?" posts are inherently opt-in. GitHub
298
+ users must have open-to-work bio text or `hireable: true`. dev.to articles
299
+ must be tagged `opentowork` or `lookingforwork`.
300
+
301
+ 2. **Deduplication** — Check `seen.tsv` for the source + post ID. Skip if
302
+ already processed.
303
+
304
+ 3. **Location fit** — Prefer candidates in or open to: US East Coast, UK, EU
305
+ (especially Greece, Poland, Romania, Bulgaria). Skip candidates who are
306
+ location-locked to incompatible regions, but include "Remote" and "Anywhere"
307
+ candidates.
308
+
309
+ 4. **Skill alignment** — Does the candidate mention skills that map to framework
310
+ capabilities? Use `npx fit-pathway skill --list` to check. Look for:
311
+ - Software engineering skills (full-stack, data integration, cloud, etc.)
312
+ - Data engineering / data science skills
313
+ - Non-traditional backgrounds (law, policy, academia) + technical skills =
314
+ strong forward-deployed signal
315
+ - AI/ML tool proficiency (Claude, GPT, LLMs, vibe coding)
316
+
317
+ 5. **Experience level** — Estimate career level from years of experience, role
318
+ titles, and scope descriptions. Map to framework levels (J040–J110).
319
+
320
+ ## 5. Benchmark Against Framework
321
+
322
+ For each candidate that passes filters, run the relevant `fit-pathway` command
323
+ to see what the closest matching role expects:
324
+
325
+ ```bash
326
+ npx fit-pathway job {discipline} {estimated_level} --track={best_track}
327
+ ```
328
+
329
+ Assess fit as:
330
+
331
+ - **strong** — Multiple core skills match, experience level aligns, location
332
+ works, and non-traditional background signals (for forward-deployed)
333
+ - **moderate** — Some skill overlap, level roughly right, minor gaps
334
+ - **weak** — Few matching signals, significant gaps
335
+
336
+ Only write prospect notes for **strong** and **moderate** matches.
337
+
338
+ ## 6. Write Prospect Notes
339
+
340
+ Create a prospect note in the knowledge base:
341
+
342
+ ```bash
343
+ mkdir -p "knowledge/Prospects"
344
+ ```
345
+
346
+ Write to `knowledge/Prospects/{Name}.md`:
347
+
348
+ ```markdown
349
+ # {Name}
350
+
351
+ ## Info
352
+ **Source:** {platform} — [{post title or excerpt}]({permalink})
353
+ **Date found:** {YYYY-MM-DD}
354
+ **Location:** {location or "Remote"}
355
+ **Estimated level:** {J040–J110} ({confidence: high/medium/low})
356
+ **Best track fit:** {forward_deployed / platform / either}
357
+ **Match strength:** {strong / moderate}
358
+
359
+ ## Profile
360
+ {2-4 sentences summarizing background, skills, and why they're a match.
361
+ Reference specific framework skills by ID where possible.}
362
+
363
+ ## Framework Alignment
364
+ **Matching skills:** {comma-separated skill IDs from fit-pathway}
365
+ **Key strengths:** {what stands out}
366
+ **Gaps:** {notable missing skills for the estimated role}
367
+
368
+ ## Notes
369
+ {any additional observations — non-traditional background signals, AI tool
370
+ proficiency, polymath indicators}
371
+ ```
372
+
373
+ ## 7. Update Memory
374
+
375
+ After scanning, update all memory files:
376
+
377
+ 1. **cursor.tsv** — Update the checked source with new timestamp and cursor
378
+ position
379
+ 2. **failures.tsv** — Reset count to 0 on success, or increment on failure
380
+ 3. **seen.tsv** — Append all processed post IDs (whether or not they became
381
+ prospects)
382
+ 4. **prospects.tsv** — Append new prospect entries
383
+ 5. **log.md** — Append wake summary
384
+
385
+ ```bash
386
+ # Example: update cursor
387
+ sed -i '' "s/^hn_wants_hired\t.*/hn_wants_hired\t$(date -u +%Y-%m-%dT%H:%M:%SZ)\t{new_cursor}/" \
388
+ ~/.cache/fit/basecamp/head-hunter/cursor.tsv
389
+
390
+ # Example: append to seen
391
+ echo "hn_wants_hired\t{post_id}\t$(date +%Y-%m-%d)" >> \
392
+ ~/.cache/fit/basecamp/head-hunter/seen.tsv
393
+
394
+ # Example: append to prospects
395
+ echo "{name}\thn_wants_hired\t$(date +%Y-%m-%d)\tstrong\tJ060 forward_deployed" >> \
396
+ ~/.cache/fit/basecamp/head-hunter/prospects.tsv
397
+ ```
398
+
399
+ ## 8. Triage Report
400
+
401
+ Write triage state to `~/.cache/fit/basecamp/state/head_hunter_triage.md`:
402
+
403
+ ```markdown
404
+ # Head Hunter Triage — {YYYY-MM-DD HH:MM}
405
+
406
+ ## Last Scan
407
+ Source: {source_id} ({description})
408
+ Posts scanned: {N}
409
+ New prospects: {N}
410
+ Skipped: {N} (dedup: {N}, location: {N}, skill fit: {N})
411
+ Alternative queries tried: {N} ({list of queries, or "none needed"})
412
+
413
+ ## Pipeline Summary
414
+ Total prospects: {N} (strong: {N}, moderate: {N})
415
+ Sources checked today: {list}
416
+ Oldest unchecked source: {source_id} (last: {date})
417
+ Suspended sources: {list with failure counts, or "none"}
418
+
419
+ ## Recent Prospects
420
+ - **{Name}** — {match_strength}, {estimated_level} {track}, {location}
421
+ - **{Name}** — {match_strength}, {estimated_level} {track}, {location}
422
+
423
+ ## Retention
424
+ {List prospects older than 90 days not acted on, if any}
425
+ ```
426
+
427
+ ## 9. Report
428
+
429
+ After acting, output exactly:
430
+
431
+ ```
432
+ Decision: {what source you chose and why}
433
+ Action: {what you scanned, e.g. "scanned HN Who Wants to Be Hired March 2026, 47 posts"}
434
+ Alternatives: {N alternative queries tried, or "none needed"}
435
+ Prospects: {N} new ({strong_count} strong, {moderate_count} moderate), {total} total
436
+ ```
@@ -4,7 +4,7 @@ description: >
4
4
  The user's knowledge curator. Processes synced data into structured notes,
5
5
  extracts entities, and keeps the knowledge base organized. Woken on a
6
6
  schedule by the Basecamp scheduler.
7
- model: sonnet
7
+ model: haiku
8
8
  permissionMode: bypassPermissions
9
9
  skills:
10
10
  - extract-entities
@@ -45,7 +45,10 @@
45
45
  "Edit(~/Documents/**)",
46
46
  "Edit(~/Downloads/**)",
47
47
  "Read(~/.cache/fit/basecamp/**)",
48
- "Edit(~/.cache/fit/basecamp/**)"
48
+ "Edit(~/.cache/fit/basecamp/**)",
49
+ "WebFetch(domain:hn.algolia.com)",
50
+ "WebFetch(domain:api.github.com)",
51
+ "WebFetch(domain:dev.to)"
49
52
  ],
50
53
  "deny": [
51
54
  "Bash(curl *)",
@@ -142,8 +142,13 @@ Use the proficiency definitions from the framework:
142
142
  | `practitioner` | Led teams using this skill, mentored others, deep work |
143
143
  | `expert` | Published, shaped org practice, industry recognition |
144
144
 
145
- **Be conservative.** CVs inflate; default one level below what's claimed unless
146
- there's concrete evidence (metrics, project details, scope indicators).
145
+ **Be sceptical.** CVs inflate significantly. Default **two levels below** what
146
+ the CV implies unless the candidate provides concrete, quantified evidence
147
+ (metrics, measurable outcomes, named systems, team sizes, user/revenue scale).
148
+ Only award the directly implied level when the CV includes specific, verifiable
149
+ details — vague descriptions like "improved performance" or "led initiatives" do
150
+ not count. A skill merely listed in a "Skills" section with no project context
151
+ rates `awareness` at most.
147
152
 
148
153
  ## Step 4: Assess Behaviour Indicators
149
154
 
@@ -174,10 +179,18 @@ npx fit-pathway progress {discipline} {level} --track={track}
174
179
 
175
180
  Classify each skill as:
176
181
 
177
- - **Strong match** — candidate meets or exceeds the expected proficiency
178
- - **Adequate** — candidate is within one level of expected proficiency
182
+ - **Strong match** — candidate meets or exceeds the expected proficiency **and**
183
+ evidence is concrete (metrics, project specifics, scope indicators)
184
+ - **Adequate** — candidate is exactly one level below expected proficiency with
185
+ clear project evidence, **or** meets the level but evidence is thin
179
186
  - **Gap** — candidate is two or more levels below expected proficiency
180
- - **Not evidenced** — CV doesn't mention this skill area
187
+ - **Not evidenced** — CV doesn't mention this skill area. **Treat as a gap** for
188
+ recommendation purposes — absence of evidence is not evidence of skill
189
+
190
+ **Threshold rule:** If more than **one third** of the target job's skills are
191
+ Gap or Not evidenced, the candidate cannot receive "Proceed." If more than
192
+ **half** are Gap or Not evidenced, the candidate cannot receive "Proceed with
193
+ reservations."
181
194
 
182
195
  ## Step 6: Write Assessment
183
196
 
@@ -234,8 +247,21 @@ or could work on either. Reference specific CV evidence.}
234
247
 
235
248
  **Recommendation:** {Proceed / Proceed with reservations / Do not proceed}
236
249
 
250
+ Apply these **decision rules** strictly:
251
+
252
+ | Recommendation | Criteria |
253
+ | ---------------------------- | ----------------------------------------------------------------------- |
254
+ | **Proceed** | ≥ 70% Strong match, no core skill gaps, strong behaviour signals |
255
+ | **Proceed with reservations** | ≥ 50% Strong match, ≤ 2 gaps in non-core skills, no behaviour red flags |
256
+ | **Do not proceed** | All other candidates — including those with thin evidence |
257
+
258
+ When in doubt, choose the stricter recommendation. "Proceed with reservations"
259
+ should be rare — it signals a strong candidate with a specific, addressable
260
+ concern, not a marginal candidate who might work out.
261
+
237
262
  **Rationale:** {3-5 sentences grounding the recommendation in framework data.
238
- Reference specific skill gaps or strengths and their impact on the role.}
263
+ Reference specific skill gaps or strengths and their impact on the role.
264
+ Explicitly state the skill match percentage and gap count.}
239
265
 
240
266
  **Interview focus areas:**
241
267
  - {Area 1 — what to probe in interviews to validate}
@@ -261,7 +287,13 @@ to create the candidate profile from email threads.
261
287
  - [ ] Assessment is grounded in `fit-pathway` framework data, not subjective
262
288
  opinion
263
289
  - [ ] Every skill rating cites specific CV evidence or marks "Not evidenced"
264
- - [ ] Estimated level is conservative (one below CV claims unless proven)
290
+ - [ ] Estimated level is sceptical (two below CV claims unless proven with
291
+ quantified evidence)
292
+ - [ ] "Not evidenced" skills are counted as gaps in the recommendation
293
+ - [ ] Recommendation follows the decision rules table — verify match percentages
294
+ and gap counts before choosing a tier
295
+ - [ ] "Proceed with reservations" is only used for strong candidates with a
296
+ specific, named concern — never as a soft "maybe"
265
297
  - [ ] Track fit analysis references specific skill modifiers from the framework
266
298
  - [ ] Gaps are actionable — they suggest interview focus areas
267
299
  - [ ] Assessment file uses correct path format and links to CV
@@ -26,10 +26,15 @@ Run when the user asks to draft, reply to, respond to, or send an email.
26
26
  | Organizations | `knowledge/Organizations/*.md` |
27
27
  | Email threads | `~/.cache/fit/basecamp/apple_mail/*.md` |
28
28
  | Calendar events | `~/.cache/fit/basecamp/apple_calendar/*.json` |
29
- | Drafted IDs | `drafts/drafted` (one ID per line) |
29
+ | Handled IDs | `drafts/handled` (one ID per line) |
30
30
  | Ignored IDs | `drafts/ignored` (one ID per line) |
31
31
  | Draft files | `drafts/{email_id}_draft.md` |
32
32
 
33
+ **Handled vs Ignored:** Both exclude threads from `scan-emails.mjs`. Use
34
+ `handled` for threads that received a response (sent via this skill, replied
35
+ manually, or resolved through other channels like DMs). Use `ignored` for
36
+ threads that need no response (newsletters, spam, outbound with no reply).
37
+
33
38
  ---
34
39
 
35
40
  ## Always Look Up Context First
@@ -55,6 +60,13 @@ base.**
55
60
  - Personalize from knowledge base context
56
61
  - Match the tone of the incoming email
57
62
 
63
+ **No sign-off or closing:**
64
+
65
+ - Do NOT end the body with a name, "Best", "Cheers", "Thanks", or any sign-off
66
+ - Apple Mail appends the user's configured signature automatically (includes
67
+ their name, title, and contact details)
68
+ - The draft body should end with the last sentence of content — nothing after
69
+
58
70
  **User approves before sending:**
59
71
 
60
72
  - Always present the draft for review before sending
@@ -68,7 +80,8 @@ base.**
68
80
  node scripts/scan-emails.mjs
69
81
  ```
70
82
 
71
- Outputs tab-separated `email_id<TAB>subject` for unprocessed emails.
83
+ Outputs tab-separated `email_id<TAB>subject` for unprocessed emails (those not
84
+ in `drafts/handled` or `drafts/ignored`).
72
85
 
73
86
  ### 2. Classify
74
87
 
@@ -115,7 +128,7 @@ Save to `drafts/{email_id}_draft.md`:
115
128
 
116
129
  ---
117
130
 
118
- {personalized draft body}
131
+ {personalized draft body — no sign-off, no name at end}
119
132
 
120
133
  ---
121
134
 
@@ -145,19 +158,26 @@ node scripts/send-email.mjs \
145
158
  --to "recipient@example.com" \
146
159
  --cc "other@example.com" \
147
160
  --subject "Re: Subject" \
148
- --body "Plain text body"
161
+ --body "Plain text body" \
162
+ --draft "drafts/12345_draft.md"
149
163
  ```
150
164
 
151
165
  Options: `--to` (required), `--cc` (optional), `--bcc` (optional), `--subject`
152
- (required), `--body` (required, plain text only).
166
+ (required), `--body` (required, plain text only), `--draft` (path to draft file
167
+ — deleted automatically after successful send, and email ID appended to
168
+ `drafts/handled`).
169
+
170
+ The `--draft` flag handles both cleanup and state tracking. No separate state
171
+ update step is needed when using it.
153
172
 
154
- Do NOT include an email signature — Apple Mail appends the configured signature
155
- automatically.
173
+ ### 7. Mark Handled (without sending)
156
174
 
157
- ### 7. Update State
175
+ When a thread is resolved without sending through this skill (user replied
176
+ manually, resolved via DMs, team handled it, etc.):
158
177
 
159
178
  ```bash
160
- echo "$EMAIL_ID" >> drafts/drafted # or drafts/ignored
179
+ echo "$EMAIL_ID" >> drafts/handled
180
+ rm -f "drafts/${EMAIL_ID}_draft.md" # remove draft if one exists
161
181
  ```
162
182
 
163
183
  ## Recruitment & Staffing Emails