ferc/pruneguard
GitHub: ferc/pruneguard
基于 Rust 的高性能 JS/TS monorepo 死代码检测与架构治理工具,支持安全删除分析和 CI 集成。
Stars: 1 | Forks: 0
# pruneguard
Pruneguard 审查 JS/TS 仓库,告诉你哪些代码未使用、哪些违反了架构规则、哪些可以安全删除,以及你的分支中发生了哪些变化。
它以编译好的 Rust 二进制文件形式发布,并带有一层轻薄的 JS 封装。无需 Rust 工具链,无需编译,无需原生插件 —— 只需 `npm install` 即可开始使用。
## 快速开始
```
npm install pruneguard
```
在 `package.json` 中添加脚本:
```
{
"scripts": {
"review": "pruneguard",
"scan": "pruneguard scan",
"prune:delete": "pruneguard safe-delete"
}
}
```
运行:
```
# 审查你的 repo (默认命令)
npx pruneguard
# 仅审查你 branch 上的更改
npx pruneguard --changed-since origin/main
# 完整详细扫描
npx pruneguard scan
# 检查 file 是否可安全删除
npx pruneguard safe-delete src/legacy/old-widget.ts
# 获取修复计划
npx pruneguard fix-plan src/legacy/old-widget.ts
```
需要 Node.js >= 18。支持的平台:macOS (ARM64, x64), Linux (x64/ARM64, glibc 和 musl), Windows (x64, ARM64)。
有关从安装到获取首个结果的完整演练,请参阅 [docs/getting-started.md](docs/getting-started.md)。
## 工作原理
pruneguard 为每个支持的平台发布一个编译好的 Rust 二进制文件。JS API 和 CLI 都在后台调用该二进制文件。在你的本地机器上,daemon 会将图保存在内存中保持温热,以实现亚毫秒级的查询。在 CI 中(或当你传递 `--daemon off` 时),每次调用都是一次全新的一次性运行。
```
npm install pruneguard
|
v
@pruneguard/cli- <-- native binary, auto-selected by OS+arch
|
v
pruneguard (JS wrapper) <-- spawns the binary, parses JSON output
|
+-- CLI: npx pruneguard
+-- JS API: import { review } from "pruneguard"
```
| 上下文 | 模式 | 原因 |
|---------------------|----------|-------------------------------------------|
| 本地终端 | daemon | 保持图温热,即时 `review` 和 `impact` |
| CI / `--daemon off` | 一次性运行 | 确定性,无残留进程 |
## CLI
### 命令
#### 日常使用
```
pruneguard # Review your repo or branch (default command)
pruneguard scan [paths...] # Full repo scan with detailed findings
pruneguard safe-delete # Check if files or exports are safe to remove
pruneguard fix-plan # Generate a remediation plan
```
#### 调查
```
pruneguard impact # Analyze blast radius for a target
pruneguard explain # Explain a finding with proof chain
```
#### 策略与治理
```
pruneguard suggest-rules # Auto-suggest governance rules from graph analysis
```
#### 设置
```
pruneguard init # Generate pruneguard.json with schema reference
pruneguard print-config # Print resolved config
```
#### 调试与迁移
```
pruneguard debug resolve --from # Trace module resolution
pruneguard debug entrypoints # List detected entrypoints
pruneguard debug runtime # Print binary/platform info
pruneguard daemon start|stop|status # Manage the background daemon
```
### 全局标志
```
-c, --config Config file path [default: pruneguard.json]
--format text | json | sarif | dot
--profile production | development | all
--changed-since Only report findings for changed files
--focus Filter findings to matching paths
--severity Minimum severity: error | warn | info
--no-cache Disable incremental cache
--no-baseline Disable baseline suppression
--max-findings Cap reported findings
--require-full-scope Fail if scan would be partial-scope
--daemon auto | off | required
```
### 常见 CLI 工作流
```
# 审查你的 branch (日常命令)
pruneguard --changed-since origin/main
# 不受 baseline 影响的完整扫描 (确定性 CI)
pruneguard --no-baseline --no-cache scan
# 聚焦到 repo 的一个切片 (完整分析,过滤输出)
pruneguard --focus "src/**" scan
# 如果扫描是部分范围的则失败
pruneguard --require-full-scope scan
# 用于 CI pipelines 的 JSON 输出
pruneguard --format json
# 用于 GitHub Code Scanning 的 SARIF
pruneguard --format sarif scan > results.sarif
# Graphviz DOT 输出
pruneguard --format dot scan | dot -Tsvg -o graph.svg
# file 的 Blast radius
pruneguard impact src/utils/helpers.ts
# 解释特定的 finding
pruneguard explain unused-export:packages/core:src/old.ts#deprecatedFn
# 检查 files 是否可安全删除
pruneguard safe-delete src/utils/old-helper.ts src/legacy/widget.ts
# Debug module resolution
pruneguard debug resolve ./utils --from src/index.ts
```
## JS API
每个函数都会调用原生二进制文件并返回解析后的类型化结果。
完整 API 参考请参阅 [docs/js-api.md](docs/js-api.md)。
### review
```
import { review } from "pruneguard";
const result = await review({
baseRef: "origin/main",
noCache: true,
});
console.log("Blocking:", result.blockingFindings.length);
console.log("Advisory:", result.advisoryFindings.length);
console.log("Trust:", JSON.stringify(result.trust));
if (result.blockingFindings.length > 0) {
for (const f of result.blockingFindings) {
console.error(` [${f.confidence}] ${f.code}: ${f.message}`);
}
process.exit(1);
}
```
### scan
```
import { scan } from "pruneguard";
const report = await scan({
cwd: "/path/to/repo", // optional, defaults to process.cwd()
profile: "production", // optional: "production" | "development" | "all"
changedSince: "origin/main", // optional
focus: "packages/core/**", // optional
noCache: true, // optional
noBaseline: true, // optional
requireFullScope: true, // optional
paths: ["src/lib"], // optional, partial-scope scan
});
console.log(report.summary.totalFindings);
console.log(report.findings[0].id, report.findings[0].confidence);
```
### safeDelete
```
import { safeDelete } from "pruneguard";
const result = await safeDelete({
targets: ["src/legacy/old-widget.ts", "src/utils/deprecated-helper.ts"],
});
console.log("Safe:", result.safe.map(e => e.target));
console.log("Blocked:", result.blocked.map(e => `${e.target}: ${e.reasons.join(", ")}`));
console.log("Deletion order:", result.deletionOrder);
```
### fixPlan
```
import { fixPlan } from "pruneguard";
const plan = await fixPlan({
targets: ["unused-export:packages/core:src/old.ts#deprecatedFn"],
});
for (const action of plan.actions) {
console.log(`${action.kind}: ${action.targets.join(", ")} (${action.risk} risk)`);
for (const step of action.steps) {
console.log(` - ${step.description}`);
}
}
```
### run
```
import { run } from "pruneguard";
// Run arbitrary CLI args
const result = await run(["--format", "json", "--no-cache", "scan"]);
console.log(result.exitCode);
console.log(result.stdout);
console.log(result.durationMs);
```
### binaryPath
```
import { binaryPath } from "pruneguard";
// Resolve the native binary path (for custom integrations)
console.log(binaryPath());
// => /path/to/node_modules/@pruneguard/cli-darwin-arm64/bin/pruneguard
```
### 其他 API 函数
```
import {
impact,
explain,
suggestRules,
loadConfig,
schemaPath,
scanDot,
resolutionInfo,
debugResolve,
debugEntrypoints,
} from "pruneguard";
// Blast radius
const blast = await impact({ target: "src/utils/helpers.ts" });
console.log(blast.affectedEntrypoints, blast.affectedFiles);
// Proof chain
const proof = await explain({ query: "src/old.ts#deprecatedFn" });
console.log(proof.proofs);
// Suggest governance rules from graph analysis
const rules = await suggestRules();
console.log(rules.suggestedRules);
// Load resolved config
const config = await loadConfig();
// Path to the bundled JSON schema
console.log(schemaPath());
// Graphviz DOT output
const dot = await scanDot();
// Binary resolution diagnostics
const info = resolutionInfo();
console.log(info.source, info.platform);
```
### 完整 API 参考
| 函数 | 签名 | 描述 |
|---|---|---|
| `review` | `(options?) => Promise` | 审查你的仓库或分支 |
| `scan` | `(options?) => Promise` | 带有详细发现的全仓库扫描 |
| `safeDelete` | `(options) => Promise` | 检查文件或导出是否可以安全移除 |
| `fixPlan` | `(options) => Promise` | 生成修复计划 |
| `impact` | `(options) => Promise` | 分析目标的波及范围 |
| `explain` | `(options) => Promise` | 附带证据链解释发现 |
| `suggestRules` | `(options?) => Promise` | 自动建议治理规则 |
| `loadConfig` | `(options?) => Promise` | 加载已解析的配置 |
| `schemaPath` | `() => string` | 捆绑的配置 JSON schema 路径 |
| `binaryPath` | `() => string` | 已解析的原生二进制文件路径 |
| `run` | `(args, options?) => Promise` | 运行任意 CLI 参数 |
| `scanDot` | `(options?) => Promise` | Graphviz DOT 输出 |
### 错误处理
所有 API 函数在失败时都会抛出 `PruneguardExecutionError`。该错误包含一个 `code` 字段,用于程序化处理:
| 代码 | 含义 |
|---|---|
| `PRUNEGUARD_BINARY_NOT_FOUND` | 无法定位原生二进制文件 |
| `PRUNEGUARD_EXECUTION_FAILED` | 二进制文件以意外代码退出 |
| `PRUNEGUARD_JSON_PARSE_FAILED` | 二进制文件输出无效 JSON |
```
import { scan, PruneguardExecutionError } from "pruneguard";
try {
await scan();
} catch (err) {
if (err instanceof PruneguardExecutionError) {
console.error(err.code, err.message);
console.error("stderr:", err.stderr);
}
}
```
## GitHub Actions
pruneguard 包含一个可复用的 [GitHub Action](.github/actions/pruneguard/) 用于 CI 集成。有关完整的设置指南,包括基线工作流、SARIF 和 monorepo 策略,请参阅 [docs/ci-integration.md](docs/ci-integration.md)。
### 分支审查关卡
```
name: pruneguard
on: [pull_request]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-node@v6
with:
node-version: 24
- run: npm install pruneguard
- name: Branch review
run: npx pruneguard --changed-since origin/main --format json
```
退出代码 0 表示没有阻断性发现;退出代码 1 表示存在阻断性发现。JSON 输出包含 `blockingFindings` 和 `advisoryFindings` 数组,用于进一步处理。
### 基线门控 CI
通过在 `main` 分支上保存基线并仅针对新发现进行失败处理,来增量式地采用 pruneguard。
```
name: pruneguard-baseline
on:
push:
branches: [main]
pull_request:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-node@v6
with:
node-version: 24
- run: npm install pruneguard
# On main: save baseline
- name: Save baseline
if: github.ref == 'refs/heads/main'
run: npx pruneguard --no-cache --no-baseline --format json scan > baseline.json
- uses: actions/upload-artifact@v6
if: github.ref == 'refs/heads/main'
with:
name: pruneguard-baseline
path: baseline.json
# On PRs: compare against baseline
- uses: actions/download-artifact@v7
if: github.event_name == 'pull_request'
with:
name: pruneguard-baseline
continue-on-error: true
- name: Check for new findings
if: github.event_name == 'pull_request'
run: |
npx pruneguard --no-cache --no-baseline --format json scan > current.json
node -e "
const fs = require('fs');
if (!fs.existsSync('baseline.json')) { console.log('No baseline found, skipping comparison'); process.exit(0); }
const baseline = JSON.parse(fs.readFileSync('baseline.json', 'utf-8'));
const current = JSON.parse(fs.readFileSync('current.json', 'utf-8'));
const baseIds = new Set(baseline.findings.map(f => f.id));
const newFindings = current.findings.filter(f => !baseIds.has(f.id));
if (newFindings.length > 0) {
console.error(newFindings.length + ' new finding(s):');
newFindings.forEach(f => console.error(' ' + f.id + ': ' + f.message));
process.exit(1);
}
console.log('No new findings relative to baseline.');
"
```
### 安全删除审查
在自动清理 PR 合并之前,验证标记为移除的候选项是否确实可以安全删除。
```
name: safe-delete-check
on:
pull_request:
paths:
- "scripts/cleanup-*.mjs"
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-node@v6
with:
node-version: 24
- run: npm install pruneguard
- name: Check deletion safety
run: |
# Find files deleted in this PR
DELETED=$(git diff --name-only --diff-filter=D origin/main...HEAD | grep -E '\.(ts|tsx|js|jsx|mts|mjs)$' || true)
if [ -z "$DELETED" ]; then
echo "No source files deleted in this PR."
exit 0
fi
echo "Checking deletion safety for:"
echo "$DELETED"
npx pruneguard --format json safe-delete $DELETED
```
## 信任模型
- 全仓库 `scan` 是删除决策的可信模式。
- `--focus` 在完整分析后过滤报告的发现。
- 位置参数 `scan ` 会缩小分析的文件集范围,并作为部分范围/建议性进行报告。
- `--require-full-scope` 将建议性的部分范围死代码扫描转变为硬性失败(退出代码 2)。
- `--no-baseline` 禁用基线自动发现,以实现确定性的 CI、奇偶校验和基准测试。
- 在包含许多未解析说明符的仓库中删除代码前,请使用 `impact` 和 `explain`。
- 发现结果带有 `confidence`(高、中、低)以指示可信度。
## 配置
大多数仓库无需配置文件即可工作。运行 `pruneguard init` 生成一个仅包含 `$schema` 引用的最小配置,以便编辑器自动补全。
对于需要自定义的仓库:
```
{
"$schema": "./node_modules/pruneguard/configuration_schema.json",
"workspaces": {
"packageManager": "pnpm",
"roots": ["apps/*", "packages/*"]
},
"analysis": {
"unusedExports": "error",
"unusedFiles": "warn",
"unusedDependencies": "error",
"cycles": "warn"
}
}
```
完整配置参考请参阅 [docs/config.md](docs/config.md)。
## 文档
| 指南 | 描述 |
|---|---|
| [入门指南](docs/getting-started.md) | 从安装到获取首个结果的演练 |
| [配置](docs/config.md) | 完整配置参考 |
| [CI 集成](docs/ci-integration.md) | GitHub Actions, 基线工作流, SARIF |
| [JS API 参考](docs/js-api.md) | 完整的类型化 API 文档 |
| [Agent 集成](docs/agent-integration.md) | AI Agent 工作流(review + safe-delete + fix-plan) |
| [配方](docs/recipes.md) | 复制粘贴自动化示例 |
| [迁移](docs/migration.md) | 从其他工具迁移 |
| [架构](docs/architecture.md) | 内部设计和流水线阶段 |
| [性能](docs/performance.md) | 性能模型、缓存行为、基准测试 |
| [基准测试](docs/benchmarks.md) | 目标延迟和基准测试方法论 |
## 开发
依赖:Rust (stable), Node.js, pnpm, just
```
just build-js # Build the JS wrapper
just stage-release # Stage npm packages into .release/
just pack-smoke # End-to-end package install smoke test
just smoke-repos # Opt-in real-repo smoke tests
just parity # Real-repo parity checks
just benchmark CASE=../../repo # Benchmark a single corpus
just benchmark-repos # Benchmark all configured corpora
```
其他有用的命令:
```
just ready # fmt + check + test + lint
just build # Release binary
just run scan # Run against current directory
just schemas # Regenerate shipped schemas
just schemas-check # Verify schemas are committed
just ci # Full CI pipeline locally
```
## 许可证
MIT
标签:Agent-first, CMS安全, GNU通用公共许可证, JavaScript, MITM代理, Node.js, Rust, TypeScript, WebSocket, 二进制工具, 云安全监控, 代码审查, 代码清理, 依赖分析, 可视化界面, 守护进程, 安全删除, 安全插件, 开发效率, 影响分析, 数据可视化, 暗色界面, 架构治理, 死代码检测, 网络可观测性, 网络流量审计, 通知系统, 静态分析