star-momentum-alert
$
npx mdskill add aaronjmars/aeon/star-momentum-alert> **${var}** — Optional. Pass `dry-run` to skip the notification (article still writes, log still appends). Pass a positive integer to override the auto-picked milestone (e.g. `var=500`). Empty = audit every watched repo and auto-pick the next un-crossed ladder rung.
SKILL.md
.github/skills/star-momentum-alertView on GitHub ↗
---
name: star-momentum-alert
description: Project the date a watched repo crosses its next star milestone — alert only when projected date lands inside the Show HN dispatch window (7-14 days out, Tue/Wed/Thu)
var: ""
tags: [meta, growth]
---
> **${var}** — Optional. Pass `dry-run` to skip the notification (article still writes, log still appends). Pass a positive integer to override the auto-picked milestone (e.g. `var=500`). Empty = audit every watched repo and auto-pick the next un-crossed ladder rung.
Today is ${today}. Convert the last 14 days of `repo-pulse` star-count data into a projected date for the next milestone crossing, and surface a single decision-ready alert: when should the operator dispatch `show-hn-draft` so the launch lands at the milestone moment?
The skill answers a question `star-milestone` and `repo-pulse` cannot. `star-milestone` celebrates a crossing **after** it happens. `repo-pulse` reports today's deltas. Neither tells the operator "the next milestone is on a Wednesday 9 days from now — that's the launch slot." Without that lead-time signal, the milestone passes reactively, and a dispatch-ready Show HN draft sits unused while the moment slips by.
## Why this exists
`show-hn-draft` shipped May 1 (PR #151) as a `workflow_dispatch` skill. Its launch checklist requires Tue–Thu morning timing. Today there is no signal that tells the operator "the milestone you wanted to anchor the launch around is 9 days out, on a Wednesday — dispatch now so the post is ready Tuesday." This skill provides that signal — and only that signal. It is silent on every other day. The dispatch decision still belongs to the operator; this skill just makes the timing legible.
## Config
No new secrets. No new env vars. No new state file beyond `memory/topics/star-momentum-state.json` for prior-run dedup. No outbound HTTP — pure local file I/O over `memory/logs/` and `memory/topics/`.
Reads:
- `memory/watched-repos.md` — repos to track. Skip lines containing `aeon-agent` or ending in `-aeon` (agent repos, not project repos).
- `memory/logs/YYYY-MM-DD.md` for the last 14 days — extract the `**owner/repo**: stargazers_count=N, forks_count=M` lines that `repo-pulse` writes under its `## Repo Pulse` blocks.
- Optional fallback: `articles/repo-pulse-*.md` if any fork writes them — same regex applies. Logs are the source of truth on the canonical instance.
- `memory/topics/star-momentum-state.json` — prior-run dedup state.
Writes:
- `articles/star-momentum-${today}.md` — the per-repo projection report (always written, even when no alert fires).
- `memory/topics/star-momentum-state.json` — last-alert timestamp per `(repo, target_milestone)` pair.
- `memory/logs/${today}.md` — log block.
## Milestone ladder
```
50, 100, 150, 200, 250, 300, 400, 500, 750, 1000, 1500, 2000, 3000, 5000, 7500, 10000, 15000, 25000, 50000, 100000
```
Same ladder as `star-milestone` so the two skills agree on which numbers count as round-number moments worth marking.
## Steps
### 1. Parse var
- If `${var}` matches `^dry-run` → `MODE=dry-run`. Strip the prefix; remainder is treated as a milestone override.
- Otherwise `MODE=execute`.
- If the remaining var is a positive integer → `OVERRIDE_MILESTONE=$var`. Otherwise `OVERRIDE_MILESTONE=auto`.
- If the remaining var is non-empty and non-numeric → log `STAR_MOMENTUM_BAD_VAR: ${var}` and exit (no notify, no article).
### 2. Load repos
```bash
mkdir -p memory/topics articles
[ -f memory/topics/star-momentum-state.json ] || echo '{"last_run_at":null,"alerts":{}}' > memory/topics/star-momentum-state.json
REPOS=$(grep -oE '^- [a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+' memory/watched-repos.md \
| sed 's/^- //' \
| grep -vE '(aeon-agent|-aeon$)' || true)
```
If `REPOS` is empty, log `STAR_MOMENTUM_NO_REPOS` and exit cleanly without notifying.
### 3. Per-repo: build the 14-day stargazer series
For each repo in `REPOS`:
```bash
SERIES=""
for D in $(seq 13 -1 0); do
DATE=$(date -u -d "${today} - ${D} days" +%Y-%m-%d 2>/dev/null \
|| date -u -j -v-${D}d -f %Y-%m-%d "${today}" +%Y-%m-%d)
LOG=memory/logs/${DATE}.md
[ -f "$LOG" ] || continue
# Extract: - **owner/repo**: stargazers_count=N, forks_count=M
STARS=$(grep -oE "\\*\\*${REPO}\\*\\*: stargazers_count=[0-9]+" "$LOG" \
| grep -oE '[0-9]+$' | head -1)
[ -z "$STARS" ] && continue
SERIES="${SERIES}${DATE} ${STARS}\n"
done
```
The result is a `(date, stars)` series sorted ascending, one row per day where `repo-pulse` ran. Days with no log entry are simply absent — gaps in the series are fine and do not require interpolation.
If the series has fewer than 4 data points: record this repo's verdict as `INSUFFICIENT_DATA`, write its section in the article anyway, and skip projection.
### 4. Compute deltas and rolling averages
For consecutive `(date_i, stars_i), (date_{i+1}, stars_{i+1})` pairs:
- `delta_i = stars_{i+1} - stars_i`
- (No per-day normalization — `repo-pulse` runs daily, so each delta is a one-day delta. If two log entries are >1 day apart, divide by the gap so a 2-day gap doesn't double-count.)
Compute:
- `current_stars = SERIES[-1].stars`
- `v3 = mean of the last 3 normalized deltas` (or fewer if <3 are available)
- `v7 = mean of the last 7 normalized deltas` (or fewer if <7 are available)
If `v7 <= 0` (zero or net-negative growth across the 7-day window): record verdict `STALLED`. Article still writes; no projection, no alert.
### 5. Pick the target milestone
If `OVERRIDE_MILESTONE` is set:
- If `OVERRIDE_MILESTONE <= current_stars` → record verdict `BAD_TARGET`, log `STAR_MOMENTUM_BAD_TARGET: ${REPO} override=${OVERRIDE_MILESTONE} current=${current_stars}`, skip projection.
- Otherwise `target = OVERRIDE_MILESTONE`.
Otherwise: `target = smallest milestone in the ladder where milestone > current_stars`.
`gap = target - current_stars`.
### 6. Project the crossing date
```
days_remaining_v7 = ceil(gap / v7)
days_remaining_v3 = ceil(gap / max(v3, 0.5))
projected_date_v7 = today + days_remaining_v7
projected_date_v3 = today + days_remaining_v3
day_of_week_v7 = weekday name of projected_date_v7
day_of_week_v3 = weekday name of projected_date_v3
```
`v7` is the headline projection; `v3` is a faster-bound sanity check. Both go in the article.
### 7. Decide whether to alert
Apply gates in this order. The first gate to fail records the verdict and skips notify for that repo.
a. **STALLED / INSUFFICIENT_DATA / BAD_TARGET** (from steps 4 / 3 / 5) → no alert.
b. **Out of window** — if `days_remaining_v7 < 7` OR `days_remaining_v7 > 14` → record `OUT_OF_WINDOW`, no alert. (Under 7d is too late to dispatch `show-hn-draft` thoughtfully; over 14d is too far out and trades on noisy projection data.)
c. **Wrong day** — `projected_date_v7` weekday must be Tue, Wed, or Thu. Otherwise record `OFF_DAY`, no alert.
d. **Already alerted** — if `state.alerts.${repo}.${target}.alerted_at` exists AND was set within the last 7 days → record `ALREADY_ALERTED`, no alert.
If all gates pass: verdict `ALERT`. Promote this repo into the notify list.
### 8. Build the article (always — even when zero alerts fire)
Path: `articles/star-momentum-${today}.md`. Overwrite if exists.
```markdown
# Star Momentum — ${today}
**Verdict:** ${one of: ALERT — N repo(s) in launch window | NO_ALERTS — 0 repos in launch window today | INSUFFICIENT_DATA across the board}
*Audited ${repo_count} repos · ${alert_count} alerts · projection method: linear extrapolation from 7-day rolling average*
---
## ${repo} — ${current_stars}⭐ → ${target}⭐ in ~${days_remaining_v7}d
| Metric | Value |
|--------|-------|
| Current stars | ${current_stars} |
| Target milestone | ${target} |
| Gap | ${gap} |
| 3-day avg / day | ${v3} |
| 7-day avg / day | ${v7} |
| Days remaining (v7) | ${days_remaining_v7} |
| Projected date (v7) | ${projected_date_v7} (${day_of_week_v7}) |
| Days remaining (v3) | ${days_remaining_v3} |
| Projected date (v3) | ${projected_date_v3} (${day_of_week_v3}) |
| In Show HN window | ${YES — Tue/Wed/Thu inside 7-14d | NO — out of window | NO — off day} |
| Verdict | ${ALERT | OUT_OF_WINDOW | OFF_DAY | ALREADY_ALERTED | STALLED | INSUFFICIENT_DATA | BAD_TARGET} |
### Source data — ${repo}
| Date | Stars | Δ |
|------|-------|---|
| ${date_1} | ${s_1} | — |
| ${date_2} | ${s_2} | ${d_1} |
| ... | | |
(One section per repo. Repos with `INSUFFICIENT_DATA` show the partial series under the metrics table with a one-line note.)
---
## What this means
For each repo with verdict `ALERT`, one short paragraph:
> **${repo}** — ${current_stars}⭐ projected to cross ${target}⭐ on ${projected_date_v7} (${day_of_week_v7}), ${days_remaining_v7} days from today. Pace: ${v7}/day across the last 7 days, ${v3}/day across the last 3. ${day_of_week_v7} is inside the Show HN dispatch window (Tue/Wed/Thu morning). Suggested action: dispatch `show-hn-draft` 24-48 hours before ${projected_date_v7} so the post is ready when the milestone lands.
For each repo with verdict `OUT_OF_WINDOW`, one line:
> ${repo}: ${target}⭐ in ~${days_remaining_v7}d — outside the 7-14d launch window. No action.
For `OFF_DAY` / `STALLED` / `INSUFFICIENT_DATA` / `BAD_TARGET`: one line each, same format.
---
*Reads `memory/logs/YYYY-MM-DD.md` repo-pulse blocks. Pure local file I/O. Companion to `star-milestone` (post-crossing celebration) and `show-hn-draft` (the launch artifact this signal times).*
```
### 9. Notify (only on ALERT)
If `MODE == dry-run`: skip notify, log `STAR_MOMENTUM_DRY_RUN`, exit.
If `alert_count == 0`: log `STAR_MOMENTUM_NO_ALERTS`, **do not notify** (no signal = silence).
If `alert_count >= 1`: send one notification per alerting repo.
```
*Star Momentum — ${today} — ${repo}*
${current_stars}⭐ projected to cross ${target}⭐ on ${projected_date_v7} (${day_of_week_v7}) — ${days_remaining_v7} days from today.
Pace:
- 7-day avg: ${v7}/day
- 3-day avg: ${v3}/day
- Gap: ${gap} stars
${projected_date_v7} is a ${day_of_week_v7} — inside the Show HN dispatch window (Tue/Wed/Thu morning).
Suggested action: dispatch \`show-hn-draft\` 24-48 hours before ${projected_date_v7} so the post is ready when the milestone lands.
Article: articles/star-momentum-${today}.md
```
Cap each message at ~2500 chars. Notifications fan out via `./notify` (Telegram/Discord/Slack — whichever are configured).
### 10. Persist state
Write `memory/topics/star-momentum-state.json`:
```json
{
"last_run_at": "${ISO timestamp}",
"alerts": {
"${repo}": {
"${target_milestone}": {
"first_seen_in_window_at": "${ISO}",
"alerted_at": "${ISO or null}",
"projected_date_v7": "${YYYY-MM-DD}",
"v7_at_alert": ${v7}
}
}
}
}
```
State invariants:
- `first_seen_in_window_at` is set the first run a `(repo, milestone)` pair enters the 7-14d window. Persists across runs while the pair stays in-window.
- `alerted_at` is set the run the notification fires. Stays set for 7 days; subsequent runs see `ALREADY_ALERTED` and skip notify.
- After 7 days `alerted_at` ages out — if the milestone still hasn't been crossed and the projection still lands in-window on a Tue/Wed/Thu, the alert re-fires as a periodic reminder.
- When `current_stars >= target` (milestone crossed), drop that entry from `alerts.${repo}` next run — `star-milestone` will emit the celebratory crossing notification, and this skill's job for that target is done.
Cap to last 20 milestone entries per repo to bound the file.
### 11. Log to `memory/logs/${today}.md`
```
## Star Momentum
- **Skill**: star-momentum-alert
- **Repos audited**: ${repo_count}
- **Per-repo verdicts**:
- ${repo}: ${verdict} — ${current_stars}⭐ → ${target}⭐ in ~${eta}d (${projected_date_v7}, ${day_of_week_v7})
- **Alerts sent**: ${alert_count}
- **Article**: articles/star-momentum-${today}.md
- **Notification sent**: ${yes — N alerts | no — STAR_MOMENTUM_NO_ALERTS | no — dry-run}
- **Status**: ${STAR_MOMENTUM_OK | STAR_MOMENTUM_NO_ALERTS | STAR_MOMENTUM_DRY_RUN | STAR_MOMENTUM_NO_REPOS | STAR_MOMENTUM_BAD_VAR}
```
## Exit taxonomy
| Status | Meaning | Notify? |
|--------|---------|---------|
| `STAR_MOMENTUM_OK` | At least one repo passed every gate | Yes (one message per alerting repo) |
| `STAR_MOMENTUM_NO_ALERTS` | Article wrote, but no repo cleared all gates | No |
| `STAR_MOMENTUM_DRY_RUN` | `var=dry-run` mode | No (article still writes) |
| `STAR_MOMENTUM_NO_REPOS` | Watched-repos list empty after filtering agent repos | No |
| `STAR_MOMENTUM_BAD_VAR` | `${var}` was non-empty, non-numeric, non-`dry-run` | No |
## Sandbox note
Pure local file I/O — no curl, no `gh api`, no env-var-in-headers, no prefetch script. Every read is a directory listing, file existence check, or grep over `memory/logs/`. Every write goes to `articles/`, `memory/topics/`, or `memory/logs/`. Works in the GitHub Actions sandbox without any of the network workarounds other skills need. The only outbound call is `./notify` itself, which is already sandbox-safe (postprocess-notify pattern).
## Constraints
- **Only fires inside the launch window.** Both gates (7-14 day projection AND Tue/Wed/Thu landing) must pass. A 5-day projection is too late to dispatch `show-hn-draft` thoughtfully; a 21-day projection is too far out and trades on noisy projection data.
- **Per-milestone dedup.** Once an alert fires for `(repo, target_milestone)` it stays silent for 7 days. Even if pace shifts, the operator already has the signal — re-pinging adds noise without adding information.
- **Linear extrapolation only.** No regression, no exponential model, no S-curve fitting. The goal is to convert today's pace into a date, not to forecast trajectory shape changes. If pace shifts, the alert simply fires (or doesn't) on a different day.
- **Ignores agent repos.** `aeon-agent` and `*-aeon` repos are filtered upfront; they are infrastructure mirrors, not project repos with growth narratives worth anchoring a launch around.
- **Read-only across `memory/logs/`.** This skill never edits past log files; it parses them. Today's log is the only target it appends to.
- **Article writes regardless.** Even on `NO_ALERTS` the article still writes — operators or other skills may read it for projection context without needing a notification to fire.
- **Idempotent.** Same-day reruns overwrite the article and the state's `last_run_at`; per-`(repo, milestone)` `alerted_at` timestamps persist so re-runs don't double-fire.
More from aaronjmars/aeon
- [REPLACE: SKILL_NAME]Watch Vercel deploys for [REPLACE: VERCEL_PROJECT] — alert on [REPLACE: ALERT_ON] in the last [REPLACE: LOOKBACK_HOURS] hours
- Action Converter5 concrete real-life actions for today, leverage-scored against open loops with specificity and anti-fluff gates
- Agent BuzzCurated AI-agent tweets, clustered into narratives with insight summaries
- agent-displacementWeekly tracker of AI agent substitution signals — which roles, companies, and industries show real headcount displacement. Named roles + real deployments only.
- AI Framework WatchWeekly competitive-intelligence digest on the AI agent framework space — momentum, releases, breaking changes across a curated watchlist
- AIXBT PulseCross-domain market pulse from AIXBT's free grounding endpoint — crypto, macro, tradfi, geopolitics. Refreshes taxonomy references (clusters, chains) as a bonus.
- api-health-probeDaily pre-batch API provider health check — detects credit exhaustion or auth failure for every configured provider key before the morning batch runs, giving the operator a window to act before skills degrade
- Approval AuditList a wallet's live ERC-20 token approvals on Base and flag unlimited / risky spender grants. Keyless via Base RPC (eth_getLogs + eth_call) — no explorer key needed.
- article-queueWeekly article idea synthesizer — ranks signals from topic-momentum, beat-tracker, and narrative-tracker into a prioritized queue the article skill reads on next run
- atrium-catalog-watcherWeekly diff of the Atrium marketplace catalog at https://atriumhermes.tech/.well-known/skills/index.json against the prior snapshot — surfaces newly-published skills, removed skills, and updated descriptions. Supply-side complement to sparkleware-catalog (curated skill-packs.json registry) and skill-update-check (version drift of installed skills).