| name | pm-team |
| description | Use this skill when the user says "sync the team", "who's on the team", "show the roster", "set my skills", "/pm-team", or asks about team members, member skills, or assignee recommendations. Manages the team roster — Linear-synced identity (gitignored cache) paired with a curated checked-in team.md of handle + skills + owns + aliases — merged at read time by handle. The roster is the data source for pm-organize assignee recommendations and pm-notes speaker matching. |
| version | 0.44.4 |
PM Team
The team roster is split across two storage locations that are merged at read time by handle.
- Curated file (
<repo>/.claude/pm/team.md, checked in): non-PII — GitHub/Linear handle, skills, proficiency buckets, owned workstreams, and display aliases. This file is safe to commit.
- Synced cache (
~/.claude/project-manager/cache/<slug>/team.json, gitignored): identity pulled from Linear — name, status, and the userId used for assignee mutations. This file is never committed.
The merge key is handle (case-insensitive). Members present in both halves are unified; members present only in the cache are surfaced with empty skill fields; curated handles with no cache match generate an unmatchedHandles warning.
Usage
/pm-team sync
/pm-team show
/pm-team edit @handle
- sync — pull current workspace members from Linear and write them into the per-slug cache.
- show — merge the curated file with the cache and render a table of every member with warnings for any mismatches.
- edit @handle — open (or create) the curated entry for a handle, update its skill/owns/aliases bullets from operator input, preview the diff, and confirm before writing.
Storage
Curated file — <repo>/.claude/pm/team.md
Checked in alongside project.json and conventions.md. Contains only non-PII fields:
# Team
## @scoussens
- skills: frontend, auth, billing
- owns: ws:billing, src/auth
- aliases: Seth, SC
## @jdoe
- strong: postgres, migrations
- familiar: frontend
- owns: ws:data
- aliases: John, JD
Each ## @handle section opens a member entry. Bullet lines - <field>: <comma-separated list> populate the field. Recognised fields:
| Field | Meaning |
|---|
skills | Flat skill list |
strong | Skills the member is strong in (coarse proficiency — pm-proficiency writes these) |
familiar | Skills the member is familiar with |
owns | Owned workstreams or path prefixes used for scoring by pm-organize |
aliases | Display names used by pm-notes speaker matching |
All fields are optional. A member block with no bullets is valid.
Never put email addresses in team.md. Email is an identity field that belongs only in the synced cache. If a line in team.md contains what looks like an email, the skill must reject it.
Synced cache — ~/.claude/project-manager/cache/<slug>/team.json
Gitignored runtime state written by pm-cache.js team-sync. Schema:
{
"version": "<cache-version>",
"syncedAt": "<ISO-8601 timestamp>",
"members": [
{ "handle": "scoussens", "name": "Seth Coussens", "status": "active", "userId": "u1" }
]
}
The members array contains identity fields sourced from the tracker. This file is never committed; it lives in the user-home cache directory only.
sync
Resolve the active slug from project.json, then run:
node "$CLAUDE_PLUGIN_ROOT/hooks/bin/pm-cache.js" team-sync \
--slug <slug> --provider linear
This query runs against api.linear.app/graphql using LINEAR_API_KEY. It paginates through all workspace users and writes team.json into the per-slug cache directory.
Linear only. If the active project uses GitHub as its tracker, stop and tell the operator: team-sync is Linear-only; GitHub does not expose a workspace-members API via the plugin's current GraphQL transport.
After writing, print the member count and syncedAt timestamp so the operator can confirm the sync completed.
show
Read both halves and merge them via the roster library. Render a table of every member with columns: handle · name · status · skills · owns. Surface any unmatchedHandles (curated handles with no cache identity) and unmappedMembers (cache members not in team.md) as warnings below the table.
Use this one-liner to produce the merged JSON from Bash:
node -e '
const fs = require("fs");
const r = require(process.env.CLAUDE_PLUGIN_ROOT + "/hooks/lib/team-roster.js");
const cs = require(process.env.CLAUDE_PLUGIN_ROOT + "/hooks/lib/cache-store.js");
const slug = process.argv[1];
const teamMdPath = process.argv[2];
const curated = r.parseTeamMd(fs.existsSync(teamMdPath) ? fs.readFileSync(teamMdPath, "utf8") : "");
const cache = cs.readTeam(slug) || { members: [] };
const { roster, warnings } = r.mergeRoster(curated, cache.members);
process.stdout.write(JSON.stringify({ roster, warnings }, null, 2));
' "<slug>" "<repo>/.claude/pm/team.md"
Replace <slug> with the active project slug and <repo> with the repo root path (from git rev-parse --show-toplevel).
If the cache has never been synced (team.json missing), render a table from the curated file only and note that sync should be run to add identity data.
edit
- Resolve the curated file path:
<repo>/.claude/pm/team.md.
- If the file does not exist, offer to create it with an empty
# Team header.
- Locate the
## @<handle> section. If it is absent, append a new block at the end of the file.
- Parse the operator's requested changes — new skills, removed skills, updated owns or aliases.
- Preview the diff against the current file content.
- Ask the operator to confirm before writing.
- Write the updated block.
Reiterate before writing: never write email addresses into team.md. If the operator's input contains an email-shaped string, reject it and ask them to use only the non-PII fields (skills, owns, aliases).
What this skill does NOT do
- Does not estimate skills from history — that is pm-proficiency (a future skill). This skill only reads what the operator explicitly writes in team.md.
- Does not assign issues — that is pm-organize. This skill provides the roster data; pm-organize calls
rankAssignees to score candidates.
- Does not write identity fields (name, email, status, userId) into team.md — those fields live only in the synced cache. Edits to team.md are curated, non-PII information only.