
1. 为什么我会在仓颉上写一个Coding Agent先说一下背景。接触仓颉语言有一段时间了整体感觉是它既有现代语言的表达力又在底层互操作和编译优化上做了很扎实的设计。但真正让我产生要用仓颉做点正经东西这个念头的是一次很别扭的体验当时手上同时维护好几个微服务仓库每个仓库都有不同的构建命令、测试入口和部署脚本我试图用通用AI编程助手统一指挥结果它经常把A仓库的规则套到B仓库上Context一长还会把边界条件忘掉。这种看起来很强但处处需要盯着改的状态反而让效率变低了。我的真实需求其实朴素得多能不能有一个工具它只围绕我这一个项目工作知道这个项目的目录结构、依赖关系、构建方式、测试逻辑能在我描述一个问题之后自己去看代码、自己改代码、自己跑验证最后把改动汇总给我审。而不是一个什么都会一点、但对我这个项目一无所知的通用问答机器人。于是就有了cjh。它不是一个框架不是一个库它就是一个单独的仓颉源文件。我把它设计成一个文件就是整个开发团队的形态所有核心逻辑都收在一个文件里不依赖外部服务不做重量级抽象。你把它放到仓库根目录告诉它帮我看一下登录模块的重复代码它会自己去遍历文件树、理解模块依赖、定位重复逻辑、生成重构补丁然后执行测试验证。这不是一个概念Demo。我在本地几个真实项目上跑了实际任务包括接口字段提取、死代码清理、跨模块改动前的影响面评估。它都能给出可用的结果。这篇文章会把它的设计思路、关键实现、以及我踩过的坑完整讲一遍重点说清楚单文件这个约束为什么反而成了它最大的优势以及仓颉语言在这类工具上体现出来的原生能力边界在哪里。适合谁来读两类人。一类是正在做或想自己做编码智能工具、但对如何控制Agent在项目内的行为边界有困惑的开发者另一类是关注仓颉语言生态、想知道这门语言除了应用开发之外还能承担什么系统级工具的开发者。前者能从这里拿到一套小体积、高内聚的Agent设计参考后者能看到仓颉在字符串处理、进程管理、文件系统操作上的实际表现。2. 单文件架构的取舍为什么不是插件、不是服务、不是多模块工程先回答一个必然会有人问的问题Coding Agent这种听起来就很系统工程的东西为什么要做成单文件我的出发点不是炫技而是三个非常实际的约束。第一部署成本必须为零。市面上大部分AI编码工具都是插件形态要装运行时、要配IDE路径、要同步配置中心、要处理版本升级。但在真实团队里每个人用的IDE版本、Shell环境、甚至网络策略都不一样任何一个环节不匹配工具就用不起来。单文件意味着只需要把cjh这个文件拷贝到仓库根目录然后执行一行命令完事。这天然绕开了环境适配这个最大的交付障碍。第二项目边界必须可控。Agent最危险的地方不是能力不够而是能力溢出。一个Agent如果绑定在一个大而全的框架上天然倾向于处理任意问题于是经常改错文件。cjh只读取当前工作目录下的文件树只允许在仓库根目录范围内做变更所有决策都基于当前项目的上下文不允许引入任何全局状态。单文件的物理形态恰恰是项目专属工具这个心智模型的最佳载体。第三审计链路必须透明。多文件工程项目最大的问题是调用链被分散到不同模块里出了问题很难追踪。单文件把所有逻辑放在同一个作用域体系下每一个动作都有明确的入口和出口。别人Review的时候打开这一个文件就能看完整条执行路径。很多人会担心单文件是不是意味着代码必然臃肿、难以维护这取决于设计方式。我在cjh里没有用传统的功能分目录思路而是把整个文件组织成一条任务流水线main入口只做参数解析和上下文初始化ProjectContext负责构建项目画像TaskPlanner负责把自然语言意图转化成步骤序列CodeModifier负责安全地改写文件内容Verifier负责执行构建和测试反馈校验每一段逻辑在文件里都有清晰的位置注释边界像极了一条产线上的工位。而且仓颉的顶层函数和结构体定义允许我把这些段落组织得足够模块化不用class嵌套地狱也能保持高内聚。实际开发中这个文件当前约3000行我可以在不借助IDE跳转的情况下靠位置约定快速定位任意一段逻辑。仓颉在这里体现的一个原生优势是它对文件系统、进程、字符串处理这类基础设施操作没有额外依赖。如果你用Python写这种工具第一反应是找os、subprocess、re、pathlib这些标准库用仓颉写这些能力都在标准库里直接可用且类型系统能在编译期帮我挡掉大量低级错误。这对单文件形态尤其重要——没有类型检查的辅助3000行代码靠人肉眼查会是什么体验不敢想。3. 核心能力模块拆解cjh是如何看懂一个项目的3.1 项目画像构建不是扫一遍文件名那么简单任何Coding Agent要做出靠谱的修改决策前提是它得知道这个项目长什么样。cjh的第一步不是直接响应你的指令而是先在后台构建一份ProjectContext。这份Context包含的信息维度比大多数人预想的多文件树不止是列出所有文件名而是按目录层级组织并标注每个文件的类型源码、测试、配置、文档、构建脚本、资源文件。语言指纹根据文件后缀和文件头注释推断每个文件使用的语言或技术栈。依赖关系在当前目录范围内通过解析import语句、use声明、相对路径引用等建立起一个粗略的文件间依赖图。构建入口识别寻找常见的构建标志文件比如package.json、Cargo.toml、build.gradle、cangjie.toml、Makefile等并提取对应的构建命令模板。变更足迹读取git状态如果有的话标出哪些文件处于已修改状态哪些是新增的。这个构建过程完全在本地完成不调用任何远端服务。仓颉的File系统库和Directory遍历接口在这里表现很扎实针对大目录几千个文件做了递归遍历也不会卡顿。我特意测过一个包含8000文件的monorepo仓库完整构建Context耗时在2秒以内完全可接受。在构建文件树时有一个细节值得说必须排除噪音目录。.git、node_modules、dist、build、.venv这些目录如果不排除不但拖慢构建速度更严重的是会让Agent的注意力被无关文件干扰从而做出错误判断。cjh内置了一份默认忽略清单同时支持从项目根目录读取自定义忽略规则文件。这个优先级的设定是自定义规则 内置规则 全量扫描。3.2 意图理解与任务规划把一句话拆成可执行的步骤链当用户输入一个自然语言指令之后cjh不会直接拿整段话去匹配代码操作。它先做意图解析把输入归入几个预定义的任务类型然后针对每种类型调用不同的规划策略。我最初只预置了四类能力查询解释只读分析、缺陷修复定位修改、重构结构变换、测试执行/编写。后来在实际使用中又加了第五类影响面评估即如果我要改A模块会波及哪些文件。意图解析本身不涉及大模型我用的是基于关键词和句式模板的规则引擎。比如包含为什么什么原因在哪这类疑问词归到查询解释包含修复报错不工作归到缺陷修复包含重构简化提取归到重构。这个做法的好处是零延迟、完全可控、不会因为模型幻觉把查询指令执行成修改指令。坏处是句式复杂的时候召回率不够但这个缺陷在窄域Agent里是能接受的因为它服务的用户通常已经具备一定描述能力能把自己的需求拆成几个明确的短句。规划阶段是规则引擎加轻量启发式判断的组合。以缺陷修复为例在Context里定位报错信息中提到的文件名提取正则中形如src/xxx.ts的片段若没有出现具体文件则在整个Context里搜索与错误关键字相关的符号定义锁定候选文件后读取该文件的具体行号区间抽取符号级别的代码块将代码块、相关依赖引用、构建设置打包成一个修改工作包交给修改执行器这里我特意做了一次只处理一个工作包的限制而不是让Agent同时并行处理多个文件。原因是并行改动多个文件时很难判断编译错误到底是哪个改动引入的。串行工作包让每步变更都对应一个可验证的中间态出错时定位成本最低。3.3 代码检索与定位不使用IDE也能准确找到目标检索能力决定了Agent的眼神好不好。cjh的代码检索体系分三层从快到慢第一层是符号索引。在构建Context时我会用轻量正则扫一遍所有源码文件提取函数定义、结构体/类声明、顶层变量、接口定义等关键符号建立一份符号名 → 文件行号的映射表。查某个函数在哪定义这种问题直接查这张表毫秒级返回。第二层是内容搜索。基于包含关键字的行级匹配支持大小写敏感/不敏感切换支持限定文件类型。这一层处理哪里用到了这个变量这类问题。第三层是依赖图回溯。当第一层和第二层都找不到直接结果时会沿着依赖图做广度优先搜索比如谁依赖了utils模块里的函数就是一条反向引用追溯路径。实际测试中检索一个200文件规模的项目里某个符号的引用位置平均耗时在50ms以内。这个速度已经不影响Agent的交互体感更重要的是它的检索结果完全基于当前磁盘内容不会出现IDE缓存过期导致结果偏差的问题。这里有一个我反复调优过的性能细节不要一次性把所有文件内容读进内存。大文件比如生成的protocol buffer文件、打包后的bundle文件读进来既浪费内存又拖慢索引。cjh对文件大小做了上限阈值超过阈值只做文件名和头部注释的索引不做全文内容索引。实践证明这类大文件在绝大多数修改任务中都不是真正的目标。4. 代码生成与修改的安全护栏让Agent改代码但不出乱子允许Agent改动代码是这个项目里最需要谨慎设计的地方。我的原则是宁可给它更强的约束也不能让它完全自由发挥。cjh的修改执行器里内置了四层保护。4.1 第一层范围锁定在规划阶段生成的工作包里明确记录了允许修改的文件路径列表。执行器在执行任何写操作之前都会校验目标文件是否在这个列表里以及目标文件是否处于仓库根目录下。任何不在列表中的文件修改请求直接拒绝并生成一条告警日志。对于符号引用分析不完全准确的情况范围锁定能起到最后一道闸门的作用。即使前面的定位逻辑跑偏了实际写入动作也会被拦住最多只是报一个未找到允许修改的目标而不会真的动到错误文件。4.2 第二层基于作用域的代码变换cjh的代码修改不是粗暴的字符串替换。在执行修改时它会先用语言无关的括号配对算法把目标符号所处的最近作用域圈定出来然后在这个作用域内做替换。举个例子假设要把函数foo里的oldCall()替换成newCall()。不能简单搜索所有oldCall出现的位置因为可能有另一个文件、另一个作用域里也有同名的符号。cjh的执行逻辑是先定位foo的完整函数体起止行在这个范围内搜索oldCall的调用点校验每一个调用点是否确实属于这个函数作用域检查括号深度层级执行替换这个策略能挡住同名符号误替换这类Agent最常犯的错误。实际上我在前几版就踩过这个坑——当时没做作用域圈定一条把A组件里的方法名从handleClick改成onSubmit的指令把整个项目里所有组件里的handleClick全给改了最终只能靠git恢复。加了作用域约束之后再也没出过这种事故。4.3 第三层语义依赖审查改完一处代码往往会影响它的调用方。cjh在产生修改补丁之后、写盘之前会做一道语义审查把这个文件里被修改的符号拿到依赖图里去查一下找出所有引用了这个符号的上游文件逐个检查这些引用处的调用签名判断是否仍有意义。如果引用的签名完全匹配则标记为安全如果不匹配比如删除了一个参数但上游还在传旧参数则生成一个连带修改建议。我把这个设计叫做牵一发动全身检查。它并不能做到100%精确——毕竟没有运行时的类型推导——但能提前暴露大多数不一致问题避免Agent在错误的代码基础上继续堆错误。4.4 第四层验证驱动收尾所有修改执行完毕cjh会按照构建入口识别阶段提取到的命令自动执行一次构建和如果存在测试。这一步通常是最耗时的也是最有价值的。我设置了两个验证档位quick和full。quick只跑构建不做测试适合快速反馈循环full跑构建加测试适合最终确认。默认走quick。验证失败时cjh会读取构建输出中的错误信息尝试定位失败原因如果失败位置和本次修改的文件相关自动生成一份回滚或调整的建议。如果要让Agent完全自动修复验证失败还有单独的--self-heal开关默认关闭。这一层保护原本是为了效率但实际用下来它对信任感的贡献远大于效率。当我能看到每次修改都经过真实构建验证时才敢放心地把代码改动交给它执行而不是只让它输出diff让我手动应用。5. 实际使用场景与边界限制哪些任务适合它哪些暂时不行5.1 我用cjh解决过的真实问题场景一抽取重复逻辑。有个服务里三个文件各自实现了一套类似的时间戳格式化逻辑肉眼看着像但细节有差异。我让cjh扫描这三个文件提取出公共格式函数并统一调用点。它先通过了依赖图找到三个函数的引用面生成修改补丁后跑通了构建最终改动涉及4个文件、17处调用点无一遗漏。场景二定位报错根因。线上日志报了一个状态机非法转换的错误光看堆栈完全不知道是哪个入口触发的。我让cjh找一下State从PENDING跳到FINISHED的所有路径它沿着依赖图回溯列出了3条可能路径最终定位到一条我完全没注意到的回调链路。这种跨文件追踪的能力说实话比我自己在IDE里跳转快得多。场景三改动影响面评估。在重构一个核心数据结构之前我需要知道有多少文件用到了它的字段。cjh生成了一份变更影响面清单按直接引用间接引用仅类型引用分了三档。这让重构的风险从玄学变成了清单。5.2 现有边界与已知短板诚实地讲cjh目前的能力是有清晰天花板的。不支持跨多语言混合项目的精确修改。依赖图分析对不同语言的识别精度不一致比如对TypeScript和Go的识别不错但对C宏展开和模板推导无能为力。因此它更适合定位在单语言纯度较高的仓库中工作。没有真正的语义理解。所有分析都是基于语法层面和模式匹配的不理解这段代码的业务含义。所以它胜任不了把这个接口的语义改成支持分页这种需要业务推理的任务。意图解析的召回率有限。规则引擎面对复杂的复合指令会拆解失败需要用户拆成几个短指令分步执行。这是当前设计取向下的有意取舍——换取零幻觉的可控性。对超大规模仓库10万文件支持不佳。构建完整Context的时间会明显上升内存占用也会超出单文件工具的理想边界。我的建议是该场景下换用更重型的全工程索引工具。5.3 和通用AI编码助手的本质差异很多人会拿cjh跟能聊天的AI编程助手对比我的看法是这完全是两种东西。通用助手是大而全路线它懂成千上万个开源项目的共性规律但对你的特定项目一无所知它的每一步建议都需要你人工校对本质上是一个高级代码搜索文本生成器。cjh走的是小而专路线它只懂当前这一个项目但它对这个项目的了解是结构性的——知道目录怎么组织、依赖怎么流转、构建怎么触发。这决定了它们回答问题的深度完全不在一个层级。用一句不太严谨但很形象的话概括通用AI编程助手像是一个转行来的全能顾问什么都能聊但什么都不深入cjh像一个跟了这个项目三年的老开发你问它这里能不能动它能给你拉出一张影响清单来。6. 把一个文件当开发团队的工程哲学带给我的启发做cjh这件事让我重新思考了一个问题在AI辅助编程越来越普及的今天工具的设计哲学到底应该是什么市面上的主流思路是越强大越好——接入更多上下文、支持更多语言、挂上更多外部能力。但cjh的实践告诉我对开发者来说可理解比强大更稀缺。当一个工具的内部逻辑超出使用者的理解范围它就从助手变成了黑盒你永远不知道它为什么这么改、下一步会改什么。而单文件架构天然地保持着可侦查性出问题了打开这一个文件从main开始顺着一行行看下去表达的是最直白的命令式逻辑没有任何框架黑话。这种轻到我可以一眼看穿的安心感恰恰是现在很多超重工程给不了的。另一方面cjh让我重新认识了仓颉这门语言。在做这个项目之前我对它的预期还停留在应用开发语言这个层面。但实际写下来它在标准库设计上对系统级任务的支持程度让这类工具的实现变得极其顺手。尤其是文件遍历、进程调用、命令行参数解析、正则匹配这些非业务代码但高频出现的部分几乎没有需要绕路的地方。编译产物是单一可执行文件也强化了这个工具即拷即用的属性。如果你对这类项目专属Agent的思路感兴趣我的建议是不要一上来就追求什么都能做的通用框架先从一个最小的、只干一件事的单文件工具开始踏踏实实把手上的仓库摸透再逐步叠加能力。你会发现在这个过程中真正困难的部分不是让Agent会写代码而是为Agent划定一个它可以在其中自由发挥、又不会伤到项目的安全边界。cjh就是在这条边界的探索中长出来的一个具体答案。