Archify Skill 内置可选更新提醒器:跨 Agent 低打扰更新感知的设计与实现
【免费下载链接】archifyAgent skill for beautiful, verifiable architecture, workflow, sequence,>项目地址: https://gitcode.com/GitHub_Trending/arch/archify
面向读者:Skill 维护者、Agent 集成开发者、安全与发布工程师。本文基于仓库内设计文档 docs/skill-embedded-optional-update-notifier-design.md 展开,并结合源码 archify/scripts/check-update.mjs、archify/scripts/update-contract.mjs 与测试 archify/test/update-notifier.test.mjs 佐证。
Archify 是一套为架构、工作流、时序、数据流与生命周期图提供可验证渲染的 Agent Skill(见 archify/SKILL.md)。本文讲解其内置“可选更新提醒器”的完整技术方案:如何在 Skill 被激活时以一次低频、只读、可缓存的网络检查发现候选版本,如何在确认提醒可见后才去重,以及如何用本地状态机、安全缓存与发布门禁把“提醒”和“更新”彻底解耦。读完本文,你将掌握一套可在多 Agent 宿主(Codex、Claude Code、Cursor、OpenCode 等)间复用、可测试且不扩权的最小更新感知协议。
1. 背景:Skill 安装方式碎片化带来的“长期旧版本”问题
Agent Skills 可以通过 Marketplace、Plugin、跨 Agent CLI(如gh skill)、Git 仓库或直接复制目录等多种方式安装。不同安装方式的更新能力并不一致——尤其是直接复制安装的SKILL.md目录,通常没有持续的上游版本提醒机制。
对于高关注度、频繁发布的 Skill,会累积出四类问题:
- 用户长期停留在旧版本,却从不主动打开管理页面或运行更新命令;
- 同一个 Skill 分布在不同 Agent 宿主中,维护者难以依赖单一 Marketplace 触达全部用户;
- 在 Skill 内直接执行安装命令会扩大供应链与权限风险;
- 每次激活都联网检查或弹窗提示,会带来时延、工具调用和通知疲劳。
仓库的配套市场调研 docs/research/skill-plugin-update-reminder-market-design.md 对比了 Claude Code、GitHub Copilot、gh skill、Gemini CLI、VS Code Agent Plugins 与 Vercel Labsskills的更新机制后给出结论:市场还没有一套同时覆盖“跨 Agent 安装、可靠版本识别、权限差异、低打扰提醒、自动更新、回滚”的完整 Skills 更新系统。因此设计文档推荐建设独立于各 Agent 的更新感知层——这正是 Archify v0.1 提醒器的定位。
核心思路:提醒与更新解耦。Skill 只负责让用户“知道有新版本”;用户不作任何选择时,已安装内容与当前任务完全保持不变。
2. 目标与非目标:v0.1 的边界
v0.1 的唯一职责可以用一句话概括:
发现候选版本并让用户感知,不下载、不安装、不执行远端命令、不覆盖 Skill 文件。
2.1 目标
- 在支持本地脚本和网络访问的 Agent 中提供一致的更新感知;
- 检查失败时保持静默,不阻断、不降级用户原任务;
- 同一候选版本在成功确认展示后不再提醒,正常路径只展示一次;
- 让用户清楚看到当前版本、候选版本、本地固定状态摘要和官方发布说明;
- 把版本检查控制在低频、低流量、可缓存的只读请求内;
- 保持检查逻辑确定、可测试,并独立于模型的版本比较能力;
- 保证“提醒事件”本身不构成更新授权。
2.2 非目标(v0.1 明确不负责)
- 自动下载、安装或激活新版本;
- 调用
gh skill update、npx skills update或宿主原生更新命令; - 修改、替换或删除 Skill 安装目录中的任何文件;
- 解决跨 Skill 依赖、版本约束或回滚;
- 统一 Claude、Codex、Gemini、Cursor 等原生 Plugin 的更新状态;
- 把远端响应文本转换为可执行命令;
- 向从未安装过提醒器版本的旧用户主动推送消息。
3. 设计原则与安全不变量
六条安全不变量是整套协议的“宪法”,后续所有组件、缓存与发布流程都围绕它们展开。
- 用户决定:提醒器只报告事实和候选版本;用户忽略、稍后处理或继续使用旧版本时,不产生任何安装副作用。
- 非阻断:版本检查不是 Skill 主工作流的前置成功条件。超时、断网、缓存损坏、远端格式错误和运行时缺失均转换为静默结果,随后继续原任务。
- 本地决策:远端只声明发布元数据;是否显示提醒、是否已提醒过、何时再次检查,全部由本地逻辑与本地状态决定。
- 不执行远端输入:远端 manifest 不含
updateCommand;提醒器不把远端字符串传给 shell、包管理器、脚本解释器或动态模块加载器。 - 只写缓存:提醒器仅能写入自己的系统缓存目录,实现中不存在写入 Skill 根目录、Agent 配置目录或项目目录的路径。
- 不以版本号作为提醒事件的唯一身份:
version只用于比较和向用户解释;远端候选发布 ZIP 的 SHA-256 用于构造提醒eventKey,Git tree SHA 用于发布门禁。摘要只能证明候选内容一致,不能单独证明发布者可信;本地快照不保存自身 ZIP digest,避免生成自引用摘要。
4. 方案范围:v0.1 支持的模式与运行时基线
设计文档规划了三种触发模式,其中 v0.1 只实现第一种:
| 模式 | 触发方式 | Agent 额外工具调用 | v0.1 状态 |
|---|---|---|---|
| Skill 激活调用 | SKILL.md在首个候选产物存在后调用独立检查脚本 | 每次激活最多 1 次 | 已实现 |
| CLI/MCP 顺带检测 | 未来由原本必经的 CLI 或 MCP 调用附带检测结果 | 0 | 未实现;需单独评审 |
| 宿主 Hook 调用 | 未来由 SessionStart 或 Plugin Hook 在会话边界调用 | 通常为 0 | 未实现;需单独评审 |
注意:CLI/MCP 与 Hook 只是“可复用同一契约”的未来适配点,不是当前 Archify 行为;它们不得在未经独立设计、测试和用户可见性评审时接入正常 CLI 输出。缓存只能减少网络请求,不能消除纯
SKILL.md模式下那次 Agent 工具调用;对短小、纯提示词型 Skill,评审时需要确认该额外调用是否值得。
运行时基线:MVP 使用无第三方依赖的 Node.js ESM 脚本,声明 Node.js 18+ 兼容;Node.js 不可用时返回静默结果。若目标用户中缺少 Node.js 的比例不可接受,再评估单文件二进制或宿主专用实现。
5. 组件与目录结构
建议发布包包含以下内容(仓库中已实际落地):
archify/ ├── SKILL.md ├── skill-release.json └── scripts/ ├── check-update.mjs └── update-contract.mjs外部组件:
https://tt-a1i.github.io/archify/skill-updates/archify/stable.json <system-cache-dir>/archify-skill/version-<version-sha256-prefix>/committed-<generation>/state.json仓库实际文件与职责对应如下:
- archify/SKILL.md:定义何时调用检查器,以及不同状态对应的 Agent 行为(见其
## Update awareness一节); - archify/skill-release.json:随当前安装版本发布的本地身份快照;
- archify/scripts/update-contract.mjs:零依赖纯模块,唯一拥有 SemVer、严格字段、UTC 时间、固定来源和发布说明 URL 契约;运行时、发布门禁和包内烟测共同复用;
- archify/scripts/check-update.mjs:执行缓存、HTTP、版本比较与提醒去重;
stable.json:维护者发布的最新稳定版元数据(仓库内实样见 docs/skill-updates/archify/stable.json);committed-<generation>/state.json:按已安装版本分片的完整检查与提醒快照,不随 Skill 更新覆盖;同一版本的多个 Agent 共享去重,不同版本互不重置状态。
关于缓存目录命名:检查器按“本地完整版本的 SHA-256 前缀”分片(源码中取前 24 位十六进制,见versionCacheDirectory),因此state.json实际位于形如version-<24位hex>/committed-<generation>/state.json的路径下。
6. 运行流程:一次检查的生命周期
设计文档给出了完整的判定流程图,核心决策链如下:
- Skill 被激活;
- 首个候选产物存在后运行
check-update.mjs; - 若
nextCheckAt尚未到期 → 返回silent(读缓存); - 否则无条件
GET stable.json; - 请求与校验失败 → 记录退避时间并返回
silent; - 候选 SemVer 未严格高于安装版本 → 刷新缓存并返回
silent/current; - 候选 digest 已确认展示 → 按策略返回
silent; - 否则返回
update_available+eventKey; - Agent 展示提醒 → 确认
eventKey已展示 → 继续原始任务。
对应到源码 archify/scripts/check-update.mjs 的checkForUpdate主流程,检查步骤可以拆成七步:
- 读取本地身份:读取随包发布的
skill-release.json,确认本地skillId、channel、版本、官方仓库和固定 manifest URL;完成条件是得到合法本地身份,或安全返回silent。 - 检查缓存:读取用户缓存并检查
nextCheckAt;未过期则直接使用缓存,否则进入一次远端检查。 - 无条件请求:使用硬编码可信 origin 请求
stable.json;不持久化或回传服务端 validator。源码中DEFAULT_MANIFEST_URL固定为https://tt-a1i.github.io/archify/skill-updates/archify/stable.json,且fetchCandidate会在 URL 不匹配时直接抛错。 - 校验响应:校验响应大小、JSON schema、
skillId、channel、来源和必要字段;候选身份可被本地确定地接受或拒绝。 - 版本比较与去重:先比较候选与本地版本的 SemVer precedence,只有严格更高的稳定版本才是更新;随后用不可变 digest 构造事件身份并检查是否已经展示。同版本或降级候选即使 digest 不同也必须返回
current。 - Agent 展示并确认:Agent 只在
update_available时展示提醒,然后确认该eventKey已展示;确认状态与实际用户可见行为一致。 - 继续原任务:版本检查绝不取代或缩减原请求。
7. 本地发布快照:skill-release.json
skill-release.json随每个版本构建,不由运行时修改。设计文档示例为 v3.1.0,仓库当前实样为 2.17.0-dev.1:
{ "schemaVersion": 1, "skillId": "archify", "channel": "stable", "version": "3.1.0", "source": { "repository": "https://github.com/tt-a1i/archify" }, "updateManifestUrl": "https://tt-a1i.github.io/archify/skill-updates/archify/stable.json" }本地快照使用严格字段白名单,并由 release identity 门禁保证与package.json完整版本一致。源码 archify/scripts/update-contract.mjs 的validateLocalRelease要求:
- 顶层字段必须是
schemaVersion、skillId、channel、version、source、updateManifestUrl六项(不多不少,用键排序比较实现); schemaVersion === 1、skillId === 'archify';source只允许repository字段且必须等于https://github.com/tt-a1i/archify;updateManifestUrl必须等于固定的 GitHub Pages manifest URL;- channel 必须与版本号匹配:prerelease 版本 →
development,否则 →stable。
读取安全:运行时只接受不超过 4 KiB 的非符号链接普通文件,通过固定文件句柄执行有界读取;路径到句柄绑定期间发现替换即拒绝,绑定完成后只从该固定 inode 读取。FIFO、设备文件、符号链接和超限内容都按无效安装静默处理,不会阻塞 Skill 主流程。这些约束在源码的readJsonFile、assertBoundedRegularFile、isSameFile中逐一实现(archify/scripts/check-update.mjs)。
为什么本地快照不存 ZIP digest:候选的 tree/archive digest 只存在于外部 stable manifest。如果把当前 ZIP 的 digest 写进 ZIP 内部,会形成无法收敛的自引用。运行时永远不会用远端声明重写本地身份。
8. 远端发布协议:stable.json 与字段约束
远端协议是提醒器唯一信任的数据源。设计文档示例(v3.2.0):
{ "schemaVersion": 1, "skillId": "archify", "channel": "stable", "version": "3.2.0", "publishedAt": "2026-08-28T08:00:00Z", "source": { "repository": "https://github.com/tt-a1i/archify", "ref": "v3.2.0", "treeSha": "8f11d3..." }, "artifact": { "sha256": "56da..." }, "summary": "改进大型项目扫描与架构图布局", "releaseNotes": "https://github.com/tt-a1i/archify/releases/tag/v3.2.0", "severity": "normal" }仓库当前真实 manifest 见 docs/skill-updates/archify/stable.json(v2.16.0,含完整 40 位 treeSha 与 64 位 sha256)。
8.1 字段约束表
| 字段 | 要求 |
|---|---|
schemaVersion | 必须为检查器支持的整数版本 |
skillId | 必须与本地快照完全一致 |
channel | v0.1 仅接受stable |
version | 必须是严格的稳定 SemVerMAJOR.MINOR.PATCH;不接受 prerelease、build metadata 或数字前导零 |
publishedAt | 必须是秒精度、真实日历日期的 UTCYYYY-MM-DDTHH:mm:ssZ;v0.1 以稳定版 annotated tag 的 tagger time 为权威值,运行时不把它用于调度或事件身份 |
source.repository | 必须匹配本地允许的官方仓库 |
source.ref | 必须精确等于v<version>;只能验证,不能拼接成 shell 命令 |
source.treeSha | 必须是发布 tag 中archify/的 40 位小写 Git tree SHA |
artifact.sha256 | 必须是发布archify.zip的 64 位小写 SHA-256;用于提醒事件身份 |
summary | 必填纯文本,1–160 个字符;v0.1 校验但不直接输出远端文本 |
releaseNotes | 必须逐字节等于https://github.com/tt-a1i/archify/releases/tag/v<version>,不接受显式端口、大小写变体、查询或片段 |
severity | normal或security;两者都不触发自动安装 |
响应体硬上限 32 KiB,重定向禁用;非成功响应、错误媒体类型和声明超限的响应会先取消未读 body,再静默失败。这些校验在 archify/scripts/update-contract.mjs 的validateStableUpdateManifest中实现:它检查字段精确集合、isStableCoreVersion、ref === v<version>、HEX_40的 treeSha、HEX_64的 sha256、validateCanonicalUtcTimestamp(真实日历时间回验)、summary 的 1–160 长度与CONTROL_OR_BIDI控制字符过滤、validateReleaseNotesUrl的逐字节匹配。
SemVer 实现细节:parseSemver与compareSemver支持 BigInt 数值比较(避免超大版本号溢出)、prerelease 语义(2.16.0-dev.9 < 2.16.0,数字段按数值比较、前导零拒绝)、build metadata 忽略(2.16.0+build.9 === 2.16.0)。这些行为有专门的单测覆盖(见下文 §13)。
9. 本地缓存协议:状态机、并发协调与容量边界
本地缓存是整套设计中最复杂的部分。完整committed-<generation>/state.json示例:
{ "schemaVersion": 1, "skillId": "archify", "installedVersion": "3.1.0", "check": { "nextCheckAt": "2026-08-31T08:00:00Z", "consecutiveFailures": 0 }, "notification": { "offeredDigests": [ "sha256:56da..." ], "acknowledgedDigests": [ "sha256:12ab..." ] }, "candidate": { "version": "3.2.0", "targetDigest": "sha256:56da...", "severity": "normal", "releaseNotes": "https://github.com/tt-a1i/archify/releases/tag/v3.2.0" } }缓存状态载荷只持久化“调度、去重和展示候选”所需的最小事实:eventKey始终由skillId与targetDigest确定推导;远端publishedAt在网络边界校验后不进入缓存;未被行为读取的观测时间不成为持久协议字段。state.json只接受不超过 64 KiB 的非符号链接普通文件,需要解析的active-claim/owner.json只接受不超过 1 KiB;读取器使用O_NOFOLLOW、O_NONBLOCK,并在读取前完成“路径 → 句柄 → 路径”的身份绑定,随后最多从固定 inode 读取“上限 + 1”字节。缓存叶文件还必须位于读取前后身份不变的真实committed、pending或active-claim父目录中。
9.1 两阶段确认:offered → acknowledged
检查器输出提醒并不等于用户已经看见。为了避免 Agent 未展示结果却把版本永久标记为已提醒,采用两阶段状态:
check返回update_available和eventKey,把 digest 加入尚待确认的offeredDigests;- Agent 展示提醒后执行轻量本地确认,把该事件从
offeredDigests移入acknowledgedDigests。
确认调用只写缓存、不联网,仅在真正出现新候选时增加一次工具调用。确认按eventKey中的 digest 匹配已 offered 集合,而不要求它仍是当前候选——因此刷新从 A 切到 B 时,刷新期间已经展示的 A 仍可可靠确认。确认集合在当前安装版本的缓存分片内持久保留,所以 manifest 即使经历 A → B → A 回退,已确认的 A 也不会再次提醒。
acknowledgedDigests不采用概率结构或有损淘汰:不会因容量治理而重新提醒已确认事件。单一安装版本在积累到 64 KiB 极限后会进入静默退避、不再接纳新提醒,直至该版本分片被替换或清理。这是 v0.1 用“永不返回不可确认提醒”换取精确去重的显式边界(源码中encodeRecoverableState在提交前模拟全部 offered 确认闭包与最坏失败退避,见 archify/scripts/check-update.mjs)。
9.2 目录准备与路径安全
缓存目录准备也属于协议边界。检查器先把位于用户主目录或系统临时目录下的受信任前缀解析成真实路径,再从文件系统根开始逐级lstat:已有组件必须是真实目录,缺失组件只按单层创建,符号链接和其他文件类型一律拒绝。创建完成后使用 BigInt 设备号/inode(零 inode 且birthtimeNs可用时回退到 birthtime 与文件模式;两者都不可用时失败关闭)重验全部组件,并把已验证的规范路径和祖先快照作为本次进程的缓存 token。每次mkdir、独占写入、改名、非递归清理都在操作前后复验同一 token。发现身份变化、结果缺失或类型错误时返回silent/cache-unavailable,不发起后续网络请求,也不把提醒或确认报告为成功。
9.3 generation 与 active-claim 并发协调
缓存根目录下面按本地完整版本的 SHA-256 前缀分片。未过期缓存直接读取最高完整、合法的committedgeneration,不创建协调记录。需要写入时:
- 检查器先以原子
mkdir创建永久的reserved-<generation>分配标记,再只写自己的pending-<generation>目录;reservation 从不改名、删除或复用; - generation 只接受最多 20 位十进制文本,分配器从全部合法操作目录中选择最小未占用编号;超长或非规范伪名称不参与分配,不能借稀疏高水位制造超长文件名并永久阻断检查;
- 固定的
active-claim负责网络请求互斥:候选 writer 先在自己的预填充 claim 目录写入 generation 与随机 token,每轮先检查固定 claim,只有路径不存在时才通过原子 rename 晋升,绝不直接覆盖空目录; - 晋升成功后必须确认自己的 pending lease 仍新鲜,并在最终缓存 token 复验之后、调用 fetch 之前再次确认 active generation/token;任何一个可观测等待点失权都取消而不发请求;
- 即使两个进程都读到“没有 pending”的旧快照,在 lease 有效的协作竞态中也只有一个能进入 fetch;30 秒硬 lease 过期后,后继 writer 才把旧 claim 原子移动到按旧实例稳定身份命名的退役目录;
- 提交不是“读 token 后覆盖固定文件”:writer 在 mutation 前后验证自己仍持有同一 active generation/token;更高 generation 接管后,会把所有较低 pending 逐个原子改名为唯一的
fenced目录,再重新读取最高 committed 快照、应用本次 mutation、写完完整 state,最后把自身 pending 原子改名为 committed。
v0.1 把协调目录视为追加式本地 journal:reserved、完整committed、fenced、cancelled、retired-claim和discarded-claim均保留,避免在并发路径引入递归清理或 generation 复用。代价是同版本分片的 inode 数和readdir成本会随写入次数增长;v0.1 不在运行路径内压缩 journal。升级产生的新版本分片天然与旧 journal 隔离。
10. 检查器输出协议:一行 JSON
检查器 stdout 只输出一行 JSON;诊断日志写入受控 debug 日志或 stderr,并且默认关闭。CLI 入口与参数解析见 archify/scripts/check-update.mjs。
10.1 静默
{"status":"silent","reason":"cache-valid"}可用reason全集(源码中逐一对应分支):
cache-valid、current、already-notified、runtime-unavailable、check-failed、invalid-manifest、invalid-local-release、cache-unavailable、check-in-progress、disabled、invalid-clock、invalid-acknowledgement、invalid-arguments
所有silent状态对用户表现一致:Agent 不输出“当前已是最新版”或内部错误。
10.2 有可选更新
{ "status": "update_available", "eventKey": "archify@sha256:56da...", "installedVersion": "3.1.0", "latestVersion": "3.2.0", "targetDigest": "sha256:56da...", "severity": "normal", "summary": "Archify 3.2.0 is available; see the official release notes for details.", "releaseNotes": "https://github.com/tt-a1i/archify/releases/tag/v3.2.0" }注意:summary由已安装检查器根据已校验版本号生成固定文案,不透传远端summary(源码notification()中硬编码为Archify ${version} is available; see the official release notes for details.)。manifest 仍保留供发布审核使用的简短摘要,但不能借提醒通道向 Agent 注入动态指令。
10.3 展示确认
Agent 只在提醒已经对用户可见后运行--ack "<eventKey>":
{"status":"acknowledged","eventKey":"archify@sha256:56da..."}无效、过期或竞争失败的确认返回silent协议,不联网,也不改变安装内容。
10.4 安全更新
安全更新使用相同协议,仅将severity设为security。它可以使用更醒目的文案,但在 v0.1 中仍由用户决定是否更新。
11. 检查策略:频率、超时与退避
建议默认值(源码 archify/scripts/check-update.mjs 中的常量与之对应):
| 参数 | 默认值 |
|---|---|
| 正常检查 TTL | 72 小时 |
| 随机 jitter | ±20% |
| HTTP 总超时 | 1000 毫秒 |
| 单次检查重试 | 0 |
| 响应体上限 | 32 KiB |
| 更新通道 | stable |
| 同一 digest 主提醒 | 1 次 |
| 已确认 digest 再次提醒 | 不提醒 |
失败时不在当前调用内重试:第一次失败后退避 6 小时,连续失败后退避 24 小时(nextFailedCheck按consecutiveFailures饱和到 2 档)。失败状态不能被解释成“当前已经是最新版”。新候选导致状态容量超限时使用同一退避节奏,并删除已经被成功刷新撤回的旧candidate,防止退避期间重复展示旧候选。
关键语义:
- 只有失败刷新保留 last-good candidate;一次成功且通过全部契约校验的刷新以当前 manifest 为权威——如果维护者撤回先前较高版本并把 stable manifest 恢复为当前版或更低版,检查器必须提交该结果并返回
current,不能继续展示已撤回候选(测试a successful refresh withdraws a previously offered higher candidate验证了这一点)。 - 每次 TTL 到期后执行一次无条件
GET;v0.1 不持久化或回传ETag等不透明服务端 validator,避免把每客户端唯一值变成长生命周期关联标识;304因此一律按普通 HTTP 失败处理(测试an HTTP 304 is always a failed unconditional refresh验证)。 - 展示确认会按不受系统时间回拨影响的单调时钟在 1.2 秒内有界等待(
ACK_LOCK_WAIT_MS),若自己的 generation 被更高 writer fence 则重新分配并重试,避免用户已经看到提醒却丢失 ack。
12. Skill 指令契约:SKILL.md 的“Update awareness”
设计文档建议在SKILL.md中保持简短,把确定性逻辑留给脚本。仓库的 archify/SKILL.md 已经落地了该段,核心语义如下:
After the first candidate exists, run the packaged checker
scripts/check-update.mjsonce with Node and continue the requested workflow. If the command cannot run, continue without mentioning the check.
silent→ 继续,不提版本检查;update_available→ 用会话语言展示一条紧凑提醒,说明当前版本、候选版本、本地固定摘要与官方发布说明链接;severity为security时以克制的警告强调标注为安全更新(只改变强调程度,不改变用户自主权);明确说明已安装 Skill 未变、是否更新何时更新由用户决定;允许翻译本地固定句子,但绝不引用、概括或翻译远端 manifest 的 summary;提醒可见后,用同一检查器--ack "<eventKey>"确认,然后继续用户原任务。
该段只定义“状态 → 行为”映射。HTTP、缓存、版本比较和安全校验全部由脚本负责,避免不同 Agent 自行解释实现细节。The notice is information, not permission—— 通知不是授权。
用户或宿主可设置环境变量ARCHIFY_UPDATE_CHECK_DISABLED=1完全关闭检查:CLI 直接返回silent/disabled,不联网也不读写提醒状态(见源码runCli首行判断)。
13. 测试方案:不变量如何被机器证明
设计文档列出的测试方案在仓库中由 archify/test/update-notifier.test.mjs(约 3300 行)完整实现。以下用单元测试样例印证关键设计点:
- SemVer 比较覆盖:stable、prerelease、降级、build metadata、BigInt 大版本号、前导零拒绝都有断言(如
compareSemver('2.16.0-dev.2', '2.16.0-dev.10') === -1、compareSemver('2.16.0+build.9', '2.16.0+build.1') === 0)。 - 同一版本/降级保护:
a changed digest never bypasses same-version or downgrade protection—— 同版本或降级候选无论 digest 是否变化都返回current。 - 成功刷新撤回、失败刷新保留:成功刷新可撤回先前较高候选;失败刷新保留 last-good 未确认候选(
a failed refresh preserves the last-good unacknowledged candidate)。 - 两阶段确认闭环:
a newer immutable candidate is re-offered until the visible notice is acknowledged验证了update_available → --ack → already-notified的完整路径,且确认前不重复发网络请求。 - 回退不重复提醒:
an acknowledged candidate stays suppressed after a later candidate and manifest rollback验证 A → B → A 回退后已确认的 A 不再提醒。 - ETag 不持久化不回传:
opaque response validators are neither persisted nor replayed断言请求不带if-none-match,且持久化状态不含 etag 字段。 - 容量边界:多组测试精确卡在 64 KiB 与 64 KiB+1 字节边界,验证“恰好可确认、超限被忽略、不剪枝历史、不返回不可确认提醒”(
a saturated exact acknowledgement history never returns an unacknowledgeable offer等)。 - 失败退避饱和:
failure backoff saturates safely instead of overflowing the cache counter防止consecutiveFailures溢出。 - 并发与 fencing:测试通过暂停
mkdir/readdir/open/rename系统调用制造竞态窗口,验证 reservation 不被复用、低代被 fence 后不能提交、恢复的旧 owner 无 pending 路径可提交、不删除新 owner 的唯一目录等。 - 损坏与恶意文件:测试用
mkfifo创建 FIFO、符号链接、空目录、错误owner.json,验证在 30 秒 lease 内按 busy 处理、超时后按稳定实例身份退役,且绝不永久阻塞。
集成测试要求:Codex、Claude Code、Cursor、OpenCode 至少各验证一次激活流程;有更新时提醒出现后原任务继续完成;无更新时用户看不到任何版本检查文案;断网条件下端到端额外等待不超过配置总超时;Agent 未展示提醒时候选不会被错误永久标记为已读;debug 日志不包含项目路径、用户输入和响应正文之外的敏感数据。
安全不变量测试:检查器不引用 shell 或进程执行 API(源码确实未导入child_process);远端字段和验证时可见的符号链接不能把写入导向 Skill 根目录、项目目录或 Agent 配置目录;任意远端 manifest 都不能改变请求 origin、缓存路径或本地命令;恢复性错误统一退出成功并返回有效silentJSON。
14. 失败处理速查表
| 故障 | 行为 | 下次检查 |
|---|---|---|
| DNS、离线、超时 | silent/check-failed | 6 小时后 |
| HTTP 304 | silent/check-failed;无条件请求不接受 304 | 退避 |
| HTTP 4xx/5xx | silent/check-failed | 退避 |
| 响应超过上限 | silent/invalid-manifest | 首次 6 小时,连续失败 24 小时 |
| JSON/schema 错误 | silent/invalid-manifest | 首次 6 小时,连续失败 24 小时 |
skillId/仓库不匹配 | silent/invalid-manifest | 首次 6 小时,连续失败 24 小时 |
| 验证时缓存根或祖先是符号链接/非目录,或关键 mutation 前后身份变化 | silent/cache-unavailable;停止后续联网且不报告提醒/确认成功 | 下次激活重新验证 |
state.json/owner.json是 FIFO、符号链接、非普通文件、超限或损坏 | 不跟随该叶文件;回退合法 generation,或按 claim lease 恢复 | 正常 TTL |
| 新候选使提交/确认闭包/失败退避投影超过 64 KiB | 不返回提醒;保留精确历史、撤销旧候选并silent/cache-unavailable | 首次 6 小时,连续失败 24 小时 |
| 本地发布快照损坏 | silent/invalid-local-release,不联网 | 修复安装后 |
| Node.js 不可用 | 跳过检测 | 下次激活 |
| 并发检查 | 新鲜 lease 内一个进程检查,其余用缓存;跨 lease 暂停可能产生重复幂等 GET,但只有当前 generation 可提交 | 正常 TTL |
| 系统时间回拨 | 对异常时间戳设上限并重新计算 | 正常 TTL |
无论哪种故障,都不能改变主任务结果或安装内容。
15. 隐私与安全
15.1 最小网络披露
检查器在成功检查后的 72 小时 ±20% TTL 到期时,才会再次向固定 URL 执行静态无条件GET;失败后若 Skill 再次被激活,则在首次 6 小时、后续 24 小时退避到期时允许重试。它不回传服务端ETag,也不上传:
- 本地安装版本;
- Agent 宿主名称;
- 项目路径、仓库名称或文件内容;
- 显式的 Skill 使用次数、频率字段或用户输入;
- 设备标识和账户标识。
服务端仍会自然获得 IP、请求时间和常规 HTTP 元数据;由于检查在 Skill 使用期间触发,该请求时间也会泄露“TTL 到期后至少发生过一次使用”的粗粒度活跃信号,设计文档要求如实披露这一点。
15.2 信任边界
- 更新 URL 和允许的官方仓库由本地发布包固定;
releaseNotes只作为用户可见链接,不作为指令来源;- 远端
summary作为不可信纯文本校验长度和控制字符,但不进入检查器输出;用户看到的是本地固定摘要; - 远端字段不能决定本地文件路径和可执行程序;
- 缓存路径由本地常量和操作系统 API 构造,不接受远端片段;
- 检查器不导入
child_process,也不提供 shell 执行接口。
15.3 残余风险(v0.1 如实披露)
- HTTPS origin 或发布账号被劫持时,攻击者可能伪造“存在新版本”和发布说明链接;
- checksum 能证明候选身份稳定,不能证明发布者善意;
- Skill 指令是否稳定执行仍受具体 Agent 宿主影响;
- 纯 Skill 模式需要一次额外工具调用,会增加少量时延和上下文开销;
- 展示与本地确认不是同一原子动作:若 Agent 在两者之间崩溃、确认失败或多个 Agent 同时读取未确认事件,同一候选可能重复提醒。系统选择at-least-once 展示,避免把用户尚未看到的提醒误记为已确认;
- 30 秒 hard lease 只能约束本地提交,不能给已发出的 HTTP 请求提供远端 exactly-once;要消除重复 GET 需要远端幂等键或 fencing token,超出静态 GitHub Pages v0.1 的能力;
- Node 18+ 没有稳定、跨平台的
openat/renameat目录句柄 API;能以同一用户权限精确插入两个系统调用之间、替换 canonical 祖先的恶意进程不属于本地缓存安全边界。后置复验会阻止它得到成功提醒或成功确认,但无法承诺零外部单路径 mutation;root/管理员、映射盘或网络挂载重映射同样不在保证范围内。
后续可通过签名发布、透明日志或宿主原生 Marketplace 降低来源风险,但不属于 v0.1。
16. 用户体验与交互语义
16.1 普通更新
⬆ Archify Skill v3.2.0 可用,你正在使用 v3.1.0。
有可用的新版本;详情请查看官方发布说明。查看变更说明
是否升级由你决定;本次任务继续使用当前版本。
16.2 安全更新
⚠ Archify Skill 发布了安全更新 v3.2.1,你正在使用 v3.1.0。
建议查看安全说明后决定是否升级。查看安全说明
当前版本保持不变。
16.3 交互语义
- 用户忽略提醒:不更新,不追问,继续原任务;
- 用户要求查看变更:只打开或概述发布说明;
- 用户要求更新:v0.1 只提供官方升级入口;执行更新属于后续独立流程。
v0.1 只实现“忽略”和“查看变更”;Snooze/Skip 不预留运行时字段,待 v0.2 重新评审状态语义。
17. 发布与旧版本迁移:两阶段稳定版发布
本仓库原先由main:/docs直接发布;该模式不会等待普通 CI,因此不能承载 manifest 的发布门禁。启用本方案前,仓库管理员必须在Settings → Pages → Build and deployment → Source将来源一次性切换为GitHub Actions。仓库内的deploy-pagesjob 只在mainpush 上运行,并显式依赖全部测试、ZIP freshness、包内 smoke 与 published-manifest 门禁;切换前不得发布新的 stable manifest。
稳定版发布顺序:
- 发布准备提交:更新包版本、Changelog 与确定性
archify.zip,但docs/skill-updates/archify/stable.json仍保留紧邻的上一稳定版。 - stable tag 工作流:拒绝任何已经等于或高于待发布版本的公共 manifest,然后烟测并创建带
archify.zip资产的 GitHub Release。 - manifest 跟进提交:Release 成功后,单独提交 manifest,填入该 tag 的
archifytree SHA、最终 Release 资产 SHA-256,以及 annotated tag 的 canonical UTC tagger time。GitHub Releasepublished_at只作为运营观测值,不进入 v0.1 运行时身份。 - 后续 commit 更新
stable.json:CI 通过 GitHub API 要求 manifest 精确等于当前 latest stable Release(不是任意历史 Release),确认目标非 draft、非 prerelease,下载其中的archify.zip,并要求它逐字节等于目标 tag 根目录提交的archify.zip。从首个携带确定性构建器的版本起,CI 还会在独立 worktree 从该 tag 重建 ZIP 并再次逐字节比较;仅历史 bootstrap 版本允许以 tagged blob 作为闭环。最后把资产 SHA-256 与 manifest、目标 tag 的archifytree 同时核对。仓库内的发布门禁脚本 scripts/check-stable-update-manifest.mjs 实现了 treeSha 与归档 sha256 的双重比对。 - 部署:只有全部 CI job 成功,
deploy-pages才上传docs/artifact 并公开新候选;部署前还会确认本次GITHUB_SHA仍是远端main,因此完成较晚的旧 workflow run 不能把站点回滚。
release identity 只允许公共 manifest 等于最新稳定版,或在稳定版发布准备窗口中暂时等于紧邻的上一稳定版;更旧版本不能借两阶段流程长期滞后。工作流失败时,公共 manifest 仍指向上一条完整 Release,不会提醒用户访问尚不存在的发布说明。
旧安装用户迁移:旧版本没有检查逻辑,无法通过本方案被远程唤醒。首次上线需要一次独立迁移活动:发布明确标注“更新提醒能力迁移版本”的 Release、在 README 顶部增加阶段性升级公告、置顶 Issue 或 Discussion、通过社群和文章渠道通知、提供经过验证的官方重新安装入口。完成这次人工迁移后,新版本用户才进入持续的内置提醒链路。
18. 分阶段实现路线图
v0.1:通知闭环(已落地)
- 本地
skill-release.json; - 远端
stable.json; - 无依赖检查脚本;
- 72 小时 TTL、无条件 GET、1 秒超时、失败静默;
- SemVer 更新资格判断、digest 事件身份和确认后去重;
- Agent 提醒后继续原任务;
- 只提供发布说明,不执行更新。
v0.2:用户提醒偏好
- Snooze;
- Skip this version;
- 关闭普通更新提醒但保留安全提示;
- 提醒历史和调试诊断。
后续独立提案
- 识别原生 Plugin/Extension 更新所有者;
- 对接
gh skill或其他跨 Agent 更新管理器; - 展示脚本、MCP、Hooks 和权限差异;
- 签名发布、安装验证和回滚。
这些能力不应通过扩充 v0.1 检查脚本顺带实现,应分别评审其权限与生命周期。
19. 验收标准与评审
v0.1 达到以下条件后可进入小范围发布:
- 100% 更新检查仅执行只读网络请求和本地缓存写入;
- 0 条代码路径可以下载、安装或执行候选版本内容;
- 缓存命中时脚本执行时间目标低于 50 毫秒(不计 Agent 工具调用调度);
- 网络检查的额外等待由 1000 毫秒总超时严格封顶;
- 同一候选 digest 成功确认后不再提醒;正常路径展示一次,展示与确认间故障允许重复;
- 所有恢复性故障均不阻断用户任务;
- 用户未明确选择后续动作时,Skill 安装状态完全不变;
- 提醒内容包含当前版本、候选版本、摘要和官方发布说明;
- 至少在四个目标 Agent 中完成真实调用验收。
设计文档还给出了可复制的评审结论模板(结论 / 必须修改 / 延后到 v0.2 / 已接受的关键取舍:检查周期、提醒去重、支持运行时、安全更新交互、远端 manifest owner),供维护者与评审人在 Issue #167 的讨论中直接使用。
20. 总结
Archify 的内置可选更新提醒器演示了一种“提醒与更新解耦”的最小可行方案:以skill-release.json固定本地身份,以严格字段契约的stable.json承载远端元数据,以check-update.mjs完成缓存调度、SemVer 比较与 digest 去重,并以 offered/acknowledged 两阶段确认保证“提醒可见才去重”。六条安全不变量(用户决定、非阻断、本地决策、不执行远端输入、只写缓存、不以版本号作唯一身份)把每一次版本检查都限制为一次低频只读 GET 和一次受控的本地状态写入——它从不下载、不安装、不执行,也因此可以在 Codex、Claude Code、Cursor、OpenCode 等宿主间安全复用。
如需继续深入,建议依次阅读:设计文档 → 市场调研 → 契约模块 → 检查器实现 → 不变量测试 → 发布门禁脚本。
【免费下载链接】archifyAgent skill for beautiful, verifiable architecture, workflow, sequence,>项目地址: https://gitcode.com/GitHub_Trending/arch/archify
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考