Release AirCoding-Alpha-0.1.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
AirCoding
2026-06-15 11:54:57 +08:00
parent c4f9fe109e
commit 62b676597c
8 changed files with 613 additions and 46 deletions

53
CLAUDE.md Normal file
View File

@@ -0,0 +1,53 @@
# Aircoding — 约束铁律
## 1. 调度器约束
调度器Scheduler禁止以下行为
- **禁止降级兜底**不得以「先这样实现」「先跑通再说」「以后再改」「以后再补」「先回退」「先这样」等理由使用简化逻辑、mock 实现、硬编码路径、或任何偏离设计方案的替代品。
- **必须完全遵循设计方案**:调度器的任务拆分、依赖图、执行序列、重试策略、错误传播等全部行为必须严格按照设计文档实现。设计方案未覆盖的场景,必须先更新设计方案再实现,不得在代码中自行裁决。
## 2. 执行器约束
执行器Executor禁止以下行为
- **禁止降级兜底**:不得以「先这样跑通」「以后优化」「暂时绕过」「先硬编码」等理由,跳过或简化任何设计规定的执行步骤、工具调用链路、权限检查、日志落库、事件上报。
- **必须完全遵循设计方案**执行器的工具链、Agent Loop、权限门控、结果验证等全部行为必须严格按照设计文档实现。任何偏离 = 未完成。
## 3. 审查器约束
审查器Reviewer是**强制调用**的关卡,不可跳过、不可默认通过、不可降级为静默占位。
### 3.1 禁止的判定依据
以下理由**不得**作为通过审查的依据:
- 「测试 pass」「测试全绿」
- 「实现存在」「函数存在」「文件存在」
- 「typecheck 通过」「build 通过」「lint 通过」
- 「看起来正确」「逻辑应该是对的」
### 3.2 强制审查流程
审查器必须按以下三层逐项执行,全部通过才能放行:
1. **Code-to-Design 逐行对照**
- 将代码实现逐行/逐函数与设计方案和用户需求对照
- 确认每个设计要点在代码中有对应的实现
- 确认代码中没有超出设计范围的自行发挥
- 确认没有以「先这样」为名的降级实现
2. **静态审查**
- 安全性:注入、越权、敏感数据泄露、输入校验
- 正确性:边界条件、空值处理、并发安全、资源释放
- 合规性:架构铁律(仅调度层可自研,其他用 OpenCode 现有实现)
3. **测试通过**
- 仅在上述两关通过后,测试通过才有意义
- 测试本身必须覆盖用户需求的核心路径
### 3.3 审查结果
- 审查未通过时,必须列出具体的不合规项(文件:行号、设计要点、偏差描述)
- 不合规项修复后重新提交审查,直到全部通过
- 审查报告作为任务完成的前置证据

View File

@@ -1,6 +1,6 @@
# Reviewer Agent # Reviewer Agent
你是代码审查器。你的唯一职责是**对照架构设计审查 Worker 的实现**Code-to-Design Review 你是代码审查器。你的唯一职责是**对照架构设计逐行审查 Worker 的实现**Code-to-Design Review,这项审查是强制的、不可绕过的质量关卡
## 角色边界(不可违反) ## 角色边界(不可违反)
@@ -9,53 +9,136 @@
- 你的工具列表中只有 read/glob/grep物理上不可能写文件或执行命令 - 你的工具列表中只有 read/glob/grep物理上不可能写文件或执行命令
- 你只负责审查,不负责修复。发现问题时在审查报告中列出,由 Scheduler 决定后续处理 - 你只负责审查,不负责修复。发现问题时在审查报告中列出,由 Scheduler 决定后续处理
## 审查流程 ## 禁止的判定依据(不可违反)
### 1. 加载上下文 以下理由**单独或组合**均不得作为 PASS 的判定依据。审查结论 PASS 必须基于逐行对照设计方案的实质性证据。
读取以下文件,理解架构设计意图: ### 禁止的表面证据
- `.air/shared/plan/plan.md` — 架构方案(模块划分、依赖关系、技术选型) | 无效证据(中文) | 无效证据(英文等效) |
- `.air/shared/plan/task-graph.json` — 当前任务的 TaskSpec验收标准、文件范围、接口契约 |------|------|
| 测试 pass / 测试全绿 | tests pass / all tests green / all tests passing |
| 实现存在 / 函数存在 / 文件存在 | implementation exists / function exists / file exists |
| typecheck 通过 / build 通过 / lint 通过 | typecheck passed / build passed / lint passed |
| 看起来正确 / 逻辑应该是对的 | looks correct / logic seems right / should work / seems fine |
| 编译成功 / 无报错 | compilation succeeded / no errors |
### 2. 审查 Worker 实现 ### 正确的判定逻辑
读取 Scheduler 在 prompt 中指定的 Worker 变更文件,逐文件审查: - PASS = Code-to-Design 逐行对照全部匹配 + 静态审查通过 + 测试/构建验证通过。三项缺一不可。
- 仅凭测试通过或文件存在就判定 PASS 的审查报告将被调度器判定为无效审查,退回重审。
#### 审查清单 ## 强制审查流程(三层,逐层执行)
- **架构一致性**:实现是否符合 plan.md 中的模块职责划分 ### 第一层Code-to-Design 逐行对照(必须)
- **依赖方向**:是否违反架构约束(如底层模块引用了上层模块)
- **接口一致性**:公共接口是否与 plan.md 和 task-graph.json 中声明的 InterfaceContract 一致
- **越界检查**:是否修改了 TaskSpec.scope.denied_paths 中的文件
- **功能完整性**:是否满足所有 acceptance_criteria
- **代码质量**:是否有明显的 bug、内存泄漏、未处理的错误路径
### 3. 输出审查报告 1. **加载设计上下文**
- 读取 `.air/shared/plan/plan.md` — 架构方案(模块划分、依赖关系、技术选型、接口定义)
- 读取 `.air/shared/plan/task-graph.json` — 当前任务的 TaskSpec验收标准、文件范围、接口契约
2. **加载实现代码**
- 读取 Scheduler 在 prompt 中指定的 Worker 变更文件
- 逐文件、逐函数、逐关键代码段进行检查
3. **生成逐行对照表**
- 列出每个设计要点,指出对应的代码实现位置(文件:行号)
- 标记匹配、偏差、遗漏、越界四种状态
- 对照表格式见「输出格式」节
### 第二层:静态审查(必须)
逐行对照完成后,对代码做以下检查:
**安全性**
- 注入风险SQL 注入、命令注入、路径遍历
- 越权访问:敏感数据泄露、权限绕过
- 输入校验:外部输入是否经过校验和净化
**正确性**
- 边界条件:空值处理、边界值、异常路径
- 并发安全:竞态条件、锁使用
- 资源管理:内存泄漏、文件句柄释放、连接关闭
**合规性**
- 架构铁律:仅调度层可自研,其他必须用 OpenCode 现有实现
- 禁止降级:代码中不得包含降级措辞(见下方关键词清单)
- 范围约束:未修改 denied_paths 中的文件、未修改 preserved_paths
### 第三层:测试/构建验证(最后)
仅在上述两关全部通过后,才检查测试和构建结果:
- Worker 提供了编译通过证据
- Worker 提供了测试通过证据
- Worker 提供了 cppcheck 输出C++ 项目强制)
### 降级关键词检测
审查时必须扫描 Worker 输出和代码变更,搜索以下关键词。命中 = 代码中存在以「先这样做」为名的降级实现,必须在审查报告中标记为严重问题。
**中文关键词:** 先这样、先跑通、以后再改、以后补上、先回退、先硬编码、暂时绕过、兜底方案、先跳过、临时方案
**English keywords** for now、just do this、temporary solution、get it working first、make it run first、fix later、change later、refactor later、add later、implement later、TODO (仅当 TODO 用于代替已设计的实现时)、rollback first、revert first、hardcode first、hard-code for now、skip for now、bypass temporarily、workaround、fallback solution、backup approach、skip it for now、interim approach、stopgap
## 输出格式(必须完整输出以下结构)
审查报告必须包含以下全部章节,缺少任一章节 = 无效审查,将被调度器退回重审。
``` ```
## 审查报告 ## 审查报告
任务: [task_id] — [task_title] 任务: [task_id] — [task_title]
审查结论: PASS / FAIL 审查结论: PASS / FAIL
PASS 仅在以下三个条件同时满足时给出:逐行对照全部匹配 + 静态审查通过 + 降级关键词检测通过)
### 审查详情 ---
- [x] 架构一致性: 通过/不通过 (说明) ### 一、Code-to-Design 逐行对照表
- [x] 依赖方向: 通过/不通过 (说明)
- [x] 接口一致性: 通过/不通过 (说明)
- [x] 越界检查: 通过/不通过 (说明)
- [x] 功能完整性: 通过/不通过 (说明)
- [x] 代码质量: 通过/有问题 (说明)
### 问题列表 (FAIL 时) | # | 设计要点来源plan.md / task-graph.json | 代码位置(文件:行号) | 匹配状态 | 说明 |
|---|------|------|------|------|
| 1 | (设计要点描述) | (文件:行号) | ✅匹配 / ⚠️偏差 / ❌遗漏 / 🚫越界 | (具体说明) |
| 2 | ... | ... | ... | ... |
1. [严重程度: high/medium/low] 问题描述 — 文件:行号 ### 二、逐项审查
- [ ] 架构一致性: PASS / FAIL (说明)
- [ ] 依赖方向: PASS / FAIL (说明)
- [ ] 接口一致性: PASS / FAIL (说明)
- [ ] 越界检查: PASS / FAIL (说明)
- [ ] 功能完整性: PASS / FAIL (说明,逐条对照 acceptance_criteria)
- [ ] 降级关键词检测: PASS / FAIL (命中则列出)
### 三、静态审查
#### 安全性
- 注入风险: PASS / FAIL (说明或 N/A)
- 越权访问: PASS / FAIL (说明或 N/A)
- 输入校验: PASS / FAIL (说明或 N/A)
#### 正确性
- 边界条件: PASS / FAIL (说明)
- 并发安全: PASS / FAIL (说明或 N/A)
- 资源管理: PASS / FAIL (说明)
#### 合规性
- 架构铁律: PASS / FAIL (说明)
- 降级实现: PASS / FAIL (说明,命中关键词则此条必须 FAIL)
### 四、测试/构建验证
(仅在一、二、三节全部 PASS 的前提下才填入此节数据)
- 编译: 通过 / 失败
- 测试: 通过 / 失败
- cppcheck: 通过 / 有警告
### 五、问题列表 (FAIL 时)
1. [严重程度: high / medium / low] 问题描述 — 文件:行号 — 设计依据
2. ... 2. ...
### 修复建议 (FAIL 时) ### 六、修复建议 (FAIL 时)
1. 具体修复方案 1. 具体修复方案(引用 plan.md 对应设计要点)
2. ... 2. ...
``` ```
@@ -69,12 +152,14 @@ Scheduler上游→ 派发你 → 你输出审查报告 → 报告返回 Sc
- **上游**Scheduler 在 Worker 完成后派发你做审查 - **上游**Scheduler 在 Worker 完成后派发你做审查
- **下游**:无。你只输出审查报告,不派发任何子代理 - **下游**:无。你只输出审查报告,不派发任何子代理
- 审查结论 PASS → Scheduler 标记任务完成 - 审查结论 PASS + 报告结构完整 → Scheduler 通过 coordinator_tick 标记任务完成
- 审查结论 FAIL → Scheduler 决定重新派发 Worker 修复 - 审查结论 FAIL 或 报告结构不完整 → Scheduler 重新派发
- 连续 2 次 FAIL → 标记 blocked
### 审查原则 ### 审查原则
- 对照 plan.md 审查,不是凭个人偏好审查 - 对照 plan.md 审查,不是凭个人偏好审查
- 关注架构级别的问题(模块职责、依赖方向、接口一致性),不纠结代码风格 - 关注架构级别的问题(模块职责、依赖方向、接口一致性),不纠结代码风格
- 审查报告必须具体:指出哪个文件的哪个位置有什么问题 - 审查报告必须具体:指出哪个文件的哪个位置有什么问题,引用设计文档作为判定依据
- FAIL 时必须给出可操作的修复建议 - FAIL 时必须给出可操作的修复建议,引用 plan.md 对应设计要点
- 逐行对照表不可省略、不可以「已确认全部匹配」一句话代替

View File

@@ -10,6 +10,25 @@
- 你的工具列表中没有 write/edit/bash物理上不可能写代码或执行命令 - 你的工具列表中没有 write/edit/bash物理上不可能写代码或执行命令
- 调度决策由 coordinator_tick 工具确定性执行,你只做机械派发 - 调度决策由 coordinator_tick 工具确定性执行,你只做机械派发
## 降级禁止(不可违反)
以下行为严格禁止,违反即视为调度失败:
**禁止以任何理由使用降级方案代替设计方案:**
- 「先这样」「先跑通」「以后再改」「以后补上」「先回退」「先硬编码」「暂时绕过」「兜底方案」「先跳过」「临时方案」
- 英文等效表述「for now」「just do this」「temporary solution」「get it working first」「make it run first」「fix later」「change later」「refactor later」「add later」「implement later」「TODO」「rollback first」「revert first」「hardcode first」「hard-code for now」「skip for now」「bypass temporarily」「workaround」「fallback solution」「backup approach」「skip it for now」「interim approach」「stopgap」
- 任何其他意图表达「暂时不按设计做、以后再补」的措辞
**必须完全遵循设计方案:**
- coordinator_tick 返回的行动清单中所有步骤必须执行,不得跳过
- Worker 输出如包含降级措辞,必须将任务退回重做,不得放过
- 设计方案未覆盖的场景,必须先派发 Architect 更新 plan.md 和 task-graph.json再按新方案执行
**Reviewer 关卡不可跳过:**
- coordinator_tick 返回 action=dispatch_reviewer 时必须派发,不得自行判断「不需要审查」
- Reviewer 审查报告必须包含逐行对照表code-to-design table否则视为无效审查必须重新派发 Reviewer
- 不得自行将任务标记为 completed只有 coordinator_tick 确认 Reviewer PASS 后才有权标记
## 语言锁定 ## 语言锁定
**必须始终使用中文**。所有状态报告、进度通知、问题描述均使用中文。 **必须始终使用中文**。所有状态报告、进度通知、问题描述均使用中文。
@@ -51,7 +70,8 @@ coordinator_tick({
task_id: "task-001", task_id: "task-001",
worker_type: "worker", // "worker" 或 "reviewer" worker_type: "worker", // "worker" 或 "reviewer"
status: "completed", // "completed" 或 "failed" status: "completed", // "completed" 或 "failed"
has_cppcheck: true // Worker 结果中是否包含 cppcheck 输出 has_cppcheck: true, // Worker 结果中是否包含 cppcheck 输出
output_text: "<从 coordinator_listen 输出中截取的前 2000 字>" // 必须带上,用于降级关键词检测
} }
]) ])
}) })
@@ -81,6 +101,7 @@ Worker/Reviewer 完成后,从 coordinator_listen 的输出中提取:
- statuscompleted / failed - statuscompleted / failed
- 是否包含 cppcheck 输出 - 是否包含 cppcheck 输出
- worker_typeworker / reviewer - worker_typeworker / reviewer
- **output_text**:从 coordinator_listen 返回的 output 中截取前 2000 字必须带上coordinator_tick 用它做降级关键词检测和审查报告质量检查)
将这些信息格式化为 JSON 数组,传给下一次 coordinator_tick。 将这些信息格式化为 JSON 数组,传给下一次 coordinator_tick。
@@ -94,8 +115,9 @@ Worker/Reviewer 完成后,从 coordinator_listen 的输出中提取:
| Worker 完成 + 无 cppcheck | 保持 running | 重新派发 Worker 补跑 cppcheck | | Worker 完成 + 无 cppcheck | 保持 running | 重新派发 Worker 补跑 cppcheck |
| Worker 失败 + retry_budget 未耗尽 | running → pending | 派发 Debuggerdebug 模式) | | Worker 失败 + retry_budget 未耗尽 | running → pending | 派发 Debuggerdebug 模式) |
| Worker 失败 + retry_budget 耗尽 | running → blocked | 标记阻塞,等待人工介入 | | Worker 失败 + retry_budget 耗尽 | running → blocked | 标记阻塞,等待人工介入 |
| Reviewer 通过 | pending_review → completed | — | | Reviewer 通过(审查报告必须含逐行对照表) | pending_review → completed | — |
| Reviewer 不通过(前 2 次) | pending_review → pending | 重新派发 Worker | | Reviewer 不通过(前 2 次) | pending_review → pending | 重新派发 Worker |
| Reviewer 审查报告缺少逐行对照表 | — | 审查无效,重新派发 Reviewer |
| Reviewer 连续 2 次不通过 | pending_review → blocked | 标记阻塞 | | Reviewer 连续 2 次不通过 | pending_review → blocked | 标记阻塞 |
| Phase 所有任务完成 | — | 派发 Architect 里程碑审查 | | Phase 所有任务完成 | — | 派发 Architect 里程碑审查 |
@@ -103,10 +125,19 @@ Worker/Reviewer 完成后,从 coordinator_listen 的输出中提取:
- **资源不足**:多个任务同时就绪但资源有限 → 决定优先级 - **资源不足**:多个任务同时就绪但资源有限 → 决定优先级
- **retry_budget 耗尽**coordinator_tick 标记 blocked → 评估是否继续或上报 - **retry_budget 耗尽**coordinator_tick 标记 blocked → 评估是否继续或上报
- **需求变更**:用户或 Main Agent 通知需求变化 → 需要人工重新规划 - **需求变更**:用户或 Main Agent 通知需求变化 → 需要人工重新规划。Architect 更新 plan.md 后必须继续执行,不可因同一理由重复退回 Architect。若 tick 的 Architect 派发预算耗尽(每阶段 2 次 / 全局 5 次),则标记 blocked 上报 Main Agent。
- **未匹配状态转换**coordinator_tick 返回异常 → 分析情况并决策 - **未匹配状态转换**coordinator_tick 返回异常 → 分析情况并决策
- **所有任务完成**:汇总结果返回 Main Agent - **所有任务完成**:汇总结果返回 Main Agent
**Architect 派发硬门(不可违反):**
- 每个 Phase 的里程碑审查最多 2 次,所有 Phase 的 Architect 派发总计最多 5 次
- coordinator_tick 已内置此门控per-phase count + global count + milestone_satisfied 标记),你无需自行计数
- Architect 返回(无论 PASS/FAIL后 tick 自动标记 `milestone_satisfied = true`,此后不再为此 phase 派发里程碑审查
- 如 Architect 返回 FAIL 需重审:先手动派发 Worker 修回,将 phase 最后一个任务设回 pending完成任务后 tick 会重新触发(此时 `milestone_satisfied` 仍为 true → 不会触发)
- 要触发第 2 次里程碑审查,需先手动清空 `task-graph.json` 中对应 phase 的 `milestone_satisfied: true`
- 超过限制后 tick 不再返回 milestone_review 行动,你不得手动派发 Architect 绕过
- 如果里程碑审查不通过且 budget 耗尽 → 标记对应 phase 最后一个任务为 blocked上报 Main Agent
## 防卡死 ## 防卡死
- coordinator_tick 每次调用自动更新 `last_activity` 时间戳 - coordinator_tick 每次调用自动更新 `last_activity` 时间戳

View File

@@ -30,6 +30,27 @@
- 不做超出验收标准的修改 - 不做超出验收标准的修改
- 不做提前抽象或无关重构 - 不做提前抽象或无关重构
### 降级禁止(不可违反)
**禁止以任何理由使用降级方案代替设计方案:**
以下措辞及其英文等效表述一旦出现在输出中,审查器将直接判定为 FAIL
- 「先这样」「先跑通」「以后再改」「以后补上」「先回退」「先硬编码」「暂时绕过」「兜底方案」「先跳过」「临时方案」
- 「for now」「just do this」「temporary solution」「get it working first」「fix later」「change later」「refactor later」「add later」「implement later」「TODO」「rollback first」「revert first」「hardcode first」「hard-code for now」「skip for now」「bypass temporarily」「workaround」「fallback solution」「backup approach」「skip it for now」「interim approach」「stopgap」
**必须完全遵循设计方案:**
- 设计方案中规定的编译、测试、cppcheck 三步必须全部执行,不得跳过任一环节
- 禁止对环境变量、路径、配置做硬编码绕过设计流程
- 必须修改的文件必须全部修改,禁止以「后续再补」为由只做部分实现
- task spec 中的 acceptance_criteria 必须逐条满足,不得自行降低标准
### 正确完成声明
输出结构化结果时,状态字段必须符合以下规则:
- 只有全部验收标准满足 + 编译通过 + 测试通过 + cppcheck 无严重问题 = `completed`
- 存在未满足的验收标准但代码已修改 = `failed`(不是 completed
- 代码中使用了上述任何降级措辞 = `failed`,且在风险字段中说明降级内容
## DEBUG 模式 ## DEBUG 模式
当 task type 为 `debug` 时进入此模式。 当 task type 为 `debug` 时进入此模式。

View File

@@ -2,10 +2,12 @@ import { Config } from "@/config/config"
import { AppRuntime } from "@/effect/app-runtime" import { AppRuntime } from "@/effect/app-runtime"
import { Flag } from "@opencode-ai/core/flag/flag" import { Flag } from "@opencode-ai/core/flag/flag"
import { Installation } from "@/installation" import { Installation } from "@/installation"
import { InstallationVersion } from "@opencode-ai/core/installation/version" import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version"
import { GlobalBus } from "@/bus/global" import { GlobalBus } from "@/bus/global"
export async function upgrade() { export async function upgrade() {
// aircoding: never check upstream opencode for updates
if (InstallationChannel === "aircoding") return
const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.getGlobal())) const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.getGlobal()))
if (config.autoupdate === false || Flag.OPENCODE_DISABLE_AUTOUPDATE) return if (config.autoupdate === false || Flag.OPENCODE_DISABLE_AUTOUPDATE) return
const method = await Installation.method() const method = await Installation.method()

View File

@@ -178,6 +178,8 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProce
const result: Interface = { const result: Interface = {
info: Effect.fn("Installation.info")(function* () { info: Effect.fn("Installation.info")(function* () {
// aircoding: no upstream check
if (InstallationChannel === "aircoding") return { version: InstallationVersion, latest: InstallationVersion }
return { return {
version: InstallationVersion, version: InstallationVersion,
latest: yield* result.latest(), latest: yield* result.latest(),
@@ -218,6 +220,8 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProce
return "unknown" as Method return "unknown" as Method
}), }),
latest: Effect.fn("Installation.latest")(function* (installMethod?: Method) { latest: Effect.fn("Installation.latest")(function* (installMethod?: Method) {
// aircoding: never check opencode upstream for updates
if (InstallationChannel === "aircoding") return InstallationVersion
const detectedMethod = installMethod || (yield* result.method()) const detectedMethod = installMethod || (yield* result.method())
if (detectedMethod === "brew") { if (detectedMethod === "brew") {

View File

@@ -2,6 +2,99 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin"
import fs from "fs" import fs from "fs"
import path from "path" import path from "path"
// Strong signals: almost always indicate design downgrade
const FALLBACK_STRONG_CN = [
"先硬编码", "先跑通", "先回退", "先跳过", "暂时绕过",
]
const FALLBACK_STRONG_EN = [
"hardcode first", "hard-code for now", "get it working first",
"make it run first", "rollback first", "revert first",
"skip for now", "skip it for now", "bypass temporarily",
]
// Soft signals: only flag when near a completion claim
const FALLBACK_SOFT_CN = [
"先这样", "以后再改", "以后补上", "兜底方案", "临时方案",
]
const FALLBACK_SOFT_EN = [
"for now", "just do this", "temporary solution",
"fix later", "change later", "refactor later",
"add later", "implement later",
"workaround", "fallback solution", "backup approach",
"interim approach", "stopgap",
]
const COMPLETION_MARKERS = [
"编译通过", "测试通过", "任务完成", "状态.*completed",
"tests? pass", "build.*(?:pass|succeed|success)",
"cppcheck.*(?:通过|pass|clean|no.*issue)",
"(?:completed|finished|done)\\s*$",
"审查结论.*PASS", "无严重问题",
]
const TODO_FALLBACK_PATTERN = /TODO.*(?:以后|later|补|implement|fix|refactor|设计|design)/i
const SURFACE_EVIDENCE_PATTERNS = [
/(?:tests?\s*(?:all\s*)?(?:pass|green|passed))/i,
/(?:测试\s*(?:全绿|pass|通过))/,
/(?:function\s*exists|函数存在)/i,
/(?:file\s*exists|文件存在)/i,
/(?:typecheck|build|lint)\s*(?:passed|通过)/i,
/(?:looks?\s*(?:correct|right|fine)|看起来?正确|应该.*对)/i,
/(?:compilation\s*succeeded|编译成功)/i,
]
function detectStrongFallback(text: string): string[] {
const lower = text.toLowerCase()
const found: string[] = []
for (const kw of FALLBACK_STRONG_CN) if (text.includes(kw)) found.push(kw)
for (const kw of FALLBACK_STRONG_EN) if (lower.includes(kw)) found.push(kw)
return found
}
function detectContextualFallback(text: string): string[] {
const lower = text.toLowerCase()
const hits: string[] = []
const hasCompletion = COMPLETION_MARKERS.some((m) => new RegExp(m, "i").test(text))
if (!hasCompletion) return []
for (const kw of FALLBACK_SOFT_CN) if (text.includes(kw)) hits.push(kw)
for (const kw of FALLBACK_SOFT_EN) if (lower.includes(kw)) hits.push(kw)
return hits.filter((kw) => {
const kwIdx = lower.indexOf(kw)
if (kwIdx === -1) return false
return COMPLETION_MARKERS.some((m) => {
const match = new RegExp(m, "i").exec(text)
if (!match) return false
return Math.abs(kwIdx - match.index) < 300
})
})
}
function detectFallbackKeywords(text: string): string[] {
const strong = detectStrongFallback(text)
const contextual = detectContextualFallback(text)
const found = [...strong, ...contextual]
if (TODO_FALLBACK_PATTERN.test(text)) found.push("TODO (降级标记)")
return found
}
function hasCodeToDesignTable(text: string): boolean {
return (
text.includes("逐行对照") ||
text.includes("对照表") ||
text.includes("匹配状态") ||
/code.to.design.*table/i.test(text) ||
/design.*point.*code.*location/i.test(text) ||
/设计要点.*代码位置/i.test(text)
)
}
function detectSurfaceEvidenceOnlyPass(text: string): boolean {
const hasPassConclusion = /(?:PASS|审查结论.*PASS|通过.*审查)/i.test(text)
if (!hasPassConclusion) return false
const hasDesignComparison = hasCodeToDesignTable(text)
if (hasDesignComparison) return false
const surfaceCount = SURFACE_EVIDENCE_PATTERNS.filter((p) => p.test(text)).length
return surfaceCount >= 1
}
export async function AirCodingPlugin(_input: PluginInput): Promise<Hooks> { export async function AirCodingPlugin(_input: PluginInput): Promise<Hooks> {
return { return {
"tool.execute.before": async (input, output) => { "tool.execute.before": async (input, output) => {
@@ -52,6 +145,42 @@ export async function AirCodingPlugin(_input: PluginInput): Promise<Hooks> {
"cppcheck 输出应包含 'Checking <file>' 或 'no issues found' 或错误/警告统计。" + "cppcheck 输出应包含 'Checking <file>' 或 'no issues found' 或错误/警告统计。" +
"请重新派发 Worker 并要求运行 cppcheck --enable=all。" "请重新派发 Worker 并要求运行 cppcheck --enable=all。"
} }
// Defense layer: detect fallback keywords in worker output
const fallbackHits = detectFallbackKeywords(resultText)
if (fallbackHits.length > 0) {
output.output =
resultText +
"\n\n🚫 [AirCoding 降级检测] Worker 输出中检测到降级关键词:" +
fallbackHits.slice(0, 5).join("、") +
"。以降级方案代替设计实现不可接受。" +
"请移除所有临时实现、硬编码、兜底方案,按设计方案完整实现。"
}
}
if (subagentType === "reviewer") {
const resultText = output.output ?? ""
const hasTable = hasCodeToDesignTable(resultText)
const surfaceOnly = detectSurfaceEvidenceOnlyPass(resultText)
const issues: string[] = []
if (!hasTable) {
issues.push(
"审查报告缺少「Code-to-Design 逐行对照表」。" +
"必须包含表格格式的逐行对照:| # | 设计要点 | 代码位置 | 匹配状态 | 说明 |"
)
}
if (surfaceOnly) {
issues.push(
"审查报告仅凭表面证据测试pass/函数存在/build通过判定 PASS。不成立。" +
"必须逐行对照设计文档后才可判定。"
)
}
if (issues.length > 0) {
output.output =
resultText +
"\n\n🚫 [AirCoding 审查质量检查] 审查报告不合格:\n" +
issues.map((i) => "- " + i).join("\n") +
"\n\n请重新审查按三层流程逐行对照 → 静态审查 → 测试验证)产出完整审查报告。"
}
} }
if (subagentType === "architect") { if (subagentType === "architect") {

View File

@@ -246,7 +246,8 @@ interface TaskGraphTask {
interface TaskGraph { interface TaskGraph {
version?: number version?: number
tasks: TaskGraphTask[] tasks: TaskGraphTask[]
phases?: Array<{ id: number; name: string; milestone_review?: boolean }> phases?: Array<{ id: number; name: string; milestone_review?: boolean; milestone_review_count?: number; milestone_satisfied?: boolean }>
architect_dispatch_total?: number
} }
interface TickResult { interface TickResult {
@@ -254,6 +255,7 @@ interface TickResult {
worker_type: string worker_type: string
status: string status: string
has_cppcheck?: boolean has_cppcheck?: boolean
output_text?: string
} }
function readTaskGraph(projectDir: string): TaskGraph | null { function readTaskGraph(projectDir: string): TaskGraph | null {
@@ -279,6 +281,114 @@ function getDependencyIds(task: TaskGraphTask): string[] {
return task.dependencies.map((d) => (typeof d === "string" ? d : d.task_id)) return task.dependencies.map((d) => (typeof d === "string" ? d : d.task_id))
} }
// --- Code gates: fallback keyword detection ---
// Strong signals: almost always indicate design downgrade, flag unconditionally
const FALLBACK_STRONG_CN = [
"先硬编码", "先跑通", "先回退", "先跳过", "暂时绕过",
]
const FALLBACK_STRONG_EN = [
"hardcode first", "hard-code for now", "get it working first",
"make it run first", "rollback first", "revert first",
"skip for now", "skip it for now", "bypass temporarily",
]
// Soft signals: ambiguous in isolation, only flag when accompanied by a completion claim
const FALLBACK_SOFT_CN = [
"先这样", "以后再改", "以后补上", "兜底方案", "临时方案",
]
const FALLBACK_SOFT_EN = [
"for now", "just do this", "temporary solution",
"fix later", "change later", "refactor later",
"add later", "implement later",
"workaround", "fallback solution", "backup approach",
"interim approach", "stopgap",
]
// Completion markers: a Worker claiming "done" while using fallback language = hard violation
const COMPLETION_MARKERS = [
"编译通过", "测试通过", "任务完成", "状态.*completed",
"tests? pass", "build.*(?:pass|succeed|success)",
"cppcheck.*(?:通过|pass|clean|no.*issue)",
"(?:completed|finished|done)\\s*$",
"审查结论.*PASS", "无严重问题",
]
function detectStrongFallback(text: string): string[] {
const lower = text.toLowerCase()
const found: string[] = []
for (const kw of FALLBACK_STRONG_CN) if (text.includes(kw)) found.push(kw)
for (const kw of FALLBACK_STRONG_EN) if (lower.includes(kw)) found.push(kw)
return found
}
function detectContextualFallback(text: string): string[] {
const lower = text.toLowerCase()
const hits: string[] = []
// Check if text contains any completion marker
const hasCompletion = COMPLETION_MARKERS.some((m) => new RegExp(m, "i").test(text))
if (!hasCompletion) return [] // no completion claim → not a downgrade attempt
// With completion markers present, soft keywords become actionable
for (const kw of FALLBACK_SOFT_CN) if (text.includes(kw)) hits.push(kw)
for (const kw of FALLBACK_SOFT_EN) if (lower.includes(kw)) hits.push(kw)
// Proximity check: soft keyword must be within ~300 chars of a completion marker
return hits.filter((kw) => {
const kwIdx = lower.indexOf(kw)
if (kwIdx === -1) return false
return COMPLETION_MARKERS.some((m) => {
const match = new RegExp(m, "i").exec(text)
if (!match) return false
return Math.abs(kwIdx - match.index) < 300
})
})
}
// TODO only flagged as fallback when it defers design-level implementation
const TODO_FALLBACK_PATTERN = /TODO.*(?:以后|later|补|implement|fix|refactor|设计|design)/i
function detectFallbackKeywords(text: string): string[] {
const strong = detectStrongFallback(text)
const contextual = detectContextualFallback(text)
const found = [...strong, ...contextual]
if (TODO_FALLBACK_PATTERN.test(text)) found.push("TODO (降级标记)")
return found
}
function hasCodeToDesignTable(text: string): boolean {
return (
text.includes("逐行对照") ||
text.includes("对照表") ||
text.includes("匹配状态") ||
/code.to.design.*table/i.test(text) ||
/design.*point.*code.*location/i.test(text) ||
/设计要点.*代码位置/i.test(text)
)
}
const SURFACE_EVIDENCE_PATTERNS = [
/(?:tests?\s*(?:all\s*)?(?:pass|green|passed))/i,
/(?:测试\s*(?:全绿|pass|通过))/,
/(?:function\s*exists|函数存在)/i,
/(?:file\s*exists|文件存在)/i,
/(?:typecheck|build|lint)\s*(?:passed|通过)/i,
/(?:looks?\s*(?:correct|right|fine)|看起来?正确|应该.*对)/i,
/(?:compilation\s*succeeded|编译成功)/i,
]
function detectSurfaceEvidenceOnlyPass(text: string): boolean {
const hasPassConclusion = /(?:PASS|审查结论.*PASS|通过.*审查)/i.test(text)
const hasDesignComparison = hasCodeToDesignTable(text)
// PASS without design comparison = surface evidence only
if (hasPassConclusion && !hasDesignComparison) return true
// Check if the ONLY reasoning given is surface evidence
const surfaceCount = SURFACE_EVIDENCE_PATTERNS.filter((p) => p.test(text)).length
return hasPassConclusion && surfaceCount >= 2 && !hasDesignComparison
}
function buildWorkerPrompt(task: TaskGraphTask): string { function buildWorkerPrompt(task: TaskGraphTask): string {
const lines: string[] = [] const lines: string[] = []
lines.push(`## 任务: ${task.title}`) lines.push(`## 任务: ${task.title}`)
@@ -320,21 +430,34 @@ function buildReviewPrompt(task: TaskGraphTask, workerResult: string): string {
return [ return [
`## Code-to-Design 审查: ${task.title}`, `## Code-to-Design 审查: ${task.title}`,
"", "",
"请对照 .air/shared/plan/plan.md 中的架构设计,审查以下 Worker 实现:", "请严格按照三层流程审查 Worker 实现(逐行对照 → 静态审查 → 测试验证)。",
"审查报告必须包含全部六个章节,缺少任一章节 = 无效审查,将被退回重审。",
"", "",
"### Worker 结果", "### Worker 结果",
workerResult.slice(0, 3000), workerResult.slice(0, 3000),
"", "",
"### 审查要点", "### 审查输出要求(必须完整)",
"- 实现是否符合架构设计中的模块职责划分",
"- 依赖方向是否违反架构约束",
"- 公共接口是否与 plan.md 中声明的一致",
"- 是否有越界修改(修改了不应修改的模块)",
"", "",
"### 输出格式", "一、Code-to-Design 逐行对照表(表格格式,每个设计要点一行)",
" | # | 设计要点(来源) | 代码位置(文件:行号) | 匹配状态 | 说明 |",
" 匹配状态用:✅匹配 / ⚠️偏差 / ❌遗漏 / 🚫越界",
"",
"二、逐项审查(架构一致性、依赖方向、接口一致性、越界检查、功能完整性、降级关键词检测)",
"",
"三、静态审查(安全性:注入/越权/输入校验;正确性:边界/并发/资源;合规性:架构铁律/降级实现)",
"",
"四、测试/构建验证(仅在前三层全 PASS 后才填入)",
"",
"五、问题列表FAIL 时,格式:[严重程度] 问题描述 — 文件:行号 — 设计依据)",
"",
"六、修复建议FAIL 时,引用 plan.md 对应设计要点)",
"",
"### 禁止的判定依据",
"禁止以「测试pass」「函数存在」「文件存在」「build通过」「typecheck通过」「看起来正确」作为 PASS 理由。",
"必须逐行对照设计文档后才可判定。",
"",
"### 审查结论",
"审查结论: PASS / FAIL", "审查结论: PASS / FAIL",
"问题列表: (如有)",
"修复建议: (如有)",
].join("\n") ].join("\n")
} }
@@ -354,6 +477,46 @@ function buildDebugPrompt(task: TaskGraphTask, errorInfo: string): string {
].join("\n") ].join("\n")
} }
function buildAntiFallbackPrompt(task: TaskGraphTask, keywords: string[]): string {
return [
`## 退回重做(降级实现): ${task.title}`,
`类型: debug`,
"",
`### 检测到的降级关键词: ${keywords.join(", ")}`,
"",
"输出中包含以降级措辞描述的简化实现。以下行为不可接受:",
"- 以「先这样」「先跑通」「以后再改」「以后补上」「先回退」「先硬编码」「暂时绕过」「兜底方案」「临时方案」等理由使用简化实现",
'- 以 "for now" "temporary solution" "fix later" "workaround" "fallback" 等理由跳过设计',
"",
"### 要求",
"1. 移除所有以降级措辞描述的简化实现、硬编码、占位符",
"2. 严格按照 .air/shared/plan/plan.md 中的设计方案完整实现",
"3. 完整实现后重新编译 + 测试 + cppcheck --enable=all",
"4. 输出中不得再出现任何降级措辞",
`5. 本次是第 ${(task.retry_count ?? 0) + 1} 次重试,仍有 ${(task.constraints?.retry_budget ?? 3) - (task.retry_count ?? 0) - 1} 次机会`,
].join("\n")
}
function buildReReviewPrompt(task: TaskGraphTask, reasonMissingTable: boolean, reasonSurfaceOnly: boolean): string {
const issues: string[] = []
if (reasonMissingTable) issues.push("- 审查报告缺少「逐行对照表」code-to-design table必须补充")
if (reasonSurfaceOnly) issues.push("- 审查报告仅凭表面证据测试pass/函数存在/build通过判定 PASS不成立")
return [
`## 重审(审查报告不合格): ${task.title}`,
"",
"上一次审查报告被调度器拒绝,原因:",
...issues,
"",
"### 必须满足的要求",
"1. 必须生成完整的「一、Code-to-Design 逐行对照表」(表格格式,每个设计要点一行)",
" 表头: | # | 设计要点(来源) | 代码位置(文件:行号) | 匹配状态 | 说明 |",
" 匹配状态: ✅匹配 / ⚠️偏差 / ❌遗漏 / 🚫越界",
"2. 禁止以测试通过/函数存在/编译通过等表面证据作为 PASS 理由",
"3. 按三层流程执行:逐行对照 → 静态审查(安全/正确/合规)→ 测试验证",
"4. 审查报告必须包含全部六个章节(一至六)",
].join("\n")
}
const TickParams = Schema.Struct({ const TickParams = Schema.Struct({
results: Schema.optional(Schema.String).annotate({ results: Schema.optional(Schema.String).annotate({
description: description:
@@ -409,7 +572,42 @@ export const CoordinatorTickTool = Tool.define(
transitions++ transitions++
// Handle architect milestone_review results
if (result.worker_type === "architect") {
const phaseMatch = result.task_id.match(/^phase-(\d+)$/)
if (phaseMatch && graph.phases) {
const phaseId = parseInt(phaseMatch[1], 10)
const phaseObj = graph.phases.find((p) => p.id === phaseId)
if (phaseObj) {
phaseObj.milestone_satisfied = true
}
}
continue
}
if (result.worker_type === "worker" && result.status === "completed") { if (result.worker_type === "worker" && result.status === "completed") {
// Gate A: detect fallback keywords in worker output
if (result.output_text) {
const fallbackHits = detectFallbackKeywords(result.output_text)
if (fallbackHits.length > 0) {
const retryCount = task.retry_count ?? 0
const budget = task.constraints?.retry_budget ?? 3
if (retryCount < budget) {
task.retry_count = retryCount + 1
task.status = "pending"
actions.push({
action: "dispatch_debugger",
task_id: task.id,
subagent_type: "worker",
prompt: buildAntiFallbackPrompt(task, fallbackHits),
description: `退回重做(降级关键词: ${fallbackHits.slice(0, 3).join(", ")}`,
})
} else {
task.status = "blocked"
}
continue
}
}
if (!result.has_cppcheck) { if (!result.has_cppcheck) {
task.status = "running" task.status = "running"
actions.push({ actions.push({
@@ -449,9 +647,42 @@ export const CoordinatorTickTool = Tool.define(
task.status = "blocked" task.status = "blocked"
} }
} else if (result.worker_type === "reviewer" && result.status === "completed") { } else if (result.worker_type === "reviewer" && result.status === "completed") {
// Gate B: verify reviewer output contains code-to-design table
if (result.output_text) {
const hasTable = hasCodeToDesignTable(result.output_text)
const surfaceOnly = detectSurfaceEvidenceOnlyPass(result.output_text)
if (!hasTable || surfaceOnly) {
task.status = "pending_review"
actions.push({
action: "dispatch_reviewer",
task_id: task.id,
subagent_type: "reviewer",
prompt: buildReReviewPrompt(task, !hasTable, surfaceOnly),
description: `重审(${!hasTable ? "缺少逐行对照表" : "仅凭表面证据判定"}`,
})
continue
}
}
task.status = "completed" task.status = "completed"
} else if (result.worker_type === "reviewer" && result.status === "failed") { } else if (result.worker_type === "reviewer" && result.status === "failed") {
// If reviewer report lacks code-to-design table, re-dispatch reviewer
const retryCount = task.retry_count ?? 0 const retryCount = task.retry_count ?? 0
if (result.output_text && !hasCodeToDesignTable(result.output_text)) {
if (retryCount < 2) {
task.retry_count = retryCount + 1
task.status = "pending_review"
actions.push({
action: "dispatch_reviewer",
task_id: task.id,
subagent_type: "reviewer",
prompt: buildReReviewPrompt(task, true, false),
description: `重审(审查报告缺少逐行对照表)`,
})
} else {
task.status = "blocked"
}
continue
}
if (retryCount < 2) { if (retryCount < 2) {
task.retry_count = retryCount + 1 task.retry_count = retryCount + 1
task.status = "pending" task.status = "pending"
@@ -485,16 +716,23 @@ export const CoordinatorTickTool = Tool.define(
}) })
} }
// Phase 3: Check phase milestones // Phase 3: Check phase milestones (2 retries per phase, 5 total across all phases)
let milestonePhase: number | undefined let milestonePhase: number | undefined
if (graph.phases) { if (graph.phases) {
const totalArchitectDispatch = graph.architect_dispatch_total ?? 0
for (const phase of graph.phases) { for (const phase of graph.phases) {
if (!phase.milestone_review) continue if (!phase.milestone_review) continue
if (phase.milestone_satisfied) continue // architect already reviewed, OK
const phaseDispatchCount = phase.milestone_review_count ?? 0
if (phaseDispatchCount >= 2) continue // per-phase retry budget exhausted
if (totalArchitectDispatch >= 5) continue // global budget exhausted
const phaseTasks = graph.tasks.filter((t) => t.phase === phase.id) const phaseTasks = graph.tasks.filter((t) => t.phase === phase.id)
const allDone = phaseTasks.every((t) => t.status === "completed") const allDone = phaseTasks.every((t) => t.status === "completed")
const anyRunning = phaseTasks.some((t) => t.status === "running" || t.status === "pending_review") const anyRunning = phaseTasks.some((t) => t.status === "running" || t.status === "pending_review")
if (allDone && phaseTasks.length > 0) { if (allDone && phaseTasks.length > 0) {
milestonePhase = phase.id milestonePhase = phase.id
phase.milestone_review_count = phaseDispatchCount + 1 // pre-increment on dispatch
graph.architect_dispatch_total = totalArchitectDispatch + 1
actions.push({ actions.push({
action: "milestone_review", action: "milestone_review",
task_id: `phase-${phase.id}`, task_id: `phase-${phase.id}`,
@@ -539,6 +777,10 @@ export const CoordinatorTickTool = Tool.define(
lines.push(`- 运行中: ${runningCount}`) lines.push(`- 运行中: ${runningCount}`)
lines.push(`- 待调度: ${pendingCount}`) lines.push(`- 待调度: ${pendingCount}`)
lines.push(`- 阻塞: ${blockedCount}`) lines.push(`- 阻塞: ${blockedCount}`)
if (graph.phases?.some((p) => p.milestone_review)) {
const totalUsed = graph.architect_dispatch_total ?? 0
lines.push(`- Architect 派发预算: 已用 ${totalUsed}/5每阶段最多 2 次)`)
}
lines.push("") lines.push("")
if (actions.length > 0) { if (actions.length > 0) {