{"owner":"CarGuo","repo":"gsy_github_app_flutter","hasSkills":true,"totalSkillsCount":1,"totalTokensCount":8583,"categories":["root-instruction"],"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"skills":{"AGENTS.md":"# GSY GitHub App Flutter 协作说明\n\n这是一个 Flutter GitHub 客户端，同时也是带有教学展示性质的示例工程。\n不要假设整个仓库是单一架构风格。多种状态管理方案并存是当前设计现实，不是偶然脏代码。\n\n## 进入仓库后先读\n\n在进行非微小改动前，先读这些文件：\n\n1. `README.md`\n2. `docs/README.md`\n3. `docs/00-overview/project-map.md`\n4. `docs/00-overview/roadmap.md`\n5. `docs/01-architecture/app-layering.md`\n6. `docs/01-architecture/state-management-matrix.md`\n\n## 工作规则\n\n- 改动尽量限制在当前功能域，不要顺手做跨模块重构。\n- 非任务明确要求时，不要迁移状态管理框架。\n- 优先遵循目标模块现有模式，而不是引入新的全局规范。\n- 未经明确允许，不得为了满足需求擅自替换模块既有框架或状态实现，即使替换后看起来更简单。\n- `*.g.dart`、多语言生成文件、env 生成文件都视为生成产物；优先重新生成，不要手改。\n- 不要提交密钥。`lib/common/config/ignoreConfig.dart` 属于本地或 CI 环境材料。\n\n## 高风险目录\n\n- `lib/app.dart`：应用根装配、导航、全局报错、Redux 和 Riverpod 混合接线\n- `lib/common/net/`：共享网络栈、拦截器、GraphQL/REST 入口\n- `lib/common/repositories/`：功能数据访问边界\n- `lib/env/`：构建期环境配置和生成文件\n- `lib/common/localization/`：ARB 与多语言生成输出\n\n## 建议修改策略\n\n- 纯 UI 任务：优先只改页面或局部 widget，不碰全局状态\n- API 任务：同时检查 `common/net` 与对应 `common/repositories`\n- 状态任务：沿用该模块当前已有状态方案，除非任务明确要求迁移\n- 配置/构建任务：同步更新 `docs/03-runbooks/` 中的操作说明\n\n## Review 规则\n\n- 中等以上改动默认使用独立 reviewer 上下文\n- 每次非微小代码改动后，默认拉起新的 reviewer subagent 或新的干净上下文审查刚刚的修改\n- author 在通过一轮新的 reviewer subagent 审查前，不应直接宣告代码任务完成\n- reviewer 不应复用 author 的完整上下文历史\n- 先看 `docs/05-ai/review-harness.md`\n- reviewer 提示模板见 `docs/05-ai/prompts/reviewer-system.md`\n- `tool/ai/build_review_bundle.ps1` 只是可选辅助，不是主流程\n\n## 本地最小验证\n\n按改动范围选择最小验证集合：\n\n- `flutter pub get`\n- `dart run build_runner build --delete-conflicting-outputs`\n- `flutter gen-l10n`\n- `flutter analyze`\n- `flutter build apk --release --target-platform=android-arm64 --no-shrink`\n\n说明：\n\n- 改模型、env、注解生成代码时跑 `build_runner`\n- 改 ARB 或本地化输入时跑 `flutter gen-l10n`\n- 改 Android 构建、依赖或运行时关键路径时跑 APK 构建\n\n## 运行时冒烟验证（强制）\n\n**\"能编译过 + 装机不崩\" 不算测试通过。**\n任何涉及运行时行为（UI 渲染、事件解析、状态流转、网络分支、多语言文案）的改动，\n在宣告完成前，author 必须在真机或模拟器上跑通对应改动路径，\n并把真实证据（截图 / 文案 dump / 错误日志）**以文件形式产出并写清路径**，\n禁止只凭\"app 启动了、日志没红\"就报完成。\n\n强制的是**证据本身**，不是具体工具。工具链按可用性挑：\n\n### 工具选型（`mcp_dart` 是唯一主路径，adb / xcrun simctl 只作为截图 fallback）\n\n**2026-09-02 拍板**：本仓库统一走 `mcp_dart` 冒烟。历史上 `adb` 被写成\"一等公民 fallback\"是路径依赖的锅——`adb` 只是 Android 平台工具，天然把 iOS 排除在外，也不会随 Flutter / Dart 升级而同步演进。`mcp_dart` 直接连 DTD/VM Service，跨平台、随 Flutter 版本演进、能拉真 widget tree 和 runtime errors，是真正的一等公民。\n\n1. **一等公民 — `mcp_dart`（永远优先）**\n   - `dtd` → `listDtdUris` / `connect` 建立 DTD 连接（`flutter run` 起来后 stdout 会打印 DTD/VM Service URI）\n   - `widget_inspector` → `get_widget_tree` / `get_selected_widget` 抓**真实渲染出的 widget 树**，包含 `textPreview` 字段，可直接验证文案 / 是否渲染 / 挂载数量\n   - `get_runtime_errors` 拉 Dart 层异常（改动前后各拉一次；跑完关键路径后再拉一次）\n   - `vm_service` 走 isolate、eval、library 反射等更深的运行时诊断\n   - `hot_reload` / `hot_restart` 快速让改动生效验证\n   - `flutter_driver_command` 只在工程已引入 `flutter_driver` / `integration_test`\n     并且 `main.dart` 里显式调用 `enableFlutterDriverExtension()` 时才可用。\n     本仓库目前**未引入这套依赖**，因此该子工具默认不可用，不得作为唯一验证手段。\n\n2. **截图 fallback（只做截图，不做操作）**\n   - **iOS Simulator**：`xcrun simctl io <UDID> screenshot <path>`\n   - **Android 设备/模拟器**：`adb exec-out screencap -p > <path>`\n   - **禁止用截图 fallback 承担业务验证职责**。业务验证一律走 `mcp_dart` 拿 widget tree 与 runtime errors。截图只是给 reviewer 复核\"人眼层面\"用的补充证据。\n   - 截图**绝对路径必须写进完成汇报**，reviewer 需要能拿到该路径复核。\n\n3. **UI 操作驱动**\n   - **优先方式**：`mcp_dart` `hot_restart` 后由 `main.dart` 走既定路由；或者通过 `vm_service` `eval` 触发 `Navigator.push`。\n   - **降级方式**：仅当无法通过 `mcp_dart` 触发时才允许人肉在模拟器上点，且必须在完成汇报里说明\"这一步为什么无法自动化\"。\n   - **禁止**：新增 `adb shell input tap/swipe` 类的坐标序列脚本——这类脚本硬编码分辨率、随 UI 微调即坏、且天然锁死 Android，是本次拍板要清理的历史包袱。\n\n4. **日志（辅助）**\n   - `mcp_dart` `get_runtime_errors` 是主渠道。\n   - `flutter run` 前台窗口本身就是 Dart 日志渠道，改动前后各留一份 stdout。\n   - **不再**推荐 `adb logcat -d -s flutter` 作为常规日志渠道（仅在 mcp_dart 掉线且需要跨 Flutter 生命周期的 native 侧日志时使用）。\n\n5. **Web / DevTools 类改动**：用 `integrated_browser` 走同样\"操作 → 截图 → 抓日志\"流程。\n\n### 强制流程\n\n1. `flutter run` 或已装机的 build 起到已连接设备。\n   - **debug 构建**：直接 `flutter run -d <deviceId>`，DTD/VM Service URI 会打印在 stdout。\n   - **iOS release 装机**：`flutter build ios --release` 后走 `xcrun simctl install <UDID> <app>` 或走 Xcode 装机。\n   - **Android release 装机**：`flutter build apk --release --target-platform=android-arm64 --no-shrink` 后走 `adb install -r <apk>`（`-r` 表示 reinstall，保留 app 数据）。\n   - **禁止用 `flutter install`**：该命令内部走 `adb uninstall <package>` + `adb install`，会把 `/data/data/<pkg>/` 下所有数据（含 SharedPreferences 里的 `TOKEN_KEY`）一起抹掉，等同把用户从 fixture 账号强制登出。详细教训见\"禁止行为\"章节。\n2. 用 `mcp_dart` `dtd listDtdUris` + `connect` 拿到 app 连接；跑 `get_runtime_errors` 拿到\"改动前\"基线。\n3. 走一遍改动**直接覆盖**的路径（例：改了 PR timeline 事件行 → 进 PR 详情页把 timeline 滚一遍，抓 `widget_inspector get_widget_tree` 找到目标事件行的 `textPreview`），并用 `xcrun simctl io screenshot` 或 `adb exec-out screencap -p` 抓截图作为人眼层面补充证据。\n4. 关键路径跑完后再次 `get_runtime_errors`，确认无新增异常。\n5. 覆盖不到的分支（例：`base_ref_force_pushed` 真机上罕见）必须在完成汇报里**显式列为已知缺口**，不能糊成\"通过\"。稀有分支覆盖率无法靠真机保证时，优先考虑加模型层单测 + 真实 JSON fixture 补齐。\n\n### 分级要求（避免形式主义）\n\n| 改动类型 | 最低证据要求 |\n|---|---|\n| 纯模型 / 纯工具函数 | `flutter analyze` + 单测（若 test 目录已存在）；无需截图 |\n| UI 渲染 / 文案 / 事件行 | 至少 1 张真机截图 + `mcp_dart` `widget_inspector get_widget_tree` 命中目标 widget 或 `textPreview` + `get_runtime_errors` 无异常 |\n| 关键路径（登录 / 网络栈 / 根装配 / 状态边界） | 主路径截图 + widget tree 命中 + `get_runtime_errors` 无异常 + 至少 1 个失败/边界分支的证据 |\n\n### 折叠 + 非折叠双姿态强制（2026-09-09 拍板）\n\n**只要改动会经过 [gsy_adaptive_shell.dart](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/style/gsy_adaptive_shell.dart) / [navigator_utils.dart](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/utils/navigator_utils.dart) 的分栏分支（包括但不限于任何列表 → 详情、右列 detail navigator、`wrapListChild`、`canShowTwoPane` 门控、弹窗路由），冒烟证据必须覆盖两档硬件姿态，缺一档视为汇报不完整**：\n\n| 姿态 | 硬件切换命令 | 期望 layout | 必须留的证据 |\n|---|---|---|---|\n| **CLOSED（折叠）单栏 compact** | `adb -s <serial> emu fold` | `canShowTwoPane == false`，`Navigator.push` 到主 Navigator | 1 张截图 + widget tree 命中 + `get_runtime_errors` |\n| **OPENED（展开）双栏 expanded** | `adb -s <serial> emu unfold` | `canShowTwoPane == true`，走 `detailNavigatorKey` 到右列 | 1 张截图 + widget tree 命中 + `get_runtime_errors` |\n\n规则细则：\n\n- **必须走 `adb emu fold` / `adb emu unfold` 硬件姿态**（Android SDK platform-tools 内置 [Emulator Console](https://developer.android.com/studio/run/emulator-console) 命令），不允许用 `wm size` / `wm density` / `mediaQuery.override` 等伪造分档手段。\n- 每次跑完必须 `adb -s <serial> emu unfold` **把设备复位回展开姿态**（default identifier=2 OPENED），否则下一位 author 会拿到一台以为是\"标准 Pixel Fold\"的折叠设备做基线，属于 author 责任事故。\n- 姿态切换后必须重新拉一次 `widget_inspector get_widget_tree`，确认 `Row` 承载的双 Navigator 结构（expanded）或单 Navigator（compact）与预期一致，才能开始记录 pt.x 证据。\n- **例外**：纯模型 / 纯工具函数（分级表第 1 行）改动不涉及分栏路径的，可豁免双姿态；豁免必须在完成汇报里显式声明\"改动路径不经过 adaptive shell\"。\n- 历史教训：2026-09-09 Wave 1 首次冒烟只做了 CLOSED 单栏一档就报进度，被作者当场纠正。此后凡是提交带 UI 冒烟证据的 PR，两姿态截图 + widget tree + `get_runtime_errors` 必须齐备。\n\n### 完成汇报三段式（必填）\n\n**看代码**：改了哪些文件、哪些函数、为什么这么改。\n**看编译**：`flutter analyze` / `build_runner` / `gen-l10n` 的产物结论。\n**看运行**：设备 id + 平台（iOS Simulator UDID 或 Android device serial）、`mcp_dart` DTD/VM Service URI、`get_runtime_errors` 结果、`widget_inspector` 命中的关键 widget/textPreview、**截图文件绝对路径**、无法覆盖的分支列表。\n\n三段任一缺失 = 任务未完成。\n\n### 禁止行为\n\n- ❌ 只跑 `flutter analyze` 就报\"测试通过\"\n- ❌ 把\"app 启动到首页\"当成本次功能验证\n- ❌ 让用户手动操作 UI 代替 author 自测（除非物理上无法自动化，且已说明原因）\n- ❌ 拿\"日志里没 Exception\"当作行为正确的证据——必须配 `mcp_dart` `widget_inspector` 命中或截图佐证\n- ❌ 只把截图放在自己上下文里而不写路径，导致 reviewer 拿不到证据\n- ❌ **只用 `adb` / `xcrun simctl` 而不连 `mcp_dart`**：截图工具不能承担业务验证职责（2026-09-02 拍板），至少要连一次 DTD 拿一次 `get_runtime_errors`，否则视为汇报不完整\n- ❌ **新增 `adb shell input tap/swipe` 坐标脚本**（2026-09-02 拍板）：这类脚本硬编码分辨率、锁死 Android、随 UI 微调即坏。旧脚本会被逐步替换成 `mcp_dart` 方案，新脚本一律不允许\n- ❌ **使用 `flutter install` 装机**：该命令内部走 `adb uninstall <package>` +\n  `adb install`，会把 `/data/data/<pkg>/` 下所有数据（含 SharedPreferences 里的\n  `TOKEN_KEY`）一起抹掉，等同把用户从 fixture 账号强制登出。\n  正确姿势：`flutter build apk --release --target-platform=android-arm64 --no-shrink`\n  后走 `adb install -r <apk 路径>`（`-r` 表示 reinstall，保留 app 数据）；iOS 走\n  `xcrun simctl install` 或 Xcode 装机。\n  历史教训：2026 装 discussions 冒烟版本时用了 `flutter install`，导致\n  CarSmallGuo 的 gho\\_ token 从设备上被清空，reviewer 侧无法直接复核，属于\n  **author 责任事故**。此后但凡装机脚本 / 命令里出现 `flutter install`，\n  reviewer 一律直接打回。\n- ❌ **在文档 / commit message / code comment 里编造 VM Service / mcp_dart\n  时序细节**（2026-09-03 拍板）：不许写\"evaluate 会同步塞进 build/layout/paint\n  半程\"、\"addPostFrameCallback 一定在当前帧末尾跑到\"这类未在\n  [Dart VM Service Protocol](https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md)\n  或 [api.flutter.dev](https://api.flutter.dev/) 官方文档里出现的因果。\n  历史教训：commit `224a0d8` body 编造 `evaluate` 同步注入回调栈，把\n  `_smokePostFrame` 定性成\"根因修复\"，实际上 `evaluate` 是走 isolate 事件循环\n  排队执行，`_smokePostFrame` 只是防御性保底；该编造在 `ed7077e` / `8106529`\n  连续订正，并在\n  [tool/ai/smoke/README.md `## 历史勘误（errata）`](file:///Users/guoshuyu/workspace/flutter-work/gsy_github_app_flutter/tool/ai/smoke/README.md)\n  章节留档（用章节名定位，避免行号漂移）。原则：**写不确定的时序 / 语义细节\n  前先查一次官方 spec，不能\"根据经验\"猜**。\n\n## 当前已知约束\n\n- 仓库既有 `test/`（纯 Dart 单测 + widget test）也有 `patrol_test/`（Patrol 集成测试脚手架，pubspec 中已引入 `patrol`）；两条测试路径并存，按改动性质挑合适档次\n- CI 使用 GitHub Actions，当前偏重构建成功\n- 项目同时使用 Redux、Riverpod、Provider、Signals\n- OAuth 登录相关流程依赖本地 `ignoreConfig.dart`\n- **GSY 是 GitHub 的轻量作者 + 阅读客户端**：产品语义上是\"看仓库 / 读 issue-PR-discussion\"为主，\n  但**允许用户在自己或已授权的仓库里做已实现的写操作**（发 issue、发 discussion、发评论、reaction、star/watch、resolve review thread 等，见下文清单）。\n  历史上曾把口径写成\"只读 + 评论客户端\"是根据\"AI 冒烟阶段\"的观察反推的口径，\n  与代码里既有的 [IssueRepository.createIssueRequest](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/repositories/issue_repository.dart#L428-L440)\n  等入口不符，2026-09-08 拍板订正。\n\n### 允许 / 禁止的写操作清单\n\n> 状态：作者已于 2026-07-06 首次拍板，2026-09-08 二次订正（把\"针对能力\"改成\"针对目标\"）。修改需在 PR 描述里显式提出并同步 `docs/00-overview/roadmap.md §4.1`。\n\n**判断口径（2026-09-08 订正，读清单前先看这段）**：\n\n- **写操作是否允许，判断的是\"针对谁 / 为了什么\"，不是\"是不是 write endpoint\"**：\n  - GSY 提供的产品能力（发 issue / 发 discussion / 发评论 / reaction / star / watch / mark thread resolved 等）→ **允许用户对任意有权限的仓库做**，这是 app 的正常使用；\n  - **AI / 开发者做冒烟或回归测试** → **不允许往 `CarGuo/*` 主仓或任何不属于自己 / 未经明确授权的第三方仓库塞测试数据**；允许在 AI / 开发者本人名下的测试仓库、fork、sandbox 里造数据，但**必须在 fixture 表里登记该仓库**以便 reviewer 复核。\n- 仓库运维类（改分支保护 / webhook / rerun workflow / Projects V2 卡片状态等）与 GSY 的产品定位无关，**始终禁止**，不因主体而放松。\n- 若未来需要新增一条允许项（新的产品能力），需要在 PR 描述里显式提出，并同步更新本清单与 roadmap §4.1。\n\n**允许（GSY 已实现或即将实现的产品能力，用户对**有权限**的仓库都可以做）**：\n\n- Issue / Comment 上加/取消 reaction\n- Issue / PR / Discussion 下发评论\n- **新建 issue**（[IssueRepository.createIssueRequest](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/repositories/issue_repository.dart#L428-L440) 早就在做，`_createIssue` 按钮在仓库详情页右下角）\n- **新建 discussion**（2026-09-08 新增，见 [discussion_list_page.dart](file:///d:/workspace/project/gsy_github_app_flutter/lib/page/discussion/discussion_list_page.dart) 里的 FAB + `createDiscussion` mutation）\n- Notify 标记已读 / 标记 done / unsubscribe\n- 关注 / 取消关注仓库（star / watch 切换）\n- **PR review thread mark as resolved / unresolved（2026-07-06 新加）**：仅操作层，不做\"未 resolved thread 计数\"这类仪表盘\n- **编辑自己发的 issue body / comment 内容（2026-07-06 新加）**：服务端会按作者身份校验，不会误伤他人；范围**限编辑**，不含删除\n\n**明确禁止（越界的作者行为 / 仓库运维行为）**：\n\n- **AI / 开发者为了造冒烟数据往主仓 `CarGuo/*` 或任何第三方非本人授权的仓库写入**（issue / PR / discussion / comment 都算），2026-09-08 订正口径：这里禁止的是\"造数据的目的和目标\"，不是\"能力\"\n- 新建仓库 / fork 仓库（fork 视图目前只做**读**，动作没做）\n- **新建 PR**（暂未做，且合入前会引入较高审查开销，本轮暂不打开）\n- 提交 / dismiss review\n- 合并 PR / close issue / lock conversation\n- 修改仓库设置 / 分支保护 / webhook / secret\n- **GitHub Actions workflow rerun / cancel（2026-07-06 拍板归入禁止）**：属于仓库运维行为，GSY 不介入；用户如需 rerun 请去 GitHub 官网或官方 app\n- **Projects V2 卡片移动 / 状态字段编辑（2026-07-06 拍板归入禁止）**：等同协作作者视角编辑，与只读 + 评论定位冲突；连 Projects V2 阅读也一并搁置\n- **删除自己发的 issue / comment**：即使 API 支持，也不做——避免\"误删无法恢复\"的用户投诉面\n\n\n## 真机验证专用 fixture（写死，不允许随手换）\n\n以下 PR/仓库是真机冒烟脚本 [tool/ai/smoke/](file:///Users/guoshuyu/workspace/flutter-work/gsy_github_app_flutter/tool/ai/smoke) 默认命中的证据源。改动 timeline 相关代码时，优先复用它们；只有在明确覆盖不到时才另外找 PR。\n\n- **fixture 账号**：`CarSmallGuo`（当前 iOS 模拟器 / Android 设备与 gh cli 登录的账号，token 有 `repo` 权限但**只做读**）\n- **fixture 仓库**：`CarGuo/gsy_github_app_flutter`（GSY 主仓库自身）\n- **fixture PR**：\n  - `#938`：Copilot 提交的 `reviewed / state=commented`，body 788 字符，同时覆盖 `ready_for_review` / `review_requested` / `assigned` / `merged` / `closed` / 未知事件兜底等新事件类型\n    - 打开方式：`flutter run` 起 app 后走 `mcp_dart` `dtd connect` + `widget_inspector` 观测；参考 [tool/ai/smoke/open_pr_timeline.md](file:///Users/guoshuyu/workspace/flutter-work/gsy_github_app_flutter/tool/ai/smoke/open_pr_timeline.md) 的路径描述（人肉操作 UI 或用 `vm_service eval` 触发路由）\n    - 期望证据：`widget_inspector get_widget_tree` 命中包含 `textPreview: Copilot 提交了评审意见` 的事件行 + 其下方灰底 body 卡片（`Card` widget），并附一张 `xcrun simctl io screenshot` 截图\n  - 后续若需要 approved/changes_requested body 场景，请在 `CarGuo` 名下的其它 PR 里挑选并把 PR 号写回本段落，不要造 PR\n\n### 按功能分类的 fixture 表（2026-07-06 扩展）\n\n上面 fixture PR 只覆盖 PR timeline 一个场景。为了让**开新功能前先看有没有可用测试数据**（避免走到装机验证才发现 fixture 空——Discussions 就吃过这个亏），把 GSY 各功能域可用的真实数据源固化如下。变更本表需附带该行 API 探针的实测结果。\n\n**探针复核方式**：所有 `✅` 项都用 CarSmallGuo 的 gho\\_ token 在 `2026-07-06` 实测过；每半年可用 [docs/00-overview/roadmap.md §3.5 探针结果快照](file:///d:/workspace/project/gsy_github_app_flutter/docs/00-overview/roadmap.md) 那批命令重跑一次防止过时。\n\n**Fixture 优先级**：主仓 [CarGuo/gsy_github_app_flutter](https://github.com/CarGuo/gsy_github_app_flutter) > CarGuo 其他仓库 > CarSmallGuo 数据 > AI/开发者自有测试仓库（**必须登记**，见下文 Discussion 行示例） > 外部真实仓库（`flutter/flutter` / `defunkt` 等，**必须标注为\"外部妥协项\"**）。**禁止往主仓或第三方非授权仓库造冒烟数据**（口径见 §允许 / 禁止的写操作清单 2026-09-08 订正）。\n\n| 功能域 | 首选 fixture | 备注 |\n|---|---|---|\n| PR timeline 词典化事件 | 主仓 `#938` | 见上文，已固化 |\n| PR review thread（line-level comment） | 主仓 `#938`（2 条：`android/app/build.gradle` L41 / L84 by Copilot） | 探针实测 |\n| Issue assignee 挂件 | 主仓 `#938`（assignees=`CarGuo,Copilot`） | 主仓 issue 只有 #938 有 assignee，其余是 (none) |\n| Issue 长 timeline / 分页 | 主仓 [`#13`](https://github.com/CarGuo/gsy_github_app_flutter/issues/13) | README 明示\"所有运行问题请点这里\" |\n| Issue comment reactions | 主仓 [`#643`](https://github.com/CarGuo/gsy_github_app_flutter/issues/643) | README 里的\"登录失败\"高流量 issue |\n| **Discussion 创建 / 回复冒烟（2026-09-08 新加）** | ✅ 已登记：[`CarSmallGuo/gsy-smoke-2026q3`](https://github.com/CarSmallGuo/gsy-smoke-2026q3)（fixture 账号自有，创建时 `has_discussions=true`），首条 [`#1 Smoke test 2026-09-09`](https://github.com/CarSmallGuo/gsy-smoke-2026q3/discussion/1)（category=General，含 1 条真机回复 \"reply smoke\"） | 2026-09-09 真机走通 create + comment 端到端（expanded 双栏），证据见 `tool/ai/smoke/evidence/2026090{8,9}-discussion-*.png`。禁止用主仓 `CarGuo/gsy_github_app_flutter` 承接；造数据只允许在该自有测试仓库；用完不删（AGENTS.md 禁止删除自己发的内容） |\n| Release 详情 / reactions | 主仓 `releases`（`8.0.0` 已在真机日志出现） | 本轮真机 `versionName 8.0.0` |\n| Compare 视图 | 主仓 `423c762...bf557aa`（本轮实际 commit） | |\n| Contributors / Stargazers / Watchers | 主仓（★15461） | |\n| Repo Language 分布 / Community Health | 主仓（Dart+Java+ObjC+Kotlin，含 LICENSE / README） | |\n| Repo Branches 切换 | 主仓多分支 | |\n| Repo Fork 网络事件流 | 主仓 `/networks/CarGuo/gsy_github_app_flutter/events` | |\n| Repo Milestones | ⚠️ **主仓无**（探针 count=0）→ 外部妥协项 `flutter/flutter` | |\n| Repo Topics chip | ⚠️ 需重跑探针 → 若空退到 `flutter/flutter` | |\n| User Pinned Repositories | CarGuo（GSYVideoPlayer / gsy\\_github\\_app\\_flutter / GSYGithubAppCompose / GSYGithubAPP / GSYGithubAppKotlin / gsy\\_flutter\\_book） | 探针实测 6 个 pinned |\n| User 贡献日历 | CarGuo（活跃开发者 calendar 稠密） | |\n| User Following/Followers 交集 | CarGuo × CarSmallGuo | 天然双账号 |\n| User 公共 Gists | ⚠️ **CarGuo 无**（探针 count=0）→ 外部妥协项 `defunkt`（3 条非空） | |\n| Org 详情页 / Org 事件流 | 外部妥协项 `flutter` / `dart-lang`（CarGuo 是个人账号，非组织） | |\n| Notifications 仓库级筛选 | 主仓（CarSmallGuo 30 条 subscribed 都指向主仓） | |\n| Notifications reason chip | ⚠️ CarSmallGuo 当前 reason 单一（`subscribed x 28 / manual x 2`）→ 需 mention / review\\_requested 场景时另找 | |\n| Search Labels | 主仓 `repository_id=142308181`（本轮真机日志已确认） | |\n| Search Commits / Topics | 全站搜索无需锁 fixture | 主仓也在结果集 |\n| Rate Limit 诊断 | 当前 token 自查（无需 fixture） | 本轮实测 core 4997/5000 |\n\n\n\n\n"},"files":{"AGENTS.md":"# GSY GitHub App Flutter 协作说明\n\n这是一个 Flutter GitHub 客户端，同时也是带有教学展示性质的示例工程。\n不要假设整个仓库是单一架构风格。多种状态管理方案并存是当前设计现实，不是偶然脏代码。\n\n## 进入仓库后先读\n\n在进行非微小改动前，先读这些文件：\n\n1. `README.md`\n2. `docs/README.md`\n3. `docs/00-overview/project-map.md`\n4. `docs/00-overview/roadmap.md`\n5. `docs/01-architecture/app-layering.md`\n6. `docs/01-architecture/state-management-matrix.md`\n\n## 工作规则\n\n- 改动尽量限制在当前功能域，不要顺手做跨模块重构。\n- 非任务明确要求时，不要迁移状态管理框架。\n- 优先遵循目标模块现有模式，而不是引入新的全局规范。\n- 未经明确允许，不得为了满足需求擅自替换模块既有框架或状态实现，即使替换后看起来更简单。\n- `*.g.dart`、多语言生成文件、env 生成文件都视为生成产物；优先重新生成，不要手改。\n- 不要提交密钥。`lib/common/config/ignoreConfig.dart` 属于本地或 CI 环境材料。\n\n## 高风险目录\n\n- `lib/app.dart`：应用根装配、导航、全局报错、Redux 和 Riverpod 混合接线\n- `lib/common/net/`：共享网络栈、拦截器、GraphQL/REST 入口\n- `lib/common/repositories/`：功能数据访问边界\n- `lib/env/`：构建期环境配置和生成文件\n- `lib/common/localization/`：ARB 与多语言生成输出\n\n## 建议修改策略\n\n- 纯 UI 任务：优先只改页面或局部 widget，不碰全局状态\n- API 任务：同时检查 `common/net` 与对应 `common/repositories`\n- 状态任务：沿用该模块当前已有状态方案，除非任务明确要求迁移\n- 配置/构建任务：同步更新 `docs/03-runbooks/` 中的操作说明\n\n## Review 规则\n\n- 中等以上改动默认使用独立 reviewer 上下文\n- 每次非微小代码改动后，默认拉起新的 reviewer subagent 或新的干净上下文审查刚刚的修改\n- author 在通过一轮新的 reviewer subagent 审查前，不应直接宣告代码任务完成\n- reviewer 不应复用 author 的完整上下文历史\n- 先看 `docs/05-ai/review-harness.md`\n- reviewer 提示模板见 `docs/05-ai/prompts/reviewer-system.md`\n- `tool/ai/build_review_bundle.ps1` 只是可选辅助，不是主流程\n\n## 本地最小验证\n\n按改动范围选择最小验证集合：\n\n- `flutter pub get`\n- `dart run build_runner build --delete-conflicting-outputs`\n- `flutter gen-l10n`\n- `flutter analyze`\n- `flutter build apk --release --target-platform=android-arm64 --no-shrink`\n\n说明：\n\n- 改模型、env、注解生成代码时跑 `build_runner`\n- 改 ARB 或本地化输入时跑 `flutter gen-l10n`\n- 改 Android 构建、依赖或运行时关键路径时跑 APK 构建\n\n## 运行时冒烟验证（强制）\n\n**\"能编译过 + 装机不崩\" 不算测试通过。**\n任何涉及运行时行为（UI 渲染、事件解析、状态流转、网络分支、多语言文案）的改动，\n在宣告完成前，author 必须在真机或模拟器上跑通对应改动路径，\n并把真实证据（截图 / 文案 dump / 错误日志）**以文件形式产出并写清路径**，\n禁止只凭\"app 启动了、日志没红\"就报完成。\n\n强制的是**证据本身**，不是具体工具。工具链按可用性挑：\n\n### 工具选型（`mcp_dart` 是唯一主路径，adb / xcrun simctl 只作为截图 fallback）\n\n**2026-09-02 拍板**：本仓库统一走 `mcp_dart` 冒烟。历史上 `adb` 被写成\"一等公民 fallback\"是路径依赖的锅——`adb` 只是 Android 平台工具，天然把 iOS 排除在外，也不会随 Flutter / Dart 升级而同步演进。`mcp_dart` 直接连 DTD/VM Service，跨平台、随 Flutter 版本演进、能拉真 widget tree 和 runtime errors，是真正的一等公民。\n\n1. **一等公民 — `mcp_dart`（永远优先）**\n   - `dtd` → `listDtdUris` / `connect` 建立 DTD 连接（`flutter run` 起来后 stdout 会打印 DTD/VM Service URI）\n   - `widget_inspector` → `get_widget_tree` / `get_selected_widget` 抓**真实渲染出的 widget 树**，包含 `textPreview` 字段，可直接验证文案 / 是否渲染 / 挂载数量\n   - `get_runtime_errors` 拉 Dart 层异常（改动前后各拉一次；跑完关键路径后再拉一次）\n   - `vm_service` 走 isolate、eval、library 反射等更深的运行时诊断\n   - `hot_reload` / `hot_restart` 快速让改动生效验证\n   - `flutter_driver_command` 只在工程已引入 `flutter_driver` / `integration_test`\n     并且 `main.dart` 里显式调用 `enableFlutterDriverExtension()` 时才可用。\n     本仓库目前**未引入这套依赖**，因此该子工具默认不可用，不得作为唯一验证手段。\n\n2. **截图 fallback（只做截图，不做操作）**\n   - **iOS Simulator**：`xcrun simctl io <UDID> screenshot <path>`\n   - **Android 设备/模拟器**：`adb exec-out screencap -p > <path>`\n   - **禁止用截图 fallback 承担业务验证职责**。业务验证一律走 `mcp_dart` 拿 widget tree 与 runtime errors。截图只是给 reviewer 复核\"人眼层面\"用的补充证据。\n   - 截图**绝对路径必须写进完成汇报**，reviewer 需要能拿到该路径复核。\n\n3. **UI 操作驱动**\n   - **优先方式**：`mcp_dart` `hot_restart` 后由 `main.dart` 走既定路由；或者通过 `vm_service` `eval` 触发 `Navigator.push`。\n   - **降级方式**：仅当无法通过 `mcp_dart` 触发时才允许人肉在模拟器上点，且必须在完成汇报里说明\"这一步为什么无法自动化\"。\n   - **禁止**：新增 `adb shell input tap/swipe` 类的坐标序列脚本——这类脚本硬编码分辨率、随 UI 微调即坏、且天然锁死 Android，是本次拍板要清理的历史包袱。\n\n4. **日志（辅助）**\n   - `mcp_dart` `get_runtime_errors` 是主渠道。\n   - `flutter run` 前台窗口本身就是 Dart 日志渠道，改动前后各留一份 stdout。\n   - **不再**推荐 `adb logcat -d -s flutter` 作为常规日志渠道（仅在 mcp_dart 掉线且需要跨 Flutter 生命周期的 native 侧日志时使用）。\n\n5. **Web / DevTools 类改动**：用 `integrated_browser` 走同样\"操作 → 截图 → 抓日志\"流程。\n\n### 强制流程\n\n1. `flutter run` 或已装机的 build 起到已连接设备。\n   - **debug 构建**：直接 `flutter run -d <deviceId>`，DTD/VM Service URI 会打印在 stdout。\n   - **iOS release 装机**：`flutter build ios --release` 后走 `xcrun simctl install <UDID> <app>` 或走 Xcode 装机。\n   - **Android release 装机**：`flutter build apk --release --target-platform=android-arm64 --no-shrink` 后走 `adb install -r <apk>`（`-r` 表示 reinstall，保留 app 数据）。\n   - **禁止用 `flutter install`**：该命令内部走 `adb uninstall <package>` + `adb install`，会把 `/data/data/<pkg>/` 下所有数据（含 SharedPreferences 里的 `TOKEN_KEY`）一起抹掉，等同把用户从 fixture 账号强制登出。详细教训见\"禁止行为\"章节。\n2. 用 `mcp_dart` `dtd listDtdUris` + `connect` 拿到 app 连接；跑 `get_runtime_errors` 拿到\"改动前\"基线。\n3. 走一遍改动**直接覆盖**的路径（例：改了 PR timeline 事件行 → 进 PR 详情页把 timeline 滚一遍，抓 `widget_inspector get_widget_tree` 找到目标事件行的 `textPreview`），并用 `xcrun simctl io screenshot` 或 `adb exec-out screencap -p` 抓截图作为人眼层面补充证据。\n4. 关键路径跑完后再次 `get_runtime_errors`，确认无新增异常。\n5. 覆盖不到的分支（例：`base_ref_force_pushed` 真机上罕见）必须在完成汇报里**显式列为已知缺口**，不能糊成\"通过\"。稀有分支覆盖率无法靠真机保证时，优先考虑加模型层单测 + 真实 JSON fixture 补齐。\n\n### 分级要求（避免形式主义）\n\n| 改动类型 | 最低证据要求 |\n|---|---|\n| 纯模型 / 纯工具函数 | `flutter analyze` + 单测（若 test 目录已存在）；无需截图 |\n| UI 渲染 / 文案 / 事件行 | 至少 1 张真机截图 + `mcp_dart` `widget_inspector get_widget_tree` 命中目标 widget 或 `textPreview` + `get_runtime_errors` 无异常 |\n| 关键路径（登录 / 网络栈 / 根装配 / 状态边界） | 主路径截图 + widget tree 命中 + `get_runtime_errors` 无异常 + 至少 1 个失败/边界分支的证据 |\n\n### 折叠 + 非折叠双姿态强制（2026-09-09 拍板）\n\n**只要改动会经过 [gsy_adaptive_shell.dart](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/style/gsy_adaptive_shell.dart) / [navigator_utils.dart](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/utils/navigator_utils.dart) 的分栏分支（包括但不限于任何列表 → 详情、右列 detail navigator、`wrapListChild`、`canShowTwoPane` 门控、弹窗路由），冒烟证据必须覆盖两档硬件姿态，缺一档视为汇报不完整**：\n\n| 姿态 | 硬件切换命令 | 期望 layout | 必须留的证据 |\n|---|---|---|---|\n| **CLOSED（折叠）单栏 compact** | `adb -s <serial> emu fold` | `canShowTwoPane == false`，`Navigator.push` 到主 Navigator | 1 张截图 + widget tree 命中 + `get_runtime_errors` |\n| **OPENED（展开）双栏 expanded** | `adb -s <serial> emu unfold` | `canShowTwoPane == true`，走 `detailNavigatorKey` 到右列 | 1 张截图 + widget tree 命中 + `get_runtime_errors` |\n\n规则细则：\n\n- **必须走 `adb emu fold` / `adb emu unfold` 硬件姿态**（Android SDK platform-tools 内置 [Emulator Console](https://developer.android.com/studio/run/emulator-console) 命令），不允许用 `wm size` / `wm density` / `mediaQuery.override` 等伪造分档手段。\n- 每次跑完必须 `adb -s <serial> emu unfold` **把设备复位回展开姿态**（default identifier=2 OPENED），否则下一位 author 会拿到一台以为是\"标准 Pixel Fold\"的折叠设备做基线，属于 author 责任事故。\n- 姿态切换后必须重新拉一次 `widget_inspector get_widget_tree`，确认 `Row` 承载的双 Navigator 结构（expanded）或单 Navigator（compact）与预期一致，才能开始记录 pt.x 证据。\n- **例外**：纯模型 / 纯工具函数（分级表第 1 行）改动不涉及分栏路径的，可豁免双姿态；豁免必须在完成汇报里显式声明\"改动路径不经过 adaptive shell\"。\n- 历史教训：2026-09-09 Wave 1 首次冒烟只做了 CLOSED 单栏一档就报进度，被作者当场纠正。此后凡是提交带 UI 冒烟证据的 PR，两姿态截图 + widget tree + `get_runtime_errors` 必须齐备。\n\n### 完成汇报三段式（必填）\n\n**看代码**：改了哪些文件、哪些函数、为什么这么改。\n**看编译**：`flutter analyze` / `build_runner` / `gen-l10n` 的产物结论。\n**看运行**：设备 id + 平台（iOS Simulator UDID 或 Android device serial）、`mcp_dart` DTD/VM Service URI、`get_runtime_errors` 结果、`widget_inspector` 命中的关键 widget/textPreview、**截图文件绝对路径**、无法覆盖的分支列表。\n\n三段任一缺失 = 任务未完成。\n\n### 禁止行为\n\n- ❌ 只跑 `flutter analyze` 就报\"测试通过\"\n- ❌ 把\"app 启动到首页\"当成本次功能验证\n- ❌ 让用户手动操作 UI 代替 author 自测（除非物理上无法自动化，且已说明原因）\n- ❌ 拿\"日志里没 Exception\"当作行为正确的证据——必须配 `mcp_dart` `widget_inspector` 命中或截图佐证\n- ❌ 只把截图放在自己上下文里而不写路径，导致 reviewer 拿不到证据\n- ❌ **只用 `adb` / `xcrun simctl` 而不连 `mcp_dart`**：截图工具不能承担业务验证职责（2026-09-02 拍板），至少要连一次 DTD 拿一次 `get_runtime_errors`，否则视为汇报不完整\n- ❌ **新增 `adb shell input tap/swipe` 坐标脚本**（2026-09-02 拍板）：这类脚本硬编码分辨率、锁死 Android、随 UI 微调即坏。旧脚本会被逐步替换成 `mcp_dart` 方案，新脚本一律不允许\n- ❌ **使用 `flutter install` 装机**：该命令内部走 `adb uninstall <package>` +\n  `adb install`，会把 `/data/data/<pkg>/` 下所有数据（含 SharedPreferences 里的\n  `TOKEN_KEY`）一起抹掉，等同把用户从 fixture 账号强制登出。\n  正确姿势：`flutter build apk --release --target-platform=android-arm64 --no-shrink`\n  后走 `adb install -r <apk 路径>`（`-r` 表示 reinstall，保留 app 数据）；iOS 走\n  `xcrun simctl install` 或 Xcode 装机。\n  历史教训：2026 装 discussions 冒烟版本时用了 `flutter install`，导致\n  CarSmallGuo 的 gho\\_ token 从设备上被清空，reviewer 侧无法直接复核，属于\n  **author 责任事故**。此后但凡装机脚本 / 命令里出现 `flutter install`，\n  reviewer 一律直接打回。\n- ❌ **在文档 / commit message / code comment 里编造 VM Service / mcp_dart\n  时序细节**（2026-09-03 拍板）：不许写\"evaluate 会同步塞进 build/layout/paint\n  半程\"、\"addPostFrameCallback 一定在当前帧末尾跑到\"这类未在\n  [Dart VM Service Protocol](https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md)\n  或 [api.flutter.dev](https://api.flutter.dev/) 官方文档里出现的因果。\n  历史教训：commit `224a0d8` body 编造 `evaluate` 同步注入回调栈，把\n  `_smokePostFrame` 定性成\"根因修复\"，实际上 `evaluate` 是走 isolate 事件循环\n  排队执行，`_smokePostFrame` 只是防御性保底；该编造在 `ed7077e` / `8106529`\n  连续订正，并在\n  [tool/ai/smoke/README.md `## 历史勘误（errata）`](file:///Users/guoshuyu/workspace/flutter-work/gsy_github_app_flutter/tool/ai/smoke/README.md)\n  章节留档（用章节名定位，避免行号漂移）。原则：**写不确定的时序 / 语义细节\n  前先查一次官方 spec，不能\"根据经验\"猜**。\n\n## 当前已知约束\n\n- 仓库既有 `test/`（纯 Dart 单测 + widget test）也有 `patrol_test/`（Patrol 集成测试脚手架，pubspec 中已引入 `patrol`）；两条测试路径并存，按改动性质挑合适档次\n- CI 使用 GitHub Actions，当前偏重构建成功\n- 项目同时使用 Redux、Riverpod、Provider、Signals\n- OAuth 登录相关流程依赖本地 `ignoreConfig.dart`\n- **GSY 是 GitHub 的轻量作者 + 阅读客户端**：产品语义上是\"看仓库 / 读 issue-PR-discussion\"为主，\n  但**允许用户在自己或已授权的仓库里做已实现的写操作**（发 issue、发 discussion、发评论、reaction、star/watch、resolve review thread 等，见下文清单）。\n  历史上曾把口径写成\"只读 + 评论客户端\"是根据\"AI 冒烟阶段\"的观察反推的口径，\n  与代码里既有的 [IssueRepository.createIssueRequest](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/repositories/issue_repository.dart#L428-L440)\n  等入口不符，2026-09-08 拍板订正。\n\n### 允许 / 禁止的写操作清单\n\n> 状态：作者已于 2026-07-06 首次拍板，2026-09-08 二次订正（把\"针对能力\"改成\"针对目标\"）。修改需在 PR 描述里显式提出并同步 `docs/00-overview/roadmap.md §4.1`。\n\n**判断口径（2026-09-08 订正，读清单前先看这段）**：\n\n- **写操作是否允许，判断的是\"针对谁 / 为了什么\"，不是\"是不是 write endpoint\"**：\n  - GSY 提供的产品能力（发 issue / 发 discussion / 发评论 / reaction / star / watch / mark thread resolved 等）→ **允许用户对任意有权限的仓库做**，这是 app 的正常使用；\n  - **AI / 开发者做冒烟或回归测试** → **不允许往 `CarGuo/*` 主仓或任何不属于自己 / 未经明确授权的第三方仓库塞测试数据**；允许在 AI / 开发者本人名下的测试仓库、fork、sandbox 里造数据，但**必须在 fixture 表里登记该仓库**以便 reviewer 复核。\n- 仓库运维类（改分支保护 / webhook / rerun workflow / Projects V2 卡片状态等）与 GSY 的产品定位无关，**始终禁止**，不因主体而放松。\n- 若未来需要新增一条允许项（新的产品能力），需要在 PR 描述里显式提出，并同步更新本清单与 roadmap §4.1。\n\n**允许（GSY 已实现或即将实现的产品能力，用户对**有权限**的仓库都可以做）**：\n\n- Issue / Comment 上加/取消 reaction\n- Issue / PR / Discussion 下发评论\n- **新建 issue**（[IssueRepository.createIssueRequest](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/repositories/issue_repository.dart#L428-L440) 早就在做，`_createIssue` 按钮在仓库详情页右下角）\n- **新建 discussion**（2026-09-08 新增，见 [discussion_list_page.dart](file:///d:/workspace/project/gsy_github_app_flutter/lib/page/discussion/discussion_list_page.dart) 里的 FAB + `createDiscussion` mutation）\n- Notify 标记已读 / 标记 done / unsubscribe\n- 关注 / 取消关注仓库（star / watch 切换）\n- **PR review thread mark as resolved / unresolved（2026-07-06 新加）**：仅操作层，不做\"未 resolved thread 计数\"这类仪表盘\n- **编辑自己发的 issue body / comment 内容（2026-07-06 新加）**：服务端会按作者身份校验，不会误伤他人；范围**限编辑**，不含删除\n\n**明确禁止（越界的作者行为 / 仓库运维行为）**：\n\n- **AI / 开发者为了造冒烟数据往主仓 `CarGuo/*` 或任何第三方非本人授权的仓库写入**（issue / PR / discussion / comment 都算），2026-09-08 订正口径：这里禁止的是\"造数据的目的和目标\"，不是\"能力\"\n- 新建仓库 / fork 仓库（fork 视图目前只做**读**，动作没做）\n- **新建 PR**（暂未做，且合入前会引入较高审查开销，本轮暂不打开）\n- 提交 / dismiss review\n- 合并 PR / close issue / lock conversation\n- 修改仓库设置 / 分支保护 / webhook / secret\n- **GitHub Actions workflow rerun / cancel（2026-07-06 拍板归入禁止）**：属于仓库运维行为，GSY 不介入；用户如需 rerun 请去 GitHub 官网或官方 app\n- **Projects V2 卡片移动 / 状态字段编辑（2026-07-06 拍板归入禁止）**：等同协作作者视角编辑，与只读 + 评论定位冲突；连 Projects V2 阅读也一并搁置\n- **删除自己发的 issue / comment**：即使 API 支持，也不做——避免\"误删无法恢复\"的用户投诉面\n\n\n## 真机验证专用 fixture（写死，不允许随手换）\n\n以下 PR/仓库是真机冒烟脚本 [tool/ai/smoke/](file:///Users/guoshuyu/workspace/flutter-work/gsy_github_app_flutter/tool/ai/smoke) 默认命中的证据源。改动 timeline 相关代码时，优先复用它们；只有在明确覆盖不到时才另外找 PR。\n\n- **fixture 账号**：`CarSmallGuo`（当前 iOS 模拟器 / Android 设备与 gh cli 登录的账号，token 有 `repo` 权限但**只做读**）\n- **fixture 仓库**：`CarGuo/gsy_github_app_flutter`（GSY 主仓库自身）\n- **fixture PR**：\n  - `#938`：Copilot 提交的 `reviewed / state=commented`，body 788 字符，同时覆盖 `ready_for_review` / `review_requested` / `assigned` / `merged` / `closed` / 未知事件兜底等新事件类型\n    - 打开方式：`flutter run` 起 app 后走 `mcp_dart` `dtd connect` + `widget_inspector` 观测；参考 [tool/ai/smoke/open_pr_timeline.md](file:///Users/guoshuyu/workspace/flutter-work/gsy_github_app_flutter/tool/ai/smoke/open_pr_timeline.md) 的路径描述（人肉操作 UI 或用 `vm_service eval` 触发路由）\n    - 期望证据：`widget_inspector get_widget_tree` 命中包含 `textPreview: Copilot 提交了评审意见` 的事件行 + 其下方灰底 body 卡片（`Card` widget），并附一张 `xcrun simctl io screenshot` 截图\n  - 后续若需要 approved/changes_requested body 场景，请在 `CarGuo` 名下的其它 PR 里挑选并把 PR 号写回本段落，不要造 PR\n\n### 按功能分类的 fixture 表（2026-07-06 扩展）\n\n上面 fixture PR 只覆盖 PR timeline 一个场景。为了让**开新功能前先看有没有可用测试数据**（避免走到装机验证才发现 fixture 空——Discussions 就吃过这个亏），把 GSY 各功能域可用的真实数据源固化如下。变更本表需附带该行 API 探针的实测结果。\n\n**探针复核方式**：所有 `✅` 项都用 CarSmallGuo 的 gho\\_ token 在 `2026-07-06` 实测过；每半年可用 [docs/00-overview/roadmap.md §3.5 探针结果快照](file:///d:/workspace/project/gsy_github_app_flutter/docs/00-overview/roadmap.md) 那批命令重跑一次防止过时。\n\n**Fixture 优先级**：主仓 [CarGuo/gsy_github_app_flutter](https://github.com/CarGuo/gsy_github_app_flutter) > CarGuo 其他仓库 > CarSmallGuo 数据 > AI/开发者自有测试仓库（**必须登记**，见下文 Discussion 行示例） > 外部真实仓库（`flutter/flutter` / `defunkt` 等，**必须标注为\"外部妥协项\"**）。**禁止往主仓或第三方非授权仓库造冒烟数据**（口径见 §允许 / 禁止的写操作清单 2026-09-08 订正）。\n\n| 功能域 | 首选 fixture | 备注 |\n|---|---|---|\n| PR timeline 词典化事件 | 主仓 `#938` | 见上文，已固化 |\n| PR review thread（line-level comment） | 主仓 `#938`（2 条：`android/app/build.gradle` L41 / L84 by Copilot） | 探针实测 |\n| Issue assignee 挂件 | 主仓 `#938`（assignees=`CarGuo,Copilot`） | 主仓 issue 只有 #938 有 assignee，其余是 (none) |\n| Issue 长 timeline / 分页 | 主仓 [`#13`](https://github.com/CarGuo/gsy_github_app_flutter/issues/13) | README 明示\"所有运行问题请点这里\" |\n| Issue comment reactions | 主仓 [`#643`](https://github.com/CarGuo/gsy_github_app_flutter/issues/643) | README 里的\"登录失败\"高流量 issue |\n| **Discussion 创建 / 回复冒烟（2026-09-08 新加）** | ✅ 已登记：[`CarSmallGuo/gsy-smoke-2026q3`](https://github.com/CarSmallGuo/gsy-smoke-2026q3)（fixture 账号自有，创建时 `has_discussions=true`），首条 [`#1 Smoke test 2026-09-09`](https://github.com/CarSmallGuo/gsy-smoke-2026q3/discussion/1)（category=General，含 1 条真机回复 \"reply smoke\"） | 2026-09-09 真机走通 create + comment 端到端（expanded 双栏），证据见 `tool/ai/smoke/evidence/2026090{8,9}-discussion-*.png`。禁止用主仓 `CarGuo/gsy_github_app_flutter` 承接；造数据只允许在该自有测试仓库；用完不删（AGENTS.md 禁止删除自己发的内容） |\n| Release 详情 / reactions | 主仓 `releases`（`8.0.0` 已在真机日志出现） | 本轮真机 `versionName 8.0.0` |\n| Compare 视图 | 主仓 `423c762...bf557aa`（本轮实际 commit） | |\n| Contributors / Stargazers / Watchers | 主仓（★15461） | |\n| Repo Language 分布 / Community Health | 主仓（Dart+Java+ObjC+Kotlin，含 LICENSE / README） | |\n| Repo Branches 切换 | 主仓多分支 | |\n| Repo Fork 网络事件流 | 主仓 `/networks/CarGuo/gsy_github_app_flutter/events` | |\n| Repo Milestones | ⚠️ **主仓无**（探针 count=0）→ 外部妥协项 `flutter/flutter` | |\n| Repo Topics chip | ⚠️ 需重跑探针 → 若空退到 `flutter/flutter` | |\n| User Pinned Repositories | CarGuo（GSYVideoPlayer / gsy\\_github\\_app\\_flutter / GSYGithubAppCompose / GSYGithubAPP / GSYGithubAppKotlin / gsy\\_flutter\\_book） | 探针实测 6 个 pinned |\n| User 贡献日历 | CarGuo（活跃开发者 calendar 稠密） | |\n| User Following/Followers 交集 | CarGuo × CarSmallGuo | 天然双账号 |\n| User 公共 Gists | ⚠️ **CarGuo 无**（探针 count=0）→ 外部妥协项 `defunkt`（3 条非空） | |\n| Org 详情页 / Org 事件流 | 外部妥协项 `flutter` / `dart-lang`（CarGuo 是个人账号，非组织） | |\n| Notifications 仓库级筛选 | 主仓（CarSmallGuo 30 条 subscribed 都指向主仓） | |\n| Notifications reason chip | ⚠️ CarSmallGuo 当前 reason 单一（`subscribed x 28 / manual x 2`）→ 需 mention / review\\_requested 场景时另找 | |\n| Search Labels | 主仓 `repository_id=142308181`（本轮真机日志已确认） | |\n| Search Commits / Topics | 全站搜索无需锁 fixture | 主仓也在结果集 |\n| Rate Limit 诊断 | 当前 token 自查（无需 fixture） | 本轮实测 core 4997/5000 |\n\n\n\n\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/CarGuo/gsy_github_app_flutter/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# GSY GitHub App Flutter 协作说明\n\n这是一个 Flutter GitHub 客户端，同时也是带有教学展示性质的示例工程。\n不要假设整个仓库是单一架构风格。多种状态管理方案并存是当前设计现实，不是偶然脏代码。\n\n## 进入仓库后先读\n\n在进行非微小改动前，先读这些文件：\n\n1. `README.md`\n2. `docs/README.md`\n3. `docs/00-overview/project-map.md`\n4. `docs/00-overview/roadmap.md`\n5. `docs/01-architecture/app-layering.md`\n6. `docs/01-architecture/state-management-matrix.md`\n\n## 工作规则\n\n- 改动尽量限制在当前功能域，不要顺手做跨模块重构。\n- 非任务明确要求时，不要迁移状态管理框架。\n- 优先遵循目标模块现有模式，而不是引入新的全局规范。\n- 未经明确允许，不得为了满足需求擅自替换模块既有框架或状态实现，即使替换后看起来更简单。\n- `*.g.dart`、多语言生成文件、env 生成文件都视为生成产物；优先重新生成，不要手改。\n- 不要提交密钥。`lib/common/config/ignoreConfig.dart` 属于本地或 CI 环境材料。\n\n## 高风险目录\n\n- `lib/app.dart`：应用根装配、导航、全局报错、Redux 和 Riverpod 混合接线\n- `lib/common/net/`：共享网络栈、拦截器、GraphQL/REST 入口\n- `lib/common/repositories/`：功能数据访问边界\n- `lib/env/`：构建期环境配置和生成文件\n- `lib/common/localization/`：ARB 与多语言生成输出\n\n## 建议修改策略\n\n- 纯 UI 任务：优先只改页面或局部 widget，不碰全局状态\n- API 任务：同时检查 `common/net` 与对应 `common/repositories`\n- 状态任务：沿用该模块当前已有状态方案，除非任务明确要求迁移\n- 配置/构建任务：同步更新 `docs/03-runbooks/` 中的操作说明\n\n## Review 规则\n\n- 中等以上改动默认使用独立 reviewer 上下文\n- 每次非微小代码改动后，默认拉起新的 reviewer subagent 或新的干净上下文审查刚刚的修改\n- author 在通过一轮新的 reviewer subagent 审查前，不应直接宣告代码任务完成\n- reviewer 不应复用 author 的完整上下文历史\n- 先看 `docs/05-ai/review-harness.md`\n- reviewer 提示模板见 `docs/05-ai/prompts/reviewer-system.md`\n- `tool/ai/build_review_bundle.ps1` 只是可选辅助，不是主流程\n\n## 本地最小验证\n\n按改动范围选择最小验证集合：\n\n- `flutter pub get`\n- `dart run build_runner build --delete-conflicting-outputs`\n- `flutter gen-l10n`\n- `flutter analyze`\n- `flutter build apk --release --target-platform=android-arm64 --no-shrink`\n\n说明：\n\n- 改模型、env、注解生成代码时跑 `build_runner`\n- 改 ARB 或本地化输入时跑 `flutter gen-l10n`\n- 改 Android 构建、依赖或运行时关键路径时跑 APK 构建\n\n## 运行时冒烟验证（强制）\n\n**\"能编译过 + 装机不崩\" 不算测试通过。**\n任何涉及运行时行为（UI 渲染、事件解析、状态流转、网络分支、多语言文案）的改动，\n在宣告完成前，author 必须在真机或模拟器上跑通对应改动路径，\n并把真实证据（截图 / 文案 dump / 错误日志）**以文件形式产出并写清路径**，\n禁止只凭\"app 启动了、日志没红\"就报完成。\n\n强制的是**证据本身**，不是具体工具。工具链按可用性挑：\n\n### 工具选型（`mcp_dart` 是唯一主路径，adb / xcrun simctl 只作为截图 fallback）\n\n**2026-09-02 拍板**：本仓库统一走 `mcp_dart` 冒烟。历史上 `adb` 被写成\"一等公民 fallback\"是路径依赖的锅——`adb` 只是 Android 平台工具，天然把 iOS 排除在外，也不会随 Flutter / Dart 升级而同步演进。`mcp_dart` 直接连 DTD/VM Service，跨平台、随 Flutter 版本演进、能拉真 widget tree 和 runtime errors，是真正的一等公民。\n\n1. **一等公民 — `mcp_dart`（永远优先）**\n   - `dtd` → `listDtdUris` / `connect` 建立 DTD 连接（`flutter run` 起来后 stdout 会打印 DTD/VM Service URI）\n   - `widget_inspector` → `get_widget_tree` / `get_selected_widget` 抓**真实渲染出的 widget 树**，包含 `textPreview` 字段，可直接验证文案 / 是否渲染 / 挂载数量\n   - `get_runtime_errors` 拉 Dart 层异常（改动前后各拉一次；跑完关键路径后再拉一次）\n   - `vm_service` 走 isolate、eval、library 反射等更深的运行时诊断\n   - `hot_reload` / `hot_restart` 快速让改动生效验证\n   - `flutter_driver_command` 只在工程已引入 `flutter_driver` / `integration_test`\n     并且 `main.dart` 里显式调用 `enableFlutterDriverExtension()` 时才可用。\n     本仓库目前**未引入这套依赖**，因此该子工具默认不可用，不得作为唯一验证手段。\n\n2. **截图 fallback（只做截图，不做操作）**\n   - **iOS Simulator**：`xcrun simctl io <UDID> screenshot <path>`\n   - **Android 设备/模拟器**：`adb exec-out screencap -p > <path>`\n   - **禁止用截图 fallback 承担业务验证职责**。业务验证一律走 `mcp_dart` 拿 widget tree 与 runtime errors。截图只是给 reviewer 复核\"人眼层面\"用的补充证据。\n   - 截图**绝对路径必须写进完成汇报**，reviewer 需要能拿到该路径复核。\n\n3. **UI 操作驱动**\n   - **优先方式**：`mcp_dart` `hot_restart` 后由 `main.dart` 走既定路由；或者通过 `vm_service` `eval` 触发 `Navigator.push`。\n   - **降级方式**：仅当无法通过 `mcp_dart` 触发时才允许人肉在模拟器上点，且必须在完成汇报里说明\"这一步为什么无法自动化\"。\n   - **禁止**：新增 `adb shell input tap/swipe` 类的坐标序列脚本——这类脚本硬编码分辨率、随 UI 微调即坏、且天然锁死 Android，是本次拍板要清理的历史包袱。\n\n4. **日志（辅助）**\n   - `mcp_dart` `get_runtime_errors` 是主渠道。\n   - `flutter run` 前台窗口本身就是 Dart 日志渠道，改动前后各留一份 stdout。\n   - **不再**推荐 `adb logcat -d -s flutter` 作为常规日志渠道（仅在 mcp_dart 掉线且需要跨 Flutter 生命周期的 native 侧日志时使用）。\n\n5. **Web / DevTools 类改动**：用 `integrated_browser` 走同样\"操作 → 截图 → 抓日志\"流程。\n\n### 强制流程\n\n1. `flutter run` 或已装机的 build 起到已连接设备。\n   - **debug 构建**：直接 `flutter run -d <deviceId>`，DTD/VM Service URI 会打印在 stdout。\n   - **iOS release 装机**：`flutter build ios --release` 后走 `xcrun simctl install <UDID> <app>` 或走 Xcode 装机。\n   - **Android release 装机**：`flutter build apk --release --target-platform=android-arm64 --no-shrink` 后走 `adb install -r <apk>`（`-r` 表示 reinstall，保留 app 数据）。\n   - **禁止用 `flutter install`**：该命令内部走 `adb uninstall <package>` + `adb install`，会把 `/data/data/<pkg>/` 下所有数据（含 SharedPreferences 里的 `TOKEN_KEY`）一起抹掉，等同把用户从 fixture 账号强制登出。详细教训见\"禁止行为\"章节。\n2. 用 `mcp_dart` `dtd listDtdUris` + `connect` 拿到 app 连接；跑 `get_runtime_errors` 拿到\"改动前\"基线。\n3. 走一遍改动**直接覆盖**的路径（例：改了 PR timeline 事件行 → 进 PR 详情页把 timeline 滚一遍，抓 `widget_inspector get_widget_tree` 找到目标事件行的 `textPreview`），并用 `xcrun simctl io screenshot` 或 `adb exec-out screencap -p` 抓截图作为人眼层面补充证据。\n4. 关键路径跑完后再次 `get_runtime_errors`，确认无新增异常。\n5. 覆盖不到的分支（例：`base_ref_force_pushed` 真机上罕见）必须在完成汇报里**显式列为已知缺口**，不能糊成\"通过\"。稀有分支覆盖率无法靠真机保证时，优先考虑加模型层单测 + 真实 JSON fixture 补齐。\n\n### 分级要求（避免形式主义）\n\n| 改动类型 | 最低证据要求 |\n|---|---|\n| 纯模型 / 纯工具函数 | `flutter analyze` + 单测（若 test 目录已存在）；无需截图 |\n| UI 渲染 / 文案 / 事件行 | 至少 1 张真机截图 + `mcp_dart` `widget_inspector get_widget_tree` 命中目标 widget 或 `textPreview` + `get_runtime_errors` 无异常 |\n| 关键路径（登录 / 网络栈 / 根装配 / 状态边界） | 主路径截图 + widget tree 命中 + `get_runtime_errors` 无异常 + 至少 1 个失败/边界分支的证据 |\n\n### 折叠 + 非折叠双姿态强制（2026-09-09 拍板）\n\n**只要改动会经过 [gsy_adaptive_shell.dart](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/style/gsy_adaptive_shell.dart) / [navigator_utils.dart](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/utils/navigator_utils.dart) 的分栏分支（包括但不限于任何列表 → 详情、右列 detail navigator、`wrapListChild`、`canShowTwoPane` 门控、弹窗路由），冒烟证据必须覆盖两档硬件姿态，缺一档视为汇报不完整**：\n\n| 姿态 | 硬件切换命令 | 期望 layout | 必须留的证据 |\n|---|---|---|---|\n| **CLOSED（折叠）单栏 compact** | `adb -s <serial> emu fold` | `canShowTwoPane == false`，`Navigator.push` 到主 Navigator | 1 张截图 + widget tree 命中 + `get_runtime_errors` |\n| **OPENED（展开）双栏 expanded** | `adb -s <serial> emu unfold` | `canShowTwoPane == true`，走 `detailNavigatorKey` 到右列 | 1 张截图 + widget tree 命中 + `get_runtime_errors` |\n\n规则细则：\n\n- **必须走 `adb emu fold` / `adb emu unfold` 硬件姿态**（Android SDK platform-tools 内置 [Emulator Console](https://developer.android.com/studio/run/emulator-console) 命令），不允许用 `wm size` / `wm density` / `mediaQuery.override` 等伪造分档手段。\n- 每次跑完必须 `adb -s <serial> emu unfold` **把设备复位回展开姿态**（default identifier=2 OPENED），否则下一位 author 会拿到一台以为是\"标准 Pixel Fold\"的折叠设备做基线，属于 author 责任事故。\n- 姿态切换后必须重新拉一次 `widget_inspector get_widget_tree`，确认 `Row` 承载的双 Navigator 结构（expanded）或单 Navigator（compact）与预期一致，才能开始记录 pt.x 证据。\n- **例外**：纯模型 / 纯工具函数（分级表第 1 行）改动不涉及分栏路径的，可豁免双姿态；豁免必须在完成汇报里显式声明\"改动路径不经过 adaptive shell\"。\n- 历史教训：2026-09-09 Wave 1 首次冒烟只做了 CLOSED 单栏一档就报进度，被作者当场纠正。此后凡是提交带 UI 冒烟证据的 PR，两姿态截图 + widget tree + `get_runtime_errors` 必须齐备。\n\n### 完成汇报三段式（必填）\n\n**看代码**：改了哪些文件、哪些函数、为什么这么改。\n**看编译**：`flutter analyze` / `build_runner` / `gen-l10n` 的产物结论。\n**看运行**：设备 id + 平台（iOS Simulator UDID 或 Android device serial）、`mcp_dart` DTD/VM Service URI、`get_runtime_errors` 结果、`widget_inspector` 命中的关键 widget/textPreview、**截图文件绝对路径**、无法覆盖的分支列表。\n\n三段任一缺失 = 任务未完成。\n\n### 禁止行为\n\n- ❌ 只跑 `flutter analyze` 就报\"测试通过\"\n- ❌ 把\"app 启动到首页\"当成本次功能验证\n- ❌ 让用户手动操作 UI 代替 author 自测（除非物理上无法自动化，且已说明原因）\n- ❌ 拿\"日志里没 Exception\"当作行为正确的证据——必须配 `mcp_dart` `widget_inspector` 命中或截图佐证\n- ❌ 只把截图放在自己上下文里而不写路径，导致 reviewer 拿不到证据\n- ❌ **只用 `adb` / `xcrun simctl` 而不连 `mcp_dart`**：截图工具不能承担业务验证职责（2026-09-02 拍板），至少要连一次 DTD 拿一次 `get_runtime_errors`，否则视为汇报不完整\n- ❌ **新增 `adb shell input tap/swipe` 坐标脚本**（2026-09-02 拍板）：这类脚本硬编码分辨率、锁死 Android、随 UI 微调即坏。旧脚本会被逐步替换成 `mcp_dart` 方案，新脚本一律不允许\n- ❌ **使用 `flutter install` 装机**：该命令内部走 `adb uninstall <package>` +\n  `adb install`，会把 `/data/data/<pkg>/` 下所有数据（含 SharedPreferences 里的\n  `TOKEN_KEY`）一起抹掉，等同把用户从 fixture 账号强制登出。\n  正确姿势：`flutter build apk --release --target-platform=android-arm64 --no-shrink`\n  后走 `adb install -r <apk 路径>`（`-r` 表示 reinstall，保留 app 数据）；iOS 走\n  `xcrun simctl install` 或 Xcode 装机。\n  历史教训：2026 装 discussions 冒烟版本时用了 `flutter install`，导致\n  CarSmallGuo 的 gho\\_ token 从设备上被清空，reviewer 侧无法直接复核，属于\n  **author 责任事故**。此后但凡装机脚本 / 命令里出现 `flutter install`，\n  reviewer 一律直接打回。\n- ❌ **在文档 / commit message / code comment 里编造 VM Service / mcp_dart\n  时序细节**（2026-09-03 拍板）：不许写\"evaluate 会同步塞进 build/layout/paint\n  半程\"、\"addPostFrameCallback 一定在当前帧末尾跑到\"这类未在\n  [Dart VM Service Protocol](https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md)\n  或 [api.flutter.dev](https://api.flutter.dev/) 官方文档里出现的因果。\n  历史教训：commit `224a0d8` body 编造 `evaluate` 同步注入回调栈，把\n  `_smokePostFrame` 定性成\"根因修复\"，实际上 `evaluate` 是走 isolate 事件循环\n  排队执行，`_smokePostFrame` 只是防御性保底；该编造在 `ed7077e` / `8106529`\n  连续订正，并在\n  [tool/ai/smoke/README.md `## 历史勘误（errata）`](file:///Users/guoshuyu/workspace/flutter-work/gsy_github_app_flutter/tool/ai/smoke/README.md)\n  章节留档（用章节名定位，避免行号漂移）。原则：**写不确定的时序 / 语义细节\n  前先查一次官方 spec，不能\"根据经验\"猜**。\n\n## 当前已知约束\n\n- 仓库既有 `test/`（纯 Dart 单测 + widget test）也有 `patrol_test/`（Patrol 集成测试脚手架，pubspec 中已引入 `patrol`）；两条测试路径并存，按改动性质挑合适档次\n- CI 使用 GitHub Actions，当前偏重构建成功\n- 项目同时使用 Redux、Riverpod、Provider、Signals\n- OAuth 登录相关流程依赖本地 `ignoreConfig.dart`\n- **GSY 是 GitHub 的轻量作者 + 阅读客户端**：产品语义上是\"看仓库 / 读 issue-PR-discussion\"为主，\n  但**允许用户在自己或已授权的仓库里做已实现的写操作**（发 issue、发 discussion、发评论、reaction、star/watch、resolve review thread 等，见下文清单）。\n  历史上曾把口径写成\"只读 + 评论客户端\"是根据\"AI 冒烟阶段\"的观察反推的口径，\n  与代码里既有的 [IssueRepository.createIssueRequest](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/repositories/issue_repository.dart#L428-L440)\n  等入口不符，2026-09-08 拍板订正。\n\n### 允许 / 禁止的写操作清单\n\n> 状态：作者已于 2026-07-06 首次拍板，2026-09-08 二次订正（把\"针对能力\"改成\"针对目标\"）。修改需在 PR 描述里显式提出并同步 `docs/00-overview/roadmap.md §4.1`。\n\n**判断口径（2026-09-08 订正，读清单前先看这段）**：\n\n- **写操作是否允许，判断的是\"针对谁 / 为了什么\"，不是\"是不是 write endpoint\"**：\n  - GSY 提供的产品能力（发 issue / 发 discussion / 发评论 / reaction / star / watch / mark thread resolved 等）→ **允许用户对任意有权限的仓库做**，这是 app 的正常使用；\n  - **AI / 开发者做冒烟或回归测试** → **不允许往 `CarGuo/*` 主仓或任何不属于自己 / 未经明确授权的第三方仓库塞测试数据**；允许在 AI / 开发者本人名下的测试仓库、fork、sandbox 里造数据，但**必须在 fixture 表里登记该仓库**以便 reviewer 复核。\n- 仓库运维类（改分支保护 / webhook / rerun workflow / Projects V2 卡片状态等）与 GSY 的产品定位无关，**始终禁止**，不因主体而放松。\n- 若未来需要新增一条允许项（新的产品能力），需要在 PR 描述里显式提出，并同步更新本清单与 roadmap §4.1。\n\n**允许（GSY 已实现或即将实现的产品能力，用户对**有权限**的仓库都可以做）**：\n\n- Issue / Comment 上加/取消 reaction\n- Issue / PR / Discussion 下发评论\n- **新建 issue**（[IssueRepository.createIssueRequest](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/repositories/issue_repository.dart#L428-L440) 早就在做，`_createIssue` 按钮在仓库详情页右下角）\n- **新建 discussion**（2026-09-08 新增，见 [discussion_list_page.dart](file:///d:/workspace/project/gsy_github_app_flutter/lib/page/discussion/discussion_list_page.dart) 里的 FAB + `createDiscussion` mutation）\n- Notify 标记已读 / 标记 done / unsubscribe\n- 关注 / 取消关注仓库（star / watch 切换）\n- **PR review thread mark as resolved / unresolved（2026-07-06 新加）**：仅操作层，不做\"未 resolved thread 计数\"这类仪表盘\n- **编辑自己发的 issue body / comment 内容（2026-07-06 新加）**：服务端会按作者身份校验，不会误伤他人；范围**限编辑**，不含删除\n\n**明确禁止（越界的作者行为 / 仓库运维行为）**：\n\n- **AI / 开发者为了造冒烟数据往主仓 `CarGuo/*` 或任何第三方非本人授权的仓库写入**（issue / PR / discussion / comment 都算），2026-09-08 订正口径：这里禁止的是\"造数据的目的和目标\"，不是\"能力\"\n- 新建仓库 / fork 仓库（fork 视图目前只做**读**，动作没做）\n- **新建 PR**（暂未做，且合入前会引入较高审查开销，本轮暂不打开）\n- 提交 / dismiss review\n- 合并 PR / close issue / lock conversation\n- 修改仓库设置 / 分支保护 / webhook / secret\n- **GitHub Actions workflow rerun / cancel（2026-07-06 拍板归入禁止）**：属于仓库运维行为，GSY 不介入；用户如需 rerun 请去 GitHub 官网或官方 app\n- **Projects V2 卡片移动 / 状态字段编辑（2026-07-06 拍板归入禁止）**：等同协作作者视角编辑，与只读 + 评论定位冲突；连 Projects V2 阅读也一并搁置\n- **删除自己发的 issue / comment**：即使 API 支持，也不做——避免\"误删无法恢复\"的用户投诉面\n\n\n## 真机验证专用 fixture（写死，不允许随手换）\n\n以下 PR/仓库是真机冒烟脚本 [tool/ai/smoke/](file:///Users/guoshuyu/workspace/flutter-work/gsy_github_app_flutter/tool/ai/smoke) 默认命中的证据源。改动 timeline 相关代码时，优先复用它们；只有在明确覆盖不到时才另外找 PR。\n\n- **fixture 账号**：`CarSmallGuo`（当前 iOS 模拟器 / Android 设备与 gh cli 登录的账号，token 有 `repo` 权限但**只做读**）\n- **fixture 仓库**：`CarGuo/gsy_github_app_flutter`（GSY 主仓库自身）\n- **fixture PR**：\n  - `#938`：Copilot 提交的 `reviewed / state=commented`，body 788 字符，同时覆盖 `ready_for_review` / `review_requested` / `assigned` / `merged` / `closed` / 未知事件兜底等新事件类型\n    - 打开方式：`flutter run` 起 app 后走 `mcp_dart` `dtd connect` + `widget_inspector` 观测；参考 [tool/ai/smoke/open_pr_timeline.md](file:///Users/guoshuyu/workspace/flutter-work/gsy_github_app_flutter/tool/ai/smoke/open_pr_timeline.md) 的路径描述（人肉操作 UI 或用 `vm_service eval` 触发路由）\n    - 期望证据：`widget_inspector get_widget_tree` 命中包含 `textPreview: Copilot 提交了评审意见` 的事件行 + 其下方灰底 body 卡片（`Card` widget），并附一张 `xcrun simctl io screenshot` 截图\n  - 后续若需要 approved/changes_requested body 场景，请在 `CarGuo` 名下的其它 PR 里挑选并把 PR 号写回本段落，不要造 PR\n\n### 按功能分类的 fixture 表（2026-07-06 扩展）\n\n上面 fixture PR 只覆盖 PR timeline 一个场景。为了让**开新功能前先看有没有可用测试数据**（避免走到装机验证才发现 fixture 空——Discussions 就吃过这个亏），把 GSY 各功能域可用的真实数据源固化如下。变更本表需附带该行 API 探针的实测结果。\n\n**探针复核方式**：所有 `✅` 项都用 CarSmallGuo 的 gho\\_ token 在 `2026-07-06` 实测过；每半年可用 [docs/00-overview/roadmap.md §3.5 探针结果快照](file:///d:/workspace/project/gsy_github_app_flutter/docs/00-overview/roadmap.md) 那批命令重跑一次防止过时。\n\n**Fixture 优先级**：主仓 [CarGuo/gsy_github_app_flutter](https://github.com/CarGuo/gsy_github_app_flutter) > CarGuo 其他仓库 > CarSmallGuo 数据 > AI/开发者自有测试仓库（**必须登记**，见下文 Discussion 行示例） > 外部真实仓库（`flutter/flutter` / `defunkt` 等，**必须标注为\"外部妥协项\"**）。**禁止往主仓或第三方非授权仓库造冒烟数据**（口径见 §允许 / 禁止的写操作清单 2026-09-08 订正）。\n\n| 功能域 | 首选 fixture | 备注 |\n|---|---|---|\n| PR timeline 词典化事件 | 主仓 `#938` | 见上文，已固化 |\n| PR review thread（line-level comment） | 主仓 `#938`（2 条：`android/app/build.gradle` L41 / L84 by Copilot） | 探针实测 |\n| Issue assignee 挂件 | 主仓 `#938`（assignees=`CarGuo,Copilot`） | 主仓 issue 只有 #938 有 assignee，其余是 (none) |\n| Issue 长 timeline / 分页 | 主仓 [`#13`](https://github.com/CarGuo/gsy_github_app_flutter/issues/13) | README 明示\"所有运行问题请点这里\" |\n| Issue comment reactions | 主仓 [`#643`](https://github.com/CarGuo/gsy_github_app_flutter/issues/643) | README 里的\"登录失败\"高流量 issue |\n| **Discussion 创建 / 回复冒烟（2026-09-08 新加）** | ✅ 已登记：[`CarSmallGuo/gsy-smoke-2026q3`](https://github.com/CarSmallGuo/gsy-smoke-2026q3)（fixture 账号自有，创建时 `has_discussions=true`），首条 [`#1 Smoke test 2026-09-09`](https://github.com/CarSmallGuo/gsy-smoke-2026q3/discussion/1)（category=General，含 1 条真机回复 \"reply smoke\"） | 2026-09-09 真机走通 create + comment 端到端（expanded 双栏），证据见 `tool/ai/smoke/evidence/2026090{8,9}-discussion-*.png`。禁止用主仓 `CarGuo/gsy_github_app_flutter` 承接；造数据只允许在该自有测试仓库；用完不删（AGENTS.md 禁止删除自己发的内容） |\n| Release 详情 / reactions | 主仓 `releases`（`8.0.0` 已在真机日志出现） | 本轮真机 `versionName 8.0.0` |\n| Compare 视图 | 主仓 `423c762...bf557aa`（本轮实际 commit） | |\n| Contributors / Stargazers / Watchers | 主仓（★15461） | |\n| Repo Language 分布 / Community Health | 主仓（Dart+Java+ObjC+Kotlin，含 LICENSE / README） | |\n| Repo Branches 切换 | 主仓多分支 | |\n| Repo Fork 网络事件流 | 主仓 `/networks/CarGuo/gsy_github_app_flutter/events` | |\n| Repo Milestones | ⚠️ **主仓无**（探针 count=0）→ 外部妥协项 `flutter/flutter` | |\n| Repo Topics chip | ⚠️ 需重跑探针 → 若空退到 `flutter/flutter` | |\n| User Pinned Repositories | CarGuo（GSYVideoPlayer / gsy\\_github\\_app\\_flutter / GSYGithubAppCompose / GSYGithubAPP / GSYGithubAppKotlin / gsy\\_flutter\\_book） | 探针实测 6 个 pinned |\n| User 贡献日历 | CarGuo（活跃开发者 calendar 稠密） | |\n| User Following/Followers 交集 | CarGuo × CarSmallGuo | 天然双账号 |\n| User 公共 Gists | ⚠️ **CarGuo 无**（探针 count=0）→ 外部妥协项 `defunkt`（3 条非空） | |\n| Org 详情页 / Org 事件流 | 外部妥协项 `flutter` / `dart-lang`（CarGuo 是个人账号，非组织） | |\n| Notifications 仓库级筛选 | 主仓（CarSmallGuo 30 条 subscribed 都指向主仓） | |\n| Notifications reason chip | ⚠️ CarSmallGuo 当前 reason 单一（`subscribed x 28 / manual x 2`）→ 需 mention / review\\_requested 场景时另找 | |\n| Search Labels | 主仓 `repository_id=142308181`（本轮真机日志已确认） | |\n| Search Commits / Topics | 全站搜索无需锁 fixture | 主仓也在结果集 |\n| Rate Limit 诊断 | 当前 token 自查（无需 fixture） | 本轮实测 core 4997/5000 |\n\n\n\n\n","isInternal":false,"tokens":8583,"sizeBytes":24221}],"systemPromptSnippet":"<yakaai_skills repo=\"CarGuo/gsy_github_app_flutter\">\n<!-- File: AGENTS.md (Tokens: ~8583 | Category: root-instruction) -->\n# GSY GitHub App Flutter 协作说明\n\n这是一个 Flutter GitHub 客户端，同时也是带有教学展示性质的示例工程。\n不要假设整个仓库是单一架构风格。多种状态管理方案并存是当前设计现实，不是偶然脏代码。\n\n## 进入仓库后先读\n\n在进行非微小改动前，先读这些文件：\n\n1. `README.md`\n2. `docs/README.md`\n3. `docs/00-overview/project-map.md`\n4. `docs/00-overview/roadmap.md`\n5. `docs/01-architecture/app-layering.md`\n6. `docs/01-architecture/state-management-matrix.md`\n\n## 工作规则\n\n- 改动尽量限制在当前功能域，不要顺手做跨模块重构。\n- 非任务明确要求时，不要迁移状态管理框架。\n- 优先遵循目标模块现有模式，而不是引入新的全局规范。\n- 未经明确允许，不得为了满足需求擅自替换模块既有框架或状态实现，即使替换后看起来更简单。\n- `*.g.dart`、多语言生成文件、env 生成文件都视为生成产物；优先重新生成，不要手改。\n- 不要提交密钥。`lib/common/config/ignoreConfig.dart` 属于本地或 CI 环境材料。\n\n## 高风险目录\n\n- `lib/app.dart`：应用根装配、导航、全局报错、Redux 和 Riverpod 混合接线\n- `lib/common/net/`：共享网络栈、拦截器、GraphQL/REST 入口\n- `lib/common/repositories/`：功能数据访问边界\n- `lib/env/`：构建期环境配置和生成文件\n- `lib/common/localization/`：ARB 与多语言生成输出\n\n## 建议修改策略\n\n- 纯 UI 任务：优先只改页面或局部 widget，不碰全局状态\n- API 任务：同时检查 `common/net` 与对应 `common/repositories`\n- 状态任务：沿用该模块当前已有状态方案，除非任务明确要求迁移\n- 配置/构建任务：同步更新 `docs/03-runbooks/` 中的操作说明\n\n## Review 规则\n\n- 中等以上改动默认使用独立 reviewer 上下文\n- 每次非微小代码改动后，默认拉起新的 reviewer subagent 或新的干净上下文审查刚刚的修改\n- author 在通过一轮新的 reviewer subagent 审查前，不应直接宣告代码任务完成\n- reviewer 不应复用 author 的完整上下文历史\n- 先看 `docs/05-ai/review-harness.md`\n- reviewer 提示模板见 `docs/05-ai/prompts/reviewer-system.md`\n- `tool/ai/build_review_bundle.ps1` 只是可选辅助，不是主流程\n\n## 本地最小验证\n\n按改动范围选择最小验证集合：\n\n- `flutter pub get`\n- `dart run build_runner build --delete-conflicting-outputs`\n- `flutter gen-l10n`\n- `flutter analyze`\n- `flutter build apk --release --target-platform=android-arm64 --no-shrink`\n\n说明：\n\n- 改模型、env、注解生成代码时跑 `build_runner`\n- 改 ARB 或本地化输入时跑 `flutter gen-l10n`\n- 改 Android 构建、依赖或运行时关键路径时跑 APK 构建\n\n## 运行时冒烟验证（强制）\n\n**\"能编译过 + 装机不崩\" 不算测试通过。**\n任何涉及运行时行为（UI 渲染、事件解析、状态流转、网络分支、多语言文案）的改动，\n在宣告完成前，author 必须在真机或模拟器上跑通对应改动路径，\n并把真实证据（截图 / 文案 dump / 错误日志）**以文件形式产出并写清路径**，\n禁止只凭\"app 启动了、日志没红\"就报完成。\n\n强制的是**证据本身**，不是具体工具。工具链按可用性挑：\n\n### 工具选型（`mcp_dart` 是唯一主路径，adb / xcrun simctl 只作为截图 fallback）\n\n**2026-09-02 拍板**：本仓库统一走 `mcp_dart` 冒烟。历史上 `adb` 被写成\"一等公民 fallback\"是路径依赖的锅——`adb` 只是 Android 平台工具，天然把 iOS 排除在外，也不会随 Flutter / Dart 升级而同步演进。`mcp_dart` 直接连 DTD/VM Service，跨平台、随 Flutter 版本演进、能拉真 widget tree 和 runtime errors，是真正的一等公民。\n\n1. **一等公民 — `mcp_dart`（永远优先）**\n   - `dtd` → `listDtdUris` / `connect` 建立 DTD 连接（`flutter run` 起来后 stdout 会打印 DTD/VM Service URI）\n   - `widget_inspector` → `get_widget_tree` / `get_selected_widget` 抓**真实渲染出的 widget 树**，包含 `textPreview` 字段，可直接验证文案 / 是否渲染 / 挂载数量\n   - `get_runtime_errors` 拉 Dart 层异常（改动前后各拉一次；跑完关键路径后再拉一次）\n   - `vm_service` 走 isolate、eval、library 反射等更深的运行时诊断\n   - `hot_reload` / `hot_restart` 快速让改动生效验证\n   - `flutter_driver_command` 只在工程已引入 `flutter_driver` / `integration_test`\n     并且 `main.dart` 里显式调用 `enableFlutterDriverExtension()` 时才可用。\n     本仓库目前**未引入这套依赖**，因此该子工具默认不可用，不得作为唯一验证手段。\n\n2. **截图 fallback（只做截图，不做操作）**\n   - **iOS Simulator**：`xcrun simctl io <UDID> screenshot <path>`\n   - **Android 设备/模拟器**：`adb exec-out screencap -p > <path>`\n   - **禁止用截图 fallback 承担业务验证职责**。业务验证一律走 `mcp_dart` 拿 widget tree 与 runtime errors。截图只是给 reviewer 复核\"人眼层面\"用的补充证据。\n   - 截图**绝对路径必须写进完成汇报**，reviewer 需要能拿到该路径复核。\n\n3. **UI 操作驱动**\n   - **优先方式**：`mcp_dart` `hot_restart` 后由 `main.dart` 走既定路由；或者通过 `vm_service` `eval` 触发 `Navigator.push`。\n   - **降级方式**：仅当无法通过 `mcp_dart` 触发时才允许人肉在模拟器上点，且必须在完成汇报里说明\"这一步为什么无法自动化\"。\n   - **禁止**：新增 `adb shell input tap/swipe` 类的坐标序列脚本——这类脚本硬编码分辨率、随 UI 微调即坏、且天然锁死 Android，是本次拍板要清理的历史包袱。\n\n4. **日志（辅助）**\n   - `mcp_dart` `get_runtime_errors` 是主渠道。\n   - `flutter run` 前台窗口本身就是 Dart 日志渠道，改动前后各留一份 stdout。\n   - **不再**推荐 `adb logcat -d -s flutter` 作为常规日志渠道（仅在 mcp_dart 掉线且需要跨 Flutter 生命周期的 native 侧日志时使用）。\n\n5. **Web / DevTools 类改动**：用 `integrated_browser` 走同样\"操作 → 截图 → 抓日志\"流程。\n\n### 强制流程\n\n1. `flutter run` 或已装机的 build 起到已连接设备。\n   - **debug 构建**：直接 `flutter run -d <deviceId>`，DTD/VM Service URI 会打印在 stdout。\n   - **iOS release 装机**：`flutter build ios --release` 后走 `xcrun simctl install <UDID> <app>` 或走 Xcode 装机。\n   - **Android release 装机**：`flutter build apk --release --target-platform=android-arm64 --no-shrink` 后走 `adb install -r <apk>`（`-r` 表示 reinstall，保留 app 数据）。\n   - **禁止用 `flutter install`**：该命令内部走 `adb uninstall <package>` + `adb install`，会把 `/data/data/<pkg>/` 下所有数据（含 SharedPreferences 里的 `TOKEN_KEY`）一起抹掉，等同把用户从 fixture 账号强制登出。详细教训见\"禁止行为\"章节。\n2. 用 `mcp_dart` `dtd listDtdUris` + `connect` 拿到 app 连接；跑 `get_runtime_errors` 拿到\"改动前\"基线。\n3. 走一遍改动**直接覆盖**的路径（例：改了 PR timeline 事件行 → 进 PR 详情页把 timeline 滚一遍，抓 `widget_inspector get_widget_tree` 找到目标事件行的 `textPreview`），并用 `xcrun simctl io screenshot` 或 `adb exec-out screencap -p` 抓截图作为人眼层面补充证据。\n4. 关键路径跑完后再次 `get_runtime_errors`，确认无新增异常。\n5. 覆盖不到的分支（例：`base_ref_force_pushed` 真机上罕见）必须在完成汇报里**显式列为已知缺口**，不能糊成\"通过\"。稀有分支覆盖率无法靠真机保证时，优先考虑加模型层单测 + 真实 JSON fixture 补齐。\n\n### 分级要求（避免形式主义）\n\n| 改动类型 | 最低证据要求 |\n|---|---|\n| 纯模型 / 纯工具函数 | `flutter analyze` + 单测（若 test 目录已存在）；无需截图 |\n| UI 渲染 / 文案 / 事件行 | 至少 1 张真机截图 + `mcp_dart` `widget_inspector get_widget_tree` 命中目标 widget 或 `textPreview` + `get_runtime_errors` 无异常 |\n| 关键路径（登录 / 网络栈 / 根装配 / 状态边界） | 主路径截图 + widget tree 命中 + `get_runtime_errors` 无异常 + 至少 1 个失败/边界分支的证据 |\n\n### 折叠 + 非折叠双姿态强制（2026-09-09 拍板）\n\n**只要改动会经过 [gsy_adaptive_shell.dart](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/style/gsy_adaptive_shell.dart) / [navigator_utils.dart](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/utils/navigator_utils.dart) 的分栏分支（包括但不限于任何列表 → 详情、右列 detail navigator、`wrapListChild`、`canShowTwoPane` 门控、弹窗路由），冒烟证据必须覆盖两档硬件姿态，缺一档视为汇报不完整**：\n\n| 姿态 | 硬件切换命令 | 期望 layout | 必须留的证据 |\n|---|---|---|---|\n| **CLOSED（折叠）单栏 compact** | `adb -s <serial> emu fold` | `canShowTwoPane == false`，`Navigator.push` 到主 Navigator | 1 张截图 + widget tree 命中 + `get_runtime_errors` |\n| **OPENED（展开）双栏 expanded** | `adb -s <serial> emu unfold` | `canShowTwoPane == true`，走 `detailNavigatorKey` 到右列 | 1 张截图 + widget tree 命中 + `get_runtime_errors` |\n\n规则细则：\n\n- **必须走 `adb emu fold` / `adb emu unfold` 硬件姿态**（Android SDK platform-tools 内置 [Emulator Console](https://developer.android.com/studio/run/emulator-console) 命令），不允许用 `wm size` / `wm density` / `mediaQuery.override` 等伪造分档手段。\n- 每次跑完必须 `adb -s <serial> emu unfold` **把设备复位回展开姿态**（default identifier=2 OPENED），否则下一位 author 会拿到一台以为是\"标准 Pixel Fold\"的折叠设备做基线，属于 author 责任事故。\n- 姿态切换后必须重新拉一次 `widget_inspector get_widget_tree`，确认 `Row` 承载的双 Navigator 结构（expanded）或单 Navigator（compact）与预期一致，才能开始记录 pt.x 证据。\n- **例外**：纯模型 / 纯工具函数（分级表第 1 行）改动不涉及分栏路径的，可豁免双姿态；豁免必须在完成汇报里显式声明\"改动路径不经过 adaptive shell\"。\n- 历史教训：2026-09-09 Wave 1 首次冒烟只做了 CLOSED 单栏一档就报进度，被作者当场纠正。此后凡是提交带 UI 冒烟证据的 PR，两姿态截图 + widget tree + `get_runtime_errors` 必须齐备。\n\n### 完成汇报三段式（必填）\n\n**看代码**：改了哪些文件、哪些函数、为什么这么改。\n**看编译**：`flutter analyze` / `build_runner` / `gen-l10n` 的产物结论。\n**看运行**：设备 id + 平台（iOS Simulator UDID 或 Android device serial）、`mcp_dart` DTD/VM Service URI、`get_runtime_errors` 结果、`widget_inspector` 命中的关键 widget/textPreview、**截图文件绝对路径**、无法覆盖的分支列表。\n\n三段任一缺失 = 任务未完成。\n\n### 禁止行为\n\n- ❌ 只跑 `flutter analyze` 就报\"测试通过\"\n- ❌ 把\"app 启动到首页\"当成本次功能验证\n- ❌ 让用户手动操作 UI 代替 author 自测（除非物理上无法自动化，且已说明原因）\n- ❌ 拿\"日志里没 Exception\"当作行为正确的证据——必须配 `mcp_dart` `widget_inspector` 命中或截图佐证\n- ❌ 只把截图放在自己上下文里而不写路径，导致 reviewer 拿不到证据\n- ❌ **只用 `adb` / `xcrun simctl` 而不连 `mcp_dart`**：截图工具不能承担业务验证职责（2026-09-02 拍板），至少要连一次 DTD 拿一次 `get_runtime_errors`，否则视为汇报不完整\n- ❌ **新增 `adb shell input tap/swipe` 坐标脚本**（2026-09-02 拍板）：这类脚本硬编码分辨率、锁死 Android、随 UI 微调即坏。旧脚本会被逐步替换成 `mcp_dart` 方案，新脚本一律不允许\n- ❌ **使用 `flutter install` 装机**：该命令内部走 `adb uninstall <package>` +\n  `adb install`，会把 `/data/data/<pkg>/` 下所有数据（含 SharedPreferences 里的\n  `TOKEN_KEY`）一起抹掉，等同把用户从 fixture 账号强制登出。\n  正确姿势：`flutter build apk --release --target-platform=android-arm64 --no-shrink`\n  后走 `adb install -r <apk 路径>`（`-r` 表示 reinstall，保留 app 数据）；iOS 走\n  `xcrun simctl install` 或 Xcode 装机。\n  历史教训：2026 装 discussions 冒烟版本时用了 `flutter install`，导致\n  CarSmallGuo 的 gho\\_ token 从设备上被清空，reviewer 侧无法直接复核，属于\n  **author 责任事故**。此后但凡装机脚本 / 命令里出现 `flutter install`，\n  reviewer 一律直接打回。\n- ❌ **在文档 / commit message / code comment 里编造 VM Service / mcp_dart\n  时序细节**（2026-09-03 拍板）：不许写\"evaluate 会同步塞进 build/layout/paint\n  半程\"、\"addPostFrameCallback 一定在当前帧末尾跑到\"这类未在\n  [Dart VM Service Protocol](https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md)\n  或 [api.flutter.dev](https://api.flutter.dev/) 官方文档里出现的因果。\n  历史教训：commit `224a0d8` body 编造 `evaluate` 同步注入回调栈，把\n  `_smokePostFrame` 定性成\"根因修复\"，实际上 `evaluate` 是走 isolate 事件循环\n  排队执行，`_smokePostFrame` 只是防御性保底；该编造在 `ed7077e` / `8106529`\n  连续订正，并在\n  [tool/ai/smoke/README.md `## 历史勘误（errata）`](file:///Users/guoshuyu/workspace/flutter-work/gsy_github_app_flutter/tool/ai/smoke/README.md)\n  章节留档（用章节名定位，避免行号漂移）。原则：**写不确定的时序 / 语义细节\n  前先查一次官方 spec，不能\"根据经验\"猜**。\n\n## 当前已知约束\n\n- 仓库既有 `test/`（纯 Dart 单测 + widget test）也有 `patrol_test/`（Patrol 集成测试脚手架，pubspec 中已引入 `patrol`）；两条测试路径并存，按改动性质挑合适档次\n- CI 使用 GitHub Actions，当前偏重构建成功\n- 项目同时使用 Redux、Riverpod、Provider、Signals\n- OAuth 登录相关流程依赖本地 `ignoreConfig.dart`\n- **GSY 是 GitHub 的轻量作者 + 阅读客户端**：产品语义上是\"看仓库 / 读 issue-PR-discussion\"为主，\n  但**允许用户在自己或已授权的仓库里做已实现的写操作**（发 issue、发 discussion、发评论、reaction、star/watch、resolve review thread 等，见下文清单）。\n  历史上曾把口径写成\"只读 + 评论客户端\"是根据\"AI 冒烟阶段\"的观察反推的口径，\n  与代码里既有的 [IssueRepository.createIssueRequest](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/repositories/issue_repository.dart#L428-L440)\n  等入口不符，2026-09-08 拍板订正。\n\n### 允许 / 禁止的写操作清单\n\n> 状态：作者已于 2026-07-06 首次拍板，2026-09-08 二次订正（把\"针对能力\"改成\"针对目标\"）。修改需在 PR 描述里显式提出并同步 `docs/00-overview/roadmap.md §4.1`。\n\n**判断口径（2026-09-08 订正，读清单前先看这段）**：\n\n- **写操作是否允许，判断的是\"针对谁 / 为了什么\"，不是\"是不是 write endpoint\"**：\n  - GSY 提供的产品能力（发 issue / 发 discussion / 发评论 / reaction / star / watch / mark thread resolved 等）→ **允许用户对任意有权限的仓库做**，这是 app 的正常使用；\n  - **AI / 开发者做冒烟或回归测试** → **不允许往 `CarGuo/*` 主仓或任何不属于自己 / 未经明确授权的第三方仓库塞测试数据**；允许在 AI / 开发者本人名下的测试仓库、fork、sandbox 里造数据，但**必须在 fixture 表里登记该仓库**以便 reviewer 复核。\n- 仓库运维类（改分支保护 / webhook / rerun workflow / Projects V2 卡片状态等）与 GSY 的产品定位无关，**始终禁止**，不因主体而放松。\n- 若未来需要新增一条允许项（新的产品能力），需要在 PR 描述里显式提出，并同步更新本清单与 roadmap §4.1。\n\n**允许（GSY 已实现或即将实现的产品能力，用户对**有权限**的仓库都可以做）**：\n\n- Issue / Comment 上加/取消 reaction\n- Issue / PR / Discussion 下发评论\n- **新建 issue**（[IssueRepository.createIssueRequest](file:///d:/workspace/project/gsy_github_app_flutter/lib/common/repositories/issue_repository.dart#L428-L440) 早就在做，`_createIssue` 按钮在仓库详情页右下角）\n- **新建 discussion**（2026-09-08 新增，见 [discussion_list_page.dart](file:///d:/workspace/project/gsy_github_app_flutter/lib/page/discussion/discussion_list_page.dart) 里的 FAB + `createDiscussion` mutation）\n- Notify 标记已读 / 标记 done / unsubscribe\n- 关注 / 取消关注仓库（star / watch 切换）\n- **PR review thread mark as resolved / unresolved（2026-07-06 新加）**：仅操作层，不做\"未 resolved thread 计数\"这类仪表盘\n- **编辑自己发的 issue body / comment 内容（2026-07-06 新加）**：服务端会按作者身份校验，不会误伤他人；范围**限编辑**，不含删除\n\n**明确禁止（越界的作者行为 / 仓库运维行为）**：\n\n- **AI / 开发者为了造冒烟数据往主仓 `CarGuo/*` 或任何第三方非本人授权的仓库写入**（issue / PR / discussion / comment 都算），2026-09-08 订正口径：这里禁止的是\"造数据的目的和目标\"，不是\"能力\"\n- 新建仓库 / fork 仓库（fork 视图目前只做**读**，动作没做）\n- **新建 PR**（暂未做，且合入前会引入较高审查开销，本轮暂不打开）\n- 提交 / dismiss review\n- 合并 PR / close issue / lock conversation\n- 修改仓库设置 / 分支保护 / webhook / secret\n- **GitHub Actions workflow rerun / cancel（2026-07-06 拍板归入禁止）**：属于仓库运维行为，GSY 不介入；用户如需 rerun 请去 GitHub 官网或官方 app\n- **Projects V2 卡片移动 / 状态字段编辑（2026-07-06 拍板归入禁止）**：等同协作作者视角编辑，与只读 + 评论定位冲突；连 Projects V2 阅读也一并搁置\n- **删除自己发的 issue / comment**：即使 API 支持，也不做——避免\"误删无法恢复\"的用户投诉面\n\n\n## 真机验证专用 fixture（写死，不允许随手换）\n\n以下 PR/仓库是真机冒烟脚本 [tool/ai/smoke/](file:///Users/guoshuyu/workspace/flutter-work/gsy_github_app_flutter/tool/ai/smoke) 默认命中的证据源。改动 timeline 相关代码时，优先复用它们；只有在明确覆盖不到时才另外找 PR。\n\n- **fixture 账号**：`CarSmallGuo`（当前 iOS 模拟器 / Android 设备与 gh cli 登录的账号，token 有 `repo` 权限但**只做读**）\n- **fixture 仓库**：`CarGuo/gsy_github_app_flutter`（GSY 主仓库自身）\n- **fixture PR**：\n  - `#938`：Copilot 提交的 `reviewed / state=commented`，body 788 字符，同时覆盖 `ready_for_review` / `review_requested` / `assigned` / `merged` / `closed` / 未知事件兜底等新事件类型\n    - 打开方式：`flutter run` 起 app 后走 `mcp_dart` `dtd connect` + `widget_inspector` 观测；参考 [tool/ai/smoke/open_pr_timeline.md](file:///Users/guoshuyu/workspace/flutter-work/gsy_github_app_flutter/tool/ai/smoke/open_pr_timeline.md) 的路径描述（人肉操作 UI 或用 `vm_service eval` 触发路由）\n    - 期望证据：`widget_inspector get_widget_tree` 命中包含 `textPreview: Copilot 提交了评审意见` 的事件行 + 其下方灰底 body 卡片（`Card` widget），并附一张 `xcrun simctl io screenshot` 截图\n  - 后续若需要 approved/changes_requested body 场景，请在 `CarGuo` 名下的其它 PR 里挑选并把 PR 号写回本段落，不要造 PR\n\n### 按功能分类的 fixture 表（2026-07-06 扩展）\n\n上面 fixture PR 只覆盖 PR timeline 一个场景。为了让**开新功能前先看有没有可用测试数据**（避免走到装机验证才发现 fixture 空——Discussions 就吃过这个亏），把 GSY 各功能域可用的真实数据源固化如下。变更本表需附带该行 API 探针的实测结果。\n\n**探针复核方式**：所有 `✅` 项都用 CarSmallGuo 的 gho\\_ token 在 `2026-07-06` 实测过；每半年可用 [docs/00-overview/roadmap.md §3.5 探针结果快照](file:///d:/workspace/project/gsy_github_app_flutter/docs/00-overview/roadmap.md) 那批命令重跑一次防止过时。\n\n**Fixture 优先级**：主仓 [CarGuo/gsy_github_app_flutter](https://github.com/CarGuo/gsy_github_app_flutter) > CarGuo 其他仓库 > CarSmallGuo 数据 > AI/开发者自有测试仓库（**必须登记**，见下文 Discussion 行示例） > 外部真实仓库（`flutter/flutter` / `defunkt` 等，**必须标注为\"外部妥协项\"**）。**禁止往主仓或第三方非授权仓库造冒烟数据**（口径见 §允许 / 禁止的写操作清单 2026-09-08 订正）。\n\n| 功能域 | 首选 fixture | 备注 |\n|---|---|---|\n| PR timeline 词典化事件 | 主仓 `#938` | 见上文，已固化 |\n| PR review thread（line-level comment） | 主仓 `#938`（2 条：`android/app/build.gradle` L41 / L84 by Copilot） | 探针实测 |\n| Issue assignee 挂件 | 主仓 `#938`（assignees=`CarGuo,Copilot`） | 主仓 issue 只有 #938 有 assignee，其余是 (none) |\n| Issue 长 timeline / 分页 | 主仓 [`#13`](https://github.com/CarGuo/gsy_github_app_flutter/issues/13) | README 明示\"所有运行问题请点这里\" |\n| Issue comment reactions | 主仓 [`#643`](https://github.com/CarGuo/gsy_github_app_flutter/issues/643) | README 里的\"登录失败\"高流量 issue |\n| **Discussion 创建 / 回复冒烟（2026-09-08 新加）** | ✅ 已登记：[`CarSmallGuo/gsy-smoke-2026q3`](https://github.com/CarSmallGuo/gsy-smoke-2026q3)（fixture 账号自有，创建时 `has_discussions=true`），首条 [`#1 Smoke test 2026-09-09`](https://github.com/CarSmallGuo/gsy-smoke-2026q3/discussion/1)（category=General，含 1 条真机回复 \"reply smoke\"） | 2026-09-09 真机走通 create + comment 端到端（expanded 双栏），证据见 `tool/ai/smoke/evidence/2026090{8,9}-discussion-*.png`。禁止用主仓 `CarGuo/gsy_github_app_flutter` 承接；造数据只允许在该自有测试仓库；用完不删（AGENTS.md 禁止删除自己发的内容） |\n| Release 详情 / reactions | 主仓 `releases`（`8.0.0` 已在真机日志出现） | 本轮真机 `versionName 8.0.0` |\n| Compare 视图 | 主仓 `423c762...bf557aa`（本轮实际 commit） | |\n| Contributors / Stargazers / Watchers | 主仓（★15461） | |\n| Repo Language 分布 / Community Health | 主仓（Dart+Java+ObjC+Kotlin，含 LICENSE / README） | |\n| Repo Branches 切换 | 主仓多分支 | |\n| Repo Fork 网络事件流 | 主仓 `/networks/CarGuo/gsy_github_app_flutter/events` | |\n| Repo Milestones | ⚠️ **主仓无**（探针 count=0）→ 外部妥协项 `flutter/flutter` | |\n| Repo Topics chip | ⚠️ 需重跑探针 → 若空退到 `flutter/flutter` | |\n| User Pinned Repositories | CarGuo（GSYVideoPlayer / gsy\\_github\\_app\\_flutter / GSYGithubAppCompose / GSYGithubAPP / GSYGithubAppKotlin / gsy\\_flutter\\_book） | 探针实测 6 个 pinned |\n| User 贡献日历 | CarGuo（活跃开发者 calendar 稠密） | |\n| User Following/Followers 交集 | CarGuo × CarSmallGuo | 天然双账号 |\n| User 公共 Gists | ⚠️ **CarGuo 无**（探针 count=0）→ 外部妥协项 `defunkt`（3 条非空） | |\n| Org 详情页 / Org 事件流 | 外部妥协项 `flutter` / `dart-lang`（CarGuo 是个人账号，非组织） | |\n| Notifications 仓库级筛选 | 主仓（CarSmallGuo 30 条 subscribed 都指向主仓） | |\n| Notifications reason chip | ⚠️ CarSmallGuo 当前 reason 单一（`subscribed x 28 / manual x 2`）→ 需 mention / review\\_requested 场景时另找 | |\n| Search Labels | 主仓 `repository_id=142308181`（本轮真机日志已确认） | |\n| Search Commits / Topics | 全站搜索无需锁 fixture | 主仓也在结果集 |\n| Rate Limit 诊断 | 当前 token 自查（无需 fixture） | 本轮实测 core 4997/5000 |\n</yakaai_skills>"}