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
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Why: git classifies a whole file as binary if a NUL byte (0x00) appears in the
# first 8000 bytes. `git diff` then degrades to "Bin 9679 -> 10779 bytes" and
# `git blame` stops working entirely. That happened to server/usage-ledger.js
# (#27): a composite-key separator was written as a raw 0x00 instead of the
# (fixed in #30): a composite-key separator was written as a raw 0x00 instead of
# `\u0000` escape, and it survived review precisely because the file could not
# be diffed. Nobody was careless — a single pipeline that eats backslash escapes
# is enough to produce it, and the damage hides itself.
Expand Down
4 changes: 2 additions & 2 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__/` — 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/`
- `server/__tests__/` — Node test runner unit tests, one file per module (~680 tests / 71 files as of `c7c7d5b`; run `npm test` for the live number rather than trusting this one); frontend has Vitest files under `client/src/{components,pages,utils}/` plus Playwright 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 @@ -105,7 +105,7 @@ preview_start influencex # server on :8080 (SQLite if no DATABASE_URL)
preview_start influencex-client # Vite dev on :5173 (HMR)

# Tests
npm test # 678 server unit tests, ~1s
npm test # full server suite, ~1s (~680 tests as of c7c7d5b)
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/
Expand Down
12 changes: 7 additions & 5 deletions docs/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,14 @@ node --test server/__tests__/email-jobs.test.js
node --test --test-name-pattern "specific test" server/__tests__/*.test.js
```

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

**不要在文档里写死测试数量。** 上面这个数字只是个量级参考,带 commit 才有意义 —— 每合一个带测试的 PR 它就过期一次。#26 刚把 5 处 `234`/`377`/`656` 改成当时正确的 `678`,#28 一合并就又变成 679。**要准确数字就现跑一次**,别引用文档里的。

**`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
node --test --test-concurrency=1 server/__tests__/*.test.js # 串行,~8 秒,全绿
```

串行绿 = 并发那几个是 flake。串行也红才是真的坏了。(在 main 上空跑也能复现,与任何 PR 无关。)
Expand Down Expand Up @@ -301,7 +303,7 @@ kill %1
1. **静默错配比崩溃危险**:引用不存在的 CSS token(ErrorCard 白底白字)、不存在的 class(`className="input"` 原生控件)、错误的函数契约(`llm.embed` 永远返回 null)——三者都不报错,只是安静地渲染成错的样子或永远返回空。**"看起来像空状态"要当成 bug 线索查。**
2. **只在新库上测 = 测不到生产**:session 索引写进基础 schema,全新库没问题,已有库启动即死。563 个测试全绿也拦不住。**改 schema 必须在有数据的库上启动一次。**
3. **列可空 = 隐形数据丢失**:`workspace_id` 一直 nullable,任何忘记带它的 INSERT 都静默成功、然后从所有 scoped 读里消失。#11 用条件 NOT NULL 做了结构性根治。
4. **源码里的裸控制字节 = 文件从 review 里消失**:`usage-ledger.js` 的复合键分隔符是直接写进源码的裸 `0x00`,而不是 `\u0000` 转义。NUL 一旦落在文件前 8000 字节内,git 就把**整个文件**判成二进制 —— `git diff` 只剩 `Bin N -> M bytes`,`git blame` 彻底失效。**它不需要谁疏忽:任何一个会吞反斜杠转义的管线都能产生它,而且产生之后会把自己藏起来。** #27 修了这个实例,`.gitattributes` 的 `diff` 属性做了结构性兜底(对 NUL 文件也强制文本 diff)。**在全量 diff 里看到源码文件显示 `Bin`,立刻当 bug 查。**
4. **源码里的裸控制字节 = 文件从 review 里消失**:`usage-ledger.js` 的复合键分隔符是直接写进源码的裸 `0x00`,而不是 `\u0000` 转义。NUL 一旦落在文件前 8000 字节内,git 就把**整个文件**判成二进制 —— `git diff` 只剩 `Bin N -> M bytes`,`git blame` 彻底失效。**它不需要谁疏忽:任何一个会吞反斜杠转义的管线都能产生它,而且产生之后会把自己藏起来。** #30 修了这个实例(#27 是同一修复的重复 PR,已 close),`.gitattributes` 的 `diff` 属性做了结构性兜底(对 NUL 文件也强制文本 diff)。**在全量 diff 里看到源码文件显示 `Bin`,立刻当 bug 查。**

## 7. 与协作者的协议

Expand Down Expand Up @@ -334,7 +336,7 @@ type: `feat` / `fix` / `chore` / `docs` / `refactor`。scope: `discovery` / `out
- 小改动(i18n / 文案 / a11y) —— 不强求立刻 deploy,下次大改一起
- bug 修复 —— 立即 deploy(push + run `./deploy.sh`)
- 安全修复 —— 立即 deploy + 通知用户
- 大重构 —— 至少跑过全套 678 个服务端测试 + 客户端 build + 本地 preview 烟测,再 deploy
- 大重构 —— 至少跑过全套服务端测试(全绿,不是"大部分绿")+ 客户端 build + 本地 preview 烟测,再 deploy

### 7.4 push 前 checklist

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

---

**Last reviewed:** 2026-08-09 (post `#24`, main green: server 678 / client 82 / e2e 5).
**Last reviewed:** 2026-08-09 (post `#29` / `c7c7d5b`, main green: server 679 serialized / 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