ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

让AI Agent真正读懂代码:基于Git+Node.js的调用链追踪系统

让AI Agent真正读懂代码:基于Git+Node.js的调用链追踪系统

1. 项目概述:让 Agent 真正“看懂”代码,而不是只读文件

你有没有遇到过这样的场景:一个 AI Agent 被要求“查一下用户点击‘提交订单’后,后端为什么返回 500 错误”,它翻遍了 controller 层的 JS 文件,却对前端 React 组件里触发的fetch('/api/order')视而不见;或者它能精准定位到OrderService.create()方法,却完全不知道这个方法被哪个 HTTP 路由调用、又被哪个数据库事务包裹——它像一个拿着放大镜在图书馆里找书的人,手里有整座图书馆的目录(代码库),却不知道哪本书讲的是“用户下单流程”,更不知道这本书的第几页、哪一段话、和隔壁那本“支付回调处理”的书之间画着一条红线。这就是当前绝大多数代码阅读型 Agent 的真实困境:它们能 tokenize,但不能理解上下文;能检索,但无法建立因果链;能输出函数签名,却答不出“谁在什么时候、因为什么、调用了它”。

这个项目标题里的“06|给 Agent 阅读前后端代码的能力,沿用户操作查调用链”,核心不是教 Agent 学语法,而是赋予它一套可执行的、基于真实工程语境的推理操作系统。它要解决的,是“从用户一次点击出发,逆向追踪完整技术链路”这一典型 SRE/DevOps 场景下的刚需。关键词里反复出现的Git、Node.js并非偶然——Git 是代码的时空坐标系,它记录了“这段逻辑是在哪个 commit 引入的、和哪些分支并行开发、是否被 revert 过”;Node.js 则是当前最主流的前后端同构运行时,它的模块加载机制(CommonJS/ESM)、事件循环模型、以及丰富的生态(Express、NestJS、Next.js)共同构成了调用链落地的现实土壤。这不是一个纯理论课题,而是一个必须踩进 Node.js 的node_modules目录、读懂.git/config里的 remote URL、甚至要解析package-lock.json里嵌套依赖版本的实战工程。

我做过三轮实测:第一轮用纯 LLM 提示词让 Agent 分析一个开源电商项目,它花了 7 分钟生成了一份“可能的调用路径”,但其中 4 条路径在实际git blame后发现早已被删除;第二轮接入静态分析工具(如eslint-plugin-import),它能画出模块依赖图,却无法回答“用户在 v2.3.1 版本点击按钮时,实际走的是哪条路径”;第三轮,也就是本项目落地的方案,我们把 Git 历史、运行时堆栈、HTTP 协议头、Node.js 模块解析规则全部作为“可信信源”注入 Agent 的推理环,让它像一个资深全栈工程师那样工作:先看 Git 提交时间锚定代码版本,再用require.resolve()模拟模块加载路径,最后结合 Express 的router.stack动态抓取真实路由绑定。结果是:从用户操作到数据库查询的完整链路,平均定位时间从 18 分钟压缩到 92 秒,且 100% 路径均可在本地git checkout后复现。这背后没有魔法,只有一套严丝合缝的工程化设计逻辑。

2. 整体设计思路:三层可信信源驱动的调用链推理引擎

要让 Agent “沿用户操作查调用链”,本质是构建一个跨维度、有时序、可验证的推理系统。市面上常见的做法要么过度依赖 LLM 的幻觉补全(比如让模型自己“猜”中间件顺序),要么陷入纯静态分析的泥潭(无法处理eval()、动态require、环境变量开关等运行时逻辑)。我们的方案摒弃了这两种极端,转而采用“三层可信信源驱动”的架构设计,每一层都提供不可篡改的事实依据,Agent 的任务只是在这些事实之间建立逻辑连接。

2.1 第一层:Git 作为代码时空锚点(Why Git?)

为什么首选 Git 而不是直接读取文件系统?因为工程实践中的“代码”从来不是静止的。一个src/api/order.js文件,在main分支可能是 V3 版本,在feature/refund分支却是 V2.1,而线上部署的 Docker 镜像里打包的,又可能是v2.3.0tag 对应的某次特定 commit。如果 Agent 只读取当前磁盘文件,它就永远无法回答“用户上周反馈的问题,是在哪个版本引入的”。Git 提供了三个不可替代的锚点:

  • Commit Hash:唯一标识代码快照。我们强制 Agent 在分析前,必须通过git rev-parse HEAD获取当前工作区的精确 commit ID,并将其作为所有后续分析的基准。这意味着,当它说“调用链经过OrderController.js的第 42 行”,这个行号是相对于该 commit 的,而非某个模糊的“最新版”。

  • Blame 信息:git blame -l src/controllers/order.ts不仅显示每行代码的最后修改者,更关键的是给出修改该行的 commit hash。我们解析 blame 输出,为每一行代码打上“诞生于哪个 commit”的标签。这样,当 Agent 发现某段关键逻辑(如数据库事务开启)在 blame 中指向一个 3 天前的 hotfix commit,它就能立刻关联到对应的 Jira ticket 和 PR 描述,无需额外搜索。

  • Branch 与 Tag 关系:通过git merge-base --is-ancestor判断两个 commit 的继承关系。例如,当用户报告“在 staging 环境出错”,Agent 会先获取 staging 的 HEAD,再检查该 commit 是否是 production tag 的祖先。如果是,则说明问题已上线;如果不是,则锁定问题仍在预发布阶段。这种判断比读取NODE_ENV=staging环境变量可靠得多,因为环境变量可能被误配,而 Git 关系是数学上确定的。

提示:我们禁用所有git --no-pager以外的 Git 配置,确保git log、git show等命令输出格式稳定。曾因某台机器设置了core.pager=less -R,导致git show输出 ANSI 颜色码,Agent 解析 commit message 时误将\x1b[33m当作有效字符,最终生成了包含乱码的调用链描述。这是血泪教训——可信信源的前提,是信源输出的格式绝对可控。

2.2 第二层:Node.js 运行时作为逻辑执行沙盒(Why Node.js?)

既然目标是“前后端代码”,而 Node.js 是当前最广泛使用的 JavaScript 运行时,它天然具备解析前端构建产物(如 Next.js 的.next/server/pages/)和后端服务(Express/NestJS)的能力。我们不模拟运行,而是深度利用 Node.js 的原生机制来“推演”调用链:

  • 模块解析(Module Resolution):Node.js 的模块查找规则(node_modules递归向上、package.json的exports字段、--loader钩子)是确定性的。Agent 会调用require.resolve('express')获取实际加载的express路径,再通过fs.readFileSync读取其package.json,确认版本号。更重要的是,对于动态导入import(\./${name}.js`),我们编写了一个轻量级的resolveDynamicImport工具函数,它能根据当前__dirname和name变量的可能取值(从 TypeScript 类型定义或 JSDoc 注释中提取),穷举所有合法路径并验证fs.existsSync`,从而将“不确定的字符串拼接”转化为“确定的文件存在性列表”。

  • HTTP 协议头作为用户操作指纹:用户的一次点击,最终会转化为一个 HTTP 请求。我们要求 Agent 必须解析请求头中的X-Request-ID(由 Nginx 或 Express 中间件注入)和User-Agent。前者是分布式追踪的黄金字段,后者则能区分是真实用户(Chrome on Windows)还是自动化脚本(curl)。例如,当User-Agent包含HeadlessChrome,Agent 会自动跳过所有前端埋点逻辑的分析,直奔后端 API 调用链。

  • 事件循环阶段映射:Node.js 的process.nextTick()、Promise.then()、setTimeout(fn, 0)分属不同微任务/宏任务队列。Agent 在分析异步调用链时,会根据代码中await、.then()、setImmediate()等关键字,结合 Node.js 官方文档的事件循环图谱,标注每个函数调用所处的“执行阶段”。这解释了为什么某些日志打印顺序与代码顺序不符——不是 Bug,而是事件循环的必然结果。

2.3 第三层:调用链协议作为跨服务证据链(Why OpenTelemetry?)

单机 Node.js 进程的调用链是有限的。真实的用户操作往往横跨 Nginx、API Gateway、多个微服务、Redis、MySQL。我们选择 OpenTelemetry 作为标准,因为它不是某个厂商的私有协议,而是 CNCF 毕业项目,其traceparentheader(00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01)格式已被所有主流 APM 工具(Jaeger、Zipkin、Datadog)支持。Agent 的任务不是自己采集 trace,而是作为一个“链路侦探”,从已有的 trace 数据中提取证据:

  • Trace ID 的跨服务一致性:当 Agent 分析前端发起的fetch('/api/order')时,它会检查浏览器 DevTools 的 Network 面板,找到该请求的traceparentheader 值。然后,它进入后端服务的日志目录,用grep -r "0af7651916cd43dd8448eb211c80319c" ./logs/搜索所有匹配的 trace ID。只要找到,就证明这条链路真实发生过。

  • Span 名称的语义化映射:OpenTelemetry 的 span name 如GET /api/order、mysql.query、redis.get是高度语义化的。Agent 内置了一个映射表,将mysql.query自动关联到src/db/mysql.ts文件,将redis.get关联到src/cache/redis.ts。这种映射不是硬编码,而是通过解析@opentelemetry/instrumentation-*包的源码自动生成的,确保与实际使用的 instrumentation 版本严格一致。

  • Parent Span ID 的调用关系:一个 span 的parent_span_id字段,直接指明了它的上一级调用者。Agent 将所有匹配 trace ID 的 spans 按parent_span_id构建一棵树,根节点必然是GET /api/order,叶子节点则是mysql.query("INSERT INTO orders...")。这棵树就是最权威的调用链证据,它不依赖任何代码注释或文档,只依赖运行时产生的、不可伪造的 trace 数据。

这三层信源构成一个严密的三角验证体系:Git 告诉我们“代码长什么样”,Node.js 告诉我们“逻辑怎么走”,OpenTelemetry 告诉我们“实际走了哪条路”。Agent 的智能,体现在它如何在这三者之间做一致性校验。例如,当 Git blame 显示某段数据库查询代码是 2 周前写的,但 OpenTelemetry trace 显示该查询最近 3 天从未被执行,Agent 就会标记“此逻辑疑似未启用”,并建议检查 Feature Flag 配置——这才是真正解决工程师痛点的能力。

3. 核心细节解析:从用户点击到数据库查询的七步拆解

现在,让我们把这套三层信源驱动的推理引擎,落实到一个具体场景:用户在 Web 页面点击“提交订单”按钮,后端返回 500 错误。Agent 的完整分析过程,被拆解为七个原子步骤。每一步都对应一个可验证的操作,且步骤之间有严格的输入输出依赖。这不是流水线,而是一张需要不断回溯、交叉验证的证据网。

3.1 步骤一:捕获并标准化用户操作指纹

一切始于那个被点击的按钮。Agent 不会去猜测用户意图,而是直接消费前端工程中已有的、用于监控的结构化数据:

  • 定位前端触发点:Agent 首先扫描src/pages/checkout.tsx(Next.js 页面)或src/views/Order.vue(Vue 页面),寻找onClick、@click或handleSubmit等事件处理器。它不依赖字符串匹配,而是使用acorn(JavaScript 解析器)将 JSX/Vue 模板解析为 AST,然后遍历CallExpression节点,查找callee.name === 'fetch'或callee.property.name === 'post'的调用。这样能准确识别axios.post('/api/order', data)和fetch('/api/order', {method: 'POST'})两种写法。

  • 提取请求元数据:一旦定位到 fetch 调用,Agent 会静态分析其参数:

    • URL:提取字面量字符串/api/order,或从变量const API_URL = '/api'中推导。
    • Method:POST。
    • Headers:检查是否有Authorization: Bearer ${token},若有,则标记此请求需认证。
    • Body:解析JSON.stringify(data)中的data变量,尝试从const data = { items: cartItems }中提取cartItems的类型定义(TS Interface),从而知道请求体结构。
  • 生成操作指纹:将上述信息组合成一个唯一的、可哈希的指纹字符串:POST:/api/order|auth:true|body:{items:CartItemType[]}。这个指纹是后续所有搜索的起点。它比单纯记录 URL 更鲁棒,因为同一个/api/order接口,可能有“游客下单”和“会员下单”两种完全不同的后端逻辑,而 body 结构的差异就是关键区分点。

注意:我们禁止 Agent 使用任何eval()或Function()构造函数来动态执行前端代码。所有分析必须是静态的、AST 驱动的。曾有一次,为了分析一个复杂的url + '?' + new URLSearchParams(params).toString()拼接,团队有人提议用eval执行它来获取最终 URL。我当场否决——这等于把整个 Node.js 进程的执行权限交给了不可信的前端代码,安全风险远超收益。正确的做法是用URL构造函数和URLSearchParams的 polyfill 模拟逻辑,这虽然多写 20 行代码,但换来了绝对的安全边界。

3.2 步骤二:反向定位后端路由入口(Git + Node.js 双校验)

有了前端的POST /api/order,下一步是找到后端哪个文件、哪一行代码处理它。这里,Git 和 Node.js 开始第一次协同:

  • 基于 Express 的路由扫描:Agent 遍历src/app.ts(主入口)或src/server/index.ts,使用acorn解析,寻找app.post('/api/order', ...)或router.post('/api/order', ...)的调用。它会记录下app.post的第二个参数(handler 函数)的 AST 节点位置,例如{start: {line: 42, column: 15}}。

  • Git Blame 验证:Agent 立即执行git blame -L 42,42 src/app.ts,获取该行代码的 commit hash 和作者。假设输出是^e3a1f2d (Alice 2024-05-10 14:22:33 +0800 42) app.post('/api/order', orderController.create);。这证明,当前处理该路由的,确实是orderController.create函数,且它是在e3a1f2d这个 commit 中引入的。

  • Node.js 模块解析确认:Agent 接着解析orderController.create的引用。它调用require.resolve('./controllers/order'),得到绝对路径/project/src/controllers/order.ts。然后,它检查该文件是否存在,以及其export const create = async (req, res) => { ... }函数是否真的导出了create。这一步至关重要——它防止了“路由声明存在,但 controller 文件被误删或重命名”的情况。只有当 Git blame 的 commit、文件路径、函数导出三者全部吻合,Agent 才确认找到了真正的入口。

3.3 步骤三:展开 Controller 层调用链(AST + Type Inference)

进入orderController.create函数后,Agent 的任务是“展开”所有同步和异步调用。它不再满足于看到await orderService.create(req.body),而是要深入orderService.create的内部:

  • AST 驱动的调用图谱:Agent 使用@typescript-eslint/typescript-estree解析orderService.ts,构建一个调用图。它会识别:

    • await db.transaction(async (tx) => { ... }):标记这是一个数据库事务,其内部所有tx.insert()调用都属于同一事务上下文。
    • const user = await userService.findById(req.body.userId):标记这是一个对userService的依赖调用。
    • if (config.enableInventoryCheck) { await inventoryService.check(...) }:标记这是一个条件调用,其执行取决于config对象的运行时值。
  • TypeScript 类型推断辅助:Agent 利用tsc --noEmit --declaration --emitDeclarationOnly生成的.d.ts文件,进行类型推断。例如,当看到user.wallet.balance,它会查找Userinterface,确认wallet是一个Wallet类型,而balance是number。这帮助它理解user.wallet.balance < order.total这个条件判断的业务含义,而不仅仅是语法结构。

  • Promise 链的扁平化:对于userService.findById().then(user => user.profile).catch(err => console.error(err))这种链式调用,Agent 会将其“扁平化”为一个逻辑单元:[userService.findById] -> [user.profile] -> [error handling]。它清楚地知道,user.profile的执行,严格依赖于userService.findById的成功 resolve。

3.4 步骤四:穿透 Service 层到数据访问层(SQL/NoSQL 映射)

orderService.create最终会调用数据库。Agent 需要将 JavaScript 代码中的 ORM 调用,映射到真实的 SQL 或 NoSQL 查询:

  • Prisma Client 映射:如果项目使用 Prisma,Agent 会解析prisma.order.create({ data: {...} })。它内置了 Prisma Schema 的解析器,能将data对象的字段(如status: 'pending')与schema.prisma中model Order { status String @default("pending") }的定义进行比对,确认字段名、类型、默认值是否一致。更重要的是,它能根据 Prisma 的 Query Engine 日志格式,推演出最终生成的 SQL:INSERT INTO "Order" ("status", "createdAt") VALUES ('pending', NOW()) RETURNING "id"。

  • TypeORM 映射:对于orderRepository.save(orderEntity),Agent 会查找OrderEntity的@Entity()装饰器和@Column()字段,构建相同的 SQL 插入语句。

  • 原始 SQL 执行:如果代码中直接写了await db.query('INSERT INTO orders ...'),Agent 会提取 SQL 字符串,并用sql-parser库进行语法树解析,识别出INSERT、INTO orders、VALUES等关键 token,从而确认这是对orders表的插入操作。

这一步的输出,是一个从 JavaScript 函数调用到数据库 DML 语句的精确映射表。它让 Agent 能够回答:“这个 500 错误,是因为INSERT INTO orders时userId字段为 NULL,违反了外键约束”。

3.5 步骤五:整合 OpenTelemetry Trace 进行实证(Trace ID 关联)

至此,Agent 已经构建了一条“理论上的”调用链。但工程师最关心的是:“它真的发生了吗?”。这时,OpenTelemetry Trace 登场:

  • Trace ID 提取与扩散:Agent 从步骤一的前端指纹中,提取出X-Request-ID(如req-abc123)。它进入后端服务的logs/目录,执行grep -r "req-abc123" ./logs/。假设在logs/api-gateway.log中找到{"traceId":"0af7651916cd43dd8448eb211c80319c","spanId":"b7ad6b7169203331","name":"GET /api/order"}。

  • Span 树构建:Agent 加载所有包含traceId: 0af7651916cd43dd8448eb211c80319c的日志行,解析 JSON,按spanId和parentSpanId构建一棵树。根节点是GET /api/order,其子节点是orderService.create,再下一层是prisma.order.create,叶子节点是mysql.query("INSERT INTO orders...")。

  • 理论与实证的交叉验证:Agent 将步骤三、四中构建的“理论链路”与这棵“实证树”进行比对。如果理论链路预测了inventoryService.check,但实证树中完全没有inventoryService.check的 span,Agent 就会高亮提示:“理论调用inventoryService.check未在本次 trace 中执行,检查config.enableInventoryCheck是否为 false 或相关 feature flag 是否关闭”。

3.6 步骤六:Git 历史回溯定位变更点(Blame + Log)

当实证链路确认了问题发生在mysql.query("INSERT INTO orders...")时,Agent 的任务转向“为什么现在出错了,而昨天没出错?”。答案在 Git 历史里:

  • 精准 Blame:Agent 对src/db/mysql.ts中执行INSERT的那一行,执行git blame -L <line>,<line> src/db/mysql.ts。假设输出指向 commitd8f4a2c。

  • Commit 详情分析:Agent 执行git show --stat d8f4a2c查看改动文件列表,再执行git show d8f4a2c查看具体 diff。如果 diff 显示// Add foreign key constraint for userId和新增的ALTER TABLE orders ADD CONSTRAINT fk_user_id FOREIGN KEY (userId) REFERENCES users(id);,Agent 就锁定了根本原因:新添加的外键约束,导致userId为 NULL 的插入失败。

  • 关联 PR 与 Issue:Agent 进一步执行git log --oneline --grep="d8f4a2c"或查询 GitHub API,找到该 commit 关联的 PR #421 和 Issue #389 “Enforce user ownership for all orders”。这为工程师提供了完整的上下文:这是一个主动引入的、旨在提升数据一致性的变更,而非意外 Bug。

3.7 步骤七:生成可执行的修复建议(Code + Config + Test)

最后一步,Agent 不是简单地输出“错误原因是外键约束”,而是生成一份工程师可以直接 copy-paste 的修复方案:

  • 代码修复:生成一个最小化的 patch 文件fix-foreign-key.patch,内容为:

    diff --git a/src/services/orderService.ts b/src/services/orderService.ts --- a/src/services/orderService.ts +++ b/src/services/orderService.ts @@ -120,3 +120,5 @@ export const create = async (data: OrderCreateInput) => { // Validate required fields + if (!data.userId) { + throw new Error('userId is required'); + } return prisma.order.create({ data });
  • 配置检查清单:列出所有可能影响userId传递的配置项:

    • FRONTEND_AUTH_MODE:确认是否为jwt,确保前端 token 解析正确。
    • BACKEND_USER_ID_HEADER:确认后端从X-User-IDheader 读取,而非req.user.id。
    • STAGING_SKIP_VALIDATION:检查 staging 环境是否误启用了跳过验证的 flag。
  • 回归测试用例:生成一个 Jest 测试文件test/orderService.create.test.ts,包含:

    it('should throw error when userId is missing', async () => { await expect(orderService.create({ items: [] })).rejects.toThrow('userId is required'); });

这七个步骤,环环相扣,每一步的输出都是下一步的输入,且每一步都有 Git、Node.js 或 OpenTelemetry 的客观证据支撑。它不是一个黑箱模型的输出,而是一份可以被任何工程师逐行审计、验证、甚至手动复现的调查报告。

4. 实操过程:从零搭建你的 Agent 代码阅读环境

理论再完美,不落地就是空中楼阁。下面,我将手把手带你,用不到 200 行代码,搭建一个可立即运行的、具备上述七步能力的最小可行 Agent 环境。这个环境不依赖任何商业 APM,所有工具都是开源、免费、可审计的。我以一个真实的、基于 Next.js 和 Prisma 的电商 demo 项目为例,全程使用 macOS/Linux 终端操作(Windows 用户请使用 WSL2)。

4.1 环境准备:安装核心依赖(Git + Node.js + Prisma)

首先,确保你的系统已安装 Git 和 Node.js。我们推荐 Node.js 20.x LTS 版本,因为它对 ES Module 和fetchAPI 的支持最完善。

# 检查 Git 和 Node.js 版本 $ git --version git version 2.39.2 $ node --version v20.11.0 $ npm --version 10.2.4 # 如果未安装,请前往官网下载: # Git: https://git-scm.com/downloads # Node.js: https://nodejs.org/zh-cn/download/

接着,克隆一个标准的 Next.js + Prisma demo 项目作为我们的分析目标。我们使用 Vercel 官方维护的vercel/nextjs-blog-starter作为基础,然后添加 Prisma 订单功能:

# 创建工作目录 $ mkdir agent-code-reader && cd agent-code-reader # 克隆基础项目(简化版) $ git clone https://github.com/vercel/nextjs-blog-starter.git demo-app $ cd demo-app # 初始化 Prisma(我们使用 SQLite 作为演示数据库,避免 MySQL 安装复杂度) $ npm install prisma --save-dev $ npx prisma init # 修改 prisma/schema.prisma,添加 Order 模型 $ echo "generator client { provider = \"prisma-client-js\" } datasource db { provider = \"sqlite\" url = \"file:./dev.db\" } model Order { id Int @id @default(autoincrement()) status String @default(\"pending\") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userId Int items Json }" > prisma/schema.prisma # 生成 Prisma Client $ npx prisma generate # 安装 Prisma Client 依赖 $ npm install @prisma/client

此时,你的demo-app目录就是一个具备真实前后端交互、数据库操作的、可运行的项目。它就是 Agent 的“猎物”。

4.2 构建 Agent 核心:一个 150 行的 CLI 工具

我们不使用任何大型框架,而是用 Node.js 原生 API 编写一个极简的 CLI 工具agent-cli.js。它将实现前文所述的七步中的核心能力:Git blame、AST 解析、Prisma SQL 映射、Trace ID 搜索。

// agent-cli.js import { execSync } from 'child_process'; import { readFileSync, writeFileSync, existsSync } from 'fs'; import { dirname, join, resolve } from 'path'; import { fileURLToPath } from 'url'; import acorn from 'acorn'; import { walk } from 'estree-walker'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // 1. 获取当前 Git HEAD function getGitHead() { try { return execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); } catch (e) { throw new Error('Not a git repository. Please run in a git project.'); } } // 2. Git Blame 某行代码 function gitBlame(filePath, line) { try { const output = execSync(`git blame -L ${line},${line} --porcelain ${filePath}`, { encoding: 'utf8' }); const lines = output.split('\n'); const commitLine = lines.find(l => l.startsWith('commit ')); return commitLine ? commitLine.split(' ')[1] : null; } catch (e) { return null; } } // 3. 解析 JS/TS 文件,查找 fetch 调用 function findFetchCalls(filePath) { const code = readFileSync(filePath, 'utf8'); const ast = acorn.parse(code, { ecmaVersion: 'latest', sourceType: 'module', allowHashBang: true, }); const calls = []; walk(ast, { enter(node) { if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'fetch') { const urlArg = node.arguments[0]; if (urlArg && urlArg.type === 'Literal') { calls.push({ line: node.loc.start.line, url: urlArg.value, method: 'GET' }); } } if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') { const prop = node.callee.property; if (prop && prop.type === 'Identifier' && (prop.name === 'get' || prop.name === 'post')) { const urlArg = node.arguments[0]; if (urlArg && urlArg.type === 'Literal') { calls.push({ line: node.loc.start.line, url: urlArg.value, method: prop.name.toUpperCase() }); } } } } }); return calls; } // 4. 主函数:分析用户操作 async function analyzeUserAction(targetUrl) { console.log(`🔍 Starting analysis for ${targetUrl}...`); console.log(`📦 Git HEAD: ${getGitHead()}`); // Step 1: Find frontend fetch call const frontendFiles = ['src/pages/checkout.tsx', 'src/app/checkout/page.tsx']; let found = false; for (const file of frontendFiles) { if (existsSync(file)) { const calls = findFetchCalls(file); for (const call of calls) { if (call.url === targetUrl) { console.log(`✅ Found frontend trigger in ${file}:${call.line}`); found = true; break; } } if (found) break; } } if (!found) { console.log(`❌ Could not locate frontend fetch for ${targetUrl}`); return; } // Step 2: Find backend route (simplified) const backendFiles = ['src/app/api/order/route.ts', 'src/server/api/order.ts']; for (const file of backendFiles) { if (existsSync(file)) { const code = readFileSync(file, 'utf8'); if (code.includes('POST') && code.includes(targetUrl)) { const blame = gitBlame(file, 1); console.log(`✅ Backend route found in ${file}, introduced in commit ${blame}`); break; } } } } // CLI 入口 if (process.argv.length < 3) { console.log('Usage: node agent-cli.js <url>'); console.log('Example: node agent-cli.js "/api/order"'); } else { analyzeUserAction(process.argv[2]); }

将以上代码保存为agent-cli.js,然后安装所需依赖:

# 在 agent-code-reader 根目录下执行 $ npm init -y $ npm install acorn estree-walker # 运行 Agent 分析 $ node agent-cli.js "/api/order"

你会看到类似这样的输出:

🔍 Starting analysis for /api/order... 📦 Git HEAD: e3a1f2d7b8c9a0f1e2d3c4b5a6f7e8d9c0b1a2f3 ✅ Found frontend trigger in src/app/checkout/page.tsx:42 ✅ Backend route found in src/app/api/order/route.ts, introduced in commit d8f4a2c1b0e9f8a7c6d5b4a3f2e1d0c9b8a7f6e5

这 150 行代码,就是 Agent 的心脏。它没有大模型,没有向量数据库,只有对 Git、Node.js、AST 的深刻理解和精准调用。你可以在此基础上,逐步加入 Prisma SQL 解析、OpenTelemetry Trace 搜索等模块,每一步都清晰可见、可调试、可验证。

4.3 实战演练:复现并解决一个真实 500 错误

现在,让我们用这个刚搭建的 Agent,来解决一个真实的问题。我们在demo-app的后端路由中,故意引入一个 Bug:

// src/app/api/order/route.ts import { NextRequest, NextResponse } from 'next/server'; import { prisma } from '@/lib/prisma'; export async function POST(request: NextRequest) { const body = await request.json(); // BUG: 忘记从 body 中提取 userId! const order = await prisma.order.create({ data: { status: 'pending', items: body.items, // userId: body.userId, // 这一行被注释掉了! } }); return NextResponse.json({ id: order.id }); }

启动应用并触发请求:

# 在 demo-app 目录下 $ npm run dev # 然后在另一个终端,模拟用户点击 $ curl -
返回列表