AnujChhikara/cardinal
GitHub: AnujChhikara/cardinal
一个具备数据库感知能力的 TypeScript/JavaScript 静态分析工具,专注于在开发阶段发现并报告 N+1 查询、无界读取等低效数据库访问模式。
Stars: 0 | Forks: 0
# Cardinal
[](https://marketplace.visualstudio.com/items?itemName=anujchhikara.cardinal-vscode)
[](https://www.npmjs.com/package/cardinal-cli)
[](https://open-vsx.org/extension/anujchhikara/cardinal-vscode)
[](https://github.com/AnujChhikara/cardinal/actions/workflows/ci.yml)
[](LICENSE)
**一个具备数据库感知能力的 TypeScript/JavaScript 静态分析器。** 它会标记
低效的数据库访问 —— N+1 循环、过度获取、无界扇出 —— 就像
ESLint 一样,但专门针对数据层。100% 静态:没有 LLM,没有网络,没有
数据库连接;你的代码永远不会离开你的机器。
## 目录说明
这是一个 pnpm monorepo:
| Package | 功能说明 |
|---------|--------------|
| `cardinal-core` | 使用 ts-morph 解析 TS,将查询调用标准化为 `QueryDescriptor`s,运行规则,发出诊断信息。与前端无关。 |
| `cardinal-cli` | 匹配文件,运行引擎,打印诊断信息,设置退出码(用于 CI 门控)。 |
| `cardinal-vscode` | VS Code / Cursor / VSCodium 扩展 —— 实时诊断 + 抑制快速修复,共享同一个引擎。 |
| `cardinal-website` | Astro 营销网站(本仓库的落地页)。 |
## 环境要求
- Node.js >= 18
- pnpm
## 构建与测试
```
pnpm install
pnpm build # builds all packages
pnpm test # builds, then runs the full suite
```
## 使用 CLI
CLI 接受一个或多个**相对于当前目录的 glob 模式**,并且每个问题
打印一行,如果发现任何严重级别为 error 的诊断信息,则以 `1` 退出(因此它
可以作为 CI 门控)。
```
# 从 repo 根目录开始,在执行 `pnpm build` 之后
node packages/cli/dist/bin.js "src/**/*.ts"
```
### 试一试
创建一个包含 N+1 模式的文件:
```
// bad.ts
const users = await prisma.user.findMany({ where: { active: true } })
for (const user of users) {
const posts = await prisma.post.findMany({ where: { authorId: user.id } })
}
```
运行:
```
node packages/cli/dist/bin.js "bad.ts"
```
输出:
```
bad.ts:3:23 error n-plus-one Query on "post" runs inside a loop (N+1). Batch it into a single query (e.g. a WHERE ... IN / findMany).
1 problem(s), 1 error(s)
```
修复后(单次查询,无循环)报告零问题并以 `0` 退出:
```
// good.ts
const users = await prisma.user.findMany({ where: { active: true }, include: { posts: true } })
```
要全面了解 Prisma、Drizzle、Mongoose、TypeORM 和原生 SQL 的示例,请在
[`examples/anti-patterns.ts`](examples/anti-patterns.ts) 上运行 CLI。
## 机器可读输出(用于 AI agent)
Cardinal 不会重写你的代码 —— 但它会为 AI agent 提供
所需的一切。使用 `--format json` 运行,每条发现都包含其位置、消息,
以及可复用的 `why`/`fix` 解释:
```
node packages/cli/dist/bin.js --format json "src/**/*.ts"
```
```
{
"tool": "cardinal",
"version": 1,
"summary": { "problems": 2, "errors": 1 },
"findings": [
{
"ruleId": "n-plus-one",
"severity": "error",
"file": "src/orders.ts",
"line": 3, "column": 25,
"message": "Query on \"post\" runs inside a loop (N+1)...",
"explanation": {
"why": "A query awaited inside a loop runs once per iteration — 1 + N round trips...",
"fix": "Hoist the query out of the loop: collect the keys, run one batched query..."
}
}
]
}
```
`message` 包含发现的具体细节(目标表,来自
你的知识文件的基数 —— 这是 agent 无法从源代码推断出的唯一信息);
`explanation` 包含可复用的推理和修复方法。将 agent 指向它
(“运行 `cardinal check --format json` 并修复每个错误”)或将其发布到 PR 上。
状态通知会发送到 stderr,因此 stdout 始终保持纯 JSON。Cardinal 保持 100%
静态 —— 它负责发现和解释;你的 agent 负责应用编辑。
## 规则
每条诊断信息都会链接到这里。严重级别是默认值 —— 你可以使用[配置文件](#configuration)覆盖它们中的任何一个(或
关闭某条规则)。
### n-plus-one
在循环(`for`、`while`、`.map`/`.forEach`/`.flatMap`)内部 await 的查询 —— 1 + N
次往返。通过知识文件,可证明较小的循环会被静默,而
可证明较大的扇出会被提升级别。**error**(高置信度适配器)。
### unbounded-read
没有过滤条件且没有 limit 的读取 —— 它可能会扫描整张表。**warning**。
### over-fetch
在知识文件标记为*大型*的表上进行无过滤读取,而选择性
过滤本可以返回少得多的行。需要知识文件。**warning**。
### order-by-rand
`ORDER BY RAND()` / `RANDOM()` —— 对整个结果集进行排序并且无法使用
索引。**warning**。
### leading-wildcard-like
`LIKE '%…'` / `ILIKE '%…'` —— 前导通配符是非 sargable 的(全表扫描)。
**warning**。
### excessive-joins
连接多张表的查询(JOIN 由真正的 SQL 解析器计算)。庞大的 join
扇出会给查询规划器带来负担。**warning**。
### unindexed-query
在没有索引覆盖的列上进行过滤或排序的查询 —— 数据库会扫描
(或排序)整张表。Cardinal 直接从你的 **`schema.prisma`** 读取索引
(`@id`、`@unique`、`@@index`、`@@unique` —— 具备复合
前导列感知能力),像知识文件一样通过从当前目录向上遍历自动发现。
使用 `--schema ` 覆盖,使用 `--no-schema` 禁用。知识文件中标记该表为 small 会使其静默。
**warning**(目前支持 Prisma;下一步将支持更多 ORM)。
```
app.ts:2:10 warning unindexed-query Query on "user" filters on "name", but no
index has it as its leading column — the database scans the whole table. Add
`@@index([name])` in schema.prisma.
```
## 业务逻辑上下文
结构性规则看到的是*形状*,而不是*规模* —— 循环中的查询看起来像 N+1,
无论该循环运行两次还是两百万次。在你的代码旁边放置一个
**`cardinal.knowledge.yaml`**,为 Cardinal 提供缺失的
规模信息。这是一个静态的、人工编写的文件 —— 它保留在你的机器上
并且永远不会被传输。
```
version: 1
tables:
user:
rows: 10000
filters:
- when: { status: active }
rows: 10
```
**不要从空白文件开始 —— 运行 `cardinal init`。** 它会扫描你的代码并
为你构建知识文件框架:你查询的每个表,以及你的代码使用的确切过滤
子集(例如 `status = 'active'`),每个都带有一个可复制粘贴的
`count(*)` 查询。你只需填入数字。
```
node packages/cli/dist/bin.js init # writes cardinal.knowledge.yaml
```
有了这个文件,Cardinal 会**静默**处理可证明较小集合上的循环,
**提升**可证明较大集合上的循环的严重级别,并在对大型表进行
无过滤读取而存在选择性替代方案时发出警告(`over-fetch`)。Cardinal
通过从当前目录向上遍历来发现该文件;使用
`--knowledge ` 覆盖或使用 `--no-knowledge` 禁用。
如果集合无法静态追踪,请为循环添加注解:
```
// cardinal: bounded 10
for (const id of getIds()) { await prisma.post.findMany({ where: { authorId: id } }); }
```
### 抑制诊断信息
要静默特定的发现,请记录一条抑制信息,而不是修改代码:
```
node packages/cli/dist/bin.js suppress "src/contacts.ts:42" --reason "list is admin-curated, < 20"
```
这会在知识文件中追加一条记录,通过规则 + 外层函数 +
标准化的调用文本(绝不是行号,因此它能承受调用上方的编辑)进行匹配。在不带 `--reason` 的情况下运行它以进行交互式提示。在 VS Code 扩展中,相同的流程是任何 Cardinal 波浪线上的灯泡快速修复。完整详细信息:
[`docs/database-knowledge/business-logic-context.md`](docs/database-knowledge/business-logic-context.md)。
### 报告错误的发现
Cardinal 是由真实的代码库调优的。如果发现是错误的 —— 误报、
遗漏的捕获或崩溃 —— 请使用其中一个
[issue 模板](https://github.com/AnujChhikara/cardinal/issues/new/choose)进行报告。
在 `cardinal suppress`(或 VS Code 抑制快速修复)之后,Cardinal 会提供一个
**预填写的报告链接** —— 在 GitHub 上查看并点击 Create;绝对不会
自动发送任何内容。每个确认的报告都作为永久的回归
测试发布在 [`packages/core/test/corpus/`](packages/core/test/corpus/) 中,因此修复后的
误报永远不会再次出现。
## 配置
在你的项目中放置一个 **`cardinal.config.json`**(或 `.yaml`)以关闭规则
或更改其严重级别。它的发现方式与
知识文件一样,都是通过从当前目录向上遍历。
```
{
"rules": {
"over-fetch": "off",
"unbounded-read": "warning",
"n-plus-one": "error"
}
}
```
每条规则对应 `"error"`、`"warning"`、`"info"` 或 `"off"`。你没有
列出的规则将保持其默认行为。使用 `--no-config` 完全禁用发现功能。
VS Code 扩展会读取同一个文件,并在其发生更改时重新进行 lint。
## 工作原理
Cardinal 围绕**三条通道的 pipeline** 设计,根据检查运行的*时间*进行划分:
- **通道 1 — 语法**(每次击键):高置信度结构错误。`n-plus-one` 位于此处。
- **通道 2 — 常量**(每次击键):确定性的 DB/ORM 事实(例如 list-size limit)。
- **通道 3 — 数据流**(防抖/保存时):带置信度标记的警告。
指导原则:**精度优先于召回率** —— 一个谎报军情的 linter 当天
就会被禁用。高置信度检查视为 error;推断出的检查视为
warning。
设计详情:[`docs/superpowers/specs/2026-07-10-queryguard-design.md`](docs/superpowers/specs/2026-07-10-queryguard-design.md)(原始设计文档早于此次重命名)。
规则编写参考:[`docs/database-knowledge/`](docs/database-knowledge/)。
## 路线图
已发布:配置文件、真正的 SQL 解析器、**跨所有适配器的上下文感知**
(知识文件的 `over-fetch` / 基数现在适用于 Drizzle、Mongoose
和原生 SQL,而不仅仅是 Prisma)、**机器可读的 `--format json`** 输出(每个
发现都附带 why/fix 解释,以便 AI agent 应用修复),
**Prisma 的 schema 感知**(`unindexed-query` 从
`schema.prisma` 读取索引),以及发布到 **VS Code Marketplace**、**Open VSX** 和
**npm**(在版本 tag 上自动化 —— 参见 [`PUBLISHING.md`](PUBLISHING.md))。下一步:
- 为 Drizzle、TypeORM 和 Mongoose schema 提取索引(超越 Prisma 的 `unindexed-query`)。
- 更多由解析器支持的 SQL 规则:子查询、`HAVING`/`GROUP BY` 滥用、`SELECT *`。
- 更多引擎(MySQL/PlanetScale/Postgres limit)和数据层(Kysely、Sequelize)。
- 深度模式:跨模块数据流检查。
## License
MIT
标签:CMS安全, JavaScript, SOC Prime, TypeScript, 云安全监控, 安全插件, 开发工具, 性能优化, 数据可视化, 数据库, 检测绕过, 自动化攻击, 静态分析