@forwardimpact/basecamp 2.3.1 → 2.4.1
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/README.md +20 -0
- package/package.json +4 -1
- package/template/.claude/agents/recruiter.md +28 -28
- package/template/.claude/skills/analyze-cv/SKILL.md +35 -35
- package/template/.claude/skills/right-to-be-forgotten/SKILL.md +20 -17
- package/template/.claude/skills/sync-apple-mail/SKILL.md +2 -2
- package/template/.claude/skills/track-candidates/SKILL.md +25 -24
- package/template/.claude/skills/upstream-skill/SKILL.md +6 -6
- package/template/.claude/skills/workday-requisition/SKILL.md +341 -0
- package/template/.claude/skills/workday-requisition/scripts/parse-workday.mjs +243 -0
- package/template/CLAUDE.md +18 -17
package/README.md
CHANGED
|
@@ -165,6 +165,25 @@ vi ~/.fit/basecamp/scheduler.json
|
|
|
165
165
|
{ "type": "once", "runAt": "2025-02-12T10:00:00Z" }
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
+
## Updating
|
|
169
|
+
|
|
170
|
+
When you upgrade Basecamp (install a new `.pkg`), the installer automatically
|
|
171
|
+
runs `--update` on all configured knowledge bases. This pushes the latest
|
|
172
|
+
`CLAUDE.md`, skills, and agents into each KB without touching your data.
|
|
173
|
+
|
|
174
|
+
You can also run it manually at any time:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Update all configured knowledge bases
|
|
178
|
+
/Applications/Basecamp.app/Contents/MacOS/fit-basecamp --update
|
|
179
|
+
|
|
180
|
+
# Update a specific knowledge base
|
|
181
|
+
/Applications/Basecamp.app/Contents/MacOS/fit-basecamp --update ~/Documents/Personal
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The update merges `.claude/settings.json` non-destructively — new entries are
|
|
185
|
+
added but your existing permissions are preserved.
|
|
186
|
+
|
|
168
187
|
## CLI Reference
|
|
169
188
|
|
|
170
189
|
```
|
|
@@ -172,6 +191,7 @@ fit-basecamp Run due tasks once and exit
|
|
|
172
191
|
fit-basecamp --daemon Run continuously (poll every 60s)
|
|
173
192
|
fit-basecamp --run <task> Run a specific task immediately
|
|
174
193
|
fit-basecamp --init <path> Initialize a new knowledge base
|
|
194
|
+
fit-basecamp --update [path] Update KB skills, agents, and CLAUDE.md
|
|
175
195
|
fit-basecamp --status Show knowledge bases and task status
|
|
176
196
|
fit-basecamp --validate Validate agents and skills exist
|
|
177
197
|
fit-basecamp --help Show this help
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/basecamp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "Claude Code-native personal knowledge system with autonomous agents",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -28,5 +28,8 @@
|
|
|
28
28
|
],
|
|
29
29
|
"engines": {
|
|
30
30
|
"node": ">=18.0.0"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"xlsx": "^0.18.5"
|
|
31
34
|
}
|
|
32
35
|
}
|
|
@@ -29,14 +29,14 @@ framework.
|
|
|
29
29
|
|
|
30
30
|
### Career Levels
|
|
31
31
|
|
|
32
|
-
| Level | Title Pattern
|
|
33
|
-
| ----- |
|
|
34
|
-
| J040 | Level I / Associate
|
|
35
|
-
| J060 | Level II / Senior Assoc | Independent on familiar problems
|
|
36
|
-
| J070 | Level III / Manager
|
|
37
|
-
| J090 | Staff / Senior Mgr
|
|
38
|
-
| J100 | Principal / Director
|
|
39
|
-
| J110 | Senior Principal
|
|
32
|
+
| Level | Title Pattern | Key Indicators |
|
|
33
|
+
| ----- | ----------------------- | ---------------------------------------- |
|
|
34
|
+
| J040 | Level I / Associate | Learning, needs guidance, basic tasks |
|
|
35
|
+
| J060 | Level II / Senior Assoc | Independent on familiar problems |
|
|
36
|
+
| J070 | Level III / Manager | Handles ambiguity, mentors others |
|
|
37
|
+
| J090 | Staff / Senior Mgr | Area scope, leads complex initiatives |
|
|
38
|
+
| J100 | Principal / Director | Org-wide impact, shapes direction |
|
|
39
|
+
| J110 | Senior Principal | Enterprise strategy, cross-org influence |
|
|
40
40
|
|
|
41
41
|
### Forward Deployed vs Platform — Track Differences
|
|
42
42
|
|
|
@@ -47,14 +47,14 @@ track fit right is critical for hiring success.
|
|
|
47
47
|
(Commercial, Manufacturing, R&D). They operate like a "startup CTO" — bridging
|
|
48
48
|
product and business, discovering patterns in the field.
|
|
49
49
|
|
|
50
|
-
| Dimension
|
|
51
|
-
|
|
|
52
|
-
| **Core strength**
|
|
53
|
-
| **Boosted skills
|
|
54
|
-
| **Reduced skills
|
|
55
|
-
| **Key behaviours
|
|
56
|
-
| **Mindset**
|
|
57
|
-
| **Typical CV signals** | Multiple industries, customer projects, MVPs, analytics
|
|
50
|
+
| Dimension | Forward Deployed | Platform |
|
|
51
|
+
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
52
|
+
| **Core strength** | Delivery, domain immersion, rapid prototyping | Architecture, scalability, reliability |
|
|
53
|
+
| **Boosted skills** | Data Integration, Full-Stack Dev, Problem Discovery, Business Immersion, Stakeholder Mgmt, Model Development | Architecture & Design, Cloud Platforms, Code Quality, DevOps & CI/CD, Data Modeling, Technical Debt Mgmt |
|
|
54
|
+
| **Reduced skills** | Scale, Reliability, Process capabilities | Delivery capability |
|
|
55
|
+
| **Key behaviours** | Own the Outcome (+1), Be Polymath Oriented (+1), Don't Lose Your Curiosity (+1), Communicate with Precision (+1) | Think in Systems (+1), Communicate with Precision (+1) |
|
|
56
|
+
| **Mindset** | Ship fast, learn from users, bridge business & tech | Build for the long term, treat devs as customers |
|
|
57
|
+
| **Typical CV signals** | Multiple industries, customer projects, MVPs, analytics | Infrastructure, platform teams, APIs, shared services |
|
|
58
58
|
|
|
59
59
|
**Hiring implications:**
|
|
60
60
|
|
|
@@ -76,8 +76,8 @@ engineering_management — tracks: dx
|
|
|
76
76
|
product_management — no tracks
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
Use `npx fit-pathway discipline {id}` to see skill tiers and behaviour
|
|
80
|
-
|
|
79
|
+
Use `npx fit-pathway discipline {id}` to see skill tiers and behaviour modifiers
|
|
80
|
+
for each discipline.
|
|
81
81
|
|
|
82
82
|
## Data Protection
|
|
83
83
|
|
|
@@ -100,14 +100,14 @@ professional information.
|
|
|
100
100
|
views, religious beliefs, sexual orientation, or other special category data
|
|
101
101
|
— even if it appears in a CV or email.
|
|
102
102
|
5. **Assume the candidate will see it.** Write every assessment and note as if
|
|
103
|
-
the candidate will request a copy (GDPR Article 15 — right of access). If
|
|
104
|
-
|
|
103
|
+
the candidate will request a copy (GDPR Article 15 — right of access). If you
|
|
104
|
+
wouldn't be comfortable sharing it with them, don't write it.
|
|
105
105
|
|
|
106
106
|
## Human Oversight
|
|
107
107
|
|
|
108
108
|
This agent **recommends** — the user **decides**. Automated recruitment tools
|
|
109
|
-
carry legal and ethical risk when they make consequential decisions without
|
|
110
|
-
review.
|
|
109
|
+
carry legal and ethical risk when they make consequential decisions without
|
|
110
|
+
human review.
|
|
111
111
|
|
|
112
112
|
**Hard rules:**
|
|
113
113
|
|
|
@@ -115,8 +115,8 @@ review.
|
|
|
115
115
|
proceed," but the user must make the final rejection decision. Assessments
|
|
116
116
|
are advisory, not dispositive.
|
|
117
117
|
2. **Level estimates are hypotheses.** Always present estimated career level
|
|
118
|
-
with confidence language ("likely J060", "evidence suggests J070") — never
|
|
119
|
-
|
|
118
|
+
with confidence language ("likely J060", "evidence suggests J070") — never as
|
|
119
|
+
definitive fact. CVs are incomplete signals.
|
|
120
120
|
3. **Flag uncertainty.** When evidence is thin or ambiguous, say so explicitly.
|
|
121
121
|
Recommend interview focus areas to resolve uncertainty rather than guessing.
|
|
122
122
|
4. **No ranking by protected characteristics.** Never sort, filter, or rank
|
|
@@ -145,8 +145,8 @@ diverse candidates don't exist.
|
|
|
145
145
|
gender or any other protected characteristic.
|
|
146
146
|
4. **Track sourcing channels.** When a sourcing channel consistently produces
|
|
147
147
|
homogeneous candidate pools, note **the channel pattern** (not individual
|
|
148
|
-
candidates) in `knowledge/Candidates/Insights.md` so the user can address
|
|
149
|
-
|
|
148
|
+
candidates) in `knowledge/Candidates/Insights.md` so the user can address it
|
|
149
|
+
with the agency.
|
|
150
150
|
5. **Gender data handling.** Gender is recorded only when explicitly stated in
|
|
151
151
|
recruiter communications (pronouns, titles like "Ms./Mr."). Never infer
|
|
152
152
|
gender from names. Record as `Woman`, `Man`, or `—` (unknown). When
|
|
@@ -180,8 +180,8 @@ for dir in knowledge/Candidates/*/; do
|
|
|
180
180
|
done
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
For each unassessed candidate with a CV, run the `analyze-cv` skill workflow.
|
|
184
|
-
|
|
183
|
+
For each unassessed candidate with a CV, run the `analyze-cv` skill workflow. If
|
|
184
|
+
the target role is known from the candidate brief, use it:
|
|
185
185
|
|
|
186
186
|
```bash
|
|
187
187
|
# Look up the role for context
|
|
@@ -50,17 +50,17 @@ Run this skill:
|
|
|
50
50
|
|
|
51
51
|
Read the candidate's CV file. Extract:
|
|
52
52
|
|
|
53
|
-
| Field
|
|
54
|
-
|
|
|
55
|
-
| **Current role**
|
|
56
|
-
| **Years of experience
|
|
57
|
-
| **Technical skills**
|
|
58
|
-
| **Domain experience**
|
|
59
|
-
| **Education**
|
|
60
|
-
| **Leadership signals**
|
|
61
|
-
| **Scope signals**
|
|
62
|
-
| **Communication**
|
|
63
|
-
| **Gender**
|
|
53
|
+
| Field | What to look for |
|
|
54
|
+
| ----------------------- | --------------------------------------------------- |
|
|
55
|
+
| **Current role** | Most recent job title |
|
|
56
|
+
| **Years of experience** | Total and per-role tenure |
|
|
57
|
+
| **Technical skills** | Languages, frameworks, platforms, tools mentioned |
|
|
58
|
+
| **Domain experience** | Industries, business domains, customer-facing work |
|
|
59
|
+
| **Education** | Degrees, certifications, relevant courses |
|
|
60
|
+
| **Leadership signals** | Team size, mentoring, cross-team work, architecture |
|
|
61
|
+
| **Scope signals** | Scale of systems, user base, revenue impact |
|
|
62
|
+
| **Communication** | Publications, talks, open source, documentation |
|
|
63
|
+
| **Gender** | Pronouns, gendered titles (never infer from names) |
|
|
64
64
|
|
|
65
65
|
## Step 2: Look Up the Framework Reference
|
|
66
66
|
|
|
@@ -112,15 +112,15 @@ npx fit-pathway track platform
|
|
|
112
112
|
|
|
113
113
|
Map CV evidence to track indicators:
|
|
114
114
|
|
|
115
|
-
| Forward Deployed signals
|
|
116
|
-
|
|
|
117
|
-
| Customer-facing projects
|
|
118
|
-
| Business domain immersion
|
|
119
|
-
| Rapid prototyping, MVPs
|
|
120
|
-
| Data integration, analytics
|
|
121
|
-
| Stakeholder management
|
|
122
|
-
| Cross-functional work
|
|
123
|
-
| Multiple industries or domain breadth
|
|
115
|
+
| Forward Deployed signals | Platform signals |
|
|
116
|
+
| ------------------------------------- | --------------------------------------- |
|
|
117
|
+
| Customer-facing projects | Internal tooling / shared services |
|
|
118
|
+
| Business domain immersion | Infrastructure / platform-as-product |
|
|
119
|
+
| Rapid prototyping, MVPs | Architecture, system design |
|
|
120
|
+
| Data integration, analytics | CI/CD, DevOps, reliability |
|
|
121
|
+
| Stakeholder management | Code quality, technical debt management |
|
|
122
|
+
| Cross-functional work | Scalability, performance engineering |
|
|
123
|
+
| Multiple industries or domain breadth | Deep platform ownership |
|
|
124
124
|
|
|
125
125
|
## Step 3: Map CV to Framework Skills
|
|
126
126
|
|
|
@@ -134,13 +134,13 @@ npx fit-pathway skill {skill_id}
|
|
|
134
134
|
|
|
135
135
|
Use the proficiency definitions from the framework:
|
|
136
136
|
|
|
137
|
-
| Proficiency | CV Evidence
|
|
138
|
-
| -------------- |
|
|
139
|
-
| `awareness` | Mentioned but no project evidence
|
|
140
|
-
| `foundational` | Used in projects, basic application
|
|
141
|
-
| `working` | Primary tool/skill in multiple roles, independent usage
|
|
142
|
-
| `practitioner` | Led teams using this skill, mentored others, deep work
|
|
143
|
-
| `expert` | Published, shaped org practice, industry recognition
|
|
137
|
+
| Proficiency | CV Evidence |
|
|
138
|
+
| -------------- | ------------------------------------------------------- |
|
|
139
|
+
| `awareness` | Mentioned but no project evidence |
|
|
140
|
+
| `foundational` | Used in projects, basic application |
|
|
141
|
+
| `working` | Primary tool/skill in multiple roles, independent usage |
|
|
142
|
+
| `practitioner` | Led teams using this skill, mentored others, deep work |
|
|
143
|
+
| `expert` | Published, shaped org practice, industry recognition |
|
|
144
144
|
|
|
145
145
|
**Be conservative.** CVs inflate; default one level below what's claimed unless
|
|
146
146
|
there's concrete evidence (metrics, project details, scope indicators).
|
|
@@ -155,13 +155,13 @@ npx fit-pathway behaviour --list
|
|
|
155
155
|
|
|
156
156
|
Map CV evidence to behaviours:
|
|
157
157
|
|
|
158
|
-
| Behaviour
|
|
159
|
-
|
|
|
160
|
-
| Own the Outcome
|
|
161
|
-
| Think in Systems
|
|
162
|
-
| Communicate with Precision
|
|
163
|
-
| Be Polymath Oriented
|
|
164
|
-
| Don't Lose Your Curiosity
|
|
158
|
+
| Behaviour | CV Evidence |
|
|
159
|
+
| -------------------------- | -------------------------------------------------- |
|
|
160
|
+
| Own the Outcome | End-to-end ownership, P&L impact, delivery metrics |
|
|
161
|
+
| Think in Systems | Architecture decisions, system-wide reasoning |
|
|
162
|
+
| Communicate with Precision | Technical writing, documentation, talks |
|
|
163
|
+
| Be Polymath Oriented | Cross-domain work, diverse tech stack |
|
|
164
|
+
| Don't Lose Your Curiosity | Side projects, continuous learning, certifications |
|
|
165
165
|
|
|
166
166
|
## Step 5: Identify Gaps and Strengths
|
|
167
167
|
|
|
@@ -259,7 +259,7 @@ to create the candidate profile from email threads.
|
|
|
259
259
|
## Quality Checklist
|
|
260
260
|
|
|
261
261
|
- [ ] Assessment is grounded in `fit-pathway` framework data, not subjective
|
|
262
|
-
|
|
262
|
+
opinion
|
|
263
263
|
- [ ] Every skill rating cites specific CV evidence or marks "Not evidenced"
|
|
264
264
|
- [ ] Estimated level is conservative (one below CV claims unless proven)
|
|
265
265
|
- [ ] Track fit analysis references specific skill modifiers from the framework
|
|
@@ -38,7 +38,8 @@ Run this skill:
|
|
|
38
38
|
|
|
39
39
|
- **Name**: Full name of the data subject (required)
|
|
40
40
|
- **Aliases**: Alternative names, maiden names, nicknames (optional)
|
|
41
|
-
- **Email addresses**: Known email addresses (optional, improves search
|
|
41
|
+
- **Email addresses**: Known email addresses (optional, improves search
|
|
42
|
+
coverage)
|
|
42
43
|
- **Scope**: `all` (default) or `recruitment-only` (limits to candidate data)
|
|
43
44
|
|
|
44
45
|
## Outputs
|
|
@@ -55,6 +56,7 @@ Before proceeding, clearly state to the user:
|
|
|
55
56
|
> **Data erasure request for: {Name}**
|
|
56
57
|
>
|
|
57
58
|
> This will permanently delete all personal data related to {Name} from:
|
|
59
|
+
>
|
|
58
60
|
> - Knowledge base notes (People, Candidates, Organizations mentions)
|
|
59
61
|
> - Cached email threads and attachments
|
|
60
62
|
> - Agent state and triage files
|
|
@@ -134,16 +136,16 @@ Compile a complete inventory of every file and reference found.
|
|
|
134
136
|
|
|
135
137
|
For each discovered reference, classify the required action:
|
|
136
138
|
|
|
137
|
-
| Reference Type
|
|
138
|
-
|
|
|
139
|
-
| **Dedicated note** (sole subject)
|
|
140
|
-
| **Dedicated directory**
|
|
141
|
-
| **Mention in another note**
|
|
142
|
-
| **Email thread** (sole subject)
|
|
143
|
-
| **Email thread** (multiple people) | Redact: remove paragraphs about the person
|
|
144
|
-
| **Attachment** (their CV, etc.)
|
|
145
|
-
| **Triage/state file**
|
|
146
|
-
| **Insights file**
|
|
139
|
+
| Reference Type | Action | Example |
|
|
140
|
+
| ---------------------------------- | ------------------------------------------- | ----------------------------------------------- |
|
|
141
|
+
| **Dedicated note** (sole subject) | Delete entire file | `knowledge/People/{Name}.md` |
|
|
142
|
+
| **Dedicated directory** | Delete entire directory | `knowledge/Candidates/{Name}/` |
|
|
143
|
+
| **Mention in another note** | Redact: remove lines referencing the person | Backlink in `knowledge/Organizations/Agency.md` |
|
|
144
|
+
| **Email thread** (sole subject) | Delete file | `~/.cache/fit/basecamp/apple_mail/thread.md` |
|
|
145
|
+
| **Email thread** (multiple people) | Redact: remove paragraphs about the person | Thread discussing multiple candidates |
|
|
146
|
+
| **Attachment** (their CV, etc.) | Delete file | `attachments/{thread}/CV.pdf` |
|
|
147
|
+
| **Triage/state file** | Redact: remove lines mentioning them | `recruiter_triage.md` |
|
|
148
|
+
| **Insights file** | Redact: remove bullets mentioning them | `knowledge/Candidates/Insights.md` |
|
|
147
149
|
|
|
148
150
|
## Step 3: Execute Deletions
|
|
149
151
|
|
|
@@ -222,7 +224,7 @@ rg -v "{deleted_path}" ~/.cache/fit/basecamp/state/graph_processed \
|
|
|
222
224
|
|
|
223
225
|
Create the audit trail at `knowledge/Erasure/{Name}--{YYYY-MM-DD}.md`:
|
|
224
226
|
|
|
225
|
-
|
|
227
|
+
````markdown
|
|
226
228
|
# Data Erasure Report — {Full Name}
|
|
227
229
|
|
|
228
230
|
**Date:** {YYYY-MM-DD HH:MM}
|
|
@@ -278,10 +280,11 @@ After erasure, verify no traces remain:
|
|
|
278
280
|
|
|
279
281
|
```bash
|
|
280
282
|
rg "{Name}" knowledge/ ~/.cache/fit/basecamp/
|
|
281
|
-
|
|
283
|
+
````
|
|
282
284
|
|
|
283
285
|
Expected result: no matches (except this erasure report).
|
|
284
|
-
|
|
286
|
+
|
|
287
|
+
````
|
|
285
288
|
|
|
286
289
|
**IMPORTANT:** The erasure report itself must NOT contain personal data beyond
|
|
287
290
|
the name and the fact that data was deleted. Do not copy CV content, skill
|
|
@@ -294,7 +297,7 @@ Run a final search to confirm no references were missed:
|
|
|
294
297
|
|
|
295
298
|
```bash
|
|
296
299
|
rg "{Name}" knowledge/ ~/.cache/fit/basecamp/ drafts/
|
|
297
|
-
|
|
300
|
+
````
|
|
298
301
|
|
|
299
302
|
The only match should be the erasure report itself. If other matches remain,
|
|
300
303
|
process them and update the report.
|
|
@@ -310,8 +313,8 @@ When scope is `recruitment-only`, limit erasure to:
|
|
|
310
313
|
- Recruitment-related email threads (from known agency domains)
|
|
311
314
|
- `recruiter_triage.md` state file
|
|
312
315
|
|
|
313
|
-
Leave `knowledge/People/{Name}.md` and general knowledge graph references
|
|
314
|
-
|
|
316
|
+
Leave `knowledge/People/{Name}.md` and general knowledge graph references intact
|
|
317
|
+
— the person may be a colleague or contact outside of recruitment.
|
|
315
318
|
|
|
316
319
|
### all (default)
|
|
317
320
|
|
|
@@ -59,8 +59,8 @@ The script:
|
|
|
59
59
|
3. Discovers the thread grouping column (`conversation_id` or `thread_id`)
|
|
60
60
|
4. Loads last-seen ROWID (or defaults to 0 for first sync)
|
|
61
61
|
5. Finds threads with new messages since last sync (up to 500), using both
|
|
62
|
-
timestamp and ROWID to catch late-arriving emails (emails downloaded after
|
|
63
|
-
|
|
62
|
+
timestamp and ROWID to catch late-arriving emails (emails downloaded after a
|
|
63
|
+
delay may have `date_received` before the last sync timestamp, but their
|
|
64
64
|
ROWID will be higher than the last-seen ROWID)
|
|
65
65
|
6. For each thread: fetches messages, batch-fetches recipients and attachment
|
|
66
66
|
metadata, parses `.emlx` files for full email bodies (falling back to
|
|
@@ -128,29 +128,29 @@ recruitment-related.
|
|
|
128
128
|
|
|
129
129
|
For each candidate found in a recruitment email, extract:
|
|
130
130
|
|
|
131
|
-
| Field
|
|
132
|
-
|
|
|
133
|
-
| **Name**
|
|
134
|
-
| **Title**
|
|
135
|
-
| **Rate**
|
|
136
|
-
| **Availability**
|
|
137
|
-
| **English**
|
|
138
|
-
| **Location**
|
|
139
|
-
| **Source agency**
|
|
140
|
-
| **Recruiter**
|
|
141
|
-
| **CV path**
|
|
142
|
-
| **Skills**
|
|
143
|
-
| **Gender**
|
|
144
|
-
| **Summary**
|
|
145
|
-
| **Role**
|
|
146
|
-
| **Req**
|
|
147
|
-
| **Internal/External
|
|
148
|
-
| **Model**
|
|
149
|
-
| **Current title**
|
|
150
|
-
| **Email**
|
|
151
|
-
| **Phone**
|
|
152
|
-
| **LinkedIn**
|
|
153
|
-
| **Also known as**
|
|
131
|
+
| Field | Source | Required |
|
|
132
|
+
| --------------------- | ------------------------------------------------------------ | ------------------- |
|
|
133
|
+
| **Name** | Filename, email body, CV | Yes |
|
|
134
|
+
| **Title** | Email body, CV — the candidate's professional title/function | Yes |
|
|
135
|
+
| **Rate** | Email body (e.g. "$120/hr", "€80/h") | If available |
|
|
136
|
+
| **Availability** | Email body (e.g. "1 month notice", "immediately") | If available |
|
|
137
|
+
| **English** | Email body (e.g. "B2", "Upper-intermediate") | If available |
|
|
138
|
+
| **Location** | Email body, CV | If available |
|
|
139
|
+
| **Source agency** | Sender domain → Organization | Yes |
|
|
140
|
+
| **Recruiter** | Email sender or CC'd recruiter | Yes |
|
|
141
|
+
| **CV path** | Attachment directory | If available |
|
|
142
|
+
| **Skills** | Email body, CV | If available |
|
|
143
|
+
| **Gender** | Name, pronouns, recruiter context | If identifiable |
|
|
144
|
+
| **Summary** | Email body, CV | Yes — 2-3 sentences |
|
|
145
|
+
| **Role** | Internal requisition profile being hired against | If available |
|
|
146
|
+
| **Req** | Requisition ID from hiring system | If available |
|
|
147
|
+
| **Internal/External** | Whether candidate is internal or external | If available |
|
|
148
|
+
| **Model** | Engagement model (B2B, Direct Hire, etc.) | If available |
|
|
149
|
+
| **Current title** | CV or email body | If available |
|
|
150
|
+
| **Email** | Email body, CV, signature | If available |
|
|
151
|
+
| **Phone** | Email body, CV, signature | If available |
|
|
152
|
+
| **LinkedIn** | Email body, CV | If available |
|
|
153
|
+
| **Also known as** | Alternate name spellings or transliterations | If available |
|
|
154
154
|
|
|
155
155
|
### Determining Gender
|
|
156
156
|
|
|
@@ -428,4 +428,5 @@ produces a full framework-aligned assessment.
|
|
|
428
428
|
- [ ] No duplicate candidate notes created
|
|
429
429
|
- [ ] Key strategic insights added to `Insights.md` where warranted
|
|
430
430
|
- [ ] Skills tagged using framework skill IDs where possible
|
|
431
|
-
- [ ] Gender field populated only from explicit pronouns/titles (never
|
|
431
|
+
- [ ] Gender field populated only from explicit pronouns/titles (never
|
|
432
|
+
name-inferred)
|
|
@@ -63,12 +63,12 @@ skill's directory.
|
|
|
63
63
|
|
|
64
64
|
For every changed skill file, determine the type of change:
|
|
65
65
|
|
|
66
|
-
| Type
|
|
67
|
-
|
|
|
68
|
-
| `added`
|
|
69
|
-
| `modified`
|
|
70
|
-
| `removed`
|
|
71
|
-
| `renamed`
|
|
66
|
+
| Type | Description |
|
|
67
|
+
| ---------- | ---------------------------------------------------- |
|
|
68
|
+
| `added` | New skill created that doesn't exist upstream |
|
|
69
|
+
| `modified` | Existing skill updated (workflow, checklists, tools) |
|
|
70
|
+
| `removed` | Skill deleted from the installation |
|
|
71
|
+
| `renamed` | Skill directory or file renamed |
|
|
72
72
|
|
|
73
73
|
For **modified** skills, read the current file and the previous version to
|
|
74
74
|
identify what specifically changed:
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workday-requisition
|
|
3
|
+
description: >
|
|
4
|
+
Import candidates from a Workday requisition export (.xlsx) into
|
|
5
|
+
knowledge/Candidates/. Parses requisition metadata and candidate data,
|
|
6
|
+
creates candidate briefs and CV.md files from resume text, and integrates
|
|
7
|
+
with the existing track-candidates pipeline. Use when the user provides a
|
|
8
|
+
Workday export file or asks to import candidates from an XLSX requisition
|
|
9
|
+
export.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Workday Requisition Import
|
|
13
|
+
|
|
14
|
+
Import candidates from a Workday requisition export (`.xlsx`) into
|
|
15
|
+
`knowledge/Candidates/`. Extracts requisition metadata and candidate profiles,
|
|
16
|
+
creates standardized candidate briefs and `CV.md` files from the embedded resume
|
|
17
|
+
text, and integrates with the existing `track-candidates` pipeline format.
|
|
18
|
+
|
|
19
|
+
## Trigger
|
|
20
|
+
|
|
21
|
+
Run this skill:
|
|
22
|
+
|
|
23
|
+
- When the user provides a Workday requisition export file (`.xlsx`)
|
|
24
|
+
- When the user asks to import candidates from Workday or an XLSX export
|
|
25
|
+
- When the user mentions a requisition ID and asks to process the export
|
|
26
|
+
|
|
27
|
+
## Prerequisites
|
|
28
|
+
|
|
29
|
+
- A Workday requisition export file (`.xlsx`) accessible on the filesystem
|
|
30
|
+
(typically in `~/Downloads/`)
|
|
31
|
+
- The `xlsx` npm package installed in the KB root:
|
|
32
|
+
```bash
|
|
33
|
+
npm install xlsx
|
|
34
|
+
```
|
|
35
|
+
- User identity configured in `USER.md`
|
|
36
|
+
|
|
37
|
+
## Inputs
|
|
38
|
+
|
|
39
|
+
- Path to the `.xlsx` file (e.g.
|
|
40
|
+
`~/Downloads/4951493_Principal_Software_Engineer_–_Forward_Deployed_(Open).xlsx`)
|
|
41
|
+
|
|
42
|
+
## Outputs
|
|
43
|
+
|
|
44
|
+
- `knowledge/Candidates/{Full Name}/brief.md` — candidate profile note
|
|
45
|
+
- `knowledge/Candidates/{Full Name}/CV.md` — resume text rendered as markdown
|
|
46
|
+
- Updated existing candidate briefs if candidate already exists
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Workday Export Format
|
|
51
|
+
|
|
52
|
+
The Workday requisition export contains multiple sheets. This skill uses:
|
|
53
|
+
|
|
54
|
+
### Sheet 1 — Requisition Metadata
|
|
55
|
+
|
|
56
|
+
Key-value pairs, one per row:
|
|
57
|
+
|
|
58
|
+
| Row | Field | Example |
|
|
59
|
+
| --- | --------------------- | -------------------------------------- |
|
|
60
|
+
| 1 | Title header | `4951493 Principal Software Engineer…` |
|
|
61
|
+
| 2 | Recruiting Start Date | `02/10/2026` |
|
|
62
|
+
| 3 | Target Hire Date | `02/10/2026` |
|
|
63
|
+
| 4 | Primary Location | `USA - NY - Headquarters` |
|
|
64
|
+
| 5 | Hiring Manager Title | `Hiring Manager` |
|
|
65
|
+
| 6 | Hiring Manager | Name |
|
|
66
|
+
| 7 | Recruiter Title | `Recruiter` |
|
|
67
|
+
| 8 | Recruiter | Name |
|
|
68
|
+
|
|
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 |
|
|
101
|
+
|
|
102
|
+
#### Name Annotations
|
|
103
|
+
|
|
104
|
+
Names may include parenthetical annotations:
|
|
105
|
+
|
|
106
|
+
- `(Prior Worker)` → Internal/External = `External (Prior Worker)`
|
|
107
|
+
- `(Internal)` → Internal/External = `Internal`
|
|
108
|
+
- No annotation + source contains "Internal" → `Internal`
|
|
109
|
+
- Otherwise → `External`
|
|
110
|
+
|
|
111
|
+
## Before Starting
|
|
112
|
+
|
|
113
|
+
1. Read `USER.md` to get the user's name, email, and domain.
|
|
114
|
+
2. Confirm the XLSX file path with the user (or use the provided path).
|
|
115
|
+
3. Ensure the `xlsx` package is installed:
|
|
116
|
+
```bash
|
|
117
|
+
npm list xlsx 2>/dev/null || npm install xlsx
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Step 1: Parse the Export
|
|
121
|
+
|
|
122
|
+
Run the parse script to extract structured data:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
node .claude/skills/workday-requisition/scripts/parse-workday.mjs "<path-to-xlsx>" --summary
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
This prints a summary of the requisition and all candidates. Review the output
|
|
129
|
+
to confirm the file parsed correctly and note the total candidate count.
|
|
130
|
+
|
|
131
|
+
For the full JSON output (used in subsequent steps):
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
node .claude/skills/workday-requisition/scripts/parse-workday.mjs "<path-to-xlsx>"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The full output is a JSON object with:
|
|
138
|
+
|
|
139
|
+
- `requisition` — metadata (id, title, location, hiringManager, recruiter)
|
|
140
|
+
- `candidates` — array of candidate objects with all extracted fields
|
|
141
|
+
|
|
142
|
+
## Step 2: Build Candidate Index
|
|
143
|
+
|
|
144
|
+
Scan existing candidate notes to avoid duplicates:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
ls -d knowledge/Candidates/*/ 2>/dev/null
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
For each existing candidate, check if they match any imported candidate by name.
|
|
151
|
+
Use fuzzy matching — the Workday name may differ slightly from an existing note
|
|
152
|
+
(e.g. middle names, accents, spelling variations).
|
|
153
|
+
|
|
154
|
+
## Step 3: Determine Pipeline Status
|
|
155
|
+
|
|
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`.
|
|
175
|
+
|
|
176
|
+
## Step 4: Create CV.md from Resume Text
|
|
177
|
+
|
|
178
|
+
For each candidate with resume text, create
|
|
179
|
+
`knowledge/Candidates/{Clean Name}/CV.md`:
|
|
180
|
+
|
|
181
|
+
```markdown
|
|
182
|
+
# {Clean Name} — Resume
|
|
183
|
+
|
|
184
|
+
> Extracted from Workday requisition export {Req ID} on {today's date}.
|
|
185
|
+
> Original file: {Resume filename from column G}
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
{Resume text from column AC, preserving original formatting}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Formatting rules for resume text:**
|
|
193
|
+
|
|
194
|
+
- Preserve paragraph breaks (double newlines)
|
|
195
|
+
- Convert ALL-CAPS section headers to `## Heading` format
|
|
196
|
+
- Preserve bullet points and lists
|
|
197
|
+
- Clean up excessive whitespace but keep structure
|
|
198
|
+
- Do not rewrite or summarize — reproduce faithfully
|
|
199
|
+
|
|
200
|
+
If a candidate has no resume text, skip the CV.md file.
|
|
201
|
+
|
|
202
|
+
## Step 5: Write Candidate Brief
|
|
203
|
+
|
|
204
|
+
### For NEW candidates
|
|
205
|
+
|
|
206
|
+
Create the candidate directory and brief:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
mkdir -p "knowledge/Candidates/{Clean Name}"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Then create `knowledge/Candidates/{Clean Name}/brief.md` using the
|
|
213
|
+
`track-candidates` format:
|
|
214
|
+
|
|
215
|
+
```markdown
|
|
216
|
+
# {Clean Name}
|
|
217
|
+
|
|
218
|
+
## Info
|
|
219
|
+
**Title:** {Current Job Title or "—"}
|
|
220
|
+
**Rate:** {Salary Expectations or "—"}
|
|
221
|
+
**Availability:** {Availability Date or "—"}
|
|
222
|
+
**English:** {Language field or "—"}
|
|
223
|
+
**Location:** {Candidate Location or "—"}
|
|
224
|
+
**Gender:** —
|
|
225
|
+
**Source:** {Source} {via Referred by, if present}
|
|
226
|
+
**Status:** {pipeline status from Step 3}
|
|
227
|
+
**First seen:** {Date Applied, YYYY-MM-DD}
|
|
228
|
+
**Last activity:** {Date Applied, YYYY-MM-DD}
|
|
229
|
+
**Req:** {Req ID} — {Req Title}
|
|
230
|
+
**Internal/External:** {Internal / External / External (Prior Worker)}
|
|
231
|
+
**Current title:** {Current Job Title at Current Company}
|
|
232
|
+
**Email:** {Email or "—"}
|
|
233
|
+
**Phone:** {Phone or "—"}
|
|
234
|
+
|
|
235
|
+
## Summary
|
|
236
|
+
{2-3 sentences based on resume text: role focus, years of experience, key
|
|
237
|
+
strengths. If no resume text, use Current Job Title + Total Years Experience.}
|
|
238
|
+
|
|
239
|
+
## CV
|
|
240
|
+
- [CV.md](./CV.md)
|
|
241
|
+
|
|
242
|
+
## Connected to
|
|
243
|
+
- {Referred by person, if present}
|
|
244
|
+
|
|
245
|
+
## Pipeline
|
|
246
|
+
- **{Date Applied}**: Applied via {Source}
|
|
247
|
+
|
|
248
|
+
## Skills
|
|
249
|
+
{Extract key technical skills from resume text — use framework IDs where
|
|
250
|
+
possible via `npx fit-pathway skill --list`}
|
|
251
|
+
|
|
252
|
+
## Education
|
|
253
|
+
{Degrees and Fields of Study from the export columns}
|
|
254
|
+
|
|
255
|
+
## Work History
|
|
256
|
+
{All Job Titles and Companies from the export columns, formatted as a list}
|
|
257
|
+
|
|
258
|
+
## Notes
|
|
259
|
+
{Include any noteworthy fields here:}
|
|
260
|
+
{- Visa requirement (if present)}
|
|
261
|
+
{- Eligible to work (if present)}
|
|
262
|
+
{- Relocation willingness (if present)}
|
|
263
|
+
{- Non-compete status (if present)}
|
|
264
|
+
{- Total years of experience}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Extra fields** (after Last activity, in order): Req, Internal/External,
|
|
268
|
+
Current title, Email, Phone, LinkedIn — include only when available. Follow the
|
|
269
|
+
order defined in the `track-candidates` skill.
|
|
270
|
+
|
|
271
|
+
### For EXISTING candidates
|
|
272
|
+
|
|
273
|
+
Read `knowledge/Candidates/{Name}/brief.md`, then apply targeted edits:
|
|
274
|
+
|
|
275
|
+
- Add or update **Req** field with this requisition's ID
|
|
276
|
+
- Update **Status** if the Workday stage is more advanced
|
|
277
|
+
- Update **Last activity** date if this application is more recent
|
|
278
|
+
- Add a new **Pipeline** entry:
|
|
279
|
+
`**{Date Applied}**: Applied to {Req ID} — {Req Title} via {Source}`
|
|
280
|
+
- Update any missing fields (Email, Phone, Location) from the export
|
|
281
|
+
- Do NOT overwrite existing richer data with sparser Workday data
|
|
282
|
+
|
|
283
|
+
**Use precise edits — don't rewrite the entire file.**
|
|
284
|
+
|
|
285
|
+
## Step 6: Process in Batches
|
|
286
|
+
|
|
287
|
+
Workday exports can contain many candidates. Process in batches of **10
|
|
288
|
+
candidates per run** to stay within context limits.
|
|
289
|
+
|
|
290
|
+
For each batch:
|
|
291
|
+
|
|
292
|
+
1. Parse the JSON output (or re-run the parse script)
|
|
293
|
+
2. Process 10 candidates: create/update brief + CV.md
|
|
294
|
+
3. Report progress: `Processed {N}/{Total} candidates`
|
|
295
|
+
|
|
296
|
+
If the export has more than 10 candidates, tell the user how many remain and
|
|
297
|
+
offer to continue.
|
|
298
|
+
|
|
299
|
+
## Step 7: Capture Key Insights
|
|
300
|
+
|
|
301
|
+
After processing all candidates, review the batch for strategic observations and
|
|
302
|
+
add them to `knowledge/Candidates/Insights.md`:
|
|
303
|
+
|
|
304
|
+
- Candidates who stand out as strong matches
|
|
305
|
+
- Candidates better suited for a different role
|
|
306
|
+
- Notable patterns (source quality, experience distribution, skill gaps)
|
|
307
|
+
|
|
308
|
+
Follow the `track-candidates` Insights format: one bullet per insight under
|
|
309
|
+
`## Placement Notes` with `[[Candidates/Name/brief|Name]]` links.
|
|
310
|
+
|
|
311
|
+
## Step 8: Tag Skills with Framework IDs
|
|
312
|
+
|
|
313
|
+
When resume text mentions technical skills, map them to the engineering
|
|
314
|
+
framework:
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
npx fit-pathway skill --list
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Use framework skill IDs in the **Skills** section of each brief. If a candidate
|
|
321
|
+
has a CV.md, flag them for the `analyze-cv` skill for a full framework-aligned
|
|
322
|
+
assessment.
|
|
323
|
+
|
|
324
|
+
## Quality Checklist
|
|
325
|
+
|
|
326
|
+
- [ ] XLSX parsed correctly — verify candidate count matches summary
|
|
327
|
+
- [ ] Requisition metadata extracted (ID, title, hiring manager, recruiter)
|
|
328
|
+
- [ ] Each candidate has a directory under `knowledge/Candidates/{Clean Name}/`
|
|
329
|
+
- [ ] CV.md created for every candidate with resume text
|
|
330
|
+
- [ ] CV.md faithfully reproduces resume text (no rewriting or summarizing)
|
|
331
|
+
- [ ] Brief follows `track-candidates` format exactly
|
|
332
|
+
- [ ] Info fields in standard order (Title → Rate → Availability → English →
|
|
333
|
+
Location → Gender → Source → Status → First seen → Last activity → extras)
|
|
334
|
+
- [ ] Pipeline status correctly mapped from Workday stage/step
|
|
335
|
+
- [ ] Internal/External correctly derived from name annotations and source
|
|
336
|
+
- [ ] Name annotations stripped from directory names and headings
|
|
337
|
+
- [ ] Existing candidates updated (not duplicated) with precise edits
|
|
338
|
+
- [ ] Skills tagged using framework skill IDs where possible
|
|
339
|
+
- [ ] Gender field set to `—` (Workday exports don't include gender signals)
|
|
340
|
+
- [ ] Insights.md updated with strategic observations
|
|
341
|
+
- [ ] No duplicate candidate directories created
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Parse a Workday requisition export (.xlsx) and output structured JSON.
|
|
4
|
+
*
|
|
5
|
+
* Reads Sheet1 for requisition metadata and the "Candidates" sheet for
|
|
6
|
+
* candidate data. Outputs a JSON object to stdout with:
|
|
7
|
+
* - requisition: { id, title, startDate, targetHireDate, location,
|
|
8
|
+
* hiringManager, recruiter }
|
|
9
|
+
* - candidates: [ { name, cleanName, stage, step, resumeFile, dateApplied,
|
|
10
|
+
* currentTitle, currentCompany, source, referredBy,
|
|
11
|
+
* availabilityDate, visaRequirement, eligibleToWork,
|
|
12
|
+
* relocation, salaryExpectations, nonCompete, location,
|
|
13
|
+
* phone, email, totalYearsExperience, allJobTitles,
|
|
14
|
+
* companies, degrees, fieldsOfStudy, language,
|
|
15
|
+
* resumeText, internalExternal } ]
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* node scripts/parse-workday.mjs <path-to-xlsx>
|
|
19
|
+
* node scripts/parse-workday.mjs <path-to-xlsx> --summary
|
|
20
|
+
* node scripts/parse-workday.mjs -h|--help
|
|
21
|
+
*
|
|
22
|
+
* Requires: npm install xlsx
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { readFileSync } from "node:fs";
|
|
26
|
+
|
|
27
|
+
if (
|
|
28
|
+
process.argv.includes("-h") ||
|
|
29
|
+
process.argv.includes("--help") ||
|
|
30
|
+
process.argv.length < 3
|
|
31
|
+
) {
|
|
32
|
+
console.log(`parse-workday — extract candidates from a Workday requisition export
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
node scripts/parse-workday.mjs <path-to-xlsx> Full JSON output
|
|
36
|
+
node scripts/parse-workday.mjs <path-to-xlsx> --summary Name + status only
|
|
37
|
+
node scripts/parse-workday.mjs -h|--help Show this help
|
|
38
|
+
|
|
39
|
+
Output (JSON):
|
|
40
|
+
{ requisition: { id, title, ... }, candidates: [ { name, ... }, ... ] }
|
|
41
|
+
|
|
42
|
+
Requires: npm install xlsx`);
|
|
43
|
+
process.exit(process.argv.length < 3 ? 1 : 0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let XLSX;
|
|
47
|
+
try {
|
|
48
|
+
XLSX = await import("xlsx");
|
|
49
|
+
} catch {
|
|
50
|
+
console.error(
|
|
51
|
+
"Error: xlsx package not found. Install it first:\n npm install xlsx",
|
|
52
|
+
);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const filePath = process.argv[2];
|
|
57
|
+
const summaryMode = process.argv.includes("--summary");
|
|
58
|
+
|
|
59
|
+
const data = readFileSync(filePath);
|
|
60
|
+
const wb = XLSX.read(data, { type: "buffer", cellDates: true });
|
|
61
|
+
|
|
62
|
+
// --- Sheet 1: Requisition metadata ---
|
|
63
|
+
|
|
64
|
+
const ws1 = wb.Sheets[wb.SheetNames[0]];
|
|
65
|
+
const sheet1Rows = XLSX.utils.sheet_to_json(ws1, { header: 1, defval: "" });
|
|
66
|
+
|
|
67
|
+
/** Extract the requisition ID and title from the header row. */
|
|
68
|
+
function parseReqHeader(headerText) {
|
|
69
|
+
// Format: "4951493 Principal Software Engineer – Forward Deployed: 4951493 ..."
|
|
70
|
+
const text = String(headerText).split(":")[0].trim();
|
|
71
|
+
const match = text.match(/^(\d+)\s+(.+)$/);
|
|
72
|
+
if (match) return { id: match[1], title: match[2] };
|
|
73
|
+
return { id: "", title: text };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Build a key-value map from Sheet1 rows (column A = label, column B = value). */
|
|
77
|
+
function buildReqMetadata(rows) {
|
|
78
|
+
const meta = {};
|
|
79
|
+
for (const row of rows) {
|
|
80
|
+
const key = String(row[0] || "").trim();
|
|
81
|
+
const val = String(row[1] || "").trim();
|
|
82
|
+
if (key && val) meta[key] = val;
|
|
83
|
+
}
|
|
84
|
+
return meta;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const reqHeader = parseReqHeader(sheet1Rows[0]?.[0] || "");
|
|
88
|
+
const reqMeta = buildReqMetadata(sheet1Rows.slice(1));
|
|
89
|
+
|
|
90
|
+
/** Clean a metadata date string (e.g. "02/10/2026 - 22 days ago" → "2026-02-10"). */
|
|
91
|
+
function cleanMetaDate(val) {
|
|
92
|
+
if (!val) return "";
|
|
93
|
+
const clean = val.replace(/\s*-\s*\d+\s+days?\s+ago$/i, "").trim();
|
|
94
|
+
// Convert MM/DD/YYYY → YYYY-MM-DD
|
|
95
|
+
const match = clean.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
|
|
96
|
+
if (match) return `${match[3]}-${match[1]}-${match[2]}`;
|
|
97
|
+
return clean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const requisition = {
|
|
101
|
+
id: reqHeader.id,
|
|
102
|
+
title: reqHeader.title,
|
|
103
|
+
startDate: cleanMetaDate(reqMeta["Recruiting Start Date"]),
|
|
104
|
+
targetHireDate: cleanMetaDate(reqMeta["Target Hire Date"]),
|
|
105
|
+
location: reqMeta["Primary Location"] || "",
|
|
106
|
+
hiringManager: reqMeta["Hiring Manager"] || "",
|
|
107
|
+
recruiter: reqMeta["Recruiter"] || "",
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// --- Sheet 3: Candidates ---
|
|
111
|
+
|
|
112
|
+
// Find the "Candidates" sheet (usually index 2, but search by name to be safe)
|
|
113
|
+
const candSheetName =
|
|
114
|
+
wb.SheetNames.find((n) => n.toLowerCase() === "candidates") ||
|
|
115
|
+
wb.SheetNames[2];
|
|
116
|
+
const ws3 = wb.Sheets[candSheetName];
|
|
117
|
+
const candRows = XLSX.utils.sheet_to_json(ws3, { header: 1, defval: "" });
|
|
118
|
+
|
|
119
|
+
// Row 3 (index 2) has column headers. Data starts at row 4 (index 3).
|
|
120
|
+
// Stage summary rows start when column A has a non-empty value that looks like
|
|
121
|
+
// a label or number — detect by checking if column C (Stage) is empty and
|
|
122
|
+
// column A has a value.
|
|
123
|
+
const DATA_START = 3;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Clean a candidate name by stripping annotations like (Prior Worker),
|
|
127
|
+
* (Internal), etc. Returns { cleanName, internalExternal }.
|
|
128
|
+
*/
|
|
129
|
+
function parseName(raw) {
|
|
130
|
+
const name = String(raw).trim();
|
|
131
|
+
if (!name) return { cleanName: "", internalExternal: "" };
|
|
132
|
+
|
|
133
|
+
const match = name.match(/^(.+?)\s*\(([^)]+)\)\s*$/);
|
|
134
|
+
if (match) {
|
|
135
|
+
const annotation = match[2].trim();
|
|
136
|
+
let ie = "";
|
|
137
|
+
if (/prior\s*worker/i.test(annotation)) ie = "External (Prior Worker)";
|
|
138
|
+
else if (/internal/i.test(annotation)) ie = "Internal";
|
|
139
|
+
else ie = annotation;
|
|
140
|
+
return { cleanName: match[1].trim(), internalExternal: ie };
|
|
141
|
+
}
|
|
142
|
+
return { cleanName: name, internalExternal: "" };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Detect source-based internal/external when name annotation is absent. */
|
|
146
|
+
function inferInternalExternal(source, nameAnnotation) {
|
|
147
|
+
if (nameAnnotation) return nameAnnotation;
|
|
148
|
+
if (/internal/i.test(source)) return "Internal";
|
|
149
|
+
return "External";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Format a date value (may be Date object or string). */
|
|
153
|
+
function fmtDate(val) {
|
|
154
|
+
if (!val) return "";
|
|
155
|
+
if (val instanceof Date) {
|
|
156
|
+
// Use local date parts to avoid UTC offset shifting the day
|
|
157
|
+
const y = val.getFullYear();
|
|
158
|
+
const m = String(val.getMonth() + 1).padStart(2, "0");
|
|
159
|
+
const d = String(val.getDate()).padStart(2, "0");
|
|
160
|
+
return `${y}-${m}-${d}`;
|
|
161
|
+
}
|
|
162
|
+
const s = String(val).trim();
|
|
163
|
+
// Strip trailing " 00:00:00" and relative text like " - 22 days ago"
|
|
164
|
+
return s
|
|
165
|
+
.replace(/\s+\d{2}:\d{2}:\d{2}$/, "")
|
|
166
|
+
.replace(/\s*-\s*\d+\s+days?\s+ago$/i, "");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Normalise multiline cell values into clean lists. */
|
|
170
|
+
function multiline(val) {
|
|
171
|
+
if (!val) return "";
|
|
172
|
+
return String(val)
|
|
173
|
+
.split("\n")
|
|
174
|
+
.map((l) => l.trim())
|
|
175
|
+
.filter(Boolean)
|
|
176
|
+
.join(", ");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const candidates = [];
|
|
180
|
+
|
|
181
|
+
for (let i = DATA_START; i < candRows.length; i++) {
|
|
182
|
+
const row = candRows[i];
|
|
183
|
+
const rawName = String(row[1] || "").trim(); // Column B (index 1)
|
|
184
|
+
const stage = String(row[2] || "").trim(); // Column C (index 2)
|
|
185
|
+
|
|
186
|
+
// Stop at stage-summary rows: column A has a value, column C (stage) is empty
|
|
187
|
+
if (!rawName || (!stage && String(row[0] || "").trim())) break;
|
|
188
|
+
if (!rawName) continue;
|
|
189
|
+
|
|
190
|
+
const { cleanName, internalExternal: nameIE } = parseName(rawName);
|
|
191
|
+
const source = String(row[10] || "").trim();
|
|
192
|
+
|
|
193
|
+
candidates.push({
|
|
194
|
+
name: rawName,
|
|
195
|
+
cleanName,
|
|
196
|
+
stage,
|
|
197
|
+
step: String(row[3] || "").trim(),
|
|
198
|
+
awaitingMe: String(row[4] || "").trim(),
|
|
199
|
+
awaitingAction: String(row[5] || "").trim(),
|
|
200
|
+
resumeFile: String(row[6] || "").trim(),
|
|
201
|
+
dateApplied: fmtDate(row[7]),
|
|
202
|
+
currentTitle: String(row[8] || "").trim(),
|
|
203
|
+
currentCompany: String(row[9] || "").trim(),
|
|
204
|
+
source,
|
|
205
|
+
referredBy: String(row[11] || "").trim(),
|
|
206
|
+
availabilityDate: fmtDate(row[13]),
|
|
207
|
+
visaRequirement: String(row[14] || "").trim(),
|
|
208
|
+
eligibleToWork: String(row[15] || "").trim(),
|
|
209
|
+
relocation: String(row[16] || "").trim(),
|
|
210
|
+
salaryExpectations: String(row[17] || "").trim(),
|
|
211
|
+
nonCompete: String(row[18] || "").trim(),
|
|
212
|
+
location: String(row[19] || "").trim(),
|
|
213
|
+
phone: String(row[20] || "").trim(),
|
|
214
|
+
email: String(row[21] || "").trim(),
|
|
215
|
+
totalYearsExperience: String(row[22] || "").trim(),
|
|
216
|
+
allJobTitles: multiline(row[23]),
|
|
217
|
+
companies: multiline(row[24]),
|
|
218
|
+
degrees: multiline(row[25]),
|
|
219
|
+
fieldsOfStudy: multiline(row[26]),
|
|
220
|
+
language: multiline(row[27]),
|
|
221
|
+
resumeText: String(row[28] || "").trim(),
|
|
222
|
+
internalExternal: inferInternalExternal(source, nameIE),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// --- Output ---
|
|
227
|
+
|
|
228
|
+
if (summaryMode) {
|
|
229
|
+
console.log(`Requisition: ${requisition.id} — ${requisition.title}`);
|
|
230
|
+
console.log(`Location: ${requisition.location}`);
|
|
231
|
+
console.log(`Hiring Manager: ${requisition.hiringManager}`);
|
|
232
|
+
console.log(`Recruiter: ${requisition.recruiter}`);
|
|
233
|
+
console.log(`Candidates: ${candidates.length}`);
|
|
234
|
+
console.log();
|
|
235
|
+
for (const c of candidates) {
|
|
236
|
+
const resume = c.resumeText ? "has resume" : "no resume";
|
|
237
|
+
console.log(
|
|
238
|
+
` ${c.cleanName} — ${c.step || c.stage} (${c.internalExternal}, ${resume})`,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
console.log(JSON.stringify({ requisition, candidates }, null, 2));
|
|
243
|
+
}
|
package/template/CLAUDE.md
CHANGED
|
@@ -81,13 +81,13 @@ This knowledge base is maintained by a team of agents, each defined in
|
|
|
81
81
|
`.claude/agents/`. They are woken on a schedule by the Basecamp scheduler. Each
|
|
82
82
|
wake, they observe KB state, decide the most valuable action, and execute.
|
|
83
83
|
|
|
84
|
-
| Agent | Domain | Schedule | Skills
|
|
85
|
-
| ------------------ | ------------------------------ | --------------- |
|
|
86
|
-
| **postman** | Email triage and drafts | Every 5 min | sync-apple-mail, draft-emails
|
|
87
|
-
| **concierge** | Meeting prep and transcripts | Every 10 min | sync-apple-calendar, meeting-prep, process-hyprnote
|
|
88
|
-
| **librarian** | Knowledge graph maintenance | Every 15 min | extract-entities, organize-files, manage-tasks
|
|
89
|
-
| **recruiter** | Engineering recruitment | Every 30 min | track-candidates, analyze-cv, right-to-be-forgotten, fit-pathway, fit-map |
|
|
90
|
-
| **chief-of-staff** | Daily briefings and priorities | 7am, Mon 7:30am | weekly-update _(Mon)_, _(reads all state for daily briefings)_
|
|
84
|
+
| Agent | Domain | Schedule | Skills |
|
|
85
|
+
| ------------------ | ------------------------------ | --------------- | ---------------------------------------------------------------------------------------------- |
|
|
86
|
+
| **postman** | Email triage and drafts | Every 5 min | sync-apple-mail, draft-emails |
|
|
87
|
+
| **concierge** | Meeting prep and transcripts | Every 10 min | sync-apple-calendar, meeting-prep, process-hyprnote |
|
|
88
|
+
| **librarian** | Knowledge graph maintenance | Every 15 min | extract-entities, organize-files, manage-tasks |
|
|
89
|
+
| **recruiter** | Engineering recruitment | Every 30 min | track-candidates, analyze-cv, workday-requisition, right-to-be-forgotten, fit-pathway, fit-map |
|
|
90
|
+
| **chief-of-staff** | Daily briefings and priorities | 7am, Mon 7:30am | weekly-update _(Mon)_, _(reads all state for daily briefings)_ |
|
|
91
91
|
|
|
92
92
|
Each agent writes a triage file to `~/.cache/fit/basecamp/state/` every wake
|
|
93
93
|
cycle. The naming convention is `{agent}_triage.md`:
|
|
@@ -200,16 +200,17 @@ Available skills (grouped by function):
|
|
|
200
200
|
|
|
201
201
|
**Knowledge graph** — build and maintain structured notes:
|
|
202
202
|
|
|
203
|
-
| Skill
|
|
204
|
-
|
|
|
205
|
-
| `extract-entities`
|
|
206
|
-
| `manage-tasks`
|
|
207
|
-
| `track-candidates`
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
203
|
+
| Skill | Purpose |
|
|
204
|
+
| ----------------------- | ---------------------------------------- |
|
|
205
|
+
| `extract-entities` | Process synced data into knowledge notes |
|
|
206
|
+
| `manage-tasks` | Per-person task boards with lifecycle |
|
|
207
|
+
| `track-candidates` | Recruitment pipeline from email threads |
|
|
208
|
+
| `workday-requisition` | Import candidates from Workday XLSX |
|
|
209
|
+
| `analyze-cv` | CV assessment against career framework |
|
|
210
|
+
| `right-to-be-forgotten` | GDPR data erasure with audit trail |
|
|
211
|
+
| `weekly-update` | Weekly priorities from tasks + calendar |
|
|
212
|
+
| `process-hyprnote` | Extract entities from Hyprnote sessions |
|
|
213
|
+
| `organize-files` | Tidy Desktop/Downloads, chain to extract |
|
|
213
214
|
|
|
214
215
|
**Communication** — draft, send, and present:
|
|
215
216
|
|