Obviously-Not/concept-scanner
GitHub: Obviously-Not/concept-scanner
基于本地 LLM 的代码库工程概念扫描器,自动提取并评估独特的技术机制,输出结构化评审结果。
Stars: 0 | Forks: 0
# 概念扫描器
本地优先的**工程概念扫描器**,用于代码库分析。将其指向某个代码仓库或目录,它会提取出独特的技术机制(即“概念”),并根据四个核心工程质量维度(技术独特性、实现深度、问题特异性、通用性)及补充信号对它们进行评分,最后将结果保存在本地供您审查。
JSON 输出与 [Obviously-Not 平台](https://github.com/Obviously-Not) 的 `code_scan` 流水线(`CharacterizationOutputSchema` v1.3.0)在数据结构上完全匹配——仅包含工程词汇,不涉及法律术语。目前的兼容性是通过字段级别的检查来确认的;尚未针对线上平台的解析器进行实际验证。法律审查是由具备专业资质的人员单独执行的下游步骤。
默认情况下,所有分析都通过本地的 [Ollama](https://ollama.ai) 模型运行,因此**您的代码会保留在您的机器上**:无需 API 密钥,不进行云端调用,也没有单次扫描成本。您也可以选择将其指向远程的 OpenAI 兼容提供商(`--provider openai-compatible`),这会将您的源代码发送到该端点;详情请参阅 [PROVIDERS.md](PROVIDERS.md)。
## 快速开始
```
# 安装并启动 Ollama (https://ollama.ai)
ollama serve
# 构建 scanner
go build -o concept-scanner .
# 扫描本地目录(或远程 repo URL)。
# 首次运行时,具有 memory-aware 的交互式 picker 会帮助您选择模型。
./concept-scanner scan ./my-project
# 以交互方式查看发现的 concepts
./concept-scanner review
# 将已批准的 concept 导出为 JSON 草稿
./concept-scanner submit
```
## 在 Docker 中运行
预构建的镜像已发布至 GHCR 和 Docker Hub:
```
docker pull ghcr.io/obviously-not/concept-scanner:v1
# 或者:docker pull leegitw/concept-scanner:v1
```
扫描器将连接您**主机上的 Ollama**。在容器内部,`localhost` 指向的是容器自身的环回地址,而非宿主机,因此需要将 `--ollama-host` 指向宿主机:
```
# macOS / Windows (Docker Desktop):
docker run --rm -v "$PWD:/workspace" ghcr.io/obviously-not/concept-scanner:v1 \
scan /workspace --ollama-host http://host.docker.internal:11434
# Linux:添加 host-gateway 映射(或使用 --network host):
docker run --rm --add-host=host.docker.internal:host-gateway \
-v "$PWD:/workspace" ghcr.io/obviously-not/concept-scanner:v1 \
scan /workspace --ollama-host http://host.docker.internal:11434
```
如果希望使用远程提供商而不是宿主机的 Ollama,请传入 `--provider openai-compatible --base-url ... --primary-model ...` 以及 API 密钥环境变量(`-e OPENROUTER_API_KEY=...`)。请参阅 [PROVIDERS.md](PROVIDERS.md)。
若要将 Ollama 与扫描器打包在一起运行(无需宿主机 Ollama),可以使用内置的 [`docker-compose.yml`](docker-compose.yml)。注意:除非进行 GPU 直通(仅限 NVIDIA/Linux;Apple Silicon 上的 Docker 无法进行 GPU 直通),否则容器中的 Ollama 只能使用 CPU。因此在 Mac 上,上述使用宿主机 Ollama 的方法通常会更快。
## GitHub Action
在 CI 中运行概念扫描。这是一个 Docker action,因此仅在 **Linux runner** 上运行。GitHub 托管的 runner 无法运行本地 LLM,因此在那里必须使用远程的 OpenAI 兼容提供商;而在带有 Ollama 的自托管 runner 上,您可以改用本地、无外网请求的模式。
```
# .github/workflows/concept-scan.yml
name: concept-scan
on: [workflow_dispatch]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Obviously-Not/concept-scanner@v1
with:
provider: openai-compatible
base-url: # see PROVIDERS.md
model: # see PROVIDERS.md
api-key: ${{ secrets.OPENROUTER_API_KEY }} # a secret, never inline
```
输入参数:`repo-path`(默认为 `.`)、`provider`、`base-url`、`model`、`ollama-host`、`api-key`、`extra-args`。`api-key` 必须作为 secret 传入。具体的端点和模型字符串请参见 [PROVIDERS.md](PROVIDERS.md)。该 action 不附带默认的提供商或密钥:由您自己提供后端,这与本地运行的方式完全一致。
## 选择模型
首次交互式运行时会显示一个**内存感知选择器**,它会检测您的 RAM 并
推荐合适的配置方案,同时会标记出任何对您的机器而言过大的选项。您的选择
将被保存到 `data/config.json` 并在后续运行中复用。随时可以通过
`concept-scanner init` 重新选择。
| 配置方案 | 模型 | 预估 RAM | 备注 |
|---------|----------|-------------|-------|
| **Fast** | `qwen2.5-coder:7b` | ~7 GB | 适用于任何笔记本电脑的快速扫描 |
| **Balanced**(推荐) | `qwen3-coder:30b` | ~22 GB | 能力与体积的最佳平衡 |
| **Validated** | `qwen3-coder:30b` + `gpt-oss:20b` | ~38 GB | 增加了一个 Pass 2 验证模型 |
| **Thorough** | `qwen3-coder-next` | ~50 GB | 更大的 MoE 模型,速度较慢 |
| **Custom** | 任何 Ollama tag | — | 输入您自定义的模型 |
模型解析优先级:显式参数(`--primary-model` / `--multi-model`) →
已保存的 `data/config.json` → 交互式选择器(仅限 TTY) → 内置默认值。要在
非交互式环境(CI、脚本)中运行,请传入带 `--primary-model` 的 `--no-interactive`:
```
# 使用特定的已安装模型,无提示(支持 single-pass 或 multi-model)
./concept-scanner scan ./my-project --no-interactive --primary-model qwen2.5-coder:7b
# 并行使用两个模型并进行 merge 步骤
./concept-scanner scan ./my-project --multi-model \
--primary-model qwen3-coder:30b --secondary-model gpt-oss:20b
```
除非您传入 `--no-auto-pull`,否则缺失的模型会在首次使用时自动下载。
接受任何已安装的 Ollama 模型;无法识别的 tag 会提示一行注意事项,并按原样运行。
## 工作原理
1. **收集**源代码文件(跳过 `vendor`、`node_modules`、`.git` 等)。
2. 在扫描前**验证**目标——包括符号链接逃逸、设备文件和 zip 炸弹检查(远程克隆的代码库还会受到仓库大小/文件数量的限制)。
3. 通过本地模型利用基于原则的提取(PBD)来**提炼**每个文件。
4. **合成**候选概念。
5. 从四个核心工程维度(技术独特性、实现深度、问题特异性、通用性)以及补充信号(普遍性、范式转变)对每个概念进行**刻画**。商业维度(产品核心性、可防御性)会输出为 `null`——因为概念扫描器没有您的市场上下文信息,所以这些字段需要由下游的使用者(您的审查流程或平台入口)来填充。输出结果还会包含该概念的关键见解、输入/输出、组件以及可比的技术。
6. 结合依赖许可证风险和 git 作者信息进行**丰富**。
7. 将结果**保存**到 `./data/scans/` 目录下,同时生成一个 `SUMMARY.md`,并将溯源审计日志保存在 `./data/audit/` 下。
当生成过程触及 token 上限时会**直接报错**,而不是静默截断,并且所有
输出的文本都会经过一层输出检查,以确保结果中不包含法律术语。
## 命令
| 命令 | 用途 |
|---------|---------|
| `scan ` | 扫描目录或远程代码仓库以提取概念 |
| `review ` | 逐步检查发现的概念并进行批准/拒绝操作 |
| `submit ` | 将已批准的概念导出为符合平台规范的 JSON 草稿 |
| `triage --workspace ` | 在已完成的扫描上重新运行溯源分类(`scan` 会自动运行它;这主要用于在更改模型/提示词后重新运行) |
| `bridges ` | 在已完成的扫描上发现跨概念桥梁(Pass 3 组合) |
| `models` | 列出推荐的模型方案,并显示哪些已经下载到本地 |
| `init` | 重新运行内存感知模型选择器并保存选择 |
| `version` | 打印版本信息和默认模型 |
## 常用参数 (`scan`)
| 参数 | 描述 |
|------|-------------|
| `--primary-model` / `--secondary-model` | 显式选择模型(可以是任何已安装的 Ollama tag) |
| `--multi-model` | 并行运行两个本地模型并合并结果 |
| `--no-interactive` | 跳过首次运行时的选择器;使用已保存的配置或默认值(适用于 CI/脚本) |
| `--skip-review` | 仅进行单次扫描——跳过 Pass 2 验证模型(速度更快) |
| `--thorough` | 使用体积更大、质量更高的模型 |
| `--include-textbook` | 同时输出被分类为 `textbook` 的概念(默认行为:对所有机制进行分类,但暂不发 `textbook` 的概念——见下文) |
| `--two-phase` | 实验性功能。将生成过程分为两次调用执行(自由格式提炼,然后是结构化提取),而不是一次受语法限制的调用(见下文的“两阶段生成”)。也可以通过 `CS_TWO_PHASE=1` 来设置。 |
| `--extract-model` | `--two-phase` 使用的第二阶段提取模型(默认为 `gpt-oss:20b`)。也可使用 `CS_EXTRACT_MODEL`。 |
| `--timeout` | 单次请求的推理超时时间(单次调用默认为 10m,在 `--two-phase` 下为 30m)。针对慢速模型或大批量任务可适当调大。 |
| `--no-triage` | 跳过针对每个概念的溯源分类阶段(速度更快;会将未落地的概念标记为 `pending`,而不是 `file_missing` 或 `rescued`) |
| `--no-auto-pull` | 如果模型缺失则直接报错,而不是自动下载 |
| `--ollama-host` | Ollama API URL(默认为 `http://localhost:11434`) |
| `--full-history` | 获取完整的 git 历史记录,以获得更丰富的文件溯源信息 |
| `--json` | 输出机器可读的 `{ data, next_steps, notice }` 数据封装(见“机器可读输出”) |
| `-v, --verbose` | 详细的进度输出 |
运行 `./concept-scanner scan --help` 获取完整列表。
## 概念输出与落地状态
每个发现的概念都会附带一个 `grounding` 字段,用于记录
扫描器是如何验证其 `location.file` 的:
| 状态 | 含义 |
|-------|---------|
| `grounded` | 模型输出的路径在当前工作区中原样解析为了一个真实存在的文件。 |
| `repaired` | 模型输出的路径格式错误,最终处理阶段自动进行了修复(例如 `src/foo.ts` → `app/src/foo.ts`)。 |
| `rescued` | 通过模糊文件名搜索或 LLM 分类调用,找到了该概念实际所在的真实文件。最初 LLM 输出的路径会保存在 `grounding_original_file` 中。 |
| `file_missing` | 分类阶段搜索了候选文件,但无法在任何地方将该概念落地——极有可能是模型幻觉。 |
| `uncertain` | 运行了分类阶段,但模型无法给出明确结论。 |
| `pending` | 跳过了分类阶段(`--no-triage`)。 |
下游消费者通常会筛选出 `grounded`、`repaired` 或 `rescued` 状态的概念,
并对 `file_missing` / `uncertain` 状态的概念保持应有的怀疑态度。
## 分类(`distinctive` / `borderline` / `textbook`)
扫描器会**提取并分类它发现的每一个机制——它绝不会
默默地丢弃任何一个。** 每个概念都包含一个 `classification` 字段:
| 类别 | 含义 |
|-------|---------|
| `distinctive` | 同时满足所有三个工程维度的门槛(技术独特性、实现深度、问题特异性)。 |
| `borderline` | 满足其中一个或两个维度,或者虽然是常见模式但属于非常强的实例。 |
| `textbook` | 标准库用法、常见设计模式或常规的实现。 |
保留还是丢弃的决策是一个**下游的、可调的步骤**,而不是模型
在提示词中执行的操作:默认情况下,结果会包含 `distinctive` + `borderline`
并**保留 `textbook` 不输出**(但仍会报告它们的数量,因此没有任何内容被隐藏)。传入
`--include-textbook` 可将它们包含在结果中。正因如此,当强大的前沿模型面对一个浅薄的代码库时,不再会返回空集——它会将标准模式
分类为 `textbook`,而不是自我审查到什么都不输出。
## 两阶段生成(`--two-phase`,实验性功能)
默认情况下,每个生成阶段(发现、刻画、桥梁合成)
都是一次单独的调用,其解码过程受到 JSON schema 的语法限制。这对于
指令/代码模型来说既快速又可靠,但这没有给推理模型留下
任何推理空间(schema 会强制从第一个 token 开始就是 JSON token),而且在远程
提供商上,往往会导致描述性字段的内容不够丰富。
`--two-phase` 会将这些阶段中的每一个拆分为**两次**调用执行:
1. **提炼(自由格式):** 主模型用散文形式描述该机制,
不受 schema 约束,因此推理模型可以真正进行推理。
2. **提取(结构化):** 一个快速的指令模型(`--extract-model`,默认为
`gpt-oss:20b`)将该段散文转换为符合 schema 结构的 JSON。
这与扫描器所匹配的模式相呼应,预期这将允许
推理模型参与到发现过程中,并自然地填充描述性字段
(从而淘汰远程刻画的补全回退机制)。这会使得每项
消耗两次调用而不是一次;但提取调用的体量很小且速度很快。
该模式**默认关闭**,目前正在与单次调用路径进行 A/B 测试,
之后才会考虑将其为默认值。使用 `--two-phase`(或 `CS_TWO_PHASE=1`)启用它,并
通过 `--extract-model `(或 `CS_EXTRACT_MODEL`)选择提取器。它
目前适用于单模型路径以及刻画和桥梁
合成阶段;多模型(`--multi-model`)的 Pass 1 发现阶段暂不支持两阶段模式。
自由格式的提炼在每个批次上花费的时间更长(因为它生成的是散文,而且推理
模型的推理过程通常很长),因此 `--two-phase` 将单次请求的超时底线
提高到了 **30 分钟**,以确保大批量任务能够跑完,而不是在提炼过程中被强制终止。
如果您的模型需要更多时间,请使用 `--timeout` 覆盖该设置,并注意,由于每项
需要进行两次调用,其实际耗时明显高于单次调用路径。
## 机器可读输出(`--json`)
每个产生输出的命令(`scan`、`review`、
`submit`、`bridges`、`triage`)都支持 `--json`,并发出与平台对齐的 HATEOAS
数据封装——在成功和失败时保持相同的数据结构,因此一个解析器即可处理所有情况。
进度信息会输出到 stderr,因此 stdout 是纯 JSON。(`review --json` 是一个
非交互式的**状态**投影——它报告当前的审查状态,
而不是提示用户进行操作。)
```
{
"data": { "scan_id": "...", "concepts": [ ... ], "textbook_held": 12 },
"next_steps": [
{ "action": "Review the discovered concepts", "command": "concept-scanner review ",
"priority": "high", "reason": "Approval gates submission", "timing": "soon" }
],
"notice": "12 textbook-classified concept(s) held back (use --include-textbook to include them)."
}
```
失败时,`data` 为 `null`,错误信息包含在 `notice` 中。每个命令的输出
(以及每个错误)都至少包含两个优先级排序的 `next_steps`,附带原因和
可直接运行的 `command`,因此无论是人类还是 AI agent 都能随时知道下一步
该做什么——即使在返回零个概念时也是如此。
## 环境要求
- 本地运行 [Ollama](https://ollama.ai)
- Go 1.25+(用于从源码构建)
- 足够的 RAM/VRAM 来支撑所选的模型。选择器会标记超出您
检测到的内存上限的配置方案;**Fast** 方案(`qwen2.5-coder:7b`)可以在大多数笔记本电脑上运行。
## 隐私
- 不会对外部服务发起 API 调用
- 所有处理过程都在您的机器上本地进行
- 代码绝对不会离开您的本地环境
- 无限次扫描(无单次调用成本)
该隐私保证的前提是 Ollama 在本地运行。如果您将
`--ollama-host` 指向了一个远程地址(除了 `localhost` /
`127.0.0.1` 之外的任何地址),扫描器在启动时会记录一条警告——但请求
仍会被发送出去。**请在同一台机器上运行 Ollama,以确保隐私承诺成立。**
## 贡献与社区
- [CONTRIBUTING.md](CONTRIBUTING.md) — 开发环境设置、您在提交 PR 时
可能会遇到的测试检查,以及在这里什么样的改动才是好的改动
- [SECURITY.md](SECURITY.md) — 如何私下报告安全问题
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) — Contributor Covenant 2.1
- [patent-skills](https://github.com/Obviously-Not/patent-skills) — ObviouslyNot 生态系统中的一个
开源姊妹项目(用于专利扫描的 AI agent 技能)。此链接
仅作为背景参考,并非依赖项:concept-scanner 可以完全独立运行。
## 开源许可
Apache-2.0 — 请参阅 [LICENSE](LICENSE)。
标签:AI风险缓解, Clair, EVTX分析, GitHub Action, Go语言, LLM评估, Ollama, 代码质量评估, 日志审计, 本地大模型, 程序破解, 请求拦截, 错误基检测, 静态代码分析