Skip to main content

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.

Ir a la instalación

Datos de origen

Repositorio
aeonfun/aeon
Última actividad en el origen
4 de agosto de 2026 a las 21:01
Idioma detectado de SKILL.md
inglés
Estrellas
738
Forks
265

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
aeon-doctor
description
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.
metadata
{"title":"Aeon Doctor","category":"evolution","var":"","tags":["meta","health"],"mode":"read-only"}
> **${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 `write` — not a finding.) ### 7 · `.mcp.json` unresolved `${VAR}` — kills ALL MCP (warn) On the Claude harness a **single** `${VAR}` in `.mcp.json` that resolves to no secret disables **every** MCP server that run (`Skipping MCP this run.`), not just the broken one. List the referenced vars so the operator can confirm each is set: ```bash [ -f .mcp.json ] && grep -oE '\$\{[A-Z0-9_]+\}' .mcp.json | sort -u ``` Report the list as **warn** with the note: verify each with `./aeon secrets ls --set`; any one unset silently blacks out MCP for skills that rely on it. (You can't read secret values in read-only mode — surface the vars, don't try to resolve them.) ### 8 · Multi-line `aeon.yml` entry — override ignored (warn) Per-skill `model:` / `harness:` overrides are read by a single-line grep. An entry split across lines (its `{` and `}` on different lines) takes the **global default** instead — no error, and the run's `model=` line looks normal. ```bash grep -nE '^\s+[a-z0-9-]+:\s*\{[^}]*$' aeon.yml # opens { with no closing } on the same line ``` Each hit is a split entry → **warn**. Fix: collapse it to one inline `{ … }` line. ### 9 · Misleading `schedule:` / `cron:` in SKILL.md frontmatter (warn) Schedules live in `aeon.yml` only; `scheduler.yml` never reads `SKILL.md`. A `schedule:` / `cron:` line in frontmatter looks load-bearing but does nothing — a trap for anyone editing the skill. ```bash grep -rnE '^[[:space:]]*(schedule|cron):' skills/*/SKILL.md ``` Report as **warn** (informational): these lines are inert; the real schedule is the `aeon.yml` entry (the pattern allows leading indent, since spec-form frontmatter nests these under `metadata:`). ### 10 · Unquoted per-skill `harness:` / `model:` override (warn) Same quoting rule as `schedule:` — the override grep requires double quotes. An unquoted `harness: grok` or `model: …` is silently ignored and the skill keeps running the global default. ```bash grep -nE '\{[^}]*(harness|model):' aeon.yml | grep -vE '(harness|model): *"' ``` Each printed line has an unquoted override → **warn**. Fix: quote it (`harness: "grok"`). ### 11 · Category not one of the six — trips CI (warn) Every skill's `category:` must be one of `core evolution basics dev crypto productivity` or the catalog CI gate goes red. ```bash [ -x scripts/check-skill-categories.sh ] && bash scripts/check-skill-categories.sh ``` Report any offender as **warn**. ### 12 · Daily-log heading not `### <slug>` — health loop can't key it (warn) `CLAUDE.md` mandates each skill append its daily-log entry under a **`### <slug>`** heading — "the health loop parses this shape", and `skill-health` / `heartbeat` key skills by **slug**. A skill that logs under `## <Display Name>` (wrong level *and* wrong identifier) still runs, but its narrative log is harder for the health view to attribute and for the cross-skill dedup ("read the last 3 days of logs") to match — a silent degrade, never an error. ```bash for f in skills/*/SKILL.md; do s=$(basename "$(dirname "$f")"); grep -qE 'memory/logs/\$\{today\}' "$f" || continue # only log-writers grep -qE '###[[:space:]]+'"$s"'\b' "$f" && continue # compliant name=$(awk -F': *' '/^name:/{print $2; exit}' "$f" | sed 's/ *$//') hit=$(grep -oE '^##[[:space:]]+('"$s"'|'"${name:-$s}"')\b' "$f" | head -1) [ -n "$hit" ] && echo "$s logs under '$hit' — should be '### $s'" done ``` Each hit → **warn**. Fix: change the Log-section heading (the instruction line *and* the example block) to `### <slug>`, and demote any sub-sections inside the block to `####`. ## Report - **No findings → send nothing and exit.** A clean config is the common case; silence is correct and keeps this channel trustworthy. - **Findings → one consolidated `./notify`**, most-severe first. Write the body to a scratch file and send with `-f` (never a long argv): ```bash ./notify -f <file> \ --title "aeon-doctor: <N> config issue(s)" \ --severity <critical if any critical else warn> \ --mute-key "aeon-doctor" ``` Group by severity. For each finding give: the skill, one line on what breaks (and that it's **silent** — the operator won't see it in the Actions tab), and the exact one-command fix. If a `critical` exists, lead with it — an enabled skill that never fires is the whole reason this skill exists. - Do **not** open a PR or edit any file. Point mechanical fixes at `skill-repair` / the `./aeon` CLI; leave the fix to the operator. ## Constraints - Read-only by contract — inspect config, never mutate it. No `Write` / `Edit` / `git` / `gh`. - Every finding must cite the **exact** file + line and a **copy-pasteable** fix. A config finding with no fix is noise. - Don't invent problems: only report what a check actually matched. If every check is clean, say nothing. - Fully local — no network, no secrets, no GitHub API. Run-outcome health is `skill-health`'s job; live attention is `heartbeat`'s. Stay in your lane: the **static config**. ## Log Append to `memory/logs/${today}.md` under a `### aeon-doctor` heading (the health loop parses this shape), as bullets: checks run, findings by severity (or `clean`), and whether a notification was sent. End-states: `AEON_DOCTOR_CLEAN`, `AEON_DOCTOR_FINDINGS`, `AEON_DOCTOR_ERROR`.
Ver en GitHub