1
0

docs: 优化审查提示词,禁止 subagent 读取文件,明确 apply 阶段不动主规范

config.yaml: subagent 限定为计算密集/多步骤任务,文件读取用 Read 工具
prompt-proposal-review.md: 收集阶段加入读取约束和分步策略,复核补全待澄清清单
prompt-apply-review.md: 禁止同步主规范,新增 Spec 覆盖完整性扫描与补充流程
This commit is contained in:
2026-05-20 17:08:12 +08:00
parent 60a54b483f
commit f3df3a203b
10 changed files with 161 additions and 71 deletions

View File

@@ -4,6 +4,7 @@
- 先审查再修复;未经用户确认,不修改代码或变更文档 - 先审查再修复;未经用户确认,不修改代码或变更文档
- 默认按 `spec-driven` workflow 审查;识别 change 后先确认 `schemaName`;若实际 schema 不同,说明差异,仅对实际存在的 artifacts 做审查 - 默认按 `spec-driven` workflow 审查;识别 change 后先确认 `schemaName`;若实际 schema 不同,说明差异,仅对实际存在的 artifacts 做审查
- 禁止同步或修改 `openspec/specs/` 下的主规范——主规范同步属于 archive 阶段,不在此提示词范围内;本次 change 目录下的 `specs/*.md`、代码、测试、README 等均可修改
- 优先使用当前会话中的实现说明、测试结论、手动修补记录和已生成的变更文档;仅在无法明确 change、`schemaName`、改动范围或修补来源时,再用提问工具或 OpenSpec 命令补充定位 - 优先使用当前会话中的实现说明、测试结论、手动修补记录和已生成的变更文档;仅在无法明确 change、`schemaName`、改动范围或修补来源时,再用提问工具或 OpenSpec 命令补充定位
- 不要因为代码已经存在就自动以代码为准;先判断差异属于"文档要求未实现"、"测试后新增修补"还是"意外偏离/回归" - 不要因为代码已经存在就自动以代码为准;先判断差异属于"文档要求未实现"、"测试后新增修补"还是"意外偏离/回归"
- 每批代码或文档修改执行前用提问工具获得用户确认 - 每批代码或文档修改执行前用提问工具获得用户确认
@@ -12,14 +13,27 @@
## 1. 收集 ## 1. 收集
并行读取: 读取约束
- 本次 change 的实际 artifacts`spec-driven` 下通常包括 `proposal.md``design.md``tasks.md``specs/*.md` - 直接使用 Read 工具并行读取文件,禁止使用 subagent/Task 工具做文件读取和内容转发
- 当前会话中与本次变更相关的实现说明、apply 过程中的偏离、测试失败、手动修补原因、待确认事项 - 不原样输出文件内容,仅在步骤 2 输出审查结论
- 与本次变更相关的代码和测试文件;优先依据 `git diff --name-only``git diff --name-only --cached``tasks.md`、Impact、失败测试栈定位若工作区已干净再结合文档和代码模块反推
- 最近一次相关测试命令、测试结果、失败信息和修补后的验证结果 分步收集:
a) 先并行读取核心入口和配置,确定范围:
- 本次 change 的 `proposal.md`
- `openspec/config.yaml` - `openspec/config.yaml`
- 与本次改动相关的 README、架构文档以及现有 `openspec/specs/**/spec.md` 中与本次变更相关的规范,相关性来源包括:`proposal.md``Capabilities` / `Modified Capabilities`、手动修补涉及的受影响能力、`design.md` / Impact 中提到的模块、相关代码对应的现有能力
b) 从 `proposal.md` 提取 `Capabilities` / `Modified Capabilities`,确定 proposal 已声明的 spec 列表
c) 并行获取改动范围:`git diff --name-only``git diff --name-only --cached`;若工作区已干净,再结合文档和代码模块反推
d) 对比 git diff 涉及的模块与 proposal 已声明的 spec识别 apply 阶段新增改动触及但 proposal 未覆盖的现有 spec合并为完整 spec 读取列表;相关性来源还包括:手动修补涉及的受影响能力、`design.md` Impact 中提到的模块、相关代码对应的现有能力
e) 并行读取完整 spec 列表和其余 artifacts`design.md``tasks.md`、相关源码、测试文件、README、架构文档
f) 收集当前会话中与本次变更相关的实现说明、apply 过程中的偏离、测试失败、手动修补原因、待确认事项
若当前上下文无法明确 change 或文档路径: 若当前上下文无法明确 change 或文档路径:
@@ -35,11 +49,12 @@
按以下优先级检查: 按以下优先级检查:
| 优先级 | 维度 | 检查点 | | 优先级 | 维度 | 检查点 |
| ------ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| P0 | 实际实现与测试结论 | 当前代码的真实行为是什么apply 后是否有手动改动或测试后修补;测试是否证明这些实现有效;若缺少测试结果,标记相关结论为"未验证";检查是否存在回归、未覆盖场景或被掩盖的问题 | | P0 | 实际实现与测试结论 | 当前代码的真实行为是什么apply 后是否有手动改动或测试后修补;测试是否证明这些实现有效;若缺少测试结果,标记相关结论为"未验证";检查是否存在回归、未覆盖场景或被掩盖的问题 |
| P1 | 文档同步性 | 对实际存在的 artifacts 检查已落地的实现、测试后新增修补、边界处理、异常路径、验证结论是否已同步回变更文档若影响模块结构、API、实体或用户可见行为再检查 README 是否同步 | | P1 | 文档同步性 | 对本次 change 目录下实际存在的 artifacts 检查已落地的实现、测试后新增修补、边界处理、异常路径、验证结论是否已同步回变更文档若影响模块结构、API、实体或用户可见行为再检查 README 是否同步 |
| P2 | 文档要求覆盖 | 对实际存在的 artifacts 检查文档中承诺的目标、方案、Requirement、Scenario 是否都已实现;在 `spec-driven` 下重点检查 `proposal.md``design.md``specs/*.md``tasks.md` | | P2 | Spec 覆盖完整性 | 对实际代码改动范围与 proposal 中定义的 `Capabilities` / `Modified Capabilities`,识别 apply 阶段新增的功能是否触及了更多现有 spec若触及列入补充清单在本次 change 的 specs 中新增对应的 spec 文件,并更新 `proposal.md``Modified Capabilities` |
| P3 | 实现质量 | 代码结构、复用、命名、复杂度、错误处理、测试质量、与项目现有模式的一致性是否存在明显问题或可优化点 | | P3 | 文档要求覆盖 | 对实际存在的 artifacts 检查文档中承诺的目标、方案、Requirement、Scenario 是否都已实现;在 `spec-driven` 下重点检查 `proposal.md``design.md``specs/*.md``tasks.md` |
| P4 | 实现质量 | 代码结构、复用、命名、复杂度、错误处理、测试质量、与项目现有模式的一致性是否存在明显问题或可优化点 |
分析时区分三类差异: 分析时区分三类差异:
@@ -58,7 +73,7 @@
- 文档要求但未落地的功能、场景、异常处理或验证步骤 - 文档要求但未落地的功能、场景、异常处理或验证步骤
- apply 完成后新增的代码修补、边界处理、接口调整、行为变化未同步到文档 - apply 完成后新增的代码修补、边界处理、接口调整、行为变化未同步到文档
- `tasks.md` 标记完成,但代码、测试或文档未闭环 - `tasks.md` 标记完成,但代码、测试或文档未闭环
- `Modified Capabilities` 应更新但未更新的现有 spec - `Modified Capabilities` 在本次 change 的 specs 中是否已更新(注意:仅更新 change 目录下的 specs不动 `openspec/specs/`apply 阶段新增功能触及的未覆盖 spec 是否已补充到本次 change 的 `specs/` 目录
- 代码存在明显的重复、复杂度过高、命名不清、错误处理薄弱、测试质量不足等问题 - 代码存在明显的重复、复杂度过高、命名不清、错误处理薄弱、测试质量不足等问题
输出审查结果: 输出审查结果:
@@ -68,10 +83,11 @@
3. **未覆盖清单**:文档要求但未在代码中实现或未充分验证的内容 3. **未覆盖清单**:文档要求但未在代码中实现或未充分验证的内容
4. **需回写文档清单**:代码和测试中已确认、但文档未体现的实现、修补或约束变化 4. **需回写文档清单**:代码和测试中已确认、但文档未体现的实现、修补或约束变化
5. **方向待确认清单**:代码与文档不一致,且无法判断应以哪边为准的事项 5. **方向待确认清单**:代码与文档不一致,且无法判断应以哪边为准的事项
6. **任务状态问题清单**未真正完成、状态错误或需补充的新任务 6. **Spec 补充清单**apply 阶段新增功能触及但 proposal 未覆盖的现有 spec列出需新增的 spec 文件名、对应的主 spec 路径、需描述的变更内容
7. **测试问题清单**缺失覆盖、掩盖错误、验证不足或修补后未回归验证的测试问题 7. **任务状态问题清单**未真正完成、状态错误或需补充的新任务
8. **代码质量/优化清单**可优化的实现问题和建议 8. **测试问题清单**缺失覆盖、掩盖错误、验证不足或修补后未回归验证的测试问题
9. **逐项分析**:每个问题说明位置、问题、影响、建议和建议修复方向 9. **代码质量/优化清单**:可优化的实现问题和建议
10. **逐项分析**:每个问题说明位置、问题、影响、建议和建议修复方向
若所有清单均为空,输出"审查通过,未发现问题",跳至步骤 5。 若所有清单均为空,输出"审查通过,未发现问题",跳至步骤 5。
@@ -82,7 +98,8 @@
再整理完整修复方案,按类别列出: 再整理完整修复方案,按类别列出:
- 代码或测试补充:补实现、补异常处理、补回归测试、修复掩盖错误的测试 - 代码或测试补充:补实现、补异常处理、补回归测试、修复掩盖错误的测试
- 文档回写:同步 `proposal.md``design.md``tasks.md``specs/*.md`、README 中遗漏或过时的内容 - 文档回写:同步本次 change 目录下的 `proposal.md``design.md``tasks.md``specs/*.md`、README 中遗漏或过时的内容(禁止同步到 `openspec/specs/`
- Spec 补充:将 apply 阶段新增功能触及的现有 spec 复制到本次 change 的 `specs/` 目录并更新,同步更新 `proposal.md``Modified Capabilities`
- 任务状态修正:修正已完成/未完成状态,补充 apply 后新增但已完成的修补任务或验证任务 - 任务状态修正:修正已完成/未完成状态,补充 apply 后新增但已完成的修补任务或验证任务
- 代码质量优化:在不改变目标行为的前提下优化结构、复用、命名或可维护性 - 代码质量优化:在不改变目标行为的前提下优化结构、复用、命名或可维护性
@@ -112,13 +129,16 @@
若修改了文档: 若修改了文档:
- 确认实际存在的变更文档之间保持一致;在 `spec-driven`重点检查 `proposal.md``design.md``tasks.md``specs/*.md` - 确认本次 change 目录下的变更文档之间保持一致;重点检查 `proposal.md``design.md``tasks.md``specs/*.md`
- 若 apply 后新增修补改变了能力边界或行为约束,同步更新 `Capabilities` / `Modified Capabilities` - 若 apply 后新增修补改变了能力边界或行为约束,同步更新本次 change 的 `Capabilities` / `Modified Capabilities`
- 若"Spec 补充清单"中有需新增的 spec先从 `openspec/specs/` 复制对应的原 spec 到本次 change 的 `specs/` 目录,再基于实际改动更新其内容;禁止修改 `openspec/specs/` 下的原文件
- 禁止将本次 change 的 specs 同步到 `openspec/specs/`,该操作属于 archive 阶段
执行后重新读取所有被修改的代码、测试和文档,并复核: 执行后重新读取所有被修改的代码、测试和文档,并复核:
- "未覆盖清单" 是否已清空或已标注保留原因 - "未覆盖清单" 是否已清空或已标注保留原因
- "需回写文档清单" 是否已清空 - "需回写文档清单" 是否已清空
- "Spec 补充清单" 是否已清空或已标注保留原因
- "方向待确认清单" 是否已清空或已记录用户决策 - "方向待确认清单" 是否已清空或已记录用户决策
- "任务状态问题清单" 和 "测试问题清单" 是否已清空或已标注残留原因 - "任务状态问题清单" 和 "测试问题清单" 是否已清空或已标注残留原因
- "代码质量/优化清单" 中哪些已处理,哪些有意延期 - "代码质量/优化清单" 中哪些已处理,哪些有意延期

View File

@@ -10,13 +10,21 @@
## 1. 收集 ## 1. 收集
并行读取: 读取约束
- 本次 change 的实际 artifacts`spec-driven` 下通常包括 `proposal.md``design.md``tasks.md``specs/*.md` - 直接使用 Read 工具并行读取文件,禁止使用 subagent/Task 工具做文件读取和内容转发
- 当前会话中与本次变更相关的讨论、澄清、边界约束、非目标、待确认事项 - 不原样输出文件内容,仅在步骤 2 输出审查结论
- 与本次变更直接相关的源码、测试、README、架构文档
分步收集:
a) 先并行读取核心入口和配置,确定范围:
- 本次 change 的 `proposal.md`
- `openspec/config.yaml` - `openspec/config.yaml`
- 现有 `openspec/specs/**/spec.md` 中与本次变更相关的规范,相关性来源包括:`proposal.md``Capabilities` / `Modified Capabilities`、讨论中提到的受影响能力、`design.md` / Impact 中提到的模块、相关代码对应的现有能力
b) 从 `proposal.md` 提取 `Capabilities` / `Modified Capabilities`,确定需要读取的 spec 列表;相关性来源还包括:讨论中提到的受影响能力、`design.md` Impact 中提到的模块、相关代码对应的现有能力
c) 并行读取已确定的 spec 和其余 artifacts`design.md``tasks.md`、相关源码、测试、README、架构文档
若当前上下文无法明确 change 或文档路径: 若当前上下文无法明确 change 或文档路径:
@@ -48,8 +56,7 @@
- 讨论中已确定但文档未记录的内容 - 讨论中已确定但文档未记录的内容
- 文档基于错误现状做出的设计或任务拆分 - 文档基于错误现状做出的设计或任务拆分
- 文档之间相互冲突的目标、方案、约束、任务 - 文档之间相互冲突的目标、方案、约束、任务
- `proposal -> specs -> design -> tasks` 链路中的断点 - `Modified Capabilities` 在本次 change 的 specs 中是否已更新(注意:仅更新 change 目录下的 specs不动 `openspec/specs/`
- `Modified Capabilities` 应更新但未更新的现有 spec
输出审查结果: 输出审查结果:
@@ -90,7 +97,9 @@
- "讨论遗漏清单" 是否已清空或已标注保留原因 - "讨论遗漏清单" 是否已清空或已标注保留原因
- "现实性问题清单" 是否已清空或已标注为预期变更 - "现实性问题清单" 是否已清空或已标注为预期变更
- "文档冲突清单" 和 "OpenSpec 规范问题清单" 是否已清空 - "文档冲突清单" 是否已清空
- "OpenSpec 规范问题清单" 是否已清空
- "待澄清清单" 是否已清空或已记录用户决策
## 5. 收尾 ## 5. 收尾

View File

@@ -15,7 +15,7 @@ context: |
- 前端严禁组件内联style属性、CSS覆盖TD内部类名、使用!important、硬编码色值 - 前端严禁组件内联style属性、CSS覆盖TD内部类名、使用!important、硬编码色值
- Git提交: 仅中文; 格式"类型: 简短描述", 类型: feat/fix/refactor/docs/style/test/chore; 多行描述空行后写详细说明 - Git提交: 仅中文; 格式"类型: 简短描述", 类型: feat/fix/refactor/docs/style/test/chore; 多行描述空行后写详细说明
- 禁止创建git操作task - 禁止创建git操作task
- 积极使用subagents精心设计并行任务,节省上下文空间,加速任务执行 - 使用subagents处理计算密集或多步骤的并行任务如代码实现、测试执行文件读取直接使用Read工具并行调用禁止用subagent转发文件内容
- 优先使用提问工具对用户进行提问 - 优先使用提问工具对用户进行提问
- (当前项目未上线,不需要考虑向前兼容) - (当前项目未上线,不需要考虑向前兼容)

View File

@@ -0,0 +1,29 @@
# Test Output Cleanliness
## Purpose
确保测试运行时输出干净、无噪音,便于开发者快速定位问题。
## Requirements
### Requirement: 测试不应产生无关 console 输出
测试运行时,由测试用例预期的容错行为(如 JSON 解析失败、checker rejected触发的 `console.warn` 输出 SHALL 在测试代码中被抑制,不污染测试报告。
#### Scenario: 容错测试抑制 console.warn
- **WHEN** 测试用例故意注入损坏数据或触发异常以验证系统容错行为
- **THEN** 测试 SHALL 在执行前临时覆盖 `console.warn` 为空函数,在 finally 块中恢复原始函数
#### Scenario: 非预期 console.warn 不被抑制
- **WHEN** 测试用例并非专门测试容错行为
- **THEN** 测试 SHALL NOT 抑制 `console.warn`,确保意外 warn 可被观测
### Requirement: 探针执行失败日志输出单行消息
ProbeEngine 在捕获 checker rejected 时,`console.warn` SHALL 输出单行错误消息文本MUST NOT 输出 Error 对象(会导致多行堆栈噪音)。
#### Scenario: checker rejected 输出单行日志
- **WHEN** checker 执行抛出未捕获异常Promise rejected
- **THEN** `console.warn` SHALL 输出格式为 `探针执行失败: <message>` 的单行文本,其中 message 使用 `formatReason()` 提取
#### Scenario: formatReason 复用
- **WHEN** 构建失败日志消息和写入 CheckFailure
- **THEN** 两者 SHALL 共用同一个 `formatReason` 函数提取错误消息

View File

@@ -20,7 +20,7 @@
- **THEN** `assets/` 目录下的 JS 和 CSS 文件名 SHALL 包含 content hash`index-a1b2c3.js` - **THEN** `assets/` 目录下的 JS 和 CSS 文件名 SHALL 包含 content hash`index-a1b2c3.js`
### Requirement: Code Splitting 策略 ### Requirement: Code Splitting 策略
系统 SHALL 配置 Vite 的 Rolldown code splitting将 vendor 库分离为独立 chunks。 系统 SHALL 配置 Vite 的 Rolldown code splitting将 vendor 库分离为独立 chunks,并通过 `React.lazy()` 动态导入实现按需加载
#### Scenario: React 相关库分离 #### Scenario: React 相关库分离
- **WHEN** Vite 构建完成 - **WHEN** Vite 构建完成
@@ -34,8 +34,16 @@
- **WHEN** Vite 构建完成 - **WHEN** Vite 构建完成
- **THEN** `recharts``d3-*` 相关模块 SHALL 被打包到名为 `vendor-chart` 的独立 chunk - **THEN** `recharts``d3-*` 相关模块 SHALL 被打包到名为 `vendor-chart` 的独立 chunk
#### Scenario: TargetDetailDrawer 延迟加载
- **WHEN** Vite 构建完成
- **THEN** `TargetDetailDrawer` 及其依赖recharts、D3、DateRangePicker 等SHALL 通过 `React.lazy()` 动态导入,被 Rolldown 自动拆分为异步 chunk不包含在初始加载的 JS 中
#### Scenario: Drawer 首次渲染无闪烁
- **WHEN** 用户首次点击目标触发 Drawer 渲染
- **THEN** Drawer SHALL 通过 `<Suspense fallback={null}>` 包裹,利用其默认 visible=false 状态避免加载期间的视觉闪烁
### Requirement: CSS 处理 ### Requirement: CSS 处理
系统 SHALL 通过 Vite 处理 CSS 导入,产出独立的 CSS 文件。 系统 SHALL 通过 Vite 处理 CSS 导入,产出独立的 CSS 文件。TDesign 组件样式 SHALL 保持全量导入方式。
#### Scenario: CSS 文件产出 #### Scenario: CSS 文件产出
- **WHEN** Vite 构建完成 - **WHEN** Vite 构建完成
@@ -44,3 +52,7 @@
#### Scenario: CSS 压缩 #### Scenario: CSS 压缩
- **WHEN** Vite 执行生产构建 - **WHEN** Vite 执行生产构建
- **THEN** 产出的 CSS 文件 SHALL 经过压缩处理 - **THEN** 产出的 CSS 文件 SHALL 经过压缩处理
#### Scenario: TDesign CSS 全量导入
- **WHEN** 前端入口文件初始化样式
- **THEN** 系统 SHALL 通过 `tdesign-react/dist/reset.css``tdesign-react/dist/tdesign.min.css` 全量导入 TDesign 组件样式

View File

@@ -70,7 +70,7 @@ export class ProbeEngine {
this.writeResult(result.value); this.writeResult(result.value);
} else { } else {
const target = targets[index]; const target = targets[index];
console.warn("探针执行失败:", result.reason); console.warn(`探针执行失败: ${formatReason(result.reason)}`);
if (!target) continue; if (!target) continue;
this.writeResult({ this.writeResult({
detail: null, detail: null,

View File

@@ -1,16 +1,19 @@
import type { SkeletonProps } from "tdesign-react"; import type { SkeletonProps } from "tdesign-react";
import { useState } from "react"; import { lazy, Suspense, useState } from "react";
import { Alert, Layout, Menu, RadioGroup, Skeleton } from "tdesign-react"; import { Alert, Layout, Menu, RadioGroup, Skeleton } from "tdesign-react";
import { RefreshCountdown } from "./components/RefreshCountdown"; import { RefreshCountdown } from "./components/RefreshCountdown";
import { SummaryCards } from "./components/SummaryCards"; import { SummaryCards } from "./components/SummaryCards";
import { TargetBoard } from "./components/TargetBoard"; import { TargetBoard } from "./components/TargetBoard";
import { TargetDetailDrawer } from "./components/TargetDetailDrawer";
import { useDashboard } from "./hooks/use-queries"; import { useDashboard } from "./hooks/use-queries";
import { useTargetDetail } from "./hooks/use-target-detail"; import { useTargetDetail } from "./hooks/use-target-detail";
import { type ThemePreference, useThemePreference } from "./hooks/use-theme-preference"; import { type ThemePreference, useThemePreference } from "./hooks/use-theme-preference";
const TargetDetailDrawer = lazy(() =>
import("./components/TargetDetailDrawer").then((m) => ({ default: m.TargetDetailDrawer })),
);
const { Content, Header } = Layout; const { Content, Header } = Layout;
const DEFAULT_REFRESH_INTERVAL_MS = 30000; const DEFAULT_REFRESH_INTERVAL_MS = 30000;
const DASHBOARD_SKELETON_ROW_COL: SkeletonProps["rowCol"] = [ const DASHBOARD_SKELETON_ROW_COL: SkeletonProps["rowCol"] = [
@@ -122,6 +125,7 @@ export function App() {
)} )}
</div> </div>
</Content> </Content>
<Suspense fallback={null}>
<TargetDetailDrawer <TargetDetailDrawer
activeTab={activeTab} activeTab={activeTab}
historyData={historyData} historyData={historyData}
@@ -136,6 +140,7 @@ export function App() {
timeFrom={timeFrom} timeFrom={timeFrom}
timeTo={timeTo} timeTo={timeTo}
/> />
</Suspense>
</Layout> </Layout>
); );
} }

View File

@@ -421,6 +421,9 @@ describe("API 路由", () => {
}); });
test("损坏的 failure JSON 返回 null 而不崩溃", async () => { test("损坏的 failure JSON 返回 null 而不崩溃", async () => {
const originalWarn = console.warn;
console.warn = () => undefined;
try {
const targets = store.getTargets(); const targets = store.getTargets();
const t1Id = targets[0]!.id; const t1Id = targets[0]!.id;
@@ -445,5 +448,8 @@ describe("API 路由", () => {
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(body.items).toHaveLength(1); expect(body.items).toHaveLength(1);
expect(body.items[0]!.failure).toBeNull(); expect(body.items[0]!.failure).toBeNull();
} finally {
console.warn = originalWarn;
}
}); });
}); });

View File

@@ -165,6 +165,8 @@ describe("ProbeEngine", () => {
return originalExecute(target, ctx); return originalExecute(target, ctx);
}; };
const originalWarn = console.warn;
console.warn = () => undefined;
try { try {
const rejectTarget = makeCommandTarget("reject-cmd"); const rejectTarget = makeCommandTarget("reject-cmd");
const goodTarget = makeCommandTarget("good-cmd"); const goodTarget = makeCommandTarget("good-cmd");
@@ -192,6 +194,7 @@ describe("ProbeEngine", () => {
expect(results[1]!["targetId"]).toBe("good-cmd"); expect(results[1]!["targetId"]).toBe("good-cmd");
expect(results[1]!["matched"]).toBe(true); expect(results[1]!["matched"]).toBe(true);
} finally { } finally {
console.warn = originalWarn;
checker.execute = originalExecute; checker.execute = originalExecute;
} }
}); });

View File

@@ -143,8 +143,14 @@ describe("mapCheckResult", () => {
}); });
test("损坏 observation JSON 返回 null observation", () => { test("损坏 observation JSON 返回 null observation", () => {
const originalWarn = console.warn;
console.warn = () => undefined;
try {
const result = mapCheckResult(makeRow({ observation: "{invalid json" }), "http"); const result = mapCheckResult(makeRow({ observation: "{invalid json" }), "http");
expect(result.detail).toBeNull(); expect(result.detail).toBeNull();
expect(result.observation).toBeNull(); expect(result.observation).toBeNull();
} finally {
console.warn = originalWarn;
}
}); });
}); });