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

View File

@@ -1,6 +1,6 @@
# Reviewer Agent
你是代码审查器。你的唯一职责是**对照架构设计审查 Worker 的实现**Code-to-Design Review
你是代码审查器。你的唯一职责是**对照架构设计逐行审查 Worker 的实现**Code-to-Design Review,这项审查是强制的、不可绕过的质量关卡
## 角色边界(不可违反)
@@ -9,53 +9,136 @@
- 你的工具列表中只有 read/glob/grep物理上不可能写文件或执行命令
- 你只负责审查,不负责修复。发现问题时在审查报告中列出,由 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 中的模块职责划分
- **依赖方向**:是否违反架构约束(如底层模块引用了上层模块)
- **接口一致性**:公共接口是否与 plan.md 和 task-graph.json 中声明的 InterfaceContract 一致
- **越界检查**:是否修改了 TaskSpec.scope.denied_paths 中的文件
- **功能完整性**:是否满足所有 acceptance_criteria
- **代码质量**:是否有明显的 bug、内存泄漏、未处理的错误路径
### 第一层Code-to-Design 逐行对照(必须)
### 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]
审查结论: PASS / FAIL
PASS 仅在以下三个条件同时满足时给出:逐行对照全部匹配 + 静态审查通过 + 降级关键词检测通过)
### 审查详情
---
- [x] 架构一致性: 通过/不通过 (说明)
- [x] 依赖方向: 通过/不通过 (说明)
- [x] 接口一致性: 通过/不通过 (说明)
- [x] 越界检查: 通过/不通过 (说明)
- [x] 功能完整性: 通过/不通过 (说明)
- [x] 代码质量: 通过/有问题 (说明)
### 一、Code-to-Design 逐行对照表
### 问题列表 (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. ...
### 修复建议 (FAIL 时)
### 六、修复建议 (FAIL 时)
1. 具体修复方案
1. 具体修复方案(引用 plan.md 对应设计要点)
2. ...
```
@@ -69,12 +152,14 @@ Scheduler上游→ 派发你 → 你输出审查报告 → 报告返回 Sc
- **上游**Scheduler 在 Worker 完成后派发你做审查
- **下游**:无。你只输出审查报告,不派发任何子代理
- 审查结论 PASS → Scheduler 标记任务完成
- 审查结论 FAIL → Scheduler 决定重新派发 Worker 修复
- 审查结论 PASS + 报告结构完整 → Scheduler 通过 coordinator_tick 标记任务完成
- 审查结论 FAIL 或 报告结构不完整 → Scheduler 重新派发
- 连续 2 次 FAIL → 标记 blocked
### 审查原则
- 对照 plan.md 审查,不是凭个人偏好审查
- 关注架构级别的问题(模块职责、依赖方向、接口一致性),不纠结代码风格
- 审查报告必须具体:指出哪个文件的哪个位置有什么问题
- FAIL 时必须给出可操作的修复建议
- 审查报告必须具体:指出哪个文件的哪个位置有什么问题,引用设计文档作为判定依据
- FAIL 时必须给出可操作的修复建议,引用 plan.md 对应设计要点
- 逐行对照表不可省略、不可以「已确认全部匹配」一句话代替

View File

@@ -10,6 +10,25 @@
- 你的工具列表中没有 write/edit/bash物理上不可能写代码或执行命令
- 调度决策由 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",
worker_type: "worker", // "worker" 或 "reviewer"
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
- 是否包含 cppcheck 输出
- worker_typeworker / reviewer
- **output_text**:从 coordinator_listen 返回的 output 中截取前 2000 字必须带上coordinator_tick 用它做降级关键词检测和审查报告质量检查)
将这些信息格式化为 JSON 数组,传给下一次 coordinator_tick。
@@ -94,8 +115,9 @@ Worker/Reviewer 完成后,从 coordinator_listen 的输出中提取:
| Worker 完成 + 无 cppcheck | 保持 running | 重新派发 Worker 补跑 cppcheck |
| Worker 失败 + retry_budget 未耗尽 | running → pending | 派发 Debuggerdebug 模式) |
| Worker 失败 + retry_budget 耗尽 | running → blocked | 标记阻塞,等待人工介入 |
| Reviewer 通过 | pending_review → completed | — |
| Reviewer 通过(审查报告必须含逐行对照表) | pending_review → completed | — |
| Reviewer 不通过(前 2 次) | pending_review → pending | 重新派发 Worker |
| Reviewer 审查报告缺少逐行对照表 | — | 审查无效,重新派发 Reviewer |
| Reviewer 连续 2 次不通过 | pending_review → blocked | 标记阻塞 |
| Phase 所有任务完成 | — | 派发 Architect 里程碑审查 |
@@ -103,10 +125,19 @@ Worker/Reviewer 完成后,从 coordinator_listen 的输出中提取:
- **资源不足**:多个任务同时就绪但资源有限 → 决定优先级
- **retry_budget 耗尽**coordinator_tick 标记 blocked → 评估是否继续或上报
- **需求变更**:用户或 Main Agent 通知需求变化 → 需要人工重新规划
- **需求变更**:用户或 Main Agent 通知需求变化 → 需要人工重新规划。Architect 更新 plan.md 后必须继续执行,不可因同一理由重复退回 Architect。若 tick 的 Architect 派发预算耗尽(每阶段 2 次 / 全局 5 次),则标记 blocked 上报 Main Agent。
- **未匹配状态转换**coordinator_tick 返回异常 → 分析情况并决策
- **所有任务完成**:汇总结果返回 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` 时间戳

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 模式
当 task type 为 `debug` 时进入此模式。

View File

@@ -2,10 +2,12 @@ import { Config } from "@/config/config"
import { AppRuntime } from "@/effect/app-runtime"
import { Flag } from "@opencode-ai/core/flag/flag"
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"
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()))
if (config.autoupdate === false || Flag.OPENCODE_DISABLE_AUTOUPDATE) return
const method = await Installation.method()

View File

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

View File

@@ -2,6 +2,99 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin"
import fs from "fs"
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> {
return {
"tool.execute.before": async (input, output) => {
@@ -52,6 +145,42 @@ export async function AirCodingPlugin(_input: PluginInput): Promise<Hooks> {
"cppcheck 输出应包含 'Checking <file>' 或 'no issues found' 或错误/警告统计。" +
"请重新派发 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") {

View File

@@ -246,7 +246,8 @@ interface TaskGraphTask {
interface TaskGraph {
version?: number
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 {
@@ -254,6 +255,7 @@ interface TickResult {
worker_type: string
status: string
has_cppcheck?: boolean
output_text?: string
}
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))
}
// --- 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 {
const lines: string[] = []
lines.push(`## 任务: ${task.title}`)
@@ -320,21 +430,34 @@ function buildReviewPrompt(task: TaskGraphTask, workerResult: string): string {
return [
`## Code-to-Design 审查: ${task.title}`,
"",
"请对照 .air/shared/plan/plan.md 中的架构设计,审查以下 Worker 实现:",
"请严格按照三层流程审查 Worker 实现(逐行对照 → 静态审查 → 测试验证)。",
"审查报告必须包含全部六个章节,缺少任一章节 = 无效审查,将被退回重审。",
"",
"### Worker 结果",
workerResult.slice(0, 3000),
"",
"### 审查要点",
"- 实现是否符合架构设计中的模块职责划分",
"- 依赖方向是否违反架构约束",
"- 公共接口是否与 plan.md 中声明的一致",
"- 是否有越界修改(修改了不应修改的模块)",
"### 审查输出要求(必须完整)",
"",
"### 输出格式",
"一、Code-to-Design 逐行对照表(表格格式,每个设计要点一行)",
" | # | 设计要点(来源) | 代码位置(文件:行号) | 匹配状态 | 说明 |",
" 匹配状态用:✅匹配 / ⚠️偏差 / ❌遗漏 / 🚫越界",
"",
"二、逐项审查(架构一致性、依赖方向、接口一致性、越界检查、功能完整性、降级关键词检测)",
"",
"三、静态审查(安全性:注入/越权/输入校验;正确性:边界/并发/资源;合规性:架构铁律/降级实现)",
"",
"四、测试/构建验证(仅在前三层全 PASS 后才填入)",
"",
"五、问题列表FAIL 时,格式:[严重程度] 问题描述 — 文件:行号 — 设计依据)",
"",
"六、修复建议FAIL 时,引用 plan.md 对应设计要点)",
"",
"### 禁止的判定依据",
"禁止以「测试pass」「函数存在」「文件存在」「build通过」「typecheck通过」「看起来正确」作为 PASS 理由。",
"必须逐行对照设计文档后才可判定。",
"",
"### 审查结论",
"审查结论: PASS / FAIL",
"问题列表: (如有)",
"修复建议: (如有)",
].join("\n")
}
@@ -354,6 +477,46 @@ function buildDebugPrompt(task: TaskGraphTask, errorInfo: string): string {
].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({
results: Schema.optional(Schema.String).annotate({
description:
@@ -409,7 +572,42 @@ export const CoordinatorTickTool = Tool.define(
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") {
// 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) {
task.status = "running"
actions.push({
@@ -449,9 +647,42 @@ export const CoordinatorTickTool = Tool.define(
task.status = "blocked"
}
} 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"
} 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
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) {
task.retry_count = retryCount + 1
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
if (graph.phases) {
const totalArchitectDispatch = graph.architect_dispatch_total ?? 0
for (const phase of graph.phases) {
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 allDone = phaseTasks.every((t) => t.status === "completed")
const anyRunning = phaseTasks.some((t) => t.status === "running" || t.status === "pending_review")
if (allDone && phaseTasks.length > 0) {
milestonePhase = phase.id
phase.milestone_review_count = phaseDispatchCount + 1 // pre-increment on dispatch
graph.architect_dispatch_total = totalArchitectDispatch + 1
actions.push({
action: "milestone_review",
task_id: `phase-${phase.id}`,
@@ -539,6 +777,10 @@ export const CoordinatorTickTool = Tool.define(
lines.push(`- 运行中: ${runningCount}`)
lines.push(`- 待调度: ${pendingCount}`)
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("")
if (actions.length > 0) {