---
name: cse-profile-builder
description: Build or refresh CSE expertise / body-of-work profiles as YAML, one per Customer Success Engineer, for account assignment and staffing. Pulls GitHub (postman-cs org), customer engagement history (Kepler calls + emails), and Jira CSE tickets, then computes a dynamic heavy-hitter score. Use when the user asks to build CSE profiles, refresh a CSE's body of work, score the CSEs, figure out who is strongest at what, or prep data for account assignment.
argument-hint: "[CSE name(s) to build/refresh | 'all' | window in days (default 60)]"
---

# CSE Profile Builder

## Compact MCP routing

Follow the shared [compact MCP routing contract](../../shared/compact-mcp-routing.md). Interactive facade tools are `cse_capabilities`, `cse_read`, `cse_apply`, `context_assemble`, and `cse_session_info`; named operations are capability ids. Call reads through `cse_read` with the capability id. This skill has no MCP write path, but every mutation would go through `cse_apply` twice: dry-run first, then the identical capability and arguments with `execute:true`, justification, and the returned `preview_digest`. Call `context_assemble` and `cse_session_info` directly when needed.


Produce one YAML profile per CSE describing their expertise, body of work, the
account teams they ride with, and a comparative heavy-hitter score. Profiles
drive account assignment and staffing calls. Output lands in
`.work-archive/account-assignment/<first>-<last>.yaml`; the schema, rubric, and
data-source recipes live in `.work-archive/account-assignment/_profile-spec.md`
(this skill writes that spec if it is missing or stale).

## Domain configuration

The daemon resolves the Jira cloud ID and project key; use `jira_*` capability reads with semantic names.

## MCP setup

Load the voice profile before any data pull:

```bash
source "${PLUGIN_ROOT:-${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$PWD/plugins/cse-tools}}}/.agents/shared/skill-bootstrap.sh"
```

This resolves `CSE_TOOLD_BIN` and `CSE_OPERATOR`. The daemon keeps keychain auth fresh on its own, so no mint step is needed. On a 401, call `cse_session_info({"force_refresh":true})` once and retry. A 403 means access is blocked or revoked: surface the named operator action and stop without retrying. GitHub uses the `gh` CLI authed against the `postman-cs` org (admin:org). Kepler and Jira go through the local `cse-tools` MCP surface.

- **Read strategy:** Follow [`.agents/shared/read-strategy.md`](../../shared/read-strategy.md). Jira CSE-ticket pulls use `jira_search`, and GitHub attribution follows [`.agents/shared/builder-evidence.md`](../../shared/builder-evidence.md) via direct `gh`; these own exact counts and per-repo authorship. The per-CSE Kepler/Slack slice may optionally use `context_assemble({"entity":{"kind":"operator","ref":"<name-or-email>"}})` for ranked communications context. Hydrate any verbatim passage directly with the `get_communications` capability before quoting it.
- **Tool frugality:** Follow [`.agents/shared/tool-frugality.md`](../../shared/tool-frugality.md). Bound Jira fields/results and communication discovery lists. Hydrate only the calls/emails used in a profile; set `content_max_chars` near 6000 unless a longer verbatim passage is required, and read either `body` or `transcript`, never both.

## Step 1: resolve identities

Build the population from the live CSE identity snapshot first
using capability `cse_orgchart_identity_snapshot` and arguments `{"refresh":true}`. Use the configured CSE roots and
their current descendants as the authoritative roster; resolve each returned person
through `slack_users` (`action="search", query=<work-email>`) to capture name, email,
Slack ID, and title. For `all`, process exactly this intersection. Do not manufacture
`firstname.lastname@postman.com` addresses. A requested person absent from the live
snapshot is rejected as missing/leaver until the operator resolves the roster. Confirm
the GitHub login separately in the GitHub gather per
[`.agents/shared/builder-evidence.md`](../../shared/builder-evidence.md)
(exact work-email commit match; display-name guesses are not).

## Step 2: gather three sources per CSE

These recipes are validated. Honor the Kepler gotcha or the engagement pull comes
back empty.

### GitHub (postman-cs, same N-day window)

Follow [`.agents/shared/builder-evidence.md`](../../shared/builder-evidence.md)
for roster attribution, authenticated `gh`, complete repo-list pagination,
no-clone, no org-wide commit search, customer vs internal scope, and degradation.
Profile-specific rules on top of that contract:

- Use the same N-day communications window for GitHub (profile default 60 unless
  the operator overrides).
- Resolve identity inside `postman-cs` only after an exact live-roster work-email
  commit match; never through global user search. Unmatched personal-email
  commits are history notes, not assignment-score evidence.
- Treat repo-name patterns as investigation signals only (customer:
  `Cust-<NAME>-*`, `<customer>-cse-*`, `af-*`/`av-*` demo fleets, or the customer
  name; internal: `Internal-*`, `kepler*`, `postman-*-action`). Do not fill
  customer, owner, purpose, or outcome from naming alone.
- Attribute work per repo via `gh api repos/postman-cs/<repo>/contributors` and
  `.../commits?author=<login>&since=<ISO>` after the shared complete repo-list
  pagination. Abort attribution completeness claims if any page fails.
- Read the top 3-5 READMEs for "what they built." Bound the sweep.

### Kepler engagement (calls + emails, last N days, default 60)
- DO NOT use `assignee_email` on Kepler tools for a CSE. That path resolves
  Salesforce GTM roles only (owner/csm/renewals/SE/adr) and returns empty.
- Use `list_communications` with `participant_email=<email>`, `type=call`,
  `start_date=<today minus N days>`, `limit=20`. `fields`/`projection` are
  largely ignored here, so expect ids back.
- Pull detail with `get_communications` (`include_content=full`,
  `include_participants=true`, `content_max_chars=6000`) only on the richest ids
  used in the profile. If calls are thin, run `type=email` too. A bounded
  `semantic_context_search` (`max_cards=8`, `snippet_chars=500`) fills topic gaps.
- Extract accounts touched, what they built per account, expertise shown, and the
  account team: split participants by email domain (postman.com = internal /
  AE / SE, customer domain = customer contacts).
- Granola is a supplemental transcript source when enrolled (`granola_*` via the
  local cse-tools MCP). It is frequently NOT registered in a given session. When
  the `granola_*` capabilities are unavailable, treat meeting notes as a Kepler/Gong-only
  read and record an explicit data gap rather than stalling.

### Jira (CSE Engagement Tracker)
- Use `jira_search` directly, JQL
  `project = CSE AND assignee = "<email>" ORDER BY updated DESC`, explicit
  fields `summary,status,issuetype,updated,created`, and a bounded `maxResults`.
  Assignee-by-email works directly.
- Status name is the engagement stage. issuetype separates "CSE Engagement"
  (full build) from "CSE Consult" (5-day scoping). Capture open engagements with
  stage, recently closed (Closed - Complete), and a throughput read.

## Step 3: orchestration

Fan out one orchestrator per CSE. Subagents in this harness cannot spawn their
own subagents, so each orchestrator runs all three gathers itself, then
synthesizes. Use `model=opus` for the orchestrators (gather + judgment in one
context). Run the orchestrators in parallel (one Agent message, one call per
CSE) when building more than one. The main loop writes/refreshes `_profile-spec.md`
first so every orchestrator reads the same schema and rubric.

If you prefer a cheaper split on a large roster, the main loop can instead spawn
`model=sonnet` gatherers directly (three per CSE), collect their findings, and
run one `model=opus` synthesis pass per CSE. Either shape produces the same YAML.

## Step 3.5: enrichment pass (recommended second pass)

The first build scores delivery and breadth well but undershoots
customer_influence and opportunity_creation, since the evidence for those tends
to live in call transcripts. Run a second pass per CSE to deep-mine the live
meeting surfaces and re-score, gated by an adversarial verifier that holds the
line on weak evidence:

- ENRICH (opus): scan bounded pages of calls in the window via
  `list_communications` with `participant_email`, stopping once additional pages
  stop yielding profile evidence. Pull `get_communications` only on the small
  set of ids cited in the profile, plus useful emails, and run a bounded
  `semantic_context_search` for the CSE's influence / expansion /
  exec-engagement / solo-architecture moments. Propose revised 0-10 components,
  each justified by a cited call/email id or repo.
- VERIFY (opus, separate lane): approve a component increase only when the cited
  evidence is specific. customer_influence needs a customer DECISION or behavior
  change, opportunity_creation needs a NEW use case or scope the CSE surfaced,
  delivery upgrades need confirmed shipped/closed outcomes. Revert weak claims.
- APPLY (sonnet): write only verified deltas into the YAML, recompute score and
  tier, append the cited evidence to the rationale, add `enriched_on` to the
  frontmatter, and append the Granola gap to `data_gaps`.

This pass is naturally a Workflow `pipeline(CSES, enrich, verify, apply)`.

## Step 4: synthesize + score

Fill the schema in `_profile-spec.md`. Compute the heavy-hitter score from
evidence only:

```
score = delivery*3 + customer_influence*2 + opportunity_creation*2
        + independent_architecture*2 + breadth*1     # each component 0-10, max 100
```

All scored evidence must fall inside the same N-day window (default 60). Tenure and
older body-of-work history are reported separately and never change assignment
components. Tiers: 80-100 Heavy Hitter, 55-79 Solid, 30-54 Ramping, <30 Ramping.
Write an evidence-backed rationale.

Honesty rules: unavailable or missing sources are `unknown` components with explicit
`data_gaps`; they are not zero and must not depress the score as negative evidence.
Publish `score: null` and `tier: Unknown` unless Jira is available and at least one
of GitHub or Kepler communications is available, with evidence for every scored
component. Never fabricate accounts, repos, or outcomes. If a source returned
nothing, record it as a gap. A Closed - Complete ticket with a working customer repo is
the strongest delivery evidence; in-flight builds are weaker.

## Step 5: write + verify

Write each profile to `.work-archive/account-assignment/<first>-<last>.yaml`.
Each document must carry `schema_version: 1`, `rubric_version: 1`,
`generated_at` (ISO-8601), `source_statuses`, and assignment fields `score`
(integer 0-100 or null), `tier`, `incumbencies` (list), `verticals` (list), and
`jira_counts: {open: <nonnegative integer>, closed: <nonnegative integer>}`.
After the syntax lint, run a structural validator that loads every generated file,
rejects missing/extra-type fields, verifies score/tier consistency and nonnegative
Jira counts, and verifies the minimum-source rule above. A `yaml.safe_load_all`
success alone is not validation. Report a short
table: name, tier, score, top domains, open/closed counts, major gaps.

## Voice / guard note

Profiles are internal engineering data: names, ISO dates, and the generation
provenance belong in the files. The voice guard's edit enforcement will flag
those as outbound-prose violations. Run with
`CSE_TOOLS_VOICE_EDIT_ENFORCE=warn` (in `.claude/settings.local.json` env) so the
guard logs but does not block internal-file writes. Outbound Slack / Jira /
Confluence prose stays fully enforced.

## Updating a profile later

Re-run this skill for the CSE(s) in question. The recipes and rubric live in
`_profile-spec.md`; edit that file to tune the schema or rubric, then rebuild so
every profile stays consistent. The `how_to_update` frontmatter in each YAML
points back here.
