news 2026/9/10 4:39:59

AST驱动的规约协同开发:手机端指挥Kiro+Claude+Codex工作流

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AST驱动的规约协同开发:手机端指挥Kiro+Claude+Codex工作流

1. 项目概述:这不是一个“手机App”,而是一套可落地的规约协同工作流

你看到标题里写着“手机端掌控 Kiro”,第一反应可能是——又一个吹嘘移动端功能的营销话术?别急,我用三台不同型号的安卓手机(Pixel 7、小米13、华为Mate 50)和一台iPhone 14 Pro实测了整整11天,结论很明确:这不是把桌面IDE塞进手机屏幕的伪需求,而是用手机作为“规约指挥中枢”,把Claude的架构评审能力、Codex的代码生成能力、Kiro的AST驱动规约验证能力,拧成一股可调度、可追溯、可回滚的工程流。核心关键词——Kiro、Claude、Codex、规约驱动开发、AST——不是堆砌术语,而是五个咬合紧密的齿轮:Kiro是规约执行引擎,Claude是人工经验的AI化投射,Codex是代码实现的即时响应器,规约驱动开发是方法论骨架,AST(抽象语法树)是所有动作的统一语义锚点。

这个范式解决的是真实痛点:前端工程师在会议室白板上画完模块拆分图,回到工位写代码时发现接口契约没对齐;后端同学刚提交完API文档,测试同学发现字段类型和规约定义冲突;更常见的是,Code Review时发现某段逻辑“看起来没问题”,但违反了团队约定的“禁止在Service层调用外部HTTP客户端”这条规约——这种问题靠人盯人永远漏检,靠CI/CD静态扫描又太晚。而本方案让规约从“贴在wiki上的文字”变成“活在开发流里的裁判”,手机端不是用来写代码的,是用来拍一张接口草图、语音说一句“这个函数要校验JWT且超时30秒”,然后立刻看到Claude生成的评审意见、Codex输出的带规约注解的代码片段、Kiro返回的AST合规性报告。它适合三类人:技术负责人想收拢架构一致性,资深开发者想减少重复性设计决策,以及刚入职的新人——他打开手机扫一下老代码,就能看到“这段为什么这么写”的规约溯源链。我试过让实习生用这套流程完成一个支付回调模块的开发,从需求理解到可运行代码,全程未打开IDE,只用手机+耳机+Kiro CLI终端,耗时2小时17分钟,规约违规项为零。

2. 整体架构设计与核心思路拆解:为什么必须用AST做统一语义层?

2.1 传统方案的死结:工具割裂导致规约失效

先说清楚我们绕不开的坑。市面上常见的“AI编程辅助”方案,比如VS Code插件集成Claude或Codex,本质是“代码补全增强版”:你在写fetch(时,AI猜你要接什么URL。这解决不了规约问题——因为AI不知道你团队约定的“所有网络请求必须封装在apiClient类里”,它只认语法。而另一些规约工具(如ESLint自定义规则、SonarQube策略)又太笨重:它们只能扫描已存在的代码,无法在写之前干预,更无法理解“这个函数应该有重试机制”这类业务语义。我见过最典型的失败案例:某电商团队引入了Codex生成订单创建逻辑,AI输出的代码完美符合JavaScript语法,但完全忽略了“订单创建必须同步触发风控校验”这条核心规约,结果上线后风控系统形同虚设。问题根源在于——语法(Syntax)≠ 语义(Semantics)≠ 规约(Contract)。传统工具要么卡在语法层(Codex),要么卡在文本层(文档),要么卡在运行时层(监控),唯独缺一个能把三者串起来的“语义中间件”。

2.2 AST:唯一能同时承载语法、语义、规约的公共载体

AST(Abstract Syntax Tree,抽象语法树)就是这个中间件。它不是编译器的黑箱产物,而是代码的“骨骼结构”。举个最简单的例子:

function validateToken(token) { return jwt.verify(token, process.env.SECRET); }

这段代码的AST长这样(简化版):

FunctionDeclaration ├── id: Identifier "validateToken" ├── params: [Identifier "token"] └── body: BlockStatement └── returnStatement └── callExpression ├── callee: MemberExpression "jwt.verify" └── arguments: [Identifier "token", MemberExpression "process.env.SECRET"]

看到没?AST里没有括号、分号这些语法糖,只有“函数声明”“参数列表”“返回语句”“函数调用”这些纯语义节点。Kiro的核心能力,就是把团队规约翻译成AST节点匹配规则。比如“禁止直接使用process.env”这条规约,在Kiro里就是一条AST查询:

{ "type": "MemberExpression", "object": { "type": "Identifier", "name": "process" }, "property": { "type": "Identifier", "name": "env" } }

只要AST里出现这个结构,Kiro就报错。而Claude和Codex的介入点,正是AST——Claude评审时,我们喂给它的不是原始代码字符串,而是这段代码的AST JSON表示(附带上下文注释);Codex生成代码时,我们要求它输出的不是文本,而是符合特定AST模式的JSON结构(比如必须包含try-catch节点,且catch块内必须调用logger.error)。这样,三者的输入输出都统一在AST语义空间里,规约不再是纸面约定,而是可计算、可验证、可生成的工程资产。

2.3 手机端为何是“指挥中枢”而非“编码终端”

很多人误以为“手机端掌控”意味着在手机上写代码。实测证明,这既不现实也不必要。手机真正的价值在于场景适配性

  • 会议现场:产品经理在白板上画出新功能流程图,你用手机拍照,Kiro自动识别图中关键节点(如“用户登录”“支付网关”),生成初始规约草案,Claude实时评审“这个流程是否遗漏了幂等性处理”,Codex同步输出各节点的伪代码框架;
  • 生产环境:线上报警触发,运维发来错误堆栈截图,你手机打开Kiro,上传截图,它自动解析出异常类型(如NullPointerException),Claude基于AST分析关联代码路径,指出“该异常源于Service层未做空值校验”,Codex立刻生成修复补丁的AST结构;
  • 代码审查:同事推送PR,GitHub通知弹到手机,你点开Kiro,它已预加载该PR的AST变更摘要,Claude给出“此修改违反了‘数据库操作必须包裹在事务中’规约”的结论,Codex提供合规重构建议。

手机在这里是“规约事件触发器”和“决策确认器”,所有重负载计算(AST解析、大模型推理)都在本地边缘设备(如Mac Mini)或可信云节点完成,手机只负责低带宽指令下发和结果可视化。这比强行在手机上跑LLM模型靠谱十倍——我测试过在骁龙8 Gen2芯片上运行量化版Claude-3-haiku,单次推理耗时42秒,而通过手机发送HTTP请求到本地Kiro服务,平均响应时间1.7秒。

3. 核心组件深度解析与实操要点:Kiro、Claude、Codex如何真正协同?

3.1 Kiro:规约引擎的安装、配置与AST规约编写实战

Kiro不是开箱即用的黑盒,它的威力取决于你如何定义规约。官方文档强调“Kiro支持TypeScript/JavaScript/Python/Java”,但实际部署中,Java支持最成熟,Python次之,TypeScript因类型擦除存在AST信息丢失风险。我推荐从Java项目切入,以Spring Boot微服务为例:

安装步骤(macOS/Linux):

  1. 下载Kiro CLI(非官网下载页,而是GitHub Releases最新稳定版,注意避开-alpha后缀):
    curl -L https://github.com/kiro-org/kiro-cli/releases/download/v2.4.1/kiro-cli-2.4.1-macos-arm64.tar.gz | tar xz sudo mv kiro /usr/local/bin/
  2. 初始化项目规约库:
    kiro init --project my-payment-service --lang java --ast-parser javac
    关键参数--ast-parser javac指定使用JDK自带的javac解析器,比第三方Parser更稳定(实测在JDK 17+环境下无兼容问题)。

编写第一条规约(禁止硬编码密钥):
./kiro/rules/目录下创建no-hardcoded-secret.json

{ "name": "no-hardcoded-secret", "description": "禁止在代码中硬编码敏感密钥", "language": "java", "astPattern": { "type": "Literal", "value": { "$regex": "(?i)(password|secret|key|token|auth)" } }, "context": { "parent": { "type": "VariableDeclaration", "declarators": [{ "init": { "type": "Literal" } }] } }, "severity": "ERROR", "fixSuggestion": "使用@Value注解读取配置中心密钥" }

提示:astPattern中的$regex是Kiro内置的正则匹配器,不要用JavaScript的/pattern/g语法;context.parent确保只匹配变量声明中的字面量,避免误报字符串拼接场景。

实操心得:

  • 初期别贪多,先写3条高频违规规约(如“禁止System.out.println”“必须使用SLF4J日志”“Controller层不得调用DAO”),每条规约单独文件,便于调试;
  • Kiro的AST调试神器kiro ast-dump必须掌握:kiro ast-dump src/main/java/com/example/MyService.java --format json,它会输出完整AST,帮你精准定位节点类型;
  • 规约生效范围用--include参数控制:kiro check --include "src/main/**/*Service.java",避免扫描测试代码污染结果。

3.2 Claude:如何让大模型真正理解AST语义并输出可执行评审

Claude不是万能的,直接喂它Java源码,它大概率会忽略规约细节。关键在于构建AST-aware提示词(Prompt)。我反复迭代27版后,确定以下结构最有效:

Claude评审提示词模板(保存为claude-review-prompt.txt):

你是一名资深Java架构师,正在评审一段基于AST解析的代码规约。请严格按以下步骤执行: 1. 解析输入的AST JSON,识别关键节点:函数名、参数列表、返回类型、调用的外部依赖(如HttpClient、JDBC)、异常处理结构; 2. 对照团队规约清单(见下文),逐条检查是否存在违规; 3. 对每条违规,指出具体AST节点路径(如"body[0].expression.callee.property.name")和修复建议; 4. 输出格式必须为严格JSON:{"violations": [{"rule": "no-direct-http-client", "astPath": "...", "suggestion": "..."}], "summary": "..."} === 团队规约清单 === - no-direct-http-client: 禁止Service层直接调用HttpClient,必须通过ApiClient封装 - mandatory-transaction: @Transactional注解必须出现在Service方法上,且传播行为为REQUIRED - no-logging-in-controller: Controller层禁止调用Logger,日志由Service层统一处理 === 待评审AST === {AST_JSON_HERE}

集成到Kiro工作流:

  1. Kiro扫描代码后,生成AST JSON并存入临时文件;
  2. 调用Claude API(需提前注册Anthropic账号,获取API Key):
    curl -X POST https://api.anthropic.com/v1/messages \ -H "x-api-key: $ANTHROPIC_KEY" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-3-haiku-20240307", "max_tokens": 1024, "messages": [{ "role": "user", "content": "'"$(cat claude-review-prompt.txt | sed "s/{AST_JSON_HERE}/$(cat temp.ast.json | jq -c)/g")"' }] }' > claude-response.json
  3. 解析claude-response.json,提取violations数组,注入Kiro报告。

注意:Claude的max_tokens必须设为1024以上,否则AST JSON被截断;anthropic-version必须用最新版,旧版本不支持结构化输出。

实操心得:

  • 别用Claude-3-sonnet或opus——haiku速度最快,且对AST结构化输出更稳定(实测sonnet在长AST下易丢字段);
  • 提示词里必须强制要求“指出AST节点路径”,这是Claude和Kiro联动的唯一桥梁;
  • 首次运行时,用kiro ast-dump导出一个简单类的AST,手动填入提示词测试,确保Claude能正确解析节点路径。

3.3 Codex:从规约到代码的秒级生成,不是补全而是构造

Codex(此处指OpenAI的CodeX模型,非GitHub Copilot)的定位是“规约驱动的代码构造器”。它不生成if (x > 0) {...}这种碎片,而是根据规约约束生成完整函数骨架。例如,当Kiro检测到“缺少JWT校验”规约时,Codex生成的不是一行代码,而是:

public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest request) { try { // 1. 规约强制:JWT校验必须在此处完成 String token = jwtService.generateToken(request.getUsername()); // 2. 规约强制:必须记录审计日志 auditLogger.log("USER_LOGIN_SUCCESS", request.getUsername()); return ResponseEntity.ok(new AuthResponse(token)); } catch (InvalidCredentialsException e) { // 3. 规约强制:认证失败必须返回401且不泄露细节 auditLogger.log("USER_LOGIN_FAILED", request.getUsername()); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } }

Codex调用的关键参数:

curl -X POST https://api.openai.com/v1/chat/completions \ -H "Authorization: Bearer $OPENAI_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "codex-cpp-2023-03-15", "messages": [ {"role": "system", "content": "你是一个Java Spring Boot专家,严格遵循以下规约:1. 所有Controller方法必须返回ResponseEntity;2. 认证失败必须返回401且不返回错误消息;3. 必须调用auditLogger记录审计日志。"}, {"role": "user", "content": "生成一个/login接口的实现,接收LoginRequest,返回JWT token"} ], "temperature": 0.2, "response_format": {"type": "json_object"} }' > codex-output.json

关键点:system角色中嵌入规约约束,temperature设为0.2保证输出确定性,response_format强制JSON避免自由发挥。

实操心得:

  • Codex模型选codex-cpp-2023-03-15而非gpt-4——前者专为代码生成优化,后者易产生“伪代码”;
  • system提示词必须用自然语言描述规约,不要用JSON(Codex对JSON规约理解不稳定);
  • 生成后必须用Kiro验证:kiro check --code "$(cat codex-output.json | jq -r '.choices[0].message.content')",确保输出代码100%合规。

4. 全流程实操:从手机触发到代码落地的7步闭环

4.1 环境准备:手机、边缘节点、规约库的三位一体

手机端(Android/iOS):

  • 安装Termux(Android)或iSH(iOS),这是手机运行CLI的基石;
  • 在Termux中执行:
    pkg update && pkg install curl jq python clang-tools pip install kiro-cli # 注意:手机端只安装CLI,不运行Kiro引擎
  • 配置Kiro指向本地边缘节点:kiro config set --host http://192.168.1.100:8080(你的Mac Mini IP)。

边缘节点(Mac Mini/NUC):

  • 运行Kiro Server:kiro server --port 8080 --rules-dir /path/to/kiro-rules
  • 启动Claude/Codex代理服务(用Python Flask封装API调用,避免手机直连):
    # claude-proxy.py from flask import Flask, request, jsonify import requests app = Flask(__name__) @app.route('/claude/review', methods=['POST']) def review(): ast_json = request.json['ast'] # 构造Claude请求(省略API Key处理) response = requests.post('https://api.anthropic.com/v1/messages', ...) return jsonify(response.json())
    运行:python claude-proxy.py
  • 确保防火墙放行8080端口,手机与边缘节点在同一局域网。

规约库初始化:

  • 创建Git仓库git@github.com:your-org/kiro-rules.git,包含java/python/子目录;
  • 每条规约文件命名规范:RULE_NAME_LANGUAGE.json(如no-direct-http-client-java.json);
  • Kiro Server启动时自动拉取最新规约,无需重启。

4.2 手机端7步操作实录:一次真实的支付回调开发

Step 1:需求捕获(手机拍照)
产品经理发来微信:“支付回调要增加风控拦截,规则:金额>10000且用户等级<3时拒绝”。你打开Termux,执行:

kiro capture --type requirement --image payment-callback.jpg

Kiro调用手机摄像头拍照,OCR识别文字,生成规约草案payment-risk-check-java.json

Step 2:规约初审(手机触发Claude)

kiro review --rule payment-risk-check-java.json --ast ./src/main/java/com/example/PaymentController.java

Kiro将PaymentController.java的AST发送至边缘节点,Claude返回:

{ "violations": [{ "rule": "payment-risk-check", "astPath": "body[0].statements[5].ifStatement.test", "suggestion": "当前条件仅检查金额,需补充用户等级判断" }], "summary": "规约基本合理,但需增强条件覆盖" }

Step 3:生成规约代码(手机调用Codex)

kiro generate --rule payment-risk-check-java.json --context PaymentController

Codex返回:

// 规约强制:风控拦截必须在回调入口处执行 if (payment.getAmount() > 10000 && user.getLevel() < 3) { logger.warn("Risk blocked: amount={}, level={}", payment.getAmount(), user.getLevel()); return ResponseEntity.badRequest().body("RISK_BLOCKED"); }

Step 4:本地验证(手机发起Kiro检查)

kiro check --code "$(cat codex-output.java)" --rules payment-risk-check-java.json

输出:✅ All rules satisfied

Step 5:提交代码(手机生成PR)

kiro pr --branch feature/payment-risk --title "Add risk check per规约#123" --body "Generated by Kiro-Claude-Codex workflow"

自动创建Git分支、提交代码、推送并生成GitHub PR链接。

Step 6:CI/CD集成(边缘节点自动触发)
GitHub Webhook触发Jenkins Job,执行:

kiro check --project my-payment-service --ci-mode

若规约违规,立即失败并附Claude评审报告。

Step 7:知识沉淀(手机归档规约)

kiro archive --rule payment-risk-check-java.json --version v1.2.0

规约存入Git,版本号自动递增,供后续项目复用。

实操心得:

  • Termux的pkg install clang-tools是关键,它提供clang++用于AST解析,手机端无此工具则无法生成AST;
  • kiro capture命令依赖手机摄像头权限,iOS需在设置中开启iSH的相机访问;
  • 第一次kiro pr可能失败,因为GitHub Token未配置,执行kiro config set --github-token xxx即可。

5. 常见问题与排查技巧实录:那些文档里不会写的坑

5.1 Kiro相关问题速查表

问题现象根本原因排查命令解决方案
kiro check报错AST parser not found手机端未安装clang-tools或路径不对which clang++Termux中执行pkg install clang-tools,确认/data/data/com.termux/files/usr/bin/clang++存在
规约规则不生效AST节点类型名错误(如把Literal写成StringLiteralkiro ast-dump Sample.java | head -20ast-dump查看真实节点类型,Java中字符串字面量是Literal,不是StringLiteral
Kiro Server启动后无法访问防火墙阻止8080端口sudo ufw status(Ubuntu)sudo ufw allow 8080,或临时关闭防火墙测试
规约文件修改后不生效Kiro Server未自动热重载kiro server --debug启动时加--watch参数:kiro server --watch --port 8080

5.2 Claude集成问题深度解析

问题:Claude返回{"error": "invalid_request_error"}

  • 原因:提示词中AST JSON过大(超过Claude的100KB限制);
  • 排查wc -c temp.ast.json查看文件大小;
  • 解决:启用AST剪枝——Kiro提供--prune参数:kiro ast-dump --prune "body,comments" Sample.java,移除无关节点;实测剪枝后AST体积减少68%,且不影响规约检查。

问题:Claude评审结果中AST路径错误(如body[0].expression不存在)

  • 原因:Claude对AST结构理解偏差,尤其在复杂嵌套时;
  • 排查:对比kiro ast-dump输出和Claude引用的路径;
  • 解决:在提示词中加入AST结构说明:"AST节点说明:body是BlockStatement数组,每个元素是Statement,expression是ExpressionStatement的expression字段";实测添加后路径准确率从73%提升至98%。

5.3 Codex生成问题避坑指南

问题:Codex生成代码包含console.log,违反“禁止前端日志”规约

  • 原因system提示词未明确语言环境,Codex默认生成JavaScript;
  • 解决:强制指定语言:"你是一个Java Spring Boot专家,不是JavaScript开发者"
  • 额外技巧:在user消息末尾加一句"输出纯Java代码,不要任何注释或解释",避免Codex画蛇添足。

问题:Codex输出JSON格式错误,jq解析失败

  • 原因:Codex偶尔在JSON外多输出一行// Generated by Codex
  • 解决:管道过滤:curl ... \| sed '/^\/\//d' \| jq -r '.choices[0].message.content'
  • 终极方案:用Python脚本替代jq,增加JSON容错解析。

5.4 手机端特有问题实战记录

问题:Termux中kiro capture拍照后无反应

  • 原因:Android 12+权限变更,Termux无法直接访问摄像头;
  • 解决:改用termux-camera-photo命令:termux-camera-photo -c 0 photo.jpg && kiro capture --image photo.jpg
  • 注意-c 0指定后置摄像头,-c 1为前置。

问题:iPhone iSH中curl无法连接本地边缘节点

  • 原因:iOS限制App后台网络,iSH休眠后连接中断;
  • 解决:在iSH中执行while true; do sleep 30; curl -s http://192.168.1.100:8080/health; done &保持连接活跃;
  • 更优方案:用Shortcuts自动化,创建“Kiro触发”快捷指令,一键唤醒iSH并执行命令。

6. 规约协同范式的边界与演进:它不能做什么,以及下一步怎么走

这套方案不是银弹。我必须坦诚告诉你它的明确边界

  • 它不替代设计评审:Claude能指出“这个函数缺少异常处理”,但无法判断“是否该用Saga模式替代两阶段提交”——这是架构师的职责;
  • 它不处理非代码规约:比如“文档必须在Confluence更新”,Kiro无法验证,需配合其他工具;
  • 它对动态语言支持有限:Python的AST在运行时可修改(如exec()),Kiro静态分析会漏检,Java/Kotlin更可靠;
  • 它不解决性能问题:规约能保证“用了缓存”,但无法保证“缓存命中率>95%”,这需要APM工具。

那么,下一步怎么走?基于我团队半年的实践,有三个确定性方向:
第一,AST向IR(Intermediate Representation)演进:当前Kiro基于语言特定AST,未来将接入MLIR(Multi-Level Intermediate Representation),让规约一次编写,跨Java/Python/Go生效。我们已在LLVM IR层面验证了“禁止全局变量”规约的通用性;
第二,手机端轻量化增强:Termux/iSH终究是妥协方案,我们正与Flutter团队合作开发原生Kiro Mobile App,核心能力是离线AST解析(用WASM编译Kiro引擎)和蓝牙直连边缘节点,彻底摆脱网络依赖;
第三,规约即文档(Contract-as-Documentation):当所有规约都通过AST验证,Kiro可自动生成交互式API文档——点击“JWT校验”规约,直接跳转到对应代码行和Claude评审记录。这比Swagger更贴近真实约束。

最后分享一个小技巧:在团队推行此范式时,永远从“救火场景”切入。不要一上来就说“我们要建规约体系”,而是找一个最近引发P0故障的代码问题(比如“支付重复扣款”),用Kiro重现问题AST,让Claude指出规约漏洞,再用Codex生成修复代码。当大家亲眼看到“原来那行看似无害的代码,违反了三条核心规约”,信任感就建立了。我见过最成功的落地案例,是一家金融科技公司,他们用这套流程将支付模块的规约违规率从37%降至0.2%,而整个过程只花了两周——不是靠培训,而是靠让工程师每天少修一个Bug。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 4:37:21

免费物联网云平台选型与组态实操:从OneNET到FUXA

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 4:34:43

微信里装进Claude Code:Grix实现AI编程与群聊协作实战

# 把 Claude Code 装进“微信”&#xff1a;用 Grix 随时随地指挥 AI 编程与群聊协作实战接触 Claude Code 一段时间后&#xff0c;我最大的感受是&#xff1a;这玩意儿在电脑前确实能打&#xff0c;可一旦离开工位就抓瞎。命令行工具绑死在终端里&#xff0c;手机上没法直接调…

作者头像 李华
网站建设 2026/9/10 4:32:27

Claude Code Router 接入 LINE:AgentClaw 消息接力完整配置指南

Claude Code Router 接入 LINE&#xff1a;AgentClaw 消息接力完整配置指南 【免费下载链接】claude-code-router One local control plane for every AI agent: route across models, fuse new capabilities, orchestrate tools, and stay fully in control. 项目地址: http…

作者头像 李华
网站建设 2026/9/10 4:31:45

基于树莓派与智能算法的空调省电助手实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华