Files
awoooi/docs/superpowers/specs/2026-04-19-aider-watch-design.md
Your Name 8ce8efad29 docs(aider-watch): v2 設計稿 — 完全整合 awoooi AI 自主化飛輪
統帥 2026-04-20 指示「C 路線 + 甲 bot」— v1 獨立個人工具路線與
awoooi MASTER blueprint 全景割裂,違反 feedback_ai_autonomous_direction
北極星(純記錄非自主化)。v2 重新對齊:

- DB:進主 PG,新 migration adr091 的 aider_events 表
- Telegram:走既有 telegram_gateway @tsenyangbot + Redis dedup
- Incident:aider error 自動建 incident 走既有告警鏈
- AI 學習回路:symptom_pattern 抽取 + AI Router feedback hook
- Mac client:薄殼 HTTP POST + 本機 JSONL fallback buffer

v1 產物去向:events.py/redactor.py 搬進 awoooi;其他廢棄。
@NemoTronAwoooI_Bot 轉 sandbox 用,不刪。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 04:04:13 +08:00

15 KiB
Raw Blame History

aider-watch 設計稿v1 — DEPRECATED 2026-04-20

已被 v2 取代 — 統帥於 2026-04-20 指出此設計未對齊 awoooi AI 自主化飛輪全景, 獨立 PG + 獨立 bot + 獨立 repo 路線與 MASTER blueprint 割裂。 新版 spec2026-04-20-aider-watch-v2-design.md v1 保留僅供歷史對照;實作已停在 ~/aider-watch Task 58 commits

統帥本機 aider CLI 的全程監控系統
2026-04-19 Brainstorm 定稿,經統帥 §1-§7 逐段批准


背景與目標

統帥 2026-04-19 於本機安裝 aider 0.86.2via pipxPython 3.11.7),搭配 OpenRouter 免費 stealth 模型 openrouter/openrouter/elephant-alpha。aider 無原生 webhook/event stream若不加監控aider 自主工作時統帥無法掌握:改了什麼檔、花了多少 token、有沒有卡住、有沒有失敗。

目標aider 每次執行都被完整記錄 + 重要事件即時推播到 Telegram DM + 每日/每週自動彙總。故障時 aider 本身不受影響graceful degradation

非目標

  • 不整合 SignOz / awoooi telemetryaider 是個人工具,避免綁死 awoooi infra
  • 不做 Web dashboard本次先不碰未來再議
  • 不監控其他人/其他機器(單機 single-user

§1 架構總覽

  統帥下指令
     │
     ▼
  aiderw (Python wrapper, 取代 `aider`)
     │
     ├─ 啟 subprocess: aider [統帥的參數...]
     │
     ├─ stdout tee ──────► [A] 即時解析 → 即時 events
     │                        • session_start (開場)
     │                        • silent_timeout (卡住 >2min)
     │                        • error (stderr / 異常 pattern)
     │
     └─ 退出時 ──────────► [B] 讀 .aider.chat.history.md + git diff
                              → canonical events
                              • file_edit (每檔 +N -M + 3 行 diff)
                              • commit (若 auto-commit=true)
                              • session_end (duration, tokens, cost, exit_code)

  [A] + [B] 合流 → Event Dispatcher
     │
     ├─► Telegram Bot → DM chat_id 5619078117
     ├─► Postgres 192.168.0.188/aider_watch
     │      └ 離線時 fallback → ~/aider-watch/buffer/*.jsonl
     ├─► ~/aider-watch/sessions/YYYY/MM/DD/<sid>.{jsonl,md}   (source-of-truth)
     └─► ~/aider-watch/live.log  (append plain text, tail -F 用)

  Launchd Jobs (獨立於 aiderw):
     • com.awoooi.aider-watch.flush    每 5 min 補寫 PG (buffer → PG)
     • com.awoooi.aider-watch.daily    每日 23:50 → 日報
     • com.awoooi.aider-watch.weekly   週日 22:00 → 週報

關鍵設計決定

  1. 雙層 event 來源stdout (即時性) + chat_history.md (準確性) 互補,不怕 aider 改 TUI 格式
  2. PG 非強依賴Mac 離線時 JSONL 做緩衝,連線恢復自動補寫 → aider 不因 PG 掛掉而失敗
  3. JSONL 是 source-of-truth:即使 PG 資料毀了,靠 JSONL 可 replay 重建
  4. 報表工作解耦3 支 launchd job 獨立 Python script彼此壞不影響

§2 元件分工

5 個獨立單元,每個有明確單一職責、介面、依賴:

# 檔案 職責 介面 依賴
1 aider_watch/wrapper.py 主入口fork aider subprocess、tee stdout、寫 live.log、發 event 到 dispatcher、exit 時讀 chat_history.md 重建 canonical events CLI: aiderw [args] → 透明轉發給 aider退出碼同 aider subprocess, pexpect
2 aider_watch/events.py Event 資料契約dataclass/pydantic 定義 7 種 event type、序列化/反序列化 SessionStart, FileEdit, Error, Commit, SilentTimeout, SessionEnd, Raw pydantic
3 aider_watch/storage.py 雙寫儲存:優先寫 PGPG 不可達時 fallback buffer/*.jsonl;提供 flush_buffer() 清空 write_event(ev), write_session(s), flush_buffer() psycopg2, json
4 aider_watch/telegram.py Telegram 客戶端格式化、rate-limit、429 指數退避、失敗不 raiseaider 不能卡) send(event) -> bool requests
5 aider_watch/reporter.py 報表:從 PG 撈資料 → 產日/週報 Telegram markdown daily(), weekly() psycopg2

CLI subcommands

aiderw [aider args...]           # 包 aider 跑
aider-watch report daily         # 立即產日報 (launchd 呼叫)
aider-watch report weekly        # 立即產週報 (launchd 呼叫)
aider-watch flush                # 手動 flush buffer (launchd 呼叫)
aider-watch doctor               # 檢查 PG 連線、Telegram 通道、設定檔
aider-watch replay <session_id>  # 從 JSONL 重建事件到 PG

單元資料流

wrapper ──events──► events ──validated──► storage ──► PG / JSONL buffer
                              └──────────► telegram ──► Bot API
                              └──────────► live.log (tee via wrapper)
reporter ──query PG──► telegram

重要wrapper 絕不直接碰 PG 或 Telegram只發 event 給 dispatcher。dispatcher 可 mock 測。


§3 Event Schema

7 種 event type統一外殼 + type-specific payload

{
  "ts": "2026-04-19T22:05:33+08:00",
  "session_id": "01J7XZ...",
  "type": "session_start",
  "payload": { ... }
}
type 來源 payload
session_start stdout (aider banner) {cwd, model, aider_args, aider_pid, cli_version}
file_edit 退出後 git diff + chat_history.md {path, lines_added, lines_deleted, diff_head, is_new_file}
error stderr + exit code ≠ 0 + "error" pattern {kind: "api_rate_limit|api_auth|build_fail|unknown", message, context_50chars}
commit 退出後 git log 比對 {sha, message, author, files[]}
silent_timeout wrapper 監測stdout >120s 無輸出 {idle_sec, last_output_tail}
session_end subprocess 結束 {duration_sec, tokens_sent, tokens_received, cost_usd, files_changed, error_count, exit_code}
raw debug 用,壓縮 stdout 片段 {chunk: "..."} — 不推 Telegram只寫 JSONL

Telegram 訊息範例(對應事件 1-5, 8事件 6/7 user/assistant message 刻意不推):

🚀 aider 啟動
repo: /Users/ogt/awoooi
model: elephant-alpha
pid: 12345

✏️ edit apps/api/src/jobs/foo.py (+12 -3)
    @@ def scan_targets():
    +    logger.info("starting")
    -    pass

❌ error: api_rate_limit — OpenRouter 429 on elephant-alpha

📌 commit abc123: "fix: handle null auth"
    → apps/api/src/auth.py, apps/api/tests/test_auth.py

⏸️ aider 靜默 2 分鐘 (last: "Tokens: 4.2k sent")

🏁 session 結束
⏱️ 5m32s | 🎯 exit=0
📊 12.4k↑ / 3.8k↓ tokens | $0 (Elephant Alpha)
📝 改 3 檔 | ❌ 0 error

時區:所有 timestamp 以台北時區 +08:00 儲存(feedback_timezone_taipei.md)。


§4 Postgres Schema

Database: aider_watch(獨立於 awoooi 其他 DB同 PG instance 192.168.0.188:5432
User: aider_watch(只在此 DB 有權限,不碰 K3s datastore 或其他 awoooi DB

-- ===== Sessions一場 aider 執行 =====
CREATE TABLE sessions (
  id            TEXT PRIMARY KEY,              -- ULID
  started_at    TIMESTAMPTZ NOT NULL,
  ended_at      TIMESTAMPTZ,
  cwd           TEXT NOT NULL,
  model         TEXT NOT NULL,
  aider_args    TEXT[],
  aider_pid     INTEGER,
  duration_sec  INTEGER,
  tokens_sent   INTEGER DEFAULT 0,
  tokens_recv   INTEGER DEFAULT 0,
  cost_usd      NUMERIC(10,6) DEFAULT 0,
  files_changed INTEGER DEFAULT 0,
  error_count   INTEGER DEFAULT 0,
  exit_code     INTEGER,
  host          TEXT DEFAULT 'ogt-mac'
);
CREATE INDEX sessions_started_idx ON sessions(started_at DESC);
CREATE INDEX sessions_cwd_idx ON sessions(cwd);

-- ===== Events每個細粒度事件 =====
CREATE TABLE events (
  id            BIGSERIAL PRIMARY KEY,
  session_id    TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
  ts            TIMESTAMPTZ NOT NULL,
  type          TEXT NOT NULL,
  payload       JSONB NOT NULL
);
CREATE INDEX events_session_ts_idx ON events(session_id, ts);
CREATE INDEX events_type_idx ON events(type);
CREATE INDEX events_ts_idx ON events(ts DESC);

-- ===== File touches每次檔案變更展平便於 top-N 查詢 =====
CREATE TABLE file_touches (
  id            BIGSERIAL PRIMARY KEY,
  session_id    TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
  path          TEXT NOT NULL,
  touched_at    TIMESTAMPTZ NOT NULL,
  lines_added   INTEGER DEFAULT 0,
  lines_deleted INTEGER DEFAULT 0,
  is_new_file   BOOLEAN DEFAULT FALSE
);
CREATE INDEX ft_path_idx ON file_touches(path);
CREATE INDEX ft_session_idx ON file_touches(session_id);
CREATE INDEX ft_date_idx ON file_touches((touched_at::date));

為什麼不用純 event sourcing:查詢日報每次都要 aggregate events 太慢。sessions 表是派生摘要退出時一次寫。events 表原樣保留,萬一 session 摘要錯可從 events 重建。

日/週報查詢範例reporter.py 用):

-- 今日統計
SELECT count(*) AS sessions, sum(duration_sec) AS total_sec,
       sum(tokens_sent) AS toks_sent, sum(files_changed) AS files,
       sum(error_count) AS errors
FROM sessions WHERE started_at::date = CURRENT_DATE;

-- 今日 Top 3 最常改的檔
SELECT path, count(*) AS touches, sum(lines_added) AS adds, sum(lines_deleted) AS dels
FROM file_touches WHERE touched_at::date = CURRENT_DATE
GROUP BY path ORDER BY touches DESC LIMIT 3;

§5 檔案佈局

~/aider-watch/                             # 主工作目錄
├── bin/aiderw                             # 入口 shim
├── aider_watch/                           # Python 套件
│   ├── __init__.py
│   ├── wrapper.py
│   ├── events.py
│   ├── storage.py
│   ├── telegram.py
│   ├── reporter.py
│   └── cli.py
├── sessions/                              # source-of-truth永遠寫
│   └── 2026/04/19/
│       ├── 01J7XZABC_awoooi_12345.jsonl
│       └── 01J7XZABC_awoooi_12345.md
├── buffer/                                # PG 離線時 fallback
│   └── pending_01J7XZABC.jsonl
├── live.log                               # append-only 純文字流
├── state.json                             # 健康狀態
└── logs/                                  # launchd job stdout/stderr
    ├── flush.log
    ├── daily.log
    └── weekly.log

~/.aider-watch.env                         # chmod 600
    AIDER_WATCH_DATABASE_URL=postgresql://aider_watch:<PW-安裝時生成>@192.168.0.188:5432/aider_watch
    AIDER_WATCH_TELEGRAM_TOKEN=<bot token - 已確認 @NemoTronAwoooI_Bot>
    AIDER_WATCH_TELEGRAM_CHAT_ID=5619078117
    AIDER_WATCH_HOSTNAME=ogt-mac

~/Library/LaunchAgents/
├── com.awoooi.aider-watch.flush.plist     # 每 5 min
├── com.awoooi.aider-watch.daily.plist     # 每日 23:50
└── com.awoooi.aider-watch.weekly.plist    # 每週日 22:00

/opt/homebrew/bin/aiderw → ~/aider-watch/bin/aiderw   # PATH 入口 symlink

PATH 策略

  • aiderw → symlink → Python 包 → subprocess aider
  • 原生 aider 仍可直接呼叫繞過監控debug 用)
  • 可選shell alias alias aider=aiderw,不強制

§6 錯誤處理 / 降級策略

失敗情境 處理 結果
PG 188 不可達 buffer/pending_<sid>.jsonlstate.jsonlast_flush_fail_tslaunchd flush job 每 5min 重試 資料不丟
Telegram 429 指數退避 3 次1s→4s→16s失敗→ live.log + 旗標進下次日報 aider 絕不卡住
Telegram 網路 timeout 同上,最多 5s 同上
aider subprocess 崩潰 / OOM wrapper 收 SIGCHLD → 寫 session_end exit_code≠0 error_count≥1 + Telegram 推 error 死得清楚
wrapper 自己崩潰 atexit handler 強寫 session_endwrapper_crash=true 半份紀錄仍有
stdout 解析漏掉 event 退出時 chat_history.md + git diff 補上 canonical events 準確性不犧牲
secrets 外洩 ~/.aider-watch.env chmod 600payload 偵測 sk-or-*ghp_*AIza* pattern → 遮罩 <redacted> 符合 feedback_secrets_leak_incidents_2026-04-18.md
Launchd job 死 KeepAlive=true + stderr 寫 logs/*.logdoctor 指令檢查狀態 aider-watch doctor 一眼看出
PG DB 被手動刪 aiderw 啟動時 ping PG + 必要表 → 缺表自動跑 migration內建 schema.sql 自癒
磁碟爆滿 sessions/ JSONL+MD 保留 90 天,更早壓縮成 YYYY-MM.tar.gz 移到 archive/PG 不設 retention資料量小查詢歷史統計用doctor 顯示本機磁碟用量 有上限

核心原則aiderw 的任何故障絕不能讓 aider 跑不起來。wrapper 外層 try/except 全包最壞情況只是「監控失效」aider 本身 pass-through 照跑。


§7 測試策略

遵循 feedback_no_mock_testing.md(禁 mock盡量用真服務。

層級 範圍 怎麼跑 通過標準
Unit events.py schema、telegram.py 格式化、secret redactor pytestfixtures 不 mock 欄位 100% 正確、redactor 6 種 token pattern 全中
Integration: PG storage.py 真寫獨立的 aider_watch_test DB非 prod aider_watch 內的 schema 跑真 PG 188測完 DROP session/event/file_touches round-trip
Integration: Telegram 真發訊息到 chat_id 5619078117 前綴 [TEST] Telegram API 回 ok=true、收到訊息
E2E: aider session aiderw --message "print hello" --exit 跑真 aider /tmp/aider-smoke TG 收 🚀+🏁PG sessions 有 1 筆JSONL 產生
E2E: 斷線降級 ifconfig en0 down/up;跑 aiderw恢復網路跑 flush 手動 buffer 產生 → flush 後 PG 補齊、buffer 清空
E2E: 日/週報 aider-watch report daily/weekly PG 有至少 1 筆 session TG 收到日/週報,統計正確
Doctor aider-watch doctor 正常 + 刻意破壞(停 PG / 刪 env / 刪 plist 全綠 / 破壞時精準指出問題

關鍵:第一次上線必跑 E2E 斷線降級測試。這是整個設計最容易壞的地方。


相關 Memory 依據

  • feedback_timezone_taipei.md — timestamp +08:00
  • feedback_no_mock_testing.md — 測試不 mock
  • feedback_secrets_leak_incidents_2026-04-18.md — secret 遮罩 + chmod 600
  • feedback_change_annotation_standard.md — 檔案變更註解標準(實作時加入)
  • reference_aider_setup.md — aider 本機安裝現況
  • reference_four_hosts.md — PG 位於 192.168.0.188:5432
  • reference_secrets_architecture_v2.md — secret 4 層架構(本工具走 L4 本機 env file

後續

本設計稿批准後,進入 writing-plans 階段產出逐步實作計畫(建立 DB/user、寫 Python 套件、裝 launchd、E2E 驗收 ...)。