@aurelienbbn/agentlint-plugin-alchemy

中文文档
0.1.4发布于 2026/10/14MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 2264

Custom agentlint rules for Alchemy v2 (Infrastructure as Effects) stacks.

原始使用文档
文档目录6 节
  1. Quick start
  2. Rules
  3. Limits
  4. Registered contract inventory
  5. Credited concepts
  6. Credits

@aurelienbbn/agentlint-plugin-alchemy

npm downloads CI license node

6 agentlint reviews for Alchemy v2 stacks: no silent replacement, no silent data deletion, no silent takeover.

[!WARNING] Requires the engine @aurelienbbn/agentlint >=0.3.0 <0.9.0 as a peer. Evidence.

[!NOTE] Checked against [email protected]. Release candidate: no consumer has calibrated these rules yet. Alchemy is in beta; replace triggers and defaults can move between betas.

Quick start

agentlint init --preset "@aurelienbbn/agentlint-plugin-alchemy#starterPreset"
agentlint rules test
agentlint rules scan --review
agentlint next --format json

init keeps an existing config and prints the install command; never installs. Change rules need the merge base: check out with full history (fetch-depth: 0) in CI.

import { defineConfig } from "@aurelienbbn/agentlint";
import { initConfigExposure, strictPreset } from "@aurelienbbn/agentlint-plugin-alchemy";

export default defineConfig({ extends: [strictPreset], rules: [initConfigExposure] });

Rules

RulestrictstarterAuthorityLifecycleFires on
resource-replacement-review✅✅humanchangelogical ID changed without renamedFrom, declaration removed, replace prop changed, stack renamed
state-store-change✅✅humanchangestate: option of Alchemy.Stack changed
removal-policy-change✅humanchangeRemovalPolicy.retain(...) removed or narrowed, destroy() added, forceDestroy: true set
removal-policy-review✅agentstateR2, D1, KV declaration with no removal policy, or a retain condition reading Stage
adopt-review✅humanstateAdoptPolicy.adopt(...), --adopt in package.json scripts
init-config-exposure🧪agentstateyield* Config.*("KEY") directly in a Worker, Container or Lambda init

✅ in preset · 🧪 opt-in, register explicitly. Presets ignore **/*.d.ts. Every rule is standard revision 1, detector version 1.

Human authority where the answer lives outside the code: whether data in a deployed stage may go, whether a cloud resource someone else owns may be taken over. removal-policy-review stays agent-acceptable because disposability is usually visible in code (cache, preview, fixture); raise it to human in your binding if it isn't.

-yield* Cloudflare.R2.Bucket("Bucket");
+yield* Cloudflare.R2.Bucket("Assets"); // ❌ resource-replacement-review: new empty bucket, old one deleted
+yield* Cloudflare.R2.Bucket("Assets").pipe(Alchemy.renamedFrom("Bucket")); // ✅ state row migrates

-yield* Cloudflare.D1.Database("Orders", { primaryLocationHint: "weur" });
+yield* Cloudflare.D1.Database("Orders", { primaryLocationHint: "apac" }); // ❌ D1 plans a replace

-  state: localState(),
+  state: Cloudflare.state(), // ❌ state-store-change: next deploy plans against empty state
Cloudflare.D1.Database("Orders"); // ❌ removal-policy-review: default destroy
Cloudflare.D1.Database("Orders").pipe(RemovalPolicy.retain(stack.stage === "prod")); // ✅

Cloudflare.D1.Database("Db").pipe(RemovalPolicy.retain(Effect.map(Alchemy.Stage, isProd))); // ❌ alchemy#1738

deploy(program).pipe(AdoptPolicy.adopt(true)); // ❌ adopt-review
resource-replacement-review: replace triggers and options

Replace triggers, read from each provider's diff in [email protected]:

ResourceReplaces onStays in place
R2.Bucketname set or changed, jurisdiction, locationHintname removed (deployed name kept), unset ⇄ jurisdiction: "default", storageClass, domains, CORS, lifecycle
D1.Databasename set or changed, jurisdiction, primaryLocationHint set or changedname or primaryLocationHint removed, unset ⇄ jurisdiction: "default", readReplication
KV.Namespacenothing: title renames in placetitle

A logical-ID change is paired with an added declaration of the same type; a removed declaration that already carried an unconditional RemovalPolicy.retain() stays silent. Declarations moved between files stay silent.

defineResourceReplacementReview({ resources }) replaces the map: { "Queues.Queue": { name: "changed" } }. "changed" fires on any difference including removal; "set" only when the after side sets a differing value; { trigger, absent } compares an unset prop as absent.

removal-policy-change and removal-policy-review: what counts as a policy

removal-policy-change compares RemovalPolicy.retain(...) / RemovalPolicy.destroy(...) calls across the whole change, so a moved call is silent and a retain that leaves with its removed declaration is left to resource-replacement-review. retain(), retain(true) and destroy(false) are the same policy, as are destroy(), destroy(true) and retain(false); a switch to unconditional retain never fires. forceDestroy: true fires only on a bucket that existed before the change: without it R2 refuses to delete a non-empty bucket.

removal-policy-review accepts any enclosing .pipe(...) that mentions retain (including helpers such as retainOnDeployedStage) or RemovalPolicy.destroy. It fires separately when the retain argument reads Stage: the Effect form fails inside Effect-native Workers (alchemy#1738, open); read the stage from Alchemy.Stack instead.

Both take { resources }, default ["R2.Bucket", "D1.Database", "KV.Namespace"].

init-config-exposure: why it is opt-in

Alchemy binds every config key a runtime's init loads onto the deployed runtime, secret_text on Cloudflare, with the deployer's value. That is the documented way to ship a secret, so most findings are intended: calibrate before enforcing. Handler-time reads (fetch: Effect.gen(...)) and props effects stay silent. defineInitConfigExposure({ runtimePattern }) replaces the constructor pattern.

Limits

  • Lexical, not semantic. Change rules read before/after text: computed logical IDs compare as expressions, props passed as variables or Effects are not compared, Namespace.push moves are invisible, a state option given as an identifier resolves only through a single same-file const, and regex literals in stack files can confuse the scanner.
  • Named imports are missed. Resources are recognized as R2.Bucket(, D1.Database(, KV.Namespace( behind any namespace; import { Bucket } then Bucket(...) is not.
  • Only the three Cloudflare stores by default. Durable Objects, Queues, Hyperdrive and AWS stores need an explicit resources map.
  • YAML is not scanned. --adopt in GitHub workflow files is invisible: agentlint state rules parse JS, TS and JSON only.
  • Transitive config reads are invisible. A library reading Config during init is bound too (alchemy#1842); only direct yield* Config.*("KEY") fires.
  • Not a plan. alchemy plan is the ground truth; these rules make sure someone reads it.

Registered contract inventory

Generated from package exports by pnpm catalog. Rule-specific options and limitations are described above and in the source tests.

Rule/checkTrigger or review scope
adopt-reviewFlags AdoptPolicy.adopt(...) and --adopt in package.json scripts, which let Alchemy take over cloud resources it cannot prove it owns.
init-config-exposureFlags each effect/Config key yielded in an Alchemy runtime init body, which Alchemy uploads to the deployed runtime as a secret binding.
removal-policy-changeFlags diffs that remove or narrow RemovalPolicy.retain, add RemovalPolicy.destroy, or set forceDestroy on an existing R2 bucket, so a human confirms the data may be deleted.
removal-policy-reviewFlags stateful Alchemy resources (R2, D1, KV) on the default destroy removal policy, and retain conditions that read Stage through an Effect.
resource-replacement-reviewFlags diffs that make Alchemy replace or delete a stateful resource: a logical ID changed without renamedFrom, a declaration removed, a replace-triggering prop changed, or the stack renamed.
state-store-changeFlags a changed state: option on Alchemy.Stack so a human confirms how existing state moves to the new store.

Credited concepts

  • Alchemy AdoptPolicy, alchemy/src/AdoptPolicy.ts 2.0.0-beta.79, and https://alchemy.run/cli/adopting-resources (Apache-2.0 project; concept, independently implemented)
  • Alchemy RemovalPolicy, alchemy/src/RemovalPolicy.ts 2.0.0-beta.79, and https://alchemy.run/infrastructure-as-code/resource-lifecycle (Apache-2.0 project; concept, independently implemented)
  • Alchemy provider diff functions in alchemy/src/Cloudflare/R2/Bucket.ts and D1/Database.ts, 2.0.0-beta.79 (Apache-2.0; replace triggers read, not copied)
  • Alchemy renaming and resource lifecycle docs, https://alchemy.run/infrastructure-as-code/renaming (Apache-2.0 project; concept, independently implemented)
  • Alchemy secrets docs, https://alchemy.run/environments/secrets, and alchemy/src/Platform.ts 2.0.0-beta.79 plan-phase config binding (Apache-2.0 project; concept, independently implemented)
  • Alchemy state store docs, https://alchemy.run/state-store (Apache-2.0 project; concept, independently implemented)
  • alchemy-run/alchemy#1738 (Effect-form retain fails inside Effect-native Workers; reported behavior, not code)
  • alchemy-run/alchemy#1842 (deployer credentials bound through init config reads; reported behavior, not code)

Credits

Concepts from the Alchemy documentation and source (Apache-2.0), re-implemented without copying: renaming, resource lifecycle and removal policy, adopting resources, state store, secrets and config, and reported behavior in alchemy-run/alchemy#1248, #1738 and #1842. starterPreset onboarding: conceptual inspiration from desloppify by Peter O'Malley; no code or guidance copied.

版本与发布记录

@aurelienbbn/agentlint-plugin-alchemy 共发布 5 个版本,首次发布于 2026/09/28;过去 12 个月发布了 5 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.1.4latest2026/10/1424 天前
0.1.32026/10/1325 天前
0.1.22026/09/301 个月前
0.1.12026/09/301 个月前
0.1.02026/09/281 个月前

关于 @aurelienbbn/agentlint-plugin-alchemy 的常见问题

@aurelienbbn/agentlint-plugin-alchemy 是什么?

Custom agentlint rules for Alchemy v2 (Infrastructure as Effects) stacks.

如何安装 @aurelienbbn/agentlint-plugin-alchemy?

在项目目录运行 npm install @aurelienbbn/agentlint-plugin-alchemy,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@aurelienbbn/agentlint(>=0.3.0 <0.9.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@aurelienbbn/agentlint-plugin-alchemy 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@aurelienbbn/agentlint-plugin-alchemy 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@aurelienbbn/agentlint-plugin-alchemy 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js ^22.19.0 || ^24.11.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@aurelienbbn/agentlint-plugin-alchemy 可以商用吗?

@aurelienbbn/agentlint-plugin-alchemy 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@aurelienbbn/agentlint-plugin-alchemy 的下载量有多少?

根据 npm 官方下载统计,@aurelienbbn/agentlint-plugin-alchemy 在 2026/10/07 – 2026/11/05 这 30 天内被下载 2,264 次,日均约 75 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@aurelienbbn/agentlint-plugin-alchemy 还在维护吗?

最新版本 0.1.4 发布于 2026/10/14(24 天前);至今共发布 5 个版本,其中过去 12 个月发布了 5 个;首次发布于 2026/09/28。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。