引擎:每个技能动词背后的 Rust 运行时¶
技能文本运行的每个命令都是 {{scripts_path}}/impeccable <verb>。技能旁边的启动器(skill/scripts/impeccable、impeccable.cmd)会为每个平台查找或下载一个静态二进制文件并执行它。该二进制文件由本仓库的 Cargo workspace 构建。运行时没有 Node。
本页面是面向所有构建或修改运行时的人员的地图。每个动词的可观测行为在 CLI-CONTRACT.md 中有明确定义,并由 tests/oracle/ 逐字节固定。
所有内容都位于本仓库中,基于 Apache-2.0 许可,并可从源码离线构建。引擎的任何部分都不会在构建时获取。
布局¶
Cargo.toml the workspace (crates/*), release profile
rust-toolchain.toml the channel plus the wasm32 target
ENGINE_VERSION which engine release the launcher / npm shim download
.cargo/config.toml the `cargo xtask` alias
browser-bundle/ the page JS the in-page bundle is built from
crates/
cli the `impeccable` binary: verb router, exit codes
common Io handle (stdout/stderr/stdin/env/cwd), path + process helpers,
the HTTP/1.1 server core (`http`) behind every local server
context context, doctor, staleness, signals, concept-seed, pin, ...
hook the design hook (hook, hook-before-edit, hook-admin)
live live mode: server, wrap, accept, manual edits, Svelte/Vue
skills install / update / check / link (the old npm CLI verbs)
comp comp-fidelity pure libs (raster, png, metrics, fonts)
comp-verbs build-phase, comp-diff, comp-spec, font-match
detect `impeccable detect`: file walk, config, ignores, output, regex engine
html the static HTML engine: parser, cascade, static DOM, rule adapters
browser the URL engine: Chrome discovery, CDP, snapshot, visual pass
foundation JS-semantics helpers, color, findings, the rule registry, inline
ignores, the Dom trait, SnapshotDom, and the plain-data types
every check takes in and hands back
core the rule logic: every `check_*` / `scan_*` and its heuristics,
the browser rule adapters, the visual-contrast decisions
wasm wasm-bindgen exports over `core` (the in-page bundle and the
extension's offscreen core)
bundle the page JS plus the bundler: in-page bundle, extension
pieces, registry JSON, the wasm-pack call
xtask `cargo xtask bundle`: the workspace's caller of `bundle`
crates/core 在自有路径下重新导出 foundation 模块,因此每个使用者只需指定一个 crate:impeccable_core::js、impeccable_core::color、impeccable_core::checks::rules::check_colors、impeccable_core::browser::driver::collect_browser_findings。这两个 crate 之间的划分取决于检查所面向的对象,而不是谁可以看到它。
构建与测试:
cargo build --release -p impeccable # target/release/impeccable
cargo test --workspace
IMPECCABLE_BIN=target/release/impeccable node tests/oracle/run.mjs # the behavior gate
bun run test 和 oracle 会通过 IMPECCABLE_BIN 查找二进制文件,然后依次查找 skill/scripts/bin/<os>-<arch>/(bun run fetch:engine 会把固定的 release 下载到那里;IMPECCABLE_BIN=target/release/impeccable bun run fetch:engine 会复制本地构建),再查找 target/release/impeccable,因此只需 cargo build --release -p impeccable 即可。
引擎运行的每个本地服务器(live-server、serve-question 决策页面、component-review serve)都基于 impeccable_common::http 构建:一个 TcpListener、每个连接一个线程、带请求体大小上限的请求解析,以及会关闭自身连接的响应。live server 自己驱动连接(turnstile 排序、SSE、驻留轮询);另外两个则通过 SerialServer 一次只取一个请求,因此它们的处理程序保持单线程,且不会有连接等待另一个连接被读取。这取代了 tiny_http,其 0.12 连接池可能会让浏览器连接突发中的一部分请求得不到响应(#1071)。新增服务器应建立在同一个 core 之上,而不是引入另一个 HTTP crate。
tests/oracle/vectors/calls/ 中冻结的函数级向量通过 impeccable_core::vectors::call(cargo test -p impeccable-core)重放,它是 foundation 的分发分支与 core 的分发分支的并集。
浏览器 bundle¶
与本地运行相同的规则也会在页面中运行,并被编译为 WebAssembly。cargo xtask bundle 是生成所有浏览器产物的唯一命令:
- 运行
wasm-pack build crates/wasm --target no-modules --release,输出到target/wasm-bundle/(使用opt-level z,然后执行wasm-opt)。 - 按固定顺序将
browser-bundle/*.js中的页面 JS 与 wasm-bindgen 胶水代码以及以 base64 内嵌的.wasm拼接在一起。页面 JS 只实现Dom探针、编组 JSON 并绘制 overlay;其中没有任何规则逻辑。 - 写入
dist/detect-antipatterns-browser.js和dist/antipatterns.json,并将两者复制到crates/live/assets/。这两份副本是受跟踪的生成文件。crates/live/src/browser_assets.rs通过include_str!内嵌该 bundle,然后 live server 以/detect.js将其交给浏览器,因此二进制文件必须携带它。antipatterns.json是规则注册表([{ id, name, category, description }],对于声明了严重性的行还包含severity,例如"advisory"),从事源码 checkout 或仓库 tarball 的使用者在没有 Rust 工具链的情况下也能读取它,因为extension/detector/在 gitignore 中;impeccable.style 会据此统计并渲染规则。 - 将五个扩展组件写入
extension/detector/(snapshot.js、overlay.js、core.js、core_bg.wasm、antipatterns.json)。该目录在 gitignore 中;bun run build:extension会运行此任务,然后打包 zip 文件。
cargo xtask bundle --check 会重新构建,并在任一受跟踪资产过时时失败,这就是 CI 的过时性检查门禁。构建是确定性的:相同的源码,产生相同的字节。
wasm-pack 是这个项目唯一需要的额外工具(cargo install wasm-pack
--locked),外加 rust-toolchain.toml 所请求的 wasm32-unknown-unknown 目标。
设置 IMPECCABLE_BUNDLE_SKIP_WASM_PACK=1 会复用 target/wasm-bundle/ 中已有的内容,
以便单独迭代页面 JS。设置 IMPECCABLE_EXTENSION_SKIP_BUNDLE=1 可以让
bun run build:extension 在 extension/detector/ 已经完成时跳过打包步骤,
适用于预先构建好它的 CI 矩阵。
在改动 crates/core、crates/foundation、crates/wasm 或 browser-bundle/ 之后,
运行 cargo xtask bundle,并提交刷新后的资源。
下游复用打包器¶
这些都不在任务中。impeccable-bundle(crates/bundle)通过 include_str! 内嵌
browser-bundle/*.js,并负责组装;因此,某个 crate 如果把自己的规则包与
impeccable-core + impeccable-wasm 链接到同一个 wasm 模块中,就能为该模块构建出
同样的产物,而无需从本仓库复制任何文件:
let (glue, wasm) = impeccable_bundle::wasm_pack_build(
Path::new("crates/my-wasm"), // engine + pack, not crates/wasm
Path::new("target/wasm-bundle"),
&[], // extra cargo args, after `--`
)?;
let js = impeccable_bundle::in_page_bundle(&glue, &wasm); // /detect.js
let registry = impeccable_bundle::registry_json(); // built-ins + pack rows
let ext = impeccable_bundle::extension_pieces(&glue, &wasm, ®istry);
impeccable_bundle::check_capture_contract()?; // snapshot/core drift
这里没有任何代码会写文件或直接退出:由调用方放置字节并自行报告失败。一旦安装规则包,
它的注册表行就会出现在 registry_json 中,因为注册表会读取内置规则以及每个已注册的切片。
IMPECCABLE_BUNDLE_SKIP_WASM_PACK=1 是此库对跳过开关的命名(旧的
IMPECCABLE_XTASK_SKIP_WASM_PACK=1 仍然有效)。
规则包¶
内置规则会被编译进去并始终运行。规则包 是依赖此 workspace 的 crate 在不 fork 的 情况下添加自有规则的方式:一个进程生命周期的值,携带它自己的注册表行以及它拥有规则的钩子。 当未安装任何规则包时,一切保持不变——oracle 会逐字节地确保这一点。
这些 trait:
impeccable_core::rule_pack::RulePack(对象安全,Send + Sync + Debug) 包含三个钩子,每个默认都是空的:check_text(content, file_path, ext)用于文本引擎,check_element_dom(dom, el)和check_page_dom(dom)用于浏览器引擎。impeccable_html::StaticRulePack包含check_document(doc, file_path)。StaticDocument模型属于crates/html,而detect无法命名来自依赖于它的 crate 中的类型,因此静态引擎的钩子是一个独立的 trait。覆盖 HTML 的规则包需要同时实现这两个 trait。
对于下游 crate,有三个步骤:声明 static ROWS: &[Antipattern],使用带命名空间的 id
(mypack/my-rule),并从 registry() 返回它们;在启动时调用一次
impeccable_core::rule_pack::install(&PACK),这会让 get_antipattern 解析规则包的 id,
从而让它的发现项拥有名称、描述、类别和严重级别;然后把规则包传给正在运行的引擎。
规则包引用的传递位置:
| 引擎 | 字段 |
|---|---|
| 文本 | TextOptions.rule_pack、ScanOptions.rule_pack |
| 静态 HTML | DetectHtmlOptions.static_rule_pack 和 .rule_pack;StaticHtmlEngine.static_rule_pack 用于 Engines 接缝 |
| 浏览器 / 快照 | BrowserConfig.rule_pack(#[serde(skip)]:规则包是 Rust 值,绝不会来自页面的 JSON) |
每个钩子的运行位置及其原因:
- 文本引擎(
detect_text):在每个内置匹配器、样式块与 CSS-in-JS 阶段、设计系统扫描、 去重和页面分析器之后、内联忽略之前运行。放在最后会使内置输出保持不变;同时,因为处于 豁免步骤之内,impeccable-disable对规则包规则的覆盖方式与对内置规则完全相同。 - 静态 HTML 引擎(
detect_html_source):在元素规则、设计系统合并、页面级检查和模式 检查之后,同样在内联忽略之前运行。一个 HTML 文件只会经历一次规则包传递:设置了static_rule_pack时用它,否则用rule_pack.check_text处理原始 HTML 源码——这样, 仅含文本的规则包也仍然能覆盖.html文件。同时实现了两个 trait 的规则包绝不会对同一文件 报告两次。 - 浏览器驱动(
collect_browser_findings):check_element_dom在驱动的逐元素循环末尾 运行,经过同样的禁用规则过滤,并归入与内置发现项相同的元素;check_page_dom在每个内置 页面传递之后运行,其归属方式与那些自行命名元素的内置检查相同(el: None表示document.body)。skipScan也会跳过规则包。
注册表将 ANTIPATTERNS 作为内置列表,并在其后查询已注册的行(registry::extend、
registry::all_antipatterns)。extend 对每个切片是幂等的,并在 id 冲突时 panic,
因此规则包永远无法遮蔽内置规则。注册只能是追加式,且没有撤销:规则包是进程的属性,
而不是某次运行的属性。
wasm 的 detect 特性¶
crates/wasm 面向无法执行二进制文件的主机(Cloudflare Workers 和其他 wasm 沙箱)时使用
--features detect 构建。它在文件扫描引擎之上增加了两个导出,JSON 进、JSON 出:
detect_text_json(content, file_path, options_json)detect_html_source_json(html, file_path, options_json)
两者都接受 { inlineIgnores?: boolean, designSystem?: { frontmatter?, sidecar? } },
并返回 impeccable detect --json 所打印的发现项数组,键与顺序完全相同。designSystem
携带的是 DESIGN.md 的输入,而非规范化后的对象,因为 JS API 的规范化形式使用了 Set 和
Map,而 JSON 无法容纳它们。无法解析的选项会回退到默认值。antipatterns_json() 列出
内置规则以及其后任何规则包的行;immediate_tier_rules_json() 返回设计钩子的 immediate tier
(即那些值得在编辑处修复的规则 id)。该列表位于
impeccable_core::registry::IMMEDIATE_TIER_RULES 中,impeccable-hook 会重新导出它,
因此 wasm 消费者读取到的是与钩子运行时相同的列表,而无需保留副本。
一个规则包通过 impeccable_wasm::set_rule_pack 和 exports_detect::set_static_rule_pack 到达这些导出,二者均仅限 Rust:使用方是一个 crate,它把 impeccable-wasm 作为 rlib 链接,注册自己的规则包,并对自身运行 wasm-pack。这里刻意没有提供面向 JS 的 setter。
Pristine(PR 设计评审机器人)是第一个使用者:其 rules/ crate 在所有三个 hook 上承载 pristine/* 规则,并通过此特性接入引擎,取代它目前对 npm impeccable@3 包进行的 detectText 调用。
发布¶
远程技能 ZIP 在解压前需要 pinned-key 签名。请参阅 bundle signing 了解 1Password 设置以及必需的先签名后发布顺序。
有两种发布类型会触及运行时,按以下顺序:
- Engine(
engine-v<ENGINE_VERSION>):bun run release:engine会校验版本、npm 平台包的固定版本以及干净的工作树,然后打标签并推送;.github/workflows/release-engine.yml构建五个目标平台,并发布带有.sha256伴生文件的二进制文件。启动器、npm shim 和impeccable install会从github.com/pbakaus/impeccable/releases/download/engine-v<X>/下载。 - npm 平台包,然后是 skill 和 CLI 发布;
scripts/check-engine-release.mjs会以引擎发布作为这些发布的门禁。
扩展自带其 vendored WASM 核心,并且从不执行引擎二进制文件,因此 bun run release:ext 不受该门禁限制。但在附加 zip 之前,它确实需要运行 bun run build:extension(因而需要 Rust 工具链和 wasm-pack)。
CI 运行工作区构建和测试(rust、rust-windows),并针对被测 checkout 中的 release 构建重放 oracle。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 im/impeccable