- name
- auto-workflow
- description
- 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.
- metadata
- {"title":"Auto-Workflow Builder","category":"core","var":"","tags":["meta","dev"],"mode":"write","commits":true,"permissions":["contents:write","pull-requests:write"]}
<!-- autoresearch: variation B — sharper output (priority tiers + data-verification gates + delta-against-existing + exit taxonomy) + slug-enable execution branch (validate → commit → PR) -->
> **${var}** — selects the mode:
> - **Analyze (default):** a URL to analyze (GitHub repo, X account, blog, project site, API docs, etc.). Multiple URLs comma-separated. Prefix a URL with `force:` to re-analyze one already in the ledger. Produces a tiered recommendation article + an `aeon.yml` diff — it does **not** mutate `aeon.yml`.
> - **Enable:** `enable:slug1,slug2,…` — flip those skills' `enabled: false → true` in `aeon.yml`, validate each against `skills/`, then commit + open a PR. `enable:dry-run:slug1,slug2` validates and reports without editing, committing, or opening a PR.
>
> Example values: `https://example.com/blog` · `@vitalikbuterin, github.com/foundry-rs/foundry` · `force:https://mirror.xyz/somedao` · `enable:rss-digest,github-monitor` · `enable:dry-run:price-alert`
## Overview
One skill, two ends of the same loop: **analyze** decides *what to enable* for a new watch target; **enable** actually flips the switch. Dispatch `enable:` with the slugs the analyze run put in its MUST tier and you close the loop — recommendation to merged PR — without a second skill.
**Analyze mode** verifies every recommendation is backed by an *observed* signal on the URL, tiers output into **MUST** (2–3 max) / **SHOULD** / **NICE** with a one-line concrete "why", emits a *delta* against the current `aeon.yml` rather than a full config dump, stays silent when existing config already covers the URL, and anchors skill names in `skills.json` (authoritative), not a stale mapping table. It writes an article + updates a ledger; it never edits `aeon.yml`.
**Enable mode** does the mechanical part analyze deliberately leaves to the operator: a slug-scoped `enabled: false → true` substitution in `aeon.yml`, gated by directory presence / current-disabled-state / chain-conflict checks, committed on a fresh branch and shipped as a PR with per-skill rationale. Explicit opt-in only — the operator names the slugs; nothing flips on `main` until they click merge.
---
## Shared preamble (run for both modes)
1. Read `memory/MEMORY.md` for high-level context and skim the last ~3 days of `memory/logs/` — drop anything already reported so you don't re-emit the same signal.
2. Parse `${var}` to pick the branch:
- `${var}` is empty → **Analyze** branch, empty-input path → exit `AUTO_WORKFLOW_EMPTY`, notify `auto-workflow: set var= to one or more URLs (comma-separated), or enable:slug1,slug2 to flip skills on`.
- `${var}` starts with `enable:` (case-insensitive) → **Enable** branch. Strip the `enable:` prefix; the remainder is the slug list (which may itself begin with `dry-run:`). Go to **Mode B**.
- Otherwise → **Analyze** branch. Go to **Mode A**.
---
## Mode A — Analyze: generate recommendations + aeon.yml diff (default)
### A0. Parse input and load context
If the (post-preamble) input is empty → exit `AUTO_WORKFLOW_EMPTY`, notify as above.
Parse `${var}`:
- Split on `,`, trim each entry
- Detect `force:` prefix on any entry → sets `force=true` for that URL (skip ledger dedup)
- Normalize each URL:
- Add `https://` if scheme missing
- `twitter.com/` → `x.com/`
- `@handle` → `https://x.com/handle`
- Strip trailing `/`, fragment, and tracking params (`utm_*`, `ref`, `src`, `s`, `t`)
- Strip trailing `.git` on github URLs
- Reject `javascript:`, `data:`, local file URLs → exit `AUTO_WORKFLOW_ERROR` with the bad URL
Read context:
- `memory/MEMORY.md` — operator interests
- `aeon.yml` — CURRENT skill enablement, `var`, `schedule`, `model` per skill (this is the comparison baseline)
- `skills.json` — authoritative installed-skill list
- `memory/topics/auto-workflow-analyzed.md` (if exists) — for ledger dedup
**Ledger dedup:** If a URL is in the ledger with `analyzed_at` within the last 14 days and `force` is not set for it, skip it with `already_analyzed` reason. If ALL inputs are dedup-skipped → exit `AUTO_WORKFLOW_NO_CHANGE`, notify nothing, log a one-line skip entry.
---
### A1. Fetch and classify
For each remaining URL, `WebFetch` with prompt: "Return page title, meta description, all <link rel='alternate'>, og:* meta tags, social handle links (x.com, github.com, t.me, discord), detected RSS/Atom feed URLs, and any token contract addresses (0x… or Solana base58 near the words 'token'/'contract'/'mint'). Report the most recent date on the page. Report the tech stack (Jekyll/Hugo/Next.js/WordPress etc)."
If fetch fails or returns <300 chars of meaningful content, try fallbacks: `/robots.txt`, `/sitemap.xml`, `gh api` for github URLs. If all fail → mark this URL `FETCH_FAILED` with reason and continue to next URL.
Classify into ONE primary category: `github-repo` / `github-org` / `x-account` / `blog-or-news` / `crypto-project` / `api-or-docs` / `research` / `product` / `community` / `personal-site` / `other`.
Extract **concrete signals** (the "why" anchors for later recommendations):
- `feed_urls`: list of RSS/Atom URLs discovered
- `x_handles`: list of X handles linked from page
- `github_repos`: list of owner/repo from page links
- `token_contracts`: list of (chain, address, symbol) tuples
- `last_update`: most recent date found (ISO)
- `update_cadence`: estimate — `active` (<7d old), `steady` (<30d), `quiet` (<90d), `dormant` (≥90d)
- `tech`: stack hint if any
If classification confidence is low (sparse signals, no category clearly matches), mark `UNCLASSIFIED` for this URL and skip to next.
---
### A2. Match signals to installed skills
For each URL, generate candidate skills by intersecting:
- URL `category` and extracted signals
- Skills present in `skills.json`
Use this hint table — but **only emit skills whose slug exists in `skills.json`** (drop any slug not found):
| Category | Hint skills | Requires signal |
|----------|-------------|----------------|
| github-repo | github-monitor, github-issues, github-releases, pr-review, operator-scorecard, repo-pulse, repo-article, code-health | `owner/repo` resolves via `gh api` |
| github-org | github-monitor, repo-pulse, repo-scanner | `owner` resolves as Organization or User with ≥5 repos |
| x-account | fetch-tweets, tweet-roundup, list-digest, refresh-x | `x_handle` extracted |
| blog-or-news | rss-digest, digest, article | ≥1 `feed_url` OR dated articles |
| crypto-project | price-alert, token-movers, onchain-monitor, defi-overview, treasury-info | `token_contract` OR `token_symbol` |
| api-or-docs | deep-research | product is genuinely new + operator interest match |
| research | paper-pick, paper-digest, research-brief | arXiv-like URL or lab site |
| community | reddit-digest, telegram-digest, farcaster-digest, channel-recap | corresponding channel URL on page |
| product | deep-research, search-skill | operator interest match |
| personal-site | rss-digest, fetch-tweets | needs feed OR handle |
For each candidate, verify: **does this URL actually have the data the skill needs?**
| Skill need | Verification |
|------------|-------------|
| RSS feed URL | at least one valid `feed_url` in signals |
| X handle | `x_handle` extracted (not just a generic x.com link) |
| GitHub owner/repo | `gh api` returns 200 |
| Token contract | contract verified on DexScreener/CoinGecko (WebFetch fallback) |
| Topic string | operator's `MEMORY.md` mentions the topic or category |
**If verification fails, do not recommend the skill.** Record the skipped candidate as `unverified: <reason>` in the source-status footer — never carry to the output table.
---
### A3. Tier and justify
Rank each verified candidate into exactly one tier:
- **MUST** — skill produces the *primary* value for this URL type AND the URL is active or steady (`update_cadence` ≠ dormant). Cap at **3 per URL**, **5 total across batch**.
- **SHOULD** — skill meaningfully complements a MUST for this URL, and ≤1h of operator attention/week.
- **NICE** — tangentially relevant, likely noise unless operator has prior interest signal in `MEMORY.md`.
For each tiered recommendation, write a **single-sentence `why`** that names at least one concrete signal from the URL:
- ✅ GOOD: `rss-digest — MUST. Feed at /feed.xml, 12 posts in last 30d, cadence active.`
- ✅ GOOD: `fetch-tweets — MUST. Handle @example, profile links 3 active product threads.`
- ❌ BAD: `rss-digest — MUST. Blogs usually have feeds.` (generic, no URL signal)
- ❌ BAD: `token-alert — SHOULD. Crypto project, might want price alerts.` (no contract verified)
Banned justifications: "typically", "often", "you might want", "could be useful", "in case". If you catch one of those, rewrite or drop the recommendation.
Dormant URLs (`update_cadence = dormant`): demote all candidates by one tier. If MUST → SHOULD. If SHOULD → NICE. If NICE → drop.
---
### A4. Compare against current aeon.yml (delta, not dump)
For each tiered recommendation, compute the delta:
| Recommended state | Current state in aeon.yml | Action |
|-------------------|--------------------------|--------|
| enabled:true, var:"X", schedule:"Y" | enabled:false | `ENABLE` |
| enabled:true, var:"X" | enabled:true, var:"" | `SET_VAR` |
| enabled:true, var:"X,Y" | enabled:true, var:"X" | `APPEND_VAR` |
| enabled:true, schedule:"Y" | enabled:true, schedule:"Z" (equivalent cadence) | `NO_CHANGE` |
| already enabled matching suggestion | — | `NO_CHANGE` |
Skills with action `NO_CHANGE` drop out of the output. If EVERY tiered recommendation is `NO_CHANGE` → exit `AUTO_WORKFLOW_NO_CHANGE`:
- Log: `### auto-workflow\n- Mode: analyze\n- Input: ${var}\n- Exit: NO_CHANGE — existing config covers ${N_OK}/${N_TOTAL} URLs\n- Ledger updated`
- **Notify nothing** (silence on no-op preserves signal-to-noise)
- Still update the ledger
Recommendations with action `ENABLE` are the exact slugs the operator can hand back to **Enable mode** — surface them as a copy-paste `enable:` dispatch (see A6).
---
### A5. Emit secret/config gaps
For each MUST/SHOULD skill:
- Read `skills/{slug}/SKILL.md` (skip if missing — flag `CATALOG_DRIFT` in footer).
- Grep the skill body for `\$[A-Z][A-Z0-9_]{2,}` to enumerate env-var references.
- Compare against workflow secrets referenced in `.github/workflows/*.yml` (grep `secrets\.[A-Z_]+`).
- If an env var is referenced in the skill but never passed through workflows → tag the recommendation `MISSING_SECRET: <NAME>`.
**Never read or echo secret values.** Enumerate names only.
---
### A6. Write article and notify
Output shape (keep it tight — no tables for empty categories):
```markdown
# Auto-Workflow: ${input_summary}
*${today} · ${exit_mode}*
**Verdict:** ${one_line}
<!-- examples:
"2 new enables, 1 var update. Missing VERCEL_TOKEN blocks deploy-prototype recommendation."
"1 new enable. All else already active."
-->
## URLs
| URL | Category | Cadence | Key signals |
|-----|----------|---------|-------------|
| ... | blog-or-news | active | feed=/rss.xml, 12 posts/30d |
## MUST (apply now)
- **rss-digest** — `ENABLE`, var: `"https://example.com/feed"`, schedule: `"0 7 * * *"`. Feed at /feed.xml, 12 posts in 30d. Secrets: OK.
- **fetch-tweets** — `SET_VAR`, var append: `"@example"`, schedule unchanged. Handle active, 3 product threads last week. Secrets: MISSING_SECRET: X_API_BEARER.
## SHOULD (consider this week)
- **github-monitor** — ...
## NICE (only if interested)
- **paper-pick** — ...
## aeon.yml diff
\`\`\`yaml
# enable
rss-digest: { enabled: true, schedule: "0 7 * * *" }
# update var (existing: "")
fetch-tweets: { enabled: true, var: "@example" }
\`\`\`
## Apply the enables
Flip the `ENABLE` recommendations in one dispatch:
\`\`\`
enable:${comma_separated_ENABLE_slugs}
\`\`\`
(or `enable:dry-run:${...}` to preview the PR without committing)
## feeds.yml additions
\`\`\`yaml
feeds:
- name: Example
url: https://example.com/feed
\`\`\`
## New skill proposals
(none unless ≥2 URLs share a gap no installed skill fills — see constraints)
## Source status
- fetch: ${N_OK}/${N_TOTAL} (failed: ${list with reasons})
- classification: ${N_CLASSIFIED} / ${UNCLASSIFIED count}
GitHubで見る