# GSY GitHub App Flutter 协作说明

这是一个 Flutter GitHub 客户端，同时也是带有教学展示性质的示例工程。
不要假设整个仓库是单一架构风格。多种状态管理方案并存是当前设计现实，不是偶然脏代码。

## 进入仓库后先读

在进行非微小改动前，先读这些文件：

1. `README.md`
2. `docs/README.md`
3. `docs/00-overview/project-map.md`
4. `docs/00-overview/roadmap.md`
5. `docs/01-architecture/app-layering.md`
6. `docs/01-architecture/state-management-matrix.md`

## 工作规则

- 改动尽量限制在当前功能域，不要顺手做跨模块重构。
- 非任务明确要求时，不要迁移状态管理框架。
- 优先遵循目标模块现有模式，而不是引入新的全局规范。
- 未经明确允许，不得为了满足需求擅自替换模块既有框架或状态实现，即使替换后看起来更简单。
- `*.g.dart`、多语言生成文件、env 生成文件都视为生成产物；优先重新生成，不要手改。
- 不要提交密钥。`lib/common/config/ignoreConfig.dart` 属于本地或 CI 环境材料。

## 高风险目录

- `lib/app.dart`：应用根装配、导航、全局报错、Redux 和 Riverpod 混合接线
- `lib/common/net/`：共享网络栈、拦截器、GraphQL/REST 入口
- `lib/common/repositories/`：功能数据访问边界
- `lib/env/`：构建期环境配置和生成文件
- `lib/common/localization/`：ARB 与多语言生成输出

## 建议修改策略

- 纯 UI 任务：优先只改页面或局部 widget，不碰全局状态
- API 任务：同时检查 `common/net` 与对应 `common/repositories`
- 状态任务：沿用该模块当前已有状态方案，除非任务明确要求迁移
- 配置/构建任务：同步更新 `docs/03-runbooks/` 中的操作说明

## Review 规则

- 中等以上改动默认使用独立 reviewer 上下文
- 每次非微小代码改动后，默认拉起新的 reviewer subagent 或新的干净上下文审查刚刚的修改
- author 在通过一轮新的 reviewer subagent 审查前，不应直接宣告代码任务完成
- reviewer 不应复用 author 的完整上下文历史
- 先看 `docs/05-ai/review-harness.md`
- reviewer 提示模板见 `docs/05-ai/prompts/reviewer-system.md`
- `tool/ai/build_review_bundle.ps1` 只是可选辅助，不是主流程

## 本地最小验证

按改动范围选择最小验证集合：

- `flutter pub get`
- `dart run build_runner build --delete-conflicting-outputs`
- `flutter gen-l10n`
- `flutter analyze`
- `flutter build apk --release --target-platform=android-arm64 --no-shrink`

说明：

- 改模型、env、注解生成代码时跑 `build_runner`
- 改 ARB 或本地化输入时跑 `flutter gen-l10n`
- 改 Android 构建、依赖或运行时关键路径时跑 APK 构建

## 运行时冒烟验证（强制）

**"能编译过 + 装机不崩" 不算测试通过。**
任何涉及运行时行为（UI 渲染、事件解析、状态流转、网络分支、多语言文案）的改动，
在宣告完成前，author 必须在真机或模拟器上跑通对应改动路径，
并把真实证据（截图 / 文案 dump / 错误日志）**以文件形式产出并写清路径**，
禁止只凭"app 启动了、日志没红"就报完成。

强制的是**证据本身**，不是具体工具。工具链按可用性挑：

### 工具选型（按可用性挑一条主路径 + 一条 fallback）

1. **主路径 — `mcp_dart`（debug 构建、DTD 在线时首选）**
   - `dtd` → `listDtdUris` / `connect` 建立 DTD 连接
   - `widget_inspector` → `get_widget_tree` / `get_selected_widget` 抓真实渲染出的 widget 树
   - `get_runtime_errors` 拉 Dart 层异常（改动前后各拉一次）
   - `flutter_driver_command` 只在工程已引入 `flutter_driver` / `integration_test`
     并且 `main.dart` 里显式调用 `enableFlutterDriverExtension()` 时才可用。
     本仓库目前**未引入这套依赖**，因此该子工具默认不可用，不得作为唯一验证手段。

2. **Fallback — `adb`（release 构建、DTD 掉线、driver 未启用时的一等公民）**
   - `adb devices` 确认设备可见
   - `adb shell input tap/swipe/text/keyevent` 驱动 UI
   - `adb exec-out screencap -p > /tmp/xxx.png` 抓真实渲染截图
   - `adb logcat -d -s flutter` 抓 Dart 侧输出
   - **截图文件路径必须写进完成汇报**，reviewer 需要能拿到该路径复核。
   - 反复用到的 tap 序列必须沉淀到 [`tool/ai/smoke/`](tool/ai/smoke/)，
     不允许把已经跑通的坐标序列扔在一次对话里就丢掉。

3. **Web / DevTools 类改动**：用 `integrated_browser` 走同样"操作 → 截图 → 抓日志"流程。

### 强制流程

1. `flutter run` 或已装机的 build 起到已连接设备。
   **重要：装机一律用 `adb install -r <apk路径>`，禁止使用 `flutter install`。**
   原因见下方"禁止行为"条目——`flutter install` 内部会先执行 `adb uninstall`，
   把应用数据（含 SharedPreferences 里的 `TOKEN_KEY`）一并清空，等同强制登出，
   会直接毁掉真机 fixture 状态。
2. 按上表挑工具组合抓证据；`mcp_dart` 不可用时直接切 `adb`，不视为破例。
3. 走一遍改动**直接覆盖**的路径（例：改了 PR timeline 事件行 → 真的进 PR 详情页把
   timeline 滚一遍，抓到目标文案的截图或 `get_text` 输出）。
4. 覆盖不到的分支（例：`base_ref_force_pushed` 真机上罕见）必须在完成汇报里
   **显式列为已知缺口**，不能糊成"通过"。稀有分支覆盖率无法靠真机保证时，
   优先考虑加模型层单测 + 真实 JSON fixture 补齐。

### 分级要求（避免形式主义）

| 改动类型 | 最低证据要求 |
|---|---|
| 纯模型 / 纯工具函数 | `flutter analyze` + 单测（若 test 目录已存在）；无需截图 |
| UI 渲染 / 文案 / 事件行 | 至少 1 张真机截图 + `get_runtime_errors` 或 `logcat` 空异常 |
| 关键路径（登录 / 网络栈 / 根装配 / 状态边界） | 主路径截图 + 至少 1 个失败/边界分支的证据 |

### 完成汇报三段式（必填）

**看代码**：改了哪些文件、哪些函数、为什么这么改。
**看编译**：`flutter analyze` / `build_runner` / `gen-l10n` 的产物结论。
**看运行**：设备 id、所用工具组合（mcp_dart 或 adb）、**截图文件绝对路径**、
`get_runtime_errors` 或 `logcat` 结果、无法覆盖的分支列表。

三段任一缺失 = 任务未完成。

### 禁止行为

- ❌ 只跑 `flutter analyze` 就报"测试通过"
- ❌ 把"app 启动到首页"当成本次功能验证
- ❌ 让用户手动操作 UI 代替 author 自测（除非物理上无法自动化，且已说明原因）
- ❌ 拿"日志里没 Exception"当作行为正确的证据
- ❌ 只把截图放在自己上下文里而不写路径，导致 reviewer 拿不到证据
- ❌ **使用 `flutter install` 装机**：该命令内部走 `adb uninstall <package>` +
  `adb install`，会把 `/data/data/<pkg>/` 下所有数据（含 SharedPreferences 里的
  `TOKEN_KEY`）一起抹掉，等同把用户从 fixture 账号强制登出。
  正确姿势：`flutter build apk --release --target-platform=android-arm64 --no-shrink`
  后走 `adb install -r <apk 路径>`（`-r` 表示 reinstall，保留 app 数据）。
  历史教训：2026 装 discussions 冒烟版本时用了 `flutter install`，导致
  CarSmallGuo 的 gho\_ token 从设备上被清空，reviewer 侧无法直接复核，属于
  **author 责任事故**。此后但凡装机脚本 / 命令里出现 `flutter install`，
  reviewer 一律直接打回。

## 当前已知约束

- 仓库既有 `test/`（纯 Dart 单测 + widget test）也有 `patrol_test/`（Patrol 集成测试脚手架，pubspec 中已引入 `patrol`）；两条测试路径并存，按改动性质挑合适档次
- CI 使用 GitHub Actions，当前偏重构建成功
- 项目同时使用 Redux、Riverpod、Provider、Signals
- OAuth 登录相关流程依赖本地 `ignoreConfig.dart`
- **GSY 是 GitHub 的只读 + 评论客户端**，不承担写 PR / 提交 review / 建仓库这类"作者行为"。冒烟或回归时**禁止通过 gh cli 或 GitHub API 新建仓库、造 PR 或提交 review**去伪造证据，一律用既有仓库里的真实数据

### 允许 / 禁止的写操作清单

> 状态：作者已于 2026-07-06 拍板转正，正式约束。修改需在 PR 描述里显式提出并同步 `docs/00-overview/roadmap.md §4.1`。

**允许（已在做且不打算收回）**：

- Issue / Comment 上加/取消 reaction
- Issue / PR / Discussion 下发评论
- Notify 标记已读 / 标记 done / unsubscribe
- 关注 / 取消关注仓库（star / watch 切换）
- **PR review thread mark as resolved / unresolved（2026-07-06 新加）**：仅操作层，不做"未 resolved thread 计数"这类仪表盘
- **编辑自己发的 issue body / comment 内容（2026-07-06 新加）**：服务端会按作者身份校验，不会误伤他人；范围**限编辑**，不含删除

**明确禁止（越界的作者行为）**：

- 新建仓库 / fork 仓库
- 新建 issue / PR / discussion
- 提交 / dismiss review
- 合并 PR / close issue / lock conversation
- 修改仓库设置 / 分支保护 / webhook / secret
- 通过 gh cli 或 GitHub API 制造冒烟数据
- **GitHub Actions workflow rerun / cancel（2026-07-06 拍板归入禁止）**：属于仓库运维行为，GSY 不介入；用户如需 rerun 请去 GitHub 官网或官方 app
- **Projects V2 卡片移动 / 状态字段编辑（2026-07-06 拍板归入禁止）**：等同协作作者视角编辑，与只读 + 评论定位冲突；连 Projects V2 阅读也一并搁置
- **删除自己发的 issue / comment**：即使 API 支持，也不做——避免"误删无法恢复"的用户投诉面

**判断口径**：

- 判断依据是"是否让 GSY 用户在 GitHub 上产生新数据 / 修改他人内容 / 触发仓库运维"，不是"API 是不是 write endpoint"
- 若未来需要新增一条允许项，需要在 PR 描述里显式提出，并同步更新本清单与 roadmap §4.1


## 真机验证专用 fixture（写死，不允许随手换）

以下 PR/仓库是真机冒烟脚本 `tool/ai/smoke/open_pr_timeline.sh` 默认命中的证据源。改动 timeline 相关代码时，优先复用它们；只有在明确覆盖不到时才另外找 PR。

- **fixture 账号**：`CarSmallGuo`（当前 adb 设备与 gh cli 登录的账号，token 有 `repo` 权限但**只做读**）
- **fixture 仓库**：`CarGuo/gsy_github_app_flutter`（GSY 主仓库自身）
- **fixture PR**：
  - `#938`：Copilot 提交的 `reviewed / state=commented`，body 788 字符，同时覆盖 `ready_for_review` / `review_requested` / `assigned` / `merged` / `closed` / 未知事件兜底等新事件类型
    - 打开方式：`bash tool/ai/smoke/open_pr_timeline.sh`（默认参数即可）
    - 期望截图证据：`/tmp/gsy_smoke_07_issue_detail_scrolled.png` 与随后一屏能同时看到"Copilot 提交了评审意见"这一行**和其下方灰底 body 卡片**
  - 后续若需要 approved/changes_requested body 场景，请在 `CarGuo` 名下的其它 PR 里挑选并把 PR 号写回本段落，不要造 PR

### 按功能分类的 fixture 表（2026-07-06 扩展）

上面 fixture PR 只覆盖 PR timeline 一个场景。为了让**开新功能前先看有没有可用测试数据**（避免走到装机验证才发现 fixture 空——Discussions 就吃过这个亏），把 GSY 各功能域可用的真实数据源固化如下。变更本表需附带该行 API 探针的实测结果。

**探针复核方式**：所有 `✅` 项都用 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) 那批命令重跑一次防止过时。

**Fixture 优先级**：主仓 [CarGuo/gsy_github_app_flutter](https://github.com/CarGuo/gsy_github_app_flutter) > CarGuo 其他仓库 > CarSmallGuo 数据 > 外部真实仓库（`flutter/flutter` / `defunkt` 等，**必须标注为"外部妥协项"**）。**禁止造数据**同 §允许 / 禁止的写操作清单。

| 功能域 | 首选 fixture | 备注 |
|---|---|---|
| PR timeline 词典化事件 | 主仓 `#938` | 见上文，已固化 |
| PR review thread（line-level comment） | 主仓 `#938`（2 条：`android/app/build.gradle` L41 / L84 by Copilot） | 探针实测 |
| Issue assignee 挂件 | 主仓 `#938`（assignees=`CarGuo,Copilot`） | 主仓 issue 只有 #938 有 assignee，其余是 (none) |
| Issue 长 timeline / 分页 | 主仓 [`#13`](https://github.com/CarGuo/gsy_github_app_flutter/issues/13) | README 明示"所有运行问题请点这里" |
| Issue comment reactions | 主仓 [`#643`](https://github.com/CarGuo/gsy_github_app_flutter/issues/643) | README 里的"登录失败"高流量 issue |
| Release 详情 / reactions | 主仓 `releases`（`8.0.0` 已在真机日志出现） | 本轮真机 `versionName 8.0.0` |
| Compare 视图 | 主仓 `423c762...bf557aa`（本轮实际 commit） | |
| Contributors / Stargazers / Watchers | 主仓（★15461） | |
| Repo Language 分布 / Community Health | 主仓（Dart+Java+ObjC+Kotlin，含 LICENSE / README） | |
| Repo Branches 切换 | 主仓多分支 | |
| Repo Fork 网络事件流 | 主仓 `/networks/CarGuo/gsy_github_app_flutter/events` | |
| Repo Milestones | ⚠️ **主仓无**（探针 count=0）→ 外部妥协项 `flutter/flutter` | |
| Repo Topics chip | ⚠️ 需重跑探针 → 若空退到 `flutter/flutter` | |
| User Pinned Repositories | CarGuo（GSYVideoPlayer / gsy\_github\_app\_flutter / GSYGithubAppCompose / GSYGithubAPP / GSYGithubAppKotlin / gsy\_flutter\_book） | 探针实测 6 个 pinned |
| User 贡献日历 | CarGuo（活跃开发者 calendar 稠密） | |
| User Following/Followers 交集 | CarGuo × CarSmallGuo | 天然双账号 |
| User 公共 Gists | ⚠️ **CarGuo 无**（探针 count=0）→ 外部妥协项 `defunkt`（3 条非空） | |
| Org 详情页 / Org 事件流 | 外部妥协项 `flutter` / `dart-lang`（CarGuo 是个人账号，非组织） | |
| Notifications 仓库级筛选 | 主仓（CarSmallGuo 30 条 subscribed 都指向主仓） | |
| Notifications reason chip | ⚠️ CarSmallGuo 当前 reason 单一（`subscribed x 28 / manual x 2`）→ 需 mention / review\_requested 场景时另找 | |
| Search Labels | 主仓 `repository_id=142308181`（本轮真机日志已确认） | |
| Search Commits / Topics | 全站搜索无需锁 fixture | 主仓也在结果集 |
| Rate Limit 诊断 | 当前 token 自查（无需 fixture） | 本轮实测 core 4997/5000 |




