@forwardimpact/outpost 3.12.1 → 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/package.json +3 -2
- package/src/kb-manager.js +58 -5
- package/src/kb-validator.js +762 -0
- package/src/outpost.js +73 -8
- package/templates/.claude/agents/chief-of-staff.md +14 -6
- package/templates/.claude/agents/concierge.md +9 -2
- package/templates/.claude/agents/head-hunter.md +9 -2
- package/templates/.claude/agents/librarian.md +11 -4
- package/templates/.claude/agents/postman.md +9 -2
- package/templates/.claude/agents/recruiter.md +9 -2
- package/templates/.claude/skills/anarlog-follow/SKILL.md +53 -29
- package/templates/.claude/skills/anarlog-follow/references/sessions.md +52 -0
- package/templates/.claude/skills/anarlog-process/SKILL.md +88 -45
- package/templates/.claude/skills/anarlog-process/references/extraction.md +19 -11
- package/templates/.claude/skills/anarlog-process/references/sessions.md +92 -58
- package/templates/.claude/skills/anarlog-process/scripts/scan.mjs +404 -163
- package/templates/.claude/skills/candidate-report/SKILL.md +12 -9
- package/templates/.claude/skills/changelog/SKILL.md +106 -56
- package/templates/.claude/skills/deck-create/SKILL.md +60 -2
- package/templates/.claude/skills/deck-review/SKILL.md +4 -1
- package/templates/.claude/skills/deck-summarize/SKILL.md +6 -2
- package/templates/.claude/skills/deck-summarize/references/brief-template.md +1 -1
- package/templates/.claude/skills/doc-collab/SKILL.md +16 -11
- package/templates/.claude/skills/doc-create/SKILL.md +6 -3
- package/templates/.claude/skills/draft-emails/SKILL.md +31 -25
- package/templates/.claude/skills/draft-emails/references/template.md +1 -1
- package/templates/.claude/skills/draft-emails/scripts/scan-emails.mjs +14 -8
- package/templates/.claude/skills/draft-emails/scripts/send-email.mjs +9 -3
- package/templates/.claude/skills/extract-entities/SKILL.md +17 -16
- package/templates/.claude/skills/extract-entities/references/TEMPLATES.md +2 -1
- package/templates/.claude/skills/extract-entities/references/conditions.md +6 -5
- package/templates/.claude/skills/extract-entities/references/links.md +34 -10
- package/templates/.claude/skills/extract-entities/references/recruitment.md +15 -10
- package/templates/.claude/skills/extract-entities/references/resolution.md +1 -1
- package/templates/.claude/skills/extract-entities/references/sources.md +1 -1
- package/templates/.claude/skills/extract-entities/references/templates-conditions.md +10 -4
- package/templates/.claude/skills/extract-entities/references/templates-people-orgs.md +29 -10
- package/templates/.claude/skills/extract-entities/references/templates-priorities.md +8 -2
- package/templates/.claude/skills/extract-entities/references/templates-projects-topics.md +19 -7
- package/templates/.claude/skills/meeting-prep/SKILL.md +23 -18
- package/templates/.claude/skills/organize-files/SKILL.md +3 -0
- package/templates/.claude/skills/person-identify/SKILL.md +58 -10
- package/templates/.claude/skills/person-identify/scripts/identify.sh +110 -11
- package/templates/.claude/skills/person-lookup/SKILL.md +8 -3
- package/templates/.claude/skills/person-lookup/scripts/lookup.sh +27 -1
- package/templates/.claude/skills/req-assess/SKILL.md +19 -11
- package/templates/.claude/skills/req-assess/references/interview-template.md +6 -1
- package/templates/.claude/skills/req-assess/references/panel-template.md +5 -1
- 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 +26 -18
- package/templates/.claude/skills/req-decide/references/template.md +2 -2
- package/templates/.claude/skills/req-forget/SKILL.md +39 -22
- package/templates/.claude/skills/req-forget/references/classify.md +12 -10
- package/templates/.claude/skills/req-forget/references/locations.md +27 -22
- package/templates/.claude/skills/req-forget/references/report-template.md +23 -15
- package/templates/.claude/skills/req-scan/SKILL.md +6 -3
- package/templates/.claude/skills/req-scan/references/template.md +11 -1
- package/templates/.claude/skills/req-screen/SKILL.md +78 -34
- package/templates/.claude/skills/req-screen/references/rubric.md +38 -42
- package/templates/.claude/skills/req-screen/references/scoring.md +79 -0
- package/templates/.claude/skills/req-screen/references/template.md +26 -14
- package/templates/.claude/skills/req-track/SKILL.md +44 -44
- package/templates/.claude/skills/req-track/references/fields.md +18 -15
- package/templates/.claude/skills/req-track/references/overlays.md +42 -0
- package/templates/.claude/skills/req-track/references/signals.md +2 -2
- package/templates/.claude/skills/req-track/references/templates.md +28 -27
- package/templates/.claude/skills/req-workday/SKILL.md +49 -20
- package/templates/.claude/skills/req-workday/references/brief.md +81 -0
- package/templates/.claude/skills/req-workday/references/templates.md +6 -73
- package/templates/.claude/skills/req-workday/references/xlsx-format.md +19 -0
- package/templates/.claude/skills/req-workday/scripts/parse-workday.mjs +96 -20
- package/templates/.claude/skills/send-chat/SKILL.md +11 -11
- package/templates/.claude/skills/sync-apple-calendar/SKILL.md +3 -0
- package/templates/.claude/skills/sync-apple-mail/SKILL.md +3 -0
- package/templates/.claude/skills/sync-teams/SKILL.md +3 -0
- package/templates/.claude/skills/upstream-instructions/SKILL.md +8 -5
- package/templates/.claude/skills/upstream-instructions/references/examples.md +3 -3
- package/templates/CLAUDE.md +96 -87
- 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 -186
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
## Projects
|
|
4
4
|
|
|
5
5
|
```markdown
|
|
6
|
+
---
|
|
7
|
+
type: project
|
|
8
|
+
created: {YYYY-MM-DD}
|
|
9
|
+
updated: {YYYY-MM-DD}
|
|
10
|
+
---
|
|
11
|
+
|
|
6
12
|
# {Project Name}
|
|
7
13
|
|
|
8
14
|
## Info
|
|
@@ -15,17 +21,17 @@
|
|
|
15
21
|
{2-3 sentences}
|
|
16
22
|
|
|
17
23
|
## Priorities
|
|
18
|
-
- [[Priorities/{Priority}]] — {how this project contributes}
|
|
24
|
+
- [[3-Team/Priorities/{Priority}]] — {how this project contributes}
|
|
19
25
|
|
|
20
26
|
## People
|
|
21
|
-
- [[People/{Person}]] — {role}
|
|
27
|
+
- [[3-Team/People/{Person}]] — {role}
|
|
22
28
|
|
|
23
29
|
## Organizations
|
|
24
|
-
- [[Organizations/{Org}]] — {relationship}
|
|
30
|
+
- [[3-Team/Organizations/{Org}]] — {relationship}
|
|
25
31
|
|
|
26
32
|
## Related
|
|
27
|
-
- [[Priorities/{Priority}]] — {relationship}
|
|
28
|
-
- [[Topics/{Topic}]] — {relationship}
|
|
33
|
+
- [[3-Team/Priorities/{Priority}]] — {relationship}
|
|
34
|
+
- [[3-Team/Topics/{Topic}]] — {relationship}
|
|
29
35
|
|
|
30
36
|
## Timeline
|
|
31
37
|
**{YYYY-MM-DD}** ({type})
|
|
@@ -42,6 +48,12 @@
|
|
|
42
48
|
## Topics
|
|
43
49
|
|
|
44
50
|
```markdown
|
|
51
|
+
---
|
|
52
|
+
type: topic
|
|
53
|
+
created: {YYYY-MM-DD}
|
|
54
|
+
updated: {YYYY-MM-DD}
|
|
55
|
+
---
|
|
56
|
+
|
|
45
57
|
# {Topic Name}
|
|
46
58
|
|
|
47
59
|
## About
|
|
@@ -53,8 +65,8 @@
|
|
|
53
65
|
**Last mentioned:** {YYYY-MM-DD}
|
|
54
66
|
|
|
55
67
|
## Related
|
|
56
|
-
- [[People/{Person}]] — {relationship}
|
|
57
|
-
- [[Organizations/{Org}]] — {relationship}
|
|
68
|
+
- [[3-Team/People/{Person}]] — {relationship}
|
|
69
|
+
- [[3-Team/Organizations/{Org}]] — {relationship}
|
|
58
70
|
|
|
59
71
|
## Log
|
|
60
72
|
**{YYYY-MM-DD}** ({type}: {title})
|
|
@@ -5,6 +5,9 @@ description: Prepare for meetings. Gather context from the knowledge base and ca
|
|
|
5
5
|
|
|
6
6
|
# Meeting Prep
|
|
7
7
|
|
|
8
|
+
Write tier: none (the output is personal `Briefings/`)
|
|
9
|
+
Frontmatter: none
|
|
10
|
+
|
|
8
11
|
Help the user prepare for meetings. Gather context from the knowledge base and
|
|
9
12
|
calendar. Create personalized briefing documents with attendee history, open
|
|
10
13
|
items, and suggested talking points.
|
|
@@ -22,14 +25,16 @@ meetings.
|
|
|
22
25
|
## Inputs
|
|
23
26
|
|
|
24
27
|
- `~/.cache/fit/outpost/apple_calendar/*.json` — calendar events
|
|
25
|
-
- `
|
|
26
|
-
- `
|
|
27
|
-
- `
|
|
28
|
-
- `
|
|
28
|
+
- `3-Team/People/*.md` — attendee context
|
|
29
|
+
- `3-Team/Organizations/*.md` — company context
|
|
30
|
+
- `3-Team/Projects/*.md` — project context
|
|
31
|
+
- `3-Team/Priorities/*.md` — active priorities and strategic context to
|
|
29
32
|
frame the brief
|
|
30
|
-
- `
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
- `2-Confidential/Candidates/*/brief.md` — candidate context (for interview
|
|
34
|
+
meetings)
|
|
35
|
+
- `2-Confidential/Roles/*.md` — role/requisition context (for interview
|
|
36
|
+
meetings). Check the `**Status:**` field to distinguish active from historical
|
|
37
|
+
reqs
|
|
33
38
|
|
|
34
39
|
## Outputs
|
|
35
40
|
|
|
@@ -45,8 +50,8 @@ knowledge base.**
|
|
|
45
50
|
When the user asks to prep for a meeting:
|
|
46
51
|
|
|
47
52
|
1. **STOP** — Do not create a generic brief
|
|
48
|
-
2. **SEARCH** — Look up each attendee: `rg -l "Attendee Name"
|
|
49
|
-
3. **READ** — Read their notes: `cat "
|
|
53
|
+
2. **SEARCH** — Look up each attendee: `rg -l "Attendee Name" [0-9]-*/`
|
|
54
|
+
3. **READ** — Read their notes: `cat "3-Team/People/Attendee Name.md"`
|
|
50
55
|
4. **UNDERSTAND** — Extract role, organization, history, open items
|
|
51
56
|
5. **THEN BRIEF** — Create the meeting brief with this context
|
|
52
57
|
|
|
@@ -93,16 +98,16 @@ Extract: summary, start/end time, attendees (names and emails), description.
|
|
|
93
98
|
For each attendee:
|
|
94
99
|
|
|
95
100
|
```bash
|
|
96
|
-
rg -l "attendee_name"
|
|
97
|
-
rg -l "attendee_email"
|
|
98
|
-
cat "
|
|
99
|
-
cat "
|
|
100
|
-
rg -l "attendee_name"
|
|
101
|
+
rg -l "attendee_name" 3-Team/People/
|
|
102
|
+
rg -l "attendee_email" 3-Team/People/
|
|
103
|
+
cat "3-Team/People/Attendee Name.md"
|
|
104
|
+
cat "3-Team/Organizations/Their Company.md"
|
|
105
|
+
rg -l "attendee_name" 3-Team/Projects/
|
|
101
106
|
```
|
|
102
107
|
|
|
103
108
|
Extract: role/title, company, key facts, previous interactions, open items.
|
|
104
109
|
|
|
105
|
-
Also check `
|
|
110
|
+
Also check `3-Team/Priorities/` for context relevant to the meeting topic.
|
|
106
111
|
For example, if the meeting is about hiring, surface the relevant hiring
|
|
107
112
|
Priority's status and progress.
|
|
108
113
|
|
|
@@ -143,11 +148,11 @@ Suggested Talking Points
|
|
|
143
148
|
|
|
144
149
|
When you prepare for interview meetings (title contains "Interview",
|
|
145
150
|
"Screening", "Decomposition", "Panel", or a candidate name from
|
|
146
|
-
`
|
|
151
|
+
`2-Confidential/Candidates/`):
|
|
147
152
|
|
|
148
|
-
1. **Read the candidate brief:** `
|
|
153
|
+
1. **Read the candidate brief:** `2-Confidential/Candidates/{Name}/brief.md`
|
|
149
154
|
2. **Read the Role file:** Look up the `Req` field and read the corresponding
|
|
150
|
-
`
|
|
155
|
+
`2-Confidential/Roles/*.md` file. Check the `**Status:**` field for context.
|
|
151
156
|
3. **Include in the briefing:**
|
|
152
157
|
- Candidate's current status, skills, and screening recommendation
|
|
153
158
|
- Role context: hiring manager, domain lead, remaining positions
|
|
@@ -6,6 +6,9 @@ compatibility: Requires macOS filesystem access
|
|
|
6
6
|
|
|
7
7
|
# Organize Files
|
|
8
8
|
|
|
9
|
+
Write tier: none (delegates graph writes to extract-entities)
|
|
10
|
+
Frontmatter: none
|
|
11
|
+
|
|
9
12
|
Organize, tidy up, and find files in `~/Desktop/` and `~/Downloads/`. Always
|
|
10
13
|
preview changes before you act. Never delete without explicit confirmation.
|
|
11
14
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: person-identify
|
|
3
|
-
description: Look up the current user's identity (real name, company, job title, department, email, employee ID, and
|
|
3
|
+
description: Look up the current user's identity (real name, company, job title, department, email, employee ID, manager, and direct reports) from the corporate directory through LDAP. The bind uses the existing Kerberos ticket. Captures the user's org edges — manager and direct reports — so the KB can resolve who "our team" is. Use to establish who the knowledge base belongs to, when CLAUDE.md needs the user's identity, or when the user asks "who am I" / for their own directory record. To look up someone *other* than the current user, use the sibling `person-lookup` skill instead.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Person Identify
|
|
7
7
|
|
|
8
|
+
Write tier: none (cache only)
|
|
9
|
+
Frontmatter: none
|
|
10
|
+
|
|
8
11
|
Resolve the current user's identity from the corporate Active Directory over
|
|
9
12
|
LDAP. This is the canonical way to establish **who the knowledge base belongs
|
|
10
13
|
to**. It replaces any static identity file. Results reflect the live
|
|
@@ -12,9 +15,10 @@ directory, so the skill picks up a job change or a reorg automatically.
|
|
|
12
15
|
|
|
13
16
|
## Trigger
|
|
14
17
|
|
|
15
|
-
- CLAUDE.md (or another skill) needs the current user's identity
|
|
18
|
+
- CLAUDE.md (or another skill) needs the current user's identity, or needs to
|
|
19
|
+
resolve who "our team" is.
|
|
16
20
|
- The user asks "who am I", for their own directory record, or for their
|
|
17
|
-
employee ID, title, department, or
|
|
21
|
+
employee ID, title, department, manager, or direct reports.
|
|
18
22
|
|
|
19
23
|
## Prerequisites
|
|
20
24
|
|
|
@@ -33,8 +37,8 @@ password. The bind uses SASL/GSSAPI against the existing ticket.
|
|
|
33
37
|
bash .claude/skills/person-identify/scripts/identify.sh
|
|
34
38
|
```
|
|
35
39
|
|
|
36
|
-
This prints the user's directory record, resolves the manager
|
|
37
|
-
writes the result to the identity cache (below).
|
|
40
|
+
This prints the user's directory record, resolves the manager and each direct
|
|
41
|
+
report to a name, and writes the result to the identity cache (below).
|
|
38
42
|
|
|
39
43
|
## Identity cache
|
|
40
44
|
|
|
@@ -42,14 +46,25 @@ The script writes `~/.cache/fit/outpost/state/identity.md`. That file is the
|
|
|
42
46
|
**canonical identity source** for the rest of the knowledge base. It replaces
|
|
43
47
|
the old static `USER.md`. It is auto-generated markdown with `Name`, `Email`,
|
|
44
48
|
and `Domain` fields (plus title, department, company, employee ID, office, and
|
|
45
|
-
|
|
49
|
+
the user's two org edges — `Manager` and `Direct reports`):
|
|
46
50
|
|
|
47
51
|
```markdown
|
|
48
52
|
- **Name:** Jane Doe
|
|
49
53
|
- **Email:** jane.doe@example.com
|
|
50
54
|
- **Domain:** example.com
|
|
55
|
+
- **Manager:** Roe, Richard
|
|
56
|
+
- **Direct reports:**
|
|
57
|
+
- Chen, Sarah
|
|
58
|
+
- Okafor, Ada
|
|
51
59
|
```
|
|
52
60
|
|
|
61
|
+
The **Manager** and **Direct reports** edges let the KB resolve who "our team"
|
|
62
|
+
is — see the `## User Identity & Team` section of `CLAUDE.md` for the rule.
|
|
63
|
+
`Direct reports` is always written: a list when the user manages people, or
|
|
64
|
+
the explicit sentinel `- **Direct reports:** none` for an individual
|
|
65
|
+
contributor. Reports are name-sorted so re-runs of the synced cache diff
|
|
66
|
+
cleanly.
|
|
67
|
+
|
|
53
68
|
Other skills (e.g. `extract-entities`, `anarlog-process`, `req-track`,
|
|
54
69
|
`req-workday`, `candidate-report`, `sync-teams`) read this file for the user's
|
|
55
70
|
name/email/domain. They use it for self-exclusion and author attribution. They
|
|
@@ -74,12 +89,29 @@ dc=$(dig +short SRV "_ldap._tcp.dc._msdcs.$dom" | awk 'NR==1{print $4}' | sed 's
|
|
|
74
89
|
|
|
75
90
|
# 4. Look up the current user (GSSAPI = existing ticket, no password)
|
|
76
91
|
ldapsearch -Y GSSAPI -LLL -o ldif-wrap=no -H "ldap://$dc" -b "$base" \
|
|
77
|
-
"(sAMAccountName=$user)" displayName company title department employeeID mail
|
|
92
|
+
"(sAMAccountName=$user)" displayName company title department employeeID mail \
|
|
93
|
+
manager directReports
|
|
78
94
|
```
|
|
79
95
|
|
|
80
|
-
|
|
81
|
-
resolves
|
|
82
|
-
|
|
96
|
+
Both org edges come back as DNs that may live in another domain. So the script
|
|
97
|
+
resolves them against the **Global Catalog** (port 3268, forest-wide) through
|
|
98
|
+
one shared `dn_name` helper. `manager` is single-valued. `directReports` is
|
|
99
|
+
the multi-valued back-link of the same edge, so the script pulls every value
|
|
100
|
+
(not just the first), resolves each to a name, and sorts the list. The
|
|
101
|
+
resolver decodes the base64 `displayName:: …` form (used for accented names)
|
|
102
|
+
and retries past the directory's occasional attribute-less response under
|
|
103
|
+
load.
|
|
104
|
+
|
|
105
|
+
**Inactive reports are filtered from the roster.** AD does not clear the
|
|
106
|
+
`manager` edge when an account is offboarded — it just moves the account to a
|
|
107
|
+
deprovisioned OU — so the user's `directReports` back-link keeps counting
|
|
108
|
+
ex-reports as phantom team members. Reports resolve through `dn_report`, which
|
|
109
|
+
drops a DN when either (1) it sits under the obsolete OU
|
|
110
|
+
(`INACTIVE_OU_PATTERN`, a config knob at the top of the script; `OU=Obsolete`
|
|
111
|
+
by default, empty to disable), or (2) the account is disabled in AD —
|
|
112
|
+
`userAccountControl` bit `0x2` (`ACCOUNTDISABLE`), fetched in the same GC
|
|
113
|
+
query as the name. The manager edge stays unfiltered: a disabled manager is a
|
|
114
|
+
signal worth surfacing, not hiding.
|
|
83
115
|
|
|
84
116
|
## Output
|
|
85
117
|
|
|
@@ -95,6 +127,8 @@ Key attributes returned (names per Active Directory schema):
|
|
|
95
127
|
| `mail` | Email address |
|
|
96
128
|
| `physicalDeliveryOfficeName`| Office / location |
|
|
97
129
|
| `manager` | Manager (DN → resolved to name) |
|
|
130
|
+
| `directReports` | Direct reports (multi-valued DN back-link → each resolved to a name; offboarded/disabled accounts filtered out) |
|
|
131
|
+
| `userAccountControl` | Fetched per report to drop disabled accounts (bit `0x2`) |
|
|
98
132
|
|
|
99
133
|
## Notes
|
|
100
134
|
|
|
@@ -102,5 +136,19 @@ Key attributes returned (names per Active Directory schema):
|
|
|
102
136
|
free-text input (email or name). It searches the Global Catalog forest-wide
|
|
103
137
|
(`ldap://$dc:3268 -b ''`). It handles multiple matches. It does **not** touch
|
|
104
138
|
the identity cache.
|
|
139
|
+
- `directReports` is the back-link stored on the user's own home-domain
|
|
140
|
+
record, so a report whose account lives in *another* forest domain may be
|
|
141
|
+
omitted. For a single-domain manager (the common case) it is complete. If
|
|
142
|
+
cross-domain reports matter, swap it for a forest-wide reverse search over
|
|
143
|
+
the GC — `(manager=<userDN>)`, where the user's DN is the `dn:` line of the
|
|
144
|
+
fetched record — feeding the same `dn_report` resolver.
|
|
145
|
+
- **Offboarded reports linger in AD.** The `directReports` back-link keeps
|
|
146
|
+
counting a report after they leave, because offboarding moves the account to
|
|
147
|
+
a deprovisioned OU without clearing its `manager` edge. `dn_report` filters
|
|
148
|
+
these out (obsolete-OU DN, or a disabled `userAccountControl`). Tune
|
|
149
|
+
`INACTIVE_OU_PATTERN` at the top of `scripts/identify.sh` to your
|
|
150
|
+
directory's convention — it mirrors `person-lookup`'s `VENDOR_OU_PATTERN`
|
|
151
|
+
knob. The real fix is upstream (clear the manager edge at offboarding); the
|
|
152
|
+
filter shields the KB until then.
|
|
105
153
|
- Active Directory is not required. The same `ldapsearch -Y GSSAPI` shape works
|
|
106
154
|
against any Kerberos-backed LDAP directory. Only the attribute names differ.
|
|
@@ -12,6 +12,15 @@ set -u
|
|
|
12
12
|
|
|
13
13
|
CACHE="$HOME/.cache/fit/outpost/state/identity.md"
|
|
14
14
|
|
|
15
|
+
# Org-specific: substring of the DN's OU under which offboarded/deprovisioned
|
|
16
|
+
# accounts are parked. AD keeps the `manager` edge on such accounts when they
|
|
17
|
+
# are moved here at offboarding, so the user's `directReports` back-link still
|
|
18
|
+
# counts them — surfacing ex-reports as phantom team members. Reports whose DN
|
|
19
|
+
# matches this pattern are dropped. Set to your directory's convention (empty
|
|
20
|
+
# disables the OU filter; disabled-account filtering via userAccountControl
|
|
21
|
+
# still applies). `OU=Obsolete` is a common AD default.
|
|
22
|
+
INACTIVE_OU_PATTERN="OU=Obsolete"
|
|
23
|
+
|
|
15
24
|
# 1. Take the user principal from the Kerberos ticket: USER@REALM.EXAMPLE.COM
|
|
16
25
|
princ=$(klist 2>/dev/null | sed -n 's/.*[Pp]rincipal: *//p' | head -1)
|
|
17
26
|
if [ -z "$princ" ]; then
|
|
@@ -34,15 +43,24 @@ if [ -z "$dc" ]; then
|
|
|
34
43
|
fi
|
|
35
44
|
|
|
36
45
|
# 4. Look up the current user's record (GSSAPI = use the existing ticket, no password)
|
|
37
|
-
attrs="displayName givenName sn company title department employeeID mail telephoneNumber physicalDeliveryOfficeName manager"
|
|
46
|
+
attrs="displayName givenName sn company title department employeeID mail telephoneNumber physicalDeliveryOfficeName manager directReports"
|
|
38
47
|
rec=$(ldapsearch -Y GSSAPI -LLL -o ldif-wrap=no -H "ldap://$dc" -b "$base" "(sAMAccountName=$user)" $attrs 2>/dev/null)
|
|
39
48
|
if [ -z "$rec" ]; then
|
|
40
49
|
echo "No directory record found for '$user' under $base on $dc." >&2
|
|
41
50
|
exit 1
|
|
42
51
|
fi
|
|
43
52
|
|
|
44
|
-
# Pull a single attribute value out of the LDIF record.
|
|
45
|
-
|
|
53
|
+
# Pull a single attribute value out of the LDIF record. Decode the base64
|
|
54
|
+
# `attr:: <b64>` form that LDIF uses for any non-ASCII value (accented names,
|
|
55
|
+
# and DNs whose CN is accented — e.g. the manager attribute).
|
|
56
|
+
field() { # $1=attr
|
|
57
|
+
local attr="$1" line
|
|
58
|
+
line=$(printf '%s\n' "$rec" | grep -m1 -E "^$attr:: ?|^$attr: ")
|
|
59
|
+
case "$line" in
|
|
60
|
+
"$attr:: "*) printf '%s' "${line#"$attr":: }" | base64 -D 2>/dev/null ;;
|
|
61
|
+
"$attr: "*) printf '%s' "${line#"$attr": }" ;;
|
|
62
|
+
esac
|
|
63
|
+
}
|
|
46
64
|
|
|
47
65
|
disp=$(field displayName); gn=$(field givenName); sn=$(field sn)
|
|
48
66
|
company=$(field company); title=$(field title); dept=$(field department)
|
|
@@ -54,14 +72,85 @@ name="$gn $sn"; name=$(printf '%s' "$name" | sed 's/^ *//;s/ *$//')
|
|
|
54
72
|
[ -z "$name" ] && name="$disp"
|
|
55
73
|
domain=$(printf '%s' "$mail" | sed 's/.*@//' | tr '[:upper:]' '[:lower:]')
|
|
56
74
|
|
|
57
|
-
# 5. Resolve the
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
# 5. Resolve the user's org edges — the manager (up) and the direct reports
|
|
76
|
+
# (down) — to names. Both are DNs that may live in another domain, so
|
|
77
|
+
# resolve against the Global Catalog (port 3268). It is forest-wide.
|
|
78
|
+
gc="ldap://$dc:3268"
|
|
79
|
+
|
|
80
|
+
# One shared DN -> displayName resolver. Decodes the base64 `displayName:: <b64>`
|
|
81
|
+
# form (accented names are encoded that way) and retries past the directory's
|
|
82
|
+
# occasional attribute-less response under load, mirroring person-lookup.
|
|
83
|
+
dn_name() { # $1=DN -> displayName ("" if unresolvable)
|
|
84
|
+
local dn="$1" line tries=0
|
|
85
|
+
[ -z "$dn" ] && return 0
|
|
86
|
+
while [ "$tries" -lt 4 ]; do
|
|
87
|
+
line=$(ldapsearch -Y GSSAPI -LLL -o ldif-wrap=no -H "$gc" -b "$dn" -s base \
|
|
88
|
+
displayName 2>/dev/null | grep -m1 -E '^displayName:: ?|^displayName: ')
|
|
89
|
+
case "$line" in
|
|
90
|
+
"displayName:: "*) printf '%s' "${line#displayName:: }" | base64 -D 2>/dev/null; return 0 ;;
|
|
91
|
+
"displayName: "*) printf '%s' "${line#displayName: }"; return 0 ;;
|
|
92
|
+
esac
|
|
93
|
+
tries=$((tries + 1)); sleep "$tries"
|
|
94
|
+
done
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Resolve a direct-report DN to a name, but ONLY if the account is still active.
|
|
98
|
+
# A report is inactive — and therefore dropped from the team roster — when either:
|
|
99
|
+
# 1. its DN sits under the obsolete/deprovisioned OU ($INACTIVE_OU_PATTERN), or
|
|
100
|
+
# 2. its account is disabled in AD (userAccountControl bit 0x2, ACCOUNTDISABLE).
|
|
101
|
+
# Offboarding leaves the `manager` edge dangling, so without this filter ex-reports
|
|
102
|
+
# linger in the roster forever. Fetches displayName + userAccountControl together,
|
|
103
|
+
# reusing dn_name's base64 decode and retry-under-load behaviour. Prints the name,
|
|
104
|
+
# or nothing to skip the report.
|
|
105
|
+
dn_report() { # $1=DN -> displayName if active, else ""
|
|
106
|
+
local dn="$1" out line uac tries=0
|
|
107
|
+
[ -z "$dn" ] && return 0
|
|
108
|
+
# (1) Obsolete-OU filter — case-insensitive substring match on the DN.
|
|
109
|
+
if [ -n "$INACTIVE_OU_PATTERN" ]; then
|
|
110
|
+
if printf '%s' "$dn" | grep -qiF "$INACTIVE_OU_PATTERN"; then return 0; fi
|
|
111
|
+
fi
|
|
112
|
+
while [ "$tries" -lt 4 ]; do
|
|
113
|
+
out=$(ldapsearch -Y GSSAPI -LLL -o ldif-wrap=no -H "$gc" -b "$dn" -s base \
|
|
114
|
+
displayName userAccountControl 2>/dev/null)
|
|
115
|
+
line=$(printf '%s\n' "$out" | grep -m1 -E '^displayName:: ?|^displayName: ')
|
|
116
|
+
if [ -n "$line" ]; then
|
|
117
|
+
# (2) Disabled-account filter. userAccountControl is a GC-replicated
|
|
118
|
+
# attribute; if absent (older DC / partial reply) we simply don't filter.
|
|
119
|
+
uac=$(printf '%s\n' "$out" | sed -n 's/^userAccountControl: *//p' | head -1)
|
|
120
|
+
case "$uac" in
|
|
121
|
+
''|*[!0-9]*) : ;; # missing/non-numeric -> can't judge
|
|
122
|
+
*) [ $((uac & 2)) -ne 0 ] && return 0 ;; # ACCOUNTDISABLE set -> skip
|
|
123
|
+
esac
|
|
124
|
+
case "$line" in
|
|
125
|
+
"displayName:: "*) printf '%s' "${line#displayName:: }" | base64 -D 2>/dev/null; return 0 ;;
|
|
126
|
+
"displayName: "*) printf '%s' "${line#displayName: }"; return 0 ;;
|
|
127
|
+
esac
|
|
128
|
+
fi
|
|
129
|
+
tries=$((tries + 1)); sleep "$tries"
|
|
130
|
+
done
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Up-edge: the manager (single-valued DN on the user's own record).
|
|
134
|
+
mgr_name=$(dn_name "$(field manager)")
|
|
135
|
+
|
|
136
|
+
# Down-edges: direct reports — a multi-valued back-link on the user's OWN record.
|
|
137
|
+
# field() keeps only the first value, so pull EVERY value: emit one report DN per
|
|
138
|
+
# line (decoding the base64 `directReports:: <b64>` form), resolve each to a name
|
|
139
|
+
# via dn_report (which drops offboarded/disabled accounts), and sort for stable,
|
|
140
|
+
# diff-friendly cache output. Kept in a function because a `case` inside `$(...)`
|
|
141
|
+
# trips the bash 3.2 parser that macOS ships.
|
|
142
|
+
resolve_reports() {
|
|
143
|
+
printf '%s\n' "$rec" | while IFS= read -r line; do
|
|
144
|
+
case "$line" in
|
|
145
|
+
"directReports:: "*) printf '%s' "${line#directReports:: }" | base64 -D 2>/dev/null; echo ;;
|
|
146
|
+
"directReports: "*) printf '%s\n' "${line#directReports: }" ;;
|
|
147
|
+
esac
|
|
148
|
+
done | while IFS= read -r dn; do
|
|
149
|
+
[ -n "$dn" ] || continue
|
|
150
|
+
n=$(dn_report "$dn"); [ -n "$n" ] && printf '%s\n' "$n"
|
|
151
|
+
done | sort
|
|
152
|
+
}
|
|
153
|
+
reports=$(resolve_reports)
|
|
65
154
|
|
|
66
155
|
# 6. Write the cache. It keeps the Name/Email/Domain shape other skills already
|
|
67
156
|
# parse, plus extra fields. The script generates it. Never edit it by hand.
|
|
@@ -81,6 +170,16 @@ mkdir -p "$(dirname "$CACHE")"
|
|
|
81
170
|
echo "- **Employee ID:** $empid"
|
|
82
171
|
echo "- **Office:** $office"
|
|
83
172
|
[ -n "$mgr_name" ] && echo "- **Manager:** $mgr_name"
|
|
173
|
+
# Always emit Direct reports so the "our team" rule in CLAUDE.md can branch
|
|
174
|
+
# explicitly: a list => the user manages people (the team is in this cache);
|
|
175
|
+
# `none` => an individual contributor (peers resolved on demand from the
|
|
176
|
+
# manager). An omitted line would be indistinguishable from "not yet resolved".
|
|
177
|
+
if [ -n "$reports" ]; then
|
|
178
|
+
echo "- **Direct reports:**"
|
|
179
|
+
printf '%s\n' "$reports" | while IFS= read -r r; do echo " - $r"; done
|
|
180
|
+
else
|
|
181
|
+
echo "- **Direct reports:** none"
|
|
182
|
+
fi
|
|
84
183
|
} > "$CACHE"
|
|
85
184
|
|
|
86
185
|
# 7. Show the result.
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: person-lookup
|
|
3
|
-
description: Look up ANY person in the corporate directory from free-text input (an email address, or a first / last / full name) and return their record: real name, title, department, company, email, employee ID, office, and
|
|
3
|
+
description: Look up ANY person in the corporate directory from free-text input (an email address, or a first / last / full name) and return their record: real name, title, department, company, email, employee ID, office, manager, and direct reports. Searches the Global Catalog forest-wide through LDAP. The bind uses the existing Kerberos ticket. Use when the user asks "who is X", needs someone's title / department / manager / reports / email, wants to disambiguate a name, or needs to resolve a team around a person (a person's peers = their manager's other reports). For the *current* user's own identity, use the sibling `person-identify` skill instead.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Person Lookup
|
|
7
7
|
|
|
8
|
+
Write tier: none (cache only)
|
|
9
|
+
Frontmatter: none
|
|
10
|
+
|
|
8
11
|
Resolve **any** person in the corporate Active Directory from natural-language
|
|
9
12
|
input. The input is an email, a login, or any part of a name. This skill is the
|
|
10
13
|
sibling of `person-identify`, and it aims at *other* people. It searches the
|
|
@@ -56,8 +59,9 @@ The argument is free text: an email, a full name, or just a surname.
|
|
|
56
59
|
nothing, fall back to a substring search on `mail` / `displayName` /
|
|
57
60
|
`proxyAddresses`.
|
|
58
61
|
4. **Resolve per match.** For a single hit, print the full record and resolve
|
|
59
|
-
the `manager` DN
|
|
60
|
-
disambiguation list and suggest a
|
|
62
|
+
both org edges — the `manager` DN and each `directReports` DN — to names.
|
|
63
|
+
For several hits, print a compact disambiguation list and suggest a
|
|
64
|
+
narrower search by email (org edges resolve only for an exact, single hit).
|
|
61
65
|
|
|
62
66
|
## Output
|
|
63
67
|
|
|
@@ -72,6 +76,7 @@ The argument is free text: an email, a full name, or just a surname.
|
|
|
72
76
|
| Employee ID | `employeeID` |
|
|
73
77
|
| Phone / Office | `telephoneNumber` / `physicalDeliveryOfficeName` |
|
|
74
78
|
| Manager | `manager` (DN → resolved to a name) |
|
|
79
|
+
| Direct reports | `directReports` (multi-valued DN back-link → each resolved to a name; single hit only, shown when non-empty) |
|
|
75
80
|
| DN | distinguished name (region + OU, useful for disambiguation) |
|
|
76
81
|
|
|
77
82
|
## Notes
|
|
@@ -53,7 +53,7 @@ fi
|
|
|
53
53
|
|
|
54
54
|
# Global Catalog: forest-wide, base "" spans every domain.
|
|
55
55
|
GC="ldap://$dc:3268"
|
|
56
|
-
ATTRS="displayName givenName sn company title department employeeID mail telephoneNumber physicalDeliveryOfficeName objectClass manager"
|
|
56
|
+
ATTRS="displayName givenName sn company title department employeeID mail telephoneNumber physicalDeliveryOfficeName objectClass manager directReports"
|
|
57
57
|
MAX_SHOW=12 # cap detailed output for very broad name matches
|
|
58
58
|
|
|
59
59
|
# Pull one attribute out of an LDIF record (passed as $2). Decode base64 (::).
|
|
@@ -90,6 +90,23 @@ name_of_dn() { # $1=dn
|
|
|
90
90
|
field displayName "$(fetch "$1")"
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
# List a person's direct reports — the multi-valued `directReports` back-link
|
|
94
|
+
# on their record — as one resolved name per line, sorted. $1 = the person's
|
|
95
|
+
# fetched LDIF record. field() keeps only the first value, so pull EVERY value
|
|
96
|
+
# here, decoding the base64 `directReports:: <b64>` form. Kept in a function
|
|
97
|
+
# because a `case` inside $(...) trips the bash 3.2 parser that macOS ships.
|
|
98
|
+
reports_of() { # $1=record
|
|
99
|
+
printf '%s\n' "$1" | while IFS= read -r line; do
|
|
100
|
+
case "$line" in
|
|
101
|
+
"directReports:: "*) printf '%s' "${line#directReports:: }" | base64 -D 2>/dev/null; echo ;;
|
|
102
|
+
"directReports: "*) printf '%s\n' "${line#directReports: }" ;;
|
|
103
|
+
esac
|
|
104
|
+
done | while IFS= read -r dn; do
|
|
105
|
+
[ -n "$dn" ] || continue
|
|
106
|
+
n=$(name_of_dn "$dn"); [ -n "$n" ] && printf '%s\n' "$n"
|
|
107
|
+
done | sort
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
# Classify an entry: internal employee, external contact, or vendor account.
|
|
94
111
|
kind_of() { # $1=record $2=dn
|
|
95
112
|
local oc; oc=$(printf '%s\n' "$1" | grep -i '^objectClass:' | tr 'A-Z' 'a-z')
|
|
@@ -136,6 +153,15 @@ if [ "${#DNS[@]}" -eq 1 ]; then
|
|
|
136
153
|
echo "- **Phone:** $(field telephoneNumber "$rec")"
|
|
137
154
|
echo "- **Office:** $(field physicalDeliveryOfficeName "$rec")"
|
|
138
155
|
[ -n "$mgr" ] && echo "- **Manager:** $mgr"
|
|
156
|
+
# Direct reports — the down-edge, symmetric to person-identify. Lets a
|
|
157
|
+
# caller resolve a team from any person: an IC's peers are their manager's
|
|
158
|
+
# other reports (look up the manager, read this list). Printed only when
|
|
159
|
+
# non-empty.
|
|
160
|
+
reports=$(reports_of "$rec")
|
|
161
|
+
if [ -n "$reports" ]; then
|
|
162
|
+
echo "- **Direct reports:**"
|
|
163
|
+
printf '%s\n' "$reports" | while IFS= read -r r; do echo " - $r"; done
|
|
164
|
+
fi
|
|
139
165
|
echo "- **DN:** ${DNS[0]}"
|
|
140
166
|
exit 0
|
|
141
167
|
fi
|
|
@@ -9,6 +9,9 @@ description: >
|
|
|
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
17
|
higher-fidelity than CV evidence. It confirms or contradicts the screening
|
|
@@ -22,7 +25,7 @@ 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
31
|
- The concierge agent processes an Anarlog interview recording.
|
|
@@ -30,22 +33,23 @@ This is **Stage 2** of the three-stage hiring pipeline:
|
|
|
30
33
|
## Prerequisites
|
|
31
34
|
|
|
32
35
|
- `fit-pathway` CLI installed.
|
|
33
|
-
- At least one transcript in `
|
|
36
|
+
- At least one transcript in `2-Confidential/Candidates/{Name}/`.
|
|
34
37
|
- `screening.md` should exist. If it is missing, run `req-screen` first.
|
|
35
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">
|
|
@@ -55,6 +59,8 @@ evidence">
|
|
|
55
59
|
- [ ] Assess the level with standard progression criteria. Ignore gut feel.
|
|
56
60
|
- [ ] Attribute interviewer observations by name.
|
|
57
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.
|
|
58
64
|
- [ ] Write the panel brief (if created) for non-technical readers. Tie the
|
|
59
65
|
suggested questions to the remaining gaps.
|
|
60
66
|
- [ ] Update the brief's Pipeline section, links, and Status.
|
|
@@ -105,7 +111,7 @@ 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
116
|
only skills with new evidence. Do not repeat the full matrix.
|
|
111
117
|
|
|
@@ -118,20 +124,22 @@ candidates:
|
|
|
118
124
|
bunx fit-pathway interview {discipline} {level} --track={track}
|
|
119
125
|
```
|
|
120
126
|
|
|
121
|
-
Save `
|
|
127
|
+
Save `2-Confidential/Candidates/{Name}/panel.md` with
|
|
122
128
|
[references/panel-template.md](references/panel-template.md). The audience is
|
|
123
129
|
next-stage interviewers, often non-engineers. Explain without jargon. Tie the
|
|
124
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,9 +1,14 @@
|
|
|
1
1
|
# Interview Assessment Template
|
|
2
2
|
|
|
3
3
|
Reference template for `req-assess` Step 6. Save to
|
|
4
|
-
`
|
|
4
|
+
`2-Confidential/Candidates/{Name}/interview-{date}.md`. Include only skills the
|
|
5
5
|
interview produced new evidence for. Do not repeat the full matrix.
|
|
6
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.
|
|
11
|
+
|
|
7
12
|
```markdown
|
|
8
13
|
# Interview Assessment — {Full Name}
|
|
9
14
|
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# Panel Brief Template
|
|
2
2
|
|
|
3
3
|
Reference template for `req-assess` Step 7. Save to
|
|
4
|
-
`
|
|
4
|
+
`2-Confidential/Candidates/{Name}/panel.md`. The audience is **next-stage
|
|
5
5
|
interviewers, often non-engineers**. Explain without jargon. Focus on
|
|
6
6
|
behaviours and scope.
|
|
7
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.
|
|
11
|
+
|
|
8
12
|
```markdown
|
|
9
13
|
# Panel Brief — {Full Name}
|
|
10
14
|
## {Role Title} — {Level}
|