From span
Queries Span for organizational observability across five domains: productivity (cycle time, throughput, review metrics), DORA (deployment frequency, lead time, MTTR, change failure rate), investment (effort allocation, workstreams, cost capitalization), AI transformation (AI code ratio, adoption, spend), and calendar (focus time, meeting load). Use when asked about engineering metrics, team velocity, pull requests, commits, deployments, epics, issues, sprints, investments, teams, or people.
How this skill is triggered — by the user, by Claude, or both
Slash command
/span:ask <your question about engineering metrics><your question about engineering metrics>This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
If the user provided arguments with this invocation, treat `$ARGUMENTS` as their query and proceed directly (still ask for clarification if the time range is missing).
If the user provided arguments with this invocation, treat $ARGUMENTS as their query and proceed directly (still ask for clarification if the time range is missing).
All bash commands in this skill should start by setting these two variables:
SPAN_DIR="${SPAN_CONFIG_DIR:-$HOME/.spanrc}"
SKILL_SCRIPTS="${CLAUDE_SKILL_DIR}/scripts"
Use $SKILL_SCRIPTS/query.sh, $SKILL_SCRIPTS/fetch-metadata.sh, etc. for all API operations. Always prefer the scripts over inline curl commands.
$SPAN_DIR/metadata-cache.json, or fetch if missing$SKILL_SCRIPTS/query.sh!${CLAUDE_SKILL_DIR}/scripts/check-config.sh
If the configuration state above shows "api version mismatch", warn the user that their installed skill version may be incompatible with the current Span API, and suggest they update the skill. You may still attempt queries, but results may be unreliable.
If the configuration state above shows "not configured", you MUST run the onboarding flow before doing anything else:
Create the configuration folder:
SPAN_DIR="${SPAN_CONFIG_DIR:-$HOME/.spanrc}"
mkdir -p "$SPAN_DIR"
Prompt user to add their token to $SPAN_DIR/auth.json:
{
"token": "your-personal-access-token"
}
Verify the token file exists by running $SKILL_SCRIPTS/check-config.sh (never read auth.json directly).
Fetch initial metadata:
$SKILL_SCRIPTS/fetch-metadata.sh
Confirm setup is complete and proceed with the user's original request.
The skill stores configuration in ~/.spanrc/ by default. Override with SPAN_CONFIG_DIR:
~/.spanrc/ # or $SPAN_CONFIG_DIR
├── auth.json # Span Personal Access Token (required)
└── metadata-cache.json # Cached API metadata (auto-generated)
NEVER read, print, or expose the Personal Access Token. The scripts in $SKILL_SCRIPTS/ handle authentication internally. You must NOT:
auth.json (never use the Read tool on this file)curl commands with the token — use the scripts insteadAlways ask clarifying questions rather than making assumptions.
If the user's query does not specify a time range, always ask before executing:
"What time period would you like me to query? For example: last 30 days, last quarter, or a specific date range. (If you'd like, I can default to the last 30 days.)"
Do NOT assume a time range. Even if the query sounds like it implies "recent" data, ask explicitly.
Examples requiring clarification:
Examples where time range is provided (no need to ask):
Also ask for clarification when:
API metadata (assets, fields, metrics) is cached at $SPAN_DIR/metadata-cache.json. If the cache exists, use it. If not, run $SKILL_SCRIPTS/fetch-metadata.sh. Only refresh when the user explicitly asks to reload.
Before executing any query, you MUST follow this process:
Before building any query, confirm:
If verification fails, prioritize partial fulfillment:
Only refuse entirely when the core metric/asset is completely unavailable.
ALWAYS inspect the cached metadata to find relevant metrics:
SPAN_DIR="${SPAN_CONFIG_DIR:-$HOME/.spanrc}"
cat "$SPAN_DIR/metadata-cache.json" | jq '.data[].metrics[]? | select(.label | test("<keyword>"; "i"))'
NEVER calculate or aggregate metrics yourself if a pre-built metric exists. The API provides pre-computed metrics that are more accurate and efficient than manual calculations.
NEVER guess or fabricate metric IDs. Always use the exact metricId UUIDs returned by the cached metadata. Hallucinated metric IDs will cause query failures.
Assets act as aggregation points (like SQL GROUP BY). Choose the appropriate asset:
| User asks about... | Query this asset | Filter by |
|---|---|---|
| Organization/company-wide (most metrics) | Team | Team.name = "<root team>" — the org root; see note below |
| Organization-wide deployments | Team (mode: "groups") | Team.groupPath DESCENDANT_OF the org root path — Team.name is leaf-only for deploys and does NOT work here; see "Deployment metrics across a team tree" |
| A specific team's metrics | Team | Team.name = "<team-name>" (use =, NOT DESCENDANT_OF) |
| People in a team and sub-teams | Person | Person.Teams.name with DESCENDANT_OF operator |
| Metrics across a team tree (most metrics) | Person | Person.Teams.name with DESCENDANT_OF + metrics on Person — does NOT work for deployment metrics, see below |
| Deployment metrics for a team + its sub-teams | Team (mode: "groups") | Team.groupPath DESCENDANT_OF the team's path — see "Deployment metrics across a team tree" below |
| A specific person | Person or PullRequest.Author | Email or name |
| A specific repository | Repository or PullRequest.Repository | Repository name |
| Individual PRs/commits | PullRequest or Commit | As needed |
| Breakdown by dimension (tenure, job level, etc.) | Use mode: "groups" | See api-reference.md |
IMPORTANT: For org-wide metrics, query Team filtered by the root team — the one whose Team.path has no . separator (just <hash>__<slug>). Find it by listing teams: {"select": ["Team.name", "Team.path"]}. Do NOT manually aggregate across repositories or people. Exception — deployments (and other team-attributed metrics): Team.name returns only that team's own deploys, so even the org-wide total must use the Team.groupPath DESCENDANT_OF roll-up anchored at the org root path — see "Deployment metrics across a team tree".
Common mistake — querying the wrong facade for "metric by X":
"PR count by repository" queried on the PullRequest facade returns one row per PR (each with count=1) — useless for aggregation. Query the Repository facade instead to get one row per repo with aggregated counts. Same applies to "by team" (use Team), "by person" (use Person). If "by X" refers to a dimension without its own facade (tenure, job level), use mode: "groups" instead.
= vs DESCENDANT_OF vs IN= — Use for metrics on a single team. Team.name = "Backend" returns that team's aggregated metrics. For most metrics this already rolls up the team's sub-teams (the Team facade aggregates hierarchically). Exception: deployment metrics are leaf-only — see "Deployment metrics across a team tree" below.DESCENDANT_OF — Use for roster discovery (listing people), and for the deployment roll-up below. For most Team metric queries do NOT use it — Team.name = "X" already includes sub-team data, so it would double-count.IN — Use to query a specific set of teams by name.Most metrics across a team tree: Query Person (not Team) with DESCENDANT_OF on Person.Teams.name, then attach metrics to Person. Individual-level metrics don't double-count.
Deployment metrics are attributed to teams, not people (a deploy has no author), which makes them behave differently from the rest:
Person + DESCENDANT_OF pattern above does not apply — there's no person to attach them to.Team.name = "X" returns only that team's own deploys; unlike other metrics it does not roll up sub-teams.To total a team and all of its sub-teams, roll up over the team's subtree with Team.groupPath (metadata describes it: "Filter with DESCENDANT_OF to scope a metric to a team and all of its subteams"):
{ "select": ["Team.name", "Team.path"] } — and read its Team.path (an ltree, e.g. "<org-id>__organization.platform"). For the org-wide total, use the root team's path (the one with no .).{
"mode": "groups",
"select": ["Team.groupPath"],
"metrics": [{ "metricId": "<deploy metric id from metadata>" }],
"filters": [{ "field": "Team.groupPath", "operator": "DESCENDANT_OF", "value": "<Team.path>" }],
"timeDimension": { "timeRange": { "startTime": "...", "endTime": "..." } }
}
You get back one row — the subtree total, de-duplicated across teams (add granularity for a per-period series). That de-duplication is the reason to use the roll-up rather than fetching per-team rows and adding them up.
Complete working example — org-wide deployments for a date range (the value is the org root Team.path; for a single team + its sub-teams, use that team's Team.path instead):
{
"mode": "groups",
"select": ["Team.groupPath"],
"metrics": [
{ "metricId": "c3377814-3805-4803-89ba-6f77bb3a8907", "responseKey": "deploys" }
],
"filters": [
{
"field": "Team.groupPath",
"operator": "DESCENDANT_OF",
"value": "991ab8636a4c495e9febbd48509e480f__organization"
}
],
"timeDimension": {
"timeRange": { "startTime": "2026-04-01", "endTime": "2026-04-11" }
}
}
Returns { "data": [{ "deploys": <subtree total> }] }. Notes: the value is org-specific — it comes from Team.path (step 1), never hard-code it. The metricId shown is the "Deployments" total-count metric; confirm the current id from metadata (pick by id, not label — labels are shared across metric versions).
Three things to get right:
Team.groupPath is the subtree anchor only — it's filter-only and dropped from the output, so it can't double as a breakdown (doing so returns a 400).For additional patterns (top-level team discovery, manager lookups), see references/api-reference.md.
When using granularity in the time dimension, the API returns already-aggregated values. Do not perform additional aggregation on these results.
Bad pattern (DO NOT DO THIS):
Good pattern (DO THIS):
Team.name = "Organization"When comparing time periods, always use the same asset, metric, and query path for both periods. Inconsistent query paths (e.g., different facades or aggregation levels) produce incomparable numbers.
For complex queries requiring multiple API calls, use TaskCreate to track steps:
Example: "Compare PR cycle time for top 5 teams over last 30 days"
Execute independent API calls in parallel, but limit to 2 concurrent calls maximum.
Superlative queries ("most", "least", "highest", "top N"): ensure you paginate through the full dataset. A query with limit=25 may miss the actual top result if there are more than 25 rows.
Always use the query script. Write the query JSON to a temp file, then execute:
cat > /tmp/span-query.json << 'EOF'
{
"select": ["Team.name"],
"filters": [{"field": "Team.name", "operator": "=", "value": "Organization"}],
"metrics": [{"metricId": "<metric-id-from-cache>", "responseKey": "cycleTime"}],
"timeDimension": {
"timeRange": {"startTime": "2024-05-01", "endTime": "2024-06-01"},
"granularity": "week"
}
}
EOF
$SKILL_SCRIPTS/query.sh /tmp/span-query.json
For paginated results, pass limit and cursor: $SKILL_SCRIPTS/query.sh /tmp/span-query.json 25 <cursor>
See references/api-reference.md for full request body format, filter operators, time dimensions, and groups mode.
Always check annotations in the API response for each metric's unit. The annotations tell you exactly how to interpret the raw values.
Possible units: seconds, hours, days, usd, count, percentage_as_ratio, score, boolean, estimate.
Convert accordingly:
seconds → hours or days for display (e.g., 42483 seconds → "11.8 hours")percentage_as_ratio → multiply by 100 for display (e.g., 0.85 → "85%")score → display as-is (dimensionless score)boolean → display as Yes/Noestimate → display as-is (story points or similar)API Errors:
Ambiguous Queries (see "Clarifying Questions" section above):
Handle pagination if hasNextPage is true in the response.
Team or Person with cycle time / time-to-merge metrics, weekly granularitySprint with planning accuracy, velocity, story point metricsPerson filtered by name/email with PR throughput and review metrics over monthly granularityTeam or Service with deployment frequency metric, weekly granularityService or Team with change failure rate metric (incidents / deployments)Team with MTTR metric (check for avg, p50, p75, p90 variants)Team with FTE Days metric, filter by work type (features, maintenance, developer experience)Epic filtered by name with FTE Days metricTeam with workstream-related metrics for effort breakdownTeam or Person with AI adoption rate, active AI users metricsTeam or Person with AI code ratio metricTeam with AI spend metrics (base + overage)Team or Person with meeting hours, focus time metricsTeam with focus time and fragmented time metrics$SKILL_SCRIPTS/check-version.sh$SKILL_SCRIPTS/fetch-metadata.sh$SPAN_DIR/auth.json and re-run onboardingFor more detailed step-by-step walkthroughs, see references/workflows.md.
For domain-specific query guidance (investment views, DORA facades, PR time dimensions, AI tool patterns, calendar caveats), see references/domains.md.
Searches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Implements vector databases with Pinecone, Weaviate, Qdrant, Milvus, pgvector for semantic search, RAG, recommendations, and similarity systems. Optimizes embeddings, indexing, and hybrid search.
npx claudepluginhub attuned-corp/skills --plugin span