thread-master/AGENTS.md

413 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Agent Handoff Notes
這個資料夾是新的巡樓後端核心,請優先維持乾淨邊界,不要把舊 Next.js 或 `template-monorepo` 的業務包袱搬進來。
## 核心原則
- 全系統時間一律 **UTC+0**;寫入 Mongo / API 的時間欄位一律 **unix nanoseconds**`int64`)。排程的 `timezone` 只用於 cron 解讀與下發 payload不作為儲存時區。
- 複製模式,不複製舊業務。
- `logic` 做 API 編排,`model/usecase` 做可重複使用能力。
- provider adapter 不讀 setting、不碰 Mongo、不知道 HTTP。
- setting 是通用 key-value model不依賴 AI 或其他業務。
- token / API key 第一版每次 request 帶入,不寫入 config。
- SSE contract 由本服務 normalize前端不要讀 provider 原始 chunk。
- JSON API 必須使用 `code/message/data/error` envelope 與 `SSCCCDDD` 錯誤碼。
- 列表 API 必須使用 `page/pageSize` query並在 `data` 回傳 `pagination/list`
- Job 狀態轉移必須使用 guarded/conditional update不要在 API/worker 直接裸 `Update` 覆蓋 job 狀態。
- Redis job lock 的 value 是 `workerID`release / refresh 必須檢查 owner長任務必須 heartbeat。
- Auth 目前是 native email/password + JWT不包含 OAuth / OTP / MFA / Zitadel。不要為了相容 template-monorepo 把重依賴搬進來。
- AI provider token 與會員 JWT 是兩種不同 tokenAI token 每次 request header 帶入,會員 JWT 由 `/api/v1/auth/*` 簽發。
## 設計文件
- `docs/job-system-plan.md`:通用 job system 規劃,包含 template、run、schedule、Redis queue/lock、取消語意與 API 草案。
- `docs/scan-placement-plan.md`:海巡獲客(流程 B— 知識圖譜、Brave 擴展、雙軌爬取7 天重點 / 30 天補充)、產品匹配、島民交接。
## 新增 API 流程
1. 修改 `generate/api/*.api`
2. 優先使用 `make gen-api` 重新產生 handler/logic/types。
3. 若手寫 handler仍需遵守 `response.Write` 與 validator 流程。
4. SSE endpoint 不使用 `response.Write`,直接輸出 `text/event-stream`
5. 更新 `README.md` 的 API 與架構說明。
## Response / Error Code
錯誤碼格式是 `SSCCCDDD`
```text
SS = scope
CCC = category
DDD = detail
```
目前 scope
```text
10 = Facade
32 = Setting
33 = AI
34 = Job
35 = Auth
36 = Member
37 = Permission
```
建立錯誤時使用:
```go
errs.For(code.AI).InputMissingRequired("缺少 AI provider token")
errs.For(code.Setting).ResNotFound("找不到設定")
errs.For(code.Job).ResInvalidState("job state changed; update rejected")
errs.For(code.Auth).AuthUnauthorized("missing bearer token")
```
不要直接手寫 `33104000` 這種數字,也不要回傳裸 `error` 給 handler 後讓使用者看到內部錯誤。
## Pagination
列表 API 使用:
```text
?page=1&pageSize=10
```
response
```json
{
"code": 102000,
"message": "SUCCESS",
"data": {
"pagination": {
"total": 100,
"page": 1,
"pageSize": 10,
"totalPages": 10
},
"list": []
}
}
```
`page/pageSize` 必須是 server 正規化後的值。不要使用 `offset/limit/items`
## 新增 Model 流程
模組放在:
```text
internal/model/<module>/
domain/entity
domain/repository
domain/usecase
repository
usecase
```
依賴方向:
```text
handler -> logic -> model/domain/usecase
model/usecase -> model/domain/repository
model/repository -> Mongo / Redis
```
不要讓 `logic` import `model/<module>/repository`
## Auth / Permission 擴充
目前已接:
```text
POST /api/v1/auth/register
POST /api/v1/auth/login
POST /api/v1/auth/refresh
POST /api/v1/auth/logout # requires member JWT
GET /api/v1/members/me
PATCH /api/v1/members/me
GET /api/v1/permissions/catalog
GET /api/v1/permissions/me
```
Auth matrix`internal/handler/routes.go`
| 路由 | 需要會員 JWT |
|------|----------------|
| `GET /api/v1/health` | 否 |
| `POST /api/v1/auth/register/login/refresh` | 否 |
| `POST /api/v1/auth/logout` | 是(`Authorization` |
| `GET /api/v1/ai/providers` | 否 |
| `POST /api/v1/ai/chat/stream/models` | 是(`X-Member-Authorization`+ provider token`Authorization` |
| `/api/v1/members/*`、`/api/v1/permissions/me` | 是(`Authorization` |
| `/api/v1/permissions/catalog`、`/api/v1/settings/*`、`/api/v1/jobs*`、`/api/v1/job/*` | 是(`Authorization` |
規則:
- 保護路由用 `internal/middleware.Auth``Authorization: Bearer <access_token>`AI 變更路由用 `middleware.MemberAuth``X-Member-Authorization`),因 `Authorization` 保留給 provider API key。
- logic 從 `authctx.ActorFromContext``tenant_id` / `uid`
- 不要在 handler 直接 parse JWTtoken 驗證集中在 `model/auth/usecase`
- 密碼只存 bcrypt hash不回傳、不寫 log。
- `members.roles` 第一版是簡化 role key。正式 RBAC 可逐步補 roles collection但不要破壞 `role_permissions` 的 tenant + role_key contract。
- `Auth.DevHeaderFallback` 只給本機開發,正式環境應關閉。
## AI Provider 擴充
新增 provider 時:
1.`internal/model/ai/domain/enum` 新增 provider id。
2.`internal/model/ai/provider` 新增 adapter。
3.`internal/model/ai/usecase` registry 註冊 provider 與 models。
4. 確保 adapter 回傳統一 `StreamEvent`
5. 不要改 `logic/ai` 的 SSE 格式。
## Job Worker 擴充
新增 job step 時優先註冊 runner handler
```go
runner.RegisterStepHandler("analyze_8d", func(ctx context.Context, step job.StepContext) error {
if err := step.Heartbeat(ctx); err != nil {
return err
}
// do work, check cancel via job usecase if needed
return nil
})
```
規則:
- Handler 不要直接操作 Mongo / Redis透過 job usecase 更新進度、完成、失敗或取消。
- 長任務每個 checkpoint 呼叫 `StepContext.Heartbeat``RefreshRunLock`
- 收到 cancel signal 後呼叫 `AcknowledgeCancel(jobId, workerID)`,不要自行把狀態改成 `cancelled`
- release lock 時必須帶 `workerID`;不要新增無 owner 的 release helper。
## 前端設計規則(`web/`
巡樓 Console 前端在 `haixun-backend/web/`,視覺為**沉穩田園巡檢台**(動森感:天空、雲朵、奶油卡片、青綠 brand**不是**任天堂 UI 複製)。
**字體固定** Inter + Taipei Sans TC圖示僅 `AcIcon` / `AuthDecor` 內原創 SVG 線條圖。**禁止** emoji、貼圖 JPG、咖啡色木質頂欄、Nook / 任天堂命名。樣式集中在 `index.css``--hx-*` token + `hx-*` / `ac-*` / `auth-*` class
不要把舊 Next.js / `template-monorepo` UI 搬進來,也不要引入重型 UI 框架。
### 視覺架構(登入前後共用)
```text
全頁背景 .hx-scene 灰藍天空 → 淡草地單一漸層(淺/深各一套,見 index.css
裝飾層 SceneDecor 雲朵(多朵緩動)+ 淡光暈 + 小葉子;登入與 Layout 共用
奶油卡片 .auth-ticket 2px line 邊框、圓角 2rem、surface 底、可選 .ac-dialog-texture 點陣
頂部品牌列 圖示 .auth-ticket-iconbrand-soft 底)+ ink 標題;不用獨立色塊 ribbon
主內容 表單或 Outlet內文頁用 PageTitle / Card綠色 .ac-title-bar 僅內容區小標)
桌面側欄 .ac-pocket-device 掌上終端外框PATROL PAD 狀態列;固定尺寸 + .ac-pocket-scroll 內捲
手機 .ac-dock 底部最多 4 格 +「更多」sheet
```
| 區域 | 元件 | 關鍵 class |
|------|------|------------|
| 未登入 | `AuthShell` + `LoginPage` / `RegisterPage` | `hx-scene` `auth-scene` `auth-ticket` `auth-welcome` `auth-shell-form` |
| 已登入外殼 | `Layout` | `hx-scene` `ac-app-shell` `ac-app-header` `auth-ticket` `ac-app-main-inner` |
| 背景裝飾 | `AuthDecor.tsx``SceneDecor` | `hx-scene-deco` `auth-cloud--*` |
| 品牌小圖 | `AuthTicketIcon` | `auth-ticket-icon`(小屋+樹,原創 SVG |
| 側欄導覽 | `Layout` + `navApps` | `ac-pocket-device` `ac-app-tile` |
| 手機導覽 | `MobileBottomNav` | `ac-dock` |
**登入頁刻意不做的事**:上方不要獨立大色塊 header表單上方**不要**再放 `ac-title-bar`「登入」大牌(品牌已在 `auth-welcome`)。註冊頁同理可省略重複大標。
**已淘汰、勿加回**`ac-island`(改用 `hx-scene`)、`ac-wood-bar` / 咖啡色木質頂欄、`public/ac/` 貼圖、Nook Phone 文案。
### 技術棧與指令
```text
web/
src/
api/ # API clientenvelope、JWT refresh
auth/ # AuthContext
components/ # Layout、AuthShell、AuthDecor、ui、ThemeToggle、MobileBottomNav、AcIcon
theme/ # ThemeContext淺色 / 深色)
pages/ # 路由頁面
lib/ # acAssets導覽 icon key、jobStatus 等
index.css # 設計 token 與場景樣式唯一來源
```
```bash
make web-dev # dev server :5173proxy 到 :8890
make web-build # tsc + vite build
```
### 字型
| 語言 | 字型 | 載入方式 |
|------|------|----------|
| 繁體中文 | **台北黑體 Taipei Sans TC** | npm `taipei-sans-tc`,在 `index.css` `@import` Regular + Bold |
| 英文 | **Inter**(與 simular.co 相同Google Fonts 免費) | `web/index.html` link |
規則:
- `body` / 中文標題:`Inter` + `Taipei Sans TC` 混排(`--font-sans`)。
- 純英文裝飾字導覽副標、Hero 小字):加 class `display-en`,使用 `--font-en`
- 中文 `line-height` 維持 **1.7+**;不要用過細字重當標題(標題用 `font-bold` / `font-black`)。
- 只載入 Taipei Sans TC **Regular + Bold**,不要載入 Light避免小字過細。
- 不要改回 Noto Sans TC也不要手寫 `#333` 這類裸色碼當主色。
### 對比度與字級
- 內文、表頭、表單 label、卡片說明優先 `text-ink` / `text-ink-secondary`**不要**拿 `text-muted` 當主要閱讀文字。
- `text-muted` 只給次要提示筆數、hint、placeholder 用 `text-subtle`)。
- 表單輸入字級 **15px**`text-[15px]`),輸入框底用 `bg-surface` 白底,確保與背景拉開。
- 淺色 `muted``#5a6578`、深色約 `#b8c4d6`;改色時以「小字仍可舒適閱讀」為準,不要回到 `#94a3b8` 那種淡灰。
### 主題(淺色 / 深色)
- `ThemeProvider``src/theme/ThemeContext.tsx`)包住 App偏好存 `localStorage` key`haixun.theme``light` | `dark`)。
- `index.html` 內嵌 script 在 React 載入前設定 `data-theme`,避免閃爍。
- 所有顏色必須走 CSS 變數 `--hx-*`,再映射到 Tailwind `@theme``bg-canvas`、`text-brand` 等)。
- 切換按鈕用 `ThemeToggle``ac-btn-secondary` 樣式);`Layout` 頂欄與 `AuthShell` 右上角都要有。
- **禁止**在元件裡寫死 `bg-slate-*`、`text-emerald-*`、`bg-amber-*` 等 Tailwind 預設色;語意狀態用 `text-success` / `text-warning` / `text-danger``jobStatus.ts` 的 badge class。
淺色:低飽和灰藍天空 + 灰綠草地 + 奶油 `surface` + **brand 青綠**;深色:黃昏低對比、同一套 token 自動切換。頂欄與卡片內品牌區都用 **surface / ink / brand**,不要再用木色 `#c4a882` 當 header 底。
### 場景與卡片 class維護時對照
| Class | 用途 |
|-------|------|
| `.hx-scene` | 全頁天空→草地漸層(登入 + 已登入根節點) |
| `.hx-scene-deco` / `SceneDecor` | 背景雲、光暈、葉子(`pointer-events: none` |
| `.auth-ticket` | 奶油主卡片外框(登入卡、已登入主內容區) |
| `.auth-welcome` | 卡片內品牌列:圖示 + 標題 + 一句 tagline底部分隔線 |
| `.ac-app-header` | 已登入 sticky 頂欄:半透明 surface + blur**非**木色 |
| `.ac-title-bar` | 內容區綠色小標題(裝置色漸層);用於 `PageTitle` 等,**不**用於登入頁表單上方大牌 |
| `.ac-pocket-device` | 側欄掌上終端;`--pocket-width`28rem、`--pocket-screen-height` 固定,內容在 `.ac-pocket-scroll` 捲動 |
| `.ac-app-tile` / `.ac-dock` | App 格導覽、手機底欄 |
| `.auth-shell-form` | 登入/註冊表單放大字級(僅 auth 頁) |
側欄標示用 **PATROL PAD** 等中性英文裝飾字(`display-en`);圖示僅 `AcIcon` SVG。
### 色彩 token語意命名
開發時只用這些 Tailwind class值定義在 `web/src/index.css`
| Token | 用途 |
|-------|------|
| `canvas` | 全頁背景 |
| `surface` / `surface-muted` | 卡片、輸入框底 |
| `ink` / `ink-secondary` / `muted` | 主文 / 次文 / 輔助 |
| `line` | 邊框 |
| `brand` / `brand-hover` / `brand-soft` | 主 CTA、active 導覽、連結 hover |
| `glow` | 裝飾色塊(`.glow-blob-alt` |
| `success` / `warning` / `danger`(含 `*-soft` | 狀態、錯誤、Job badge |
主按鈕一律 `Button variant="primary"``bg-brand`,不要用全黑按鈕。
### 圓角與陰影
```text
--radius-sm 0.75rem 小元素、code
--radius-md 1.25rem Input / Textarea
--radius-lg 1.75rem Card
--radius-xl 2.25rem Hero、QuickLink、StatCard
--radius-pill 9999px Button、Badge、導覽 pill
```
陰影用 utility`shadow-card`(一般卡片)、`shadow-soft`主按鈕、Hero、`.auth-ticket`)。內容 Hero 可用 `ac-bulletin` + `ac-hero-gradient` token全頁裝飾雲朵走 `SceneDecor`,不要另加會打架的強色 blob。
### 共用元件(優先復用)
新頁面必須從 `src/components/ui.tsx` 組裝,不要另寫一套按鈕樣式:
| 元件 | 用途 |
|------|------|
| `PageTitle` | 頁面標題 + 副標 |
| `Card` | 內容區塊 |
| `Field` + `Input` / `Textarea` | 表單 |
| `Button` | `primary` / `ghost` / `danger` / `soft` |
| `Badge` | 標籤 pill`brand` / `sky` / `success` / `warning` / `danger` / `neutral` |
| `StatCard` / `QuickLinkCard` | 總覽統計與快捷入口 |
| `ErrorText` / `CopyableId` | 錯誤與可複製 ID |
`Button` 必須渲染 `{children}`;文案用**中文動詞**(例:「建立背景任務」「重新載入任務列表」),不要留空白小框。
### RWD手機
- `< lg`:隱藏左側欄;**底部固定導覽**最多 **4 格**(總覽 / 任務 / 排程 / **更多**),不要把漢堡或 ⋯ 選單放在左上角。
- 「更多」以底部 sheet 展開AI、模板、設定、會員、權限、主題切換、登出。
- 主內容加 `layout-main` 底部 padding避開 tab bar + `safe-area-inset-bottom`
- 寬表格包 `overflow-x-auto` + `min-w-*`,避免小螢幕擠爆版面。
### 版面與導覽
- 已登入(桌面):`Layout` = `hx-scene` 背景 + `SceneDecor` + `ac-app-header`(品牌 + 角色 chip + `ThemeToggle`+ 左 `ac-pocket-device` + 右 `auth-ticket` 主內容 `Outlet`
- 已登入(手機):同上頂欄;導覽走 `MobileBottomNav`(總覽/任務/排程/更多)。
- 側欄 App 來源:`src/lib/acAssets.ts` 的 `navApps`;圖示 key 對應 `AcIcon`
- Active 導覽:`ac-app-tile--active`brand-soft 底 + brand 字色hover`bg-brand-soft text-brand`。
- 未登入:`AuthShell` 置中 `auth-ticket` + 右上 `ThemeToggle``auth-welcome` 內品牌,表單緊接說明文字。
- 語氣:年輕、直接、短句;可帶「島民」「巡樓」等原創文案,避免企業八股與任天堂用語。
### API 與狀態
- JSON 一律走 `api/client.ts``code/message/data` envelope需登入加 `{ auth: true }`
- AI 路由用 `X-Member-Authorization`provider token 用 `Authorization`(見後端 Auth matrix
- Job 狀態中文與 badge 色:`src/lib/jobStatus.ts``jobStatusLabel` / `jobStatusBadgeClass`),列表有進行中任務時可每 3 秒 refresh。
- 不要在前端 parse JWT`uid` / `tenant_id``AuthContext` 讀。
### 新增頁面流程
1.`App.tsx` 掛路由(需登入的放在 `Layout` 底下,自動享有 `hx-scene` + 頂欄 + 主內容 `auth-ticket`)。
2. 頁面內用 `PageTitle`(含 `.ac-title-bar` 小標)+ `Card` / `ac-bulletin` + `ui.tsx` 元件;色票只引用 semantic token。
3. 若需新語意色,**先**改 `index.css``--hx-*``@theme`,再改元件;不要頁面內硬編色碼。
4. 新導覽項:改 `acAssets.ts``navApps`,並在 `AcIcon` 補 SVG path。
5. 完成後執行 `make web-build`
### 島民頁面互動(可推廣 runtime
掛在 `Layout` 底下的新頁面**自動**支援島民操作,不需每頁手寫 executor。
模組入口:`web/src/lib/islander/index.ts`
| 層 | 職責 |
|----|------|
| `pageSnapshot` | 掃描 `.ac-app-shell` 內可互動元素,產生 `hx-*` ref |
| `islanderActions` | 解析/剝除 `islander-actions` JSON 區塊 |
| `actionExecutor` | 執行 navigate/click/fill/select/scroll 等;可 `registerIslanderActionHandler` 擴充 |
| `islanderAgent` | 串流回覆 → 執行 action → 回傳結果 → 自動 follow-up |
| `buildIslanderContext` | 組裝送給後端的頁面快照 |
**零設定(預設)**:路由掛在 `Layout` 即可;島民讀 DOM + `PageTitle` / `h1` 辨識頁面。預設**不**主動介紹這一頁;僅在使用者明確問頁面/操作時才附【可互動元素】(`userWantsPageContext`)。
**可選增強**(擇一):
1. `useIslanderPage({ title, purpose, hints, suggestions })` — 頁面內動態註冊說明
2. `registerIslanderPage(/^\/foo/, { title, ... })` — 在 `siteGuide.ts` 或模組 init 靜態註冊
3. HTML 慣例:`data-islander-label`(元素名稱)、`data-islander-kind`(類型)、`data-islander-ignore`(排除)、`data-islander-page-title`(頁名)
Action 協定AI 回覆末尾):
```islander-actions
[{ "type": "navigate", "path": "/settings" }, { "type": "click", "ref": "hx-3" }]
```
### 前端禁忌
- 不要引入 MUI / Ant Design / Chakra 等大型 UI 庫。
- 不要為單頁新增第三套配色、木質頂欄、或漸層彩虹按鈕。
- 不要在登入/註冊頁加回獨立大牌 `ac-title-bar` 或咖啡色 header ribbon。
- 不要讓 SSE / AI 直接吃 provider 原始 chunk後端已 normalize
- 不要用 `offset/limit` 呼叫列表 API用 `page` / `pageSize`。
## 驗證
完成變更後至少執行:
```bash
cd haixun-backend
go mod tidy
make fmt
go test ./...
```
有動到前端時另執行:
```bash
make web-build
```