@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.
@@ -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 { mkdtempSync, writeFileSync, unlinkSync } from "node:fs";
15
- import { join } from "node:path";
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 email signature needed — Apple Mail appends it.`;
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 requisition export contains multiple sheets. This skill uses:
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
- Key-value pairs, one per row:
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
- ### Sheet 3 Candidates
70
-
71
- Row 3 contains column headers. Data rows start at row 4. After the last
72
- candidate, stage-summary rows appear (these are not candidates).
73
-
74
- | Column | Field | Maps to brief field… |
75
- | ------ | ---------------------- | ------------------------ |
76
- | B | Candidate name | `# {Name}` |
77
- | C | Stage | Status derivation |
78
- | D | Step / Disposition | Status derivation |
79
- | G | Resume filename | Reference only (no file) |
80
- | H | Date Applied | **First seen** |
81
- | I | Current Job Title | **Current title**, Title |
82
- | J | Current Company | **Current title** suffix |
83
- | K | Source | **Source** |
84
- | L | Referred by | **Source** suffix |
85
- | N | Availability Date | **Availability** |
86
- | O | Visa Requirement | Notes |
87
- | P | Eligible to Work | Notes |
88
- | Q | Relocation | Notes |
89
- | R | Salary Expectations | **Rate** |
90
- | S | Non-Compete | Notes |
91
- | T | Candidate Location | **Location** |
92
- | U | Phone | **Phone** |
93
- | V | Email | **Email** |
94
- | W | Total Years Experience | Summary context |
95
- | X | All Job Titles | Work History context |
96
- | Y | Companies | Work History context |
97
- | Z | Degrees | Education |
98
- | AA | Fields of Study | Education |
99
- | AB | Language | **English** / Language |
100
- | AC | Resume Text | `CV.md` content |
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 Workday stage and step/disposition to the `track-candidates` pipeline
157
- status:
158
-
159
- | Workday Step / Disposition | Pipeline Status |
160
- | ---------------------------- | ------------------ |
161
- | `Considered` | `new` |
162
- | `Manager Resume Screen` | `screening` |
163
- | `Assessment` | `screening` |
164
- | `Interview` / `Phone Screen` | `first-interview` |
165
- | `Second Interview` | `second-interview` |
166
- | `Reference Check` | `second-interview` |
167
- | `Offer` | `offer` |
168
- | `Employment Agreement` | `offer` |
169
- | `Background Check` | `hired` |
170
- | `Ready for Hire` | `hired` |
171
- | `Rejected` / `Declined` | `rejected` |
172
-
173
- If the step is identical to the stage (e.g. both "Considered"), default to
174
- `new`.
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