CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
项目
ACE(Agent for Cangjie to Engine) —— 用仓颉(Cangjie)语言编写的服务端框架。角色对标 Spring/MidwayJS,技术路线对标 Micronaut/Quarkus(编译期宏而非运行时反射)。自底向上分层:Koa 式洋葱中间件内核 → 路由/请求解析中间件 → 声明式宏层(@Controller/@Service/@Inject/@Get)→ 智能体运行时。路线与设计说明见 doc/roadmap-m12-plus.md、doc/blog-ace-technical-deep-dive.md。
环境与命令(关键)
任何 cjpm 命令前必须先 source scripts/env.sh,否则链接失败。本机(macOS 26 / cjc 1.1.0-alpha / arm64)有两个绕过:
- macOS 26 SDK 与 cjc 自带 LLD 不兼容 →
env.sh把SDKROOT指向MacOSX15.sdk。 - 运行 stdx 需
DYLD_LIBRARY_PATH含 openssl@3 →env.sh已设。
source scripts/env.sh # 每个 shell 必做
cjpm build # 构建整个 workspace
cjpm test # 运行全部单测(推荐:见下方 gotcha)
cjpm run examples/hello-api # 运行示例(或直接 target/release/bin/hello_api)
测试 gotchas:
- 成员目录名(
ace-router)≠ 包名(aceboot::router),所以cjpm test aceboot::router会报“路径不存在”、cjpm test ace-router跑 0 个 → 用整cjpm test(跑全 workspace),或cjpm test -m <member>。 - 单测
println默认被捕获,调试加cjpm test --no-capture-output。 - 单测用
@Test+@Expect(actual, expected)(import std.unittest.*与std.unittest.testmacro.*),文件名*_test.cj与被测同包。
stdx 接入(预编译,勿误改 cjpm.toml)
cjpm 的 git=/version= 方式拉取 stdx 在本机不可用(TLS 崩溃 / 源码 pre-build 缺工具链)。已采用预编译 bin-dependencies:
- 各模块 cjpm.toml 的
path-option/link-option统一经${CANGJIE_STDX_PATH}环境变量引用 stdx(官方约定),并同时配了aarch64-apple-darwin与x86_64-unknown-linux-gnu两个 target。 - macOS 由
scripts/env.sh导出CANGJIE_STDX_PATH="$STDX_HOME"(STDX_HOME=~/darwin_aarch64_cjnative,预编译 stdx 1.1.3.1);Linux 构建前自行 export 指向对应平台的 stdx 根目录。 - 可执行模块需
link-option = "-L${CANGJIE_STDX_PATH} -lcangjie-dynamicLoader-opensslFFI"(运行时 dlopen OpenSSL,macOS 的 DYLD 路径由 env.sh 设置)。 - 新增 stdx 依赖成员时按上述双 target 模式配置,勿硬编码绝对路径。
架构(大图)
请求生命周期:stdx.net.http → ace-http 的 catch-all 分发器 → 构造 ace-web.Context → 跑 App 洋葱 → 写回响应。
ace-web(无 stdx,纯仓颉,可独立单测):洋葱内核。compose.cj是核心——嵌套函数dispatch(i)+ 闭包递归实现洋葱,Array<Bool>守卫防next()重复调用。Context是框架无关的请求/响应载体(method/path/headers/state/status/body/query/rawBody)。App.handle(ctx)驱动洋葱并兜底异常。ace-router(依赖 ace-web,无 stdx):路由中间件。vendored 了path_to_regex到aceboot::router.pathregex子包(已去掉其 stdx.encoding.url 依赖、改纯 std 百分号编解码),Router.routes()用matchDetailed支持:name/*name/:name?/:name+/:name([0-9]+)/可选分组/转义全规则。Route.mwChains是“由外到内的中间件层(各级 Router 的 mws 活引用)”,保证根/组中间件正确叠加。ace-bodyparser(依赖 ace-web,无 stdx):请求解析中间件,读ctx.rawBody(),目前解析 urlencoded 表单(带 URL 解码),结果经formValue(ctx, name)读取。ace-http(依赖 ace-web + stdx):唯一接触 stdx 的层。AceDistributor(catch-all 分发器)把所有路径交给同一入口进洋葱;listen(app, addr, port)起服务。ace-framework(M3,进行中):声明式宏层,规划为三 module(macros / runtime / facade)。核心机制已 spike 验证:@Service/@Controller宏在类旁生成顶层let __ace_reg_X = Registry.register(...),其初始化器在程序启动期自动执行完成自注册——无反射、无枚举(这是“编译期宏 + 零运行时反射”路线的关键)。cjpm 自动处理--compile-macro宏包先编译。ace-redis(依赖 ace-framework + stdx):零反射 Redis 组件。子包resp(RESP2 编解码,可离线单测)/client(连接池、全命令、RedisSubscriberPub/Sub、RedisLockManager分布式锁,仅触 std.net)/integration(@Component RedisComponent,import 即自注册,读[redis]装配)。接入点:@Inject RedisClient/RedisLockManager、@Cacheable[ttl, "redis"]命名缓存后端、Redis 后端限流、[redis].pubsub.bridgeChannels桥接为RedisMessage应用事件(@EventListener消费)。示例见examples/redis-demo。ace-template(依赖 ace-web + ace-framework):零反射模板渲染引擎(Mustache 风格逻辑轻量)。子包engine(词法/语法/渲染,纯 std、零 IO,可离线单测)/integration(@Component TemplateComponent,import 即自注册,读[template]装配 dir/suffix)。数据以显式TemplateValue(TStr/TInt/TBool/TList/TMap/TNil)建模、TemplateModel链式构造(无反射);语法含{{ x }}(默认 HTML 转义)/{{& x }}·{{{ x }}}(原样)/{{# }}{{/ }}section(bool/对象上下文/列表迭代)/{{^ }}反转/{{! }}注释/{{> name }}局部模板/点分路径。功能完整层:过滤器链{{ x | upper | default:"无" }}(内置upper/lower/capitalize/default:"arg"/length/json+ 终结转义raw/html/js/url/attr;engine.registerFilter(name, fn)加自定义助手);循环元数据(section 迭代列表内){{@index}}(0基)/{{@number}}(1基)/{{@first}}/{{@last}}/{{@length}}/{{@odd}}/{{@even}};模板继承(父{{$ block}}默认{{/ block}}、子首行{{< parent}}+ 同名{{$ block}}覆盖,多级就近者胜,block 外内容忽略,防环);自定义分隔符{{=<% %>=}};standalone 空白(独占一行的块级标签不产生空行,standalone partial 按缩进逐行缩进);上下文转义(html/js/url/attr 各自策略)。JSON 桥接(integration 层,触 stdx):jsonToTemplateValue(jsonText): TemplateValue把 JSON 文本转模型直接喂render。接入点:@Inject TemplateEngine后engine.render(name, model)(按名走文件加载器)或renderString(src, model)(内联),配ctx.html(...)(ace-web 新增便捷方法)或renderView(ctx, engine, name, model)回写 text/html。踩坑记:indexOfSeq/标签切分不能用src[i..i+m]String 切片比较——扫描位落在多字节字符中间会抛 "Invalid utf8",须按toArray()字节比较(标签内含中文即触发)。示例见examples/template-demo。
设计取向(已定,勿擅自反转):内核纯显式、零运行时反射;声明式层用编译期宏生成等价显式代码(可 --debug-macro 审计);纯内核 ace-web/ace-router/ace-bodyparser 不依赖 stdx、可独立单测;声明层 ace-framework-runtime(JSON 工具基于 stdx.encoding.json:parseJson/stringifyJson/field*/mapToJson/jsonToMap 等)、ace-http、ace-security、agent 接触 stdx。JSON DOM/解析统一用 stdx(不再自维护解析器);纯字符串辅助(jsonEscape/jsonStringArray 等)仍在 ace-web。
仓颉语言 gotchas(本仓库踩过的坑)
std.regex用 POSIX 语法[0-9],不认\d(路由内联正则写:id([0-9]+))。- 闭包捕获并重新赋值
var不可靠 → 需在闭包内累积状态时用引用类型(ArrayList/Array)并调用其方法,不重新赋值被捕获变量(见compose.cj的守卫、洋葱测试)。 - 字节比较要带
u8后缀(b == 37u8);String.toArray()得Array<UInt8>,String.fromUtf8(...)反向。 class引用类型 /struct值类型;Option无 null(?T+match/??/getOrThrow());enum+match穷尽。- 验证
.o/.a符号用系统/usr/bin/nm(cangjie 自带llvm-nm是 LLVM15,读不了 Apple clang21 新目标格式,会显示 0 符号)。
工作流
新增成员后把目录加入根 cjpm.toml 的 [workspace].members。每个能力做成可单独引入的 module(高内聚、可单测)。改代码后跑 cjpm test 保持全绿(当前基线见 git log)。全局编码规范(不可变、小文件、错误处理等)见 ~/.claude/rules/。