aeon-doctor
Static config-correctness linter for this instance - catches the silent-failure class (unquoted schedules, duplicate keys, unconfigured skills, mode typos, broken requires/MCP refs) that no run-based health skill can see. Notifies only on problems.
git clone --depth 1 https://github.com/aeonfun/aeon /tmp/aeon-doctor && cp -r /tmp/aeon-doctor/skills/aeon-doctor ~/.claude/skills/aeon-doctorSKILL.md
> **${var}** — scope. **Empty (default)** = lint the entire instance config (`aeon.yml` + every `skills/*/SKILL.md` + `.mcp.json`). A **skill slug** (e.g. `digest`) = lint just that one skill's `aeon.yml` entry and its `SKILL.md`.
Today is ${today}. You are this instance's **config doctor**. Every other health skill (`heartbeat`, `skill-health`) reads *run outcomes* — did a skill fire, did it pass. You read the **config itself**, before anything runs, for the class of bug where a skill is silently misconfigured and **never fires at all** — no error, no failed run, nothing in the Actions tab to notice. That class is invisible to run-based observability *by construction*, and it is the single most common reason an Aeon instance quietly stops doing what its operator thinks it does.
You do **not** fix anything — a diagnostic that inspects config must never mutate it. You surface precise, actionable findings; the operator (or `skill-repair`) applies the fix.
## Preamble (always)
1. Read `memory/MEMORY.md` for context and scan the last ~3 days of `memory/logs/` — **drop any finding you already reported** so you don't re-nag a known-but-unfixed issue every run. (A finding is "the same" if it's the same check on the same skill.)
2. Resolve scope from `${var}`: empty → all skills; a slug → restrict every check to that skill (skip fleet-wide-only checks like duplicate-key detection unless they touch the target).
3. Every check below is a **pure local file read** — `grep`, `comm`, `node scripts/*.js`, `bash scripts/*.sh`. No network, no secrets, no GitHub API. If a referenced script is missing, skip that check and note it; **never let one check's failure stop the others**.
## Steps — run every check, collect findings
Each finding = **{check, skill, severity, one-line what's-wrong, exact fix}**. Severity:
- **critical** — an `enabled: true` skill that will **never fire** or will run with the **wrong privilege**. Live breakage.
- **warn** — a latent trap: the same defect on a *disabled* skill, or a correctness issue that degrades silently rather than killing the run.
### 1 · Unquoted `schedule:` — the #1 silent killer (critical / warn)
`scheduler.yml` matches schedules with the bash regex `schedule: *"([^"]+)"`. An unquoted value doesn't match, is read as empty, and the skill is **skipped every tick, forever** — the file is still valid YAML, so nothing else notices.
```bash
grep -nE '^\s+[a-z0-9-]+:\s*\{[^}]*schedule:' aeon.yml | grep -vE 'schedule: *"'
```
Each printed line is an entry whose `schedule:` isn't double-quoted. **critical** if that entry is `enabled: true`; **warn** if disabled (it'll be dead the moment it's enabled). Fix: add the quotes — `schedule: "0 12 * * *"`.
### 2 · Duplicate skill keys — silent shadow (critical)
A repeated skill name under the `skills:` map silently disables the first copy (last-wins YAML).
```bash
node scripts/validate-config.js # authoritative — dup keys + checkout ordering
grep -oE '^ [a-z0-9-]+:' aeon.yml | sort | uniq -d # names appearing more than once
```
Any name from `uniq -d` (or a dup-key error from the validator) is a finding. **critical** if either copy is enabled. Fix: remove the shadow copy.
### 3 · On disk but unconfigured — invisible skills (warn)
A skill with a `SKILL.md` but no `aeon.yml` entry defaults to disabled, so "not configured" and "deliberately off" look identical.
```bash
comm -23 <(ls skills/*/SKILL.md | cut -d/ -f2 | sort) \
<(grep -oE '^ [a-z0-9-]+:' aeon.yml | tr -d ' :' | sort)
```
Each printed name exists on disk but has no config entry. **warn** — list them so the operator can decide (enable, or accept it's intentionally uninstalled).
### 4 · Enabled skill with no `SKILL.md` — broken entry (critical)
The inverse: an `aeon.yml` entry pointing at a skill dir that doesn't exist. For every `enabled: true` key, confirm `skills/<key>/SKILL.md` is present. Missing → **critical** (the run fails or no-ops).
### 5 · `requires:` entry the allowlist silently drops (warn)
Both list forms parse — inline (`requires: [KEY?]`) and block (`- KEY` on its own line), top-level or nested under `metadata:`. What still bites is the *value*: `scripts/skill_requires.sh` injects only names matching `^[A-Z][A-Z0-9_]{2,}$` (a trailing `?` = "works better with" is allowed). An entry that fails the filter — lowercase, fewer than 3 chars, a leading digit, or stray punctuation — is silently dropped, so the skill declares a credential it never receives and fails or degrades with a confusing auth error.
```bash
for f in skills/*/SKILL.md; do awk '
/^---$/{n++; next} n!=1{next}
function chk(x){ sub(/\?$/,"",x); if(x!="" && x !~ /^[A-Z][A-Z0-9_]{2,}$/) print FILENAME": requires entry \""x"\" is dropped by the allowlist filter" }
collecting { if ($0 ~ /^[ \t]*-[ \t]*/){ it=$0; sub(/^[ \t]*-[ \t]*/,"",it); sub(/[ \t]*#.*/,"",it); gsub(/[ \t]/,"",it); chk(it); next } collecting=0 }
/^[^ \t]/{im=0} /^metadata:/{im=1}
/^requires:/ || (im && /^[ \t]+requires:/){
if ($0 ~ /\[/){ line=$0; sub(/.*\[/,"",line); sub(/\].*/,"",line); k=split(line,a,","); for(i=1;i<=k;i++){gsub(/[ \t]/,"",a[i]); chk(a[i])} }
else collecting=1
}' "$f"; done
```
Flag any entry that fails the filter. **warn**. Fix: use the exact env-var name (uppercase, `^[A-Z][A-Z0-9_]{2,}$`), with a trailing `?` only to mark it optional.
### 6 · `mode:` typo — silent write grant (critical)
An unknown `mode:` value falls back to **`write`**, never to the safer tier. The only valid strings are `read-only` and `write`.
```bash
grep -rnE '^[[:space:]]*mode:' skills/*/SKILL.md | grep -vE ':\s*(read-only|write)\s*$'
```
`mode:` is nested under `metadata:` (spec form), so the pattern allows leading indent. Any printed line is a typo (`readonly`, `read only`, `readOnly`, …) that silently grants full write / `gh` / `git`. **critical** — least-privilege is broken. Fix: the exact string `read-only`. (A skill with *no* `mode:` line is intentionally `wriSet up and run an Aeon agent instance — get started from scratch, pick which skills to turn on or install more from packs, reschedule or change what runs, edit what an existing skill does, fix a skill that isn't firing, set the STRATEGY.md north star and soul/ voice, turn a coding-agent chat into a scheduled Aeon skill, and mine past coding-agent conversations for recurring work worth automating as a skill. Use when the user mentions Aeon, aeon.yml, an Aeon skill / instance / routine / pack, asks to schedule, enable, edit, or debug an agent that runs on a cron, or asks what of their repeated/manual work Aeon could take over.
Mention/keyword sweep on social platforms for [REPLACE: KEYWORDS] — trends, sentiment, top posts
5 concrete real-life actions, leverage-scored against open loops with specificity and anti-fluff gates
Pull framework updates from the upstream Aeon repo into this instance - 3-way merges canon's new commits into a PR, never clobbering operator config.
Write a publication-ready article in one of three angles - a trending long-form piece, a watched-repo thesis, or a project-through-a-lens essay. Optional Replicate hero image with --visual.
Automatically merge open PRs that have passing CI, no blocking reviews, and no conflicts
Two-mode aeon.yml workflow builder - analyze inspects URLs and emits a tiered, signal-verified skill-enablement plan plus an aeon.yml diff; enable flips slugs to enabled:true and opens a PR.
Evolve a skill by generating variations, evaluating them, and updating the best version