# 团队自动日报（ops 入口）

**核心目的**：不是汇报，是**从每天每个团队成员跟 Claude Code 的对话里挖工作流可优化点**。

每天 Beijing 00:05 由 Gitea workflow 触发，中心化为团队成员各跑一份日报，结果同时落 git（事实归档）和 Gitea issue（讨论入口）。

## ⚠️ 知情同意 / 隐私

**cron 上线前必须做的事**：每位被加入 `DAILY_REPORT_USERS_CONFIG` 的团队成员**必须被明确告知数据流**：

> 你的 `~/.claude/projects/*.jsonl`（含你跟 Claude Code 的对话全文）会在每天 Beijing 00:05 被 act_runner 通过 SSH 拉取，喂给 Haiku 4.5 提炼日报，结果会 push 到 `chore/daily-reports-rolling` 分支（团队内可见）+ post 到 Gitea issue。

**opt-out 永远生效**：任何人随时 `touch ~/.claude-insight-optout`，下次 cron 会跳过你（不读 jsonl、不出日报、不留传输 cache）。

**敏感词预过滤**：常见 secret（API key / GitHub PAT / FFAI token / JWT / PEM private key）会在抽取阶段命中整 turn 丢弃，不进 L2、不喂 LLM、不出现在日报里。但**这只是第二道保险**——不要把"AI 没扫到"当作"对话里可以贴 secret"的理由。

被纳入 config 而不知情者不应该出现。这条不是技术问题，是团队协作前置。

## 架构（v5）

```
.gitea/workflows/daily-report.yml         触发：cron '5 16 * * *' UTC = Beijing 00:05
       ↓ runs-on self-hosted runner
scripts/ops/daily-report/run_all.py       中心 dispatcher
       │
       ├─ 解析 secret DAILY_REPORT_USERS_CONFIG（JSON 数组）
       │
       ├─ for each user in config:
       │   1. ssh user@host: tar c 当日 ~/.claude/projects/<encoded-workspace>/*.jsonl
       │   2. 本地 extract_signals.py（L1+L2 信号筛，无 LLM）
       │   3. 本地 gather.sh OVERRIDE_EMAIL=... SKIP_CC_SESSIONS=1（git log + Gitea PR）
       │   4. 本地 run.py --user-override → claude --print → markdown
       │   5. Gitea contents API push 到 chore/daily-reports-rolling
       │   6. Gitea issues API append 到本周聚合 issue
       │
       └─ 任一 user 失败 → log + 跳过 + 继续下一个（fail-soft）

客户端机器（团队成员的开发机）
└── 唯一要求：~/.claude/projects/ 下有 jsonl，可被 runner ssh 进来
    （不需要：claude CLI / cron / Gitea token / python3）
```

## 数据落地

### Git（事实归档，全文）

落到长期分支 **`chore/daily-reports-rolling`**（永不 merge 回 develop），跟现有 `chore/notes-rolling` 同款约定：

```
daily-reports/<user>/YYYY-MM-DD.md
```

Gitea web 渲染：`http://43.130.59.228/FFAIWorkspace/workspace/src/branch/chore/daily-reports-rolling/daily-reports/<user>/<date>.md`

### Gitea Issue（讨论入口，每周聚合）

每人每周一个 issue，labels: `daily-report` + `user/<git-username>`，标题：

```
[daily-report] chentao-jia · 2026-W21
```

cron 每天 append 一条 comment 含完整日报正文 + 链到 git md 文件 + @ 自己（push 通知）。

数量预估：10 人 × 52 周 = ~520 issue/年，远低于"每日 issue"方案（3650/年）。

## Phase 0 一次性 setup（merge 后由 chentao 完成）

### 1. Gitea secrets（仓库 Settings → Secrets）

```
DAILY_REPORT_USERS_CONFIG = '[{"name":"chentao-jia","email":"chentao.jia@ff.com","ssh_host":"chentao@<dev-host>","workspace_path":"~/Code/workspace"}, ...]'

AI_REVIEW_GITEA_TOKEN  → 复用现有的 AIBot token（已配）
```

users config 字段：
- `name`（必填）：git username，落地路径用 / Gitea label 用
- `email`（必填）：`git log --author` 过滤用
- `ssh_host`（必填）：`ssh <ssh_host>` 能直接连；本机测试可写 `@local`
- `workspace_path`（选填，默认 `~/Code/workspace`）：远端 workspace 路径，用于推算 jsonl 编码目录

### 2. Runner 服务器（**只在 dedicated-runner-1 上配**）

workflow 用 `runs-on: ubuntu-latest` + 显式 hostname check step pin 到 **dedicated-runner-1**（实测仓库 runner 列表：dedicated-runner-1 online + workspace-runner offline）。调度到其他 ubuntu-latest runner 会立刻 fail-fast，不会静默跑空。所以下面所有配置**只在 dedicated-runner-1 一台机器**做：

- claude CLI 已装并 `sudo -u act_runner claude login`（OAuth credential 在 `/var/lib/act_runner/.claude/.credentials.json`，跟 ai-review 共用）
- 为 `act_runner` user 生成 ssh key：`sudo -u act_runner ssh-keygen -t ed25519 -N ''`
- 把 `/var/lib/act_runner/.ssh/id_ed25519.pub` 分发到每个团队成员开发机的 `~/.ssh/authorized_keys`
- 测试每条线路：`sudo -u act_runner ssh <user>@<host> echo ok`

中期改进：让 admin 给 dedicated-runner-1 加独占 label（比如 `daily-cron`），然后改 yaml `runs-on: [ubuntu-latest, daily-cron]` + 删 hostname check step。tracking 见本 PR description 或后续 issue。

### 3. 首次 push（如需）

如果首跑时 Gitea contents API 报 `branch not found`，手工建一次空分支：

```bash
git push origin develop:chore/daily-reports-rolling
```

### 4. 手动验证

启用 cron 前先手动跑一次：

```bash
# Gitea workflow 页面 → daily-report → Run workflow
# 选 dry-run=true（不写 Gitea），看 markdown 输出
# 通过 → 正式跑（dry-run=false）→ 看 issue 创建是否正常
```

## 触发方式

| 方式 | 用法 |
|---|---|
| 自动（每天） | cron `5 16 * * *` UTC = Beijing 00:05 |
| 手动 | Gitea Actions UI → daily-report → Run workflow，可指定 date / only-user / dry-run |
| 命令行（runner 上） | `python3 scripts/ops/daily-report/run_all.py --date 2026-05-20 --dry-run` |
| 本地调试单 user | `python3 scripts/ops/daily-report/run_all.py --only-user chentao-jia --dry-run` |
| 跳过单条调试某段 | 直接调 `run.py`、`extract_signals.py` 或 `gather.sh` 看中间产物 |

## opt-out

任一团队成员可在自己机器：

```bash
touch ~/.claude-insight-optout
```

run_all 会 ssh 检测这个 flag，存在即跳过该 user（不读 jsonl、不出日报）。

## 敏感词预过滤

`extract_signals.py` 内置黑名单（API key / GitHub PAT / FFAI token / JWT / PEM private key）。命中整 turn 丢弃，不入 L2、不喂 LLM、不进日报。
要扩 → 改 `REDACT_PATTERNS`。

## 文件清单

| 文件 | 职责 |
|---|---|
| `run_all.py` | 中心 dispatcher：parse users → SSH 拉 jsonl → 编排各步 |
| `run.py` | 单 user 日报生成：调 LLM + push git + post issue（接外部喂入数据） |
| `extract_signals.py` | L1 jsonl 抽取 + L2 信号筛（无 LLM） |
| `gitea_comment.py` | 找/建本周聚合 issue，POST 当日 comment |
| `.agents/skills/daily-report/scripts/gather.sh` | 复用 skill 脚本，支持 OVERRIDE_EMAIL / SKIP_CC_SESSIONS env |

## 信号筛规则（无 LLM 层）

`extract_signals.py` 规则：
- `edit_repeat`：同 file 在 10 turn 窗口内 Edit ≥ 3 次（同 file 取 max count）
- `tool_error_streak`：连续 ≥ 2 turn 含 `is_error=true` 的 tool_result
- `long_gap`：turn_gap > 30 分钟（每 session 取最长一次）
- `frustration`：user message 含 "为什么/不对/重做/还有别的/再想想..." 等 token
- `ask_cluster`：assistant 在 ≤ 5 turn 内 ≥ 2 次 AskUserQuestion

每个信号点附 ±3 turn 上下文喂 LLM。

## 成本

| 项 | 估算 |
|---|---|
| Haiku per session insight (~25k token in, 2k out) | ~$0.005 |
| 每人每天 5-10 session | ~$0.03 |
| 10 人 / 月 | ~$9 |

可忽略。

## 排障

| 现象 | 排查 |
|---|---|
| 单 user 整段缺失 | runner 上 `sudo -u act_runner ssh <ssh_host> echo ok` 测连通 |
| 「对话洞察」段空 | extract_signals 信号 0，检查 jsonl 是否真有当日内容 |
| Gitea push 失败 | `chore/daily-reports-rolling` 分支是否存在；token 是否有 write 权限 |
| issue post 失败 | 看 run.py log；md 已写到 rolling 分支，下次跑会重 post |
| LLM 失败 | claude CLI envelope 错误塞 stdout 不是 stderr，看 step log |

## Phase 3：周聚合（本 PR 未实现）

`scripts/ops/workflow-insight/weekly_cluster.py` + `.gitea/workflows/workflow-insight-weekly.yml` 留待下次 PR：
- 每周一 09:13 跑
- 读过去 7 天每人的日报 + L2 信号缓存
- Sonnet 4.6 跨人聚类
- 在本周 `[daily-report]` issue 里 append 总结 + 单独开 issue 跟踪 actionable items
