Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ InfluenceX (https://influencexes.com) is an **invite-only AI marketing platform*
- `server/agents-v2/` — 18 LLM agents (strategy, research, content-text/visual/voice/video, kol-outreach, publisher, ads, community, etc.)
- `server/agent-runtime/` — agent registry + Conductor (goal → plan → run)
- `server/llm/` — Anthropic + OpenAI + Gemini + 火山方舟 routing layer with cache + cost stats
- `server/__tests__/` — 377 Node test runner unit tests (frontend has 4 Vitest component tests in `client/src/components/*.test.jsx`)
- `server/__tests__/` — 678 Node test runner unit tests across 71 files; frontend has 13 Vitest files under `client/src/{components,pages,utils}/` and 5 Playwright tests across 3 specs in `e2e/tests/`
- `client/` — Vite + React 18 SPA (HashRouter); `client/src/pages/*.jsx` is one page per route
- `docs/` — see file links above
- `deploy.sh` / `migrate-env-to-secret.sh` / `setup-secrets.sh` — Cloud Run + Secret Manager helpers
Expand Down Expand Up @@ -104,8 +104,11 @@ NOT `influencex.com`. Don't typo this in copy, env vars, OAuth redirect URIs, or
preview_start influencex # server on :8080 (SQLite if no DATABASE_URL)
preview_start influencex-client # Vite dev on :5173 (HMR)

# Tests (server only — no frontend tests yet)
npm test # 234 unit tests, ~3s
# Tests
npm test # 678 server unit tests, ~1s
npm test -- --test-concurrency=1 # serialized; use when SQLITE_BUSY flakes (see memory.md §5.2)
cd client && npx vitest run # 13 component/page/util test files
npx playwright test # 5 e2e tests across 3 specs in e2e/tests/

# Build client
cd client && npx vite build # outputs client/dist/
Expand All @@ -121,7 +124,8 @@ cd client && npx vite build # outputs client/dist/

# Connect to prod Postgres via Cloud SQL Auth Proxy (read-only investigation)
cloud-sql-proxy --port 5434 gameclaw-492005:us-central1:influencex-db &
# password is in .env DATABASE_URL (postgres user)
# There is NO local .env — pull the postgres password from Secret Manager:
gcloud secrets versions access latest --secret=DATABASE_URL --project=gameclaw-492005
node -e "const {Client}=require('pg');..." # one-off queries
```

Expand Down Expand Up @@ -186,7 +190,7 @@ Each prod deploy increments the Cloud Run revision number (`influencex-NNNNN-xxx
## Debugging Production

- **Cloud Run logs:** `gcloud run services logs read influencex --region=us-central1 --limit=50`
- **Postgres queries:** start `cloud-sql-proxy --port 5434 gameclaw-492005:us-central1:influencex-db` then connect with any pg client. Password is in `.env` DATABASE_URL
- **Postgres queries:** start `cloud-sql-proxy --port 5434 gameclaw-492005:us-central1:influencex-db` then connect with any pg client. There is no local `.env` — get the password from `gcloud secrets versions access latest --secret=DATABASE_URL --project=gameclaw-492005` (delete any copy you write to disk)
- **Sentry** — wired server-side (`server/sentry.js`) + client-side since `891f209` (needs `SENTRY_DSN`)
- **OpenTelemetry** — wired via `server/otel.js` since `8f00ad1` (needs OTLP endpoint env)

Expand All @@ -198,8 +202,8 @@ See [docs/memory.md](docs/memory.md) for the full list. Highlights:
- **`hakko-q1-all` is a legacy demo campaign**: server seeds it on first boot. Don't use as a fallback in new code — use `defaultCampaignForWorkspace()` instead.
- **Pipeline ↔ Contact dual flow**: solved by `pipeline_jobs.contact_id` link + worker reverse-sync. UI shows the same row from both pages.
- **Hunter.io fallback** only works for KOLs with a linked website on their channel page. No-website KOLs need a paid Hunter Email-Finder plan + known domain.
- **Frontend has no tests** — Sprint 2 task C1+C2 will add Playwright + Vitest. Until then, manual smoke after every feature.
- **`subscriptions` table is dormant** — left from removed Stripe billing. Don't query.
- **`brand_voices` is empty in production** (verified 2026-08-09, 0 rows). Brand-voice similarity was inert until `0c5323b`, but nothing was lost — the first voice created after the next deploy embeds correctly. Note the embedding is written **only on create** (`POST /api/brand-voices`); there is no update route, so any future edit endpoint must re-embed.

## Session Safety

Expand Down
26 changes: 18 additions & 8 deletions docs/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
| **Service** | `influencex` (Cloud Run) |
| **Cloud SQL Instance** | `influencex-db` (Postgres 15) |
| **Cloud SQL Connection** | `gameclaw-492005:us-central1:influencex-db` |
| **DB user / password** | `postgres` / 见 `.env` `DATABASE_URL`(不在此处明文) |
| **DB user / password** | `postgres` / 见 Secret Manager 的 `DATABASE_URL`(**本地没有 `.env`**,见 §5.5) |
| **Database name** | `influencex` |
| **Image registry** | `gcr.io/gameclaw-492005/influencex:latest` |
| **OAuth callback base** | `https://influencexes.com` |
Expand All @@ -37,7 +37,8 @@
```bash
cloud-sql-proxy --port 5434 gameclaw-492005:us-central1:influencex-db &
# 然后用任何 pg client 连 localhost:5434
# password 从 .env DATABASE_URL 提取
# password 从 Secret Manager 取(本地没有 .env):
gcloud secrets versions access latest --secret=DATABASE_URL --project=gameclaw-492005
```

**注意**:本地常驻一个 cloud-sql-proxy 指向另一个项目(`dimbluedot:us-central1:luddi-pg`,端口 5433)。我们要的是 5434 端口。
Expand Down Expand Up @@ -215,7 +216,15 @@ node --test server/__tests__/email-jobs.test.js
node --test --test-name-pattern "specific test" server/__tests__/*.test.js
```

`npm test` 跑全套 234 个,~3 秒。
`npm test` 跑全套 **678** 个(71 个文件),~1 秒。前端另有 13 个 vitest 文件(`cd client && npx vitest run`)+ 5 条 Playwright / 3 个 spec(`npx playwright test`)。

**`SQLITE_BUSY` 偶发失败不是你的改动坏了。** `npm test` 是 `node --test`,默认按文件并发,而整套测试共用仓库根目录的**同一个** `influencex.db`。并发写会随机让 2-5 个文件报 `{ code: 'SQLITE_BUSY' }`,每次挂的文件还不一样。判定方法:

```bash
node --test --test-concurrency=1 server/__tests__/*.test.js # 串行,~8 秒,稳定 678/678
```

串行绿 = 并发那几个是 flake。串行也红才是真的坏了。(在 main 上空跑也能复现,与任何 PR 无关。)

### 5.3 客户端 build 检查

Expand All @@ -233,15 +242,15 @@ build 失败通常是:
gcloud run services logs read influencex --region=us-central1 --limit=50 --project=gameclaw-492005
```

**没有 Sentry / OTEL**(Sprint 1 待加)。要查报错只能 grep Cloud Run 日志。
Sentry(`server/sentry.js`,客户端自 `891f209`)与 OpenTelemetry(`server/otel.js`,自 `8f00ad1`)**都已接入** —— 需要 `SENTRY_DSN` / OTLP endpoint 环境变量才会真正上报。没配的话仍然只能 grep Cloud Run 日志。

### 5.5 prod DB 一次性查询

```bash
cloud-sql-proxy --port 5434 gameclaw-492005:us-central1:influencex-db &
node -e "
const { Client } = require('pg');
const c = new Client({ host: 'localhost', port: 5434, user: 'postgres', password: '<在.env里>', database: 'influencex' });
const c = new Client({ host: 'localhost', port: 5434, user: 'postgres', password: '<见下方 gcloud 命令>', database: 'influencex' });
c.connect().then(async () => {
const r = await c.query('SELECT ...');
console.log(r.rows);
Expand All @@ -266,7 +275,7 @@ kill %1
| 无 frontend 测试 | 13 个文件 / 82 个 vitest + 5 条 Playwright,全部挂 CI(#12/#13) |
| 无 Sentry / OTEL | 早已接入;依赖冲突后遗症 #7 收尾 |
| In-process job queue 多副本丢消息 | BullMQ API 修复 + 发送原子抢占 + 进程级异常兜底(#10) |
| pgvector 启用但 agent 没用 | 实际是**接了但从未工作**(embed 调用契约错,`findBestBrandVoice` 永远返回 null)→ #20 修复 |
| pgvector 启用但 agent 没用 | 实际是**接了但从未工作**(embed 调用契约错,`findBestBrandVoice` 永远返回 null)→ #20 修复。**2026-08-09 查过 prod:`brand_voices` 表 0 行**,所以坏了这么久也没丢数据,不需要 backfill |
| ContactModule 5s 轮询撞 429 | 后台刷新不再置 loading + 请求序号守卫(#9);限流器桶隔离(#15) |
| Hunter API 仅对有外链网站的 KOL 有效 | 仍然成立,但已是产品决策(付费 API 预算)而非 bug |

Expand All @@ -284,6 +293,7 @@ kill %1
| `content_daily_stats` 全局 UNIQUE(content_url, stat_date) | 跨工作区同 URL 同日第二条快照被静默跳过 | fail-closed,彻底解决需改约束 |
| Marketplace 无下架/申诉流程 | 撤一条 listing 只能手工 DELETE | provenance 列可定位,需配合创作者 opt-out |
| **design.md 与现状脱节** | §10.3 说焦点还原未实现、§12 硬编码 FUNNEL_COLORS、§8.3 modal 契约现已是组件 | #13 之后未同步 |
| **brand voice 只在创建时写 embedding** | 以后加编辑接口会留下过期向量 | `POST /api/brand-voices` 是唯一写点(`server/index.js:3419`),目前只有 GET/POST/DELETE,没有 update 路由所以暂时无害 |
| `ContactModule.jsx` 与 `PipelinePage.jsx` UI 重复 | 两个 page 显示相似数据 | 未评估 |

### 这轮学到的、值得记住的失效模式
Expand Down Expand Up @@ -323,7 +333,7 @@ type: `feat` / `fix` / `chore` / `docs` / `refactor`。scope: `discovery` / `out
- 小改动(i18n / 文案 / a11y) —— 不强求立刻 deploy,下次大改一起
- bug 修复 —— 立即 deploy(push + run `./deploy.sh`)
- 安全修复 —— 立即 deploy + 通知用户
- 大重构 —— 至少跑过 234 个测试 + 客户端 build + 本地 preview 烟测,再 deploy
- 大重构 —— 至少跑过全套 678 个服务端测试 + 客户端 build + 本地 preview 烟测,再 deploy

### 7.4 push 前 checklist

Expand Down Expand Up @@ -363,7 +373,7 @@ type: `feat` / `fix` / `chore` / `docs` / `refactor`。scope: `discovery` / `out

---

**Last reviewed:** 2026-08-09 (post `#20`, main green: server 656 / client 82 / e2e 5).
**Last reviewed:** 2026-08-09 (post `#24`, main green: server 678 / client 82 / e2e 5).
Prod revision unchanged since `00049-w2x` — **this batch has not been deployed yet**; see
[MASTER_PLAN_2026-08.md](./MASTER_PLAN_2026-08.md) §5 for the pre-deploy checklist (the startup
contract changed: MAILBOX_ENCRYPTION_KEY now fail-fast, webhooks fail-closed without secrets).
Loading