From 62b676597ccad32cd6fed93e6fb67b25e644cae5 Mon Sep 17 00:00:00 2001 From: AirCoding Date: Mon, 15 Jun 2026 11:54:57 +0800 Subject: [PATCH] Release AirCoding-Alpha-0.1.0 Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 53 ++++ .../opencode/src/agent/prompt/reviewer.txt | 147 ++++++++-- .../opencode/src/agent/prompt/scheduler.txt | 37 ++- packages/opencode/src/agent/prompt/worker.txt | 21 ++ packages/opencode/src/cli/upgrade.ts | 4 +- packages/opencode/src/installation/index.ts | 4 + packages/opencode/src/plugin/aircoding.ts | 129 +++++++++ packages/opencode/src/tool/coordinator.ts | 264 +++++++++++++++++- 8 files changed, 613 insertions(+), 46 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ae2db92 --- /dev/null +++ b/CLAUDE.md @@ -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 审查结果 + +- 审查未通过时,必须列出具体的不合规项(文件:行号、设计要点、偏差描述) +- 不合规项修复后重新提交审查,直到全部通过 +- 审查报告作为任务完成的前置证据 diff --git a/packages/opencode/src/agent/prompt/reviewer.txt b/packages/opencode/src/agent/prompt/reviewer.txt index 42bb25f..95269bc 100644 --- a/packages/opencode/src/agent/prompt/reviewer.txt +++ b/packages/opencode/src/agent/prompt/reviewer.txt @@ -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 对应设计要点 +- 逐行对照表不可省略、不可以「已确认全部匹配」一句话代替 diff --git a/packages/opencode/src/agent/prompt/scheduler.txt b/packages/opencode/src/agent/prompt/scheduler.txt index 96fd001..1b9e2f7 100644 --- a/packages/opencode/src/agent/prompt/scheduler.txt +++ b/packages/opencode/src/agent/prompt/scheduler.txt @@ -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 的输出中提取: - status(completed / failed) - 是否包含 cppcheck 输出 - worker_type(worker / 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 | 派发 Debugger(debug 模式) | | 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` 时间戳 diff --git a/packages/opencode/src/agent/prompt/worker.txt b/packages/opencode/src/agent/prompt/worker.txt index 6738166..f363d38 100644 --- a/packages/opencode/src/agent/prompt/worker.txt +++ b/packages/opencode/src/agent/prompt/worker.txt @@ -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` 时进入此模式。 diff --git a/packages/opencode/src/cli/upgrade.ts b/packages/opencode/src/cli/upgrade.ts index 62b230a..bbf7d27 100644 --- a/packages/opencode/src/cli/upgrade.ts +++ b/packages/opencode/src/cli/upgrade.ts @@ -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() diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 0ed10dc..74787b8 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -178,6 +178,8 @@ export const layer: Layer.Layer 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 { return { "tool.execute.before": async (input, output) => { @@ -52,6 +145,42 @@ export async function AirCodingPlugin(_input: PluginInput): Promise { "cppcheck 输出应包含 'Checking ' 或 '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") { diff --git a/packages/opencode/src/tool/coordinator.ts b/packages/opencode/src/tool/coordinator.ts index 06f5a16..d812eb9 100644 --- a/packages/opencode/src/tool/coordinator.ts +++ b/packages/opencode/src/tool/coordinator.ts @@ -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) {