@forwardimpact/basecamp 2.4.1 → 2.5.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/config/scheduler.json +5 -0
- package/package.json +4 -1
- package/template/.claude/agents/chief-of-staff.md +14 -3
- package/template/.claude/agents/head-hunter.md +435 -0
- package/template/.claude/settings.json +4 -1
- package/template/.claude/skills/draft-emails/SKILL.md +29 -9
- package/template/.claude/skills/draft-emails/scripts/scan-emails.mjs +4 -4
- package/template/.claude/skills/draft-emails/scripts/send-email.mjs +41 -6
- package/template/.claude/skills/scan-open-candidates/SKILL.md +386 -0
- package/template/.claude/skills/workday-requisition/SKILL.md +86 -53
- package/template/.claude/skills/workday-requisition/scripts/parse-workday.mjs +107 -37
- package/template/CLAUDE.md +12 -2
|
@@ -6,13 +6,18 @@
|
|
|
6
6
|
* Apple Mail. The script writes a temporary .scpt file, executes it with
|
|
7
7
|
* osascript, and cleans up afterwards. Mail.app must be running.
|
|
8
8
|
*
|
|
9
|
-
* The body should be plain text — no HTML. Do NOT include an email signature
|
|
10
|
-
* Apple Mail appends the user's configured signature automatically.
|
|
9
|
+
* The body should be plain text — no HTML. Do NOT include an email signature
|
|
10
|
+
* or sign-off; Apple Mail appends the user's configured signature automatically.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { execFileSync } from "node:child_process";
|
|
14
|
-
import {
|
|
15
|
-
|
|
14
|
+
import {
|
|
15
|
+
appendFileSync,
|
|
16
|
+
mkdtempSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
unlinkSync,
|
|
19
|
+
} from "node:fs";
|
|
20
|
+
import { basename, join } from "node:path";
|
|
16
21
|
import { tmpdir } from "node:os";
|
|
17
22
|
|
|
18
23
|
const HELP = `send-email — send an email via Apple Mail
|
|
@@ -25,9 +30,10 @@ Options:
|
|
|
25
30
|
--bcc <addrs> Comma-separated BCC recipients
|
|
26
31
|
--subject <subj> Email subject line (required)
|
|
27
32
|
--body <text> Plain-text email body (required)
|
|
33
|
+
--draft <path> Draft file — deleted after send, ID appended to drafts/handled
|
|
28
34
|
-h, --help Show this help message and exit
|
|
29
35
|
|
|
30
|
-
Mail.app must be running. No
|
|
36
|
+
Mail.app must be running. No signature or sign-off needed — Apple Mail appends it.`;
|
|
31
37
|
|
|
32
38
|
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
33
39
|
console.log(HELP);
|
|
@@ -59,7 +65,8 @@ function main() {
|
|
|
59
65
|
cc = "",
|
|
60
66
|
bcc = "",
|
|
61
67
|
subject = "",
|
|
62
|
-
body = ""
|
|
68
|
+
body = "",
|
|
69
|
+
draft = "";
|
|
63
70
|
|
|
64
71
|
for (let i = 0; i < args.length; i++) {
|
|
65
72
|
switch (args[i]) {
|
|
@@ -78,6 +85,9 @@ function main() {
|
|
|
78
85
|
case "--body":
|
|
79
86
|
body = args[++i] ?? "";
|
|
80
87
|
break;
|
|
88
|
+
case "--draft":
|
|
89
|
+
draft = args[++i] ?? "";
|
|
90
|
+
break;
|
|
81
91
|
}
|
|
82
92
|
}
|
|
83
93
|
|
|
@@ -87,6 +97,13 @@ function main() {
|
|
|
87
97
|
process.exit(1);
|
|
88
98
|
}
|
|
89
99
|
|
|
100
|
+
// Strip leading two-space padding from each line and trim overall whitespace
|
|
101
|
+
body = body
|
|
102
|
+
.split("\n")
|
|
103
|
+
.map((line) => line.replace(/^ /, ""))
|
|
104
|
+
.join("\n")
|
|
105
|
+
.trim();
|
|
106
|
+
|
|
90
107
|
const lines = [
|
|
91
108
|
'tell application "Mail"',
|
|
92
109
|
` set newMessage to make new outgoing message with properties {subject:"${escapeAS(subject)}", content:"${escapeAS(body)}", visible:false}`,
|
|
@@ -106,6 +123,24 @@ function main() {
|
|
|
106
123
|
writeFileSync(tmp, lines);
|
|
107
124
|
execFileSync("osascript", [tmp], { stdio: "inherit" });
|
|
108
125
|
console.log(`Sent: ${subject}`);
|
|
126
|
+
|
|
127
|
+
// Clean up draft and mark thread as handled
|
|
128
|
+
if (draft) {
|
|
129
|
+
try {
|
|
130
|
+
unlinkSync(draft);
|
|
131
|
+
console.log(`Removed draft: ${draft}`);
|
|
132
|
+
} catch {
|
|
133
|
+
// ignore if draft already gone
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Extract email ID from draft filename (e.g. "drafts/12345_draft.md" → "12345")
|
|
137
|
+
const draftBasename = basename(draft, ".md");
|
|
138
|
+
const emailId = draftBasename.replace(/_draft$/, "");
|
|
139
|
+
if (emailId) {
|
|
140
|
+
appendFileSync("drafts/handled", emailId + "\n");
|
|
141
|
+
console.log(`Marked as handled: ${emailId}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
109
144
|
} finally {
|
|
110
145
|
try {
|
|
111
146
|
unlinkSync(tmp);
|
|
@@ -0,0 +1,386 @@
|
|
|
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
|
+
- `"available for hire"`
|
|
100
|
+
- `"seeking opportunities"`
|
|
101
|
+
- `"seeking new role"`
|
|
102
|
+
- `"open to new opportunities"`
|
|
103
|
+
- `"currently exploring"`
|
|
104
|
+
- `"freelance" "available"`
|
|
105
|
+
- `"between roles"`
|
|
106
|
+
- `"on the market"`
|
|
107
|
+
- `"open to opportunities"`
|
|
108
|
+
|
|
109
|
+
Response has `total_count` and `items` array. Each item has `login`,
|
|
110
|
+
`html_url`, `score`.
|
|
111
|
+
|
|
112
|
+
**Fetch full profile for promising candidates:**
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
WebFetch URL: https://api.github.com/users/{login}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Profile fields:
|
|
119
|
+
- `name` — display name
|
|
120
|
+
- `bio` — bio text (contains open-to-work signals)
|
|
121
|
+
- `location` — geographic location
|
|
122
|
+
- `hireable` — boolean, explicit hire signal
|
|
123
|
+
- `blog` — personal site URL
|
|
124
|
+
- `company` — current employer (null = likely available)
|
|
125
|
+
- `public_repos` — repository count (technical depth indicator)
|
|
126
|
+
- `created_at` — account age (experience proxy)
|
|
127
|
+
|
|
128
|
+
**Cursor:** Store the location query last used and page number. Rotate:
|
|
129
|
+
UK → Europe → Remote → repeat.
|
|
130
|
+
|
|
131
|
+
**Rate limit:** 10 requests/minute unauthenticated. Fetch at most 5 full
|
|
132
|
+
profiles per wake cycle (1 search + 5 profile fetches = 6 requests).
|
|
133
|
+
|
|
134
|
+
### 3. dev.to
|
|
135
|
+
|
|
136
|
+
Developer articles where candidates signal availability via tags.
|
|
137
|
+
|
|
138
|
+
**Fetch articles tagged with job-seeking:**
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
WebFetch URL: https://dev.to/api/articles?tag=opentowork&per_page=25
|
|
142
|
+
WebFetch URL: https://dev.to/api/articles?tag=lookingforwork&per_page=25
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
For articles, parse `title`, `description`, `user.name`, `user.username`,
|
|
146
|
+
`url`, `tag_list`, `published_at`.
|
|
147
|
+
|
|
148
|
+
Skip articles older than 90 days — the candidate may no longer be looking.
|
|
149
|
+
|
|
150
|
+
Additional tags to try when primary tags yield no results:
|
|
151
|
+
- `jobsearch`
|
|
152
|
+
- `career`
|
|
153
|
+
- `hiring`
|
|
154
|
+
- `job`
|
|
155
|
+
- `remotework`
|
|
156
|
+
|
|
157
|
+
**Cursor:** Store the `id` of the most recent article processed.
|
|
158
|
+
|
|
159
|
+
**Rate limit:** dev.to API allows 30 requests per 30 seconds. One fetch per
|
|
160
|
+
wake is fine.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Creative Fallback Strategies
|
|
165
|
+
|
|
166
|
+
When the primary query for a source yields zero new prospects after filtering,
|
|
167
|
+
try these alternative approaches before reporting an empty scan.
|
|
168
|
+
|
|
169
|
+
### Strategy 1: Alternative Search Terms
|
|
170
|
+
|
|
171
|
+
Each source has multiple query variations. If the first query returns nothing
|
|
172
|
+
new, try the next variation:
|
|
173
|
+
|
|
174
|
+
**HN:**
|
|
175
|
+
- Check the previous month's "Who Wants to Be Hired?" thread (candidates post
|
|
176
|
+
late or threads stay active)
|
|
177
|
+
- Search for `"Who is hiring"` threads — candidates sometimes post in the wrong
|
|
178
|
+
thread, and comments may link to candidate profiles
|
|
179
|
+
- Try: `https://hn.algolia.com/api/v1/search?query=%22freelancer+available%22&tags=comment`
|
|
180
|
+
|
|
181
|
+
**GitHub:**
|
|
182
|
+
- Search by skill + availability instead of just bio phrases:
|
|
183
|
+
```
|
|
184
|
+
WebFetch URL: https://api.github.com/search/users?q=%22data+engineering%22+%22open+to+work%22&per_page=30&sort=joined&order=desc
|
|
185
|
+
WebFetch URL: https://api.github.com/search/users?q=%22full+stack%22+%22available+for+hire%22&per_page=30&sort=joined&order=desc
|
|
186
|
+
WebFetch URL: https://api.github.com/search/users?q=%22devops%22+%22looking+for%22&per_page=30&sort=joined&order=desc
|
|
187
|
+
```
|
|
188
|
+
- Search repos with README availability signals:
|
|
189
|
+
```
|
|
190
|
+
WebFetch URL: https://api.github.com/search/repositories?q=%22hire+me%22+in:readme&sort=updated&order=desc&per_page=10
|
|
191
|
+
```
|
|
192
|
+
- Try different location terms: `Greece`, `Athens`, `Warsaw`, `Bucharest`,
|
|
193
|
+
`Sofia`, `Manchester`, `Edinburgh`
|
|
194
|
+
|
|
195
|
+
**dev.to:**
|
|
196
|
+
- Try broader tags: `jobsearch`, `career`, `remotework`
|
|
197
|
+
- Search articles directly:
|
|
198
|
+
```
|
|
199
|
+
WebFetch URL: https://dev.to/api/articles?tag=career&per_page=25
|
|
200
|
+
```
|
|
201
|
+
Then filter article titles/descriptions for availability signals.
|
|
202
|
+
|
|
203
|
+
### Strategy 2: Relax Filters
|
|
204
|
+
|
|
205
|
+
If geographic filtering eliminated all candidates:
|
|
206
|
+
- Re-scan the same results without the location filter
|
|
207
|
+
- Candidates without stated locations may still be open to target regions
|
|
208
|
+
- Mark these as "location unconfirmed" in the prospect note
|
|
209
|
+
|
|
210
|
+
If skill alignment filtered everyone out:
|
|
211
|
+
- Lower the minimum bar from 2 framework skills to 1
|
|
212
|
+
- Look for transferable skills (e.g., strong Python → likely data integration
|
|
213
|
+
capability)
|
|
214
|
+
- Consider adjacent skill indicators (e.g., "machine learning" implies
|
|
215
|
+
data skills)
|
|
216
|
+
|
|
217
|
+
### Strategy 3: Cross-Reference
|
|
218
|
+
|
|
219
|
+
When a source yields very few results, cross-reference what you do find:
|
|
220
|
+
- If a GitHub profile links to a blog or portfolio, check it for more detail
|
|
221
|
+
(via WebFetch) before deciding on skill fit
|
|
222
|
+
- If an HN post mentions a GitHub username, fetch their GitHub profile for
|
|
223
|
+
richer signal
|
|
224
|
+
|
|
225
|
+
### Logging Alternatives
|
|
226
|
+
|
|
227
|
+
Log every alternative approach in `log.md`:
|
|
228
|
+
|
|
229
|
+
```markdown
|
|
230
|
+
## 2026-03-05 14:00
|
|
231
|
+
|
|
232
|
+
Source: github_open_to_work
|
|
233
|
+
Primary query: "open to work" location:UK — 30 results, 0 new after dedup
|
|
234
|
+
Alternative 1: "data engineering" "open to work" — 12 results, 1 new prospect
|
|
235
|
+
Alternative 2: "full stack" "available for hire" — 8 results, 0 new
|
|
236
|
+
Stopped after 2 alternatives (1 prospect found)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Failure Handling
|
|
242
|
+
|
|
243
|
+
When a WebFetch fails (HTTP 4xx, 5xx, timeout, empty response, or redirect to
|
|
244
|
+
a block page), handle it gracefully:
|
|
245
|
+
|
|
246
|
+
1. **Record the failure** in `failures.tsv`:
|
|
247
|
+
```bash
|
|
248
|
+
sed -i '' "s/^{source}\t.*/&/" ~/.cache/fit/basecamp/head-hunter/failures.tsv
|
|
249
|
+
# Or increment the count and update last_error
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
2. **Do not retry** the same source in this wake cycle. Move on.
|
|
253
|
+
|
|
254
|
+
3. **Suspend after 3 consecutive failures.** During source selection, skip any
|
|
255
|
+
source with count ≥ 3 in `failures.tsv`. The agent should still attempt
|
|
256
|
+
suspended sources once every 7 days to detect recovery.
|
|
257
|
+
|
|
258
|
+
4. **Common failure patterns:**
|
|
259
|
+
- **503 with HTML redirect** — Corporate proxy blocking the domain
|
|
260
|
+
(social-networking category). Source will not recover without network
|
|
261
|
+
change.
|
|
262
|
+
- **403 Forbidden** — API requires authentication or blocks automated
|
|
263
|
+
requests. Source may not recover.
|
|
264
|
+
- **429 Too Many Requests** — Rate limited. Will recover. Don't suspend
|
|
265
|
+
permanently.
|
|
266
|
+
- **Empty response / timeout** — Transient. Will likely recover.
|
|
267
|
+
|
|
268
|
+
5. **Log all failures** in `log.md` with the HTTP status and error details.
|
|
269
|
+
|
|
270
|
+
6. **Reset on success.** When a previously-failing source succeeds, reset its
|
|
271
|
+
count to 0 in `failures.tsv`.
|
|
272
|
+
|
|
273
|
+
## Filtering Pipeline
|
|
274
|
+
|
|
275
|
+
Apply these filters to each candidate post, in order:
|
|
276
|
+
|
|
277
|
+
### Filter 1: Open-for-Hire Signal
|
|
278
|
+
|
|
279
|
+
- HN "Who Wants to Be Hired?" — **auto-pass** (thread is explicitly opt-in)
|
|
280
|
+
- GitHub — must have `hireable: true`, or bio containing open-to-work phrases
|
|
281
|
+
- dev.to — must be tagged `opentowork` or `lookingforwork`
|
|
282
|
+
|
|
283
|
+
### Filter 2: Deduplication
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
grep -q "^{source}\t{post_id}\t" ~/.cache/fit/basecamp/head-hunter/seen.tsv
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
If found, skip. Otherwise continue.
|
|
290
|
+
|
|
291
|
+
### Filter 3: Geographic Fit
|
|
292
|
+
|
|
293
|
+
Look for location mentions. Prefer candidates in or open to:
|
|
294
|
+
|
|
295
|
+
- **US East Coast** (NYC, Boston, DC, Philadelphia, etc.)
|
|
296
|
+
- **UK** (London, Manchester, Edinburgh, etc.)
|
|
297
|
+
- **EU** — especially Greece, Poland, Romania, Bulgaria
|
|
298
|
+
- **Remote / Anywhere / Global**
|
|
299
|
+
|
|
300
|
+
Skip candidates explicitly locked to incompatible locations (e.g., "San
|
|
301
|
+
Francisco only", "APAC only"). When location is ambiguous or unstated, include
|
|
302
|
+
the candidate — let the user decide.
|
|
303
|
+
|
|
304
|
+
### Filter 4: Skill Alignment
|
|
305
|
+
|
|
306
|
+
Run `npx fit-pathway skill --list` to get the framework skill inventory. Check
|
|
307
|
+
whether the candidate mentions skills that map to framework capabilities:
|
|
308
|
+
|
|
309
|
+
**Strong signals (forward-deployed track):**
|
|
310
|
+
- Multiple industries or domains in background
|
|
311
|
+
- Customer-facing project experience
|
|
312
|
+
- Data integration, analytics, visualization
|
|
313
|
+
- Full-stack development with business context
|
|
314
|
+
- Non-traditional path (law, policy, academia → tech)
|
|
315
|
+
- AI/ML tool proficiency (Claude, GPT, Cursor, "vibe coding")
|
|
316
|
+
|
|
317
|
+
**Strong signals (platform track):**
|
|
318
|
+
- Infrastructure, cloud platforms, DevOps
|
|
319
|
+
- Architecture and system design
|
|
320
|
+
- API design, shared services
|
|
321
|
+
- Performance, scalability, reliability
|
|
322
|
+
|
|
323
|
+
**Minimum bar:** At least 2 framework-relevant skills must be identifiable from
|
|
324
|
+
the post. Skip candidates with purely non-technical profiles.
|
|
325
|
+
|
|
326
|
+
### Filter 5: Experience Level
|
|
327
|
+
|
|
328
|
+
Estimate career level from signals:
|
|
329
|
+
|
|
330
|
+
| Signal | Likely Level |
|
|
331
|
+
| ------------------------------------------- | ------------ |
|
|
332
|
+
| "junior", "entry-level", 0-2 years | J040 |
|
|
333
|
+
| "mid-level", 3-5 years | J060 |
|
|
334
|
+
| "senior", 5-8 years, "lead" | J070 |
|
|
335
|
+
| "staff", "principal", 8+ years, "architect" | J090+ |
|
|
336
|
+
|
|
337
|
+
When uncertain, default to J060 with low confidence.
|
|
338
|
+
|
|
339
|
+
## Prospect Note Format
|
|
340
|
+
|
|
341
|
+
Write to `knowledge/Prospects/{Name}.md`:
|
|
342
|
+
|
|
343
|
+
```markdown
|
|
344
|
+
# {Name}
|
|
345
|
+
|
|
346
|
+
## Info
|
|
347
|
+
**Source:** [{platform}]({permalink})
|
|
348
|
+
**Date found:** {YYYY-MM-DD}
|
|
349
|
+
**Location:** {location or "Not specified"}
|
|
350
|
+
**Estimated level:** {J040–J110} (confidence: {high/medium/low})
|
|
351
|
+
**Best track fit:** {forward_deployed / platform / either}
|
|
352
|
+
**Match strength:** {strong / moderate}
|
|
353
|
+
|
|
354
|
+
## Profile
|
|
355
|
+
{2-4 sentences: who they are, what they do, why they match. Reference specific
|
|
356
|
+
framework skill IDs in parentheses where possible.}
|
|
357
|
+
|
|
358
|
+
## Framework Alignment
|
|
359
|
+
**Matching skills:** {comma-separated skill IDs}
|
|
360
|
+
**Key strengths:** {what stands out relative to the framework}
|
|
361
|
+
**Gaps:** {notable missing skills for the estimated role}
|
|
362
|
+
|
|
363
|
+
## Source Post
|
|
364
|
+
> {Brief excerpt or summary of the original post — 2-3 lines max}
|
|
365
|
+
|
|
366
|
+
## Notes
|
|
367
|
+
{Additional observations: polymath signals, AI tool usage, unusual background
|
|
368
|
+
combinations, anything noteworthy for the user}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Naming:** Use the candidate's display name as given. If only a username is
|
|
372
|
+
available, use the username. Never fabricate real names.
|
|
373
|
+
|
|
374
|
+
## Quality Checklist
|
|
375
|
+
|
|
376
|
+
- [ ] Selected the least-recently-checked source from cursor.tsv
|
|
377
|
+
- [ ] Fetched data using WebFetch (not curl/wget)
|
|
378
|
+
- [ ] Applied all 5 filters in order
|
|
379
|
+
- [ ] Checked seen.tsv before processing each post
|
|
380
|
+
- [ ] Used fit-pathway to benchmark each prospect against a real job
|
|
381
|
+
- [ ] Prospect notes follow the standard format
|
|
382
|
+
- [ ] Updated cursor.tsv with new position
|
|
383
|
+
- [ ] Appended all processed post IDs to seen.tsv
|
|
384
|
+
- [ ] Appended new prospects to prospects.tsv
|
|
385
|
+
- [ ] Appended wake summary to log.md
|
|
386
|
+
- [ ] Wrote triage report to state directory
|
|
@@ -49,11 +49,13 @@ Run this skill:
|
|
|
49
49
|
|
|
50
50
|
## Workday Export Format
|
|
51
51
|
|
|
52
|
-
The Workday
|
|
52
|
+
The Workday export format varies between versions. The parser handles both
|
|
53
|
+
automatically using header-driven column mapping and dynamic header row
|
|
54
|
+
detection.
|
|
53
55
|
|
|
54
56
|
### Sheet 1 — Requisition Metadata
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
**Old format** — key-value pairs with Hiring Manager, Recruiter, Location:
|
|
57
59
|
|
|
58
60
|
| Row | Field | Example |
|
|
59
61
|
| --- | --------------------- | -------------------------------------- |
|
|
@@ -66,38 +68,58 @@ Key-value pairs, one per row:
|
|
|
66
68
|
| 7 | Recruiter Title | `Recruiter` |
|
|
67
69
|
| 8 | Recruiter | Name |
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Row
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
|
|
|
77
|
-
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
98
|
-
|
|
|
99
|
-
|
|
|
100
|
-
|
|
|
71
|
+
**New format** — stage-count summary (no HM/Recruiter/Location):
|
|
72
|
+
|
|
73
|
+
| Row | Field | Example |
|
|
74
|
+
| --- | ------------------------ | -------------------------------------- |
|
|
75
|
+
| 1 | Title header | `4951493 Principal Software Engineer…` |
|
|
76
|
+
| 2 | Active Candidates | `74 of 74` |
|
|
77
|
+
| 3 | Active Referrals | `3 of 3` |
|
|
78
|
+
| 4 | Active Internal | `4 of 4` |
|
|
79
|
+
| 7+ | Stage counts | `56 → Considered` |
|
|
80
|
+
|
|
81
|
+
### Candidates Sheet
|
|
82
|
+
|
|
83
|
+
The parser auto-detects the candidates sheet and header row:
|
|
84
|
+
- **Old format**: 3+ sheets; candidates on "Candidates" sheet or Sheet3;
|
|
85
|
+
header at row 3 (index 2); two "Job Application" columns
|
|
86
|
+
- **New format**: 2 sheets; candidates on Sheet2; header at row 8 (index 7);
|
|
87
|
+
single "Job Application" column
|
|
88
|
+
|
|
89
|
+
Column mapping is header-driven — the parser reads the header row and maps
|
|
90
|
+
columns by name, not position. Columns that vary between exports (e.g.
|
|
91
|
+
"Jobs Applied to", "Referred by", "Convenience Task") are handled
|
|
92
|
+
automatically.
|
|
93
|
+
|
|
94
|
+
**Core columns** (present in all formats):
|
|
95
|
+
|
|
96
|
+
| Header | Maps to brief field… |
|
|
97
|
+
| ---------------------- | ------------------------ |
|
|
98
|
+
| Job Application | `# {Name}` |
|
|
99
|
+
| Stage | Row detection only (not used for status) |
|
|
100
|
+
| Step / Disposition | **Workday step** → status derivation |
|
|
101
|
+
| Resume | Reference only (no file) |
|
|
102
|
+
| Date Applied | **First seen** |
|
|
103
|
+
| Current Job Title | **Current title**, Title |
|
|
104
|
+
| Current Company | **Current title** suffix |
|
|
105
|
+
| Source | **Source** |
|
|
106
|
+
| Referred by | **Source** suffix |
|
|
107
|
+
| Candidate Location | **Location** |
|
|
108
|
+
| Phone | **Phone** |
|
|
109
|
+
| Email | **Email** |
|
|
110
|
+
| Availability Date | **Availability** |
|
|
111
|
+
| Visa Requirement | Notes |
|
|
112
|
+
| Eligible to Work | Notes |
|
|
113
|
+
| Relocation | Notes |
|
|
114
|
+
| Salary Expectations | **Rate** |
|
|
115
|
+
| Non-Compete | Notes |
|
|
116
|
+
| Total Years Experience | Summary context |
|
|
117
|
+
| All Job Titles | Work History context |
|
|
118
|
+
| Companies | Work History context |
|
|
119
|
+
| Degrees | Education |
|
|
120
|
+
| Fields of Study | Education |
|
|
121
|
+
| Language | **English** / Language |
|
|
122
|
+
| Resume Text | `CV.md` content |
|
|
101
123
|
|
|
102
124
|
#### Name Annotations
|
|
103
125
|
|
|
@@ -153,25 +175,36 @@ Use fuzzy matching — the Workday name may differ slightly from an existing not
|
|
|
153
175
|
|
|
154
176
|
## Step 3: Determine Pipeline Status
|
|
155
177
|
|
|
156
|
-
Map
|
|
157
|
-
status
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
|
161
|
-
|
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
`
|
|
178
|
+
Map the **Step / Disposition** column to the `track-candidates` pipeline
|
|
179
|
+
status. Do NOT use the Stage column for status — it is only used for row
|
|
180
|
+
detection (stop condition):
|
|
181
|
+
|
|
182
|
+
| Workday Step / Disposition | Pipeline Status |
|
|
183
|
+
| ------------------------------------------ | ------------------ |
|
|
184
|
+
| `Considered` | `new` |
|
|
185
|
+
| `Review` | `new` |
|
|
186
|
+
| `Manager Resume Screen` | `screening` |
|
|
187
|
+
| `Schedule Recruiter Phone Screen` | `screening` |
|
|
188
|
+
| `Manager Request to Move Forward (HS)` | `screening` |
|
|
189
|
+
| `Proposed Interview Slate` | `screening` |
|
|
190
|
+
| `Assessment` | `screening` |
|
|
191
|
+
| `Manager Request to Decline (HS)` | `rejected` |
|
|
192
|
+
| `Interview` / `Phone Screen` | `first-interview` |
|
|
193
|
+
| `Second Interview` | `second-interview` |
|
|
194
|
+
| `Reference Check` | `second-interview` |
|
|
195
|
+
| `Offer` | `offer` |
|
|
196
|
+
| `Employment Agreement` | `offer` |
|
|
197
|
+
| `Background Check` | `hired` |
|
|
198
|
+
| `Ready for Hire` | `hired` |
|
|
199
|
+
| `Rejected` / `Declined` | `rejected` |
|
|
200
|
+
|
|
201
|
+
If the step value is empty or not recognized, default to `new`.
|
|
202
|
+
|
|
203
|
+
**Important:** The raw `step` value is always preserved in the JSON output and
|
|
204
|
+
should be stored in the candidate brief's **Pipeline** section (e.g.
|
|
205
|
+
`Applied via LinkedIn — Step: Manager Request to Move Forward (HS)`). This
|
|
206
|
+
allows the user to filter and query candidates by their exact Workday
|
|
207
|
+
disposition.
|
|
175
208
|
|
|
176
209
|
## Step 4: Create CV.md from Resume Text
|
|
177
210
|
|