fmsouza/lanekeep

GitHub: fmsouza/lanekeep

一款基于 AST 的确定性架构一致性检查工具,专为拦截 AI 生成代码中违反项目特定规范的行为而设计。

Stars: 0 | Forks: 0

# lanekeep **针对 AI 生成和人工编写的代码,进行基于 AST 的确定性架构一致性检查。** [![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#license) ## 它是什么 lanekeep 不是 ESLint 意义上的 linter。ESLint 强制执行语言级别的正确性。 lanekeep 强制执行*项目特定的规范* —— 语言模型无法从展示给它的代码中推断出的规范,因为它们存在于你团队的脑海和代码审查者的评论中。 每条规则都是对**“agent 总是把这个弄错”**的具体回答。 规则是 TypeScript 程序,使用与它们检查的代码相同的语言编写: ``` import { defineRule } from 'lanekeep' export default defineRule({ id: 'local/no-numeric-sizes', severity: 'error', card: { message: 'Literal numeric size inside makeStyles', remediation: 'Use theme.spacing.*, theme.borderRadius.* or theme.borders.*', examples: { bad: 'padding: 12', good: 'padding: theme.spacing.md' }, }, // Matched in Rust, at native speed. Your code runs only on matches. query: ` (pair key: (property_identifier) @prop value: [(number) (unary_expression operand: (number))] @value) @match `, check(ctx, m) { if (!/^(padding|margin|gap|borderRadius)/.test(ctx.text(m.prop))) return if (Number(ctx.text(m.value)) === 0) return const call = ctx.closestAncestor(m.match, '(call_expression function: (identifier) @f)') if (!call) return if (!ctx.resolvesToImport(call.f, { module: '@rneui/themed', name: 'makeStyles' })) return ctx.report(m.match) }, }) ``` `check` 是普通的 TypeScript。循环、累积状态、构建数据结构、读取其他文件、导入共享的 helper —— 除了控制其执行的查询之外,没有表达能力上的上限,也不需要学习额外的 DSL。 ## 它为何存在 根据你的代码库编写代码的 agent 会自信且反复地违反你的规范,因为这些规范在展示给它的代码中是不可见的。在下一次 prompt 中再次告诉它并不可行。而将规范编码为规则则行之有效。 这使得对于静态分析器来说,其设计约束不同寻常: - **它在内循环中运行。** Agent 和开发者在每次编辑后都会调用它,因此在对数千个文件的冷启动运行中,预算时间必须在一秒以内,而在热运行中必须低于 25ms。 - **它的输出由机器读取。** 违规项会被确定性地排序,因为两次读取输出的 agent 绝不能将重新排序视为变更。 - **每条规则都带有自己的修复。** `message`、`remediation` 和 `examples` 是必填字段,而不是文档 —— 它们是反馈给 agent 的规则卡片。 ## 它如何在具备可编程规则的同时保持快速 运行 JavaScript 插件的本地工具通常面临的问题在于它们之间的边界:每个 AST 节点分派一次到 JS,意味着每个文件要进行数万次交叉调用。 lanekeep 则是每次**查询匹配**才分派一次。tree-sitter 查询在 Rust 中跨越单个共享解析运行;只有匹配项才会到达你的处理程序。这通常能使交叉调用减少两到三个数量级,这也是一旦规则使用 TypeScript 编写,Rust 引擎仍能占有一席之地的原因。 ``` discover paths (globs, gitignore-aware) └─> for each file, in parallel: cache key ──hit──> validate tracked deps ──> cached violations + facts └─miss─> path and raw-text gates reject before any parse └─> parse ─> match queries in Rust └─> invoke the TypeScript handler, per match only └─> reduce phase: cross-file rules consume facts only, never parse trees └─> filter suppressions ─> sort ─> report ``` 没有更改的热运行根本不会执行 JavaScript —— 每个文件都是缓存命中。 ## 安装说明 尚未发布。发布时,它将是一个内置 JavaScript 引擎的单一静态二进制文件,可通过 npm、cargo 和 Homebrew 获取。**运行 lanekeep 不需要 Node.js**,尽管规则是用 TypeScript 编写的。 ## 运行效果 ``` $ lanekeep check src/also.ts:2:1 error [lanekeep/no-default-export] default export → use a named export, so the symbol has one name every importer must use src/bad.ts:2:1 error [lanekeep/no-default-export] default export → use a named export, so the symbol has one name every importer must use ✖ 2 error(s) across 2 file(s) checked ``` 规则可以提供修复,通过 `--fix` 应用: ``` $ lanekeep check --fix fixed 2 violation(s) in 2 file(s) ``` 只有标记为保留行为的规则修复才会被应用。其他一切都只是建议 —— 只展示,从不写入 —— 因为谨慎的错误代价是一次手动编辑,而另一种错误会悄无声息地重写你的代码。 屏蔽指令必须附带理由和可选的过期时间,并且不起作用的指令会被明确指出 —— 缺少理由、单纯的规则 id 或无法读取的日期会被报告出来,而不是静默地什么都不做: ``` // lanekeep-ignore-next-line lanekeep/no-default-export reason: legacy entry point export default parse ``` ``` $ lanekeep check --report-unused-suppressions ``` 要在不打开源码的情况下了解规则的意图: ``` $ lanekeep explain lanekeep/no-default-export $ lanekeep rules --json ``` 为了对你修改的内容提供快速反馈: ``` $ lanekeep check --staged # what is about to be committed $ lanekeep check --since main # what changed against a ref ``` 两者都与配置中的 `include`/`exclude` 相交,并且都跳过跨文件规则 —— 在子集上运行针对整个代码库的规则只会得出错误的答案,而不是一个较小的答案,因此它们会被跳过并在 stderr 中注明,而不是悄悄地产生一个错误答案。 干净时退出 `0`,发现违规时退出 `1`,检查器无法运行时退出 `2` —— 调用者必须能够区分“你的代码有问题”和“工具坏了”。 四种输出格式:`human`(默认)、`json`(带版本号,稳定的 schema)、`sarif`(GitHub 代码扫描)和 `agent` —— token 最少,按规则而非按文件分组,每条规则的卡片只声明一次,而不是每次违规声明一次。诊断信息总是输出到 stderr,因此即使在出现故障时,通过管道传递给解析器也能正常工作。 ## 文档 | 文档 | 用途 | | --- | --- | | [`docs/architecture.md`](docs/architecture.md) | 完整设计:执行模型、host API、缓存、里程碑 | | [`docs/built-in-rules.md`](docs/built-in-rules.md) | lanekeep 附带的规则及其选项 | | [`docs/cross-file-rules.md`](docs/cross-file-rules.md) | 编写需要全局视角的规则 | | [`AGENTS.md`](AGENTS.md) | 如何在本仓库中工作 —— 面向编码 agent 和人类 | | [`CONTRIBUTING.md`](CONTRIBUTING.md) | 环境配置、命令和 pull request 流程 | | [`SECURITY.md`](SECURITY.md) | 威胁模型以及如何报告漏洞 | ## 安全性 lanekeep 旨在作为 pre-commit hook 并在 CI 内运行,这使其成为供应链攻击的目标。 规则是可执行代码,因此其安全态势侧重于限制而非缺失: - **无环境授权。** 规则在嵌入的 QuickJS sandbox 中运行,只能访问 lanekeep 公开的 host 函数。`fs`、`process`、`child_process`、网络和动态 import 并非受限 —— 它们在上下文中根本不存在。 - **无网络访问。** 在任何模式下都永久如此,且没有任何配置可以启用它。 - **文件系统限制。** 读取通过受追踪的 `ctx.readFile` 进行,限制在项目根目录下。写入仅在 `--fix` 下发生,且仅针对匹配的文件,仅在报告的范围内进行。 - **有界执行。** 每次调用的超时时间、15 秒的全局运行预算和每个 runtime 的内存上限,均不可禁用 —— 挂起 pre-commit hook 的规则与损坏的工具无异。违反其中任何一项都会取消运行并退出 `2`,而不是将部分结果报告为干净的结果。 - **结构上的确定性。** sandbox 隐瞒了时钟和随机性,因此规则即使意外也无法引入不确定性。 这限制了影响范围,并使得第三方规则集具备可审查性。对于已经能够提交到受检仓库的人来说,这并不是一种防御边界。要报告漏洞,请参阅 [`SECURITY.md`](SECURITY.md)。 ## License 根据以下任一许可授权: - Apache License, Version 2.0 ([`LICENSE-APACHE`](LICENSE-APACHE) 或 ) - MIT License ([`LICENSE-MIT`](LICENSE-MIT) 或 ) 由你选择。 除非你明确声明,否则你为包含在本作品中而故意提交的任何贡献(如 Apache-2.0 许可中所定义),均应按上述方式进行双重许可,不附加任何额外的条款或条件。
标签:AI辅助开发, Apache Flink, TypeScript, 云安全监控, 代码规范检查, 代码质量审查, 可视化界面, 安全插件, 研发效能, 通知系统, 静态分析