plugins/adobe-cja/skills/cja-kpi-pulse/SKILL.md
Produces a compact KPI digest showing how key metrics changed over a period and what's driving the movement. Use this skill when someone asks for a performance summary, a weekly recap, a morning briefing, a KPI update, or any variation of "how did we do this week/month." Also trigger for requests like "give me a performance overview," "what moved in the last 7 days," "pull our KPI report," or "summarize our metrics."
npx skillsauth add adobe/skills cja-kpi-pulseInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Produce a compact KPI digest in under 2 minutes. The goal is a crisp answer to "how did we do?" — not a deep-dive, not a data dump. Each KPI gets a scorecard showing current value, period-over-period change, trend direction, and the top dimension breakdown that explains any movement.
describeCja(DATAVIEW_CONTEXT_GUIDE) — understand the data view contextlistComponentUsage — find the most-used metrics (the org's real KPIs)findMetrics — resolve metric IDs from user-specified namesfindCalculatedMetrics — include custom KPIs if presentrunReport — pull metric values for current and prior periodssearchDimensionItems — top dimension breakdown for moversfindDataViews to list available data views.setDefaultSessionDataViewId with the chosen ID.describeCja("DATAVIEW_CONTEXT_GUIDE") to load data view context.
Record the data view's first-day-of-week as WEEK_START_DOW and timezone
as TIMEZONE. If the context guide does not return a week-start value,
default to Monday (ISO 8601). You will use both in Phase 1.1.If the user did not specify a period, ask one question:
"What time window would you like? Options: last 7 days, last 30 days, this week vs last week, this month vs last month, or a custom range."
Default to this week vs last week if no answer is given.
Map the answer to two date ranges:
Calendar rule (mandatory):
Use WEEK_START_DOW from Phase 0 to define what "week" means. The current
period (Period A) and the comparison period (Period B) MUST use the same
first-day-of-week — i.e., both periods' startDate fall on the same
day-of-week, both are exactly equal length, and the comparison period ends
immediately before the current period starts. Never mix conventions
(e.g., a Mon–Sun current with a Sun–Sat prior) within the same pulse run.
Pick the boundary once, then derive both periods from it. For custom date
ranges, compute Period B as the equal-length window ending immediately
before Period A starts.
Sanity check before calling runReport: confirm periodA.startDate and
periodB.startDate are the same day-of-week and that
periodA.startDate - periodB.endDate == 1 day. If not, recompute.
If the user named specific metrics, resolve them with findMetrics or
findCalculatedMetrics. Otherwise, discover the top 5–8 KPIs automatically:
listComponentUsage(componentType: "metric")
listComponentUsage(componentType: "calculatedMetric")
Note: listComponentUsage may return an empty list for data views with no
usage history. If it returns empty, fall back to:
findMetrics(searchQuery: "sessions visits revenue orders")
findMetrics(searchQuery: "page views cart conversion")
Pick the most business-relevant metrics from the results (sessions, orders, revenue, product views, cart views, people — in that priority order).
Deduplicate: if a built-in metric and a calculated metric measure the same thing, keep only the calculated metric (it's more intentional).
Final list: 5–8 metrics. More than 8 KPIs in a pulse report is noise.
Run a single runReport call per period with all KPI metrics included.
Use one call for Period A and one for Period B to minimize round-trips.
Use a summary dimension (e.g., variables/daterangeday) and limit: 1 to
get aggregate totals from summaryData.totals in the response.
runReport(
dimensionIds: "variables/daterangeday",
metricIds: "metrics/visits,metrics/visitors,metrics/orders_1_1,metrics/productListItems.priceTotal,metrics/cart_views",
startDate: "<periodA start>T00:00:00",
endDate: "<periodA end>T23:59:59",
page: 0,
limit: 1
)
runReport(
dimensionIds: "variables/daterangeday",
metricIds: "metrics/visits,metrics/visitors,metrics/orders_1_1,metrics/productListItems.priceTotal,metrics/cart_views",
startDate: "<periodB start>T00:00:00",
endDate: "<periodB end>T23:59:59",
page: 0,
limit: 1
)
Read aggregate totals from summaryData.totals (not row data), which
gives you the full-period sum for each metric in the order they were listed.
Capture for each metric:
valueA (current period)valueB (comparison period)delta = valueA − valueBpctChange = (delta / valueB) × 100, rounded to 1 decimalFor each KPI, assign a trend indicator:
Assign a signal color:
For the 1–2 metrics with the largest absolute % change, find what's driving the movement. Run a dimension breakdown for the current period:
runReport(
dimensionIds: "variables/marketing_channel",
metricIds: "<moving metric id>",
startDate: "<periodA start>T00:00:00",
endDate: "<periodA end>T23:59:59",
page: 0,
limit: 5
)
Note: Use variables/marketing_channel (not variables/marketingchannel) —
verify the exact dimension ID with findDimensions(searchQuery: "marketing channel")
if unsure.
Compare dimension values between Period A and Period B to identify the top contributor to the change. This becomes the "What drove it" entry in the report.
Generate the KPI Pulse HTML report INLINE — do not use a Python script.
Build the HTML string directly from the collected data and output it as a
code block the user can save, or write it to /tmp/cja_kpi_pulse_report_<YYYY-MM-DD_HHMMSS>.html
using a one-line bash command.
Two runs of this skill on the same data view + period must render identically (modulo the generation timestamp). The rules below pin the formatting choices that the AI would otherwise drift on.
8,160, 77,584, 1,250,000). Do NOT use SI
suffixes like K or M, even for large values. Executives want exact
numbers, not abbreviations.−23.55% displays as
−23.6%, never −23.5%. Compute on full-precision values; round only at
display time.pp. Example: +0.40 pp.$ prefix with thousands separators and no decimals for
values ≥ $100 ($1,240,000); cents only when value < $100 ($45.20).A KPI tile must reflect what the data view actually returned. The AI must not silently substitute a different metric or hide a tile to make the report look cleaner.
kpi-value = Data unavailable, pill class flat, pill text
⚠ N/A, and prior text = Both periods returned no data — validate instrumentation. The tile stays in the grid; do not omit it.kpi-value, pill class flat, pill text ⚠ N/A, and
prior text = Prior {period_noun}: no data.Read template.html and use it verbatim. Do not improvise the
HTML structure or CSS — only fill in the {PLACEHOLDER} tokens ({ORG_NAME},
{PERIOD_LABEL}, {COMPARISON_LABEL}, {DATA_VIEW}, {GENERATED_DATE},
{METRIC_NAME}, {FORMATTED_VALUE_A}, {FORMATTED_VALUE_B}, {PCT_CHANGE},
{VALUE_A}, {VALUE_B}, {DELTA}, {ARROW}) and repeat the KPI tile / detail
row / mover row blocks once per data item. Preserve the .up | .down | .flat
and .green | .red | .yellow | .grey modifier classes per the trend rules in
Phase 3.
After generating the HTML:
/tmp/cja_kpi_pulse_report_<YYYY-MM-DD_HHMMSS>.htmlopen /tmp/cja_kpi_pulse_report_<YYYY-MM-DD_HHMMSS>.htmlKPI Pulse — This Week vs Last Week
↑ Revenue: $1.24M (+8.2%) — Paid Search drove most of the gain
↓ Conversion Rate: 2.1% (−0.4pp) — Drop in mobile checkout
→ Sessions: 540K (+1.1%) — Flat week-over-week
↑ Orders: 11,340 (+6.7%) — Product page improvements appear to be working
↓ Bounce Rate: 43.2% (+2.1pp) — Worth monitoring next week
The text summary gives immediate value even without opening the HTML file.
"Give me a quick pulse on our key metrics for this week."
findDataViews. User selects their main data view. Call setDefaultSessionDataViewId.runReport for current week vs. prior week for all four metrics.runReport returns no data for Period B (comparison is too far in the
past or data view lacks history), show "N/A" for the delta and flag it with
a grey badge.development
Start AEM Workflows on AEM as a Cloud Service using all available triggering mechanisms. Use when starting workflows manually via the Timeline UI, programmatically via WorkflowSession.startWorkflow(), via the HTTP Workflow API, through Manage Publication, or passing initial metadata and payload to a workflow instance.
development
Single entry point for all AEM as a Cloud Service Workflow skills. Covers workflow model design, custom process step and participant chooser development, launcher configuration, workflow triggering, and production support including debugging stuck/failed workflows, triaging incidents with Cloud Manager logs, thread pool analysis, and Sling Job diagnostics for the Granite Workflow Engine.
development
[BETA] Implement custom AEM Workflow Java components on AEM as a Cloud Service. This skill is in beta. Verify all outputs before applying them to production projects. Use when writing WorkflowProcess steps, ParticipantStepChooser implementations, registering services via OSGi DS R6 annotations, reading step arguments from MetaDataMap, accessing JCR payload via WorkflowSession adapter, reading and writing workflow metadata and variables, and handling errors with WorkflowException for retry behavior.
development
Start AEM Workflows on AEM 6.5 LTS using all available triggering mechanisms. Use when starting workflows manually via the Timeline UI, programmatically via WorkflowSession.startWorkflow(), via the HTTP Workflow API, through Manage Publication, through replication triggers, or passing initial metadata and payload to a workflow instance.