Trend Analysis skill for Codex

Tell a real trend from a hype spike. Codex turns 'X is taking off' into a testable claim, pulls several independent signals such as package downloads, pageviews, job postings and survey waves, and normalises them for category growth, seasonality and base effects. A bundled script runs Mann-Kendall and Sen's slope, and the finding lists which signals agree and which alternative explanations were ruled out.

  • Research
  • $trend-analysis
  • 150 lines
  • 3 files
  • ~1,800 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- trend-analysis

Installs to ~/.agents/skills/trend-analysis/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $trend-analysis, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Spots and validates trends in technology, product and market data - npm and PyPI downloads, Google Trends, Wikipedia pageviews, GitHub activity, job postings, survey waves and internal metrics - by normalising for base effects and seasonality, testing with Mann-Kendall and Sen's slope, triangulating independent signals and separating hype from adoption. Use when asked whether something is trending, growing or declining, to back a "rise of X" claim with data, or to check if a metric's recent movement is real.

Use it when

  • Someone claims a technology or market is growing or dying
  • A metric moved recently and you need to know if the change is real
  • A talk, post or strategy doc needs trend claims backed by data

Use something else when

How to check the result

Raw data is saved with retrieval dates, trend_check.py reproduces the reported statistics, and the trend appears in at least two independent signals or the report says it does not.

The mistake it prevents

Reading raw download growth as adoption when the whole registry grew, or when one CI pipeline started pulling the package thousands of times a day.

~/.agents/skills/trend-analysis/SKILL.md

--- name: trend-analysis description: Spots and validates trends in technology, product and market data - npm and PyPI downloads, Google Trends, Wikipedia pageviews, GitHub activity, job… license: MIT ---

A trend is real when it persists beyond seasonality and noise, shows in more than one independent signal, and survives the obvious alternative explanations (a bot, a definition change, a single viral event). Report the size and the uncertainty, not just the direction.

Workflow

- [ ] 1. State the claim and the population it is about
- [ ] 2. Pick 2-4 independent signals (adoption, interest, supply, outcomes)
- [ ] 3. Pull data with a long enough baseline; store raw files
- [ ] 4. Clean: definition changes, bots, gaps, outliers
- [ ] 5. Normalise: per capita / share, seasonality, base effects
- [ ] 6. Test the trend; size it
- [ ] 7. Triangulate and check alternative explanations
- [ ] 8. Write the finding with confidence and caveats

1-2. Claim and signals

“Rust is growing” is not testable. “Rust’s share of new backend projects at companies like ours grew over the last 24 months” is. Then choose signals that measure different things, so they fail differently:

Signal typeExamplesMeasuresWeakness
Usagenpm/PyPI/crates.io downloads, Docker pulls, API callsInstalls, often CI-drivenBots, mirrors, CI caches inflate; one big user dominates
InterestGoogle Trends, Wikipedia pageviews, search volumeAttentionHype and news spikes; relative indexes
Builder activityNew repos, commits, contributors, Stack Overflow questionsDeveloper engagementStars are cheap; SO activity declining overall
Supply / demandJob postings, courses, vendors enteringMarket commitmentLags reality; posting duplication
StatedAnnual developer surveys (Stack Overflow, JetBrains, State of JS)Self-reported useSample shifts each year; wording changes
OutcomesRevenue, paid seats, production incidentsReal commitmentOften private

Hype shows in interest signals first; adoption shows in usage, supply and outcome signals. A trend only in interest data is attention, not adoption.

3. Pulling data (needs network)

# npm daily downloads for a date range (max ~18 months per request)
curl -s "https://api.npmjs.org/downloads/range/2025-01-01:2026-06-30/vite" | jq -r '.downloads[] | [.day, .downloads] | @csv'
# PyPI recent totals (for history, use the public PyPI BigQuery dataset)
curl -s https://pypistats.org/api/packages/polars/overall | jq '.data[:5]'
# Wikipedia monthly pageviews, human traffic only
curl -s "https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/user/Rust_(programming_language)/monthly/20220101/20260101" | jq -r '.items[] | [.timestamp, .views] | @csv'
# Hacker News mentions over time (Algolia API)
curl -s "https://hn.algolia.com/api/v1/search_by_date?query=htmx&tags=story&hitsPerPage=100"

Save raw responses with the retrieval date under a data/raw/ folder so the analysis can be rerun. Some APIs ask for a contact User-Agent; respect rate limits.

Google Trends values are relative (0-100, scaled to the peak within the chosen query, time range and region) and sampled. Compare terms only within the same request, and never stitch separately downloaded ranges without overlap-based rescaling.

4. Cleaning

  • Definition changes: survey questions reworded, package renamed or split, analytics tracking changed. Mark the break; do not compare across it naively.
  • Bot and CI traffic: a step jump in downloads on one day usually means a CI pipeline or mirror, not new users. Check whether a popular dependent started depending on it.
  • Gaps and partial periods: drop the current incomplete week or month.
  • Outliers: keep them in raw data, explain them, and test with and without.

5. Normalise

  • Share, not counts: total npm downloads grow every year; compare against the category (e.g. vite vs the sum of bundlers) or the registry total.
  • Seasonality: developer activity dips in late December and in summer; weekends are low. Compare year over year or use weekly/monthly aggregates and a seasonal test.
  • Base effects: 300% growth from 1,000 is not comparable to 20% growth from 10M. Report absolute and relative change together.
  • Log scale for multi-order-of-magnitude series; linear scale for share data.

6. Test and size

The bundled script runs Mann-Kendall (non-parametric, insensitive to outliers) with Sen’s slope, and a seasonal variant:

python3 scripts/trend_check.py data/vite-monthly.csv --period 12
python3 scripts/trend_check.py data/weekly-signups.csv --window 4

Input is a CSV with a header and period,value rows in time order. It prints the test statistic, p-value, slope per step relative to the median level, and the last-window vs previous-window change, then a verdict. Interpret:

  • Significant and practically large: a trend, size it with Sen’s slope.
  • Significant but tiny relative slope: real drift, probably not important.
  • Not significant: say “no clear trend”; recent movement may be noise or a cycle.

For changepoints (when did it start?), plot the series with a rolling median and look for the break; corroborate with a dated event (release, pricing change, viral post). For forecasting the trend forward, hand off to $time-series-forecasting.

7. Triangulate and challenge

Before reporting, check each alternative explanation:

  • Did one large company or dependent cause it?
  • Is it a news or launch spike that decays within weeks?
  • Did measurement change (tracking, survey sample, API definition)?
  • Is the whole category growing, so the share is flat?
  • Survivorship: are you only looking at tools that are still around?

Gotchas

  • GitHub stars track attention and can be bought or bot-inflated; never use them alone.
  • Download counts double with every new CI cache miss pattern; prefer unique-ish signals or share metrics.
  • Survey “most loved” or “most admired” is not usage.
  • Short windows (under ~2 seasonal cycles) cannot separate trend from seasonality.
  • A chart starting at the trough exaggerates growth; show a baseline of at least 2 years when available.
  • Correlation between two trending series is almost guaranteed; do not infer causation.

Verification

  • Raw data files and retrieval dates are saved; the script reruns to the same numbers.
  • The trend holds in at least two independent signals, or the finding says it does not.
  • Seasonality and base effects are addressed explicitly.
  • Each alternative explanation is checked or listed as unresolved.

Output template

# Is <X> growing? (<date>)
**Finding:** <X>'s share of <category> downloads rose from 18% to 27% over 24 months
(Sen's slope +0.4 pts/month, seasonal MK p < 0.01). **Confidence:** moderate.

| Signal | Window | Change | Test | Supports? |
|---|---|---|---|---|
| npm share of bundler downloads | 24 mo | +9 pts | MK p<0.01 | yes |
| Wikipedia pageviews | 24 mo | +4% | not significant | no (interest flat) |
| Job postings mentioning X | 12 mo | +35% | - | yes |

**Alternative explanations checked:** <list>
**Caveats:** <CI inflation, single-source data, definition changes>
**Data:** data/raw/*.json retrieved <date>

What the zip contains

Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Trend Analysis"
  short_description: "Tell a real trend from a hype spike"
  default_prompt: "$trend-analysis Is htmx actually growing or was it a 2023 hype spike? Show me the data"
scripts/trend_check.py114 lines, 4.2 KB
#!/usr/bin/env python3
"""Test whether a time series has a real monotonic trend.

Reads a CSV with a header and two columns: period label (date) and value, in time
order. Reports the Mann-Kendall test (seasonal variant with --period), Sen's slope,
the slope relative to the median level, and the last-window vs previous-window change.

Usage:
  python3 trend_check.py series.csv                 # non-seasonal
  python3 trend_check.py series.csv --period 12     # monthly data with yearly seasonality
  python3 trend_check.py series.csv --window 3 --alpha 0.05
Exits 1 on unreadable input or fewer than 8 points.
"""
import argparse
import csv
import math
import statistics
import sys
from collections import Counter


def fail(msg):
    print(f"error: {msg}", file=sys.stderr)
    sys.exit(1)


def mk_stats(x):
    """Return (S, variance) for Mann-Kendall with tie correction."""
    n = len(x)
    s = 0
    for i in range(n - 1):
        for j in range(i + 1, n):
            d = x[j] - x[i]
            s += (d > 0) - (d < 0)
    ties = Counter(x).values()
    var = (n * (n - 1) * (2 * n + 5) - sum(t * (t - 1) * (2 * t + 5) for t in ties)) / 18
    return s, var


def z_and_p(s, var):
    if var <= 0:
        return 0.0, 1.0
    z = (s - 1) / math.sqrt(var) if s > 0 else (s + 1) / math.sqrt(var) if s < 0 else 0.0
    p = 2 * (1 - 0.5 * (1 + math.erf(abs(z) / math.sqrt(2))))
    return z, p


def sen_slope(x, period=None):
    slopes = []
    if period:
        for k in range(period):
            season = x[k::period]
            slopes += [(season[j] - season[i]) / (j - i) for i in range(len(season)) for j in range(i + 1, len(season))]
        return statistics.median(slopes) / period if slopes else 0.0  # per step
    slopes = [(x[j] - x[i]) / (j - i) for i in range(len(x)) for j in range(i + 1, len(x))]
    return statistics.median(slopes)


def main():
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("csv")
    ap.add_argument("--period", type=int, help="seasonal period in steps (12 monthly, 52 weekly, 7 daily)")
    ap.add_argument("--window", type=int, help="steps per comparison window (default: period or 4)")
    ap.add_argument("--alpha", type=float, default=0.05)
    a = ap.parse_args()

    try:
        with open(a.csv, newline="", encoding="utf-8-sig") as f:
            rows = list(csv.reader(f))[1:]
        labels = [r[0] for r in rows if len(r) >= 2 and r[1].strip()]
        x = [float(r[1]) for r in rows if len(r) >= 2 and r[1].strip()]
    except (OSError, ValueError, IndexError) as e:
        fail(f"cannot read series: {e}")
    if len(x) < 8:
        fail(f"need at least 8 points, got {len(x)}")
    if a.period and len(x) < 2 * a.period:
        fail(f"seasonal test needs at least two full periods ({2 * a.period} points)")

    if a.period:
        s = var = 0
        for k in range(a.period):
            sk, vk = mk_stats(x[k::a.period])
            s, var = s + sk, var + vk
        test = f"Seasonal Mann-Kendall (period {a.period})"
    else:
        s, var = mk_stats(x)
        test = "Mann-Kendall"
    z, p = z_and_p(s, var)
    slope = sen_slope(x, a.period)
    level = statistics.median(x)
    rel = 100 * slope / level if level else float("nan")

    w = a.window or a.period or 4
    last, prev = x[-w:], x[-2 * w:-w]
    change = (sum(last) - sum(prev)) / sum(prev) * 100 if len(prev) == w and sum(prev) else None

    print(f"Series: {len(x)} points, {labels[0]} .. {labels[-1]}")
    print(f"{test}: S={s}, z={z:.2f}, p={p:.4f}")
    print(f"Sen's slope: {slope:.4g} per step ({rel:+.2f}% of median level per step)")
    if change is not None:
        print(f"Last {w} steps vs previous {w}: {change:+.1f}%")
    if p < a.alpha:
        verdict = "INCREASING" if s > 0 else "DECREASING"
        print(f"Verdict: {verdict} trend (p < {a.alpha}).")
        if abs(rel) < 0.5:
            print("Note: statistically detectable but small relative to the level; check practical size.")
    else:
        print(f"Verdict: NO significant monotonic trend at alpha={a.alpha}. Treat movement as noise or a cycle.")
    if not a.period:
        print("Hint: rerun with --period if the data has weekly or yearly seasonality.")


if __name__ == "__main__":
    main()

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
New

Introducing Codex Skills Pro

79 professional skills that take Codex through complete engagements, each with its own scripts, references and a report that verifies the result.

All-AccessOne-time payment, no subscription
$49
Get All-AccessOr browse individual skills from $2.99 →

Secure checkout by Creem