# v0.6 Pivot：撤回 Chrome 域工具，stdlib nudge 集中到 `execute_sql` description（Google 风格）

## Context

v0.5 开发期（commit `c93466b`）ship 了 4 个预置 Chrome 域工具（`chrome_scroll_jank_summary` / `chrome_page_load_summary` / `chrome_main_thread_hotspots` / `chrome_startup_summary`）。v0.3/v0.4 观察样本（记忆 `project_v03_observation_sample*` + `project_v04_observation_sample3_*`）确认：`tools/list` 通道有行为激活力（`chrome_scroll_jank_summary` 被稳定调用），`instructions` 通道无行为激活力（agent 能背诵 stdlib 目录却仍退回 `LIKE '%x%'` 扫 `slice`）。

但深入评估后发现：这 4 个工具是**答案形**——SQL 固化、字段窄、`chrome_scroll_jank_summary` 还做了 `COUNT(*) GROUP BY` 预聚合——会让即便 Claude Opus 4.7 这种顶尖模型也止步于工具返回，**失去自己写 SQL 做更深分析的机会**。`chrome_main_thread_hotspots` 名字与 SQL 不一致（没过滤 main thread）且丢失 `ipc_hash` / `posted_from` 归因关键列，是最严重的带偏。

Google 的 `com.google.PerfettoMcp` 插件走相反路线：没有预置域工具，只暴露单个 `perfetto-execute-query`，把 stdlib 指引塞进**工具描述本身**（8 条子主题 URL + "worth fetching this fully" 强措辞 + `INCLUDE` 分离调用 caveat），走同一个行为激活通道但**不给答案只给路标**。本 pivot 照这条路走。

**更正 — v0.5.0 已发布**（codex review 发现）：仓库已有 annotated tag `v0.5.0` 指向 `c93466b`（Tagger: Rulong Chen, Date: Thu Apr 16 21:21:12 2026），`Cargo.toml` 和 `CHANGELOG.md` 当前状态是 0.5.0 已发布。4 个 chrome_* 工具是**已发布 MCP surface**，删除它们是**真 breaking change**。因此 pivot 承载在新版本：`Cargo.toml` bump `0.5.0` → `0.6.0`，v0.5.0 的 CHANGELOG 条目**保持原样**（已发布历史），新增 v0.6.0 条目说明撤回。原 plan 曾误判"v0.5 未 tag"是因只查了 `git log` 未查 `git tag -l`。

## 改动总览（单个 monolithic commit）

**Commit subject**：`breaking: pivot chrome domain tools into execute_sql description (v0.6.0)`

### 1. `src/server.rs`

#### 1.1 删代码
- L94-97 `ChromeTraceParams` struct
- L133-183 四个 `pub const CHROME_*_SQL` 常量
- L391-484 四个 `#[tool]` handler
- L540-555 `format_chrome_tool_error` 函数
- L766-817 三个相关单测：`chrome_scroll_jank_hint_fires_on_missing_table` / `chrome_scroll_jank_hint_fires_on_missing_module` / `chrome_scroll_jank_skips_unrelated_query_errors`

#### 1.2 改 `tool_router_exposes_expected_tools`（L866-890）
从 L877-888 的 `vec![]` 移除 4 条 `chrome_*` 条目，保留 6 项。按字母序：

```rust
vec![
    "execute_sql",
    "list_processes",
    "list_table_structure",
    "list_tables",
    "list_threads_in_process",
    "load_trace",
]
```

（注：`list_tables` 排在 `list_table_structure` 之后是因为 `_` (0x5F) < `s` (0x73) 的字节序。）

#### 1.3 保留 `STDLIB_INSTRUCTIONS`（L99-131），只更新 doc comment

**原 plan "砍成 3 行 scaffold"方案已取消**，理由（Review 新发现）：

- **面向未来**：v0.3/0.4 样本"instructions 无效"结论只对**当前 Claude Code agent 的 prompt 拼接方式**成立；换 agent 框架 / 模型升级 / MCP 客户端改变 instructions 注入路径，硬编码目录可能再次激活
- **足够信息**：instructions token 是 handshake 一次性成本，**已付出去**；删除不省钱只减信息
- **无害冗余**：v0.4 样本显示"agent 能背诵目录但不用"——**没有**多信号源干扰效应，双通道 = 纯收益覆盖面

**实际改动**：常量文本（L103-131）**保持不变**，只更新 L99-102 的 doc comment，显式标记"多通道冗余 by design"：

```rust
/// Server-level `instructions` shipped on MCP handshake. Lists curated
/// PerfettoSQL stdlib modules so agents stop hand-rolling `LIKE '%x%'` scans
/// on the raw `slice` table. Module names and their exposed public
/// tables/views are taken from the vendored Perfetto stdlib source.
///
/// The same stdlib guidance is also carried on the `execute_sql` tool
/// description (the `tools/list` channel v0.3/0.4 samples confirmed reaches
/// Claude Code agents). This multi-channel redundancy is by design:
/// instructions token cost is paid once at handshake, so future agent
/// frameworks or MCP clients that do route `instructions` into the system
/// prompt get the nudge for free.
pub const STDLIB_INSTRUCTIONS: &str = "…(L103-131 unchanged)…";
```

常量名 `STDLIB_INSTRUCTIONS` 保持不变（非 cosmetic rename）。

#### 1.4 保持 `instructions_list_core_stdlib_modules`（L838-863）不变

**原 plan "rename + 改断言"方案已取消**。因 1.3 保留 instructions 原文，该测试的 8 个模块名断言 + `INCLUDE PERFETTO MODULE` 断言**继续通过**，无需改。该测试本身是 v0.4 的防漂移网（stdlib 模块名与 vendored Perfetto 源码对齐），保留价值高。

#### 1.5 重写 `execute_sql` description（L216-233）

```rust
#[tool(
    name = "execute_sql",
    description = "Execute a PerfettoSQL query against a loaded trace. Returns a JSON \
                   array of row objects. Results are capped at 5000 rows and \
                   aggregates are strongly preferred over raw row data.\n\
                   \n\
                   The trace_path must reference a previously loaded trace.\n\
                   \n\
                   The PerfettoSQL stdlib is documented at \
                   https://perfetto.dev/docs/analysis/stdlib-docs — auto-generated \
                   from the stdlib SQL sources across 24 packages (chrome, android, \
                   sched, slices, linux, wattson, v8, ...), worth fetching fully \
                   (or per-package via anchors like #package-chrome, \
                   #package-android) to learn exact view columns and function \
                   signatures before composing queries. Stdlib modules already \
                   encode the correct JOIN shape for most common analyses, so \
                   reach for them before hand-rolling scans on `slice`.\n\
                   \n\
                   PerfettoSQL syntax: https://perfetto.dev/docs/analysis/perfetto-sql-syntax\n\
                   \n\
                   Subtopic references:\n\
                   - Jank and frame timing: https://perfetto.dev/docs/data-sources/frametimeline\n\
                   - CPU scheduling: https://perfetto.dev/docs/data-sources/cpu-scheduling\n\
                   - Memory counters: https://perfetto.dev/docs/data-sources/memory-counters\n\
                   - Battery counters: https://perfetto.dev/docs/data-sources/battery-counters\n\
                   - Android logs: https://perfetto.dev/docs/data-sources/android-log"
)]
```

**移除 v0.4 separate-call caveat 的理由**（codex review 发现）：当前 `src/server.rs:226-227` 仍含 `` `INCLUDE PERFETTO MODULE` must be in a separate execute_sql call `` 字样，但 `docs/google-perfetto-mcp-analysis.md:1017` 明确记录："Reverted. Empirical testing against trace_processor_shell v54.0 shows the combined form works..."，且 `tests/e2e_chrome_scroll_jank.rs:27` 通过单字符串 `INCLUDE ... ; SELECT ...` 经 HTTP RPC 跑通常驻 fixture。保留该 caveat 会主动反向引导 agent 拆成两次 tool call，与 `perfetto-execute-query` 的单调用设计背道而驰。新描述**删去该句**。

**测试副作用**：`execute_sql_description_matches_max_rows_constant`（L751-764）断言 `desc.contains(&MAX_ROWS.to_string())`，即 description 必须含字面量 `"5000"`。新描述的首句 "Results are capped at 5000 rows" 满足该断言，测试不改。若日后调整 `MAX_ROWS`，需同步改此描述字面量，或把断言改为引用常量拼接。

遵循项目记忆 `feedback_single_signal_for_llm_nudges.md`：5000 行上限的引导一句到位（"aggregates are strongly preferred over raw row data"），不加 "preview with LIMIT N first" 或 "fall back to raw slice scans if stdlib fails" 这种竞争性信号。不再内联 stdlib 模块名（v0.3/0.4 样本已验证在 instructions 通道无效，重新嵌入会 import 失败模式）。

**删除 prelude baseline-tables 段的理由**（codex review 发现）：早期草稿含一段 `The prelude package is auto-loaded ... use those directly when an explicit stdlib module doesn't cover the analysis` —— 这在紧跟 "stdlib-first, 不要手搓 slice" 主信号之后给出**raw-table fallback**，正好违反 `feedback_single_signal_for_llm_nudges.md` 规则（tool description 承载单一方向，不加 bail-out），且会把 agent 重新 nudge 回底层表扫描、削弱 pivot 的"stdlib-first"立论。prelude 本身是 stdlib auto-loaded package，主信号"stdlib-first"已经天然覆盖；agent 要知道哪些表直接可用，`list_tables` 工具返回的就是事实。描述不需要复述 prelude 表名。

### 2. 测试文件改动

#### 2.1 删除（Chrome 工具相关）

- `tests/e2e_chrome_domain_tools.rs`（103 行，3 个测试：page_load_summary / main_thread_hotspots / startup_summary）
- `tests/e2e_chrome_scroll_jank.rs`（49 行，1 个测试：scroll_jank_summary；L8 导入 `CHROME_SCROLL_JANK_SUMMARY_SQL`，常量删后文件无法编译）

#### 2.2 新增 `tests/e2e_stdlib_include.rs`（codex review 发现 — 填补 CI 缺口）

**动机**：2.1 删除两个测试后，整仓对 `INCLUDE PERFETTO MODULE X; SELECT FROM X_view` 这条**新描述推荐路径**的 CI 覆盖为零——`tests/e2e_smoke.rs` 只跑 process / PRAGMA / bigint / row-cap / missing-table，从不触碰 stdlib。pivot 把 stdlib INCLUDE 放进 description 作为首推路径，又同时删掉了唯一覆盖这条路径的 e2e 链路，会让"stdlib 首选"的主张失去回归网。

**两个测试用例**（一次 codex review 追加发现）：

- **`e2e_stdlib_include_basic_trace`**：在 `basic.perfetto-trace`（207 B）上跑 `slices.with_context` — 通用模块，验证单字符串 `INCLUDE + SELECT` 路径走通 spawn → HTTP RPC → decode，即"不需要分两次 call"这条主张成立。
- **`e2e_stdlib_include_chrome_scroll_jank`**：在 `scroll_jank.pftrace` 上跑 `chrome.scroll_jank.scroll_jank_v3; SELECT cause_of_jank, COUNT(*) FROM chrome_janky_frames GROUP BY cause_of_jank` — **这正是 `README.md:95` 展示给用户作为 pivot 示范查询的那条 SQL**。删除 `tests/e2e_chrome_scroll_jank.rs`（常量 `CHROME_SCROLL_JANK_SUMMARY_SQL` 要删掉）的同时，若不把 README 里这条 Chrome stdlib 路径重新覆盖回 CI，README 示例就悬空了（pivot 主张用户做的事情，仓库自己不再验证它能做成）。codex 本地跑过这条 SQL 在 `scroll_jank.pftrace` 上有结果，是真实可验证的 path。

**因此 Section 3 的 fixture 删除列表也相应调整**：保留 `scroll_jank.pftrace`，只删 `page_loads.pftrace`（page-loads fixture 没有对应的 README 示例依赖）。

**测试代码**（完整内容，写入新文件）：

```rust
// Copyright 2025 The perfetto-mcp-rs Authors
// SPDX-License-Identifier: Apache-2.0

//! e2e coverage for `INCLUDE PERFETTO MODULE X; SELECT FROM X_view`
//! in a single execute_sql call. The v0.6 pivot removes the old
//! "separate call" caveat from the `execute_sql` description; these
//! tests are the regression net that keep the combined form honest
//! against `trace_processor_shell` upstream behavior AND verify the
//! Chrome stdlib path the README recommends as the canonical
//! replacement for the removed domain tools.

use std::path::Path;

use perfetto_mcp_rs::tp_manager::TraceProcessorManager;

#[test]
fn e2e_stdlib_include_basic_trace() {
    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .expect("build tokio runtime");

    runtime.block_on(async {
        let manager = TraceProcessorManager::new_with_starting_port(1, 19_501);
        let trace = Path::new("tests/fixtures/basic.perfetto-trace");

        let client = manager.get_client(trace).await.expect("spawn tp_shell");

        let rows = client
            .query(
                "INCLUDE PERFETTO MODULE slices.with_context; \
                 SELECT COUNT(*) AS n FROM thread_slice",
            )
            .await
            .expect("stdlib INCLUDE + SELECT must succeed in a single call");

        assert_eq!(rows.len(), 1);
        assert!(rows[0]["n"].as_i64().is_some());
    });
}

/// Regression net built on the same SQL the README prints as the
/// migration path from the removed `chrome_scroll_jank_summary` tool.
/// SQL and assertions match at time of writing; if README is later
/// updated, this test should be updated in the same commit — they
/// are not shared via a constant, so drift is possible.
///
/// Assertion strength matches the old `tests/e2e_chrome_scroll_jank.rs`:
/// non-empty result set and `n` locked to i64 (COUNT(*) is always an
/// integer; f64 is not accepted). The `scroll_jank.pftrace` fixture
/// was captured specifically for scroll-jank analysis and is known to
/// produce rows (verified against `trace_processor_shell` v54.0). An
/// empty result is a real regression, not acceptable fixture variance.
#[test]
fn e2e_stdlib_include_chrome_scroll_jank() {
    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .expect("build tokio runtime");

    runtime.block_on(async {
        let manager = TraceProcessorManager::new_with_starting_port(1, 19_601);
        let trace = Path::new("tests/fixtures/scroll_jank.pftrace");

        let client = manager.get_client(trace).await.expect("spawn tp_shell");

        let rows = client
            .query(
                "INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3; \
                 SELECT cause_of_jank, COUNT(*) AS n \
                 FROM chrome_janky_frames \
                 GROUP BY cause_of_jank \
                 ORDER BY n DESC",
            )
            .await
            .expect("chrome stdlib INCLUDE + SELECT must succeed on scroll_jank.pftrace");

        assert!(
            !rows.is_empty(),
            "scroll_jank.pftrace must yield at least one chrome_janky_frames row — \
             empty means either the stdlib view schema broke or the fixture lost jank",
        );
        for row in &rows {
            assert!(
                row.get("cause_of_jank").is_some(),
                "row missing cause_of_jank column: {row}",
            );
            assert!(
                row["n"].as_i64().is_some(),
                "COUNT(*) must decode as i64, got: {row}",
            );
        }
    });
}
```

**为什么不破坏 non-goals #9**：原 non-goal 是"不新增集成测试"（理由：proxy 测试只验证文本非行为）。但 2.1 删除**带走了已存在的 Chrome stdlib INCLUDE 覆盖**（`tests/e2e_chrome_scroll_jank.rs`），同时**暴露出仓内一直缺少 generic stdlib INCLUDE 回归网**（`tests/e2e_smoke.rs` 从未覆盖 `INCLUDE PERFETTO MODULE` 路径）。两个新测试的性质分别是：**回填**被删走的 Chrome 覆盖 + **补齐**一直缺失的 generic 覆盖——均属"保维度/填缺口"，不属于"加新 proxy 测试"。因此 non-goal #9 同步删除（见本 plan 末尾）。

### 3. 删除 / 保留 fixture

**删除**：

- `tests/fixtures/page_loads.pftrace`（2.3 MB）—— 仅被已删 Chrome 域工具测试引用；README 无对应 example。

**保留**（codex review 调整 — 原计划删，现改保留）：

- `tests/fixtures/scroll_jank.pftrace`（6.1 MB）—— `README.md:95` Chrome stdlib 示例的真实 fixture；Section 2.2 新测 `e2e_stdlib_include_chrome_scroll_jank` 依赖它。删掉会让 README 里推荐给用户的 Chrome 迁移路径失去 CI 保护，违反"pivot 要说服用户用 stdlib"的主张。成本分析：6.1 MB git blob 是 one-shot（已在 history 里；再次 `git rm` 不会减小 history 大小，只会让 working tree 拉取快 6MB），收益是 **当前 README SQL 由 CI 覆盖**（README 改时须同 commit 更新测试，CI 不会自动检测漂移）——不划算删。

两份均为普通 git blob（无 LFS，无 `.gitattributes`），`git rm tests/fixtures/page_loads.pftrace` 即可。

**其它保留**：
- `basic.perfetto-trace`（207 B）——`tests/e2e_smoke.rs` + Section 2.2 generic stdlib 用例
- `event_latency.perfetto-trace`（705 B）、`histogram.perfetto-trace`（692 B）——当前无测试引用，但体积 < 1 KB 且 `docs/plans/m5-stdlib-quickref-resource.md:390` 列为模块 quickref 审计 fixture。有意保留，不随 pivot 删除（该 plan 重启时复用）

### 4. `CHANGELOG.md`

**v0.5.0 条目（L5-8）保持原样不动**——已发布历史，不能改写。

**在 L5 上方新增 v0.6.0 条目**，整体放在所有已发布条目之前：

```markdown
### [0.6.0](https://github.com/0xZOne/perfetto-mcp-rs/releases/tag/v0.6.0) (April 17, 2026)

- **Breaking**: remove the four pre-built Chrome domain tools shipped in v0.5.0 (`chrome_scroll_jank_summary`, `chrome_page_load_summary`, `chrome_main_thread_hotspots`, `chrome_startup_summary`) along with their SQL constants, the shared `ChromeTraceParams` struct, and the `format_chrome_tool_error` helper. The tools were answer-shaped wrappers whose fixed SQL and narrow column sets risked steering even top-tier LLMs away from the richer analyses they could do with their own SQL against the stdlib. `chrome_main_thread_hotspots` additionally dropped `ipc_hash` / `posted_from` attribution columns and did not actually filter to the main thread. Clients invoking these tool names will fail; re-express via `execute_sql` with an appropriate stdlib module (e.g. `chrome.scroll_jank.scroll_jank_v3`, `chrome.page_loads`, `chrome.tasks`, `chrome.startups`).
- Augment the `execute_sql` tool description with stdlib guidance following the Google `com.google.PerfettoMcp` plugin's single-tool pattern — subtopic URL list (stdlib-docs with anchor-based per-package fetch guidance, perfetto-sql-syntax, frametimeline, cpu-scheduling, memory-counters, battery-counters, android-log) and Google's "worth fetching fully" phrasing. The description carries the nudge on the `tools/list` channel v0.4 confirmed reaches agents. The v0.4-era "`INCLUDE PERFETTO MODULE` must be in a separate execute_sql call" warning is removed — empirical testing against `trace_processor_shell` v54.0 (recorded in `docs/google-perfetto-mcp-analysis.md`) shows the combined `INCLUDE X; SELECT FROM X_view` form works, and the prior caveat was actively steering agents toward a two-call pattern they do not need. The description also carries a single stdlib-first signal only — no raw-table fallback note (prelude is part of the stdlib and `list_tables` already tells the agent which tables are directly available).
- Server `instructions` retain the v0.4 curated stdlib module directory. v0.3/0.4 samples showed the `instructions` channel is not behavior-active for the current Claude Code agent, but the handshake token cost is already paid and the directory is a free hedge for future agent frameworks or MCP clients that do route `instructions` into their system prompt. Multi-channel redundancy is by design.
- New `tests/e2e_stdlib_include.rs` with two cases that replace the behavior coverage removed alongside the Chrome domain tools. The first exercises a single-call `INCLUDE PERFETTO MODULE slices.with_context; SELECT ... FROM thread_slice` against `basic.perfetto-trace` — the generic regression net for the description's "stdlib-first, single-call" posture against upstream `trace_processor_shell` changes. The second exercises exactly the `chrome.scroll_jank.scroll_jank_v3` → `chrome_janky_frames` SQL printed in `README.md` as the canonical replacement for the removed `chrome_scroll_jank_summary` tool, run against `tests/fixtures/scroll_jank.pftrace` (kept for this reason; the original Chrome fixture removal would have left the README example unverified). `tests/fixtures/page_loads.pftrace` is still removed — no README example depends on it.
```

v0.5.0 条目（L5-8）**不动**，v0.4.0 条目（L10-13）**不动**——已发布历史，均不改写。

### 5. `README.md` 和 `README.zh-CN.md`

- `README.md:79` 和 `README.zh-CN.md:78`：删除 Tools 表中的 `chrome_scroll_jank_summary` 行
- `README.md:87-101`（Example 块）和 `README.zh-CN.md` 镜像：**保留 as-is**。当前示例展示 `INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3; SELECT cause_of_jank, COUNT(*) ...` 正好演示 "description-guided agent 应当自己产出的查询"，强化 pivot 核心主张

### 6. `docs/roadmap.md` 和 `docs/roadmap.zh-CN.md`

#### 6.1 M5 Chrome Tools 章节头（`:136` 之后）加延后注释 + 重开约束

```markdown
### Chrome Tools

> **Deferred — strategy under review.** The pre-built Chrome domain-tool
> pattern was walked back in v0.6. v0.3/0.4 samples confirmed the
> `tools/list` channel drives behavior, so the pattern picked the right
> surface, but the resulting tools were answer-shaped (fixed SQL, narrow
> columns, pre-aggregated `GROUP BY`) and risked steering agents — even
> top-tier models — away from richer analyses they could do with their
> own SQL against the stdlib. The pivot consolidates the stdlib nudge
> into the `execute_sql` tool description (Google `com.google.PerfettoMcp`
> single-tool pattern).
>
> **If reopened, Chrome tooling must be navigation-shaped, not
> answer-shaped.** The shape spectrum runs:
>
> - **Answer-shaped** (rejected): fixed SQL + pre-aggregated output.
>   Example of what NOT to ship: `chrome_scroll_jank_summary` returning
>   `COUNT(*) GROUP BY cause_of_jank` — the agent loses per-jank
>   timestamps / thread / process views and cannot follow up.
> - **Navigation-shaped** (required form): returns discovery metadata
>   (module names, view names, column schemas) as structured JSON. The
>   agent then composes its own SQL against `execute_sql`. Example:
>   `list_chrome_views` returning
>   `{module: "chrome.scroll_jank.scroll_jank_v3",
>     views: [{name: "chrome_janky_frames",
>              columns: [{name: "cause_of_jank", type: "STRING"}, ...],
>              summary: "..."}]}`
>   (view name taken from the actual stdlib source — see
>   `README.md:95` and `src/server.rs` usage; do not invent names).
> - **Raw-material** (already shipped): `execute_sql` — the baseline.
>
> **What a navigation tool must add over `list_tables` +
> `list_table_structure` (already shipped).** The repo already exposes
> two generic discovery primitives — `list_tables` lists every table /
> view currently visible on the loaded trace, and `list_table_structure`
> returns its columns (`README.md:73, :81`). A navigation-shaped Chrome
> tool only justifies its weight if it delivers information those two
> cannot produce with a comparable round-trip:
>
> 1. **Pre-INCLUDE discoverability**: `list_tables` only sees what has
>    already been `INCLUDE PERFETTO MODULE`'d. A navigation tool must
>    list Chrome stdlib views **without the agent having to INCLUDE
>    first** — otherwise the agent has to already know which module to
>    include, which is the problem we are trying to solve.
> 2. **Module-to-view grouping + semantic summaries**: the stdlib source
>    ships per-module / per-view doc strings that explain *what an
>    analysis using this view looks like*. `list_table_structure` only
>    returns column names + types, not the natural-language intent.
>    Surfacing those summaries (extracted from the vendored stdlib
>    source) is the navigation tool's unique payload — column types
>    alone are not worth a new tool.
> 3. **Stable public-API subset**: Perfetto stdlib marks some views as
>    internal (`__`-prefixed) or in-progress. A curated navigation tool
>    can ship only the stable, documented, public ones, so the agent is
>    not handed a view that changes shape next Chrome release.
>
> If a proposed navigation tool does not satisfy at least (1) + (2), it
> is almost certainly an answer-shaped wrapper in disguise — rebuilding
> `list_tables` on a narrower scope does not count as new capability.
>
> Navigation-shaped tools extend the agent's *capability* (pre-INCLUDE
> knowledge of which stdlib views exist + curated semantics) without
> deciding the agent's *strategy* (which view, which filters, which
> aggregation). This matches the Anthropic/OpenAI guidance that tools
> should be tactical primitives and leave strategy to the model.
>
> **Reopen criteria (falsifiable, defined before sampling)**: only
> prototype a navigation-shaped Chrome tool if v0.6 observation data
> shows stdlib usage < 50% on Chrome-relevant tasks (scroll jank / FCP /
> startup). Prototype acceptance requires three hypotheses to hold
> simultaneously on ≥ 10 task samples:
>
> - **H1 activation**: the navigation tool is invoked on ≥ 50% of
>   relevant tasks (tool-call-channel hypothesis from v0.3/0.4).
> - **H2 transfer**: SQL correctness (no hallucinated columns, correct
>   stdlib module selected on first try) rises ≥ 20 percentage points
>   over v0.6 description-only baseline — i.e. the agent actually uses
>   the returned schema, not just calls it as ritual.
> - **H3 economy**: total token cost to reach an answer ≤ description +
>   one WebFetch of stdlib-docs for the same task.
>
> Any one of H1/H2/H3 failing reverts to description-only. Do not ship
> navigation tools on hope — if the data doesn't meet all three, the
> `execute_sql` description is the winning surface.
```

#### 6.2 修 `chrome_frame_timeline_summary` bullet（`:138-141`）

去掉 "complementing the existing `chrome_scroll_jank_summary`"（该工具已删），加入 6.1 形态约束，fixture 行改为"复用 scroll_jank.pftrace（已保留）"：

```markdown
- [ ] (If reopened, must be navigation-shaped per 6.1 above) Replace
      `chrome_frame_timeline_summary` with a navigation-shaped
      equivalent — e.g. `list_chrome_frame_views` returning module +
      view + column schema for the relevant frame-timeline stdlib
      module (look up the exact module name from the vendored stdlib
      source in the external Chromium checkout at
      `~/chromium/src/third_party/perfetto/src/trace_processor/perfetto_sql/stdlib/chrome/`
      per `docs/plans/m5-stdlib-quickref-resource.md:439`;
      this path does NOT exist inside this repo — do NOT guess names;
      `chrome.frame_times` is unverified; the tables referenced in
      the current source are `expected_frame_timeline_slice` /
      `actual_frame_timeline_slice`).
      The original answer-shaped bullet (fixed expected-vs-actual
      aggregation) is explicitly rejected.
  - Acceptance: navigation tool returns structured JSON schema; H1/H2/H3
    falsification conditions from 6.1 all hold on ≥ 10 samples.
  - Fixture: `tests/fixtures/scroll_jank.pftrace` kept in-tree for
    the v0.6 Chrome stdlib e2e test (`e2e_stdlib_include_chrome_scroll_jank`);
    reopened frame-timeline work can reuse the same fixture rather than
    re-fetching from `third_party/perfetto/test/data`.
```

#### 6.3 M2 "Expand e2e fixtures" 条目（`:77-80`）改过去时 + 反映 v0.6 后真实状态

**原 plan "保留现在时原文 + 加一条历史括注 bullet" 方案取消**（codex review 发现）。理由：`docs/roadmap.md:7` 自述是 **current intent snapshot**，不是历史看板。原 M2 "Landed" 行是现在时（`tests/fixtures/ already ships scroll_jank.pftrace, page_loads.pftrace ...`；`tests/e2e_chrome_scroll_jank.rs now drives the chrome_scroll_jank_summary SQL ...`），pivot 删除 `page_loads.pftrace` + `tests/e2e_chrome_scroll_jank.rs` + `chrome_scroll_jank_summary` 工具之后，这些现在时事实**已知不为真**。保留就是明知故犯留下失真文本。

**实际改动**：重写 "Landed" 行，既标注原验收时的真实状态，又反映 v0.6 后当前状态：

```markdown
- [x] Expand e2e fixtures
  - Current state: a single smoke fixture only proves the main path works
  - Landed (subsequently trimmed in the v0.6 pivot): initially shipped `scroll_jank.pftrace`, `page_loads.pftrace`, `event_latency.perfetto-trace`, `histogram.perfetto-trace`, with `tests/e2e_chrome_scroll_jank.rs` driving the removed `chrome_scroll_jank_summary` SQL end-to-end against `scroll_jank.pftrace`. After v0.6, `page_loads.pftrace` and the Chrome domain-tool tests were removed; `scroll_jank.pftrace` is retained and is now exercised by `tests/e2e_stdlib_include.rs::e2e_stdlib_include_chrome_scroll_jank` running the `chrome.scroll_jank.scroll_jank_v3` → `chrome_janky_frames` SQL that `README.md` prints as the canonical migration path.
  - Acceptance: domain tools have representative e2e coverage (met at M2 close; v0.6 redistributes the coverage without losing the Chrome stdlib path)
```

这样 bisect 友好性仍在（原 bullet 结构与 checkmark 未变），但没有故意留下"已知不为真"的现在时断言——读者看到当下仓库真实状态。

### 7. `Cargo.toml` 版本号 bump

```toml
version = "0.6.0"  # 原 "0.5.0"
```

必须先于 commit。理由已在 Context 段说明——v0.5.0 是**已发布 tag**，删除 chrome_* 工具是**真 breaking**，按项目 pre-1.0 约定 minor bump 承载 breaking。

### 8. 不动的文件（显式列出，防止 scope creep）

- `src/server.rs` L517-535（`format_execute_sql_error`）：`chrome_scroll_update_info` 作为 stdlib 表**举例**出现于错误路径 hint，与 pivot 无关
- `docs/plans/m5-stdlib-quickref-resource*.md`：历史 deferred 计划，引用了 `CHROME_SCROLL_JANK_SUMMARY_SQL` 和 fixtures；未来重启时再在那个计划内处理
- `docs/plans/m6-execute-sql-summary-modes*.md`：同上
- `docs/google-perfetto-mcp-analysis*.md`：对比分析文档，历史记录
- `src/server.rs` L751-764（`execute_sql_description_matches_max_rows_constant`）：新描述保留字面量 "5000"，测试继续通过

## 执行顺序（单 commit 内的编辑顺序）

1. 修 `src/server.rs`：按 1.1 → 1.2 → 1.3 → 1.5 顺序（1.4 为"保持不动"，无编辑）
2. `git rm tests/e2e_chrome_domain_tools.rs tests/e2e_chrome_scroll_jank.rs tests/fixtures/page_loads.pftrace`（`scroll_jank.pftrace` 保留供 Section 2.2 Chrome stdlib 新测使用）
3. 新增 `tests/e2e_stdlib_include.rs`（见 2.2 代码）
4. `Cargo.toml` version `0.5.0` → `0.6.0`
5. 改 `CHANGELOG.md`（新增 v0.6.0 条目，不动 v0.5.0/v0.4.0）/ `README.md` / `README.zh-CN.md` / `docs/roadmap.md` / `docs/roadmap.zh-CN.md`
6. 跑 CI gate（依据 `feedback_run_ci_gates_before_commit.md`，三条全过才 commit）：
   ```
   cargo fmt --all --check && \
     cargo clippy --all-targets -- -D warnings && \
     cargo test --all-targets
   ```
7. `git add` 所有改动文件（含 `git rm` 标记的删除和新增的 `tests/e2e_stdlib_include.rs`），单个 commit，subject `breaking: pivot chrome domain tools into execute_sql description (v0.6.0)`。Body 引用 CHANGELOG v0.6.0 第一条 bullet 的文本作为提交说明。

## 验证

### 测试层（CI gate 内已覆盖）

`cargo test --all-targets` 整体通过。共两类断言，均为 pivot 成败一部分（不是附属）：

**静态三测**（描述文本 / instructions 文本 / 工具清单）：

- `instructions_list_core_stdlib_modules`（未改）——保护 v0.4 stdlib 目录防漂移
- `execute_sql_description_matches_max_rows_constant`（L751）——新 description 含 "5000" 字面量
- `tool_router_exposes_expected_tools`（1.2 修改后）——6 个工具按字母序

**端到端三测**（真实 `trace_processor_shell` spawn → HTTP RPC → decode 链条）：

- `e2e_smoke`（未改）——通用数据路径（process count / PRAGMA / bigint / row-cap / missing-table）在 `basic.perfetto-trace` 上全通
- `e2e_stdlib_include_basic_trace`（**新增 — pivot 成败一部分**）——`slices.with_context` 的 generic stdlib 模块在 `basic.perfetto-trace` 上走通 `INCLUDE + SELECT` 单调用路径。若此测失败，description "stdlib-first, 单调用" 主张不成立，pivot 失败
- `e2e_stdlib_include_chrome_scroll_jank`（**新增 — pivot 成败一部分**）——按写入时刻与 README 同款的 Chrome stdlib SQL（`chrome.scroll_jank.scroll_jank_v3` → `chrome_janky_frames`，含 `ORDER BY n DESC`、非空断言、`n` 锁为 i64）在 `scroll_jank.pftrace` 上跑出结果。注意：SQL 是独立副本，非共享常量——README 改时应同步改此测试，CI 不会自动检测漂移。若此测失败，Chrome stdlib 迁移路径对用户就是 broken example，pivot 失败

### 手工端到端

CI gate 的六测已覆盖静态 + e2e 全部层面，无须再起 JSON-RPC 客户端重做 wire-level 自检。

1. `cargo build --release`（确认 release 构型编译通过，与 debug 的 `cargo test` 不冗余）
2. （可选但推荐）`claude mcp add` 挂上本地构建，让 Claude 分析一段 Chrome trace 的 scroll jank，观察 agent 是否按描述走到 stdlib `INCLUDE` + 自写 SQL——与 v0.3 样本 2/3 的"仍 `LIKE` 扫 slice"基线对照。此采样数据喂回 `project_v0*_observation_sample*` 记忆，作为 pivot 后的第一组行为证据，用于决定 M5 Chrome Tools 是否永久关闭

## 关键文件

- `src/server.rs`（主改动区：删 4 工具 + 保留 instructions 只更新 doc comment + 重写 description 含删 separate-call caveat + 改 1 个测试）
- `tests/e2e_chrome_domain_tools.rs` + `tests/e2e_chrome_scroll_jank.rs`（整体 `git rm`）
- `tests/e2e_stdlib_include.rs`（**新增** — INCLUDE 单调用行为回归网）
- `tests/fixtures/page_loads.pftrace`（`git rm` — 仅这一份 Chrome fixture 删；`scroll_jank.pftrace` 保留供新 Chrome stdlib e2e 测试使用）
- `Cargo.toml`（version `0.5.0` → `0.6.0`）
- `CHANGELOG.md`（L5 前新增 v0.6.0 条目；v0.5.0/v0.4.0 不动）
- `README.md` + `README.zh-CN.md`（Tools 表移一行；Example 保留）
- `docs/roadmap.md` + `docs/roadmap.zh-CN.md`（M5 Chrome Tools 延后注释 + frame_timeline 导航形约束 + M2 "Expand e2e fixtures" 条目重写为过去时并反映 v0.6 后真实状态）

## Non-goals（不做）

1. 不 ship `list_stdlib_modules`（独立工具，roadmap M5 Foundation 条目）
2. 不 ship `stdlib-quickref` MCP Resource（`docs/plans/m5-stdlib-quickref-resource.md` deferred）
3. 不给 `execute_sql` 加 `max_rows` / `mode: "schema"` 参数（`docs/plans/m6-execute-sql-summary-modes.md` deferred）
4. 不重命名 `STDLIB_INSTRUCTIONS` 常量（cosmetic，独立 commit）
5. 不动 `format_execute_sql_error` 里 `chrome_scroll_update_info` 举例（错误路径，不在 pivot 关键路径）
6. 不在 `execute_sql` description 中重新嵌入具体 stdlib 模块名（v0.3/0.4 已验证无效；重新嵌入会 import 失败模式）
7. 不改 `docs/plans/*.md` deferred 计划里的 Chrome 引用（历史性，重启时再处理）
8. 不动 `docs/google-perfetto-mcp-analysis*.md`（对照分析文档，历史正确）
9. 不跳 CI gate（`cargo fmt` + `clippy -D warnings` + `test --all-targets` 三条全过）

（原 non-goal #9"不新增集成测试"已移除：Section 2.2 新增的两个测试分别属于**回填 Chrome stdlib INCLUDE 覆盖**（`e2e_stdlib_include_chrome_scroll_jank` — 替代被删的 `e2e_chrome_scroll_jank.rs`）和**补齐一直缺失的 generic stdlib INCLUDE 回归网**（`e2e_stdlib_include_basic_trace`），均非新增 proxy 测试。）
