Agent审计
- 作者仓库星标 430
- 作者更新于 实时读取
- 作者仓库 aeon
- 领域
- 安全 · meta · dev
- 兼容 Agent
-
- Claude Code
- Cursor
- Cline
- Codex
- Windsurf
- Gemini CLI
- +20
- 信任分
- 94 / 100 · 已通过审计
- 作者 / 版本 / 许可
- @aaronjmars · 未声明 license
- Token 消耗评级
- 低消耗
- 接入复杂程度
- 需简单配置
- 是否需要外部 API Key
- 不需要
- 兼容的系统
- Windows
- 底层运行要求
- 无特殊要求
- 文件与系统权限
-
- 只读
- 允许写入 / 修改
- Shell 执行
- 读取环境变量
- 网络行为
- 允许外网请求
- 安装命令数
- 26 条
档案由构建时根据 SKILL.md 与安装命令自动衍生,可能与作者实际意图存在差异。
---
name: skill-freshness
description: Audit every enabled skill's upstream file dependencies for staleness — flags chained skills abou…
category: 安全
runtime: 无特殊运行时
---
# skill-freshness 输出预览
## PART A: 任务判断
- 适用问题:安全审计、密钥扫描、权限检查或风险分析。
- 输入要求:目标材料、限制条件、期望输出和验收方式。
- 证据边界:围绕“Why this exists / Config / Freshness thresholds”读取原文规则,不把推断写成作者承诺。
## PART B: 执行结果
- **01** 任务判断:确认你的需求是否属于安全审计、密钥扫描、权限检查或风险分析,并标出输入、限制和预期结果。
- **02** 执行计划:优先按“Why this exists / Config / Freshness thresholds”拆成步骤,说明每一步会读取什么、修改什么、产出什么。
- **03** 交付结果:给出可复制的命令、文件改动、检查清单或内容草稿,并说明如何继续迭代。
- **04** 风险边界:结合 读取文件、写入/修改文件、执行终端命令、读取环境变量、会按任务需要访问外部网络、通常不需要额外 API Key 给出执行前确认项。
## Running Rules
- 读取文件、写入/修改文件、执行终端命令、读取环境变量;会按任务需要访问外部网络;通常不需要额外 API Key。
- 先小样例验证,再放大到真实任务。
- 交付时同时给结果、检查口径和下一步迭代建议。 原文没有稳定的斜杠命令要求。安装验证后通常全局生效,直接在对话里点名这个 Skill 并描述任务即可。
告诉 Agent 目标文件或材料、期望结果、不可改范围、是否允许联网或执行命令。本 Skill 的权限画像是:读取文件、写入/修改文件、执行终端命令、读取环境变量。
先用一个小任务确认它会围绕“Why this exists / Config / Freshness thresholds”工作;涉及文件或命令时,先看 diff、日志、预览或测试结果。
检查最终产物是否包含明确结果、必要证据和下一步动作;如果输出泛泛而谈,就补充输入、边界和验收标准后重跑。
---
name: skill-freshness
description: Audit every enabled skill's upstream file dependencies for staleness — flags chained skills abou…
category: 安全
source: aaronjmars/aeon
---
# skill-freshness
## 什么时候使用
- 把安全方向的常用动作沉淀成 Agent 可调用的技能 适合处理安全审计、密钥扫描、权限检查和风险分析,核心价值是把输入、判断、执行、验证和交付边界固定下来,避免 Agent 泛泛回答。 围绕 meta、dev 组织上下文、步骤和验收口径…
- 面向安全审计、密钥扫描、权限检查或风险分析,优先处理能明确输入、步骤和验收标准的工作。
## 需要提供什么
- 目标材料、目录范围、期望结果和不可改动内容。
- 是否允许联网、执行命令、读写文件或调用外部服务。
## 执行规则
- 围绕「Why this exists / Config / Freshness thresholds」组织步骤,不把推断写成作者事实。
- 读取文件、写入/修改文件、执行终端命令、读取环境变量;会按任务需要访问外部网络;通常不需要额外 API Key。
- 先跑小样例,确认结果可检查后再扩大任务范围。
## 输出要求
- 给出最终产物、关键证据、验证方式和下一步动作。
- 信息不足时标记 unknown,不编造命令、平台或依赖。 作者原文负责流程事实;仓库文件负责来源和命令;流狐只补充适用场景、限制和质量判断。
skill "skill-freshness" {
输入层 -> 用户目标 + 目标文件 + 禁止范围 + 验收标准
上下文层 -> Why this exists / Config / Freshness thresholds
规则层 -> SKILL.md 触发条件 / 执行顺序 / 输出格式
运行层 -> 无特殊运行时 | 读取文件、写入/修改文件、执行终端命令、读取环境变量 | 会按任务需要访问外部网络
安全层 -> 通常不需要额外 API Key + 小任务验证 + diff / 日志复核
输出层 -> 可复制结果 + 检查清单 + 下一步迭代
} ${var} — Optional. Pass
dry-runto skip the notification (article still writes, log still appends). Pass a single skill name to scope the audit to that one consumer (e.g.var=tweet-allocator). Empty = audit every enabled skill inaeon.yml.
Today is ${today}. Walk every enabled skill in aeon.yml, parse the file dependencies it declares (explicit chains: consume: edges + implicit articles/, .outputs/, memory/topics/, memory/state/ references inside each SKILL.md), check the on-disk freshness of each dependency against a per-class threshold, and surface a single decision-ready report: which enabled consumer is about to read a file that's older than its expected freshness window.
The skill answers a question the existing health stack cannot: a chained skill that runs on schedule, with no API errors, and a 100% pass rate, can still silently act on stale upstream data if the producer skill failed earlier and nothing replaced its output. Today there is no check that tweet-allocator reading articles/token-report-*.md is reading today's version rather than last Tuesday's. This skill closes that gap.
Why this exists
Aeon's reliability story has three layers — heartbeat (per-run pulse), skill-analytics (per-skill ranking over time), skill-health (per-skill failure detection) — and one gap. None of them catches the case where a producer skill's last successful run was N days ago and a downstream consumer is still happily reading the cached file as if it were fresh. The output of tweet-allocator looks normal. The output of repo-pulse looks normal. The aggregate verdict from operator-scorecard looks normal. The only signal something is wrong is that the upstream articles/token-report-*.md mtime drifted past its freshness window — and nobody is looking.
This skill looks. It's a watchdog for silent staleness, not for failures. It does not duplicate skill-health's job (which catches consecutive failures by reading run history) or skill-update-check's job (which catches upstream SKILL.md drift in imported skills). Its scope is narrow: file-on-disk freshness vs the consumer that's about to read it.
Config
No new secrets. No new env vars. No new state file beyond memory/topics/skill-freshness-state.json for prior-run dedup.
Reads:
aeon.yml— enabled skill list,chains:blocks (steps, consume, parallel), per-skillschedule(used to derive expected freshness windows).- Every
skills/*/SKILL.mdwhose correspondingaeon.ymlentry hasenabled: true— for implicit file-reference extraction. articles/,.outputs/,memory/topics/,memory/state/— directory listings + mtimes only (no content reads beyond what's needed for fingerprinting).
Writes:
articles/skill-freshness-${today}.md— the report.memory/topics/skill-freshness-state.json— fingerprint + last-verdict for run-to-run dedup.memory/logs/${today}.md— log block.
No outbound HTTP. No gh api calls. No env-var-in-headers. Pure local file I/O.
Freshness thresholds
The threshold for a dependency depends on its path class:
| Path class | Threshold | Rationale |
|---|---|---|
articles/{skill}-*.md |
28 hours | Daily skills run once per day; 28h gives a 4h grace window for clock skew + run delays. |
articles/{skill}-*.md produced by a weekly skill (cron starts with 0 _ * * 0-6 only) |
8 days (192h) | Weekly producers have a 24h grace window. |
.outputs/{skill}.md (chain runner outputs) |
4 hours | Chain steps run minutes apart; a 4h-old .outputs/ file is a stale chain run. |
memory/topics/{name}.md |
7 days (168h) | Topic files are reference material, edited on memory-flush cycles (~weekly). |
memory/state/{name}.json |
30 days (720h) | State files are append/update-on-write; 30 days is a "skill hasn't run at all" signal. |
Per-class thresholds are computed at runtime — not hardcoded per dependency. The skill discovers the producer's schedule from aeon.yml and picks the daily-vs-weekly bucket automatically.
Severity bands per dependency:
OK— file mtime within threshold.WARN— file mtime past threshold but ≤ 2× threshold.STALE— file mtime past 2× threshold (real degradation, not a one-day blip).MISSING— referenced file does not exist on disk at all.
MISSING only fires for explicit dependencies (chains: consume: entries + canonical articles/{producer}-${today}.md patterns). Implicit grep-discovered references that simply never existed are not flagged — many SKILL.md files mention paths in pseudocode or comments that aren't real reads.
Steps
1. Parse var and resolve scope
- If
${var}matches^dry-run→MODE=dry-run. Strip the prefix; remainder treated as scope override. - Otherwise
MODE=execute. - If the remaining var is a non-empty string that matches an
aeon.ymlskill key →SCOPE=single,SCOPED_SKILL=$var. If it doesn't match any key, logSKILL_FRESHNESS_NO_MATCH: ${var} not in aeon.ymland exit (no notify, no article). - Otherwise
SCOPE=fleetand audit every enabled skill.
2. Load enabled-skill list and build the producer index
Parse aeon.yml. Build two maps:
ENABLED— set of skill names whereenabled: true. (Skills withenabled: falseare not audited as consumers — their dependencies don't matter until they're turned on. They CAN appear as producers though, and their freshness is still tracked since other consumers may depend on them.)PRODUCER_CADENCE— map skill_name →daily|weekly|on_demandderived from the cron expression:- cron with
* * *in days/months/weekdays →daily - cron whose weekday field matches
^[0-6]$(single weekday) →weekly workflow_dispatchor empty →on_demand(skipped from freshness audit; on-demand outputs have no expected cadence)
- cron with
3. Gather explicit dependencies (chains: consume:)
Walk aeon.yml chains: blocks. For each step with a consume: [...] list, the consuming skill depends on .outputs/{producer}.md for each named producer. Record these as explicit edges with class outputs (4h threshold).
Also record any step with parallel: [...] followed by a downstream consume: reference as the same class.
4. Gather implicit dependencies (grep over enabled SKILL.md files)
For each skill in ENABLED, read its SKILL.md and extract every reference to:
articles/[a-zA-Z0-9_-]+(-\$\{today\}|-[0-9]{4}-[0-9]{2}-[0-9]{2})?\.md
\.outputs/[a-zA-Z0-9_-]+\.md
memory/topics/[a-zA-Z0-9_.-]+\.md
memory/state/[a-zA-Z0-9_.-]+\.json
Filter out:
- References inside fenced code blocks marked
bashortextthat are clearly examples (e.g.# example: articles/foo-2026-01-01.md). - References to the consumer's own output paths (a producer self-reading its prior file is not a freshness gap; that's its own state-keeping). Detected when the producer prefix matches the consuming skill name.
- References inside the comment marker
<!-- skill-freshness:ignore -->and the next line (escape hatch for SKILL.md authors who cite a path in prose without actually reading it).
Each surviving reference becomes an implicit edge with the appropriate path class.
5. Resolve canonical "today's article" patterns
For every articles/{producer}-${today}.md reference (or the date-suffixed equivalent), resolve to the actual most-recent file on disk: ls -1t articles/{producer}-*.md 2>/dev/null | head -1. Record the resolved path AND the producer's expected cadence (from step 2's PRODUCER_CADENCE map).
If no file matches the pattern at all, record as MISSING (only counted if the producer has cadence daily or weekly — on_demand producers may legitimately have never run).
6. Score each dependency
For every (consumer, dependency) pair:
mtime_age_hours = (now - file.mtime) in hours
threshold_hours = lookup_threshold(path_class, producer_cadence)
severity = OK if mtime_age_hours <= threshold_hours
| WARN if mtime_age_hours <= 2 * threshold_hours
| STALE if mtime_age_hours > 2 * threshold_hours
| MISSING if file does not exist (and edge is explicit OR pattern-canonical)
Aggregate per-consumer:
consumer_verdict = WORST severity across all its dependencies
MISSING > STALE > WARN > OK for the rollup.
7. Roll up to the fleet verdict
fleet_verdict = WORST consumer_verdict across all enabled consumers
Translation to exit status:
| fleet_verdict | exit_status |
|---|---|
| OK across the board | FRESHNESS_OK |
| At least one WARN, no STALE / MISSING | FRESHNESS_WARN |
| At least one STALE OR MISSING | FRESHNESS_STALE |
8. Dedup vs prior run
Compute a stable verdict fingerprint: sha1sum of the sorted list of consumer:dep:severity triples (excluding OK rows — only flagged rows count toward the fingerprint).
Compare against memory/topics/skill-freshness-state.json last_flagged_fingerprint. If identical AND today's fleet_verdict is the same as last_verdict:
- Article still writes (idempotent same-day overwrite).
memory/topics/skill-freshness-state.jsonupdates thelast_run_attimestamp.- Notify is suppressed with status
FRESHNESS_NO_CHANGE— no point pinging the operator about the same stale file two days running. The state expires after 7 days; if nothing has changed for a week, the next run will re-emit the notification as a periodic reminder.
If different (a new flag appeared, an old one cleared, or the verdict band changed): notify normally.
9. Write the article
Path: articles/skill-freshness-${today}.md. Overwrite if exists.
# Skill Freshness — ${today}
**Verdict:** ${verdict_emoji} ${fleet_verdict} — ${one_line_summary}
*Audited ${enabled_count} enabled skills · ${dependency_count} dependencies checked · ${flagged_count} flagged*
## Flagged dependencies
| Consumer | Dependency | Class | Age | Severity |
|----------|-----------|-------|-----|----------|
| ${consumer} | `${path}` | ${class} | ${age_human} | ${severity_emoji} ${severity} |
| ... | | | | |
(Sorted by severity desc, then consumer name. Omit OK rows entirely — they are noise.)
## What this means per consumer
For every consumer whose verdict ≠ OK, one paragraph:
> **${consumer}** — depends on ${N} files; ${flagged_count} flagged. Worst: `${worst_path}` last updated ${age} ago (threshold ${threshold}h, class ${class}). The producer `${producer}` last successful run: ${producer_last_run_or_unknown}. Suggested action: ${one_line_suggestion}.
`one_line_suggestion` is a small lookup:
- `MISSING` + producer is `daily`/`weekly` → "Check `${producer}` run history with `./scripts/skill-runs --skill ${producer} --hours 168`."
- `STALE` → "Verify `${producer}` is still on schedule; if so, the producer ran but did not write a new article."
- `WARN` → "Monitor — one missed run, expected to clear on next producer cadence."
## Healthy consumers
A one-line per consumer with verdict OK: `- ${consumer} — ${dep_count} deps, all fresh.`
Cap at 8 entries; collapse the rest into `+ N more all-fresh consumers.` to keep the article scannable.
## Source status
- `aeon.yml`: ${parsed_skill_count} entries, ${enabled_count} enabled
- Implicit references discovered: ${implicit_count}
- Explicit `chains: consume:` edges: ${explicit_count}
- Files not yet on disk (skipped — implicit references that never existed): ${ignored_count}
---
*Companion to `skill-health` (per-skill failure detection) and `heartbeat` (per-run pulse). This skill catches the silent-staleness gap those two cannot: a consumer reading a stale file with no API errors and a 100% pass rate. Methodology: every age and threshold is computed from on-disk mtimes — this skill measures nothing it does not also report.*
10. Persist state
Write memory/topics/skill-freshness-state.json:
{
"last_run_at": "${ISO timestamp}",
"last_verdict": "${fleet_verdict}",
"last_flagged_fingerprint": "${sha1}",
"consumer_count": ${enabled_count},
"dependency_count": ${dependency_count},
"flagged_count": ${flagged_count},
"first_seen_at": {
"${consumer}:${path}": "${ISO timestamp}"
}
}
first_seen_at records when each currently-flagged dep first crossed its threshold. Reused on the next run to detect "this has been stale for >7 days" — escalate one severity band in that case (WARN → STALE if persistent).
Cap first_seen_at to 200 entries; drop oldest by timestamp.
11. Send notification
If MODE == dry-run: skip notify, log FRESHNESS_DRY_RUN, exit.
If fleet_verdict == FRESHNESS_OK: log FRESHNESS_OK, do not notify (no news is good news; a green daily ping is noise).
If fleet_verdict ∈ {WARN, STALE} AND fingerprint changed since last run: notify.
If fingerprint identical to last run AND last run was within 7 days: log FRESHNESS_NO_CHANGE, do not notify.
Notification body:
*Skill Freshness — ${today}*
${verdict_emoji} ${fleet_verdict} — ${flagged_count} of ${dependency_count} deps flagged across ${affected_consumer_count} of ${enabled_count} enabled consumers
Worst:
- ${consumer_1} ← ${path_1} (${age_1} old, class ${class_1}, sev ${sev_1})
- ${consumer_2} ← ${path_2} (${age_2} old, class ${class_2}, sev ${sev_2})
- ${consumer_3} ← ${path_3} (${age_3} old, class ${class_3}, sev ${sev_3})
Action: ${one_line_action_for_worst_consumer}
Full: articles/skill-freshness-${today}.md
Cap message at ~3500 chars. Drop "Worst" entries 4+ if exceeded.
12. Log to memory/logs/${today}.md
## Skill Freshness
- **Skill**: skill-freshness
- **Verdict**: ${verdict_emoji} ${fleet_verdict}
- **Audited**: ${enabled_count} enabled consumers · ${dependency_count} deps · ${flagged_count} flagged
- **Worst**: ${consumer_with_worst_severity} — ${worst_path} (${worst_age} old, ${worst_severity})
- **Article**: articles/skill-freshness-${today}.md
- **Notification sent**: ${yes|no — FRESHNESS_OK|no — FRESHNESS_NO_CHANGE|no — dry-run}
- **Status**: ${FRESHNESS_OK|FRESHNESS_WARN|FRESHNESS_STALE|FRESHNESS_NO_CHANGE|FRESHNESS_DRY_RUN}
Exit taxonomy
| Status | Meaning | Notify? |
|---|---|---|
FRESHNESS_OK |
every enabled consumer's deps are fresh | No (silence is the signal) |
FRESHNESS_WARN |
at least one dep past 1× threshold but no STALE/MISSING | Yes (only on fingerprint change) |
FRESHNESS_STALE |
at least one dep past 2× threshold OR a canonical-pattern dep MISSING | Yes (only on fingerprint change) |
FRESHNESS_NO_CHANGE |
flagged set identical to prior run, last run < 7 days ago | No (re-emits after 7d) |
FRESHNESS_DRY_RUN |
var=dry-run mode |
No (article still writes) |
SKILL_FRESHNESS_NO_MATCH |
${var} named a skill not in aeon.yml |
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 or an mtime call; every write is 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
- Read-only across producers. This skill never re-runs a producer to refresh its output, never deletes stale files, never edits another skill's SKILL.md. It reports; the operator (or
skill-repair) acts. - Enabled consumers only. A skill with
enabled: falsedoes not need its dependencies audited — it isn't going to consume them. This keeps the report scoped to what's actually live in the schedule. - Implicit dependencies are best-effort. Grep-based discovery is heuristic. False positives are tolerated (consumer paragraph clarifies why); false negatives are accepted (an explicit
chains: consume:edge is the source of truth for chain runs). The goal is to surface the worst-case staleness, not to prove formally complete coverage. - Per-class thresholds, not per-skill. The threshold for
articles/token-report-*.mdis the same as forarticles/repo-pulse-*.md: the path class drives the window, derived from the producer's cadence inaeon.yml. This keeps the table maintainable as the fleet grows. - Fingerprint-based dedup. A stale file flagged today and still stale tomorrow does not re-notify. The 7-day re-emit window handles the case where a chronic stale file has been forgotten about.
- No issue filing. Anomalies surface in the verdict and the article. Persistence and resolution belong to
skill-health. This skill is read-only acrossmemory/issues/. - Idempotent. Same-day reruns overwrite the article and state file. The log entry appends one block per run.
先判断是否适合
作者设计意图
作者的方法与取舍
边界和复核