@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 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.1",
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 | 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 |
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 | 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 |
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
- modifiers for each discipline.
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
- you wouldn't be comfortable sharing it with them, don't write it.
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 human
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
- as definitive fact. CVs are incomplete signals.
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
- it with the agency.
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
- If the target role is known from the candidate brief, use it:
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 | 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) |
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 | 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 |
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 | 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 |
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
- opinion
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 coverage)
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 | Action | Example |
138
- | --- | --- | --- |
139
- | **Dedicated note** (sole subject) | Delete entire file | `knowledge/People/{Name}.md` |
140
- | **Dedicated directory** | Delete entire directory | `knowledge/Candidates/{Name}/` |
141
- | **Mention in another note** | Redact: remove lines referencing the person | Backlink in `knowledge/Organizations/Agency.md` |
142
- | **Email thread** (sole subject) | Delete file | `~/.cache/fit/basecamp/apple_mail/thread.md` |
143
- | **Email thread** (multiple people) | Redact: remove paragraphs about the person | Thread discussing multiple candidates |
144
- | **Attachment** (their CV, etc.) | Delete file | `attachments/{thread}/CV.pdf` |
145
- | **Triage/state file** | Redact: remove lines mentioning them | `recruiter_triage.md` |
146
- | **Insights file** | Redact: remove bullets mentioning them | `knowledge/Candidates/Insights.md` |
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
- ```markdown
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
- intact — the person may be a colleague or contact outside of recruitment.
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
- a delay may have `date_received` before the last sync timestamp, but their
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 | 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 |
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 name-inferred)
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 | 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 |
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
+ }
@@ -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 | 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
- | `analyze-cv` | CV assessment against career framework |
209
- | `right-to-be-forgotten` | GDPR data erasure with audit trail |
210
- | `weekly-update` | Weekly priorities from tasks + calendar |
211
- | `process-hyprnote` | Extract entities from Hyprnote sessions |
212
- | `organize-files` | Tidy Desktop/Downloads, chain to extract |
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