Skip to main content
Glama
codepilot

codepilot

by ronronner02

CodePilot-Agent

English summary

输入 GitHub 仓库地址,产出三样东西:可追溯到具体文件的架构分析报告、针对该仓库的 单轮代码问答、以及结构/错误处理/安全三类检查的代码评审。仓库分析能力同时以 MCP server 形式对外暴露,可被 Claude Code、Cursor 等客户端直接调用。

它与同类工具的区别

报告的每条结论必须能落到文件路径,且这一点由程序校验而非提示词约束 —— 引用的路径必须 真实存在、行号必须在文件实际行数内,过不了校验的结论会被丢弃并计入报告的缺失说明。所以 不会出现「本项目采用分层架构、代码结构清晰」这类放到任何仓库都成立的句子。

Related MCP server: Gitingest-MCP

架构

仓库地址 → clone 与语言识别 → 静态解析层(tree-sitter)
                                    ↓
                        结构骨架:符号表 · 依赖图 · 模块聚类 · 入口点
                                    ↓
              ┌─────────────────────┴─────────────────────┐
              ↓                                           ↓
     Planner 定深挖范围                            Reviewer 三类检查
              ↓                                           ↓
   ┌──────────┴──────────┐                             评审发现
   ↓                     ↓
模块子 Agent 并行分析   切块与向量索引  ← 与扇出、评审同时进行
   ↓                     ↓
汇总 + 引用校验      单轮代码问答 · 语义检索
   ↓
架构报告

图里画在同一层的确实同时执行。索引与模块扇出并行是有意排布的结果,不是自然发生的 —— LangGraph 按 superstep 推进,同层节点并发而跨层必然串行,所以「哪个节点挂在哪个节点之后」 决定了它与谁并行。索引是最慢的一段(fastapi 实测 30.5 分钟),把它排在扇出之前会让用户 干等半小时才看到第一条模块分析。

确定的部分用确定手段:文件树、import 依赖图、模块聚类、入口点、技术栈识别全部由静态解析 产出,不过 LLM。需要判断的部分才上 Agent —— Planner 决定深挖哪些模块,模块子 Agent 带工具 读代码,Reviewer 的候选点由 AST 定位后交模型取舍。

RAG 只服务问答链路。架构问题(分几层、谁依赖谁、入口在哪)是图问题,靠 embedding 相似度 问不出答案。

快速开始

用 Docker(推荐)

cp .env.example .env
# 编辑 .env,至少填 DEEPSEEK_API_KEY
docker compose up --build

打开 http://localhost。

两个端口默认都只绑 127.0.0.1,即不对外可达。对外开放的方式见下面的「公网部署」一节 —— 那需要访客自带 LLM 凭证,并确认三项资源保护的阈值适合你的机器。

索引缓存存在具名卷 codepilot-data 里,容器重启后仍在,同一仓库的二次分析会命中缓存并 跳过向量化。

本地开发

需要 Python 3.13 与 Node 24,与容器镜像一致。低于此版本 pip install 会直接报 requires-python 不满足——那是有意的:3.13 以下从未被验证过,不声称支持。

# 后端
pip install -e ".[dev]"
python -m uvicorn backend.main:get_app --factory --port 8000

# 前端(另开一个终端)
cd frontend && npm install && npm run dev

前端在 http://localhost:5173,Vite 的 proxy 会把 /api 转到 8000。

目录结构

codepilot-agent/
├── backend/
│   ├── api/                  FastAPI 路由与请求响应模型
│   ├── graph/                LangGraph 编排:Planner、模块子 Agent、Reviewer 的节点与边
│   ├── static_analysis/      tree-sitter 静态解析:符号表、依赖图、模块聚类、入口点
│   ├── ingest/               仓库 clone、语言识别、切块
│   ├── rag/                  向量索引与语义检索
│   ├── review/               结构 / 错误处理 / 安全三类检查
│   ├── report/               报告汇总与引用校验(路径存在性、行号越界检查在此)
│   ├── mcp_server/           MCP server 对外暴露层
│   ├── providers/            LLM provider 适配
│   ├── tools/                模块子 Agent 可调用的读码工具
│   ├── cache/                分析结果缓存
│   ├── history/              会话与运行记录
│   └── workspace/            运行时工作区(git 忽略)
├── frontend/                 前端界面
├── tests/
├── docs/
├── docker-compose.yml
├── pyproject.toml
└── .env.example              配置模板,至少需填 DEEPSEEK_API_KEY

两个目录承载了本项目的核心主张:static_analysis/ 产出全部确定性事实(文件树、import 依赖图、 模块聚类、入口点、技术栈),不经过 LLM;report/ 里的引用校验决定一条结论能否进入最终报告—— 路径必须真实存在、行号必须在文件实际行数内,过不了校验的结论会被丢弃并计入缺失说明。

配置

复制 .env.example 为 .env。必填只有一项:

变量

说明

DEEPSEEK_API_KEY

LLM 的 API key。变量名沿用 DeepSeek,但只要端点兼容 OpenAI 协议即可

常调的几项:

变量

默认

说明

DEEPSEEK_BASE_URL

https://api.deepseek.com

OpenAI 兼容端点

LLM_MODEL_FLASH

—

扇出档(高频低判断):模块分析

LLM_MODEL_PRO

—

汇总档(低频高判断):Planner、评审判断、报告汇总

EMBEDDING_PROVIDER

local

local 首次运行会下载模型;api 走远端

LLM_MAX_CONCURRENCY

2

同时在途的 LLM 请求数。经中转网关时并发过高会触发上游超时

MAX_PARSEABLE_FILES

8000

准入门限。按 TS 项目那一档定(实测 refine 6810,同规模 Python 项目约其三分之一)

MAX_REPO_SIZE_KB

1500000

准入副门(1.5GB)。报的是全量 git 对象体积,而实际是浅克隆,所以它拦的是「即使浅克隆也过大」的情形

MAX_GRAPH_NODES

8000

依赖图节点上限,与 MAX_PARSEABLE_FILES 同值。超出则降级为目录级并在报告标注

MAX_INDEX_FILES

0(全量)

送进向量索引的文件数上限。唯一能压低单次分析耗时的旋钮,代价是检索漏内容

放宽门限的代价只落在 embedding——它是唯一按量付费且随文件数线性增长的环节。实测 fastapi 的 1138 个可解析文件产出 6529 块、105 批 embedding、30.5 分钟;线性外推 8000 文件约 46000 块、约 3.5 小时。其余环节不随规模涨:模块分析的扇出恒定为 MAX_FANOUT_WIDTH(12 个模块),评审只查挑选出的文件,解析与切块是本地 CPU。

索引与模块扇出、评审并行执行,所以它不阻塞报告链路——提交后约一分钟就能看到模块分析在 推进,整次分析的跨度约等于索引本身的耗时(实测 36.6 → 约 31 分钟)。

索引按 repo + commit + provider + 索引范围 缓存,所以这笔时间每个 commit 只付一次,同一 commit 的二次分析秒级命中。压低单次耗时有两个旋钮,作用面不同:MAX_PARSEABLE_FILES 只 影响准入(超限的仓库直接被拒),MAX_INDEX_FILES 影响索引覆盖范围(按中心度取前 N 个 文件)。后者的代价是未索引的文件在 Code Search 与 AI Chat 里查不到,所以默认全量。

.env 不入版本库,也不入镜像 —— .dockerignore 排除了它,compose 用 env_file 在运行时 注入。

docker compose config 会把密钥打印成明文。 compose 在解析配置时会把 env_file 的值展开,所以那条命令的输出含真实 key —— 排查配置问题时别把它贴进 issue、日志或聊天 记录。要看结构又不想暴露值,过滤掉环境变量段再看。

MCP server

不在容器里(它走 stdio 传输,由客户端自己拉起子进程)。配置方式与工具清单见 backend/mcp_server/README.md。

最小配置(Claude Code 的 .mcp.json):

{
  "mcpServers": {
    "codepilot": {
      "command": "python",
      "args": ["-m", "backend.mcp_server.server"],
      "cwd": "/绝对路径/CodePilot-Agent"
    }
  }
}

工具读取的是已分析仓库的本地数据,所以先通过界面或 API 提交一次分析。

开发

python -m pytest              # 后端测试
python -m mypy backend        # 类型检查
cd frontend && npm test       # 前端测试
cd frontend && npm run build  # 前端构建(含类型检查)

CI

.github/workflows/ci.yml,push 到 main 与所有 PR 触发,四个 job 并行。

job

跑什么

拦的是什么

后端 (ubuntu / windows)

pytest;mypy 只在 ubuntu

逻辑回归。两个平台都跑的理由见下

前端

test / typecheck / build

组件行为、类型、构建

从零装依赖

不用缓存装一遍再跑测试

依赖声明能否解析成一套可用的版本

镜像构建

compose config / build,加两条断言

Dockerfile 与 compose 的回归

后端跑两个平台。 首轮 CI 的实测跳过分布:

平台

结果

跳过的是

ubuntu

957 passed / 13 skipped

junction(Windows 专有机制)

windows

970 passed / 0 skipped

无

Windows runner 零跳过是因为它以管理员权限跑,所以能建符号链接——而本地开发机建不了 (非提权会话下报 WinError 1314,那 2 条符号链接测试在本地跳过)。所以路径逃逸的两类 reparse point 覆盖,本地拿不全而 Windows CI 拿得全。

Linux 那一侧的必要性不在路径逃逸,而在:生产镜像是 python:3.13-slim,它是真正要跑的 平台;POSIX 的路径语义与权限模型与 Windows 不同;mypy 在这一侧跑。不因「Windows CI 恰好 全覆盖」就砍掉它——那是 runner 的环境属性而非本项目能控制的契约,日后收紧权限就会静默 失去符号链接覆盖。

从零装依赖这个 job 刻意不配缓存。 它要验的就是「依赖能否解析」,缓存复用上次的解析 结果正好绕过要验的东西。mcp 的开区间缺陷(mcp>=1.27 解析到 2.1.0,而 2.x 移除了 mcp.server.fastmcp)就是本机装着旧版所以看不出来,只在全新安装时才暴露。

镜像那个 job 的两条断言值得单独说,它们验的是设计而非「能构建」:

  • 默认绑定必须仍是 127.0.0.1。后端没有鉴权层且能克隆任意仓库、消耗 LLM 额度,默认 对外可达的后果比构建失败严重得多。断言读 docker compose config 的 host_ip。

  • 镜像层内不得有 .env 的值。该 job 会在仓库根造一份只含占位 key 的 .env(compose 用 env_file 读它),所以这正是最该查的时机:若日后有人在 Dockerfile 里加 COPY . ., 这条就会红。

两条断言都做过变异验证:BIND_ADDR=0.0.0.0 时第一条转红,而第二条的 grep 能命中镜像层内 的已知文本(确认不是假绿灯)。

独立的检查步骤带 !cancelled(),让一轮 CI 给出完整结论。 GitHub Actions 的默认行为是 「前一步成功才跑下一步」,那会让 pytest 红时 mypy 被跳过、后端红时前端被跳过——而它们是 互不依赖的检查,少一半结论就要再推一次才知道。首次运行踩到过这一点。

例外是前端的构建步骤:npm run build 是 tsc -b && vite build,类型检查红时它必然因同一 原因红,跑它只是把同一条错误报两遍。那里的默认行为恰好是对的。

CI 里不装 pdf extra——weasyprint 需要 libpango 与 libharfbuzz,而未安装时导出端点走 pdf_unavailable 降级路径(既定行为)。PDF 的中文渲染由容器内实跑核对:缺字体时它照样 生成、字形是方块,单元测试断言不了字形。

公网部署

默认形态是本地单用户。对外开放要先理解四件事,它们不是可选步骤。

1. 反代终止 TLS,本项目不签发证书

容器只提供 HTTP。证书签发依赖真实域名,与应用层逻辑无耦合,所以不做在这里 —— 用 Caddy、 nginx 或云厂商的负载均衡器终止 TLS,把明文转到 ${BIND_ADDR}:${WEB_PORT}。

2. 反代是按 IP 限流的前提,不是加固项

限流按客户端 IP 计数,IP 取自 X-Forwarded-For 的最左项,回落到直连地址。

直接把容器端口暴露到公网时,按 IP 限流完全不生效。 这不是理论风险,是实测结论:从局域网 地址(192.168.31.54)访问 BIND_ADDR=0.0.0.0 起的后端,uvicorn 记录的客户端地址是 172.20.0.1 —— Docker 网桥的网关。所有外部客户端都是这一个地址,于是三次/小时的配额被全站 共用,表现是「几个人用完之后所有人都被限流」,而服务看起来完全正常。

所以反代不是「更安全的做法」,而是限流成立的必要条件:只有它覆写 X-Forwarded-For 之后, 不同访客才真正各自计数。

location / {
    proxy_pass http://127.0.0.1:8080;
    # 覆写而非追加:追加会让客户端能自己伪造最左项绕过限流。
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Host $host;
}

链路上有多层代理时才用 $proxy_add_x_forwarded_for(它追加而非覆写),并确保最外层那一跳 是你控制的。

3. 访客自带 LLM 凭证,服务端不存

公网形态下提交分析必须携带访客自己的 base_url 与 API key(在界面的设置页填,存浏览器 localStorage)。服务端不持久化、不记日志、不写入落盘的分析结果。未配置凭证时提交在克隆 与向量化之前就被拒绝 —— 否则索引链路会消耗部署者的 embedding 额度。

向量索引仍由服务端统一构建,不暴露 embedding 配置项:索引缓存键含 provider 身份,让访客 各带一份会让键分裂、缓存全部失效。

4. 三项资源保护

后端没有鉴权层。护住服务器资源的是三道独立的门,各管一种耗尽方式:

保护

变量

默认

超限行为

IP 提交限流

SUBMISSIONS_PER_WINDOW / SUBMISSION_WINDOW_SECONDS

3 次 / 3600 秒

429 + Retry-After

并发闸门

MAX_CONCURRENT_ANALYSES / MAX_QUEUED_ANALYSES

2 / 8

排队并给出位置;队列满返回 503

磁盘配额

REPOS_QUOTA_BYTES / QUOTA_MIN_RECLAIM_RATIO

20GB / 0.10

按最近最少使用清理非在跑副本

配额清理只删仓库工作副本,不删向量索引与落盘的分析历史 —— 被清理的分析仍能从历史列表 载入,只有代码查看器与检索读不到源文件,界面会明说「代码副本已清理」。

QUOTA_MIN_RECLAIM_RATIO 是一道下限:可回收量低于配额的这个比例时不清理,本次分析直接 因磁盘不足失败。它防的是「清理与克隆互相追赶」—— 配额贴顶时每次新分析清掉上一个副本, 下次分析同一仓库又要重新克隆,磁盘始终贴顶而克隆成本被反复付出。宁可让「配额太小」这类 配置错误以明确失败暴露。

开启与回滚

# 开启:绑定地址改为对外,并填实际域名
BIND_ADDR=0.0.0.0 CORS_ORIGINS='["https://your.domain"]' docker compose up -d

# 回滚:删掉 BIND_ADDR 并重启
docker compose up -d

回滚是一步可逆动作,不涉及数据迁移(本期无 schema 变更),落盘的分析历史在回滚后仍可读。

一个容易踩的泄露路径

docker compose config 会把 .env 里的值明文展开到 stdout,包括 DEEPSEEK_API_KEY 与 EMBEDDING_API_KEY。它是排查配置的常用命令,输出也常被贴进 issue 或聊天里 —— 贴之前先过一遍 脱敏:

docker compose config | sed -E 's/((KEY|TOKEN)\s*:\s*).*/\1[REDACTED]/'

出现问题时看哪里

三个失败模式的共同特征是从外部看起来像正常运行:限流把真实访客全拒了、队列积压导致 提交后长期无进展、配额贴顶导致反复克隆 —— 三者都不产生错误页,健康检查也全绿。

  • GET /api/health 给出在途任务数、队列长度、工作副本占用、配额上限与最近一次回收量。

  • 每次拒绝写一条结构化日志,形如 reject reason=rate_limited client=… count=… limit=…。 reason 是稳定字段,可按它计数;取值为 credentials_required、rate_limited、 queue_full、disk_quota 与既有的准入拒绝原因。

出现滥用(单 IP 绕过限流、磁盘被打满、embedding 额度异常消耗)时,执行上面的回滚。

已知边界

这些是有意的取舍,不是待办:

符号级解析只支持 Python 与 TypeScript。 其他语言的文件计入文件树与规模统计,但不进 符号表、不建依赖图、不切块。报告的缺失部分会列出它们。

find_references 是依赖图定界的文本匹配,不是引用解析。 解析层不提取调用点,所以它 先由符号表定位定义文件,再在导入它的文件里做文本检索 —— 比全仓库 grep 精确得多,但同名 局部变量与注释也会命中。工具返回里附了这条说明。

「死代码」未作为发现产出。 文件级判据(零入度且非入口)在基准仓库上误报率接近 100% —— 测试、配置、工具链入口的调用方都在仓库之外。要真正做到需要符号级引用分析。评审的 结构类检查会给出计数,但不逐条列出。

循环依赖只报导入期成立的环。 依赖图区分「导入期依赖」与「调用期依赖」:函数体内的 延迟导入与 if TYPE_CHECKING: 块内的导入照常算依赖边(中心度、聚类、架构结论都算它们), 但不参与环检测 —— 环上有这样一条边,导入期就不构成环,而那往往正是作者用来打破循环的 手段。这类环只在评审的范围说明里计数,不逐条报出。

问答是单轮的,不保留上下文,不做 query 改写。

没有鉴权层。 公网形态下护资源的是限流、并发闸门与磁盘配额三项,加上「访客自带 LLM 凭证」使 LLM 成本归属访客自己。这三者护的是服务器资源,不是访问控制 —— 任何人都能提交 分析、读到任何一次分析的结果。要区分用户就得自己加鉴权。MCP server 仍假设单用户本地运行。

分析任务状态在进程内存里。 服务重启后未完成的任务丢失;已完成的索引缓存仍在,重新 提交同一仓库会秒级命中。这也是后端固定单 worker 的原因。

许可

未指定。

Related MCP Connectors

Related MCP Servers