@forwardimpact/outpost 3.12.0 → 3.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/fit-outpost.js +3 -2
- package/package.json +6 -5
- package/src/agent-path.js +10 -6
- package/src/agent-runner.js +28 -24
- package/src/index.js +2 -3
- package/src/kb-manager.js +75 -20
- package/src/kb-validator.js +762 -0
- package/src/outpost.js +108 -39
- package/src/posture.js +3 -3
- package/src/privilege.js +11 -11
- package/src/scheduler.js +14 -12
- package/src/socket-server.js +19 -18
- package/src/spawn-env.js +7 -6
- package/src/state-manager.js +2 -2
- package/templates/.claude/agents/chief-of-staff.md +32 -24
- package/templates/.claude/agents/concierge.md +26 -17
- package/templates/.claude/agents/head-hunter.md +34 -26
- package/templates/.claude/agents/librarian.md +26 -18
- package/templates/.claude/agents/postman.md +26 -18
- package/templates/.claude/agents/recruiter.md +38 -27
- package/templates/.claude/skills/anarlog-follow/SKILL.md +91 -66
- package/templates/.claude/skills/anarlog-follow/references/coaching.md +22 -21
- package/templates/.claude/skills/anarlog-follow/references/sessions.md +52 -0
- package/templates/.claude/skills/anarlog-process/SKILL.md +108 -62
- package/templates/.claude/skills/anarlog-process/references/extraction.md +41 -29
- package/templates/.claude/skills/anarlog-process/references/sessions.md +92 -58
- package/templates/.claude/skills/anarlog-process/scripts/scan.mjs +402 -160
- package/templates/.claude/skills/candidate-report/SKILL.md +41 -36
- package/templates/.claude/skills/candidate-report/references/report-template.html +10 -10
- package/templates/.claude/skills/candidate-report/references/report.css +4 -4
- package/templates/.claude/skills/candidate-report/references/rubric.md +15 -14
- package/templates/.claude/skills/candidate-report/scripts/render-pdf.mjs +2 -2
- package/templates/.claude/skills/changelog/SKILL.md +122 -67
- package/templates/.claude/skills/deck-create/SKILL.md +90 -32
- package/templates/.claude/skills/deck-create/references/slide.css +4 -4
- package/templates/.claude/skills/deck-create/scripts/convert-to-pdf.mjs +6 -6
- package/templates/.claude/skills/deck-review/SKILL.md +63 -58
- package/templates/.claude/skills/deck-review/assets/slide-annotator.js +58 -49
- package/templates/.claude/skills/deck-summarize/SKILL.md +55 -45
- package/templates/.claude/skills/deck-summarize/references/brief-template.md +5 -4
- package/templates/.claude/skills/deck-summarize/scripts/extract-pptx.mjs +8 -7
- package/templates/.claude/skills/doc-collab/SKILL.md +39 -34
- package/templates/.claude/skills/doc-create/SKILL.md +27 -23
- package/templates/.claude/skills/doc-create/scripts/convert-to-pdf.mjs +6 -6
- package/templates/.claude/skills/draft-emails/SKILL.md +63 -53
- package/templates/.claude/skills/draft-emails/references/template.md +2 -2
- package/templates/.claude/skills/draft-emails/scripts/scan-emails.mjs +15 -8
- package/templates/.claude/skills/draft-emails/scripts/send-email.mjs +19 -11
- package/templates/.claude/skills/extract-entities/SKILL.md +76 -75
- package/templates/.claude/skills/extract-entities/references/TEMPLATES.md +6 -5
- package/templates/.claude/skills/extract-entities/references/conditions.md +20 -18
- package/templates/.claude/skills/extract-entities/references/content.md +10 -9
- package/templates/.claude/skills/extract-entities/references/links.md +37 -13
- package/templates/.claude/skills/extract-entities/references/recruitment.md +30 -24
- package/templates/.claude/skills/extract-entities/references/resolution.md +14 -14
- package/templates/.claude/skills/extract-entities/references/sources.md +9 -9
- package/templates/.claude/skills/extract-entities/references/templates-conditions.md +12 -6
- package/templates/.claude/skills/extract-entities/references/templates-people-orgs.md +29 -10
- package/templates/.claude/skills/extract-entities/references/templates-priorities.md +10 -4
- package/templates/.claude/skills/extract-entities/references/templates-projects-topics.md +19 -7
- package/templates/.claude/skills/extract-entities/scripts/state.mjs +9 -8
- package/templates/.claude/skills/meeting-prep/SKILL.md +42 -36
- package/templates/.claude/skills/organize-files/SKILL.md +27 -22
- package/templates/.claude/skills/organize-files/scripts/organize-by-type.mjs +5 -4
- package/templates/.claude/skills/organize-files/scripts/summarize.mjs +4 -4
- package/templates/.claude/skills/person-identify/SKILL.md +77 -27
- package/templates/.claude/skills/person-identify/scripts/identify.sh +125 -26
- package/templates/.claude/skills/person-lookup/SKILL.md +34 -30
- package/templates/.claude/skills/person-lookup/scripts/lookup.sh +46 -17
- package/templates/.claude/skills/req-assess/SKILL.md +44 -36
- package/templates/.claude/skills/req-assess/references/interview-template.md +7 -2
- package/templates/.claude/skills/req-assess/references/panel-template.md +7 -2
- package/templates/.claude/skills/req-assess/references/rubric.md +13 -13
- package/templates/.claude/skills/req-bundle/SKILL.md +164 -0
- package/templates/.claude/skills/req-bundle/references/matching.md +70 -0
- package/templates/.claude/skills/req-bundle/references/pdf-structure.md +74 -0
- package/templates/.claude/skills/req-bundle/scripts/split-bundle.mjs +377 -0
- package/templates/.claude/skills/req-decide/SKILL.md +52 -41
- package/templates/.claude/skills/req-decide/references/rubric.md +11 -10
- package/templates/.claude/skills/req-decide/references/template.md +2 -2
- package/templates/.claude/skills/req-forget/SKILL.md +58 -39
- package/templates/.claude/skills/req-forget/references/classify.md +15 -13
- package/templates/.claude/skills/req-forget/references/locations.md +27 -22
- package/templates/.claude/skills/req-forget/references/report-template.md +24 -16
- package/templates/.claude/skills/req-scan/SKILL.md +27 -25
- package/templates/.claude/skills/req-scan/references/fallbacks.md +9 -9
- package/templates/.claude/skills/req-scan/references/filters.md +13 -13
- package/templates/.claude/skills/req-scan/references/sources.md +7 -7
- package/templates/.claude/skills/req-scan/references/state.md +1 -1
- package/templates/.claude/skills/req-scan/references/template.md +11 -1
- package/templates/.claude/skills/req-scan/scripts/state.mjs +3 -2
- package/templates/.claude/skills/req-screen/SKILL.md +96 -50
- package/templates/.claude/skills/req-screen/references/rubric.md +46 -50
- package/templates/.claude/skills/req-screen/references/scoring.md +79 -0
- package/templates/.claude/skills/req-screen/references/template.md +29 -17
- package/templates/.claude/skills/req-track/SKILL.md +76 -76
- package/templates/.claude/skills/req-track/references/fields.md +31 -27
- package/templates/.claude/skills/req-track/references/overlays.md +42 -0
- package/templates/.claude/skills/req-track/references/signals.md +12 -12
- package/templates/.claude/skills/req-track/references/statuses.md +3 -2
- package/templates/.claude/skills/req-track/references/templates.md +28 -27
- package/templates/.claude/skills/req-workday/SKILL.md +69 -40
- package/templates/.claude/skills/req-workday/references/brief.md +81 -0
- package/templates/.claude/skills/req-workday/references/status-mapping.md +5 -5
- package/templates/.claude/skills/req-workday/references/templates.md +11 -78
- package/templates/.claude/skills/req-workday/references/xlsx-format.md +26 -7
- package/templates/.claude/skills/req-workday/scripts/parse-workday.mjs +113 -35
- package/templates/.claude/skills/send-chat/SKILL.md +58 -54
- package/templates/.claude/skills/sync-apple-calendar/SKILL.md +23 -19
- package/templates/.claude/skills/sync-apple-calendar/references/SCHEMA.md +7 -7
- package/templates/.claude/skills/sync-apple-calendar/scripts/query.mjs +6 -6
- package/templates/.claude/skills/sync-apple-calendar/scripts/sync.mjs +9 -8
- package/templates/.claude/skills/sync-apple-mail/SKILL.md +20 -14
- package/templates/.claude/skills/sync-apple-mail/references/SCHEMA.md +10 -10
- package/templates/.claude/skills/sync-apple-mail/scripts/parse-emlx.mjs +9 -9
- package/templates/.claude/skills/sync-apple-mail/scripts/sync-helpers.mjs +16 -13
- package/templates/.claude/skills/sync-apple-mail/scripts/sync.mjs +3 -3
- package/templates/.claude/skills/sync-teams/SKILL.md +46 -43
- package/templates/.claude/skills/sync-teams/scripts/idb-reader.mjs +22 -21
- package/templates/.claude/skills/sync-teams/scripts/leveldb-reader.mjs +4 -4
- package/templates/.claude/skills/sync-teams/scripts/sync.mjs +4 -4
- package/templates/.claude/skills/upstream-instructions/SKILL.md +43 -41
- package/templates/.claude/skills/upstream-instructions/references/examples.md +5 -5
- package/templates/CLAUDE.md +97 -85
- package/templates/MIGRATION.md +359 -0
- package/templates/registry.yaml +25 -0
- package/templates/.claude/skills/anarlog-follow/scripts/follow.mjs +0 -243
- package/templates/.claude/skills/anarlog-trim/SKILL.md +0 -183
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: req-assess
|
|
3
3
|
description: >
|
|
4
|
-
Analyze interview transcripts against the agent-aligned engineering standard
|
|
5
|
-
|
|
6
|
-
per-interview assessments and panel briefs for
|
|
4
|
+
Analyze interview transcripts against the agent-aligned engineering standard.
|
|
5
|
+
Update skill and behaviour ratings with observed evidence. Produces
|
|
6
|
+
per-interview assessments and panel briefs for later interview stages.
|
|
7
7
|
Use when transcript files appear in a candidate's folder.
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Assess Interview
|
|
11
11
|
|
|
12
|
+
Write tier: `2-Confidential`
|
|
13
|
+
Frontmatter: candidate (stamps `status`, `updated`)
|
|
14
|
+
|
|
12
15
|
Analyze a candidate's interview transcripts. Update their skill and behaviour
|
|
13
16
|
profile with **observed** (not claimed) evidence. Interview evidence is
|
|
14
|
-
higher-fidelity than CV evidence
|
|
17
|
+
higher-fidelity than CV evidence. It confirms or contradicts the screening
|
|
15
18
|
assessment.
|
|
16
19
|
|
|
17
20
|
This is **Stage 2** of the three-stage hiring pipeline:
|
|
@@ -22,43 +25,46 @@ This is **Stage 2** of the three-stage hiring pipeline:
|
|
|
22
25
|
|
|
23
26
|
## Trigger
|
|
24
27
|
|
|
25
|
-
- A new `transcript-*.md` file appears in `
|
|
28
|
+
- A new `transcript-*.md` file appears in `2-Confidential/Candidates/{Name}/`.
|
|
26
29
|
- The user asks to analyze an interview or debrief.
|
|
27
30
|
- The user asks to prepare a panel brief.
|
|
28
|
-
- The concierge agent processes
|
|
31
|
+
- The concierge agent processes an Anarlog interview recording.
|
|
29
32
|
|
|
30
33
|
## Prerequisites
|
|
31
34
|
|
|
32
35
|
- `fit-pathway` CLI installed.
|
|
33
|
-
- At least one transcript in `
|
|
34
|
-
- `screening.md` should exist
|
|
35
|
-
regardless
|
|
36
|
+
- At least one transcript in `2-Confidential/Candidates/{Name}/`.
|
|
37
|
+
- `screening.md` should exist. If it is missing, run `req-screen` first.
|
|
38
|
+
Proceed regardless.
|
|
36
39
|
|
|
37
40
|
## Inputs
|
|
38
41
|
|
|
39
|
-
- `
|
|
40
|
-
- `
|
|
41
|
-
- `
|
|
42
|
+
- `2-Confidential/Candidates/{Name}/transcript-{date}.md`.
|
|
43
|
+
- `2-Confidential/Candidates/{Name}/screening.md`.
|
|
44
|
+
- `2-Confidential/Candidates/{Name}/brief.md` — target role.
|
|
42
45
|
|
|
43
46
|
## Outputs
|
|
44
47
|
|
|
45
|
-
- `
|
|
46
|
-
- `
|
|
48
|
+
- `2-Confidential/Candidates/{Name}/interview-{date}.md`.
|
|
49
|
+
- `2-Confidential/Candidates/{Name}/panel.md` — only when more interviews are
|
|
47
50
|
planned.
|
|
48
|
-
- Updated `
|
|
51
|
+
- Updated `2-Confidential/Candidates/{Name}/brief.md` — frontmatter `status`
|
|
52
|
+
and `updated` stamped.
|
|
49
53
|
|
|
50
54
|
<do_confirm_checklist goal="Verify the assessment is grounded in transcript
|
|
51
55
|
evidence">
|
|
52
56
|
|
|
53
|
-
- [ ]
|
|
54
|
-
- [ ]
|
|
55
|
-
- [ ]
|
|
56
|
-
- [ ]
|
|
57
|
-
- [ ]
|
|
58
|
-
- [ ]
|
|
59
|
-
|
|
60
|
-
- [ ]
|
|
61
|
-
|
|
57
|
+
- [ ] Cite a specific moment from the transcript for every skill re-rating.
|
|
58
|
+
- [ ] Base behaviour assessments on observed actions. Ignore claimed traits.
|
|
59
|
+
- [ ] Assess the level with standard progression criteria. Ignore gut feel.
|
|
60
|
+
- [ ] Attribute interviewer observations by name.
|
|
61
|
+
- [ ] Distinguish confirmed strengths from new concerns.
|
|
62
|
+
- [ ] Assessment prose about the candidate stays in the candidate's own
|
|
63
|
+
folder. Wider notes record only that the interview happened.
|
|
64
|
+
- [ ] Write the panel brief (if created) for non-technical readers. Tie the
|
|
65
|
+
suggested questions to the remaining gaps.
|
|
66
|
+
- [ ] Update the brief's Pipeline section, links, and Status.
|
|
67
|
+
- [ ] **Never** update the Gender field from interview observations.
|
|
62
68
|
|
|
63
69
|
</do_confirm_checklist>
|
|
64
70
|
|
|
@@ -79,8 +85,8 @@ bunx fit-pathway skill {skill_id}
|
|
|
79
85
|
bunx fit-pathway behaviour --list
|
|
80
86
|
```
|
|
81
87
|
|
|
82
|
-
If screening recommended a different
|
|
83
|
-
J090), load **both** for comparison.
|
|
88
|
+
If screening recommended a level different from the original target (e.g. J100
|
|
89
|
+
→ J090), load **both** for comparison.
|
|
84
90
|
|
|
85
91
|
### 3. Re-rate skills
|
|
86
92
|
|
|
@@ -90,8 +96,8 @@ moment, quote, or observation per change.
|
|
|
90
96
|
|
|
91
97
|
### 4. Re-rate behaviours
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
|
|
99
|
+
Interviews assess behaviours better than CVs. Behaviours describe how someone
|
|
100
|
+
acts. They do not describe what someone did. Use the
|
|
95
101
|
[behaviour signals](references/rubric.md#behaviour-signals) and the
|
|
96
102
|
[behaviour maturity scale](references/rubric.md#behaviour-maturity-scale).
|
|
97
103
|
|
|
@@ -105,33 +111,35 @@ Apply the [level signals](references/rubric.md#level-signals).
|
|
|
105
111
|
|
|
106
112
|
### 6. Write the interview assessment
|
|
107
113
|
|
|
108
|
-
Save to `
|
|
114
|
+
Save to `2-Confidential/Candidates/{Name}/interview-{date}.md` with
|
|
109
115
|
[references/interview-template.md](references/interview-template.md). Include
|
|
110
|
-
only skills with new evidence
|
|
116
|
+
only skills with new evidence. Do not repeat the full matrix.
|
|
111
117
|
|
|
112
118
|
### 7. Generate the panel brief (if applicable)
|
|
113
119
|
|
|
114
|
-
When more interview stages
|
|
120
|
+
When you plan more interview stages (panel, technical, etc.), pull question
|
|
115
121
|
candidates:
|
|
116
122
|
|
|
117
123
|
```bash
|
|
118
124
|
bunx fit-pathway interview {discipline} {level} --track={track}
|
|
119
125
|
```
|
|
120
126
|
|
|
121
|
-
Save `
|
|
122
|
-
[references/panel-template.md](references/panel-template.md).
|
|
123
|
-
next-stage interviewers, often non-engineers
|
|
124
|
-
suggested questions to remaining gaps.
|
|
127
|
+
Save `2-Confidential/Candidates/{Name}/panel.md` with
|
|
128
|
+
[references/panel-template.md](references/panel-template.md). The audience is
|
|
129
|
+
next-stage interviewers, often non-engineers. Explain without jargon. Tie the
|
|
130
|
+
suggested questions to the remaining gaps.
|
|
125
131
|
|
|
126
132
|
### 8. Update the candidate brief
|
|
127
133
|
|
|
128
|
-
Apply targeted Edit operations to `
|
|
134
|
+
Apply targeted Edit operations to `2-Confidential/Candidates/{Name}/brief.md`:
|
|
129
135
|
|
|
130
136
|
- Append a Pipeline entry with date, type, and outcome.
|
|
131
137
|
- Add `## Interview Notes` if missing, with key observations.
|
|
132
138
|
- Append `- [Interview Assessment](./interview-{date}.md)`.
|
|
133
139
|
- Append `- [Panel Brief](./panel.md)` when one was created.
|
|
134
140
|
- Update `Status` to reflect the current pipeline stage.
|
|
141
|
+
- Stamp the frontmatter: set `updated` to the interview date and `status` to
|
|
142
|
+
the closest registry value (agents select from `registry.yaml`).
|
|
135
143
|
|
|
136
144
|
Never rewrite the file. Never update the Gender field from interview
|
|
137
145
|
observations.
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
# Interview Assessment Template
|
|
2
2
|
|
|
3
3
|
Reference template for `req-assess` Step 6. Save to
|
|
4
|
-
`
|
|
5
|
-
interview produced new evidence for
|
|
4
|
+
`2-Confidential/Candidates/{Name}/interview-{date}.md`. Include only skills the
|
|
5
|
+
interview produced new evidence for. Do not repeat the full matrix.
|
|
6
|
+
|
|
7
|
+
Interview-outcome prose about the candidate lives only in the candidate's own
|
|
8
|
+
folder. A wider note (an interviewer's People note, a project note) records
|
|
9
|
+
only that the interview happened. Interviewer names may appear here; write no
|
|
10
|
+
assessment of the interviewers.
|
|
6
11
|
|
|
7
12
|
```markdown
|
|
8
13
|
# Interview Assessment — {Full Name}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
# Panel Brief Template
|
|
2
2
|
|
|
3
3
|
Reference template for `req-assess` Step 7. Save to
|
|
4
|
-
`
|
|
5
|
-
often non-engineers
|
|
4
|
+
`2-Confidential/Candidates/{Name}/panel.md`. The audience is **next-stage
|
|
5
|
+
interviewers, often non-engineers**. Explain without jargon. Focus on
|
|
6
|
+
behaviours and scope.
|
|
7
|
+
|
|
8
|
+
The brief goes to the panel as an export. The note keeps its tier. Assessment
|
|
9
|
+
prose about the candidate stays in this folder; never copy it into panel
|
|
10
|
+
members' People notes.
|
|
6
11
|
|
|
7
12
|
```markdown
|
|
8
13
|
# Panel Brief — {Full Name}
|
|
@@ -13,17 +13,17 @@ Reference data for `req-assess` Steps 1, 3, 4, and 5.
|
|
|
13
13
|
| **Behaviour observations** | How the candidate acted under pressure, with others |
|
|
14
14
|
| **Self-identified strengths** | What the candidate claims they're good at |
|
|
15
15
|
| **Self-identified gaps** | What the candidate acknowledges as growth areas |
|
|
16
|
-
| **Red flags** | Contradictions with CV,
|
|
16
|
+
| **Red flags** | Contradictions with CV, patterns of concern |
|
|
17
17
|
| **Interviewer feedback** | Direct quotes or observations from interviewers |
|
|
18
18
|
| **Level signals** | Autonomy, scope, complexity demonstrated in responses |
|
|
19
19
|
| **Track signals** | Business immersion vs. platform thinking in their answers |
|
|
20
20
|
|
|
21
21
|
## Skill re-rating
|
|
22
22
|
|
|
23
|
-
**Interview evidence outranks CV evidence.**
|
|
24
|
-
`practitioner` but demonstrated `foundational
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
**Interview evidence outranks CV evidence.** Re-rate a candidate who claimed
|
|
24
|
+
`practitioner` but demonstrated `foundational`. The reverse also applies. Every
|
|
25
|
+
re-rating cites a specific moment, quote, or observation from the transcript.
|
|
26
|
+
The phrase "seemed strong" is not evidence.
|
|
27
27
|
|
|
28
28
|
| Interview evidence | Rating adjustment |
|
|
29
29
|
| ------------------------------------------- | ------------------------------------- |
|
|
@@ -36,13 +36,13 @@ from the transcript — "seemed strong" is not evidence.
|
|
|
36
36
|
|
|
37
37
|
## Behaviour signals
|
|
38
38
|
|
|
39
|
-
| Behaviour | Interview evidence
|
|
40
|
-
| -------------------------- |
|
|
41
|
-
| Own the Outcome | Takes responsibility, doesn't deflect, drives to resolution
|
|
42
|
-
| Think in Systems | Considers second-order effects, trade-offs, system boundaries
|
|
43
|
-
| Communicate with Precision | Clear explanations, appropriate detail, listens actively
|
|
44
|
-
| Be Polymath Oriented | Draws on diverse knowledge, makes unexpected connections
|
|
45
|
-
| Don't Lose Your Curiosity | Asks good questions, explores alternatives, admits not
|
|
39
|
+
| Behaviour | Interview evidence |
|
|
40
|
+
| -------------------------- | ------------------------------------------------------------------------ |
|
|
41
|
+
| Own the Outcome | Takes responsibility, doesn't deflect, drives to resolution |
|
|
42
|
+
| Think in Systems | Considers second-order effects, trade-offs, system boundaries |
|
|
43
|
+
| Communicate with Precision | Clear explanations, appropriate detail, listens actively |
|
|
44
|
+
| Be Polymath Oriented | Draws on diverse knowledge, makes unexpected connections |
|
|
45
|
+
| Don't Lose Your Curiosity | Asks good questions, explores alternatives, admits what they do not know |
|
|
46
46
|
|
|
47
47
|
## Behaviour maturity scale
|
|
48
48
|
|
|
@@ -59,7 +59,7 @@ from the transcript — "seemed strong" is not evidence.
|
|
|
59
59
|
| Interview signal | Implication |
|
|
60
60
|
| --------------------------------------------------------- | ------------------------------ |
|
|
61
61
|
| Needed guidance to structure their approach | Level may be lower than est. |
|
|
62
|
-
| Self-directed
|
|
62
|
+
| Self-directed, made sound trade-offs independently | Level estimate confirmed |
|
|
63
63
|
| Mentored or coached others during the exercise | Level may be higher than est. |
|
|
64
64
|
| Struggled with complexity appropriate to the target level | Downgrade |
|
|
65
65
|
| Interviewer explicitly suggested a different level | Strong signal — weight heavily |
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: req-bundle
|
|
3
|
+
description: >
|
|
4
|
+
Split a Workday CV-bundle PDF into per-candidate CV.pdf files under
|
|
5
|
+
2-Confidential/Candidates/. Reads the PDF outline (one bookmark per candidate) to
|
|
6
|
+
find each candidate's page range, matches it to the requisition roster so
|
|
7
|
+
folder names agree with req-workday, and extracts each CV losslessly. Use
|
|
8
|
+
when the user provides a Workday CV bundle (a large combined resume PDF, often
|
|
9
|
+
split into `_N_of_M` parts) and asks to split, unbundle, or extract individual
|
|
10
|
+
CVs — run this before req-workday imports the roster.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Workday CV Bundle Split
|
|
14
|
+
|
|
15
|
+
Write tier: `2-Confidential`
|
|
16
|
+
Frontmatter: none (writes CV assets into candidate folders)
|
|
17
|
+
|
|
18
|
+
Split a Workday CV-**bundle** PDF — one combined file holding every applicant's
|
|
19
|
+
attachments — into per-candidate
|
|
20
|
+
`2-Confidential/Candidates/{Clean Name}/CV.pdf`.
|
|
21
|
+
|
|
22
|
+
This is the **PDF companion** to `req-workday`. `req-workday` imports the roster
|
|
23
|
+
from the `.xlsx` (briefs + `CV.md` from embedded resume text); `req-bundle`
|
|
24
|
+
attaches the real binary CV. Run `req-bundle` **first** so the folders it
|
|
25
|
+
creates carry canonical names that `req-workday` then enriches — no duplicate
|
|
26
|
+
folders.
|
|
27
|
+
|
|
28
|
+
**How the split works.** Workday bundles have
|
|
29
|
+
**no per-candidate separator page** and the name printed on a CV often differs
|
|
30
|
+
from the roster name, so text-based splitting is unreliable. The one dependable
|
|
31
|
+
delimiter is the **PDF outline**: exactly one flat bookmark per candidate, its
|
|
32
|
+
destination being that candidate's first page. Candidate *i* = pages
|
|
33
|
+
`[bookmark(i), bookmark(i+1) − 1]`; the last runs to EOF; the leading Table of
|
|
34
|
+
Contents (before the first bookmark) is skipped. Multiple attachments for one
|
|
35
|
+
candidate sit under one bookmark, so the range captures them all.
|
|
36
|
+
`split-bundle.mjs` reads the outline with `pdfjs-dist` (`getOutline` +
|
|
37
|
+
`getPageIndex`) and extracts each range losslessly with `pdf-lib` (`copyPages`).
|
|
38
|
+
Full anatomy: [references/pdf-structure.md](references/pdf-structure.md).
|
|
39
|
+
|
|
40
|
+
## Trigger
|
|
41
|
+
|
|
42
|
+
- The user provides a Workday CV bundle PDF (a large combined resume export),
|
|
43
|
+
possibly split into `..._1_of_N.pdf … _N_of_N.pdf` parts.
|
|
44
|
+
- The user asks to split, unbundle, or extract individual CVs from a Workday
|
|
45
|
+
export before importing candidates.
|
|
46
|
+
|
|
47
|
+
## Prerequisites
|
|
48
|
+
|
|
49
|
+
- PDF libraries for the split (`bun`/`node`), installed via the standard guard:
|
|
50
|
+
`bun pm ls pdfjs-dist 2>/dev/null || bun install pdfjs-dist` and
|
|
51
|
+
`bun pm ls pdf-lib 2>/dev/null || bun install pdf-lib`.
|
|
52
|
+
- The `req-workday` parser dependencies, since the roster is produced by
|
|
53
|
+
`parse-workday.mjs` (see [../req-workday/SKILL.md](../req-workday/SKILL.md)):
|
|
54
|
+
`read-excel-file` and `fflate`.
|
|
55
|
+
|
|
56
|
+
## Inputs
|
|
57
|
+
|
|
58
|
+
- One or more bundle PDF paths (pass **all** parts of a multi-part export).
|
|
59
|
+
- The requisition `.xlsx` (same req as the bundle) — parsed by
|
|
60
|
+
`req-workday`'s `parse-workday.mjs` into the roster JSON this script consumes,
|
|
61
|
+
so folder names are identical to what `req-workday` will create.
|
|
62
|
+
|
|
63
|
+
## Outputs
|
|
64
|
+
|
|
65
|
+
- `2-Confidential/Candidates/{Clean Name}/CV.pdf` — the candidate's extracted CV
|
|
66
|
+
(created only after the dry-run manifest is reviewed and confirmed).
|
|
67
|
+
- A JSON manifest at `$HOME/.cache/fit/outpost/state/req-bundle-{Req ID}.json`
|
|
68
|
+
— the reviewable mapping of every bookmark (page range, matched name, status).
|
|
69
|
+
- Never overwrites an existing `CV.pdf`; a second CV for the same person is
|
|
70
|
+
staged as `CV-workday.pdf` / `CV-dup-N.pdf` and flagged for review.
|
|
71
|
+
|
|
72
|
+
<do_confirm_checklist goal="Verify the split is complete, correctly named, and non-destructive">
|
|
73
|
+
|
|
74
|
+
- [ ] Bundle page/bookmark counts sanity-checked; all parts of a multi-part
|
|
75
|
+
export passed together.
|
|
76
|
+
- [ ] Roster parsed from the matching `.xlsx`; bookmark count reconciled against
|
|
77
|
+
roster count.
|
|
78
|
+
- [ ] Dry-run manifest reviewed **before** any write; every `unmatched` bookmark
|
|
79
|
+
resolved to a candidate (or explicitly left staged).
|
|
80
|
+
- [ ] Folder names equal the roster `cleanName` (annotation stripped) so they
|
|
81
|
+
match what `req-workday` produces.
|
|
82
|
+
- [ ] Collisions (duplicate applications) and pre-existing `CV.pdf` files
|
|
83
|
+
handled without overwrite (`CV-dup-N.pdf` / `CV-workday.pdf`), each
|
|
84
|
+
flagged.
|
|
85
|
+
- [ ] After commit, output CV count reconciles with matched bookmarks; a few
|
|
86
|
+
PDFs spot-opened to confirm the right, complete candidate.
|
|
87
|
+
|
|
88
|
+
</do_confirm_checklist>
|
|
89
|
+
|
|
90
|
+
## Procedure
|
|
91
|
+
|
|
92
|
+
### 1. Set up
|
|
93
|
+
|
|
94
|
+
Confirm the bundle PDF path(s) and the matching `.xlsx` (glob on the req number,
|
|
95
|
+
not the timestamp — parts can carry different timestamps). Resolve `$HOME`
|
|
96
|
+
(never pass a literal `~` to write tools). Ensure dependencies:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
bun pm ls pdfjs-dist 2>/dev/null || bun install pdfjs-dist
|
|
100
|
+
bun pm ls pdf-lib 2>/dev/null || bun install pdf-lib
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 2. Parse the roster
|
|
104
|
+
|
|
105
|
+
Produce the canonical candidate list with `req-workday`'s parser (the single
|
|
106
|
+
source of truth for `cleanName`s), saving the JSON for the split step:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
node .claude/skills/req-workday/scripts/parse-workday.mjs "<path-to-requisition.xlsx>" --summary
|
|
110
|
+
node .claude/skills/req-workday/scripts/parse-workday.mjs "<path-to-requisition.xlsx>" \
|
|
111
|
+
> "$HOME/.cache/fit/outpost/state/req-bundle-roster-{Req ID}.json"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 3. Dry-run the split
|
|
115
|
+
|
|
116
|
+
Compute the mapping and manifest **without writing** any PDF. Pass every part of
|
|
117
|
+
a multi-part bundle in one invocation:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
node .claude/skills/req-bundle/scripts/split-bundle.mjs \
|
|
121
|
+
"<bundle_1_of_N.pdf>" "<bundle_2_of_N.pdf>" ... \
|
|
122
|
+
--roster "$HOME/.cache/fit/outpost/state/req-bundle-roster-{Req ID}.json" \
|
|
123
|
+
--candidates-dir 2-Confidential/Candidates \
|
|
124
|
+
--manifest "$HOME/.cache/fit/outpost/state/req-bundle-{Req ID}.json" \
|
|
125
|
+
--dry-run
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The script prints a summary (bookmarks, matched, unmatched, collisions,
|
|
129
|
+
existing-CV, roster-without-bookmark) and writes the full manifest. Matching
|
|
130
|
+
rules and edge cases: [references/matching.md](references/matching.md).
|
|
131
|
+
|
|
132
|
+
### 4. Reconcile and resolve
|
|
133
|
+
|
|
134
|
+
Review the manifest. Confirm `bookmarks_total` reconciles with the roster count
|
|
135
|
+
(expect a near-exact match). For each `unmatched` bookmark, use its `hints`
|
|
136
|
+
(nearest roster names by shared tokens) to identify the candidate; note the
|
|
137
|
+
correct `cleanName`. Investigate any `roster_unmatched_names` (applicants with
|
|
138
|
+
no CV in the bundle — usually withdrawn/no-attachment). Do not proceed while
|
|
139
|
+
unexpected mismatches remain unexplained.
|
|
140
|
+
|
|
141
|
+
### 5. Confirm, then commit
|
|
142
|
+
|
|
143
|
+
Summarize for the user: N CVs to write, M unmatched, K collisions/existing.
|
|
144
|
+
**Get explicit confirmation** before writing. Re-run without `--dry-run` (same
|
|
145
|
+
roster JSON) to create folders and write the CVs:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
node .claude/skills/req-bundle/scripts/split-bundle.mjs \
|
|
149
|
+
"<bundle parts...>" \
|
|
150
|
+
--roster "$HOME/.cache/fit/outpost/state/req-bundle-roster-{Req ID}.json" \
|
|
151
|
+
--candidates-dir 2-Confidential/Candidates \
|
|
152
|
+
--manifest "$HOME/.cache/fit/outpost/state/req-bundle-{Req ID}.json"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
For bookmarks that were `unmatched` but you resolved by hand in Step 4, move the
|
|
156
|
+
staged file into the correct `2-Confidential/Candidates/{Clean Name}/CV.pdf`, or
|
|
157
|
+
add the missing name variant and re-run — never hand-edit PDFs.
|
|
158
|
+
|
|
159
|
+
### 6. Hand off and report
|
|
160
|
+
|
|
161
|
+
Point `req-workday` at the same `.xlsx` to build briefs/`CV.md` and link the CV
|
|
162
|
+
under each brief's `## CV` section. Flag every candidate that now has a `CV.pdf`
|
|
163
|
+
for `req-screen`. Report `Split {matched}/{bookmarks_total} CVs for {Req ID}`,
|
|
164
|
+
and list any unmatched bookmarks or roster applicants without a CV.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Matching bookmarks to the roster, and edge cases
|
|
2
|
+
|
|
3
|
+
How `split-bundle.mjs` maps each PDF bookmark to a canonical candidate folder,
|
|
4
|
+
and how it handles duplicates, existing files, and misses. The goal: folder
|
|
5
|
+
names identical to what `req-workday` produces, and **never** a destructive
|
|
6
|
+
overwrite.
|
|
7
|
+
|
|
8
|
+
## Why match at all
|
|
9
|
+
|
|
10
|
+
The bookmark title is a clean name, but folder names must equal the roster
|
|
11
|
+
`cleanName` — the applicant name from the `.xlsx` `Candidates` sheet with any
|
|
12
|
+
trailing annotation (`(Internal)`, `(Prior Worker)`, …) stripped. That
|
|
13
|
+
`cleanName` comes **directly from `req-workday`'s `parse-workday.mjs`** (the
|
|
14
|
+
single source of truth this skill consumes as JSON), so the two skills agree by
|
|
15
|
+
construction and never create duplicate folders. So: bookmark title → (match) →
|
|
16
|
+
roster `cleanName` → folder.
|
|
17
|
+
|
|
18
|
+
## Normalization (both sides)
|
|
19
|
+
|
|
20
|
+
Before comparing, each name is folded with `normalize()`:
|
|
21
|
+
|
|
22
|
+
1. Remove parentheticals — `(ΝΙΚΟΣ ΠΑΠΑΔΟΠΟΥΛΟΣ)`, `(Internal)`, `(Referral)`.
|
|
23
|
+
2. Remove bare annotation tokens — internal / referral / prior worker /
|
|
24
|
+
external.
|
|
25
|
+
3. Strip diacritics (NFKD) — `Geraño` → `gerano`.
|
|
26
|
+
4. Lowercase, keep `[a-z0-9 ]`, collapse whitespace.
|
|
27
|
+
|
|
28
|
+
## Match methods (conservative, deterministic)
|
|
29
|
+
|
|
30
|
+
Applied in order; the method is recorded in the manifest:
|
|
31
|
+
|
|
32
|
+
1. **`exact`** — normalized strings equal.
|
|
33
|
+
2. **`token-set`** — same set of tokens regardless of order (handles
|
|
34
|
+
"First Last" ↔ "Last First" and dropped middle names).
|
|
35
|
+
3. **`unmatched`** — no confident match. The script does **not** guess by
|
|
36
|
+
subset/Levenshtein (that risks merging two different "David …"s). Instead it
|
|
37
|
+
attaches `hints`: the nearest roster names by shared-token count, for a human
|
|
38
|
+
to resolve.
|
|
39
|
+
|
|
40
|
+
## Edge cases & how the script handles them
|
|
41
|
+
|
|
42
|
+
- **Duplicate applications (collisions).** The same person appears under two
|
|
43
|
+
bookmarks (e.g. across two parts of a multi-part bundle). Both resolve to one
|
|
44
|
+
folder. The first writes `CV.pdf`; the next becomes `CV-dup-2.pdf` (then
|
|
45
|
+
`-3`, …) with status `collision`. Review whether it's a true duplicate (keep
|
|
46
|
+
one) or a distinct newer packet (rename intentionally). Never silently
|
|
47
|
+
overwrite.
|
|
48
|
+
- **Pre-existing `CV.pdf`.** A candidate who already arrived via email
|
|
49
|
+
(`req-track`) may already have `CV.pdf`. The script writes `CV-workday.pdf`
|
|
50
|
+
with status `existing-cv` and flags it, so the richer existing artifact is
|
|
51
|
+
preserved. Decide per candidate which to keep.
|
|
52
|
+
- **Unmatched bookmark.** Staged in a folder named from the sanitized bookmark
|
|
53
|
+
title, status `unmatched`, with `hints`. Resolve by identifying the right
|
|
54
|
+
`cleanName` and moving the staged `CV.pdf` into that folder, or by adding the
|
|
55
|
+
missing name variant and re-running. A genuinely stray bookmark (someone not
|
|
56
|
+
on the roster) is worth flagging to the user.
|
|
57
|
+
- **Roster applicant with no bookmark** (`roster_unmatched_names`). Applied but
|
|
58
|
+
no CV in the bundle — usually withdrawn or never attached a resume. Expected;
|
|
59
|
+
just report it. Not an error.
|
|
60
|
+
- **No outline** (`no-outline` status for a file). The PDF has no bookmarks —
|
|
61
|
+
it is not a standard Workday bundle, or the outline was stripped. Do not
|
|
62
|
+
attempt a blind page-count split; stop and tell the user.
|
|
63
|
+
|
|
64
|
+
## Reconciliation checklist
|
|
65
|
+
|
|
66
|
+
- `bookmarks_total` ≈ roster count (small gaps explained by
|
|
67
|
+
`roster_unmatched_names`).
|
|
68
|
+
- `unmatched_bookmarks` == 0 after Step 4 resolution (or each is explained).
|
|
69
|
+
- `matched` + resolved unmatched == CVs you expect to write.
|
|
70
|
+
- Known repeat applicants line up with the reported `collisions`.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Workday CV Bundle — PDF anatomy
|
|
2
|
+
|
|
3
|
+
What a Workday "Download resumes / attachments" bundle looks like, and why the
|
|
4
|
+
PDF outline is the only reliable way to split it.
|
|
5
|
+
|
|
6
|
+
## The shape of a bundle
|
|
7
|
+
|
|
8
|
+
- A bundle is **one PDF that concatenates every applicant's attachments** — CV,
|
|
9
|
+
and often a cover letter and certificates too — back to back.
|
|
10
|
+
- **Front matter:** page 1 (and sometimes 2–3) is a whole-bundle **Table of
|
|
11
|
+
Contents** titled with the req, e.g. `4951493 - Principal Software Engineer`,
|
|
12
|
+
listing `Candidate Name | Candidate ID | Attachments`. The TOC has **no page
|
|
13
|
+
numbers** in its extracted text, so it is not a usable split key — skip it.
|
|
14
|
+
- **No per-candidate separator/cover page.** Each candidate's first attachment
|
|
15
|
+
begins directly on its own page. The TOC is the only inserted page.
|
|
16
|
+
|
|
17
|
+
## The delimiter: the PDF outline (bookmarks)
|
|
18
|
+
|
|
19
|
+
Every bundle carries a **flat, depth-0 PDF outline with exactly one bookmark per
|
|
20
|
+
candidate**. The bookmark's destination is that candidate's **first page**, and
|
|
21
|
+
its title is the **clean candidate name** (no `(Internal)` / `(Referral)`
|
|
22
|
+
suffix). This is the split key:
|
|
23
|
+
|
|
24
|
+
- Candidate *i* → pages `[bookmark(i).page, bookmark(i+1).page − 1]`.
|
|
25
|
+
- Last candidate → `[bookmark(last).page, EOF]`.
|
|
26
|
+
- A candidate's **multiple attachments are all under their single bookmark**, so
|
|
27
|
+
the page range captures the cover letter + CV + certs together. (This is fine
|
|
28
|
+
— a hiring manager wants the whole packet; `req-screen` reads the CV within.)
|
|
29
|
+
- Bookmark counts match roster counts exactly (verified live: 75↔75 on a
|
|
30
|
+
single-part export; 366↔366 across the four parts of a large export).
|
|
31
|
+
|
|
32
|
+
`split-bundle.mjs` reads this via `pdfjs-dist`: `doc.getOutline()` for the
|
|
33
|
+
items, then `doc.getPageIndex(dest[0])` (resolving named destinations through
|
|
34
|
+
`doc.getDestination()` first) to turn each bookmark into a 0-based page index,
|
|
35
|
+
then sorts by page. Page ranges are extracted losslessly with `pdf-lib`
|
|
36
|
+
(`copyPages`).
|
|
37
|
+
|
|
38
|
+
## What NOT to rely on
|
|
39
|
+
|
|
40
|
+
- **Separator-page detection** — there are none.
|
|
41
|
+
- **Name text at the top of a page** — a CV's printed name is the candidate's
|
|
42
|
+
own styling and often differs from the roster name (bookmark "Vivian Chen" →
|
|
43
|
+
CV reads "Wei-Ling (Vivian) Chen, PhD"). Match the **bookmark title**, not
|
|
44
|
+
page text.
|
|
45
|
+
- **Page-text extraction** — unnecessary and unreliable for boundary detection:
|
|
46
|
+
the outline gives exact boundaries and the `.xlsx` already carries the resume
|
|
47
|
+
text.
|
|
48
|
+
|
|
49
|
+
## Multi-part bundles (`_1_of_N` … `_N_of_N`)
|
|
50
|
+
|
|
51
|
+
When a req has many applicants, Workday emits several PDFs. Key facts:
|
|
52
|
+
|
|
53
|
+
- **Each part is self-contained:** its own TOC, its own ~100-candidate subset in
|
|
54
|
+
its own internal order (parts are *not* sequential slices of one ordering).
|
|
55
|
+
- **No candidate spans a part boundary** — every part's first bookmark is a
|
|
56
|
+
fresh candidate after its TOC, and its last candidate is fully contained.
|
|
57
|
+
- Therefore **process each part independently** and union the results; pass all
|
|
58
|
+
parts to `split-bundle.mjs` in one call.
|
|
59
|
+
- A person may legitimately appear in two parts (a genuine duplicate
|
|
60
|
+
application). That surfaces as a **collision** in the manifest (same folder
|
|
61
|
+
targeted twice) — handled per [matching.md](matching.md), not stitched.
|
|
62
|
+
|
|
63
|
+
## Observed reference numbers (live exports)
|
|
64
|
+
|
|
65
|
+
| Bundle | Pages | Bookmarks |
|
|
66
|
+
| ---------------------------------------- | ----- | --------- |
|
|
67
|
+
| Principal SWE req (single file) | 215 | 75 |
|
|
68
|
+
| Sr. Director req `_1_of_4` | 321 | 100 |
|
|
69
|
+
| Sr. Director req `_2_of_4` | 314 | 100 |
|
|
70
|
+
| Sr. Director req `_3_of_4` | 278 | 100 |
|
|
71
|
+
| Sr. Director req `_4_of_4` | 213 | 66 |
|
|
72
|
+
|
|
73
|
+
Note: one part's filename carried a slightly different timestamp than its
|
|
74
|
+
siblings — glob on the req number, not the timestamp.
|