Add runtime event logs and tighten timeline docs

This commit is contained in:
hy committed 2026-03-24 11:50:21 +08:00
1 parent 4022e82621
commit 23de9fb9aa
8 files changed
+1270 -21

No files matched your search

+12 -11
View File
@@ -2,18 +2,19 @@
## 1. 文档引用
- 当涉及技术栈、整体架构、进程边界、本地优先约束或混合检索链路时,请阅读 `doc/architecture.md`
- 当涉及 AI 总体架构、Director/Human 分工、复杂度路由、主流程或整体协作关系时,请阅读 `doc/ai-design/architecture.md`
- 当涉及 `director`/`human` 的实现规则、节点行为、状态转移、best-effort completion、`deliveryMode`、`watchHint`、`artifacts` 责任边界、最近决策历史、`task_handoff` / `context_note` 运行语义,或 `LangGraph`、`chatStructured` 等 AI 技术实现约束时,请阅读 `doc/ai-design/implementation-spec.md`
- 当涉及 `director`/`human` 状态字段、任务状态契约、`deliveryMarkdown` 交接结构、`contextEvidence` 提取规则、`fact/evidence` 引用约束、`artifacts` 契约、Director/Human 数据流转或流转图时,请阅读 `doc/ai-design/data-definition-and-flow.md`
- 当涉及 AI 未来重构规划、迭代方向、未决方案、下一次准备怎么改时,请阅读 `doc/ai-design/refactor-plan.md`
- 当涉及 `human` 的 skill 集合、skill 命名、skill 拆分边界、`skillCatalog` / `skillRegistry` 或 skill 与 tools 实现边界时,请阅读 `doc/ai-design/human-skill-design.md`
- 当涉及 AI 提示词设计准则、prompt 结构、标签组织、哪些字段允许保留结构化载荷、structured output 或提示词通用编排原则时,请阅读 `doc/ai-design/prompt-design.md`
- 当涉及 Core AI 测试方案、阶段顺序、各阶段 `basic / boundary / scenario / live` 用例规划、`human` skill 覆盖、backend 完整闭环、用例实现状态、`What / Why / How / Oracle / Not` 定义、正式测试代码与 `test-plan` 的对齐约束、`watchHint`/并行边界、模型使用或 backend / e2e 测试边界时,请先阅读 `doc/test-plan/ai-test.md`
- 当涉及数据库、LanceDB、表职责、存储分层或检索存储实现时,请阅读 `doc/database.md`
- 当涉及目录规范、文件归位、human 子目录归位、tools 按实现域归位、模块边界或工程结构时,请阅读 `doc/project-structure.md`
- 当涉及技术栈、整体架构、进程边界、本地优先或混合检索时,请阅读 `doc/architecture.md`
- 当涉及 AI 总体架构、Director/Human 分工或主流程时,请阅读 `doc/ai-design/architecture.md`
- 当涉及 AI 节点实现、状态转移、`deliveryMode`、`watchHint`、`task_handoff`、`context_note`、`artifacts` 或 `LangGraph` 运行约束时,请阅读 `doc/ai-design/implementation-spec.md`
- 当涉及 AI 状态字段、任务契约、`deliveryMarkdown`、`contextEvidence`、`fact/evidence` 约束或数据流转时,请阅读 `doc/ai-design/data-definition-and-flow.md`
- 当想看 AI 完整跑一遍的真实样例时,请阅读 `doc/ai-design/example.md`
- 当涉及 `human` 的 skill 集合、skill 命名、skill 边界或 skill 与 tools 的映射时,请阅读 `doc/ai-design/human-skill-design.md`
- 当涉及 AI prompt 设计、prompt 结构、标签组织或 structured output 编排时,请阅读 `doc/ai-design/prompt-design.md`
- 当涉及 AI 后续重构方向、迭代规划或未决方案时,请阅读 `doc/ai-design/refactor-plan.md`
- 当涉及 Core AI 测试方案、阶段划分、用例规划、`human` skill 覆盖、`watchHint`/并行边界、正式 case 与文档对齐或排查时何时继续查看 `runtime-events.jsonl` 时,请先阅读 `doc/test-plan/ai-test.md`
- 当涉及数据库、LanceDB、表职责或检索存储实现时,请阅读 `doc/database.md`
- 当涉及目录规范、模块边界、文件归位或工程结构时,请阅读 `doc/project-structure.md`
- 当涉及 UI 风格、布局规范或组件交互约束时,请阅读 `doc/ui-style.md`
- 当涉及测试相关内容,包括测试目录、raw log、report 规则、证据展示方式、正式测试代码准入约束或历史总数据 / 最新总数据口径时,请阅读 `doc/test.md`;
- 当涉及测试目录、raw log、`runtime-events.jsonl`、`timeline` 值域规则、report 规则、证据展示方式或测试代码准入约束时,请阅读 `doc/test.md`
## 2. 必须遵守的规则
### 代码相关
File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -105,11 +105,12 @@
- AI 编写测试代码时,必须优先对齐 `How + Oracle + Not`,不能只根据 `What` 自行脑补实现。
- `Oracle` 至少包含 2 条明确、可观察、可失败的断言,不设上限。
- `Oracle` 只定义 gating 的最低验收线,不限制测试在 `meta.json`、`timeline.jsonl`、`artifacts.json` 中暴露更多诊断事实。
- `Oracle` 只定义 gating 的最低验收线,不限制测试在 `meta.json`、`timeline.jsonl`、`runtime-events.jsonl`、`artifacts.json` 中暴露更多诊断事实。
- 测试实现必须把 `Oracle` 落成稳定断言、状态检查或产物校验,不能写成“结果正确”“与描述一致”这类套话。
- 若 `How` 已明确分支、模式或运行前提,测试实现必须按文档覆盖,不得擅自删减。
- 若 `Not` 已声明排除范围,测试实现不得把其他阶段职责塞进同一条 case。
- 若本文还没有定义对应正式 case,不得直接往 `tests/**` 非 `temp` 目录补测试代码。
- 若 `Oracle`、断言 `data`、`timeline.jsonl` 仍不足以解释失败原因,AI 必须继续读取 `runtime-events.jsonl`,不能只看摘要日志就下结论。
示例:
+32 -7
View File
@@ -5,9 +5,10 @@
- 测试体系收敛为两层产物:
- 原始日志:`tests/logs/**`
- 正式分析文档:`tests/report/<domain>/report.md`
- 原始日志统一由三类文件组成:
- 原始日志统一由四类文件组成:
- `meta.json`
- `timeline.jsonl`
- `runtime-events.jsonl`
- `artifacts.json`
- 测试样例只记录事实,不直接写失败原因分析、优化建议或能力判断。
@@ -177,6 +178,7 @@ runner 不负责:
tests/logs/<domain>/<layer>/<suite>/<groups...>/<caseId>/<runId>/
meta.json
timeline.jsonl
runtime-events.jsonl
artifacts.json
artifacts/
```
@@ -189,6 +191,9 @@ tests/logs/<domain>/<layer>/<suite>/<groups...>/<caseId>/<runId>/
- `timeline.jsonl`
- 单次 run 的事实事件流。
- 用来回答“本次 run 按顺序发生了什么”。
- `runtime-events.jsonl`
- 单次 run 的完整运行时原始事件流。
- 用来回答“底层 runtime 真实发出了哪些 `sendEvent` 事件”。
- `artifacts.json`
- 单次 run 的附件索引。
- 用来回答“本次 run 产生了哪些可回看的附件”。
@@ -226,7 +231,7 @@ tests/logs/<domain>/<layer>/<suite>/<groups...>/<caseId>/<runId>/
- `schemaVersion`
- 日志 schema 版本号。
- 固定值为 `3`。
- 固定值为 `4`。
- `runId`
- 本次执行唯一标识,对应目录名。
- `caseId`
@@ -281,11 +286,17 @@ tests/logs/<domain>/<layer>/<suite>/<groups...>/<caseId>/<runId>/
- 事件大类。
- 当前值域:
- `run`
- 只用于单次 case 的运行生命周期。
- `step`
- 只用于测试作者显式定义的语义步骤,不用于底层工具噪音。
- `assertion`
- 只用于 `Oracle` 或核心验收断言。
- `artifact`
- 只用于登记一个已经写出的附件。
- `signal`
- 只用于外部可观察信号,例如路由变化、IPC 调用、真实 runtime event、console。
- `error`
- 只用于 runner 或测试执行层面的未恢复错误。
- `name`
- 事件的稳定语义名,不是脚本名。
- 同类事实在不同 run、不同 case 中应尽量复用同一命名。
@@ -312,6 +323,18 @@ tests/logs/<domain>/<layer>/<suite>/<groups...>/<caseId>/<runId>/
- `signal` 用于记录可观察到的外部信号,例如路由变化、IPC 调用、AI 事件。
- 当失败事件存在明确证据时,优先把证据放进 `data` 或 artifact,不要在 `message` 中写大段解释。
- e2e 下不把所有 Playwright 底层调用逐条倾倒进 timeline;底层重放依赖 `trace.zip`,timeline 只保留语义 step / assertion / signal。
- `run` 之外的业务状态不要塞进 `status`。
- 例如 `blocked`、`completed`、`cancelled` 应保留在 `runtime-events.jsonl` 或写进 `data`,不要扩充 `timeline.status`。
### `runtime-events.jsonl`
- 文件内容是逐行 `AiRunEvent` JSON,不包额外 wrapper,不做 schema 投影。
- 范围固定为完整 `sendEvent` 原始事件流。
- 包括 tool、guard、scope 校验、审批、产物、runtime issue、AI 运行等所有通过 `sendEvent` 发出的事件。
- 不包含本地 harness 自己维护但未经过 `sendEvent` 的临时数组,例如某些工具测试里的 `activities[]`。
- `runtime-events.jsonl` 始终写出;如果本次 run 没有 runtime 事件,则写空文件。
- backend 与 e2e 的采集路径可以不同,但最终落盘契约必须一致。
- 当 `Oracle`、断言 `data`、`timeline.jsonl` 或 report 仍不足以解释失败原因时,必须继续回看 `runtime-events.jsonl`。
### `artifacts.json`
@@ -331,7 +354,7 @@ tests/logs/<domain>/<layer>/<suite>/<groups...>/<caseId>/<runId>/
- `schemaVersion`
- 附件索引 schema 版本号。
- 固定值为 `3`。
- 固定值为 `4`。
- `id`
- 单个附件的稳定标识。
- `kind`
@@ -358,11 +381,12 @@ tests/logs/<domain>/<layer>/<suite>/<groups...>/<caseId>/<runId>/
## 6. backend 与 e2e 的统一约束
- backend 和 e2e 共用同一份 `meta.json` / `timeline.jsonl` / `artifacts.json` 基础契约。
- backend 和 e2e 共用同一份 `meta.json` / `timeline.jsonl` / `runtime-events.jsonl` / `artifacts.json` 基础契约。
- backend 与 e2e 的差异只体现在:
- `meta.runtime.driver`
- `meta.runtime.target`
- `artifacts.json.items[].kind`
- `runtime-events.jsonl` 的采集路径
- `timeline.jsonl` 中出现的语义事件名与信号类型
### e2e 默认附件策略
@@ -431,7 +455,7 @@ tests/report/<domain>/
- `当前评估`
- `下一步建议`
- `证据` 默认只列出能定位该条证据的最小信息,例如 `caseId`、`runId`、`status`。
- 不要在 report 正文里机械展开 `meta.json`、`timeline.jsonl`、`artifacts.json` 这些文件名。
- 不要在 report 正文里机械展开 `meta.json`、`timeline.jsonl`、`runtime-events.jsonl`、`artifacts.json` 这些文件名。
- 只有当失败分析或结论明确依赖某个关键附件时,才额外补充附件名称或用途说明。
- `通过率` 只写当前节点覆盖范围内的统计,不混入其他节点数据。
- `通过率` 固定拆成两组:
@@ -550,8 +574,9 @@ tests/report/<domain>/
1. 运行目标测试命令。
2. 读取对应 domain 下的 raw logs。
3. 必要时回看失败 run 的 `meta.json`、`timeline.jsonl` 与 `artifacts.json`。
4. 更新 `tests/report/<domain>/report.md`。
3. 先看 `timeline.jsonl`;若信息仍不足,再回看 `runtime-events.jsonl` 与关键附件。
4. 必要时继续回看失败 run 的 `meta.json` 与 `artifacts.json`。
5. 更新 `tests/report/<domain>/report.md`。
## 9. 明确禁止
@@ -170,6 +170,7 @@ export const runBackendCase = async <TResult>(definition: TestCaseDefinition<TRe
})
throw error
} finally {
const runtimeEvents = eventCollector.snapshot()
recordEvent({
type: 'run',
name: 'end',
@@ -197,6 +198,7 @@ export const runBackendCase = async <TResult>(definition: TestCaseDefinition<TRe
await writeRunLog({
meta,
events,
runtimeEvents,
artifacts
})
+1 -1
View File
@@ -1,6 +1,6 @@
import type { JsonValue } from '../../../src/shared/types'
export const TEST_LOG_SCHEMA_VERSION = 3 as const
export const TEST_LOG_SCHEMA_VERSION = 4 as const
export type TestDomain = 'ai' | 'kb-search' | 'temp'
export type TestLayer = 'backend' | 'e2e'
+4 -1
View File
@@ -1,5 +1,6 @@
import { mkdir, writeFile } from 'node:fs/promises'
import path from 'node:path'
import type { AiRunEvent } from '../../../src/shared/types'
import {
TEST_LOG_SCHEMA_VERSION,
type TestArtifactsIndex,
@@ -52,7 +53,7 @@ export const resolveRunArtifactsDir = (input: {
runId: string
}): string => path.join(resolveRunLogDir(input), 'artifacts')
const toJsonLines = (events: readonly TestLogEvent[]): string =>
const toJsonLines = <T>(events: readonly T[]): string =>
events.map((event) => JSON.stringify(event)).join('\n')
const toArtifactsIndex = (items: readonly TestArtifact[]): TestArtifactsIndex => ({
@@ -63,6 +64,7 @@ const toArtifactsIndex = (items: readonly TestArtifact[]): TestArtifactsIndex =>
export const writeRunLog = async (input: {
meta: TestRunMeta
events: readonly TestLogEvent[]
runtimeEvents: readonly AiRunEvent[]
artifacts: readonly TestArtifact[]
}): Promise<string> => {
const logDir = resolveRunLogDir({
@@ -73,6 +75,7 @@ export const writeRunLog = async (input: {
await mkdir(logDir, { recursive: true })
await writeFile(path.join(logDir, 'meta.json'), JSON.stringify(input.meta, null, 2), 'utf8')
await writeFile(path.join(logDir, 'timeline.jsonl'), toJsonLines(input.events), 'utf8')
await writeFile(path.join(logDir, 'runtime-events.jsonl'), toJsonLines(input.runtimeEvents), 'utf8')
await writeFile(
path.join(logDir, 'artifacts.json'),
JSON.stringify(toArtifactsIndex(input.artifacts), null, 2),
+2
View File
@@ -371,6 +371,7 @@ export const runE2ECase = async <TResult>(definition: E2ETestCaseDefinition<TRes
throw error
} finally {
const signals = await launchedApp.collectSignals()
const runtimeEvents = signals.aiRunEventEntries.map((entry) => entry.event)
for (const routeEntry of signals.routeEntries) {
pushEvent({
@@ -488,6 +489,7 @@ export const runE2ECase = async <TResult>(definition: E2ETestCaseDefinition<TRes
await writeRunLog({
meta,
events,
runtimeEvents,
artifacts
})
}