[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6J3XNJCYEE5F71FTP76694R":3,"skill-same:01M6J3XNJCYEE5F71FTP76694R":71},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":37,"linkOnly":38,"markdownContent":39,"markdownBody":40,"frontMatter":41,"refName":47,"commitSha":48,"skillDirectory":49,"forksCount":50,"repositoryHtmlUrl":51,"repositoryHomepageUrl":52,"ownerHtmlUrl":53,"ownerBlogUrl":54,"ownerDescription":55,"updatedAt":56,"install":57,"resources":70},"01M6J3XNJCYEE5F71FTP76694R","aeon-update",null,"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.","aeonfun\u002Faeon-agent","aeonfun","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F287715280?v=4","01M6CZ23C6MXGMF4N0V1DZ1247","aeon","01M6J3TN6FHJPWGPAR9ZVJY6FP",12,"MIT","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Faeon-update\u002FSKILL.md",[],[19,12,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],"update","aeon update","new","dev","upstream","repo","core","commits","pr","canon","pull","framework","operator","config","write","updates","Meta","never","2026-11-10 10:12:30",false,"---\nname: aeon-update\ndescription: 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.\nmetadata:\n  title: Aeon Update\n  category: core\n  var: \"\"\n  tags:\n    - dev\n    - meta\n  mode: write\n---\n> **${var}** — mode selector; space-separated tokens, order-independent, all optional:\n> - **mode** (`sync` | `report`, default `sync`) — `sync` opens a PR with the framework changes; `report` computes the delta and notifies, mutating nothing (dry run).\n> - **`repo=owner\u002Fname`** — override the upstream source repo (else auto-resolved from this instance's `parent`, falling back to `aeonfun\u002Faeon`).\n> - **`reset=\u003Csha|fork-point>`** — force the stored baseline to `\u003Csha>` (or the merge-base with upstream) before running. Recovery \u002F backfill lever.\n>\n> Empty ⇒ **sync** from the auto-resolved upstream. Examples: `` · `report` · `repo=aeonfun\u002Faeon` · `reset=fork-point`.\n\nToday is ${today}. This is the fleet's **downstream updater** - the counterpart to `fork-fleet`. `fork-fleet` looks *outward* from the parent to find work in the forks worth pulling *up*; this skill runs *inside* an instance and pulls the parent's shipped framework changes *down* - new skills, script\u002Fharness fixes, workflow and doc updates - and lands them as a reviewable PR. It is how an instance stays current with `aeonfun\u002Faeon` without a hand-run rsync-overlay rebase.\n\n## Operating principles\n\n- **PR, never push to `main`.** Every framework change ships as one reviewable PR. The operator merges.\n- **Never clobber operator config.** `aeon.yml`, `STRATEGY.md`, `soul\u002F`, `memory\u002F`, `output\u002F`, `.mcp.json` and the git-derived catalogs are instance-owned. Upstream changes to them are **surfaced for manual review in the PR body**, never written into the tree.\n- **3-way, not blind overwrite.** A framework file the operator has already customized is **auto-merged** when its local edits and upstream's edits touch disjoint regions (a real `git merge-file` 3-way, S6); it is only listed as a **conflict** for a human when the same lines changed on both sides. This is what lets a hand-narrowed workflow keep receiving unrelated upstream fixes without a manual merge every run.\n- **Silent when in sync.** Baseline == upstream HEAD ⇒ nothing to do, no notification.\n- **The baseline is the watermark, and it advances by merge.** The new baseline SHA is written *only into the PR branch commit* (never main, never the run log), so the watermark only moves when the operator merges the PR, and one sync PR is open at a time. Unresolved conflicts are tracked separately so advancing the baseline can never silently drop them.\n\n---\n\n## Steps\n\nEvery command below is **one Bash call**: run it, read the value it prints, and type that value into the next command (shown as `\u003CUPSTREAM>`, `\u003CHEAD_SHA>`, ...). Never assign shell variables (`X=$(...)`), chain with `&&`\u002F`;`, redirect into a file, or write a script and run it with `bash`: the permission layer refuses all of those in this run. Create or change files with Write\u002FEdit.\n\n### S0. Bootstrap + load state\n\nIf `memory\u002Ftopics\u002Faeon-update-state.json` does not exist, create it with Write:\n\n```json\n{\"baseline_sha\": null, \"upstream\": null, \"last_run\": null, \"last_pr\": null, \"pending_conflicts\": []}\n```\n\nRead `memory\u002FMEMORY.md` for context and scan the last ~3 days of `memory\u002Flogs\u002F` - drop anything already reported so a repeat run isn't re-sent. Read the state file:\n- `BASELINE` = `.baseline_sha` (the upstream commit this instance was last synced to).\n- `PENDING` = `.pending_conflicts` (files surfaced as conflicts in a prior run, not yet resolved).\n\n### S1. Parse `${var}`\n\n- `MODE` = `report` if the token `report` (or `dry`) is present, else `sync`.\n- `REPO_OVERRIDE` = value of a `repo=owner\u002Fname` token, if any.\n- `RESET` = value of a `reset=` token, if any (`fork-point` or a 7-40 char SHA).\n\n### S2. Resolve the upstream source\n\n```bash\ngh repo view --json nameWithOwner -q .nameWithOwner                # -> SELF\ngh api \"repos\u002F\u003CSELF>\" --jq '.parent.full_name \u002F\u002F empty'            # -> UPSTREAM (skip when REPO_OVERRIDE is set)\n```\n\n`UPSTREAM` = `REPO_OVERRIDE`, else the parent, else `aeonfun\u002Faeon` when the parent is empty.\n\nIf `UPSTREAM` == `SELF`, this instance **is** canon - there is nothing upstream to pull. Write status `AEON_UPDATE_IS_UPSTREAM` to `memory\u002Flogs\u002F${today}.md`, send **no** notification, and stop.\n\n```bash\ngh api \"repos\u002F\u003CUPSTREAM>\" --jq '.default_branch'                    # -> UP_DEFAULT\ngh api \"repos\u002F\u003CUPSTREAM>\u002Fcommits\u002F\u003CUP_DEFAULT>\" --jq '.sha'          # -> HEAD_SHA (HEAD7 = first 7 chars)\n```\n\n### S3. Establish \u002F reset the baseline\n\n- **`reset=fork-point`** → run `git rev-parse HEAD` (-> LOCAL_HEAD), then `gh api \"repos\u002F\u003CUPSTREAM>\u002Fcompare\u002F\u003CHEAD_SHA>...\u003CLOCAL_HEAD>\" --jq '.merge_base_commit.sha'` -> `BASELINE`. `reset=\u003Csha>` → `BASELINE=\u003Csha>`. Persist it to the state file with Edit, then continue.\n- **First run (`BASELINE` null and no `reset`):** a fresh instance already carries all of canon from fork time, so there is no delta to apply - just **anchor the watermark**. Set `baseline_sha = HEAD_SHA`, write state, log `AEON_UPDATE_BASELINE_SET`, send a one-line notify (`baseline initialized at \u003Chead7>; future runs sync from here`), and stop. (To backfill everything since the fork point instead, re-run with `reset=fork-point`.)\n- **`BASELINE` == `HEAD_SHA`:** in sync. Run the S4 `plan` command anyway: it returns `AEON_UPDATE_IN_SYNC` with `pending_conflicts` re-verified against HEAD. If that list differs from `PENDING` (a prior conflict was resolved), write it into the state file with Edit. Log `AEON_UPDATE_IN_SYNC`, notify nothing, stop.\n- **A sync PR is still open (sync mode):** the state file on `main` still holds the old baseline until that PR merges, so a second PR from it would carry the same commits again and two branches would each move the watermark. Check before doing any work, including before persisting a `reset=`:\n\n  ```bash\n  gh pr list --repo \"\u003CSELF>\" --state open --base \"\u003CUP_DEFAULT>\" --limit 100 --json number,headRefName,url --jq '[.[] | select(.headRefName | startswith(\"aeon-update\u002Fsync-\"))] | sort_by(.number) | last \u002F\u002F empty | \"\\(.number) \\(.headRefName) \\(.url)\"'\n  ```\n\n  If it prints a PR (`OPEN_PR`): write nothing (no state file, no branch, no PR) and log `AEON_UPDATE_PR_OPEN` with the PR and its range. If its branch already ends in `HEAD_SHA`'s first 7 chars, stay silent; if upstream has moved past it, send one line (`PR #N still open; upstream is M commits past it, merge or close it and the next run syncs the rest`, `--mute-key \"aeon-update:${HEAD7}\"`). Stop. `report` mode skips this check and notes the open PR in its report.\n\n### S4-S6. Plan: compare, partition, 3-way classify\n\nAll of S4-S8's file work is done by `scripts\u002Faeon_update.py`, in single commands. **Do not re-implement it in the shell**: helper functions, loops, redirects into files and `bash \u003Cscript>` are all refused in this run, so a hand-written classify script ends the run with nothing synced (miroshark-aeon, 2026-10-21). Run:\n\n```bash\npython3 scripts\u002Faeon_update.py plan --upstream \"\u003CUPSTREAM>\" --baseline \"\u003CBASELINE>\" --head \"\u003CHEAD_SHA>\"\n```\n\nIt prints a JSON summary and mutates nothing in the repo (its scratch goes to `${RUNNER_TEMP:-\u002Ftmp}\u002Faeon-update\u002F`). Read `status`:\n- `AEON_UPDATE_IN_SYNC`: handle as S3's in-sync case.\n- `AEON_UPDATE_BASELINE_UNREACHABLE` (exit 3; the baseline is not an ancestor of HEAD - history rewrite or unrelated repo): stop with that status and notify the operator to re-run with `reset=fork-point` or `reset=\u003Csha>`.\n- `AEON_UPDATE_ERROR` (exit 2): stop, log the `error`, and notify it. Never fall back to classifying by hand.\n- `AEON_UPDATE_PLANNED`: the summary has `counts`, the `clean` paths per disposition, `conflicts` (path, reason, in-range `upstream_commits`, and a `diff` file with upstream's BASELINE..HEAD diff), `operator` (OPERATOR-owned paths upstream changed, each with a `diff` file), `unreadable`, the recomputed `pending_conflicts`, and the `commits` in range. Read the `diff` files you need with the Read tool.\n\nWhat the plan decides, so you can explain it in the PR:\n\n**Changed paths** are every path whose entry differs between upstream's BASELINE and HEAD trees (no 300-file compare cap). A rename is a delete of the old path plus an add of the new one.\n\n**OPERATOR-owned** (surfaced, never written):\n\n```\naeon.yml            STRATEGY.md         soul\u002F**             memory\u002F**\noutput\u002F**           .mcp.json           .env*               aeon.db\nskills.lock         eyebrowlock.json    .claude\u002F**  (except .claude\u002Fskills\u002Faeon\u002F**)\ncatalog\u002F*.json  (except catalog\u002Fskill-packs.json)\napps\u002Fdashboard\u002Foutputs\u002F**       .github\u002Fdependabot.yml\n```\n\nEverything else is **OWNED**: `skills\u002F**`, `scripts\u002F**`, `bin\u002F**`, `harness-adapter\u002F**`, `.github\u002F**`, `apps\u002F**`, `docs\u002F**`, `CLAUDE.md`, `AGENTS.md`, `CHANGELOG.md`, `.gitignore`, `eyebrow.policy.json`, `catalog\u002Fskill-packs.json`, root helpers. `catalog\u002F*.json` and `eyebrowlock.json` are never copied from upstream: apply regenerates them from the synced sources.\n\n**`catalog\u002Fskill-packs.json` is synced, not regenerated** (it has no generator and is hand-maintained with the Listed packs table in `docs\u002Fcommunity-skill-packs.md`; `ci-skill-packs` fails when they disagree, aeon-agent#232). Its **pack-registry unit** (`catalog\u002Fskill-packs.json`, `docs\u002Fcommunity-skill-packs.md`, `scripts\u002Fvalidate-skill-packs.mjs`, `scripts\u002Ftests\u002Ftest_validate_skill_packs.sh`, `.github\u002Fworkflows\u002Fci-skill-packs.yml`) is classified together and is all-or-nothing: one CONFLICT member demotes the rest to CONFLICT `skill-packs-unit`. A local registry that lists no pack upstream HEAD lacks is a frozen copy, not an edit, and is CLEAN-UPDATEd (`stale-copy`).\n\n**Dispositions** (local copy vs upstream@BASELINE vs upstream@HEAD; equal = same content and exec bit):\n\n| Upstream | Local | Disposition |\n|----------|-------|-------------|\n| unchanged | any | SKIP |\n| added | absent \u002F same as HEAD \u002F different | CLEAN-ADD \u002F SKIP \u002F CONFLICT `local-collision` |\n| modified | same as HEAD | SKIP |\n| modified | same as BASELINE | CLEAN-UPDATE |\n| modified | an older upstream version, byte for byte | CLEAN-UPDATE `stale-copy` (never 3-way merged: the merge would read the upstream lines it lacks as local deletions and keep them out) |\n| modified | absent | CONFLICT `deleted-locally` |\n| modified | customized | `git merge-file --diff3`: disjoint hunks -> CLEAN-MERGE (both edits kept), overlap -> CONFLICT `operator-customized` |\n| removed | absent \u002F same as BASELINE \u002F edited | SKIP \u002F CLEAN-DELETE \u002F CONFLICT `removed-upstream-local-edited` |\n\nA removed path of a skill upstream retired (`skills\u002F\u003Cname>\u002FSKILL.md` gone from HEAD) whose `\u003Cname>` is `enabled: true` in the operator's `aeon.yml` is never deleted: CONFLICT `enabled-skill-removed-upstream`, its own loud PR-body section (S9). Fork-only skills are never touched (only upstream paths are ever in the change set). A blob the API cannot return is `UNREADABLE`: list it and continue with a partial sync.\n\n**Held stale copies.** Every `pending_conflicts` entry is re-checked on each run, also when upstream did not touch it in range. A local file byte-identical to one of its last 30 upstream versions was never customized (an earlier sync held it back), so it takes HEAD (CLEAN-UPDATE `stale-copy`) and leaves the list. The plan lists both kinds under `stale_copies`. To keep an older upstream version on purpose, change one line of it (a comment is enough) so it no longer matches upstream byte for byte.\n\nIn `report` mode stop after the plan and go to S10.\n\n### S7-S8. Apply on a branch (sync mode only)\n\n```bash\ngit checkout -b \"aeon-update\u002Fsync-\u003CHEAD7>\"\npython3 scripts\u002Faeon_update.py apply\n```\n\n`apply` refuses to run off an `aeon-update\u002F*` branch. It re-checks each file against the plan (a file that changed since is CONFLICT `changed-since-plan`), then:\n\n1. Writes every CLEAN-ADD \u002F CLEAN-UPDATE \u002F CLEAN-MERGE (with upstream's exec bit) and `git rm`s every CLEAN-DELETE. A written `.json`\u002F`.yml` that does not parse is put back and becomes CONFLICT `parse-failed`.\n2. Runs `node scripts\u002Fvalidate-skill-packs.mjs` when a pack-registry member was applied; on failure it puts the unit back as CONFLICT `skill-packs-parity` and returns the validator output as `skill_packs_validator`.\n3. **Eyebrow lock.** When any `skills\u002F**` path was applied, it fetches the `eyebrow` binary at the exact version `ci-skill-integrity.yml` runs (the step's `version:` input), checks it against the release's `checksums.txt`, and rescans `eyebrowlock.json` with a scrubbed env (`eyebrow: \"scanned\"`). If the binary is unavailable or the scan fails, the fail-safe never ships a skill against a stale lock entry: for a new skill, an applied `SKILL.md` whose lock entry has findings (an edit that moves lines relocates a finding, which `eyebrow verify --ci` reports as capability expansion), or a skill whose upstream lock `capabilities` changed between BASELINE and HEAD (miroshark-aeon#183), it carries upstream's HEAD lock entry when the skill's tree is byte-identical to HEAD (`eyebrow_carried`), else puts the skill back as CONFLICT `needs-eyebrowlock-scan` (`eyebrow_held`).\n4. Copies each new skill's glyph from upstream's `catalog\u002Fskill-icons.json` (hand-authored, so the generator cannot invent it; `glyphs_carried`), then regenerates `bin\u002Fgenerate-skills-json`, `bin\u002Fgenerate-packs-json`, `bin\u002Fgenerate-skill-icons` and `node scripts\u002Fgen-agents-md.js` from the final tree. Failures are listed in `catalog_errors`.\n5. Runs `node scripts\u002Fvalidate-config.js aeon.yml`; its output, if it flagged anything, is `validate_config` (may be pre-existing drift: note it, do not abort).\n6. Recomputes `pending_conflicts`: this run's conflicts plus each prior entry that is still divergent (local differs from HEAD). Prior entries applied this run (CLEAN-UPDATE or CLEAN-MERGE) are resolved; an `enabled-skill-removed-upstream` entry resolves once the skill is disabled or upstream re-adds it.\n7. Writes `memory\u002Ftopics\u002Faeon-update-state.json` in the branch: `baseline_sha` = HEAD, `upstream`, `last_run`, `last_pr: null`, `applied` counts and `pending_conflicts` (other keys kept). It must reach `main` only through the PR merge: if the new baseline landed on `main` while the PR is open, the PR would conflict on the state file, and closing it unmerged would make the next run skip its commits.\n\nIts JSON result (`status`, the final `clean` lists, `conflicts`, `operator`, `pending_conflicts`, and the notes above) is also saved as `result.json` in its work dir. Statuses:\n- `AEON_UPDATE_APPLIED`: go to S9.\n- `AEON_UPDATE_MANUAL_ONLY` (nothing clean was left to apply): open no PR. Run `git checkout -- .`, then `git checkout \u003CUP_DEFAULT>` and `git branch -D aeon-update\u002Fsync-\u003CHEAD7>`, log `AEON_UPDATE_MANUAL_ONLY`, and go to S10 (notify that the sync needs a manual merge).\n- `AEON_UPDATE_ERROR`: same cleanup, log the error, notify it.\n\n### S9. Commit + open the PR (sync mode; skip in report mode)\n\nStage the synced files and the state file only. Never `memory\u002Flogs\u002F`, `token-usage.csv`, `output\u002F` or the dashboard feed: the run writes those again after this commit, and the post-run commit moves them to `main`, so a copy in the branch makes the PR conflict with `main` and drags the branch's state file onto `main` with it.\n\nWrite `\u002Ftmp\u002Faeon-update-commit.txt` and `\u002Ftmp\u002Faeon-update-pr-body.md` (below) with the Write tool, then run each line as its own call (`notify`, `notify-jsonrender` and `secretcurl` are the run's generated helpers):\n\n```bash\ngit add -A -- . ':(exclude)memory' ':(exclude)output' ':(exclude)apps\u002Fdashboard\u002Foutputs' ':(exclude)notify' ':(exclude)notify-jsonrender' ':(exclude)secretcurl'\ngit add -- memory\u002Ftopics\u002Faeon-update-state.json\ngit commit -F \u002Ftmp\u002Faeon-update-commit.txt\ngit push -u origin \"aeon-update\u002Fsync-\u003CHEAD7>\"\ngh pr create --repo \"\u003CSELF>\" --base \"\u003CUP_DEFAULT>\" --title \"aeon-update: sync \u003CN_COMMITS> upstream commits (\u003CBASE7>..\u003CHEAD7>)\" --body-file \u002Ftmp\u002Faeon-update-pr-body.md\n```\n\nNever inline the commit message with `-m` (backticks or `$()` in it get shell-substituted).\n\n`\u002Ftmp\u002Faeon-update-commit.txt`:\n```\naeon-update: sync upstream ${BASE7}..${HEAD7}\n\n${N_COMMITS} upstream commits from ${UPSTREAM}. ${N_APPLIED} files applied cleanly, ${N_CONFLICT} need manual review. Baseline advanced to ${HEAD7}.\n```\n\nPR body (`\u002Ftmp\u002Faeon-update-pr-body.md`) - only include sections that have content:\n```markdown\n## Upstream sync: `${UPSTREAM}` `${BASE7}..${HEAD7}`\n\n**${N_COMMITS} commits** ({earliest date} → {latest date}) · **${N_APPLIED} applied** · **${N_CONFLICT} manual** · baseline → `${HEAD7}`.\n\n### Applied cleanly\n- **New skills:** `foo`, `bar`   _(regenerated catalogs + agents.md + skill-icons)_\n- **Modified skills:** `baz`\n- **Scripts \u002F harness:** `scripts\u002Fnotify.sh`, ...\n- **Workflows:** `.github\u002Fworkflows\u002F...`\n- **Stale copies replaced:** `scripts\u002Flangfuse-otel.sh`, ...  _(the plan's `stale_copies`: byte-identical to an older upstream version, so not a local edit; held or 3-way merged before)_\n- **Auto-merged (3-way):** `.github\u002Fworkflows\u002Faeon.yml`, ...  _(your local customization kept; upstream's disjoint changes applied - review the merged hunks)_\n- **Docs \u002F other:** `docs\u002F...`, `CLAUDE.md`, ...\n\n### ⚠️ Currently-enabled skills removed upstream\nFor each CONFLICT with reason `enabled-skill-removed-upstream`:\n- `verdikta-hunter` - enabled in your `aeon.yml`, deleted upstream in {commit(s)}. **Not deleted here** so nothing breaks. Pick one: keep it as a fork-only skill going forward (nothing else to do), or disable it in `aeon.yml` to match upstream's current default set.\n\n### Needs manual review (conflicts - your local copy diverges from upstream)\nFor each *other* conflict reason (`enabled-skill-removed-upstream` is covered above, don't duplicate it here): what upstream changed and why it wasn't auto-applied.\n- `scripts\u002Ffoo.sh` - you customized this locally; upstream changed it in {upstream_commits}. Upstream diff (from the conflict's `diff` file, trimmed):\n  ```diff\n  {short upstream base..head diff for the file}\n  ```\n\n### Operator config changed upstream (not auto-applied - reconcile by hand)\n- `aeon.yml` - upstream added skills \u002F changed defaults: {summary}. Merge the new entries you want (keep your enable\u002Fschedule\u002Fmodel choices).\n- `soul\u002F…`, `STRATEGY.md` - {summary, if changed}\n\n### Upstream commits\n| SHA | Summary |\n|-----|---------|\n| abc1234 | ... |\n```\n\nCapture the PR URL; write it into `last_pr` in the branch's state file with Edit, then `git add -- memory\u002Ftopics\u002Faeon-update-state.json` and `git commit --amend --no-edit` (two calls; the post-run step pushes the amended branch) so the merged watermark records its own PR.\n\n### S10. Log + notify\n\nStay on the PR branch and do not commit from here on: the post-run commit pushes the branch and records the log and other run state on `main`, while the state file stays in the branch. Do not check out `main` or touch the state file again.\n\nAppend to `memory\u002Flogs\u002F${today}.md` under `### aeon-update`: status, `UPSTREAM`, `${BASE7}..${HEAD7}`, applied\u002Fconflict counts, PR URL (or `report`\u002F`manual-only`), and `pending_conflicts` count.\n\n**Notify only on signal** - match `soul\u002F` voice if present. Send when there is a PR, a report with changes, or a manual-only situation; stay silent for `IN_SYNC` \u002F `BASELINE_SET`-with-nothing. Keep it ≤4000 chars:\n\n```\n*aeon-update - ${today}*\n{verdict: \"synced N commits → PR\" | \"N changes need manual merge\" | \"report: N commits behind\"}\n\nUpstream `${UPSTREAM}` is ${AHEAD} commits ahead. Applied ${N_APPLIED} cleanly, ${N_CONFLICT} need review.\n{Top applied highlight: e.g. \"new skill: `token-radar`; harness fix in run-harness\"}\n{If conflicts: \"Manual: `aeon.yml` (new skills), `scripts\u002Ffoo.sh` (local edit)\"}\n\nPR: {url}   (or \"dry run - nothing changed\")\n```\n\nPass `--mute-key \"aeon-update:${HEAD7}\"` so a muted sync doesn't re-ping for the same upstream HEAD.\n\n## Exit taxonomy\n\n| Code | When | Notify |\n|------|------|--------|\n| `AEON_UPDATE_OK` | PR opened with ≥1 clean file (conflicts, if any, listed in it) | Yes - PR link |\n| `AEON_UPDATE_MANUAL_ONLY` | Upstream changed only operator-owned \u002F conflicting files - no clean apply, no PR | Yes - manual call-out |\n| `AEON_UPDATE_REPORT` | `report` mode - delta computed, nothing mutated | Yes - dry-run summary |\n| `AEON_UPDATE_IN_SYNC` | Baseline already == upstream HEAD | No (log only) |\n| `AEON_UPDATE_PR_OPEN` | An earlier `aeon-update\u002Fsync-*` PR is still open - nothing written | Only if upstream moved past it (one line) |\n| `AEON_UPDATE_BASELINE_SET` | First run - watermark anchored, nothing applied | One-line notify |\n| `AEON_UPDATE_IS_UPSTREAM` | This instance is the upstream repo itself | No (log only) |\n| `AEON_UPDATE_BASELINE_UNREACHABLE` | Baseline is not an ancestor of HEAD (history rewrite) | Yes - asks for `reset=` |\n| `AEON_UPDATE_ERROR` | `scripts\u002Faeon_update.py` could not read upstream or the repo - nothing applied | Yes - the error |\n\n## Constraints\n\n- **Never** push to `main` or auto-write an OPERATOR-owned path (`aeon.yml`, `soul\u002F`, `memory\u002F`, `STRATEGY.md`, `.mcp.json`, `output\u002F`).\n- **Never** delete a fork-only skill, and **never** copy `catalog\u002F*.json` \u002F `eyebrowlock.json` from upstream - regenerate them. Two exceptions: `catalog\u002Fskill-packs.json` has no generator and is synced as part of the pack-registry unit (S5), and a single skill's lock entry may be carried from upstream when that skill's tree matches upstream HEAD exactly (S7 fail-safe).\n- **Never** advance `baseline_sha` without carrying unresolved conflicts forward in `pending_conflicts`.\n- Cross-repo compare caps at 300 files - note `files_truncated=true` and let a follow-up run pick up the rest.\n- A clean, in-sync run is **correct**, not a failure - it notifies nothing.\n\n## Network note\n\nEvery network call is `gh api`, which authenticates via `GITHUB_TOKEN` automatically - no `curl`, no `.\u002Fsecretcurl`, no `$SECRET` on the command line for the Bash permission layer to refuse, and no secret beyond the default `GITHUB_TOKEN`. There are no irreversible side-effects: the skill's only mutation is a PR against this instance's own repo, which the operator reviews and merges. Retry policy: on `403` with `X-RateLimit-Remaining: 0`, sleep 60s and retry once; on a persistent contents-API failure for one file, mark it `UNREADABLE` in the report and continue with a partial sync rather than aborting the whole run.\n","> **${var}** — mode selector; space-separated tokens, order-independent, all optional:\n> - **mode** (`sync` | `report`, default `sync`) — `sync` opens a PR with the framework changes; `report` computes the delta and notifies, mutating nothing (dry run).\n> - **`repo=owner\u002Fname`** — override the upstream source repo (else auto-resolved from this instance's `parent`, falling back to `aeonfun\u002Faeon`).\n> - **`reset=\u003Csha|fork-point>`** — force the stored baseline to `\u003Csha>` (or the merge-base with upstream) before running. Recovery \u002F backfill lever.\n>\n> Empty ⇒ **sync** from the auto-resolved upstream. Examples: `` · `report` · `repo=aeonfun\u002Faeon` · `reset=fork-point`.\n\nToday is ${today}. This is the fleet's **downstream updater** - the counterpart to `fork-fleet`. `fork-fleet` looks *outward* from the parent to find work in the forks worth pulling *up*; this skill runs *inside* an instance and pulls the parent's shipped framework changes *down* - new skills, script\u002Fharness fixes, workflow and doc updates - and lands them as a reviewable PR. It is how an instance stays current with `aeonfun\u002Faeon` without a hand-run rsync-overlay rebase.\n\n## Operating principles\n\n- **PR, never push to `main`.** Every framework change ships as one reviewable PR. The operator merges.\n- **Never clobber operator config.** `aeon.yml`, `STRATEGY.md`, `soul\u002F`, `memory\u002F`, `output\u002F`, `.mcp.json` and the git-derived catalogs are instance-owned. Upstream changes to them are **surfaced for manual review in the PR body**, never written into the tree.\n- **3-way, not blind overwrite.** A framework file the operator has already customized is **auto-merged** when its local edits and upstream's edits touch disjoint regions (a real `git merge-file` 3-way, S6); it is only listed as a **conflict** for a human when the same lines changed on both sides. This is what lets a hand-narrowed workflow keep receiving unrelated upstream fixes without a manual merge every run.\n- **Silent when in sync.** Baseline == upstream HEAD ⇒ nothing to do, no notification.\n- **The baseline is the watermark, and it advances by merge.** The new baseline SHA is written *only into the PR branch commit* (never main, never the run log), so the watermark only moves when the operator merges the PR, and one sync PR is open at a time. Unresolved conflicts are tracked separately so advancing the baseline can never silently drop them.\n\n---\n\n## Steps\n\nEvery command below is **one Bash call**: run it, read the value it prints, and type that value into the next command (shown as `\u003CUPSTREAM>`, `\u003CHEAD_SHA>`, ...). Never assign shell variables (`X=$(...)`), chain with `&&`\u002F`;`, redirect into a file, or write a script and run it with `bash`: the permission layer refuses all of those in this run. Create or change files with Write\u002FEdit.\n\n### S0. Bootstrap + load state\n\nIf `memory\u002Ftopics\u002Faeon-update-state.json` does not exist, create it with Write:\n\n```json\n{\"baseline_sha\": null, \"upstream\": null, \"last_run\": null, \"last_pr\": null, \"pending_conflicts\": []}\n```\n\nRead `memory\u002FMEMORY.md` for context and scan the last ~3 days of `memory\u002Flogs\u002F` - drop anything already reported so a repeat run isn't re-sent. Read the state file:\n- `BASELINE` = `.baseline_sha` (the upstream commit this instance was last synced to).\n- `PENDING` = `.pending_conflicts` (files surfaced as conflicts in a prior run, not yet resolved).\n\n### S1. Parse `${var}`\n\n- `MODE` = `report` if the token `report` (or `dry`) is present, else `sync`.\n- `REPO_OVERRIDE` = value of a `repo=owner\u002Fname` token, if any.\n- `RESET` = value of a `reset=` token, if any (`fork-point` or a 7-40 char SHA).\n\n### S2. Resolve the upstream source\n\n```bash\ngh repo view --json nameWithOwner -q .nameWithOwner                # -> SELF\ngh api \"repos\u002F\u003CSELF>\" --jq '.parent.full_name \u002F\u002F empty'            # -> UPSTREAM (skip when REPO_OVERRIDE is set)\n```\n\n`UPSTREAM` = `REPO_OVERRIDE`, else the parent, else `aeonfun\u002Faeon` when the parent is empty.\n\nIf `UPSTREAM` == `SELF`, this instance **is** canon - there is nothing upstream to pull. Write status `AEON_UPDATE_IS_UPSTREAM` to `memory\u002Flogs\u002F${today}.md`, send **no** notification, and stop.\n\n```bash\ngh api \"repos\u002F\u003CUPSTREAM>\" --jq '.default_branch'                    # -> UP_DEFAULT\ngh api \"repos\u002F\u003CUPSTREAM>\u002Fcommits\u002F\u003CUP_DEFAULT>\" --jq '.sha'          # -> HEAD_SHA (HEAD7 = first 7 chars)\n```\n\n### S3. Establish \u002F reset the baseline\n\n- **`reset=fork-point`** → run `git rev-parse HEAD` (-> LOCAL_HEAD), then `gh api \"repos\u002F\u003CUPSTREAM>\u002Fcompare\u002F\u003CHEAD_SHA>...\u003CLOCAL_HEAD>\" --jq '.merge_base_commit.sha'` -> `BASELINE`. `reset=\u003Csha>` → `BASELINE=\u003Csha>`. Persist it to the state file with Edit, then continue.\n- **First run (`BASELINE` null and no `reset`):** a fresh instance already carries all of canon from fork time, so there is no delta to apply - just **anchor the watermark**. Set `baseline_sha = HEAD_SHA`, write state, log `AEON_UPDATE_BASELINE_SET`, send a one-line notify (`baseline initialized at \u003Chead7>; future runs sync from here`), and stop. (To backfill everything since the fork point instead, re-run with `reset=fork-point`.)\n- **`BASELINE` == `HEAD_SHA`:** in sync. Run the S4 `plan` command anyway: it returns `AEON_UPDATE_IN_SYNC` with `pending_conflicts` re-verified against HEAD. If that list differs from `PENDING` (a prior conflict was resolved), write it into the state file with Edit. Log `AEON_UPDATE_IN_SYNC`, notify nothing, stop.\n- **A sync PR is still open (sync mode):** the state file on `main` still holds the old baseline until that PR merges, so a second PR from it would carry the same commits again and two branches would each move the watermark. Check before doing any work, including before persisting a `reset=`:\n\n  ```bash\n  gh pr list --repo \"\u003CSELF>\" --state open --base \"\u003CUP_DEFAULT>\" --limit 100 --json number,headRefName,url --jq '[.[] | select(.headRefName | startswith(\"aeon-update\u002Fsync-\"))] | sort_by(.number) | last \u002F\u002F empty | \"\\(.number) \\(.headRefName) \\(.url)\"'\n  ```\n\n  If it prints a PR (`OPEN_PR`): write nothing (no state file, no branch, no PR) and log `AEON_UPDATE_PR_OPEN` with the PR and its range. If its branch already ends in `HEAD_SHA`'s first 7 chars, stay silent; if upstream has moved past it, send one line (`PR #N still open; upstream is M commits past it, merge or close it and the next run syncs the rest`, `--mute-key \"aeon-update:${HEAD7}\"`). Stop. `report` mode skips this check and notes the open PR in its report.\n\n### S4-S6. Plan: compare, partition, 3-way classify\n\nAll of S4-S8's file work is done by `scripts\u002Faeon_update.py`, in single commands. **Do not re-implement it in the shell**: helper functions, loops, redirects into files and `bash \u003Cscript>` are all refused in this run, so a hand-written classify script ends the run with nothing synced (miroshark-aeon, 2026-10-21). Run:\n\n```bash\npython3 scripts\u002Faeon_update.py plan --upstream \"\u003CUPSTREAM>\" --baseline \"\u003CBASELINE>\" --head \"\u003CHEAD_SHA>\"\n```\n\nIt prints a JSON summary and mutates nothing in the repo (its scratch goes to `${RUNNER_TEMP:-\u002Ftmp}\u002Faeon-update\u002F`). Read `status`:\n- `AEON_UPDATE_IN_SYNC`: handle as S3's in-sync case.\n- `AEON_UPDATE_BASELINE_UNREACHABLE` (exit 3; the baseline is not an ancestor of HEAD - history rewrite or unrelated repo): stop with that status and notify the operator to re-run with `reset=fork-point` or `reset=\u003Csha>`.\n- `AEON_UPDATE_ERROR` (exit 2): stop, log the `error`, and notify it. Never fall back to classifying by hand.\n- `AEON_UPDATE_PLANNED`: the summary has `counts`, the `clean` paths per disposition, `conflicts` (path, reason, in-range `upstream_commits`, and a `diff` file with upstream's BASELINE..HEAD diff), `operator` (OPERATOR-owned paths upstream changed, each with a `diff` file), `unreadable`, the recomputed `pending_conflicts`, and the `commits` in range. Read the `diff` files you need with the Read tool.\n\nWhat the plan decides, so you can explain it in the PR:\n\n**Changed paths** are every path whose entry differs between upstream's BASELINE and HEAD trees (no 300-file compare cap). A rename is a delete of the old path plus an add of the new one.\n\n**OPERATOR-owned** (surfaced, never written):\n\n```\naeon.yml            STRATEGY.md         soul\u002F**             memory\u002F**\noutput\u002F**           .mcp.json           .env*               aeon.db\nskills.lock         eyebrowlock.json    .claude\u002F**  (except .claude\u002Fskills\u002Faeon\u002F**)\ncatalog\u002F*.json  (except catalog\u002Fskill-packs.json)\napps\u002Fdashboard\u002Foutputs\u002F**       .github\u002Fdependabot.yml\n```\n\nEverything else is **OWNED**: `skills\u002F**`, `scripts\u002F**`, `bin\u002F**`, `harness-adapter\u002F**`, `.github\u002F**`, `apps\u002F**`, `docs\u002F**`, `CLAUDE.md`, `AGENTS.md`, `CHANGELOG.md`, `.gitignore`, `eyebrow.policy.json`, `catalog\u002Fskill-packs.json`, root helpers. `catalog\u002F*.json` and `eyebrowlock.json` are never copied from upstream: apply regenerates them from the synced sources.\n\n**`catalog\u002Fskill-packs.json` is synced, not regenerated** (it has no generator and is hand-maintained with the Listed packs table in `docs\u002Fcommunity-skill-packs.md`; `ci-skill-packs` fails when they disagree, aeon-agent#232). Its **pack-registry unit** (`catalog\u002Fskill-packs.json`, `docs\u002Fcommunity-skill-packs.md`, `scripts\u002Fvalidate-skill-packs.mjs`, `scripts\u002Ftests\u002Ftest_validate_skill_packs.sh`, `.github\u002Fworkflows\u002Fci-skill-packs.yml`) is classified together and is all-or-nothing: one CONFLICT member demotes the rest to CONFLICT `skill-packs-unit`. A local registry that lists no pack upstream HEAD lacks is a frozen copy, not an edit, and is CLEAN-UPDATEd (`stale-copy`).\n\n**Dispositions** (local copy vs upstream@BASELINE vs upstream@HEAD; equal = same content and exec bit):\n\n| Upstream | Local | Disposition |\n|----------|-------|-------------|\n| unchanged | any | SKIP |\n| added | absent \u002F same as HEAD \u002F different | CLEAN-ADD \u002F SKIP \u002F CONFLICT `local-collision` |\n| modified | same as HEAD | SKIP |\n| modified | same as BASELINE | CLEAN-UPDATE |\n| modified | an older upstream version, byte for byte | CLEAN-UPDATE `stale-copy` (never 3-way merged: the merge would read the upstream lines it lacks as local deletions and keep them out) |\n| modified | absent | CONFLICT `deleted-locally` |\n| modified | customized | `git merge-file --diff3`: disjoint hunks -> CLEAN-MERGE (both edits kept), overlap -> CONFLICT `operator-customized` |\n| removed | absent \u002F same as BASELINE \u002F edited | SKIP \u002F CLEAN-DELETE \u002F CONFLICT `removed-upstream-local-edited` |\n\nA removed path of a skill upstream retired (`skills\u002F\u003Cname>\u002FSKILL.md` gone from HEAD) whose `\u003Cname>` is `enabled: true` in the operator's `aeon.yml` is never deleted: CONFLICT `enabled-skill-removed-upstream`, its own loud PR-body section (S9). Fork-only skills are never touched (only upstream paths are ever in the change set). A blob the API cannot return is `UNREADABLE`: list it and continue with a partial sync.\n\n**Held stale copies.** Every `pending_conflicts` entry is re-checked on each run, also when upstream did not touch it in range. A local file byte-identical to one of its last 30 upstream versions was never customized (an earlier sync held it back), so it takes HEAD (CLEAN-UPDATE `stale-copy`) and leaves the list. The plan lists both kinds under `stale_copies`. To keep an older upstream version on purpose, change one line of it (a comment is enough) so it no longer matches upstream byte for byte.\n\nIn `report` mode stop after the plan and go to S10.\n\n### S7-S8. Apply on a branch (sync mode only)\n\n```bash\ngit checkout -b \"aeon-update\u002Fsync-\u003CHEAD7>\"\npython3 scripts\u002Faeon_update.py apply\n```\n\n`apply` refuses to run off an `aeon-update\u002F*` branch. It re-checks each file against the plan (a file that changed since is CONFLICT `changed-since-plan`), then:\n\n1. Writes every CLEAN-ADD \u002F CLEAN-UPDATE \u002F CLEAN-MERGE (with upstream's exec bit) and `git rm`s every CLEAN-DELETE. A written `.json`\u002F`.yml` that does not parse is put back and becomes CONFLICT `parse-failed`.\n2. Runs `node scripts\u002Fvalidate-skill-packs.mjs` when a pack-registry member was applied; on failure it puts the unit back as CONFLICT `skill-packs-parity` and returns the validator output as `skill_packs_validator`.\n3. **Eyebrow lock.** When any `skills\u002F**` path was applied, it fetches the `eyebrow` binary at the exact version `ci-skill-integrity.yml` runs (the step's `version:` input), checks it against the release's `checksums.txt`, and rescans `eyebrowlock.json` with a scrubbed env (`eyebrow: \"scanned\"`). If the binary is unavailable or the scan fails, the fail-safe never ships a skill against a stale lock entry: for a new skill, an applied `SKILL.md` whose lock entry has findings (an edit that moves lines relocates a finding, which `eyebrow verify --ci` reports as capability expansion), or a skill whose upstream lock `capabilities` changed between BASELINE and HEAD (miroshark-aeon#183), it carries upstream's HEAD lock entry when the skill's tree is byte-identical to HEAD (`eyebrow_carried`), else puts the skill back as CONFLICT `needs-eyebrowlock-scan` (`eyebrow_held`).\n4. Copies each new skill's glyph from upstream's `catalog\u002Fskill-icons.json` (hand-authored, so the generator cannot invent it; `glyphs_carried`), then regenerates `bin\u002Fgenerate-skills-json`, `bin\u002Fgenerate-packs-json`, `bin\u002Fgenerate-skill-icons` and `node scripts\u002Fgen-agents-md.js` from the final tree. Failures are listed in `catalog_errors`.\n5. Runs `node scripts\u002Fvalidate-config.js aeon.yml`; its output, if it flagged anything, is `validate_config` (may be pre-existing drift: note it, do not abort).\n6. Recomputes `pending_conflicts`: this run's conflicts plus each prior entry that is still divergent (local differs from HEAD). Prior entries applied this run (CLEAN-UPDATE or CLEAN-MERGE) are resolved; an `enabled-skill-removed-upstream` entry resolves once the skill is disabled or upstream re-adds it.\n7. Writes `memory\u002Ftopics\u002Faeon-update-state.json` in the branch: `baseline_sha` = HEAD, `upstream`, `last_run`, `last_pr: null`, `applied` counts and `pending_conflicts` (other keys kept). It must reach `main` only through the PR merge: if the new baseline landed on `main` while the PR is open, the PR would conflict on the state file, and closing it unmerged would make the next run skip its commits.\n\nIts JSON result (`status`, the final `clean` lists, `conflicts`, `operator`, `pending_conflicts`, and the notes above) is also saved as `result.json` in its work dir. Statuses:\n- `AEON_UPDATE_APPLIED`: go to S9.\n- `AEON_UPDATE_MANUAL_ONLY` (nothing clean was left to apply): open no PR. Run `git checkout -- .`, then `git checkout \u003CUP_DEFAULT>` and `git branch -D aeon-update\u002Fsync-\u003CHEAD7>`, log `AEON_UPDATE_MANUAL_ONLY`, and go to S10 (notify that the sync needs a manual merge).\n- `AEON_UPDATE_ERROR`: same cleanup, log the error, notify it.\n\n### S9. Commit + open the PR (sync mode; skip in report mode)\n\nStage the synced files and the state file only. Never `memory\u002Flogs\u002F`, `token-usage.csv`, `output\u002F` or the dashboard feed: the run writes those again after this commit, and the post-run commit moves them to `main`, so a copy in the branch makes the PR conflict with `main` and drags the branch's state file onto `main` with it.\n\nWrite `\u002Ftmp\u002Faeon-update-commit.txt` and `\u002Ftmp\u002Faeon-update-pr-body.md` (below) with the Write tool, then run each line as its own call (`notify`, `notify-jsonrender` and `secretcurl` are the run's generated helpers):\n\n```bash\ngit add -A -- . ':(exclude)memory' ':(exclude)output' ':(exclude)apps\u002Fdashboard\u002Foutputs' ':(exclude)notify' ':(exclude)notify-jsonrender' ':(exclude)secretcurl'\ngit add -- memory\u002Ftopics\u002Faeon-update-state.json\ngit commit -F \u002Ftmp\u002Faeon-update-commit.txt\ngit push -u origin \"aeon-update\u002Fsync-\u003CHEAD7>\"\ngh pr create --repo \"\u003CSELF>\" --base \"\u003CUP_DEFAULT>\" --title \"aeon-update: sync \u003CN_COMMITS> upstream commits (\u003CBASE7>..\u003CHEAD7>)\" --body-file \u002Ftmp\u002Faeon-update-pr-body.md\n```\n\nNever inline the commit message with `-m` (backticks or `$()` in it get shell-substituted).\n\n`\u002Ftmp\u002Faeon-update-commit.txt`:\n```\naeon-update: sync upstream ${BASE7}..${HEAD7}\n\n${N_COMMITS} upstream commits from ${UPSTREAM}. ${N_APPLIED} files applied cleanly, ${N_CONFLICT} need manual review. Baseline advanced to ${HEAD7}.\n```\n\nPR body (`\u002Ftmp\u002Faeon-update-pr-body.md`) - only include sections that have content:\n```markdown\n## Upstream sync: `${UPSTREAM}` `${BASE7}..${HEAD7}`\n\n**${N_COMMITS} commits** ({earliest date} → {latest date}) · **${N_APPLIED} applied** · **${N_CONFLICT} manual** · baseline → `${HEAD7}`.\n\n### Applied cleanly\n- **New skills:** `foo`, `bar`   _(regenerated catalogs + agents.md + skill-icons)_\n- **Modified skills:** `baz`\n- **Scripts \u002F harness:** `scripts\u002Fnotify.sh`, ...\n- **Workflows:** `.github\u002Fworkflows\u002F...`\n- **Stale copies replaced:** `scripts\u002Flangfuse-otel.sh`, ...  _(the plan's `stale_copies`: byte-identical to an older upstream version, so not a local edit; held or 3-way merged before)_\n- **Auto-merged (3-way):** `.github\u002Fworkflows\u002Faeon.yml`, ...  _(your local customization kept; upstream's disjoint changes applied - review the merged hunks)_\n- **Docs \u002F other:** `docs\u002F...`, `CLAUDE.md`, ...\n\n### ⚠️ Currently-enabled skills removed upstream\nFor each CONFLICT with reason `enabled-skill-removed-upstream`:\n- `verdikta-hunter` - enabled in your `aeon.yml`, deleted upstream in {commit(s)}. **Not deleted here** so nothing breaks. Pick one: keep it as a fork-only skill going forward (nothing else to do), or disable it in `aeon.yml` to match upstream's current default set.\n\n### Needs manual review (conflicts - your local copy diverges from upstream)\nFor each *other* conflict reason (`enabled-skill-removed-upstream` is covered above, don't duplicate it here): what upstream changed and why it wasn't auto-applied.\n- `scripts\u002Ffoo.sh` - you customized this locally; upstream changed it in {upstream_commits}. Upstream diff (from the conflict's `diff` file, trimmed):\n  ```diff\n  {short upstream base..head diff for the file}\n  ```\n\n### Operator config changed upstream (not auto-applied - reconcile by hand)\n- `aeon.yml` - upstream added skills \u002F changed defaults: {summary}. Merge the new entries you want (keep your enable\u002Fschedule\u002Fmodel choices).\n- `soul\u002F…`, `STRATEGY.md` - {summary, if changed}\n\n### Upstream commits\n| SHA | Summary |\n|-----|---------|\n| abc1234 | ... |\n```\n\nCapture the PR URL; write it into `last_pr` in the branch's state file with Edit, then `git add -- memory\u002Ftopics\u002Faeon-update-state.json` and `git commit --amend --no-edit` (two calls; the post-run step pushes the amended branch) so the merged watermark records its own PR.\n\n### S10. Log + notify\n\nStay on the PR branch and do not commit from here on: the post-run commit pushes the branch and records the log and other run state on `main`, while the state file stays in the branch. Do not check out `main` or touch the state file again.\n\nAppend to `memory\u002Flogs\u002F${today}.md` under `### aeon-update`: status, `UPSTREAM`, `${BASE7}..${HEAD7}`, applied\u002Fconflict counts, PR URL (or `report`\u002F`manual-only`), and `pending_conflicts` count.\n\n**Notify only on signal** - match `soul\u002F` voice if present. Send when there is a PR, a report with changes, or a manual-only situation; stay silent for `IN_SYNC` \u002F `BASELINE_SET`-with-nothing. Keep it ≤4000 chars:\n\n```\n*aeon-update - ${today}*\n{verdict: \"synced N commits → PR\" | \"N changes need manual merge\" | \"report: N commits behind\"}\n\nUpstream `${UPSTREAM}` is ${AHEAD} commits ahead. Applied ${N_APPLIED} cleanly, ${N_CONFLICT} need review.\n{Top applied highlight: e.g. \"new skill: `token-radar`; harness fix in run-harness\"}\n{If conflicts: \"Manual: `aeon.yml` (new skills), `scripts\u002Ffoo.sh` (local edit)\"}\n\nPR: {url}   (or \"dry run - nothing changed\")\n```\n\nPass `--mute-key \"aeon-update:${HEAD7}\"` so a muted sync doesn't re-ping for the same upstream HEAD.\n\n## Exit taxonomy\n\n| Code | When | Notify |\n|------|------|--------|\n| `AEON_UPDATE_OK` | PR opened with ≥1 clean file (conflicts, if any, listed in it) | Yes - PR link |\n| `AEON_UPDATE_MANUAL_ONLY` | Upstream changed only operator-owned \u002F conflicting files - no clean apply, no PR | Yes - manual call-out |\n| `AEON_UPDATE_REPORT` | `report` mode - delta computed, nothing mutated | Yes - dry-run summary |\n| `AEON_UPDATE_IN_SYNC` | Baseline already == upstream HEAD | No (log only) |\n| `AEON_UPDATE_PR_OPEN` | An earlier `aeon-update\u002Fsync-*` PR is still open - nothing written | Only if upstream moved past it (one line) |\n| `AEON_UPDATE_BASELINE_SET` | First run - watermark anchored, nothing applied | One-line notify |\n| `AEON_UPDATE_IS_UPSTREAM` | This instance is the upstream repo itself | No (log only) |\n| `AEON_UPDATE_BASELINE_UNREACHABLE` | Baseline is not an ancestor of HEAD (history rewrite) | Yes - asks for `reset=` |\n| `AEON_UPDATE_ERROR` | `scripts\u002Faeon_update.py` could not read upstream or the repo - nothing applied | Yes - the error |\n\n## Constraints\n\n- **Never** push to `main` or auto-write an OPERATOR-owned path (`aeon.yml`, `soul\u002F`, `memory\u002F`, `STRATEGY.md`, `.mcp.json`, `output\u002F`).\n- **Never** delete a fork-only skill, and **never** copy `catalog\u002F*.json` \u002F `eyebrowlock.json` from upstream - regenerate them. Two exceptions: `catalog\u002Fskill-packs.json` has no generator and is synced as part of the pack-registry unit (S5), and a single skill's lock entry may be carried from upstream when that skill's tree matches upstream HEAD exactly (S7 fail-safe).\n- **Never** advance `baseline_sha` without carrying unresolved conflicts forward in `pending_conflicts`.\n- Cross-repo compare caps at 300 files - note `files_truncated=true` and let a follow-up run pick up the rest.\n- A clean, in-sync run is **correct**, not a failure - it notifies nothing.\n\n## Network note\n\nEvery network call is `gh api`, which authenticates via `GITHUB_TOKEN` automatically - no `curl`, no `.\u002Fsecretcurl`, no `$SECRET` on the command line for the Bash permission layer to refuse, and no secret beyond the default `GITHUB_TOKEN`. There are no irreversible side-effects: the skill's only mutation is a PR against this instance's own repo, which the operator reviews and merges. Retry policy: on `403` with `X-RateLimit-Remaining: 0`, sleep 60s and retry once; on a persistent contents-API failure for one file, mark it `UNREADABLE` in the report and continue with a partial sync rather than aborting the whole run.\n",{"name":5,"metadata":42,"description":7},{"var":43,"mode":33,"tags":44,"title":46,"category":25},"",[22,45],"meta","Aeon Update","main","ba8ac7e88c8ff955526557369fadc27eca9c1037","skills\u002Faeon-update",4,"https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent","https:\u002F\u002Ft.me\u002Faeon_agent","https:\u002F\u002Fgithub.com\u002Faeonfun","https:\u002F\u002Fwww.aeon.fun\u002F","The most autonomous agent framework.","2026-11-14 00:38:03",{"source":8,"skillName":5,"commands":58,"prompt":68,"zipAvailable":69},[59,62,65],{"tool":60,"command":61},"npx","npx skills add aeonfun\u002Faeon-agent --skill aeon-update",{"tool":63,"command":64},"bunx","bunx skills add aeonfun\u002Faeon-agent --skill aeon-update",{"tool":66,"command":67},"pnpm","pnpm dlx skills add aeonfun\u002Faeon-agent --skill aeon-update","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Faeon-update\u002FSKILL.md",true,[],[72,110,132,158,181,204],{"skillNo":73,"name":74,"nameZh":6,"description":75,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":76,"categories":77,"keywords":88,"publishedAt":37},"01M6J4ASEHAQYBZ5TZT4RVAM1Z","you-web-search","Web search using You.com Search API with high-quality, cited results — runs keyless out of the box; YDC_API_KEY unlocks higher limits and real-time web crawling","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fyou-web-search\u002FSKILL.md",[78,83],{"categoryNo":79,"categoryName":80,"taxonomyType":81,"taxonomyTypeDesc":82},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":84,"categoryName":85,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109],"web search","search","you","you web search","web","time","only","api","web crawling","read","read-only","real-time","basics","keyless","with","research","results","high","quality","box","limits",{"skillNo":111,"name":112,"nameZh":6,"description":113,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":114,"categories":115,"keywords":116,"publishedAt":37},"01M6J4AK1ZAR1HS398K0CWWBFX","write-tweet","Multi-format tweet studio - standalone drafts (10 across 5 size tiers), a 5-10 tweet thread, or 10 remixes of past tweets, selected via ${var}","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fwrite-tweet\u002FSKILL.md",[],[33,117,118,119,120,96,121,122,123,124,101,125,126,127,128,129,130,131],"write tweet","standalone","social","studio","across","xai","past","drafts","twitter","thread","multi","size","via","format","content",{"skillNo":133,"name":134,"nameZh":6,"description":135,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":136,"categories":137,"keywords":141,"publishedAt":37},"01M6J4AFYF6ZHHNQV6H7N92S8K","weekly-aeoncard","Build a weekly token-consumption recap image from memory\u002Ftoken-usage.csv - this week + all-time totals, top skills, rendered as a shareable SVG card.","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fweekly-aeoncard\u002FSKILL.md",[138],{"categoryNo":139,"categoryName":140,"taxonomyType":81,"taxonomyTypeDesc":82},"01JBSEEDCAT000000000000016","图像与多媒体",[142,143,144,145,146,147,148,94,35,149,150,151,152,153,154,155,156,33,157,12],"weekly","weekly aeoncard","as","usage","SVG","memory","card","image","recap","csv","monitoring","notifications","productivity","top","build","token",{"skillNo":159,"name":160,"nameZh":6,"description":161,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":162,"categories":163,"keywords":164,"publishedAt":37},"01M6J4AAZF6D0YQ8NK92NYJSRX","vuln-tracker","One lifecycle poll over everything vuln-scanner produces - PR and advisory status, PVR triage transitions, and pending-disclosure aging, with a stars-secured impact headline and one action queue.","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fvuln-tracker\u002FSKILL.md",[],[165,166,35,167,168,33,157,169,170,171,27,172,173,22,174,175,176,103,177,178,179,180],"tracker","vuln tracker","impact","scanner","status","action","security","stars","lifecycle","gh","aging","advisory","github","transitions","triage","global",{"skillNo":182,"name":183,"nameZh":6,"description":184,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":185,"categories":186,"keywords":190,"publishedAt":37},"01M6J4A3ZM65VJNT4VEA9ESV39","vuln-scanner","Audit trending repos for real security vulnerabilities and disclose responsibly - scan and route findings (PVR \u002F dependency PR), re-submit queued advisories, and send armed email disclosures","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fvuln-scanner\u002FSKILL.md",[187],{"categoryNo":188,"categoryName":189,"taxonomyType":81,"taxonomyTypeDesc":82},"01JBSEEDCAT000000000000005","邮件与沟通",[168,191,192,193,194,35,195,171,196,197,198,22,177,199,180,200,96,201,202,203],"vuln scanner","resend","re","trending","repos","email","for","scan","vulnerabilities","submit","route","audit","send",{"skillNo":205,"name":206,"nameZh":6,"description":207,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":208,"categories":209,"keywords":211,"publishedAt":37},"01M6J49YHDRN3JNAN9GJW810P2","video-script","Turn a repo, product page, or update into a recording-ready video script in a receipts-first format - verifies every claim against live sources, then writes timestamped VO + on-screen direction with a plain-language glossary, assets checklist, anti-tells, and a verify-before-recording list","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fvideo-script\u002FSKILL.md",[210],{"categoryNo":139,"categoryName":140,"taxonomyType":81,"taxonomyTypeDesc":82},[212,213,214,215,216,217,218,219,24,101,220,221,222,103,223,224,225,226,19,130],"Video","video script","script","recording","sources","ready","direction","page","claim","plain-language","glossary","first","verify","product","screen"]